    :root{
      --bg:#14161a;
      --panel:rgba(255,255,255,0.04);
      --panel-line:rgba(255,255,255,0.14);
      --text:#FFF;
      --text-2:#9BA3A8;
      --text-3:#7C858B;  /* 4.8:1 к фону — проходит WCAG AA (было #6E767C = 3.9:1) */
      --green:#a6e08a;
      --blue:#4bb7ff;
      --aqua:#0affff;
      --violet:#7f3aef;
      --purple:#9b7bd9;
      --orange:#FF9F00;
    }

    *{box-sizing:border-box; margin:0; padding:0;}
    /* Прокрутка "Наверх" анимируется вручную через JS (см. script.js) —
       CSS scroll-behavior здесь не нужен и мог бы конфликтовать с ней. */
    /* overflow-x:clip, не hidden — hidden превращает html/body в scroll-контейнер
       и ломает position:sticky у .top-contact-bar (сама sticky-панель отменяется,
       т.к. её ближайший скролл-контейнер — body — не является вьюпортом). clip
       даёт то же обрезание горизонтального переполнения без этого побочного эффекта. */
    html{overflow-x: clip;}
    html,body{background:var(--bg);}
    body{min-height:100vh; overflow-x: clip;}

    .stage{
      position:relative;
      width:100%;
      /* Раньше здесь был ещё один слой — radial-gradient(ellipse 900px
         500px at 85% -5%, ...) — тот же приём, что и у .hero-bg. Процент
         позиции в radial-gradient считается от РАЗМЕРА САМОГО БОКСА, а
         .stage — это весь остаток страницы (тысячи px высотой), поэтому
         "-5%" — это сотни px выше видимого верха .stage, и в кадр
         попадала только нижняя кромка большого блюра: тонкая светлая
         полоса поперёк всей ширины. Раньше её не было видно — герой
         (старый .header-wrap) начинался прямо в .stage и перекрывал эту
         кромку своим содержимым. После переноса героя за пределы .stage
         (см. .hero/.hero-spacer выше) верх .stage — это уже "Профиль" на
         пустом фоне, и полоса читалась как шов между блоками. У героя
         давно есть свой, куда более богатый (три градиента, курсор-
         реактивный) вариант этого же эффекта — .hero-bg — так что здесь
         он просто лишний, а не потерянный. */
      background:var(--bg);
      overflow:hidden;
      font-family:'Inter', sans-serif;
      color:var(--text);
      padding:48px 0 0 0;
      /* Без fill-mode (был both) — без задержки перед стартом (animation-
         delay не задан) fill-mode:backwards тут ничего и не давал, а
         fill-mode:forwards держал бы анимацию "присоединённой" к .stage
         НАВСЕГДА после конца, даже к её собственному to{}. Проверено
         экспериментально: пока Animation-объект присоединён (в т.ч. только
         за счёт fill), браузер отдаёт transform из getComputedStyle как
         РАЗРЕШЁННУЮ МАТРИЦУ, а не буквальный none, даже когда конечный
         кадр — to{transform:none}, — то есть сам keyframe тут ни при чём,
         дело именно в fill. Любая матрица, отличная от "none", делает
         элемент containing block для position:fixed потомков — тот же
         баг, что чинили у .hero (см. его комментарий про "Скролл-пин"),
         только здесь он не проявлялся, пока внутри .stage не было ни
         одного fixed-элемента. Раз .page-glow (курсорное свечение по
         всему сайту, ниже) — как раз такой потомок, анимация обязана
         полностью отвязаться от .stage по завершении: без fill-mode
         Animation сама открепляется через 0.5s, и transform возвращается
         к обычному каскадному значению — здесь оно нигде больше не
         задано, то есть initial, то есть буквально none. Видимо
         идентично: opacity/transform в момент окончания и так совпадают
         с тем, что .stage имел бы без анимации вовсе (opacity:1 — initial,
         transform:none — initial), поэтому открепление незаметно глазу. */
      animation:stageIn 0.5s ease-out;
    }
    @keyframes stageIn{
      from{opacity:0;transform:translateY(10px);}
      to{opacity:1;transform:none;}
    }
    @keyframes glowPulse{
      0%  {opacity:.75; transform:scale(1);}
      100%{opacity:1;   transform:scale(1.04);}
    }

    /* ---- Единый фон всей страницы: блики + свечение под курсором ----
       Один слой на весь сайт, а не два по разные стороны первого экрана.
       Раньше их было два — .hero-bg (три градиента, курсорный на 0.09) в
       герое и .page-glow (один градиент на 0.05) в .stage — и на границе
       фон менялся и по насыщенности, и по составу: визуально это читалось
       как "включился другой фон". Теперь набор градиентов ровно тот, что
       был у героя, и он же действует до самого низа страницы, поэтому
       переключаться нечему.
       Первый градиент — курсорный: --gx/--gy пишет скрипт (см. "Параллакс
       карточек + курсорное свечение" в script.js) на :root, поэтому слой
       видит их независимо от того, где сам лежит в DOM. Дефолт 50%/50% —
       на тач-устройствах и при prefers-reduced-motion (там слушатель
       mousemove не вешается вовсе) пятно просто стоит по центру, что и
       было раньше поведением .hero-bg в тех же условиях.
       Два остальных — статичные декоративные блики, координаты те же, что
       были у героя.
       position:fixed + z-index:1: и у .hero, и у .stage непрозрачный
       background:var(--bg), поэтому слой обязан рисоваться ПОВЕРХ их
       заливок, но ПОД контентом (у .content и внутренностей героя z-index
       от 2). Лежит на уровне body — так он не зависит ни от overflow:hidden
       у .stage, ни от того, создаёт ли она containing block. */
    .page-glow{
      position:fixed; inset:0; z-index:1; pointer-events:none;
      background:
        radial-gradient(700px 700px at var(--gx, 50%) var(--gy, 50%), rgba(75,183,255,0.09), transparent 60%),
        radial-gradient(ellipse 900px 500px at 85% -5%, rgba(75,183,255,0.10), transparent 60%),
        radial-gradient(ellipse 820px 480px at 99% 66%, rgba(75,183,255,0.06), transparent 60%);
    }

    .content{
      position:relative; z-index:2;
      display:flex; flex-direction:column;
      max-width:1440px; margin:0 auto;
      padding:0;
    }

    /* === TOP-CONTACT-BAR: липкая панель навигации над всем контентом ===
       Место под меню разделов оставлено в HTML закомментированным — пока
       его нет, .top-contact-bar__menu не рендерится вовсе.
       .top-contact-bar__inner переиспользует .content ради общего
       max-width/padding-каскада; чтобы padding-top/bottom панели не
       затирались padding-шорткодами .content на отдельных тирах, селектор
       составной (.top-contact-bar__inner.content) — две специфичности
       перебивают одну независимо от порядка в файле. */
    .top-contact-bar{
      position:sticky; top:0; z-index:95;
      width:100%;
      background:rgba(20,22,26,0.4); /* rgba-версия --bg (#14161a) — у самой переменной нет полупрозрачного варианта. Было 0.8 — вдвое темнее, чем нужно (панель читалась как полностью залитая, особенно в статике на старте, когда за ней ещё нет проскроленного контента для blur) */
      backdrop-filter:blur(12px);
      -webkit-backdrop-filter:blur(12px);
      border-bottom:1px solid var(--panel-line);
    }
    .top-contact-bar__inner.content{
      display:flex; flex-direction:row; flex-wrap:wrap;
      align-items:center; justify-content:space-between;
      gap:8px 24px;
      padding-top:8px; padding-bottom:8px;
    }
    .top-contact-bar__brand{
      display:flex; align-items:center; gap:12px;
    }
    /* Исходник 1118×1722 — без ограничения высоты рендерился в полный
       пиксельный размер. Ограничиваем только height, width:auto держит
       пропорции; object-fit:contain страхует на случай, если у файла
       когда-нибудь появятся встроенные width/height атрибуты. */
    .top-contact-bar__logo{
      height:60px; width:auto;
      display:block;
      object-fit:contain;
      flex-shrink:0;
    }
    .top-contact-bar__name{
      font-family:'Space Grotesk', sans-serif;
      font-weight:700; font-size:16px; line-height:1.2;
      text-transform:uppercase; color:var(--text);
    }
    .top-contact-bar__contacts{
      display:flex; flex-wrap:wrap; align-items:center; gap:4px 4px;
    }
    /* Кнопка в шапке компактнее своей базовой версии (.cta-contacts__button
       — 12px 20px, задумана как крупный самостоятельный CTA в футере
       "Спасибо за внимание"): в узкой строке навигации рядом с контактами
       такой же padding делал её непропорционально высокой на фоне соседних
       36–44px элементов. Только padding — размер шрифта, цвет, hover
       остаются от базового правила. */
    .top-contact-bar__contacts .cta-contacts__button{
      padding:8px 16px;
    }
    /* Не .meta-chip — по запросу это просто слова без фона/рамки в покое,
       подложка (--panel/--panel-line) появляется только по hover/focus,
       padding не меняется, чтобы не было сдвига контента. */
    .top-contact-bar__contact{
      display:flex; align-items:center; gap:6px;
      font-family:'IBM Plex Mono', monospace;
      font-size:16px; line-height:1.4; color:var(--text);
      text-decoration:none;
      padding:6px 10px; border-radius:8px;
      background:transparent; 
      transition:background .2s, border-color .2s, color .2s;
    }
    .top-contact-bar__contact:hover,
    .top-contact-bar__contact:focus-visible{
      background:var(--panel); border-color:var(--panel-line); color:var(--text);
    }
    .top-contact-bar__contact:focus-visible{
      outline:2px solid var(--green); outline-offset:2px;
    }
    .top-contact-bar__contact-icon{
      display:block; width:16px; height:16px; flex-shrink:0;
    }

    .block{margin:48px 0 32px 0;}
    .block:first-of-type{margin-top:8px;}

    h2.section{
      font-family:'Space Grotesk', sans-serif;
      font-weight:600; font-size:28px; line-height:1.2; margin-bottom:40px; margin-top: 20px;
      color:var(--text); text-transform:uppercase;
    }

    /* "Мой опыт" + общий диапазон дат (первая работа — последняя) в одну
       строку. Тот же приём nowrap+min-width:0, что у .exp-header ниже
       (там название роли/год) — если места не хватает, сжимается название
       заголовка, а диапазон дат остаётся компактным и на месте. */
    .exp-section-head{
      display:flex; flex-wrap:nowrap; align-items:baseline;
      justify-content:space-between; gap:16px;
    }
    .exp-section-head .exp-section-title{ min-width:0; }
    /* Без чипа (без border/background/padding) — тот же шрифт, что и у
       самого заголовка (наследуется: family/weight/size/line-height/
       uppercase у h2.section заданы на родителе), просто приглушённый
       цвет вместо белого. Тот же приём, что у "ЕВГЕНИЙ" в hero
       (.hero-name-2 — та же типографика, что у "БАКАЛА", только
       color:var(--text-3) вместо var(--text)) — читается как более
       "прозрачная"/второстепенная версия того же текста на любом
       разрешении, а не жёстко заданная по брейкпоинтам прозрачность. */
    .exp-section-head .exp-range{
      flex-shrink:0; color:var(--text-3);
    }

    .stats{
      display:grid; grid-template-columns:repeat(4,1fr); gap:8px;
      margin-top:12px; margin-bottom:16px;
    }
    .stat{
      background:var(--panel); border:1px solid var(--panel-line);
      border-radius:8px; padding:12px 12px;
      transition:border-color .25s, background .25s, transform .25s;
    }
    .stat:hover{
      border-color:rgba(166,224,138,0.35);
      background:rgba(166,224,138,0.06);
      transform:translateY(-2px);
    }
    .stat:hover .num{color:var(--green);}
    .stat .num{
      font-family:'IBM Plex Mono', monospace; font-weight:700; font-size:28px;
      color:var(--green); line-height:1.2; transition:color .25s;
    }
    .stat .lbl{margin-top:4px; font-size:16px; color:var(--text-2); line-height:1.4;}

    .exp-list{display:flex; flex-direction:column; gap:12px;}
    .exp-row{
      padding:20px;
      border:1px solid rgba(255,255,255,0.16);
      transition:background .3s, border-color .3s;
      border-radius:8px;
      position: relative;
      overflow: hidden;
    }
    .exp-row:hover{
      background:rgba(255,255,255,0.015);
      border-color:rgba(255,255,255,0.13);
    }
    .exp-row:has(.spoiler-body.open){
      border-color: rgba(166,224,138,0.35);
    }
    .exp-row:has(.spoiler-body.open)::before{
      content:'';
      position:absolute; inset:0;
      /* Та же цветовая гамма, что у .photo-glow (бело-голубой → синий → фиолетовый),
         но на порядок слабее по opacity — там это яркое свечение под фото,
         здесь только слегка заметный акцент в углу открытой карточки.
         Радиус вдвое меньше исходного (0/35/55/70 → 0/17/27/35) — на весь
         размах он перекрывал почти весь блок карточки. */
      background:
        radial-gradient(circle at 0% 0%,
          rgba(210,225,255,0.10) 0%,
          rgba(75,183,255,0.06) 17%,
          rgba(120,100,200,0.05) 27%,
          transparent 35%);
      pointer-events:none;
      z-index:-1;
    }

    .exp-top{
      display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap;
    }
    .exp-info{justify-content:flex-start; gap:12px;}
    .exp-company{
      display:inline-flex; align-items:center; gap:8px;
      font-weight:600; font-size:21px; line-height:1.2; transition:color .2s;
    }
    /* Тот же визуальный рецепт, что у .lvl-wrapper.show .dot.active
       ("Профессиональные навыки"), но без gate-класса .show — здесь это
       постоянный маркер, а не reveal-анимация по скроллу. .dot базово
       задаёт только форму/transition (opacity:0.4, приглушённый фон) —
       здесь эти значения намеренно перебиваются. */
    .exp-company .dot.active{
      opacity:1; transform:scale(1); flex-shrink:0;
      background:var(--green);
      box-shadow:0 0 6px rgba(166,224,138,0.5);
    }

    .exp-years{
      font-family:'IBM Plex Mono', monospace; font-size:16px; line-height:1.4; color:var(--text-2);
      border:1px solid var(--panel-line); border-radius:8px; padding:8px 12px;
      white-space:nowrap; transition:border-color .2s, color .2s;
    }
    .exp-row:hover .exp-years{border-color:var(--green); color:var(--green);}
    .exp-niche{font-size:16px; line-height:1.4; color:var(--green); margin-top:8px; font-weight:400;}
    .exp-sub{font-size:18px; color:var(--text-2); margin:12px 0 12px 0; line-height:1.45;}

    .spoiler-toggle{
      display:inline-flex; align-items:center; gap:4px;
      font-family:'IBM Plex Mono', monospace;
      font-size:16px; line-height:1.4; color:var(--blue);
      margin-top:12px; cursor:pointer;
      border:none; background:none;
      /* минимальная область нажатия 24px по высоте (WCAG 2.2 Target Size) */
      padding:4px 0; min-height:24px;
      transition:color .2s, gap .2s; user-select:none;
    }
    .spoiler-toggle:focus-visible{
      outline:2px solid var(--blue); outline-offset:2px; border-radius:4px;
    }
    .spoiler-toggle:hover{color:var(--blue);}
    .spoiler-toggle svg{
      width:14px; height:14px;
      transition:transform .3s ease; flex-shrink:0;
    }
    .spoiler-toggle.open svg{transform:rotate(180deg);}
    .spoiler-toggle.open{color:var(--blue);}

    .exp-list-toggle{
      display:flex;
      width:100%;
      justify-content:center;
      padding: 16px;
      margin-bottom: 20px;
      border-radius: 8px;
      font-size: 16px;
      line-height: 1.4;
      border: 1px solid var(--panel-line);
      background: rgba(255,255,255,0.02);
      transition: background .2s, border-color .2s, color .2s;
    }
    .exp-list-toggle:hover{
      background: rgba(75,183,255,0.06);
      border-color: rgba(75,183,255,0.35);
      color: var(--blue);
    }

    .spoiler-body{
      max-height:0; overflow:hidden;
      transition:max-height .4s ease, opacity .3s ease; opacity:0;
    }
    /* Раньше было 400px — при длинном списке (особенно с переносом строк
       на узких экранах) контент выше 400px просто обрезался overflow:hidden.
       Ставим заведомо большой потолок вместо точной высоты по контенту —
       у этой техники анимации max-height нет способа анимировать к "auto" */
    .spoiler-body.open{max-height:3000px; opacity:1;}
    .spoiler-body-wide.open{max-height:5000px; opacity:1;}

    .exp-list .spoiler-body .spoiler-inner {
      border-top: 1px solid var(--panel-line);
      margin-top: 12px;
      padding-top: 16px;
    }

    .spoiler-list-container{
      position: relative;
      padding-top: 24px; 
      margin-top: 20px;
      border-top: 1px dashed rgba(255,255,255,0.15);
    }
    .spoiler-list-container::before {
      content: 'Предыдущий опыт';
      position: absolute;
      top: -10px;
      left: 50%;
      transform: translateX(-50%);
      background: var(--bg);
      padding: 0 16px;
      font-family: 'IBM Plex Mono', monospace;
      font-size: 14px;
      line-height: 1.4;
      color: var(--text-3);
      text-transform: uppercase;
      letter-spacing: .05em;
      white-space: nowrap;
    }

    .spoiler-inner{padding-top:12px; font-size:16px; color:var(--text-2); line-height:1.45;}
    .spoiler-inner ul{list-style:none; padding:0; display:flex; flex-direction:column; gap:0;}

    .exp-list .spoiler-inner ul li{
      position:relative;
      padding-left:20px;
      padding-bottom:12px;
      margin-bottom:12px;
      border-bottom: 1px solid rgba(255, 255, 255, 0.12);
    }
    .exp-list .spoiler-inner ul li:last-child{
      border-bottom: none;
      margin-bottom: 0;
      padding-bottom: 0;
    }
    .exp-list .spoiler-inner ul li::before{
      content:'';
      position:absolute;
      left:0; top:0.3em;
      width:14px; height:14px;
      background-color:var(--green);
      -webkit-mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 6 15 12 9 18'/%3E%3C/svg%3E");
      mask-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 6 15 12 9 18'/%3E%3C/svg%3E");
      -webkit-mask-repeat:no-repeat;
      mask-repeat:no-repeat;
      -webkit-mask-size:contain;
      mask-size:contain;
    }

    .exp-list .spoiler-inner .stack-row {
      display: grid;
      grid-template-columns: 400px 1fr;
      grid-template-areas: "label list";
      gap: 16px;
    }
    .exp-list .spoiler-inner .stack-label {
      grid-area: label;
      max-width: 100%;
      line-height: 1.4;
      color: var(--green);
    }
    .exp-list .spoiler-inner ul {
      grid-area: list;
      min-width: min(300px, 100%);
    }

    /* Ключевые показатели и описание направления теперь живут в закрытой
       карточке — видны сразу, до раскрытия "Подробнее", а не только внутри
       спойлера "Достижения и результаты" */
    .exp-list .stack-proof {
      margin-top: 16px;
      padding-top: 16px;
      border-top: 1px solid var(--panel-line);
      display: flex;
      flex-direction: column;
      gap: 12px;
    }
    /* Описание направления/продуктов — отдельно от жёстких метрик:
       стиль взят из .cta-contacts__label (моноширинный, приглушённый,
       letter-spacing .05em), чтобы не спорить визуально с зелёными
       цифрами пруфа ниже. Живёт внутри спойлера (видно только по клику
       "Подробнее"), а не в закрытой карточке — там остаются только метрики */
    .exp-list .proof-scope {
      font-family: 'IBM Plex Mono', monospace;
      font-size: 14px;
      color: var(--text-3);
      letter-spacing: .05em;
      line-height: 1.4;
    }
    .exp-list .spoiler-inner > .proof-scope {
      margin-top: 20px;
      margin-bottom: 16px;
    }
    .exp-list .proof-caption {
      font-family: 'IBM Plex Mono', monospace;
      font-size: 16px;
      line-height: 1.4;
      margin-bottom: 12px;
      color: var(--text-3);
      letter-spacing: .05em;
    }
    .exp-list .proof-chips {
      display: flex;
      flex-wrap: wrap;
      margin-bottom: 4px;
    }
    /* Без тега-бейджа: значение крупным моноширинным шрифтом + мелкая
       подпись снизу (как .stat в "Профиле"), пункты разделяет тонкая
       вертикальная черта — ни фона, ни обводки, ни скругления в пилюлю */
    .exp-list .proof-chip {
      display: flex;
      flex-direction: column;
      gap: 4px;
      padding: 0 20px;
      border-left: 2px solid rgba(166,224,138,0.35);
    }
    .exp-list .proof-chip:first-child {
      padding-left: 0;
      border-left: none;
    }
    .exp-list .proof-chip .proof-num {
      font-size: 21px;
      font-weight: 700;
      color: var(--green);
      line-height: 1.2;
      white-space: nowrap;
    }
    .exp-list .proof-chip .proof-lbl {
      font-size: 14px;
      color: var(--text-3);
      line-height: 1.4;
      white-space: nowrap;
    }

    .body-grid{display:grid; grid-template-columns:1.05fr 0.95fr; gap:32px;}

    .bar-item{margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--panel-line);}
    .bar-top{
      display:flex; justify-content:space-between; align-items:center;
      font-size:21px; line-height:1.2; gap:16px;
    }
    .lvl-wrapper{
      display:flex; align-items:center; flex-shrink:0;
    }
    .dot-bar{
      display:flex; gap:12px; align-items:center;
    }
    .dot{
      width:10px; height:10px; border-radius:50%;
      background:var(--panel-line);
      opacity:0.4; transform:scale(0.8);
      transition:opacity .3s ease, background .3s ease, box-shadow .3s ease, transform .3s ease;
    }
    .lvl-wrapper.show .dot{opacity:1; transform:scale(1);}
    .lvl-wrapper.show .dot.active{
      background: var(--green);
      box-shadow: 0 0 6px rgba(166,224,138,0.5);
    }

    .tag-row{display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; margin-bottom:16px;}
    .tag{
      font-family:'IBM Plex Mono', monospace;
      font-size:18px; padding:8px 12px; border-radius:16px;
      background:rgba(155,123,217,0.16); border:1px solid rgba(155,123,217,0.35);
      color:var(--purple); line-height:1.4;
      transition:background .2s, border-color .2s, transform .2s, box-shadow .2s;
      cursor:default;
    }
    .tag:hover{
      background:rgba(155,123,217,0.28);
      border-color:rgba(155,123,217,0.55);
      transform:translateY(-1px);
      box-shadow:0 2px 8px rgba(155,123,217,0.15);
    }
    .tag.niche{background:rgba(166,224,138,0.14); border:1px solid rgba(166,224,138,0.4); color:var(--green);}
    .tag.niche:hover{
      background:rgba(166,224,138,0.26);
      border-color:rgba(166,224,138,0.6);
      box-shadow:0 2px 8px rgba(166,224,138,0.15);
    }
    .tag.soft{color:var(--text);}

    .roles-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 16px;
    }
    .role-card {
      background: var(--panel);
      border: 1px solid var(--panel-line);
      border-radius: 12px;
      padding: 24px;
      display: flex;
      flex-direction: column;
      gap: 20px;
      text-decoration: none;
      color: var(--text);
      transition: border-color .25s, background .25s, transform .25s, box-shadow .25s;
      position: relative;
      overflow: hidden;
    }
    .role-card::before {
      content: "";
      position: absolute;
      left: 0; top: 0; bottom: 0;
      width: 3px;
      background: var(--green);
      transition: width .25s;
    }
    .role-card:hover {
      transform: translateY(-3px);
      background: rgba(166,224,138,0.03);
      border-color: rgba(166,224,138,0.4);
      box-shadow: 0 8px 20px rgba(166,224,138,0.08);
    }
    .role-card:hover::before {
      width: 5px;
    }
    .role-card-top {
      display: flex;
      align-items: flex-start;
      gap: 16px;
    }
    .role-icon-wrap {
      width: 44px; height: 44px;
      border-radius: 8px;
      background: rgba(166,224,138,0.1);
      border: 1px solid rgba(166,224,138,0.3);
      display: flex; align-items: center; justify-content: center;
      color: var(--green);
      flex-shrink: 0;
    }
    .role-icon-wrap svg { width: 22px; height: 22px; }
    .role-text { flex: 1; }
    .role-title {
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 600; font-size: 21px;
      line-height: 1.2;
      color: var(--text);
      margin-bottom: 8px;
    }
    .role-subtitle {
      font-family: 'IBM Plex Mono', monospace;
      font-size: 16px;
      line-height: 1.45;
      color: var(--text-2);
    }
    .role-footer {
      margin-top: auto;
      padding-top: 16px;
      border-top: 1px solid var(--panel-line);
      font-family: 'IBM Plex Mono', monospace;
      font-size: 16px;
      line-height: 1.4;
      color: var(--green);
      display: flex;
      align-items: center;
      justify-content: flex-end;
      gap: 8px;
    }
    .role-footer svg {
      width: 16px; height: 16px;
      transition: transform .2s;
    }
    .role-card:hover .role-footer svg {
      transform: translate(2px,-2px) scale(1.1);
    }

    .stack-panel{
      background:var(--panel);
      border:1px solid var(--panel-line);
      border-radius:12px;
      padding: 12px 20px;
      margin-bottom: 20px;
    }
    .stack-row{
      display:flex;
      gap:16px;
      padding:12px 0;
      border-bottom:1px solid var(--panel-line);
      align-items:baseline;
      flex-wrap:wrap;
    }
    .stack-row:last-child{border-bottom:none;}
    .stack-label{
      font-size:16px;
      line-height:1.4;
      color:var(--text-2);
      text-transform:uppercase;
      letter-spacing:.05em;
      width:400px;
      max-width:100%;   /* иначе на узких экранах фикс. 400px выходит за пределы карточки */
      flex-shrink:0;
    }
    .stack-items{
      font-size:16px;
      color:var(--text);
      line-height:1.45;
      flex:1;
      min-width:min(300px, 100%);
    }
    /* На планшете и уже — метка и список в колонку, иначе строка ломается */
    @media (max-width:899px){
      .stack-row{ flex-direction:column; gap:8px; }
      .stack-label{ width:100%; }
    
  }

    .tool4-block{
      position: relative;
      width: 100%;
      margin: 48px 0 0 0;
      box-sizing: border-box;
    }

    .tool4-header{
      display:flex; 
      justify-content:space-between; 
      align-items:center;
      margin: 0 0 40px 0;
      padding-bottom: 12px;
      border-bottom: 1px solid var(--panel-line);
      box-sizing: border-box;
    }
    .tool4-subtitle{
      font-size:18px; line-height:1.45; color:var(--text-2);
      max-width:700px;
      margin:0 0 32px 0;
    }
    .tool4-header .section {
      margin-bottom: 0;
    }
    .tool4-nav{ display:flex; gap:12px; }

    .tool4-nav-btn{
      width: 40px; height: 40px;
      border-radius: 8px;
      background: var(--panel);
      border: 1px solid var(--panel-line);
      color: var(--text-2);
      display: flex; align-items: center; justify-content: center;
      cursor: pointer;
      transition: all 0.2s ease;
      flex-shrink: 0;
    }
    .tool4-nav-btn:hover{
      border-color: rgba(75,183,255,0.5);
      color: var(--blue);
      box-shadow: 0 0 12px rgba(75,183,255,0.2);
    }
    .tool4-nav-btn svg{ width: 18px; height: 18px; }

    .tool4-row{
      display: flex !important;
      flex-wrap: nowrap !important;
      overflow-x: auto !important;
      gap: 16px;
      padding-top: 4px;
      padding-bottom: 20px;
      width: 100vw;
      margin-left: calc(50% - 50vw);
      padding-left: calc(50vw - 50%);
      padding-right: calc(50vw - 50%);
      align-items: flex-start;
      -webkit-overflow-scrolling: touch;
      /* Было touch-action:pan-x (потом pan-x pan-y) — на части мобильных
         браузеров даже комбинация двух значений не отпускала вертикальный
         скролл страницы пальцем поверх карточек. touch-action тут вообще
         не нужен: overflow-x:auto у самого ряда — это ровно тот случай,
         который браузер по умолчанию (auto) прекрасно разруливает сам,
         как с любой другой горизонтально скроллящейся областью на странице. */
      touch-action: auto;
      scrollbar-width: none;
      -ms-overflow-style: none;
      cursor: grab;
    }

    .tool4-row::-webkit-scrollbar { display: none; }
    .tool4-row.dragging{ cursor: grabbing; scroll-snap-type: none; }
    .tool4-row.dragging .tool4-card{ pointer-events: none; }

    .tool4-card{
      flex: 0 0 470px !important;
      background: var(--panel);
      border: 1px solid var(--panel-line);
      border-radius: 12px;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      transition: border-color .3s, background .3s, transform .3s, box-shadow .3s;
      cursor: pointer;
    }

    .tool4-card.tapped .tool4-extra{
      /* Было 250px — под однострочный текст. Список из пунктов с отступами
         и разделителями занимает заметно больше места по высоте, поэтому
         потолок с большим запасом, как и у "Достижения и результаты" */
      max-height: 1000px;
      opacity: 1;
      margin-top: 12px;
      padding-top: 12px;
      border-top: 1px solid var(--panel-line);
    }

    .tool4-card.tapped{transform: translateY(-3px);}
    .tool4-card.c-green.tapped{border-color: rgba(166,224,138,0.4); box-shadow: 0 8px 20px rgba(166,224,138,0.08); background: rgba(166,224,138,0.02);}
    .tool4-card.c-blue.tapped{border-color: rgba(75,183,255,0.4); box-shadow: 0 8px 20px rgba(75,183,255,0.08); background: rgba(75,183,255,0.02);}
    .tool4-card.c-purple.tapped{border-color: rgba(155,123,217,0.45); box-shadow: 0 8px 20px rgba(155,123,217,0.08); background: rgba(155,123,217,0.02);}

    .tool4-card.c-green.tapped .tool4-icon-wrap{background: rgba(166,224,138,0.1); border-color: rgba(166,224,138,0.3); box-shadow: 0 0 12px rgba(166,224,138,0.15);}
    .tool4-card.c-blue.tapped .tool4-icon-wrap{background: rgba(75,183,255,0.1); border-color: rgba(75,183,255,0.3); box-shadow: 0 0 12px rgba(75,183,255,0.15);}
    .tool4-card.c-purple.tapped .tool4-icon-wrap{background: rgba(155,123,217,0.1); border-color: rgba(155,123,217,0.35); box-shadow: 0 0 12px rgba(155,123,217,0.15);}

    .tool4-card.tapped .tool4-img{ filter: grayscale(0.1) brightness(0.85) contrast(1.15); }

    .tool4-img{
      width: 100%; height: 120px;
      object-fit: cover;
      filter: grayscale(0.6) brightness(0.65) contrast(1.1);
      border-bottom: 1px solid var(--panel-line);
      transition: filter .3s ease;
    }
    .tool4-icon-wrap{
      width: 44px; height: 44px;
      border-radius: 8px;
      display: flex; align-items: center; justify-content: center;
      margin: -22px 0 0 20px;
      position: relative; z-index: 2;
      background: var(--bg);
      border: 1px solid var(--panel-line);
      transition: background .3s, border-color .3s, box-shadow .3s;
    }
    .tool4-icon-wrap svg{ width: 22px; height: 22px; }

    .tool4-content{
      padding: 12px 20px 20px;
      flex: 1;
      display: flex;
      flex-direction: column;
      position: relative;
    }

    .tool4-card.c-green .tool4-icon-wrap svg{color: var(--green);}
    .tool4-card.c-blue .tool4-icon-wrap svg{color: var(--blue);}
    .tool4-card.c-purple .tool4-icon-wrap svg{color: var(--purple);}

    .tool4-card:focus-visible{
      outline: 2px solid var(--green);
      outline-offset: 3px;
    }
    .tool4-card:hover{transform: translateY(-3px);}
    .tool4-card.c-green:hover{border-color: rgba(166,224,138,0.4); box-shadow: 0 8px 20px rgba(166,224,138,0.08); background: rgba(166,224,138,0.02);}
    .tool4-card.c-blue:hover{border-color: rgba(75,183,255,0.4); box-shadow: 0 8px 20px rgba(75,183,255,0.08); background: rgba(75,183,255,0.02);}
    .tool4-card.c-purple:hover{border-color: rgba(155,123,217,0.45); box-shadow: 0 8px 20px rgba(155,123,217,0.08); background: rgba(155,123,217,0.02);}

    .tool4-card.c-green:hover .tool4-icon-wrap{background: rgba(166,224,138,0.1); border-color: rgba(166,224,138,0.3); box-shadow: 0 0 12px rgba(166,224,138,0.15);}
    .tool4-card.c-blue:hover .tool4-icon-wrap{background: rgba(75,183,255,0.1); border-color: rgba(75,183,255,0.3); box-shadow: 0 0 12px rgba(75,183,255,0.15);}
    .tool4-card.c-purple:hover .tool4-icon-wrap{background: rgba(155,123,217,0.1); border-color: rgba(155,123,217,0.35); box-shadow: 0 0 12px rgba(155,123,217,0.15);}

    .tool4-title-row{
      display: flex;
      align-items: flex-start;
      justify-content: space-between;
      gap: 12px;
      margin: 12px 0 8px;
    }
    .tool4-title{
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 600; font-size: 21px;
      line-height: 1.2; margin: 0;
    }
    .tool4-card.c-green .tool4-title{color: var(--green);}
    .tool4-card.c-blue .tool4-title{color: var(--blue);}
    .tool4-card.c-purple .tool4-title{color: var(--purple);}

    .tool4-chevron{
      width: 16px; height: 16px;
      flex-shrink: 0;
      margin-top: 4px;
      color: var(--text-2);
      transition: transform .3s ease, color .3s ease;
    }
    .tool4-card.tapped .tool4-chevron{
      transform: rotate(180deg);
      color: var(--text);
    }

    .tool4-short{
      font-size: 16px; color: var(--text-2); line-height: 1.45;
    }

    .tool4-extra{
      max-height: 0;
      opacity: 0;
      overflow: hidden;
      font-size: 16px;
      color: var(--text-2);
      line-height: 1.45;
      transition: max-height .4s ease, opacity .3s ease, margin-top .3s ease, padding-top .3s ease;
      border-top: 1px solid transparent;
    }
    /* Список внутри раскрытой карточки — тот же паттерн, что у "Достижения
       и результаты" в Опыте: без точек-маркеров, разделитель снизу вместо
       них, последний пункт без отступа/линии */
    .tool4-extra ul{
      list-style: none;
      display: flex;
      flex-direction: column;
      gap: 0;
    }
    .tool4-extra ul li{
      padding-bottom: 8px;
      margin-bottom: 8px;
      border-bottom: 1px solid rgba(255,255,255,0.06);
    }
    .tool4-extra ul li:last-child{
      border-bottom: none;
      margin-bottom: 0;
      padding-bottom: 0;
    }
    /* Смысловая часть пункта ("что делаю для бизнеса") ярче обычного
       текста карточки — так можно просканировать только её, а конкретные
       инструменты после двоеточия читать по необходимости */
    .tool4-extra ul li strong{
      color: var(--text);
      font-weight: 600;
    }

    /* === КОНТАКТЫ === */
    .footer-contacts {
      position: relative;
      display: flex;
      flex-direction: column;
      overflow: visible;
      /* Тот же верхний отступ, что у .stage в начале страницы */
      margin: 48px 0 0 0;
      min-height: 450px;
    }

    .contacts__qr {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 8px;
      text-decoration: none;
      flex: 0 0 auto;
    }

    .contacts__qr-code {
      width: 80px; height: 80px;
      background: #fff;
      padding: 4px;
      border-radius: 8px;
    }

    .contacts__qr-code svg { width: 100%; height: 100%; display: block; }

    .contacts__qr-label {
      font-family: 'IBM Plex Mono', monospace;
      font-size: 16px;
      line-height: 1.4;
      color: var(--text-3);
    }

    /* === ВТОРОЙ CTA-БЛОК КОНТАКТОВ === */
    .cta-contacts{
      margin: 20px 0 0 0;
      background: var(--panel);
      border: 1px solid var(--panel-line);
      border-radius: 16px;
      overflow: hidden;
    }
    .cta-contacts__top{
      display: grid;
      grid-template-columns: 1fr 1fr;
    }
    .cta-contacts__photo-wrap{
      position: relative;
      min-height: 320px;
    }
    .cta-contacts__glow{
      position: absolute;
      inset: -20px;
      background: radial-gradient(
        circle closest-corner at 50% 30%,
        rgba(210,225,255,0.5) 0%,
        rgba(75,183,255,0.18) 40%,
        rgba(120,100,200,0.35) 65%,
        transparent 75%
      );
      filter: blur(40px);
      z-index: 0;
      pointer-events: none;
      animation: glowPulse 6s ease-in-out infinite alternate;
    }
    .cta-contacts__photo{
      position: absolute;
      inset: 0;
      z-index: 1;
      width: 100%;
      height: 100%;
      object-fit: cover;
      object-position: top center;
      filter: grayscale(.4) contrast(.95) brightness(1);
      -webkit-mask-image: linear-gradient(180deg, black 30%, transparent 100%), linear-gradient(to bottom, black 40%, transparent 70%);
      mask-image: linear-gradient(180deg, black 60%, transparent 100%), linear-gradient(to bottom, black 35%, transparent 70%);
    }
    .cta-contacts__body{
      padding: 40px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      gap: 24px;
    }
    .cta-contacts__blurb{
      font-size: 18px;
      line-height: 1.45;
      color: var(--text-2);
    }
    .cta-contacts__actions{
      display: flex;
      align-items: center;
      gap: 24px;
      flex-wrap: wrap;
    }
    .cta-contacts__button{
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-family: 'IBM Plex Mono', monospace;
      font-weight: 600;
      font-size: 16px;
      line-height: 1.4;
      color: var(--bg);
      background: var(--green);
      padding: 12px 20px;
      border-radius: 8px;
      text-decoration: none;
      transition: transform .2s, box-shadow .2s;
    }
    .cta-contacts__button:hover{
      transform: translateY(-1px);
      box-shadow: 0 6px 16px rgba(166,224,138,0.25);
    }
    .cta-contacts__row{
      display: flex;
      flex-wrap: wrap;
      border-top: 1px solid var(--panel-line);
    }
    .cta-contacts__tile{
      flex: 1 1 200px;
      padding: 24px 32px;
      border-left: 1px solid var(--panel-line);
      text-decoration: none;
      display: flex;
      flex-direction: column;
      justify-content: center;
      transition: background .2s;
    }
    .cta-contacts__tile:first-child{ border-left: none; }
    a.cta-contacts__tile:hover{ background: rgba(255,255,255,0.03); }
    .cta-contacts__name{
      font-family: 'Space Grotesk', sans-serif;
      font-weight: 700;
      font-size: 18px;
      line-height: 1.2;
      color: var(--text);
    }
    .cta-contacts__role{
      font-family: 'IBM Plex Mono', monospace;
      font-size: 16px;
      line-height: 1.4;
      color: var(--green);
      margin-top: 4px;
    }
    .cta-contacts__label{
      font-family: 'IBM Plex Mono', monospace;
      font-size: 14px;
      line-height: 1.4;
      letter-spacing: .05em;
      color: var(--text-3);
    }
    .cta-contacts__value{
      font-family: 'IBM Plex Mono', monospace;
      font-size: 18px;
      line-height: 1.45;
      color: var(--text);
      margin-top: 4px;
      word-break: break-word;
    }

    /* === БЛОК С ПАДАЮЩИМИ ТЕГАМИ (matter.js) ===
       Базовые правила ОБЯЗАТЕЛЬНО должны стоять выше медиазапросов: у них
       одинаковая специфичность, и раньше этот блок лежал в конце файла —
       поэтому все адаптивные значения ниже перебивались базовыми и высота
       с размером шрифта оставались 450px/18px на любом экране. */
    .mjs-app-wrapper {
      position: relative;
      top: 0;
      left: 0;
      margin: 0;
      padding: 0;
      background: var(--bg);
      color: transparent;
      font-family: 'IBM Plex Mono', monospace;
      width: 100%;
      height: 450px;
      overflow: hidden;
      /* вертикальный скролл страницы пальцем по блоку должен работать;
         горизонтальные жесты остаются перетаскиванию тегов */
      touch-action: pan-y;
    }

    .mjs-app-wrapper * {
      user-select: none;
    }

    .mjs-app-wrapper canvas{
      position: absolute;
      top: 0;
      left: 0;
    }

    .mjs-word {
      position: absolute;
      cursor: grab;
      font-size: 21px;
      color: var(--green); /* Зеленый текст */
      touch-action: none;
    }

    .mjs-word.mjs-highlighted {
      font-weight: bold;
      color: var(--purple); /* Фиолетовый текст для выделения */
    }

    /* Теги, не поместившиеся в контейнер по плотности, гасит JS */
    .mjs-word.mjs-dropped {
      display: none;
    }

    .mjs-main {
      position: relative;
      z-index: 2;
    }

    @media (min-width:1920px){
      /* Вся типографика этого тира сдвинута на один шаг вверх по шкале
         (12→14→16→18→21→24→28...→56), а не только "залатан" пол 12px —
         иначе на таком широком экране 14px-пол упирался бы в соседний
         "обычный" размер и разница между уровнями стиралась бы. */
      .content{max-width:1640px; padding:0;}
      .stage{padding-top:48px; padding-bottom:20px;}
      .top-contact-bar__logo{height:60px;}
      .top-contact-bar__name{font-size:18px;}
      .top-contact-bar__contact{font-size:16px;}
      h2.section{font-size:28px; margin-top: 20px;}
      .stat .num{font-size:40px;}
      .stat .lbl{font-size:18px;}
      .stat{padding:10px 12px;}
      .exp-company{font-size:28px;}
      .exp-years{font-size:21px;}
      .exp-niche{font-size:18px;}
      .exp-sub{font-size:21px;}
      .tool4-subtitle{font-size:21px;}
      .spoiler-toggle{font-size:18px;}
      .exp-list-toggle{font-size:18px;}
      .spoiler-list-container::before{font-size:18px;}
      .spoiler-inner{font-size:18px;}
      .exp-list .proof-caption{font-size:18px;}
      .exp-list .proof-chip .proof-num{font-size:28px;}
      .exp-list .proof-chip .proof-lbl{font-size:18px;}
      .bar-top{font-size:24px;}
      .tag{font-size:21px; padding:8px 18px;}
      .role-title { font-size: 24px; }
      .role-subtitle{font-size:21px;}
      .role-footer{font-size:18px;}
      .stack-label{font-size:16px; width: 500px;}
      .stack-items{font-size:18px;}
      .tool4-title{font-size:21px;}
      .tool4-short{font-size:18px;}
      .tool4-extra{font-size:18px;}
      .contacts__qr-label{font-size:18px;}
      .cta-contacts__blurb{font-size:21px;}
      .cta-contacts__button{font-size:18px;}
      .cta-contacts__name{font-size:24px;}
      .cta-contacts__role{font-size:21px;}
      .cta-contacts__label{font-size:18px;}
      .cta-contacts__value{font-size:21px;}
      .resume-actions__btn{font-size:18px;}
      .block{margin:48px 0 32px 0;}
      .role-card { padding: 32px; gap: 24px; }
      .role-icon-wrap { width: 48px; height: 48px; }
      .role-icon-wrap svg { width: 24px; height: 24px; }
      .mjs-app-wrapper { height: 440px; }
      .mjs-word { font-size: 24px; }
      .footer-contacts { min-height: 520px; margin-top: 48px; }
    
  }

    @media (max-width:1439px){
      .content{max-width:1080px; padding:0;}
      .stage{padding-top:32px;}
      .footer-contacts{margin-top:32px;}
      h2.section{font-size:24px; margin-bottom:20px; margin-top:16px;}
      .stat .num{font-size:24px;}
      .stat .lbl{font-size:14px;}
      .stat{padding:8px 8px;}
      .exp-company{font-size:18px;}
      .exp-years{font-size:14px;}
      .exp-sub{font-size:18px;}
      .exp-niche{font-size:14px;}
      .bar-top{font-size:18px;}
      .tag{font-size:16px; padding:8px 12px;}
      .role-title{font-size:18px;}
      .exp-list .proof-scope{font-size:14px;}
      .exp-list .proof-caption{font-size:14px;}
      .exp-list .proof-chip .proof-num{font-size:18px;}
      .contacts__qr-label{font-size:14px;}
      .cta-contacts__name{font-size:16px;}
      .cta-contacts__label{font-size:14px;}
      .cta-contacts__value{font-size:16px;}
      .mjs-word{font-size:18px;}
      .body-grid{gap:32px;}
      .block{margin:48px 0 24px 0;}
      .tool4-card { flex: 0 0 360px !important; }
      .tool4-title { font-size: 18px; }
      .tool4-short { font-size: 16px; }
      .tool4-extra { font-size: 16px; }
      .tool4-content { padding: 12px 20px 20px; }
    
  }

    @media (max-width:1219px){
      .content{max-width:980px; padding:0;}
      .stage{padding-top:32px;}
      h2.section{font-size:21px; margin-bottom:20px; margin-top:12px;}
      .stats{gap:8px;}
      .stat .num{font-size:21px;}
      .stat .lbl{font-size:14px; margin-top:4px;}
      .stat{padding:4px 8px; border-radius:8px;}
      .exp-company{font-size:16px;}
      .exp-years{font-size:14px; padding:4px 8px;}
      .exp-sub{font-size:16px; margin-top:8px;}
      .exp-niche{font-size:14px; margin-top:8px;}
      .exp-row{padding:16px;}
      .bar-top{font-size:16px; margin-bottom:8px;}
      .bar-item{margin-bottom: 8px; padding-bottom: 8px;}
      .tag{font-size:14px; padding:4px 8px; border-radius:16px;}
      .tag-row{gap:8px; margin-top:8px; margin-bottom:12px;}
      .exp-list .proof-scope{font-size:14px;}
      .exp-list .proof-caption{font-size:14px;}
      .exp-list .proof-chip .proof-num{font-size:16px;}
      .cta-contacts__name{font-size:16px;}
      .cta-contacts__label{font-size:14px;}
      .cta-contacts__value{font-size:16px;}
      .mjs-word{font-size:16px;}
      .body-grid{gap:24px;}
      .block{margin:40px 0 24px 0;}
      .spoiler-toggle{font-size:16px;}
      .spoiler-inner{font-size:16px;}
      .tool4-card { flex: 0 0 320px !important; }
      .tool4-title { font-size: 16px; }
      .tool4-short { font-size: 16px; }
      .tool4-extra { font-size: 16px; }
      .tool4-content { padding: 12px 16px 16px;}
      .tool4-img { height: 110px; }
      .tool4-header { margin-bottom: 16px; padding-bottom: 8px; }
      .role-card { padding: 20px; gap: 16px; }
      .role-title { font-size: 16px; }

      /* На 1219px и ниже заголовок и список встают в одну колонку */
      .exp-list .spoiler-inner .stack-row {
        grid-template-columns: 1fr;
        grid-template-areas:
          "label"
          "list";
        gap: 12px;
      }
      .exp-list .spoiler-inner .stack-label {
        width: 100%;
      }
      .exp-list .spoiler-inner ul {
        width: 100%;
      }

      /* Контакты: компактно в одну строку, без переноса */
      .footer-contacts{min-height:360px; margin-top:32px;}
      .contacts__qr-code{width:50px; height:50px;}
      .contacts__qr-label{font-size:14px;}
      .contacts__qr{gap:8px;}

      .cta-contacts__photo-wrap{min-height:280px;}
      .cta-contacts__body{padding:32px;}
      .cta-contacts__blurb{font-size:16px;}
      .cta-contacts__tile{padding:20px 24px;}
    
  }

    @media (max-width:1079px){
      .content{max-width:none; padding:0 20px;}
      .stage{padding-top:24px;}
      .top-contact-bar__logo{height:40px;}
      .top-contact-bar__name{font-size:14px;}
      .top-contact-bar__contact{padding:4px 8px;}
      h2.section{font-size:16px; margin-bottom:16px; margin-top:12px;}
      .stats{grid-template-columns:repeat(4,1fr); gap:4px;}
      .stat .num{font-size:21px;}
      .stat .lbl{font-size:14px; margin-top:8px;}
      .stat{padding:8px 8px; border-radius:8px;}
      .exp-company{font-size:14px;}
      .exp-years{font-size:12px; padding:4px 8px;}
      .exp-sub{font-size:14px; margin-top:8px;}
      .exp-niche{font-size:12px; margin-top:4px;}
      .exp-row{padding:16px;}
      .body-grid{gap:20px;}
      .bar-top{font-size:14px; margin-bottom:8px;}
      .bar-item{margin-bottom:12px;}
      .tag{font-size:14px; padding:4px 8px; border-radius:12px;}
      .tag-row{gap:6px; margin-top:8px; margin-bottom:12px;}
      .block{margin:32px 0 20px 0;}
      .spoiler-toggle{font-size:14px; margin-top:8px;}
      .spoiler-inner{font-size:12px; padding-top:8px;}
      .tool4-card { flex: 0 0 300px !important; }
      .tool4-title { font-size: 16px; }
      .tool4-short { font-size: 12px; }
      .tool4-extra { font-size: 12px; }
      .tool4-content { padding: 12px 16px 16px;}
      .tool4-img { height: 100px; }
      .tool4-icon-wrap { width: 40px; height: 40px; margin: -20px 0 0 16px; }
      .tool4-header { margin-bottom: 16px; padding-bottom: 8px; }
      .role-card { padding: 20px; gap: 16px; }
      .role-title { font-size: 14px; }
      .role-subtitle { font-size: 12px; }
      .role-icon-wrap { width: 40px; height: 40px; }
      .role-icon-wrap svg { width: 20px; height: 20px; }

      /* Контакты: ещё компактнее, всё ещё в одну строку */
      .footer-contacts{min-height:310px; margin-top:24px;}
      .contacts__qr-code{width:42px; height:42px; padding:4px;}
      .contacts__qr-label{font-size:12px;}
      .contacts__qr{gap:4px;}

      .cta-contacts__photo-wrap{min-height:240px;}
      .cta-contacts__body{padding:24px;}
      .cta-contacts__blurb{font-size:14px;}
      /* высота не ниже 44px — это основной CTA и на тач-устройствах он должен быть удобным */
      .cta-contacts__button{font-size:14px; padding:12px 16px; min-height:44px;}
      .cta-contacts__tile{padding:20px 16px; flex-basis:150px;}
      .cta-contacts__name{font-size:14px;}
      .cta-contacts__value{font-size:14px;}
    
  }

    @media (max-width:899px){
      .stage{padding-top:24px; padding-bottom:16px;}
      h2.section{font-size:16px; margin-bottom:20px; margin-top:16px;}
      .stats{grid-template-columns:repeat(2,1fr); gap:4px; margin-bottom:12px;}
      .stat .num{font-size:18px;}
      .stat .lbl{font-size:12px;}
      .stat{padding:4px 8px;}
      .exp-top{gap:8px;}
      .exp-info{flex-direction:column; align-items:flex-start; gap:4px;}
      .exp-company{font-size:14px;}
      .exp-years{font-size:12px; padding:4px 8px;}
      .exp-sub{font-size:14px; margin-top:8px;}
      .exp-niche{font-size:12px; margin-top:8px;}
      .exp-row{padding:12px;}
      .bar-top{font-size:12px; margin-bottom:4px;}
      .bar-item{margin-bottom:12px;}
      .tag{font-size:12px; padding:4px 8px; border-radius:12px;}
      .tag-row{gap:8px; margin-top:8px; margin-bottom:8px;}
      .body-grid{grid-template-columns:1fr; gap:16px;}
      .block{margin:24px 0 16px 0;}
      .spoiler-toggle{font-size:12px; margin-top:8px;}
      .spoiler-inner{font-size:12px; padding-top:8px;}
      .tool4-card { flex: 0 0 260px !important; }
      .tool4-img { height: 90px; }
      .tool4-icon-wrap { width: 36px; height: 36px; margin: -18px 0 0 12px; }
      .tool4-icon-wrap svg { width: 18px; height: 18px; }
      .tool4-content { padding: 12px 12px 12px;}
      .tool4-title { font-size: 14px; }
      .tool4-short { font-size: 12px; }
      .tool4-extra { font-size: 12px; }
      /* на тач-экранах стрелки карусели не уменьшаем ниже 40px */
      .tool4-nav-btn svg { width: 16px; height: 16px; }
      .tool4-header { margin-bottom: 12px; padding-bottom: 4px; }
      .roles-grid { grid-template-columns: 1fr; }
      .mjs-app-wrapper { height: 340px; }
      .mjs-word { font-size: 16px; }

      /* Контакты: максимально плотно, всё ещё в одну строку (до 680px) */
      .footer-contacts{min-height:280px; margin-top:24px;}
      .contacts__qr-code{width:36px; height:36px; padding:4px;}
      .contacts__qr-label{font-size:12px;}
      .contacts__qr{gap:4px;}
      .cta-contacts__role{font-size:14px;}

      /* 480–899px: фото и текст остаются рядом (Вариант A — равные колонки), не в столбик */
      .cta-contacts__photo-wrap{min-height:260px;}
    
  }

    @media (max-width: 679px){
      /* Ключевые показатели (proof-chips) при переносе на 2-ю строку
         прилипали друг к другу — у flex-wrap не было row-gap, только
         горизонтальный интервал (padding+border-left у .proof-chip). */
      .exp-list .proof-chips{ row-gap:16px; }
      .exp-list .proof-scope{ font-size:12px; }
      .tool4-subtitle{ font-size:14px; }

      /* На мобильных 3 контакта в строку то и дело переносились вразнобой
         (2+1). Вместо этого — по образцу референса «1a» (icon-badge из
         .contacts-mini, не большая сетка label+value) — контакты становятся
         компактными квадратными плитками только с иконкой, без текста;
         текст остаётся в DOM для доступности (sr-only + aria-label на
         самой ссылке — избыточно, но надёжно). */
      .top-contact-bar__contact{
        width:36px; height:36px;
        padding:0;
        justify-content:center;
        background:var(--panel); border-color:var(--panel-line);
      }
      .top-contact-bar__contact-text{
        position:absolute; width:1px; height:1px;
        overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap;
      }
      /* Точечное исключение из правила "line-height задаётся один раз на
         базовом тире и не переопределяется по брейкпоинтам" (см. design-
         system.md) — по запросу именно этот список должен стать плотнее
         на узких экранах, а не просто унаследовать общий Main/body-role 1.45. */
      .exp-list .spoiler-inner li{ line-height:1.3; }
      /* Теги: шрифт ~в 1.5 раза меньше базовых 18px, но не ниже 13px —
         ниже этого их уже неудобно и читать, и хватать пальцем */
      .mjs-app-wrapper { height: 330px; }
      .mjs-word { font-size: 14px; }

      .exp-company{
        font-size: 16px;
      }

      /* Год раньше уходил на новую строку, как только название роли не
         помещалось (flex-wrap:wrap у .exp-top по умолчанию переносит ЦЕЛЫЕ
         элементы). Теперь ряд не переносится сам (flex-wrap:nowrap), а
         название роли — единственное, что сжимается и переносит СВОЙ
         текст внутри себя (min-width:0 разрешает это), год остаётся
         компактным и на месте; если места хватает — оба на одной строке. */
      .exp-header{
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        align-items: baseline;
        justify-content: space-between;
        gap: 8px;
      }
      .exp-header .exp-company{
        min-width: 0;
        flex: 1 1 auto;
      }
      .exp-header .exp-years{
        flex-shrink: 0;
      }

      /* Пол читаемости для основного текста на мобильных экранах — не ниже
         14px (теги/года/лейблы-подписи вроде .exp-niche, .tag, .proof-lbl
         сюда не входят, им можно оставаться мельче как вспомогательным).
         Без этого часть контента проседала до 12px в диапазоне 480-679px,
         унаследовав размер от планшетного тира (900-1079px).
         .spoiler-inner (единственное применение — "Достижения и
         результаты") сюда больше НЕ входит: по запросу список должен
         остаться на 12px во всём диапазоне ≤679px, а не подскакивать
         обратно на 14px — намеренное исключение из этого пола.
         .stack-label/.stack-items ("Инструменты и стек", таблица технологий)
         добавлены сюда отдельно — их вообще не было ни в одном полу до сих
         пор, они держали фиксированные 16px на всех тирах ≤899px. По
         фидбэку на узких экранах это делало их визуально самым крупным
         текстом на странице — крупнее заголовков разделов рядом. */
      .role-subtitle,
      .tool4-short,
      .tool4-extra,
      .cta-contacts__role,
      .cta-contacts__button,
      .cta-contacts__value,
      .stack-label,
      .stack-items{
        font-size: 14px;
      }

      .footer-contacts { min-height: auto;}
      .contacts__qr { flex-shrink: 0; }
      .contacts__qr-code { width: 46px; height: 46px; }
      .contacts__qr-label { display: none; }

      .cta-contacts{ margin-top: 32px; border-radius: 12px; }
      /* Всё ещё рядом (фото+текст) вплоть до 480px — только столбец с контактами ниже становится вертикальным */
      .cta-contacts__photo-wrap{ min-height: 230px; }
      .cta-contacts__body{ padding: 24px; gap: 16px; }
      .cta-contacts__blurb{ font-size: 14px; max-width: 100%; }
      .cta-contacts__row{ flex-direction: column; }
      .cta-contacts__tile{
        flex-basis: auto;
        border-left: none;
        border-top: 1px solid var(--panel-line);
        padding: 16px 24px;
      }
      .cta-contacts__tile:first-child{ border-top: none; }
    
  }

    /* ≤479px — самые узкие телефоны. Раньше этот порог стоял на 539px, и
       480-539px получали урезанные значения без реальной необходимости —
       470px "лишней" ширины хватает на то, чтобы выглядеть как 540-679px
       (see тир ниже). Пересчитано отдельно от 480-679, а не просто
       скопировано с 539px: там разброс уже не помещался. */
    @media (max-width:479px){
      .top-contact-bar__logo{height:32px;}
      .top-contact-bar__contact{font-size:12px;}

      /* Ниже 480px даже 12-13px читается с трудом — держим пол 14px для
         содержательного текста и 12px для плашек/сносок/уточнений */
      .exp-company{
        font-size: 16px;
      }

      .exp-sub,
      .bar-top span,
      .role-subtitle,
      .cta-contacts__role,
      .cta-contacts__value,
      .cta-contacts__button,
      .cta-contacts__blurb,
      .stack-label,
      .stack-items{
        font-size: 14px;
      }
      /* .exp-list .spoiler-inner li сюда намеренно НЕ входит: по запросу
         список "Достижения и результаты" должен оставаться на 12px и ниже
         680px, а не подскакивать обратно к полу 14px — исключение из
         правила выше специально для этого элемента. Работает через
         каскад .spoiler-inner{font-size:12px} (≤1079px), а не через
         отдельное правило здесь. */
      .stat .lbl,
      .tag,
      .exp-years,
      .exp-niche{
        font-size: 14px;
      }

      .tool4-card { flex: 0 0 85% !important; }
      .tool4-img { height: 80px; }
      .tool4-title { font-size: 16px; }

      /* По запросу убран ≤479px — падающие теги на Matter.js грузили
         мобильный CPU/батарею сильнее, чем оправдано для декоративного
         виджета. Прячем через :has() саму обёртку .block (не только
         .mjs-app-wrapper внутри неё) — иначе пустой .block с одним
         только margin остался бы висеть как пустой зазор. JS-часть
         (checkSize() в script.js) отдельно проверяет window.innerWidth
         и гасит уже запущенный движок (destroyScene()), если пользователя
         ресайзнуло через границу 480px после старта — CSS-скрытия самого
         по себе для этого недостаточно, т.к. видимость и вычисления
         физики у Matter.js не связаны напрямую. */
      .block:has(.mjs-app-wrapper){ display: none; }

      /* Ниже 480px фото и текст уже не помещаются рядом — переходят в столбик */
      .cta-contacts__top{ grid-template-columns: 1fr; }
      .cta-contacts__photo-wrap{ min-height: 200px; }

      .exp-list .proof-chip .proof-num{ font-size: 16px; }

  }

    /* ≤399 — "Ключевые показатели" (proof-chips), по запросу. Раньше
       каждая плашка была колонкой (num над lbl), а плашки сами шли в ряд
       через flex-wrap — на узких телефонах это давало то шаткую раскладку
       "то 2, то 3 в ряд" (что и лечил отдельный частный случай на ≤340
       ниже — replaced целиком этим правилом, а не рядом с ним: иначе два
       блока конкурировали бы за одни и те же селекторы на самых узких
       экранах, где оба диапазона пересекаются), то просто тесно. Теперь
       каждый показатель — ОДНА строка (num и lbl рядом через gap), а сами
       показатели идут по одному в столбик, никогда не пытаясь встать по
       несколько в ряд. */
    @media (max-width:399px){
      .exp-list .proof-chips{
        display: flex;
        flex-direction: column;
        flex-wrap: nowrap;
        gap: 10px;
      }
      .exp-list .proof-chip{
        flex-direction: row;
        align-items: baseline;
        gap: 8px;
        padding: 0;
        border-left: none;
      }
      /* min-width:0 — иначе flex-item по умолчанию не сжимается ниже
         ширины своего контента, и длинная подпись (напр. "органический
         тарфик") вытолкнула бы строку за край контейнера вместо переноса. */
      .exp-list .proof-chip .proof-lbl{
        white-space: normal;
        min-width: 0;
      }

  }

    @media (max-width:340px){
      .role-footer{font-size: 14px;}

  }

    /* === RESUMEACTIONS: PDF / ПЕЧАТЬ / ПОДЕЛИТЬСЯ (плавающая панель) ===
       Наследует шрифты, цвета (CSS-переменные) и радиусы скругления страницы —
       собственной палитры/шрифтов у виджета нет. */
    .resume-actions{
      --fab-gap: 24px;
      position: fixed;
      left: 50%;
      right: auto;
      bottom: 10px;
      transform: translateX(-50%);
      /* 96, не 90 — выше .top-contact-bar (95). На ≥1220px недокнутое
         положение панели (top:24px; right:20px, см. ниже) физически
         лежит под липкой навигацией; раньше панель пряталась за ней до
         скролла >500px (когда JS переводит её вниз). Поверх — проще и
         надёжнее, чем сдвигать стартовую позицию под высоту бара. */
      z-index: 96;
      display: flex;
      flex-direction: row;
      align-items: center;
      gap: 8px;
      padding: 0;
      background: var(--panel);
      border: 1px solid var(--panel-line);
      border-radius: 16px;
      backdrop-filter: blur(14px);
      -webkit-backdrop-filter: blur(14px);
      box-shadow: 0 12px 32px rgba(0,0,0,0.35);
    }
    .resume-actions__btn{
      display: inline-flex;
      align-items: center;
      gap: 8px;
      font-family: 'IBM Plex Mono', monospace;
      font-weight: 600;
      font-size: 16px;
      line-height: 1.4;
      color: var(--text);
      background: transparent;
      border: 1px solid transparent;
      padding: 12px 16px;
      border-radius: 8px;
      cursor: pointer;
      text-decoration: none;
      transition: transform .2s ease, box-shadow .2s ease, border-color .2s ease, background .2s ease, color .2s ease;
    }
    .resume-actions__btn svg{ width: 17px; height: 17px; flex-shrink: 0; }
    .resume-actions__btn:hover{
      transform: translateY(-2px);
      border-color: rgba(166,224,138,0.4);
      background: rgba(166,224,138,0.06);
      color: var(--green);
    }
    .resume-actions__btn:active{ transform: translateY(0); }
    .resume-actions__btn:focus-visible{
      outline: 2px solid var(--green);
      outline-offset: 2px;
    }
    .resume-actions__btn--copied{
      border-color: rgba(166,224,138,0.5);
      background: rgba(166,224,138,0.1);
      color: var(--green);
    }

    /* Ниже 1080px — панель без подписей, только иконки (title показывает подсказку при наведении) */
    @media (max-width:1079px){
      .resume-actions{ --fab-gap: 16px; gap: 4px; padding: 4px; bottom: 8px;}
      .resume-actions__btn{ padding: 12px; }
      .resume-actions__label{
        position: absolute;
        width: 1px; height: 1px;
        overflow: hidden;
        clip: rect(0,0,0,0);
        white-space: nowrap;
      }
    
  }

    @media (min-width:480px) and (max-width:679px){
      .resume-actions{ bottom: 10px;}
    
  }

    /* От 1220px и выше — вертикально, иконками, над кнопкой "Наверх" (тот же правый угол) */
    @media (min-width:1220px){
      /* Вертикальная панель у правого края.
         В начале страницы висит в правом верхнем углу; как только появляется
         кнопка "Наверх" — плавно съезжает вниз, вставая ровно над этой кнопкой.
         Обратный скролл так же плавно возвращает её наверх.

         Якорь всегда один и тот же (top), движение — через transform, чтобы
         не анимировать переход top <-> bottom (браузер это не умеет).
         Саму анимацию делает JS (script.js, requestAnimationFrame) — он же
         на каждом кадре выставляет inline transform, перекрывая значение
         .is-docked ниже. Оно остаётся как мгновенный fallback на случай,
         если JS отключён. */
      .resume-actions{
        left: auto;
        right: 20px;
        top: 24px;
        bottom: auto;
        transform: translateY(0);
        will-change: transform;
        flex-direction: column;
        gap: 4px;
        padding: 8px;
      }
      .resume-actions.is-docked{
        transform: translateY(calc(100vh - 108px - 100%));
      }
      .resume-actions__btn{ padding: 12px; }
      .resume-actions__label{
        position: absolute;
        width: 1px; height: 1px;
        overflow: hidden;
        clip: rect(0,0,0,0);
        white-space: nowrap;
      }
    
  }

    /* === ПОДВАЛ СТРАНИЦЫ (копирайт) ===
       Независим от .block: свои отступы и своя логика по брейкпоинтам.
       Floating Action Bar и "Наверх" — fixed и лежат ПОВЕРХ подвала, на одном
       с ним уровне. Так и задумано: это убирает и большой зазор внизу страницы,
       и наложение бара на блок контактов. Место под бар НЕ резервируем —
       кроме экранов уже 480px, где бар по центру закрывал бы половину строки
       копирайта: там подвал отводит полосу ровно в высоту бара. */
    .page-footer{
      display: flex;
      align-items: center;
      flex-wrap: wrap;
      gap: 16px;
      margin-top: 40px;
      padding-bottom: 32px;
    }
    @media (min-width:1920px){
      .page-footer{ margin-top: 48px; padding-bottom: 40px; }
    
  }
    @media (max-width:1439px){
      .page-footer{ margin-top: 48px; padding-bottom: 32px; }
    
  }
    @media (max-width:1219px){
      .page-footer{ margin-top: 40px; padding-bottom: 32px; }
    
  }
    @media (max-width:1079px){
      .page-footer{ margin-top: 32px; padding-bottom: 24px; }
    
  }
    @media (max-width:899px){
      .page-footer{ padding-bottom: 16px; }
    
  }
    @media (max-width:679px){
      .page-footer{ padding-bottom: 16px; }
    
  }
    /* Уже 480px строка копирайта короче ширины бара — отводим полосу под него:
       16px отступ снизу + 53px высота бара + 8px просвет */
    @media (max-width:479px){
      .page-footer{ margin-top: 20px; padding-bottom: 77px; }
    
  }

    /* === КНОПКА "НАВЕРХ" === */
    .back-to-top{
      position: fixed;
      bottom: 8px;
      right: 24px;
      z-index: 100;
      width: 50px; height: 50px;
      border-radius: 25%;
      background: var(--panel);
      backdrop-filter: blur(14px);
      border: 1px solid var(--panel-line);
      color: var(--text);
      display: flex; align-items: center; justify-content: center;
      text-decoration: none;
      opacity: 0;
      pointer-events: none;
      transform: translateY(14px) scale(.9);
      /* появление/скрытие — плавно, с лёгким выездом снизу */
      transition:
        opacity .32s cubic-bezier(.22,.61,.36,1),
        transform .32s cubic-bezier(.22,.61,.36,1),
        border-color .25s ease,
        color .25s ease,
        box-shadow .25s ease;
      will-change: opacity, transform;
    }
    .back-to-top.visible{
      opacity: 1;
      pointer-events: auto;
      transform: translateY(0) scale(1);
    }
    .back-to-top:hover{
      border-color: rgba(166,224,138,0.5);
      color: var(--green);
      box-shadow: 0 6px 16px rgba(166,224,138,0.15);
    }
    .back-to-top:focus-visible{
      outline: 2px solid var(--green);
      outline-offset: 3px;
    }
    .back-to-top:active{ transform: translateY(0) scale(.94); }
    .back-to-top svg{ width: 20px; height: 20px; }

    @media (max-width:479px){
      /* 44x44 — минимальный размер тач-таргета, уменьшаем только отступы от края */
      .back-to-top{ right: 14px; }
      .back-to-top svg{ width: 18px; height: 18px; }
    
  }

    /* === ПЕЧАТЬ / PDF: раскрываем скрытый контент, убираем интерактивные элементы === */
    @media print{
      .back-to-top, .resume-actions, .tool4-nav, .spoiler-toggle, #stackToggleOpen, #stackToggleClose{
        display: none !important;
      }
      .spoiler-body, .tool4-extra, .spoiler-body-wide, .stack-panel{
        max-height: none !important;
        opacity: 1 !important;
        overflow: visible !important;
      }
      .tool4-row{
        overflow-x: visible !important;
        flex-wrap: wrap !important;
        width: auto !important;
        margin-left: 0 !important;
        padding-left: 0 !important;
        padding-right: 0 !important;
      }
      .tool4-card{ flex: 0 0 320px !important; }
      .mjs-app-wrapper{ display: none !important; }
    
  }

    /* ===================== ПЕРВЫЙ ЭКРАН (HERO) =====================
       Перенесено из sandbox-hero-brutal-dots.html. Ниже — только те
       правила, которых в этом файле ещё не было: вся шапка
       (.top-contact-bar*), кнопка (.cta-contacts__button), :root-токены и
       ресет в проде уже есть и переиспользуются как есть.

       --gutter — единственный новый токен. Он повторяет каскад .content
       (1640/1440/1080/980, ниже 1080 — 20px), поэтому левый край "БАКАЛА"
       встаёт ровно под левый край "Профиль" и остальных секций. Проценты
       считаются от .hero, а она всегда во всю ширину вьюпорта — та же
       точка отсчёта, что у центрированной .content. */
    :root{ --gutter: max(0px, calc((100% - 1440px) / 2)); }
    @media (min-width:1920px){ :root{ --gutter: calc((100% - 1640px) / 2); } 
  }
    @media (max-width:1439px){ :root{ --gutter: calc((100% - 1080px) / 2); } 
  }
    @media (max-width:1219px){ :root{ --gutter: calc((100% - 980px) / 2); } 
  }
    @media (max-width:1079px){ :root{ --gutter: 20px; } 
  }

/* ===================== HERO ===================== */
  /* height:100vh (не 100%): раньше высота бралась от родителя (body),
     который был жёстко заперт в один экран без скролла. Герой — самостоятельный
     полноэкранный блок независимо от того, что идёт после него (на живом сайте —
     "Мой опыт" и другие секции, здесь — тестовая заглушка ниже для проверки
     скролл-эффекта разлёта букв). */
.hero{
    position:relative;
    width:100%; height:100vh;
    overflow:hidden;
    /* Сам градиент теперь не здесь — см. .hero-bg ниже. Здесь остаётся
       только сплошной --bg: тот же цвет, что и у спейсера/body под героем,
       поэтому даже когда .hero-bg (fixed) когда-нибудь уберут, шва по цвету
       всё равно не будет — обе заливки совпадают. */
    background:var(--bg);
  }

@supports (height:100dvh){
    .hero{ height:100dvh; }
  }

/* Резерв под уход закреплённой композиции — см. подробный комментарий у
   самого элемента в index.html и у measure()/apply() в script.js. Высоту
   в px всегда выставляет JS инлайном; здесь только цвет (тот же --bg, что
   у .hero и .stage — переход между тремя блоками не должен читаться как
   стык) и запрет на горизонтальный скролл от самого факта наличия блока. */
.hero-spacer{
    width:100%;
    background:var(--bg);
  }

/* Фон героя раньше жил здесь отдельным слоем .hero-bg. Теперь это общий
   для всей страницы .page-glow (см. его правило в начале файла): держать
   два разных слоя с разными градиентами по разные стороны первого экрана
   было прямой причиной того, что фон "переключался" на границе — см.
   комментарий там же. */

/* ---- Разлёт букв по скроллу (перенесено из демо-сценария "Time flies") ----
     Каждая буква "БАКАЛА"/"ЕВГЕНИЙ" — отдельный inline-block span со своей
     случайной скоростью. transform требует inline-block (на строчных элементах
     transform не работает), поэтому только это свойство и нужно явно —
     цвет/шрифт/uppercase уже наследуются от .hero-name-1/.hero-name-2. */
.hero-name-1 span, .hero-name-2 span{
    display:inline-block;
  }

/* ---- Интерактивные карточки вокруг портрета (z-index:6) ----
     Стеклянные плашки поверх фото (6 > 4) и поверх «ЕВГЕНИЙ» (6 > 2) —
     так и задумано в исходнике: они «плавают» над композицией.
     Каждая уезжает за курсором на свою величину (--shift), разная сила
     сдвига и даёт ощущение глубины. --mx/--my (диапазон -1..1) ставит
     скрипт внизу страницы; фолбэк 0 — карточки просто стоят на месте.
     Отступление от исходника: типографика приведена к шкале проекта
     (13px → 14px) и к токенам.
     Заливка/бордер — по запросу возвращены к исходному стилю: тот же
     нейтральный "стеклянный" вид, что и у .hero-tag (ряд тегов сразу под
     меню навигации) — background:var(--panel), т.е. та же rgba(255,255,255,
     0.04), а не цветной акцент (пятицветную версию, где у каждой карточки
     был свой --plate-rgb, отменили — все пять снова выглядят одинаково,
     разница только в контенте). Бордер — единственное отступление от
     .hero-tag: там он еле заметен (var(--panel-line), альфа 0.14), здесь —
     выраженный (0.4), по запросу, чтобы явно читаться и на фоне героя, и на
     фоне того, что под карточкой (фото, «ЕВГЕНИЙ», буквы «БАКАЛА»).
     Заголовок и число — --text (белый), --plate__text — --text-2 (серый),
     как и раньше. */
  /* position/top/left/opacity управляются скриптом внизу страницы напрямую
     (инлайн-стилями, каждый кадр скролла) — плашки появляются только при
     скролле, влетая каждая со своей стороны и приходя в конечную точку/
     непрозрачность ровно к моменту, когда фото останавливается. position
     здесь смысла не имеет писать в CSS: скрипт всегда выставляет fixed сам,
     как только включает сценарий (см. "Скролл-пин" внизу страницы — там же
     объяснение, почему именно fixed, а не absolute, как было раньше).
     opacity:0 по умолчанию — до первого кадра скрипта (и если он выключен
     шириной экрана — тогда плашки и так display:none) плашек не видно.
     transform (параллакс за курсором, --mx/--my) — независимое свойство,
     скрипт его не трогает, оба складываются без конфликта. */
  /* На десктопе (≥1220) полностью прозрачна для раскладки — display:contents
     убирает у неё собственный бокс, фото и .arc-wrapper (дуга-карусель) ведут себя
     так, будто лежат прямо в .hero, как и было до появления этой обёртки.
     На ≤1219 (см. адаптив) становится реальным position:relative-боксом
     размером с фото — точкой отсчёта для position:absolute у карточек. */
.hero-photo-cluster{ display:contents; }
/* .arc-runway — обёртка вокруг кластера. Осталась от варианта со
   sticky, где давала кластеру "разгон" на прилипание и её высоту каждый
   resize считал JS. Сейчас пин сделан на position:fixed, высоту ей никто
   не задаёт; бокс сохранён потому, что на ≤1219 несёт order:6 и
   grid-area:photo (см. адаптив ниже) — то есть держит место фото-кластера
   в колонке героя. На десктопе прозрачна для раскладки. */
.arc-runway{ display:contents; }

/* ===================== ПОДСКАЗКА "СКРОЛЛ" ВНИЗУ ГЕРОЯ =====================
   Раньше была завязана на animation-timeline:scroll(nearest block) — той же
   переменной, что крутила всю страницу. Теперь у дуги свой собственный
   scroll-driver (см. .arc-track ниже), общего с героем больше нет, поэтому
   подсказка — простая статичная иконка с самостоятельным SVG-бампом,
   привязанная к низу героя, а не к скроллу всей страницы. */
.hero-scroll-hint{
  /* top и position:fixed выставляет скролл-пин в <script>, строго под
     дугой (см. apply()) — bottom здесь не задаём: вместе с top на fixed-
     элементе без явной height это растянуло бы блок между ними. */
  position:absolute; left:50%; top:80vh; translate:-50% 0;
  color:var(--text); /* было var(--panel-line) — едва заметный серый, по запросу белая */
  z-index:5; pointer-events:none;
  /* видима, только пока видно колесо карточек — opacity каждый кадр
     перезаписывает apply() в <script>, синхронно с .arc-wrapper (см. ниже),
     а не постоянно как раньше (иконка висела с самого начала скролла,
     ещё до появления карточек). */
  opacity:0;
}
@media (max-width:1219px){
  .hero-scroll-hint{ display:none; }
}

/* ===================== ДУГА-КАРУСЕЛЬ ВОКРУГ ФОТО =====================
   По макету — дуга больше не отдельная секция со своим скроллом (не
   100vh/500vh, никакого animation-timeline). Теперь это часть композиции
   героя: карточки — position:fixed, едут ВМЕСТЕ с фото (тот же скролл-пин
   в <script> ниже), накладываются на фото в районе груди/плеч, лицо не
   закрывают. --rotate (какая карточка в фокусе) теперь не CSS-анимация,
   а обычная переменная, которую каждый кадр пишет тот же JS, что двигает
   фото/роль/саммари — колесо прокручивается СИНХРОННО с их сборкой. */
@property --rotate {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.arc-wrapper {
  /* те же размеры/отступы, что и в отдельной песочнице дуги
     (experience-carousel/index.html) — там специально с воздухом, здесь то
     же самое, просто наложено на фото, а не на всю ширину контента */
  --card-width: clamp(210px, 18vw, 230px);
  /* Пропорция карточки (ширина / высота). В присланном наборе карточки
     были квадратные (240×240 → aspect 1), по запросу сделаны ниже.
     Это ЕДИНСТВЕННАЯ ручка высоты: 1 = квадрат, больше = ниже.
     Два ограничителя, из-за которых не стоит задирать выше 1.2:
     — «до 170М ₽»: мозаика из 3 рядов квадратных ячеек, её высота жёстко
       задана шириной колонок и не ужимается вообще;
     — «Играющий тренер»: в правом верхнем углу фото 90×90, и нижний
       текстовый блок должен под него не залезать (на 1.25 заголовок уже
       начинал перекрываться с фото). */
  --card-aspect: 1.2;
  --card-gap: clamp(40px, 4.5vw, 64px);
  /* --card-cutoff теперь ТОЛЬКО про блюр (см. --card-blur ниже) — раньше
     тем же порогом ещё и гасили прозрачность в ноль (карточка "исчезала"
     по факту пересечения дистанции), но по фидбэку это читалось как
     "крайние карточки появляются с задержкой" — их не должно быть не
     видно вовсе, только темнее/размытее. Поэтому opacity и brightness
     теперь не используют cutoff вообще — гладкие функции дистанции
     0..0.5 без порога (см. их комментарии). Порог остался только за
     блюром: он должен быть "лёгким и только у самого края", то есть
     резко ограниченным зоной, а не растянутым по всей дистанции — на
     это и нужен явный порог, а не гладкая кривая, как у остальных двух. */
  --card-cutoff: .26;

  position: fixed;
  left: 50%;
  top: 0; /* top каждый кадр перезаписывает apply() в <script> — едет вместе с фото */
  transform: translateX(-50%); /* тот же центр, что у .hero-photo */
  z-index: 6; /* тот же слой, что был у .hero-plate — поверх фото (z-index:4) */
  opacity: 0; /* стартовое — до первого кадра apply() (пишет реальный opacity=p) */
  pointer-events: none;

  @media (max-width: 319px) {
    /* Было ≤1219, потом ≤479 — по фидбэку колесо теперь работает и на
       320-479 тоже (та же логика, что и на 480-1219: карточки просто
       уже/теснее, см. --card-width ниже). Откат на display:none остаётся
       только ниже реального минимума дизайн-системы (микро-тир ≤340 — 320
       взят с небольшим запасом вниз), где вместо колеса — грид "Профиль". */
    display: none;
  }
  @media (max-width: 399px) {
    /* Было "чисто по ширине окна" (100vw - гуттеры, до 380px) — по
       фидбэку слишком крупно. 3/4 ширины экрана вместо почти полной —
       потолок 300px тоже снижен пропорционально (был 380), чтобы у
       самой границы 399 (75vw≈299) не было скачка вверх. */
    --card-width: min(75vw, 300px);
  }
  @media (min-width: 400px) and (max-width: 479px) {
    /* 400-479 — откат на дугу как было (по фидбэку): та же узкая кривая
       clamp(150,46vw,210), тот же --card-aspect:1.1 под "Играющего
       тренера" (см. историю/тесты выше — 0.85 тут не нужен, тот был
       только под самое дно 320px, а сюда уже не попадает). */
    --card-width: clamp(150px, 46vw, 210px);
    --card-gap: clamp(24px, 7vw, 40px);
    --card-aspect: 1.1;
  }
  @media (min-width: 1920px) {
    --card-width: 260px;
    --card-gap: 84px;
  }

  & > .hero2-plate:nth-child(1) { --card-i: 1; }
  & > .hero2-plate:nth-child(2) { --card-i: 2; }
  & > .hero2-plate:nth-child(3) { --card-i: 3; }
  & > .hero2-plate:nth-child(4) { --card-i: 4; }
  & > .hero2-plate:nth-child(5) { --card-i: 5; }
  & > .hero2-plate:nth-child(6) { --card-i: 6; }
  & > .hero2-plate:nth-child(7) { --card-i: 7; }
  & > .hero2-plate:nth-child(8) { --card-i: 8; }
  & > .hero2-plate:nth-child(9) { --card-i: 9; }

  & > .hero2-plate {
    --cards: 9;
    --card-phase: calc((var(--card-i) - 1) / var(--cards));
    --card-signed: calc(mod(calc(var(--card-phase) - var(--rotate) + 1.5), 1) - 0.5);
    --card-dist: max(var(--card-signed), calc(var(--card-signed) * -1));
    /* Без clamp(0,...) и без деления на --card-cutoff — раньше карточка
       гасла в честный ноль по факту пересечения порога, что на глаз
       читалось как "крайние карточки появляются с задержкой" (карточки
       не было видно, ПОКА она не подойдёт достаточно близко к фокусу).
       По фидбэку — карточка должна быть видна сразу, просто более тусклой
       по краям. Прямая линейная функция dist (0..0.5) без порога: в
       фокусе opacity=1, на максимальном удалении (dist=0.5, "затылок"
       дуги) — 0.2, то есть еле заметная, но не отсутствующая. Про то,
       почему это не возвращает старый баг с "рывком" при mod()-скачке на
       затылке — см. --card-blur ниже. */
    --card-opacity: calc(1 - var(--card-dist) * 1.6);
    /* Та же логика и тот же диапазон дистанции, отдельный (более крутой)
       коэффициент — приоритет центра держат оба сигнала вместе, но
       затемнение агрессивнее прозрачности. Было 1.7, потом 1.85 — по
       повторному фидбэку "ещё сильнее" подняли до 2.1: на предельной
       РЕАЛЬНО достижимой в покое дистанции (4/9≈0.444 — при 9 карточках
       дальше просто некуда, дуга зациклена) brightness≈0.07, почти
       чёрный силуэт. На dist=0.5 (только транзитно, во время перехода
       между соседними карточками, и то на карточке, физически уже
       ушедшей за край экрана — см. --card-blur ниже) формула уходит в
       отрицательные, CSS клампит в 0 (полностью чёрная) — на этот раз
       не баг, а разряжено само по себе, т.к. на экране этой точки не
       видно. */
    --card-brightness: calc(1 - var(--card-dist) * 2.1);
    /* Блюр — "лёгкий и только на карточках по краям", не на всех боковых.
       Порог (--card-cutoff, переиспользован, означает именно "с какой
       дистанции начинается блюр", не "где гаснет прозрачность"): 0px до
       dist=--card-cutoff, дальше линейно до максимума на dist=0.5. Было
       .32/2px — по фидбэку сдвинули порог чуть раньше (.26, накрывает
       уже 3-ю от фокуса, не только 4-ю) и подняли максимум до 2.5px,
       но это по-прежнему "лёгкий" блюр, не тот 5px, что выглядел
       неопрятно на ключевых карточках в прошлой версии.
       Почему безопасно, что opacity/brightness выше больше не гасят
       карточку в ноль перед "затылком" (dist=0.5, где mod() скачком
       телепортирует карточку на противоположный край) — проверено
       расчётом в браузере: сама позиция (--card-x = sin(angle)×radius-x)
       на dist=0.5 (angle=90°) физически уходит ЗА пределы вьюпорта на
       всех десктопных брейкпоинтах (на 1440 это ~784px от центра при
       радиусе экрана 720px) — то есть скачок происходит за кадром, а не
       "на глазах", независимо от прозрачности. */
    --card-blur: calc(clamp(0, (var(--card-dist) - var(--card-cutoff)) / (0.5 - var(--card-cutoff)), 1) * 2.5px);

    /* Настоящая окружность (sin/cos) — карточки реально идут по дуге
       контура круга, а не по плоской полосе. --card-signed (-0.5..0.5)
       переводим в угол по ободу. sin — горизонталь, (1-cos) — просадка
       вниз: у центральной (angle≈0) она почти нулевая, у соседних —
       заметно больше, у крайних — больше всего, нелинейно к краям.

       Множитель был 250° при 6 карточках (макс. угол 0.5×250=125°) —
       СОЗНАТЕЛЬНО за 90°, чтобы крайняя карточка «подворачивалась»
       обратно к центру. На 11 карточках так делать нельзя: sin() после
       90° убывает, и карточка на 111° встала бы РОВНО МЕЖДУ карточками
       на 83° и 55° — то есть влезла бы в уже занятое место (проверено
       расчётом координат в браузере; при 6 карточках такой пары просто
       не возникало). Поэтому 180°: предельная дистанция 0.5 даёт ровно
       90°, порядок карточек по горизонтали строго монотонный, никто ни
       в кого не влезает. */
    --card-angle: calc(var(--card-signed) * 180deg);
    /* Радиус — от ширины карточки и отступа (а не от их числа × ширины:
       та формула на широких экранах расползалась за пределы контента).
       Коэффициент 2.8 (было 1.6 под 6 карточек, 3.4 под 11): чем больше
       карточек, тем мельче шаг по дуге и тем шире нужен обод, чтобы
       соседняя не наезжала на центральную. Подобран так, чтобы на всех
       трёх десктопных брейкпоинтах (1220 / 1440 / 1920) центральная и ±1
       расходились с зазором 40-70px, а ±2 целиком помещалась в экран —
       узкое место именно 1220. Формула сама масштабируется вместе с
       --card-width/--card-gap. */
    --card-radius-x: calc((var(--card-width) + var(--card-gap)) * 2.8);
    /* Было 70px (6 карточек) → 120px (первая версия под 9). По запросу
       дугу сделали ещё выше — до 190px. Просадка = (1-cos(angle)) ×
       radius-y — эта формула ВСЕГДА ≥0, то есть боковые карточки только
       опускаются НИЖЕ якоря (--card-drop прибавляется к позиции), а
       центральная (angle≈0, drop≈0) как раз и есть самая ВЫСОКАЯ точка
       дуги. Поэтому рост radius-y не может поднять карточки к лицу — он
       только увеличивает, насколько боковые карточки провисают вниз.
       Единственное, что от этого растёт — общая занимаемая высота дуги
       вниз, отсюда же и рост ARC_CARD_OVERHANG в <script> (иначе дуга
       наедет на "Профиль"). Если лицо всё же нужно освободить сильнее —
       не эта переменная, а ARC_ANCHOR_RATIO/photoTargetTop в <script>
       (см. точку 1 в фидбэке про "можно поднять фото"). */
    --card-radius-y: 190px;
    --card-x: calc(sin(var(--card-angle)) * var(--card-radius-x));
    --card-drop: calc((1 - cos(var(--card-angle))) * var(--card-radius-y));
    /* Центральная карточка приподнята над остальными на 20px — по запросу,
       чтобы фокусная карточка читалась ещё и как "выступающая", а не
       только как самая яркая/чёткая. Только у карточки с --card-dist=0 (в
       любой момент снапа она ровно одна — см. computeWheelP в <script>);
       у всех прочих (dist ≥ 1/9 ≈ .111 при 9 карточках) уже 0. Порог .05
       выбран специально МЕНЬШЕ минимального ненулевого dist (.111), чтобы
       подъём был честной "ступенькой" ровно у фокуса, а не смазанным
       градиентом, слегка цепляющим и соседей. */
    --card-lift: calc(-20px * clamp(0, (0.05 - var(--card-dist)) / 0.05, 1));

    /* ближе к фокусу — выше в стопке: без этого при пересечении траекторий
       (сейчас — по дуге круга) две карточки могли бы наложиться в
       произвольном DOM-порядке и читаться как "проезжают друг за другом" */
    z-index: calc(600 - var(--card-dist) * 1000);

    position: absolute;
    top: 50%;
    left: 50%;
    width: var(--card-width) !important;
    /* Высоту держим через aspect-ratio, а не фиксированной --card-height:
       она сама следует за --card-width на всех брейкпоинтах. Отсюда и
       margin для центрирования: слева половина ширины, сверху половина
       ВЫСОТЫ, то есть ширина / (2 × aspect). */
    aspect-ratio: var(--card-aspect);
    margin: calc(var(--card-width) / (-2 * var(--card-aspect))) 0 0 calc(var(--card-width) / -2);
    transform: translate(var(--card-x), calc(var(--card-drop) + var(--card-lift)));
    filter: brightness(var(--card-brightness)) blur(var(--card-blur));
    opacity: var(--card-opacity);
    pointer-events: auto;
    /* transform — БЕЗ transition (как и раньше): --rotate пишется каждый
       кадр скролла, плавность даёт сам скролл, а transition поверх этого
       и создавал «перепрыгивание» на «затылке» колеса, где --card-signed
       через mod() скачком меняет знак.
       opacity/filter — transition ВЕРНУЛ (в прошлой правке убирал совсем).
       Тогда причина была в том, что --card-cutoff=.9 никогда не давал
       карточке дойти до настоящего opacity:0 — в момент скачка позиции
       она была ещё частично видна, и transition честно тянул это
       "полупрозрачное" состояние на новое место. Сейчас cutoff=.42 —
       карточка гаснет в идеальный ноль задолго до скачка (см. комментарий
       у --card-cutoff), так что лаг transition больше не встречается со
       скачком позиции: можно вернуть плавное появление/исчезание, не
       возвращая тот баг. */
    transition: opacity 300ms ease, filter 300ms ease;
    backface-visibility: hidden;
    box-shadow: 0 20px 50px rgba(0,0,0,.35);

    /* ===== 320-479 — карточка больше не центрируется на точке привязки
       дуги по вертикали =====
       Баг, из-за которого "все карточки лежат на фото": базовый margin
       выше центрирует карточку НА якоре (top:50% + margin-top:-height/2 —
       обычная техника "точка = центр бокса"). На десктопе это не мешало,
       потому что там --card-drop всегда ≥0 и якорь (0.74 высоты фото)
       сам по себе не на самом краю. На мобильном якорь специально
       подвинут на 1.0 (ровно нижний край фото, см. ARC_ANCHOR_RATIO_MOBILE
       в <script>) — но центрирование всё равно поднимало верх карточки
       на половину её высоты ВЫШЕ этой точки, то есть обратно на фото,
       какой бы ни была величина --stack-y/--card-drop дальше.
       margin-top:0 делает точку привязки верхним краем карточки, а не
       центром — дальше вниз её двигают уже --card-drop (дуга, 400-1219) и
       --stack-y (стопка, ≤399), но НИКОГДА вверх. Одно правило на весь
       мобильный диапазон целиком — у него одна и та же причина бага везде.
       Было по ошибке ограничено ≤479 — при первом фиксе спутал границу
       дуги (400-1219) с 400-479, оставив 480-1219 непроверенным. Реально
       проверил числами уже потом: на 800px фокусная карточка заходила на
       фото на 108px, ровно тот же баг, просто раньше не измерял именно
       этот брейкпоинт.
       --card-lift туда же: это тот самый "фокусная карточка приподнята на
       20px" эффект (см. его определение выше) — на десктопе он поднимает
       карточку НАД остальными карточками дуги, а не над фото, там это
       нормально. На мобильном/планшетном при margin-top:0 (якорь = верхний
       край) те же -20px поднимают фокусную карточку буквально на 20px
       выше фото — ровно тот же класс бага, только меньше масштабом.
       Обнуляем и его. */
    @media (max-width: 1219px) {
      margin-top: 0;
      --card-lift: 0px;
    }

    /* ===== ≤399 — "Cards Stack Slider" вместо дуги =====
       По фидбэку — переделано с нуля, первая версия (двусторонний веер
       вверх/вниз от фокуса) реально ложилась на фото: "предстоящие"
       карточки уезжали ВВЕРХ от точки привязки дуги (которая сама сидит
       ровно на нижнем крае фото — см. ARC_ANCHOR_RATIO_MOBILE в
       <script>), то есть возвращались туда же, откуда фото специально
       уводили. Новая модель — ОДНОНАПРАВЛЕННАЯ, ничего не поднимается
       выше точки привязки в принципе:
         · "предстоящие" (--stack-n > 0, ещё не дошли) ждут в одной и той
           же точке чуть НИЖЕ фокуса (не расходятся по расстоянию — все
           одинаково спрятаны за фокусной), и въезжают в фокус СНИЗУ по
           мере приближения — то самое "выезжают снизу и складируются на
           предыдущую" из фидбэка;
         · "пройденные" (--stack-n < 0) после этого продолжают тот же путь
           вниз, оседая в стопку отыгранных карт — каждая на свою
           глубину, до 5 карточек различимо, дальше все на одной глубине;
         · при обратном скролле — та же самая непрерывная функция от
           --rotate, поэтому анимация автоматически обратима без всякой
           отдельной "обратной" логики.
       Полностью непрозрачные (opacity:1, filter:none) — по фидбэку
       прозрачность на стопке карт читалась как неразбериха, а не как
       глубина; глубину теперь отыгрывают только сдвиг/масштаб/z-index.
       --card-phase/--rotate — те же переменные, что пишет <script> для
       дуги (JS не знает и не должен знать, дуга это или стопка), но БЕЗ
       mod()-обёртки: --card-signed выше специально циклический (даёт
       "гладкий обод" у дуги), тут нужно однонаправленное расстояние в
       карточках, не по кругу — умножение на 9 переводит шаг 1/9 обратно
       в "число карточек расстояния" (±0, ±1, ±2…). */
    @media (max-width: 399px) {
      --stack-n: calc((var(--card-phase) - var(--rotate)) * 9);
      --stack-behind-n: calc(var(--stack-n) * -1);
      /* Было 60px — по фидбэку эффект "выезжает снизу" не читался, потому
         что зона ожидания (60px) и стопка отыгранных (до 80px, см. ниже)
         почти совпадали по глубине — предстоящая и уже пройденная
         карточки оказывались в одной и той же полосе, визуально "куча-
         мала", а не понятная последовательность. 220px — заметно больше
         высоты самой карточки: предстоящая ждёт настолько ниже, что
         фактически ниже видимой зоны стопки (не видна вообще, пока не её
         очередь), и когда приходит её черёд — весь путь наверх, в фокус,
         читается как настоящий "заезд снизу страницы", а не лёгкое
         покачивание. Точка ожидания по-прежнему одна на все предстоящие
         (клампим на 0..1) — едет только та, что на очереди прямо сейчас. */
      /* По фидбэку — эффект "друг на друге" (видны края нескольких
         карточек внахлёст) должен быть у ВЕРХА композиции, ближе к фото,
         а не внизу, как получалось раньше: там пройденные карточки
         уезжали ВНИЗ от фокуса, то есть сама "стопка" визуально
         оказывалась дальше от фото, а не сразу под ним.
         Фикс — фокус больше не сидит вплотную к якорю (фото): вводим
         ANCHOR_BUFFER (60px) — зазор МЕЖДУ фото и фокусной карточкой,
         и именно в этом зазоре теперь живёт стопка пройденных: они едут
         не вниз от фокуса, а ВВЕРХ, к фото, глубже в этот зазор (12px на
         карточку × до 5 = все 60px буфера, у самой глубокой карточки её
         верх ровно у нижнего края фото — не заходит НА него, доезжает
         точно до края, это по-прежнему безопасно). Предстоящие — как и
         раньше, ниже фокуса, просто вся система сдвинута на 60px вниз
         целиком (была точка отсчёта 0, теперь 60). Одна формула вместо
         двух отдельных (ahead/behind) — на --stack-n=0 (чистый фокус) оба
         клампа дают 0, и получается ровно 60px, без задвоения буфера. */
      /* По фидбэку — у нижней (ожидающей) стопки тоже должен быть виден
         эффект складирования, только в противоположную сторону (вниз, а
         не вверх к фото, как у верхней). Раньше ВСЕ ещё не дошедшие
         карточки (--stack-n от 1 до 8) были прижаты к ОДНОЙ и той же
         точке (220px) — clamp(0,--stack-n,1) выше единицы уже не растёт,
         поэтому 2-я, 5-я, 8-я карточки в очереди визуально совпадали,
         никакой "стопки" не читалось. --stack-ahead-far-n — расстояние
         СВЕРХ первого шага (того самого "въезда в фокус", он остаётся
         прежним, 0→220px) — до 5 дополнительных карточек, каждая ещё
         на 15px ниже: сама первая (следующая по очереди) стоит на 220px,
         вторая на 235, и так далее, пятая и дальше — на 295px, дальше
         уже сливаются, как и в верхней стопке. */
      /* Въезд 220px и ступень 15px урезаны до 110px и 8px. Причина —
         не эстетика, а отступ перед "Профиль", на который жаловались.
         Резерв места (см. requiredStageTopDoc в script.js) обязан покрыть
         САМЫЙ НИЗКИЙ край композиции за всю прокрутку, а это момент, когда
         очередь ещё полная: 60 (буфер) + 220 (въезд) + 75 (ступени) + 223
         (высота карточки) = 578px ниже якоря. Но в КОНЦЕ прокрутки очередь
         разобрана и низ composition'а всего 294px — то есть 284px
         зарезервированного места превращаются в пустоту ровно тогда, когда
         пользователь докрутил колесо и смотрит на результат. Это и есть
         тот самый "большой отступ": он не в бюджете прокрутки и не в
         формуле, он в глубине очереди.
         110+40 вместо 220+75 уполовинивает эту разницу (578→433), сохраняя
         и читаемый "въезд снизу" (полкарточки хода), и видимую ступенчатую
         очередь. Свести к нулю нельзя в принципе: любая ВИДИМАЯ очередь
         ниже фокуса — это место, которое приходится резервировать. */
      --stack-ahead-far-n: clamp(0, calc(var(--stack-n) - 1), 5);
      --stack-y: calc(60px + clamp(0, var(--stack-n), 1) * 110px + var(--stack-ahead-far-n) * 8px - clamp(0, var(--stack-behind-n), 5) * 12px);
      --stack-scale: calc(1 - clamp(0, var(--stack-behind-n), 5) * 0.02 - var(--stack-ahead-far-n) * 0.02);
      /* z-index предстоящей карточки — второй заход, первый (простой рост
         бонуса от 0 до +50 по мере приближения к фокусу) сам был с
         дефектом: бонус НАСЫЩАЛСЯ (упирался в потолок) уже при --stack-n=1
         и дальше не рос — то есть ЛЮБАЯ ещё не начавшая двигаться карточка
         (--stack-n=2,3…8, вообще все, кто ещё далеко в очереди) сидела
         на ТОМ ЖЕ максимальном бонусе, что и карточка, которая уже
         реально в процессе подъёма. Раз они ВСЕ пришиты к максимуму
         одновременно — какая из них физически выше остальных, решал уже
         не сценарий, а порядок в DOM (обычно последняя карточка,
         "AI-прокачка" — она и "лежала сверху, не двигаясь", закрывая
         собой ту, что реально едет).
         Фикс — треугольник, а не ступенька: бонус растёт от 0 (на самом
         старте пути, --stack-n=1) к пику в середине перехода и обратно к 0
         на приходе в фокус (--stack-n=0) — а для ВСЕХ карточек за
         пределами [0,1] (то есть для тех, чья очередь ещё не подошла
         вовсе) бонус честный ноль, не потолок. Тем самым только ТА
         карточка, что реально сейчас едет, когда-либо получает бонус
         больше нуля — с остальными (и с уже пройденными через отдельный
         --stack-behind-n штраф) она не спорит за z вообще. min() из двух
         полуоткрытых клампов даёт именно такую форму, и оба конца (0 и 1)
         остаются точно нулевыми независимо от лерпа рядом (те же честные
         границы у clamp(), что и раньше, скачка в фокусе как не было, так
         и нет). */
      --stack-ahead-bonus: min(clamp(0, var(--stack-n), 1), clamp(0, calc(1 - var(--stack-n)), 1));
      /* Та же логика глубины, что и у --stack-y/--stack-scale выше: чем
         дальше в очереди ожидания карточка, тем она чуть ниже в z —
         ближайшая по очереди читается чуть "поверх" тех, что за ней.
         ВАЖНО: штраф очереди берётся от --stack-n, а НЕ от
         --stack-ahead-far-n. Последняя = clamp(0, stack-n - 1, 5), то есть
         равна нулю И для фокусной карточки (stack-n=0), И для следующей за
         ней в очереди (stack-n=1). Бонус --stack-ahead-bonus на обоих этих
         концах тоже честный ноль. В итоге у фокусной и у следующей z-index
         совпадал (ровно 600), ничья разрешалась порядком в DOM — и
         следующая карточка, стоящая на 110px ниже, накрывала собой низ
         фокусной. Именно это и было видно как "часть первой карточки
         перекрыта другой". clamp(0, stack-n, 5) даёт очереди ненулевой
         штраф уже с первого шага, и фокусная всегда выше очереди. */
      --stack-ahead-z-n: clamp(0, var(--stack-n), 5);
      z-index: calc(600 + var(--stack-ahead-bonus) * 60 - clamp(0, var(--stack-behind-n), 5) * 10 - var(--stack-ahead-z-n) * 6);
      /* --card-x/--card-drop/--card-lift (дуга) тут не участвуют вовсе —
         только вертикальный сдвиг и масштаб, никакого горизонтального
         движения (стопка держит X ровно по центру, туда её и центрирует
         базовый margin выше). */
      transform: translateY(var(--stack-y)) scale(var(--stack-scale));
      filter: none;
      opacity: 1;
    }
  }
}

/* Градиент-виньетка по краям дуги — тёмные зоны у левого/правого края
   экрана "гасят" карточки под цвет фона, центр остаётся прозрачным и не
   мешает. Было 260px — по факту это трогало только уже почти выпавшие
   за экран карточки (dist≈0.4+), а те, что просто "тускловаты" (opacity
   0.3-0.6) сидели в полностью прозрачной середине и никакого затемнения
   не получали — проверено расчётом фактических координат карточек в
   браузере. 440px правильно накрывает как раз эту зону. Особенность
   позиционирования: .arc-wrapper сам имеет transform (translateX для
   центрирования), а элемент с transform становится containing block для
   position:fixed потомков — обычный "фикс на весь вьюпорт" тут не сработал
   бы. Вместо этого — классический приём для absolute-потомка "во всю
   ширину экрана независимо от ширины родителя": left:50% + margin-left:
   -50vw + width:100vw. .arc-wrapper и так центрирован по горизонтали
   (тем же translateX(-50%)), так что 50% его собственной ширины — это и
   есть центр вьюпорта, от него трюк работает верно. Прозрачность/позиция
   по вертикали наследуются от родителя автоматически: это дочерний
   элемент .arc-wrapper, а его opacity/top пишет тот же JS, что и раньше
   (см. apply() в <script>) — отдельно ничего не позиционируем. */
.arc-wrapper > .arc-fade{
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 100vw;
  margin-left: -50vw;
  pointer-events: none;
  z-index: 650; /* выше любой карточки (максимум z-index карточки — 600) */
  background: linear-gradient(
    to right,
    var(--bg) 0,
    transparent 560px,
    transparent calc(100% - 560px),
    var(--bg) 100%
  );
}
/* На 480-1219 (мобильный режим колеса) 440/560px с каждого края засветили
   бы весь экран целиком — своя, гораздо более узкая зона у истинного
   края. Было 48px, по фидбэку "сильнее" — 90px. */
@media (max-width: 1219px){
  .arc-wrapper > .arc-fade{
    background: linear-gradient(
      to right,
      var(--bg) 0,
      transparent 90px,
      transparent calc(100% - 90px),
      var(--bg) 100%
    );
  }
}
/* На ≤399 (стопка, не дуга) карточки вообще не двигаются по горизонтали
   и никогда не уезжают за левый/правый край — виньетка, заточенная под
   горизонтальное "утекание" карточек дуги, тут просто не у дел. */
@media (max-width: 399px){
  .arc-wrapper > .arc-fade{ display: none; }
}

/* ===== Внешний вид карточек — из achievement-final-selected.html =====
   Перенесено 1:1, кроме двух вещей:
   1) снят фиксированный размер 240×240 — размер карточки в колесе задаёт
      .arc-wrapper > .hero2-plate (--card-width + aspect-ratio:1);
   2) фиксированные кегли (14/32/14px, рассчитанные на 240px) переведены в
      clamp() — карточка в колесе ужимается до ~220px на 1220 и растёт до
      260px на 1920, а верхняя граница clamp равна исходному значению, то
      есть на 1440 (базовый брейкпоинт макета) размеры совпадают с
      оригиналом до пикселя.
   Разделение ролей: .hero2-plate — геометрия (позиция на дуге, размер,
   прозрачность, z-index), .fcard + .bg-* — внешний вид. Оба класса всегда
   висят на одном элементе.
   --card-purple — НЕ глобальная --purple (#9b7bd9, ею пользуется остальной
   сайт, 1:1 из styles.css): в наборе карточек свой, более светлый
   оттенок, поэтому он локальный и глобальный токен не трогает. */
.fcard{
  --card-purple: #c9b3f5;
  /* вынесено в переменную, потому что этим же отступом позиционируется
     фото в углу (.fcorner-photo) — абсолютный элемент считается от
     padding-box, то есть про padding карточки он сам не знает */
  --fcard-pad: clamp(14px, 1.25vw, 18px);
  border-radius: 18px;
  padding: var(--fcard-pad);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  overflow: hidden;
  isolation: isolate;
  font-family: 'Inter', sans-serif;
}
.fcard.sparse{ justify-content: flex-end; }
.fcard.bg-green{ background: var(--green); color: var(--bg); }
.fcard.bg-purple{ background: var(--card-purple); color: #241a33; }
.fcard.bg-standard{ background: var(--panel); border: 1px solid var(--panel-line); color: var(--text); }
.fcard.bg-standard::before{
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(circle at 25% 15%, rgba(166,224,138,0.16), transparent 70%),
    radial-gradient(circle at 85% 90%, rgba(75,183,255,0.14), transparent 70%);
}
/* На ≤399 (стопка) "стандартные" карточки лежат друг на друге и на фото —
   var(--panel) это всего 4% белого поверх фона, почти прозрачно, поэтому
   через фокусную карточку просвечивало то, что под ней (фото, соседняя
   карточка), и границу между ними было не разобрать. Непрозрачный литерал
   ниже — это ровно то, во что визуально сводится --panel (4% белого) поверх
   --bg (#14161a), просто заранее посчитано и залито сплошным цветом, а не
   полупрозрачным слоем — тон тот же, что и everywhere на сайте, но теперь
   не зависит от того, что находится под карточкой. */
@media (max-width: 399px){
  .fcard.bg-standard{ background: #1d1f23; }
}
/* min-height:0 — иначе flex-элемент не может стать ниже своего контента, и
   средний блок с крупной SVG распирал карточку выше 100% (у «Отдела» и
   «IT-опыта» контент вылезал на 2-4px и обрезался overflow:hidden). Вместе
   с max-height у svg ниже это даёт правильное поведение: сначала ужимается
   картинка, а не ломается карточка. */
.fcard > *{ position: relative; z-index: 1; min-height: 0; }

.fcard .ftitle{ font-family:'IBM Plex Mono', monospace; font-size:clamp(12px, 0.97vw, 14px); font-weight:600; text-transform:uppercase; letter-spacing:.05em; margin-bottom:10px; }
.fcard .fnum{ font-family:'IBM Plex Mono', monospace; font-size:clamp(26px, 2.22vw, 32px); font-weight:700; line-height:1; }
.fcard .flabel{ font-size:clamp(12px, 0.97vw, 14px); opacity:.75; margin-top:5px; line-height:1.3; }
.fcard .ftext{ font-size:clamp(12px, 0.97vw, 14px); opacity:.8; line-height:1.4; }
.fcard .fhead{ display:flex; align-items:center; gap:10px; margin-bottom:10px; }
.fcard .fhead .ftitle{ margin-bottom:0; }
/* Круглое фото в правом верхнем углу карточки. Селектор из двух классов
   (0,2,0) специально «тяжелее», чем .fcard > * (0,1,0) — иначе то правило
   сбросило бы position:absolute обратно в relative и фото уехало бы в
   поток. Рамка тёмная, а не белая: этот блок живёт на салатовой карточке,
   где белая полупрозрачная рамка не читается. */
.fcard .fcorner-photo{
  position: absolute;
  top: var(--fcard-pad);
  right: var(--fcard-pad);
  width: 90px; height: 90px;
  border-radius: 50%;
  overflow: hidden;
  border: 2px solid rgba(20,22,26,.18);
  z-index: 2;
}
.fcard .fcorner-photo img{
  width: 340%; max-width: none; height: 340%;
  object-fit: cover; object-position: 46% 20%;
}
/* То же позиционирование в угол, что у .fcorner-photo, но без круглой
   обрезки/рамки — для некруглых картинок (SVG-иконки/схемы), у карточки
   "3 команды" (радар). */
.fcard .fcorner-icon{
  position: absolute;
  top: var(--fcard-pad);
  right: var(--fcard-pad);
  z-index: 2;
}
/* max-width/max-height, но БЕЗ height:auto: у части карточек width/height
   заданы не в пропорции viewBox (напр. 116×92 при viewBox 140×118), и
   height:auto растянул бы их против задумки. max-height:100% же ничего не
   растягивает — только ужимает вьюпорт SVG, когда карточка тесная, а сама
   картинка внутри честно вписывается за счёт preserveAspectRatio. */
.fcard svg{ display:block; max-width:100%; max-height:100%; }

/* ===== ≤399 — текст крупнее пола clamp(), но умереннее прежнего =====
   Карточка теперь 3/4 экрана (было "почти во весь"), поэтому и текст
   умерил — 44/16px были откалиброваны под 380px-карточку, на нынешних
   240-300px это уже само выглядело бы непропорционально крупно. Взял
   значения ближе к десктопному ПОТОЛКУ clamp() (32/14px) — тот
   недостижим на этих vw без override, но как фиксированные числа на
   карточке 240-300px смотрится соразмерно. Причина override та же:
   clamp() у .fnum/.ftitle/.flabel/.ftext завязан на vw ВЬЮПОРТА, а не на
   ширину карточки — на 320-399px vw слишком мал, и не помогает то, что
   сама карточка сузилась. Иконки не трогаем — риск сломать выравнивание
   в углу. */
@media (max-width: 399px){
  .fcard{ --fcard-pad: 18px; }
  .fcard .ftitle{ font-size: 14px; margin-bottom: 10px; }
  .fcard .fnum{ font-size: 32px; }
  .fcard .flabel{ font-size: 14px; margin-top: 6px; line-height: 1.35; }
  .fcard .ftext{ font-size: 13px; line-height: 1.4; }
}


/* ---- 2.1 / 2.2 гигантский заголовок (z-index:2) ----
     Обе строки — независимые absolute-блоки, каждая анкерится к своему краю
     контента (left / right) и получает ширину по своему контенту.
     Осознанные отступления от design-system.md:
       · font-size в vw, а не с фиксированной шкалы — заголовок здесь
         масштабируется от ширины вьюпорта (референс 2560px), это тот же
         класс исключения, что и задокументированный h1.name 90px на ≥1920;
       · line-height 0.9 вместо heading-роли 1.2 — плотная «лесенка» из двух
         строк держится только на таком интерлиньяже. */
.hero-name-1{
    position:absolute; left:var(--gutter); top:17%; z-index:2;
    font-family:'Space Grotesk', sans-serif;
    font-size:13vw; font-weight:700; line-height:0.9;
    text-transform:uppercase; color:var(--text);
    white-space:nowrap;
  }

.hero-name-2-wrap{
    position:absolute; right:var(--gutter); top:36%; z-index:2;
  }

.hero-name-2{
    font-family:'Space Grotesk', sans-serif;
    font-size:13vw; font-weight:700; line-height:1;
    text-transform:uppercase; color:var(--text-3);
    white-space:nowrap;
  }

/* ---- 2.3 верхняя строка: теги локации (z-index:3) ----
     Роль здесь больше не живёт — перенесена вниз, к саммари (см. .hero-role
     ниже). Контейнер сохранён (а не заменён на прямые правила у .hero-tags)
     ради роста вверх при переносе тегов на несколько строк: см. следующий
     комментарий. */
.hero-topline{
    position:absolute; left:var(--gutter); z-index:3;
    /* Закреплён НИЗ строки (8% + высота одного ряда тегов), а не верх.
       Вместе с align-items:flex-end это даёт рост вверх: при переносе тегов
       нижний ряд остаётся на исходной линии, а новые ряды уходят к верхнему
       краю. Строка не наезжает на "БАКАЛА" (top:17%) ни при каком
       соотношении сторон — проверено вплоть до 1220×700, где вариант с
       ростом вниз перекрывал имя. */
    top:calc(8% + 40px); transform:translateY(-100%);
    display:flex; align-items:flex-end; justify-content:flex-start;
  }

/* Ряд тегов — 1:1 .meta-row на живом сайте: flex-row, wrap. */
.hero-tags{
    display:flex; flex-direction:row; flex-wrap:wrap;
    align-items:center; justify-content:flex-start; gap:8px;
  }

/* Тег — 1:1 .meta-chip: border-radius:8px (не пилюля), padding:8px 12px,
     серый в покое (--text-2), белеет по hover, иконка-пин у первого тега.
     cursor:default — как у .meta-chip: элемент некликабельный, курсор не
     должен обещать обратного. */
.hero-tag{
    display:flex; align-items:center; gap:8px;
    font-family:'IBM Plex Mono', monospace;
    border:1px solid var(--panel-line); border-radius:8px;
    background:var(--panel);
    padding:8px 12px; font-size:16px; line-height:1.4; color:var(--text-2);
    white-space:nowrap; cursor:default;
    transition:border-color .2s, background .2s, color .2s;
  }

.hero-tag:hover{
    border-color:rgba(255,255,255,0.18);
    background:rgba(255,255,255,0.07);
    color:var(--text);
  }

.hero-tag svg{ flex-shrink:0; }

/* ---- 2.4 фото (z-index:4) ----
     Все фильтры/эффекты — 1:1 из styles.css (.photo на живом сайте):
     object-fit/position, filter и двойная mask-image-подсветка снизу
     (mask-composite:intersect). top:40%/left:50% — позиция на первом
     экране, до скролла; дальше top каждый кадр переписывает инлайном
     скрипт внизу страницы (см. "Скролл-пин") — сам на fixed переключает
     тоже он. transform:translateX(-50%) (центрирование) скрипт не трогает
     — работает одинаково что при position:absolute (как сейчас), что при
     position:fixed, которую скрипт выставит. */
.hero-photo{
    position:absolute; top:40%; left:50%; transform:translateX(-50%);
    width:30%; z-index:4;
    display:block; pointer-events:none;
  }

/* Внутренняя обёртка — самостоятельная точка отсчёта для свечения,
     не зависящая от того, что .hero-photo (внешний блок) в тире ≤1219
     теряет position:absolute и уходит в обычный поток. Благодаря этому
     свечение не «убегает» к .hero при пересчёте раскладки на планшете. */
.hero-photo-inner{ position:relative; width:100%; }

/* Свечение за портретом — тот же радиальный градиент (бело-голубой →
     синий → фиолетовый), тот же blur и та же пульсация, что и на живом
     сайте, но по запросу — по центру кадра, а не смещено вверх-вправо:
     box 110%×110% с равным отступом -5% со всех сторон, фокус градиента
     50% 50%. z-index ниже, чем у img (которому даём z-index:1 ниже), —
     свечение видно по контуру и через полупрозрачный край маски, но не
     поверх лица. */
.hero-photo-glow{
    position:absolute;
    /* top поднят на треть собственной высоты блока (110% высоты фото):
       было -5%, -110%/3 ≈ -36.7%, итого -41.7% — по запросу. left/width/
       height не трогал: горизонталь и размер пятна не менялись, только
       вертикальная позиция. */
    top:-41.7%; left:-5%; width:110%; height:110%;
    background:radial-gradient(
      circle closest-corner at 50% 50%,
      rgba(210,225,255,0.8) 0%,
      rgba(75,183,255,0.15) 40%,
      rgba(120,100,200,0.5) 65%,
      transparent 70%
    );
    filter:blur(45px);
    z-index:0; pointer-events:none;
    animation:heroGlowPulse 6s ease-in-out infinite alternate;
  }

@keyframes heroGlowPulse{
    0%  { opacity:.75; transform:scale(1); }
    100%{ opacity:1;   transform:scale(1.04); }
  }

@media (prefers-reduced-motion: reduce){
    .hero-photo-glow{ animation:none; }
  
  }

.hero-photo img{
    position:relative; z-index:1;
    display:block; width:100%; height:auto;
    object-fit:contain;
    object-position:center 15%;
    -webkit-mask-image:
      linear-gradient(180deg, black 30%, transparent 100%),
      linear-gradient(to bottom, black 45%, transparent 65%);
    mask-image:
      linear-gradient(180deg, black 60%, transparent 100%),
      linear-gradient(to bottom, black 45%, transparent 65%);
    mask-composite:intersect;
    -webkit-mask-composite:source-in;
    filter:grayscale(.6) contrast(1.05) brightness(1.1);
  }

/* ---- 2.5 роль (z-index:3) — низ слева, на одном уровне с саммари ----
     top:70% — то же значение, что у .hero-summary: оба блока стоят на
     одной горизонтали, роль слева, саммари справа. Раньше здесь был CTA
     (кнопка+QR), потом ссылка "Контакты" — оба убраны, роль встала на их
     место. line-height 1.4 и letter-spacing 0.05em — AUX-роль по
     design-system.md. */
.hero-role{
    position:absolute; left:var(--gutter); top:70%; z-index:3;
    display:flex; align-items:center;
  }

.hero-role__line{
    font-family:'IBM Plex Mono', monospace;
    font-size:21px; font-weight:600; line-height:1.4; color:var(--green);
    text-transform:uppercase; letter-spacing:0.05em;
    white-space:nowrap;
  }

/* ---- 2.6 саммари (z-index:3) ----
     top вместо bottom: якорим саммари от заголовка вниз, а не от низа экрана
     вверх — иначе на других пропорциях "Евгений" (top:36% + 13vw) мог бы
     дорасти до саммари снизу. top совпадает с .hero-tags-block — оба блока
     выровнены по верхнему краю. 18px / 1.45 — body-роль по design-system.md. */
.hero-summary{
    position:absolute; right:var(--gutter); top:70%; z-index:3;
    width:30%;
    /* Явный font-family, а не унаследованный: в песочнице это давал
       body{font-family:'Inter'}, в проде та же роль — на .stage. Герой
       живёт вне .stage (см. комментарий у .hero-photo-glow ниже про
       контекст наложения), поэтому без своего font-family здесь падало
       на браузерный Times New Roman по умолчанию. */
    font-family:'Inter', sans-serif;
    color:var(--text-2); font-size:18px; line-height:1.45;
  }

/* ===================== АДАПТИВ =====================
     Тиры — из фиксированного списка design-system.md
     (≤1439 · ≤1219 · ≤1079 · ≤899 · ≤679 · ≤479). Сам заголовок и позиции
     блоков заданы в vw/%, поэтому тирами правятся только фиксированные в px
     компоненты (теги, кнопка, QR, саммари, роль) и точка слома раскладки. */

  /* ≤1219 — раскладка распадается в одну колонку.
     Именно здесь, а не ниже: на 1080–1219px абсолютная композиция ломается —
     фото (z-index:4) наезжает на ряд тегов и перекрывает их текст. */
@media (max-width:1219px){
    .hero{
      height:auto; overflow:visible;
      /* Сверху/снизу 24px — как .stage{padding-top:24px} на живом сайте. */
      padding:24px var(--gutter);
      display:flex; flex-direction:column; gap:24px;
    }

    /* Правило .hero-bg{position:absolute} здесь больше не нужно: фон стал
       общим для всей страницы (.page-glow), запирать его в бокс героя не
       требуется ни на одном тире — он и должен покрывать всё. */
    .hero-topline, .hero-role, .hero-name-1, .hero-name-2-wrap,
    .hero-summary{
      position:static; transform:none; width:auto;
    }
    .hero-topline{ order:1; }
    .hero-role__line{ white-space:normal; }
    .hero-name-1{ order:2; font-size:15vw; white-space:normal; }

    /* Каскад БАКАЛА/ЕВГЕНИЙ — тот же принцип, что и на десктопе (там —
       left:var(--gutter) у первой строки, right:var(--gutter) у второй,
       разный top), только средствами flex вместо absolute: имя-1 остаётся
       растянутым на всю ширину колонки (текст естественно прижат к левому
       краю), а у имя-2-wrap align-self:flex-end сжимает обёртку по
       контенту и прижимает К ПРАВОМУ краю — та же "лесенка", что в
       скрине. margin-top в vw (не px) — пропорционально шрифту (тоже в
       vw), чтобы нахлёст оставался тем же на любой ширине экрана: гасит
       обычный gap колонки (24px/20px) и добавляет небольшой нахлёст
       поверх — то же самое соотношение строк, что и на скрине (без
       зазора, чуть внахлёст). */
    .hero-name-2-wrap{ order:3; align-self:flex-end; margin-top:-4.5vw; }
    .hero-name-2{ font-size:15vw; white-space:normal; line-height: 0.9; }

    /* По запросу роль ("Head of...") и саммари ("Формирую...") переехали
       выше фото — сразу под именем (order 4/5), а .hero-photo-cluster
       (фото+карточки) — ниже них (order:6, было 4). Раньше порядок был
       топлайн→имя→фото→роль→саммари, теперь топлайн→имя→роль→саммари→
       фото. На отступ margin-top у кластера (нужен под карточку "top",
       которая торчит выше границы кластера) это не влияет — он считается
       от границы БЛИЖАЙШЕГО соседа сверху по потоку, кем бы он ни был. */
    .hero-role{ order:4; }
    .hero-summary{ order:5; }

    /* .hero-photo-cluster занимает то место в потоке, где раньше стояло
       голое .hero-photo. По фидбэку колесо теперь работает и на этом тире
       (480-1219, см. .arc-wrapper и <script> — режим 'mobile'), не только
       на десктопе — order:6 переехал на обёртку .arc-runway (см. разметку),
       а margin кластера пересчитан с нуля: старые 125px/20px были под
       нахлёст пяти .hero-plate, у дуги просадка карточек всегда ≥0
       (только вниз, и под это отдельно резервирует место JS), так что тут
       нужен только небольшой воздух, не компенсация под несуществующий
       элемент. */
    .arc-runway{
      display:block;
      order:6; width:100%;
    }
    .hero-photo-cluster{
      /* display:block явно перебивает базовый display:contents (см.
         правило выше, вне медиа-запроса) — без него position:relative
         тут не создаёт containing block вообще (contents-элемент не имеет
         собственного бокса). margin:auto вместо align-self:center — в
         мобильном режиме колеса кластер становится position:sticky (JS),
         margin:auto по горизонтали работает как обычно и там. */
      display:block;
      position:relative;
      width:min(70%, 360px);
      margin:24px auto 16px;
    }
    .hero-photo{ position:static; transform:none; width:100%; }

    /* На десктопе низ фото уходит за край экрана и прозрачный хвост маски
       не виден. В потоке он виден как пустота: второй градиент маски
       (black 45% → transparent 65%) гасит всё ниже 65% высоты, т.е. ~35%
       бокса были пустыми. Подрезаем бокс ровно по границе видимого —
       768 × (1069 × 0.65). Тот же приём, что .header-wrap .photo{aspect-ratio}
       на живом сайте; ни один видимый пиксель не теряется. */
    .hero-photo img{
      aspect-ratio:768 / 695;
      object-fit:cover;
      object-position:center top;
      /* Маска считается от бокса элемента, а не от пикселей картинки,
         поэтому вместе с подрезкой бокса пересчитаны и её стопы: исходная
         гасила фото на 45%→65% своей высоты, после подрезки до 65% те же
         точки приходятся на 69%→100% нового бокса. Слой один, поэтому
         mask-composite сбрасывается с intersect. */
      -webkit-mask-image:linear-gradient(to bottom, black 69%, transparent 100%);
      mask-image:linear-gradient(to bottom, black 69%, transparent 100%);
      -webkit-mask-composite:source-over;
      mask-composite:add;
    }

  
  }

/* 900-1219 — "Head of..." и "Формирую..." снова в два столбца, как на
     десктопе (там оба на одном top:70%, роль слева/саммари справа) — по
     запросу, именно для этого промежуточного тира: ширины уже хватает на
     колонку, а ниже 900 (см. ≤899 дальше) — нет, там остаётся вертикальный
     стек, как было. Средствами grid, а не flex: во flex-column нельзя
     поставить рядом два элемента, не смежных по order, — понадобился бы
     реальный HTML-обёртка. С grid — у каждого прямого ребёнка .hero свой
     grid-area, роль и саммари делят одну строку в двух колонках, у
     остальных area повторяется в обеих колонках (растягивает на всю
     ширину, как и было во flex). row-gap 24px — то же значение, что и
     flex{gap} у соседнего тира, чтобы вертикальные отступы не менялись
     при переходе через границы 899/1220. */
@media (min-width:900px) and (max-width:1219px){
    .hero{
      display:grid;
      /* 1fr 1fr — по запросу строго 50/50, а не по контенту роли
         (было max-content). */
      grid-template-columns:1fr 1fr;
      grid-template-areas:
        "topline topline"
        "name1 name1"
        "name2 name2"
        "role summary"
        "photo photo";
      align-items:start;
      gap:24px 48px;
    }
    .hero-topline{ grid-area:topline; }
    .hero-name-1{ grid-area:name1; }

    /* align-self здесь — вертикальная ось (не горизонтальная, как во
       flex-column у базового ≤1219 правила выше) — унаследованный оттуда
       align-self:flex-end под grid означал бы "прижать к низу строки", а
       не "прижать вправо". Сбрасываем и переносим тот же эффект (прижать
       к правому краю, сжавшись по контенту) на justify-self:end — это и
       есть горизонтальная ось в grid. */
    .hero-name-2-wrap{ grid-area:name2; align-self:auto; justify-self:end; }
    .hero-role{ grid-area:role; }

    /* width:auto уже стоит в ≤1219 (общее правило со статикой), здесь
       достаточно назначить область сетки. */
    .hero-summary{ grid-area:summary; }

    /* justify-self:center — та же самая ловушка оси, что и у имя-2 выше:
       align-self:center, унаследованный из flex-правила базового ≤1219
       (там центрировал ПО ГОРИЗОНТАЛИ, поперечная ось flex-column), в
       grid означает центр ПО ВЕРТИКАЛИ — реальная горизонтальная ось тут
       justify-self, без него блок (у него есть свой width:min(70%,360px))
       прилипал к левому краю на всю строку "photo photo" — отсюда и
       "фото с плашками ушли влево". */
    .arc-runway{ grid-area:photo; justify-self:center; }
  
  }

/* ≤899. Горизонталь (нахлёст в % ширины фото) целиком наследуется от
     ≤1219 — она уже описывает десктопную расстановку и от ширины экрана
     не зависит. Здесь переопределяется только то, что от неё зависит:
     ШИРИНА карточки (полные 190px помещаются не на всей ширине тира) и
     вертикали (высота карточки меняется с её шириной). */
@media (max-width:899px){

    /* margin-top 117 — под "top" (высота до 81px + десктопные 24px
       зазора до фото, см. ≤1219). */
    .hero-photo-cluster{ margin:20px auto 16px; }


    /* Кнопка "Написать в Telegram" убирается уже с этого тира (по
       запросу) — раньше уходила только ниже 680. */
    .top-contact-bar__contacts .cta-contacts__button{ display:none; }

  
  }

@media (min-width: 1220px) and (max-width: 1439px){
    .hero-name-2 { line-height:0.7; }
  
  }

/* ≤1079 — типографские полы. Тир взят у живого сайта: там с него же
     .cta-contacts__button получает 14px и min-height:44px под тач-таргет. */
@media (max-width:1079px){
    .hero-summary{ font-size:16px; }

    /* БАКАЛА/ЕВГЕНИЙ — тот же приём, что и на ≤479 (см. подробный
       комментарий там): почти без просвета между строками. margin-top у
       роли — тот же явный запас, чтобы "Head of..." не липло к ЕВГЕНИЙ. */
    .hero-role{ margin-top:12px; }
  
  }

/* ≤679 */
@media (max-width:679px){
    .hero-role__line{ font-size:16px; }
    .hero-tag{ font-size:12px; padding:4px 8px; gap:4px; border-radius:4px; }
    .hero-tags{ gap:4px; }
    .hero-summary{ font-size:14px; }

    /* На ≤899 (см. выше) поля по бокам фото ещё хватало на карточки без
       урезания самого фото. Здесь, на нижней границе тира (480px), поля
       остаётся уже совсем немного (фото — min(70%,360px) — без изменений
       съедало бы почти всю ширину экрана) — поэтому здесь уменьшено ещё и
       само фото (60% вместо 70%, потолок 240px вместо 360px), чтобы у
       карточек осталось, где стоять. Внутри этого тира кластер держит
       константные 240px на всей ширине 480–679 (0.6×(vw−40) уже ≥240 при
       vw≥440). */
    /* margin-top 122 — под поднятую "top" (−110px при высоте до 100px).
       margin-bottom 175 — под самую нижнюю пару. */
    .hero-photo-cluster{ width:min(60%, 240px); margin:16px auto 12px; }

  
  }

/* ≤479 */
@media (max-width:479px){
    .hero{ gap:20px; }
    .hero-role__line{ font-size:14px; }

    /* БАКАЛА/ЕВГЕНИЙ — по запросу почти без просвета между строками
       (line-height:0.9 на 15vw шрифте оставлял заметный зазор — площадь
       строки заметно больше самих чернил буквы). .hero-name-1 и
       .hero-name-2-wrap — два независимых flex-элемента (не две строки
       внутри одного блока), расстояние между ними — общий .hero{gap:20px},
       который также действует МЕЖДУ ВСЕМИ остальными парами блоков (в т.ч.
       дальше — ЕВГЕНИЙ и "Head of..."), поэтому здесь его не трогаем.
       line-height:0.2 первым заходом реально накладывал буквы друг на
       друга (поймано на скриншоте — координаты боксов показывали 0
       зазора, но чернила вылезали далеко за пустые края бокса). 0.5 по
       следующему запросу — безопаснее (бокс строки заметно выше самих
       чернил буквы уже не так сильно, запас перекрытия меньше), при этом
       видимый зазор между строками всё ещё заметно уже, чем при
       исходных 0.9. */
    .hero-name-1, .hero-name-2{ line-height:0.8;}

    /* "Head of..." после сжатия зазора выше не касается ЕВГЕНИЙ и без
       этого margin (та же логика: их зазор — обычный 20px gap, не сжатый
       ничем), но по запросу гарантированно не должен слипаться — явный
       запас добавлен всё равно. */
    .hero-role{ margin-top:12px; }

    /* margin-top 130 — под "top" (высота до 94px + десктопные 24px
       зазора до фото, см. ≤1219).
       margin-bottom 245 — под самую нижнюю пару (role/shoulder), по
       ХУДШЕЙ высоте во всём диапазоне тира: на 10px-описании (см.
       __text/__label ниже) пик приходится на узкий край (320px, shoulder
       уходит на 267px ниже кромки кластера), а не на широкий, как можно
       было бы ожидать, — карточка с более узким полем сильнее переносит
       строки. 275 (прежнее значение) было посчитано под 12px-текст и
       после перехода на 10px оставляло 32-140px пустого места снизу. */
    /* Было 44%/190px — по фидбэку "места хватает, не надо присматриваться к
       лицу". 75%/210px крупнее (место освободилось после того, как большие
       верхний/нижний отступы под старые пять .hero-plate убрали ранее). */
    .hero-photo-cluster{ width:min(75%, 210px); margin:16px auto 12px; }


  }

/* 349-479px — по запросу "Head of Product Marketing & Growth" должна идти
   одной строкой, а не переноситься по <br> из разметки. white-space:nowrap
   перебивает normal, включённый на ≤1219 (там же разметка полагается на
   явный <br> в index.html — "Head of Product Marketing" / "& Growth");
   прячем сам <br>, а не просто чиним white-space (без display:none на нём
   перенос всё равно случился бы в месте тега).
   Нижняя граница — не ≤479 целиком: при 14px (тот же пол читаемости, что
   и у остального контента на этом тире, см. design-system.md) строка
   занимает 309px, а доступная ширина = вьюпорт − 2×20px гуттера. Она
   перестаёт хватать уже при вьюпорте <349px — на 320-348px (самые узкие
   реальные телефоны) текст либо обрезался бы (overflow-x:clip на
   html/body глушит горизонтальный скролл молча, без видимой полосы —
   поймано прямым замером rect, не на глаз), либо потребовал бы шрифта
   мельче общего пола 14px. Вместо этого на самой узкой полосе (≤348, ниже
   этого блока) правило просто не действует — там снова работает
   унаследованный с ≤1219 перенос по <br>, как было всегда. */
@media (min-width:349px) and (max-width:479px){
    .hero-role__line{ white-space:nowrap; }
    .hero-role__line br{ display:none; }
  }

/* Микро-тир ≤340 из design-system.md. */
@media (max-width:340px){



    /* По запросу убран визуальный "чип" у тегов локации/условий (фон +
       рамка + паддинг) — остаётся только текст и иконка. Теги и так
       некликабельны (cursor:default, см. .hero-tag выше) — тач-таргет
       ничем не жертвует. Hover не трогаем: на тач-устройствах он почти не
       срабатывает, а по факту это тот же самый .hero-tag:hover, что и
       везде — просто здесь не будет визуального повода его увидеть. */
    .hero-tag{ background:transparent; border:none; border-radius:0; padding:0; }
  
  }

/* ---- Точечная сетка с подсветкой под курсором ----
     Слой ровно между .hero-bg (z-index:0, радиальные градиенты) и контентом
     героя (у того z-index от 2). position:fixed — по той же причине, что и у
     .hero-bg: во время скролл-пина бокс .hero уезжает выше вьюпорта, и любой
     absolute-слой обрезался бы по его нижней границе тем же самым швом, что
     чинили у градиентов. pointer-events:none обязательны: сетка лежит поверх
     .hero-bg, и без этого она перехватывала бы hover у тегов, контактов и
     плашек — координаты курсора скрипт и так берёт с window, свои слушатели
     канвасу не нужны. */
.hero-dots{
    /* width/height:100% обязательны: canvas — replaced-элемент, и в отличие
       от div, position:fixed; inset:0 его НЕ растягивает — без явного
       размера канвас остаётся на intrinsic 300×150 (стандартный дефолт
       HTML canvas) и просто прибивается в угол вьюпорта. */
    position:fixed; inset:0; width:100%; height:100%;
    z-index:1; pointer-events:none;
  }

/* ≤899 — тот же порог, что design-system.md берёт для планшетов
     (следующий тир вниз от 1079). Ниже него .hero и так уходит в
     overflow:visible/height:auto (см. @media max-width:1219px) — фон уже
     не полноэкранный fixed-слой, а обычная прокручиваемая композиция, и
     тысячи точек с курсорным следом на тач-экране, где самого курсора нет,
     не несут той же роли, что на десктопе. display:none, а не убранный
     canvas: сам скрипт живёт в DOM и просто не выполняет тяжёлую часть
     работы (см. MOBILE_MIN ниже) — восстановится сам, если экран
     развернуть шире. */
@media (max-width:899px){
    .hero-dots{ display:none; }
  
  }

  /* Ниже 1220px сетку нужно запереть в бокс героя — ровно та же причина и
     те же границы, что у .hero-bg (см. его правило в тире ≤1219): на
     десктопе fixed-слой гасит скролл-пин по мере ухода первого экрана, но
     ниже 1220px пина нет вовсе (DESKTOP_MIN), гасить некому — и сетка
     тянулась бы через все секции резюме до конца страницы.
     Правило стоит здесь, в конце файла, а не рядом с .hero-bg внутри тира
     ≤1219: базовое .hero-dots{position:fixed} объявлено ниже того блока, и
     при равной специфичности побеждало бы оно (проверено — переопределение
     внутри тира не срабатывало). Реально этот тир один: 900–1219px, ниже
     900 сетка и так display:none. */
  @media (min-width:900px) and (max-width:1219px){
    .hero-dots{ position:absolute; }
  
  }
