  /* ============ ШРИФТ ============ */
  /* Montserrat лежит своими файлами, а не грузится со стороннего
     сервера: тот может стать недоступен, и сайт останется без
     шрифта. Заодно ни один запрос посетителя не уходит наружу.

     Кириллица и латиница разными файлами с диапазоном символов:
     браузер забирает только те, что встретил на странице. */
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:400; font-display:swap;
    src:url("/static/fonts/montserrat-400-cyrillic-ext.woff2") format("woff2");
    unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:400; font-display:swap;
    src:url("/static/fonts/montserrat-400-cyrillic.woff2") format("woff2");
    unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:400; font-display:swap;
    src:url("/static/fonts/montserrat-400-latin-ext.woff2") format("woff2");
    unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:400; font-display:swap;
    src:url("/static/fonts/montserrat-400-latin.woff2") format("woff2");
    unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:600; font-display:swap;
    src:url("/static/fonts/montserrat-600-cyrillic-ext.woff2") format("woff2");
    unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:600; font-display:swap;
    src:url("/static/fonts/montserrat-600-cyrillic.woff2") format("woff2");
    unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:600; font-display:swap;
    src:url("/static/fonts/montserrat-600-latin-ext.woff2") format("woff2");
    unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:600; font-display:swap;
    src:url("/static/fonts/montserrat-600-latin.woff2") format("woff2");
    unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:700; font-display:swap;
    src:url("/static/fonts/montserrat-700-cyrillic-ext.woff2") format("woff2");
    unicode-range:U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:700; font-display:swap;
    src:url("/static/fonts/montserrat-700-cyrillic.woff2") format("woff2");
    unicode-range:U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:700; font-display:swap;
    src:url("/static/fonts/montserrat-700-latin-ext.woff2") format("woff2");
    unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
  }
  @font-face{
    font-family:"Montserrat";
    font-style:normal; font-weight:700; font-display:swap;
    src:url("/static/fonts/montserrat-700-latin.woff2") format("woff2");
    unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
  }

/* ============ TOKENS (single committed dark world — glass at night) ============ */
  :root{
    --bg:#0a0d0e;
    --surface:#12181a;
    --surface-2:#1a2124;
    --ink:#f4f2ec;
    --muted:#a3ada8;
    --accent:#c9a769;
    --accent-soft: rgba(201,167,105,.16);
    --accent-ink:#1c140a;
    --accent-2:#5f8083;
    --line: rgba(244,242,236,.12);
    --line-strong: rgba(244,242,236,.24);
    --shadow: 0 30px 70px -25px rgba(0,0,0,.75);
    --shadow-gold: 0 18px 40px -18px rgba(201,167,105,.55);

    /* Та же 1180 при корневом кегле в шестнадцать точек, но в долях от него:
       на большом мониторе колонка растёт вместе со всем остальным. */
    --content-w: 73.75rem;
    --space: clamp(1rem, .6rem + 1.6vw, 2.25rem);

    --fs-h2: clamp(1.7rem, 1.35rem + 1.5vw, 2.6rem);
    --fs-h3: clamp(1.2rem, 1.08rem + .5vw, 1.5rem);
    --fs-lead: clamp(1.05rem, .98rem + .3vw, 1.25rem);
    --fs-body: 1.03rem;
    --fs-label: .76rem;

    /* Два межстрочных интервала на весь сайт: один для текста, другой для
       заголовков. Одним значением не обойтись — при кегле заголовка в
       два с половиной ректа тот же интервал, что у абзаца, разносит строки
       так, что название перестаёт читаться единым целым. */
    /* Снимок за страницей. На главной это природа, на странице работы —
       её обложка: значение подменяется на самом теге body. Одной переменной
       хватает и фону, и боковым панелям — иначе за стеклом оказалась бы
       другая картинка, чем на странице. */
    --page-bg: url("/static/img/backdrop.webp");

    /* Насколько снимок за страницей уводится в тень. Тремя ступенями сверху
       вниз: у края текст начинается сразу, в середине его меньше, у нижнего
       края снова плотно. Значения — доля тёмной заливки поверх кадра.

       Отдельными переменными, потому что природа на главной и обложка работы
       светятся по-разному. Природа тёмная и мягкая (средняя яркость 86),
       обложки — светлые интерьеры с бликами на стекле и металле (медиана 134,
       вдвое подробнее по мелким деталям). Под одной шторкой страница работы
       выходила в полтора раза светлее главной, и подписи на ней тонули.
       Значения ниже подобраны так, чтобы обложка выходила на экран с той же
       яркостью, что природа.

       Шторку пробовали облегчить на одну восьмую — за стеклом читалась сцена,
       но страница выходила светлее, чем нужно, и вернулись к прежней. */
    --scrim-1: .62;
    --scrim-2: .50;
    --scrim-3: .72;

    /* Высота шапки. Считается из тех же величин, из которых шапка и сложена:
       поля строки плюс логотип — он в ней самый высокий, — плюс черта снизу.
       Нужна обложке работы: та занимает ровно остаток окна, и без этой
       величины из-под неё выглядывал бы край следующего блока. */
    --header-h: calc(1.8rem + clamp(2.875rem, 4.4vw, 4rem) + 1px);

    --lh-text: 1.65;
    --lh-head: 1.2;

    /* Один шрифт на весь сайт. Раньше заголовки набирались антиквой, а
       текст — системным гротеском; теперь и то и другое Montserrat, как
       в макете. Переменных осталось две: у заголовков своя роль, и если
       однажды понадобится их отделить, менять придётся одну строку. */
    --font-display: 'Montserrat', 'Segoe UI', system-ui, Arial, sans-serif;
    --font-body: 'Montserrat', 'Segoe UI', system-ui, Arial, sans-serif;

    /* motion constants lifted from the reference site's animation config */
    --ease-out: cubic-bezier(.25,.46,.45,.94);
    --ease-in-out: cubic-bezier(.7,0,.4,1);
    --dur-title:.8s;
    --dur-text:1.2s;
    --dur-block:1.4s;

    color-scheme: dark;
  }

  /* ============ RESET-ISH ============ */
  *,*::before,*::after{ box-sizing:border-box; }
  /* Всё на странице считано от корневого кегля, и до 1600 точек он равен
     привычным шестнадцати: на телефонах и ноутбуках ничего не меняется. Дальше
     он растёт вместе с окном — доля вместо точек, — и вслед за ним растут поля,
     колонка, кегли и зазоры. Иначе на мониторе в 3840 точек колонка страницы
     занимала треть ширины, а текст оставался в 16 точек: сайт тонул в середине
     экрана.

     Один процент ширины — это ровно шестнадцать точек при окне в 1600, поэтому
     переход из точек в долю проходит незаметно. Потолок — тридцать восемь точек,
     доля от окна в 3840: на четырёх тысячах точек страница выходит той же, что
     на ноутбуке, а дальше расти перестаёт. */
  html{ -webkit-text-size-adjust:100%; font-size:clamp(16px, 1vw, 38px); }
  body{
    margin:0;
    background:var(--bg);
    color:var(--ink);
    font-family:var(--font-body);
    font-size:var(--fs-body);
    line-height:var(--lh-text);
    /* clip, not hidden: `hidden` would turn body into a scroll container and break position:sticky */
    overflow-x:clip;
  }
  @media (prefers-reduced-motion:no-preference){
    html{ scroll-behavior:smooth; }
  }
  h1,h2,h3,p,dl,dd{ margin:0; }
  ul{ margin:0; padding:0; list-style:none; }
  a{ color:inherit; }
  img,svg{ display:block; max-width:100%; }
  button{ font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
  :focus-visible{ outline:2px solid var(--accent); outline-offset:3px; }
  /* Браузер прячет [hidden] через display:none, но это правило слабее любого
     нашего display: блок «заявка отправлена» так и висел бы на виду. */
  [hidden]{ display:none !important; }
  /* Шапка липкая: без отступа она накрывает заголовок раздела,
     к которому только что перешли по ссылке. */
  section[id], main[id]{ scroll-margin-top:5.5rem; }

  /* Заголовки наследовали интервал абзаца: у названия работы в две строки
     между ними зияла дыра. Теперь у них свой, общий на все заголовки. */
  h1,h2,h3{
    font-family:var(--font-display); font-weight:600; letter-spacing:-.01em;
    line-height:var(--lh-head); text-wrap:balance;
  }

  .label{
    font-size:var(--fs-label);
    letter-spacing:.14em;
    text-transform:uppercase;
    color:var(--accent);
    font-weight:600;
  }
  /* section eyebrows get a short gold rule; compact field labels (contacts) stay plain */
  .section-head .label, .hero-eyebrow{
    display:inline-flex; align-items:center; gap:.65em;
  }
  .section-head .label::before, .hero-eyebrow::before{
    content:""; display:inline-block; width:22px; height:1px;
    background:var(--accent);
  }

  /* ============ material texture ============ */
  .grain{
    position:fixed; inset:0; z-index:50; pointer-events:none;
    opacity:.05; mix-blend-mode:overlay;
    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  }

  ::selection{ background:var(--accent); color:var(--accent-ink); }
  html{ scrollbar-color: var(--line-strong) var(--bg); }
  ::-webkit-scrollbar{ width:11px; height:11px; }
  ::-webkit-scrollbar-track{ background:var(--bg); }
  ::-webkit-scrollbar-thumb{ background:var(--line-strong); border:3px solid var(--bg); border-radius:6px; }
  ::-webkit-scrollbar-thumb:hover{ background:var(--accent); }

  /* unify the colour grading across photos pulled from different shoots */

  /* ============ LIGHT ON GLASS ============
     A slow specular band drifts across the hero, and a soft prismatic wash moves
     behind the frosted side panels, so wiping the fog uncovers light in motion. */
  .sheen{ position:absolute; inset:0; pointer-events:none; overflow:hidden; }
  .sheen::before{
    content:"";
    position:absolute; top:-60%; bottom:-60%; left:-70%;
    width:42%;
    transform:rotate(9deg);
    background:linear-gradient(100deg,
      transparent 0%,
      rgba(255,255,255,.05) 30%,
      rgba(201,167,105,.13) 48%,
      rgba(150,205,225,.09) 60%,
      rgba(255,255,255,.04) 72%,
      transparent 100%);
    filter:blur(16px);
    opacity:0;
  }

  @media (prefers-reduced-motion:no-preference){
    .sheen::before{ animation:sheen-travel 16s cubic-bezier(.5,0,.5,1) infinite; }
  }
  @keyframes sheen-travel{
    0%   { left:-70%; opacity:0; }
    10%  { opacity:1; }
    82%  { opacity:1; }
    100% { left:128%; opacity:0; }
  }

  /* a specular streak crosses a project photo as the pointer passes over it */
  .p-item .glint{
    position:absolute; inset:0; z-index:1; pointer-events:none;
    background:linear-gradient(105deg,
      transparent 36%,
      rgba(255,255,255,.16) 47%,
      rgba(201,167,105,.14) 53%,
      transparent 64%);
    transform:translateX(-115%);
    transition:transform 1s var(--ease-out);
  }
  .p-item:hover .glint{ transform:translateX(115%); }
  .p-item figcaption{ z-index:2; }

  /* ============ SCROLL TRANSITIONS ============
     Nothing here applies until JS adds .anim-ready to <html>, so with JS off
     (or reduced-motion on) every element stays fully visible in its final state. */

  /* -- headings: split into lines, each line rises out of its own mask -- */
  /* Каждая строка заголовка выезжает из своей рамки. Слова внутри строки —
     обычный текст, без inline-block: он округлял ширину коробок и сбивал
     замер переносов на узком экране. */
  .line-mask{ display:block; overflow:hidden; padding-bottom:.12em; margin-bottom:-.12em; }
  .line{ display:block; transform-origin:left top; }
  html.anim-ready [data-anim="title"] .line{
    transform:translateY(160%) rotate(6deg);
    transition:transform var(--dur-title) var(--ease-out);
    transition-delay:calc(var(--line-i, 0) * .05s);
  }
  html.anim-ready [data-anim="title"].in-view .line{ transform:translateY(0) rotate(0); }
  /* Заголовок, который не удалось разложить по строкам, проявляется целиком:
     без этого он один оставался бы стоять на месте, пока всё вокруг всплывает. */
  html.anim-ready [data-anim="title"].is-whole{
    opacity:0; transform:translateY(28px);
    transition:opacity var(--dur-text) var(--ease-out), transform var(--dur-text) var(--ease-out);
  }
  html.anim-ready [data-anim="title"].is-whole.in-view{ opacity:1; transform:translateY(0); }

  /* -- body copy and blocks: lift and fade -- */
  html.anim-ready [data-anim="text"]{
    opacity:0; transform:translateY(28px);
    transition:opacity var(--dur-text) var(--ease-out), transform var(--dur-text) var(--ease-out);
    transition-delay:var(--anim-delay, 0s);
  }
  html.anim-ready [data-anim="text"].in-view{ opacity:1; transform:translateY(0); }

  /* -- photos: a curtain wipes across while the image settles out of a slight zoom -- */
  html.anim-ready [data-anim="image"]{
    clip-path:polygon(0% 0%, 0% 0%, 0% 100%, 0% 100%);
    transition:clip-path var(--dur-block) var(--ease-in-out);
    transition-delay:var(--anim-delay, 0s);
  }
  html.anim-ready [data-anim="image"].in-view{ clip-path:polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%); }
  /* wipe from the right for photos on the right-hand side of a split layout */
  html.anim-ready [data-anim="image"][data-anim-from="right"]{
    clip-path:polygon(100% 0%, 100% 0%, 100% 100%, 100% 100%);
  }
  html.anim-ready [data-anim="image"][data-anim-from="right"].in-view{
    clip-path:polygon(100% 0%, 0% 0%, 0% 100%, 100% 100%);
  }
  html.anim-ready [data-anim="image"] .zoom-target{
    transform:scale(1.2);
    transition:transform var(--dur-block) var(--ease-in-out);
    transition-delay:var(--anim-delay, 0s);
  }
  html.anim-ready [data-anim="image"].in-view .zoom-target{ transform:scale(1); }


  /* ============ SHELL ============ */
  .site{ position:relative; z-index:1; }
  .wrap{ max-width:var(--content-w); margin-inline:auto; padding-inline:var(--space); }

  /* ============ HEADER ============ */
  header{
    /* Заметно выше содержимого страницы, а не вровень: шапка липкая, и под
       ней проезжает всё подряд. Десятого не хватало — столько же набирали
       карточки карусели. */
    position:sticky; top:0; z-index:30;
    background:color-mix(in srgb, var(--bg) 78%, transparent);
    backdrop-filter:blur(10px);
    border-bottom:1px solid var(--line);
    transition:border-color .4s ease;
  }
  header.scrolled{ border-bottom-color:var(--accent); }
  /* Потолки зазоров занижены — иначе шапка переполнялась. Причина в том,
     что зазоры и логотип растут вместе с шириной экрана, а строка упирается
     в 1180px и расти перестаёт: начиная примерно с 1580px содержимому не
     хватало места, меню сжималось и «Контакты» наезжали на телефон. Уменьшены
     именно потолки, а не сами значения: на узких экранах, где запас и так был,
     ничего не изменилось. */
  .header-row{
    display:flex; align-items:center; padding-block:.9rem;
    gap:clamp(.75rem, .4rem + .9vw, 1.2rem);
  }
  /* flex:none обязателен. Без него логотип сжимался вместе со всей строкой,
     а название внутри него сжаться не может — и наезжало на соседей.
     С переходом на Montserrat, который шире прежнего шрифта, это и вылезло. */
  .logo{ display:flex; align-items:center; gap:.9rem; text-decoration:none; flex:none; }
  .logo-mark{
    height:clamp(2.875rem, 4.4vw, 4rem); width:auto; flex:none;
  }
  .logo-text{
    display:flex; flex-direction:column; line-height:1.05;
    font-family:var(--font-display); font-size:clamp(1.75rem, 1.3rem + 1.4vw, 2.7rem);
    font-weight:700; letter-spacing:.01em; white-space:nowrap;
  }
  .logo-text small{
    font-family:var(--font-body); font-size:.66rem; letter-spacing:.22em; text-transform:uppercase;
    color:var(--accent); font-weight:600; margin-top:.3rem;
  }

  /* social links sit next to the name, fenced by a short bar at each end */
  .socials-inline{
    display:none; align-items:center; gap:.45rem;
    margin-right:auto; flex:none;
  }
  /* Иконки и телефон появляются позже, чем раньше: в промежутке от 760 до
     1100 они вместе с меню и кнопкой в строку уже не помещались. Оба
     продублированы в мобильном меню, так что ничего не теряется. */
  @media (min-width:1240px){ .socials-inline{ display:flex; } }
  /* below that breakpoint the row is hidden, so the logo takes over the spacer duty */
  @media (max-width:759px){ .logo{ margin-right:auto; } }
  .socials-inline::before, .socials-inline::after{
    content:""; flex:none; width:1px; height:1.6rem; background:var(--line-strong);
  }
  /* Черта по краям убрана: место в строке дороже украшения */
  .socials-inline::before, .socials-inline::after{ display:none; }
  .socials-inline a{
    display:inline-flex; align-items:center; justify-content:center;
    width:2.1rem; height:2.1rem; border-radius:50%;
    border:1px solid var(--line-strong);
    color:var(--muted);
    transition:color .3s var(--ease-out), border-color .3s var(--ease-out), background .3s var(--ease-out), transform .3s var(--ease-out);
  }
  .socials-inline a:hover{
    color:var(--accent-ink); border-color:var(--accent); background:var(--accent);
    transform:translateY(-2px);
  }
  .socials-inline svg{ width:.94rem; height:.94rem; fill:currentColor; }
  .socials-inline .mark{ font-size:.55rem; font-weight:700; letter-spacing:.02em; }

  /* the same row, laid out for the mobile drawer */
  .mobile-nav .socials-inline{
    display:flex; margin:1rem 0 0; justify-content:flex-start;
  }
  .mobile-nav .socials-inline::before{ display:none; }
  .mobile-nav .socials-inline::after{ display:none; }
  nav.main-nav{ display:none; gap:clamp(1rem, .5rem + 1vw, 1.4rem); min-width:0; }
  nav.main-nav a{ text-decoration:none; font-size:.86rem; letter-spacing:.03em; color:var(--muted); position:relative; padding-block:.25rem; }
  nav.main-nav a:hover{ color:var(--ink); }
  nav.main-nav a::after{ content:""; position:absolute; left:0; right:100%; bottom:0; height:1px; background:var(--accent); transition:right .25s ease; }
  nav.main-nav a:hover::after{ right:0; }
  @media (min-width:820px){ nav.main-nav{ display:flex; } }

  .header-actions{ display:flex; align-items:center; gap:.85rem; }
  .phone-link{ display:none; text-decoration:none; font-size:.86rem; color:var(--ink); white-space:nowrap; }
  /* Телефон виден и на телефоне: звонок — самый короткий путь к заказу, и
     прятать его в выдвижное меню значит терять звонки. Ниже 360px он всё же
     скрыт: там строка шапки переполняется, и он остаётся в меню. */
  @media (min-width:360px){ .phone-link{ display:inline; } }
  /* На узком экране кегль поменьше: в строке с логотипом и кнопкой меню
     запас всего десяток точек. */
  @media (max-width:899px){ .phone-link{ font-size:.8rem; } }

  .btn{
    display:inline-flex; align-items:center; gap:.5rem;
    padding:.65rem 1.15rem; border-radius:2px;
    font-size:.82rem; letter-spacing:.03em; text-decoration:none; white-space:nowrap;
    transition:transform .2s ease, box-shadow .2s ease, background .2s ease, border-color .2s ease;
  }
  .btn-primary{ background:var(--accent); color:var(--accent-ink); font-weight:600; }
  .btn-primary:hover{ box-shadow:var(--shadow-gold); transform:translateY(-1px); }
  .btn-ghost{ border:1px solid var(--line-strong); color:var(--ink); }
  .btn-ghost:hover{ border-color:var(--accent); color:var(--accent); }
  .btn-on-photo{ border:1px solid rgba(242,244,242,.55); color:#fff; }
  .btn-on-photo:hover{ border-color:#fff; background:rgba(255,255,255,.08); }

  .menu-toggle{ display:inline-flex; width:2.1rem; height:2.1rem; align-items:center; justify-content:center; border:1px solid var(--line-strong); }
  @media (min-width:820px){ .menu-toggle{ display:none; } }
  .menu-toggle svg{ width:1rem; height:1rem; }

  .mobile-nav{ display:none; flex-direction:column; border-top:1px solid var(--line); padding:.75rem var(--space) 1.25rem; }
  .mobile-nav.open{ display:flex; }
  .mobile-nav a{ text-decoration:none; color:var(--ink); padding:.65rem 0; border-bottom:1px solid var(--line); font-size:.95rem; }
  @media (min-width:820px){ .mobile-nav{ display:none !important; } }

  /* ============ SECTIONS ============ */
  section{ padding-block:clamp(3rem, 2rem + 4vw, 6.5rem); }
  .section-head{ max-width:42rem; margin-bottom:clamp(2rem, 1.5rem + 2vw, 3.5rem); display:flex; flex-direction:column; gap:.75rem; }
  .section-head h2{ font-size:var(--fs-h2); }

  /* ---- hero ---- */
  /* Текст стоит по центру экрана, а не у нижнего края. Прижатый к низу, он
     оставлял под шапкой всю лишнюю высоту разом — на высоком окне больше
     двухсот точек, на низком почти ничего. По центру пустота делится поровну
     сверху и снизу, и отступ перестаёт скакать вместе с высотой окна.

     Это минимум, а не высота: на низком экране блок вырастет под содержимое,
     и ничего не обрежется. */
  /* Первый экран занимает ровно то, что осталось от окна под шапкой: вместе они
     и составляют экран целиком, и следующий раздел начинается за краем.

     Прежде высота была задана долей окна с потолком — 76 сотых, но не больше 700
     точек, — и на невысоком экране ноутбука первый экран выходил за край: не
     потолком, а собственным содержимым. На окне 1518 на 734 он занимал 767 точек
     при 640 доступных.

     Высоту шапки снимает со страницы скрипт; формула в переменной остаётся
     запасным вариантом. Содержимое ужимается под невысокое окно ниже. */
  .hero{
    position:relative;
    /* Первый экран занимает ровно остаток окна под шапкой.

       Полосы окна и снимка совпадают редко, и выбрать можно только одно из
       трёх: дать первому экрану вырасти под кадр, оставить поля по бокам или
       скрыть кадру верх и низ. Первые два пробовали: экран уходил за нижний
       край окна на две сотни точек, а с полями по краям вставал фон страницы.
       Осталось третье.

       Кадр идёт во всю ширину, полей нет, ничего не сплющено — снимок
       уменьшается целиком, сохраняя пропорцию, и в окно не помещается только
       по высоте. На окне 1518 на 734 кадр во всю ширину выходит 854 точки при
       640 доступных: 214 точек, по сотне сверху и снизу, остаются за рамкой.
       Меньше не выйдет ни при какой раскладке — это разница пропорций окна и
       снимка, а не выбор вёрстки. */
    min-height:calc(100svh - var(--header-h));
    /* Своих полей у первого экрана нет: общие поля раздела прибавлялись к
       остатку окна и выносили экран за край — 93 точки сверху и столько же
       снизу при 640 доступных. Содержимое и так стоит по центру, а свой отступ
       ему задаёт колонка внутри. */
    padding-block:0;
    /* Столбцом, а не в строку: полоса доводов стоит на самом кадре, у нижнего
       края, и должна занимать своё место, а не лежать поверх. Содержимое
       держится по центру того, что осталось выше неё, — отсюда margin:auto у
       колонки. */
    display:flex; flex-direction:column;
    overflow:hidden;
  }
  /* Three nested layers, one transform each, so they never fight:
     .hero-bg carries the scroll parallax, .hero-slide the cross-fade,
     .hero-slide-img the slow zoom. */
  .hero-bg{
    position:absolute; inset:0;
  }
  .hero-slide{
    position:absolute; inset:0;
    opacity:0;
    transition:opacity 1.5s var(--ease-out);
    /* Низ кадра растворяется в фоне страницы: кадр не обрывается ровной чертой,
       а уходит в темноту — и первый экран переходит в следующий раздел, а не
       приставлен к нему встык. Растворение только снизу: сверху над кадром
       шапка, по бокам он упирается в края окна.

       Живёт растворение на слое, а не на самой картинке: картинка медленно
       наплывает, и вместе с ней уехала бы вниз граница растворения — низ кадра
       снова обрывался бы чертой. Слой стоит на месте. */
    -webkit-mask-image:linear-gradient(180deg, #000 84%, transparent);
    mask-image:linear-gradient(180deg, #000 84%, transparent);
  }
  .hero-slide.is-active{ opacity:1; }

  /* Кадр идёт во всю ширину окна и заполняет первый экран, сохраняя пропорцию:
     за край уходит только то, что не помещается по высоте. Ни сплющивания, ни
     полей по сторонам — высоту первому экрану задаёт сам кадр, и подгонять
     почти нечего.

     Картинкой, а не фоном в стилях: у картинки можно перечислить копии разной
     ширины, и браузер возьмёт ту, что нужна его экрану. Фон такого не умеет —
     он тянул оригинал целиком, все кадры разом. */
  .hero-slide-img{
    position:absolute; inset:0;
    width:100%; height:100%;
    object-fit:cover; object-position:center;
    filter:saturate(.82) contrast(1.07) brightness(.95) sepia(.06);
    transform:scale(1);
  }
  /* Медленный наплыв: кадр приближается, пока его показывают, и следующий
     начинает свой ход заново. Наплыв — это увеличение, а значит и обрезка: за
     край уходит то, что не поместилось. Поэтому ход короткий, в двадцатую долю,
     и растянут ровно на время показа — глазу видно движение, а не потерю краёв.

     Уходящий кадр держит набранный наплыв, пока растворяется: наплыв живёт при
     отметке «показан», и без этой строчки кадр прыжком возвращался бы к
     исходному размеру прямо на глазах, посреди смены. Ход равен времени показа
     как раз для того, чтобы держать было что: к смене наплыв успевает
     дойти до конца. */
  @media (prefers-reduced-motion:no-preference){
    .hero-slide.is-active .hero-slide-img{ animation:hero-zoom 7s linear forwards; }
    .hero-slide.is-leaving .hero-slide-img{ transform:scale(1.05); }
  }
  @keyframes hero-zoom{
    from{ transform:scale(1); }
    to  { transform:scale(1.05); }
  }

  .hero-dots{ display:flex; gap:.85rem; margin-top:clamp(1.75rem, 1.25rem + 1.5vw, 2.75rem); }
  .hero-dot{
    position:relative; width:44px; height:18px;
    display:flex; align-items:center;
    padding:0;
  }
  .hero-dot::after{
    content:""; position:absolute; left:0; right:0; top:50%;
    height:1px; background:rgba(244,242,236,.28);
  }
  .hero-dot i{
    position:absolute; left:0; right:100%; top:50%;
    height:1px; background:var(--accent); z-index:1;
  }
  @media (prefers-reduced-motion:no-preference){
    .hero-dot.is-active i{ animation:hero-bar 7s linear forwards; }
  }
  @media (prefers-reduced-motion:reduce){
    .hero-dot.is-active i{ right:0; }
  }
  @keyframes hero-bar{ from{ right:100%; } to{ right:0; } }
  /* Затемнение первого экрана — такое же короткое, как на обложке работы:
     растяжка снизу, сходящая на нет к двум пятым высоты. Прежняя закрывала снимок
     целиком — девять десятых плотности внизу, половина у самого верха и виньетка
     в восемнадцать долей окна поверх, — и от пяти кадров, ради которых первый
     экран и сделан, оставалась тёмная подложка под текстом.

     Читаемость держат тени под самими буквами, как на обложке: они видны только
     там, где текст, и кадра не трогают. */
  .hero-scrim{
    position:absolute; inset:0;
    background:linear-gradient(0deg, rgba(6,9,10,.72) 0%, rgba(6,9,10,.32) 25%, transparent 45%);
  }
  .hero .sheen{ z-index:1; }

  /* magnifier: the patch of photo under the pointer is shown enlarged and
     un-dimmed, as though a light were held against the glass at that spot */
  .hero-lens{
    position:absolute; z-index:2;
    width:clamp(11.25rem, 19vw, 18.75rem); aspect-ratio:1;
    border-radius:50%;
    translate:-50% -50%;
    border:1px solid rgba(201,167,105,.5);
    box-shadow:
      0 24px 60px -18px rgba(0,0,0,.85),
      inset 0 0 40px rgba(6,9,10,.45),
      inset 0 1px 0 rgba(255,255,255,.28);
    background-repeat:no-repeat;
    filter:saturate(.9) contrast(1.05) brightness(1.06);
    opacity:0; scale:.8;
    transition:opacity .3s var(--ease-out), scale .3s var(--ease-out);
    pointer-events:none;
  }
  .hero.lens-on .hero-lens{ opacity:1; scale:1; }
  .hero-lens::after{
    content:""; position:absolute; inset:0; border-radius:50%;
    background:radial-gradient(circle at 32% 26%, rgba(255,255,255,.16), transparent 46%);
  }
  /* Поля сверху и снизу одинаковые: при выравнивании по центру разные
     значения сдвигали бы текст с середины. */
  .hero-inner{
    position:relative; z-index:2; width:100%; flex:none;
    margin-block:auto; padding-block:clamp(1.5rem, 1rem + 2vw, 2.5rem);
  }
  /* крупнее обычных надписей-меток: на первом экране это подпись,
     а не служебный ярлык раздела */
  /* Тени под буквами — те же, что на обложке работы: растяжка снизу коротка, и
     верхние строки стоят на голом снимке. Тень видна только там, где текст, и
     кадра не трогает; сплошное затемнение ради тех же строк закрыло бы снимок
     целиком. */
  .hero-eyebrow{
    margin-bottom:1.1rem; color:#e7ddc9; font-size:.95rem; letter-spacing:.12em;
    text-shadow:0 1px 2px rgba(6,9,10,.6), 0 2px 18px rgba(6,9,10,.5);
  }
  .hero h1{ font-size:clamp(1.9rem, 1.3rem + 2.4vw, 3.4rem); line-height:1.1;
    max-width:20ch; color:#fff;
    text-shadow:0 1px 2px rgba(6,9,10,.6), 0 2px 22px rgba(6,9,10,.5); }
  .hero h1 em{ font-style:normal; color:var(--accent); }
  .hero-actions{ display:flex; flex-wrap:wrap; gap:1rem; margin-top:2.25rem; }
  /* Номер на первом экране нужен, пока его нет в шапке. Порог тот же, на
     котором телефон появляется в шапке: иначе между порогами номер стоял бы
     дважды — и в шапке, и на фотографии. */
  @media (min-width:360px){ .hero-actions .btn-on-photo{ display:none; } }

  /* Цифры живут внутри «О компании», под текстом и фотографией: там они
     подтверждают сказанное, а не заявляют о себе отдельно. Полосу рисует
     обёртка, а не секция, — секция теперь общая с рассказом о компании. */
  /* Ни рамки, ни фона: цифры — часть рассказа о компании, а не отдельная
     панель. Отделяет их только воздух. */
  .company-figures{ margin-top:clamp(2.5rem, 2rem + 2.5vw, 4.5rem); }
  .stat-row{ display:grid; grid-template-columns:repeat(2,1fr); gap:2rem 1.5rem; }
  @media (min-width:900px){
    .stat-row{ grid-template-columns:repeat(4,1fr); gap:0; }
    .stat{ padding-inline:2rem; }
    .stat:not(:first-child){ border-left:1px solid var(--line); }
    .stat:first-child{ padding-left:0; }
  }
  .stat b{
    display:block; font-family:var(--font-display);
    font-size:clamp(2rem, 1.4rem + 2vw, 3.4rem); font-weight:700;
    line-height:1; letter-spacing:-.02em;
    color:var(--ink);
    font-variant-numeric:tabular-nums;
  }
  .stat span{ display:block; margin-top:.7rem; font-size:.84rem; color:var(--muted); max-width:20ch; }

  /* ---- about ---- */
  .about-grid{ display:grid; gap:2.5rem; }
  @media (min-width:840px){ .about-grid{ grid-template-columns:1.05fr .95fr; gap:4rem; align-items:center; } }
  .about-copy p + p{ margin-top:1.1rem; }
  .about-copy{ font-size:var(--fs-body); color:var(--muted); max-width:52ch; }
  .about-photo{
    position:relative;
    margin:0;
    aspect-ratio:4/5;
    overflow:hidden;
    border:1px solid var(--line-strong);
    box-shadow:var(--shadow);
  }
  .about-photo .parallax-layer{
    position:absolute; inset:-10% 0;
    background-image:url("/static/img/owner.webp");
    background-size:cover; background-position:center 22%;
    will-change:transform;
    /* a portrait on a pale studio backdrop: warm it toward the page rather than crushing it */
    filter:saturate(.88) contrast(1.02) brightness(.9) sepia(.1);
  }
  .about-photo figcaption{
    position:absolute; left:0; right:0; bottom:0; z-index:3;
    padding:3.5rem 1.5rem 1.35rem;
    background:linear-gradient(0deg, rgba(6,9,10,.92) 12%, rgba(6,9,10,0));
    display:flex; flex-direction:column; gap:.15rem;
  }
  .about-photo figcaption b{
    font-family:var(--font-display); font-size:1.15rem; font-weight:600; color:#fff;
  }
  .about-photo figcaption span{
    font-size:.76rem; letter-spacing:.14em; text-transform:uppercase; color:var(--accent);
  }
  .about-photo::before, .about-photo::after{
    content:""; position:absolute; z-index:2; width:26px; height:26px; pointer-events:none;
  }
  .about-photo::before{
    top:14px; left:14px; border-top:1px solid var(--accent); border-left:1px solid var(--accent);
  }
  .about-photo::after{
    bottom:14px; right:14px; border-bottom:1px solid var(--accent); border-right:1px solid var(--accent);
  }
  /* the portrait carries a caption along its bottom edge, so its second bracket moves up top */
  .about-photo::after{
    top:14px; right:14px; bottom:auto;
    border:0; border-top:1px solid var(--accent); border-right:1px solid var(--accent);
  }

  /* ============ ДОВОДЫ ПОД ПЕРВЫМ ЭКРАНОМ ============
     Четыре довода полосой сразу под кадром: человек, пролиставший первый экран,
     первым делом узнаёт, чем эта мастерская отличается от соседней.

     Полоса с заливкой, как у продукции: раздел, набранный поверх снимка за
     страницей, на светлых местах кадра читался бы через силу. */
  /* Полоса доводов лежит на самом кадре, прижатая к его нижнему краю: она
     последняя в первом экране и своей подложкой доходит ровно до его низа, без
     зазора и без сходящего на нет края.

     Подложка непрозрачна не до конца — сквозь неё виден снимок, но не
     настолько, чтобы светлые места съедали буквы. Ни растворения, ни размытия
     под ней нет: у полосы ровные края, и кадр за ней виден как есть. */
  /* Своих полей у полосы нет: общие поля раздела отодвинули бы подложку от
     низа кадра — замерено, на 87 точек. Отступы внутри полосы задаёт сама
     подложка. */
  /* Полоса не ужимается: первый экран — столбец, и когда содержимому тесно,
     ряды по умолчанию сжимаются, а лишнее уходит под обрез. На невысоком
     телефоне так пропадал четвёртый довод. Пусть лучше первый экран
     вырастет — он и задуман как минимум высоты, а не как потолок. */
  #advantages{ position:relative; z-index:2; padding-block:0; flex:none; }
  /* Полоса держится полей страницы, а не идёт от края до края окна: и подложка,
     и черта поверху начинаются там же, где начинается текст. Ширину задаём той
     же мерой, что и колонке, но без её боковых полей — иначе подложка вылезала
     бы за текст на ширину поля с каждой стороны. Отсюда и padding-inline:0:
     колонка своё поле уже отдала ширине. */
  #advantages .advantage-row{
    position:relative; isolation:isolate;
    width:min(var(--content-w) - 2 * var(--space), 100% - 2 * var(--space));
    max-width:none; margin-inline:auto; padding-inline:0;
    border-top:1px solid var(--line);
    padding-block:clamp(.6rem, .45rem + .5vw, 1rem);
  }
  /* Подложка сходит на нет к низу — как и сам кадр под ней: полоса не
     обрывается чертой, а растворяется вместе со снимком.

     Растворяется подложка, а не полоса целиком: под маской вместе с фоном
     побледнел бы и текст доводов у нижнего края. Поэтому она вынесена отдельным
     слоем, а доводы лежат поверх неё нетронутыми. Отмеряется растворение от
     нижнего края, а не долей высоты: на телефоне доводы встают друг под друга,
     полоса вырастает вдвое — и доля высоты съела бы последний заголовок. */
  #advantages .advantage-row::before{
    content:""; position:absolute; inset:0; z-index:-1;
    background:rgba(18,24,26,.41);
    -webkit-mask-image:linear-gradient(180deg, #000 calc(100% - 2.2rem), transparent);
    mask-image:linear-gradient(180deg, #000 calc(100% - 2.2rem), transparent);
  }
  .advantage-row{ display:grid; gap:clamp(.9rem, .6rem + 1vw, 2rem); }
  /* Разделители волосяной чертой, а не рамкой у каждого довода: рамка на
     четырёх доводах читается сеткой ячеек, а черта — одной полосой. */
  @media (min-width:900px){
    .advantage-row{ grid-template-columns:repeat(4, 1fr); }
    .advantage + .advantage{
      border-left:1px solid var(--line); padding-left:clamp(.9rem, .6rem + 1vw, 2rem);
    }
  }
  /* Всё в полосе стоит по центру: значок, заголовок под ним и описание ниже.
     Значок стоит над заголовком, а не сбоку, — только так расстояние до
     заголовка у всех четырёх доводов одинаковое. Сбоку оно равным не выходит:
     заголовок в две-три строки занимает весь остаток колонки, строки внутри
     стоят по центру, и до значка у каждой своё расстояние — замерено 58 и 83
     точки у первого довода против 18 у последнего. Сузить заголовок до текста
     нельзя: у перенесённого текста ширина всегда равна доступной. */
  /* Пропорция рисунка и высота значка живут на самом доводе: их спрашивает и
     значок, и отступ описания под ним. Пропорция задана числом в разметке —
     ширину от высоты часть браузеров на телефоне считать не берётся, и значок
     выходил нулевой ширины, а появлялся только после того, как раскрытое
     описание заставляло страницу пересчитать раскладку. */
  .advantage{ --icon-size:2.4rem; text-align:center; }
  /* Тени под буквами — как на первом экране: подложка теперь вдвое прозрачнее,
     и на светлом кадре одного её тона не хватит. Тень видна только там, где
     текст, и снимка не трогает. */
  .advantage-fold h3, .advantage-fold p{ text-shadow:0 1px 3px rgba(6,9,11,.8); }
  .advantage-fold > summary{
    display:grid; justify-items:center; gap:.4rem;
    cursor:pointer; list-style:none;
  }
  .advantage-fold > summary::-webkit-details-marker{ display:none; }
  .advantage-fold h3{
    font-size:.82rem; letter-spacing:.1em; text-transform:uppercase;
    line-height:1.3; color:var(--ink);
  }
  .advantage-fold p{
    color:var(--muted); font-size:.82rem; line-height:1.45;
    margin-top:.4rem;
  }
  /* Значок без рамки и заметно крупнее подписи — как на образце: втрое выше
     прописной буквы заголовка. В рамке он читался кнопкой, а не знаком. */
  /* Рамка значка равна самому рисунку: у каждого своя, по его чернилам. Иначе
     расстояние до заголовка у всех разное — рисунки кончаются на разной высоте
     и разной ширине, а рамка у всех была одна. Замерено: сбоку 15,9 / 17 / 14,6
     / 10,9 точки, сверху 14 / 11 / 14 / 9. Теперь расстояние задаёт один зазор,
     и оно одинаково по определению. Высота у всех общая, ширина у каждого своя:
     венок и должен быть шире ключа. */
  .advantage-icon{
    flex:none; display:grid; place-items:center;
    height:var(--icon-size); width:calc(var(--icon-size) * var(--icon-ar, 1));
    color:var(--accent);
    filter:drop-shadow(0 1px 3px rgba(6,9,11,.8));
  }
  .advantage-icon svg{ width:100%; height:100%; }
  /* Стрелка — только на телефоне: на большом экране описания раскрыты всегда,
     и указывать ей не на что. */
  /* На телефоне доводы встают друг под друга, и столбик из значка, заголовка и
     стрелки у каждого отъедал бы у кадра половину экрана. Там довод идёт
     строкой: значок слева, заголовок от него на одном и том же расстоянии,
     стрелка у правого края. Заголовок при этом прижат влево, а не по центру, —
     иначе расстояние до значка у каждого своё, оно зависит от длины строки. */
  @media (max-width:899px){
    /* На телефоне полоса идёт во всю ширину экрана, без полей: узкому экрану
       поля по краям обходятся дороже, чем большому, — тот же приём, что у
       снимков в блоках рассказа. Поля остаются у текста внутри, чтобы буквы не
       упирались в край. */
    #advantages .advantage-row{ width:100%; padding-inline:var(--space); }
    .advantage{ text-align:left; }
    .advantage{ --icon-size:2rem; }
    .advantage-fold > summary{
      display:flex; align-items:center; gap:.6rem;
    }
    .advantage-fold h3{ flex:1; }
    /* Описание встаёт под заголовок, а не под значок: отступ равен ширине
       значка вместе с зазором — а ширина у каждого своя, по его рисунку. */
    .advantage-fold p{
      padding-left:calc(var(--icon-size) * var(--icon-ar, 1) + .6rem);
    }
  }
  .advantage-arrow{
    flex:none; width:.8rem; height:.55rem;
    fill:none; stroke:currentColor; stroke-width:2; color:var(--muted);
    transition:transform .3s var(--ease-out), color .25s var(--ease-out);
  }
  .advantage-fold > summary:hover .advantage-arrow{ color:var(--accent); }
  .advantage-fold[open] .advantage-arrow{ transform:rotate(180deg); }
  @media (min-width:900px){
    .advantage-fold[open] > summary{ cursor:default; }
    .advantage-fold[open] .advantage-arrow{ display:none; }
  }

  /* ---- products ---- */
  /* Полоса с заливкой и волосяными линиями по краям. Тот же приём у ленты
     партнёров с подвалом: разделы с заливкой чередуются с прозрачными, и
     страница читается сменой полос, а не сплошным полотном. «Этапы работы»
     остаются прозрачными — между двумя залитыми полосами им и место. */
  #products{ background:var(--surface); border-block:1px solid var(--line); }

  /* ---- portfolio ---- */
  /* Кнопка на все работы стоит под лентой, отдельной строкой. Отступ сверху
     обязателен: вплотную к снимкам она читалась как их часть. */
  .portfolio-more{
    margin-top:clamp(1.25rem, 1rem + 1vw, 2rem);
    display:flex;
  }
  .portfolio-all{ flex:none; }
  .portfolio-grid{ display:grid; grid-template-columns:repeat(2,1fr); gap:2px; background:var(--line); border:1px solid var(--line); }
  @media (min-width:700px){ .portfolio-grid{ grid-template-columns:repeat(3,1fr); } }
  .p-item{ position:relative; aspect-ratio:4/5; background:var(--surface); overflow:hidden; transition:box-shadow .35s ease; }
  .p-item img{
    width:100%; height:100%; object-fit:cover; display:block;
    filter:saturate(.82) contrast(1.07) brightness(.95) sepia(.06);
    transition:transform .6s var(--ease-out), filter .35s ease;
  }
  .p-item:hover img{ filter:saturate(.92) contrast(1.08) brightness(1) sepia(.03); }
  .p-item:hover{ box-shadow:inset 0 0 0 1px var(--accent); }
  /* outranks the entrance zoom above so hover still works once the photo has landed */
  html.anim-ready .p-item[data-anim="image"].in-view:hover .zoom-target{
    transform:scale(1.06);
    transition:transform .6s var(--ease-out);
  }
  .p-item figcaption{
    position:absolute; left:0; right:0; bottom:0;
    padding:1.75rem 1.1rem .9rem;
    background:linear-gradient(0deg, rgba(6,9,10,.88), rgba(6,9,10,0));
    color:#fff; font-size:.82rem; letter-spacing:.02em;
  }

  /* На широком экране кнопка на все работы возвращается наверх, в строку с
     заголовком, к правому краю. В разметке она стоит после ленты — так она
     оказывается под снимками на телефоне, где рядом с заголовком ей нет
     места. Сетка позволяет поднять её обратно, не трогая порядок разметки:
     заголовок и кнопка кладутся в одну клетку, лента — в следующую.

     Кнопка сохраняет обёртку с теми же полями, что у заголовка, и жмётся
     внутри неё вправо — поэтому её правый край точно совпадает с правым
     краем заголовка на любой ширине. */
  @media (min-width:900px){
    .portfolio-stage{ display:grid; align-content:center; }
    .portfolio-head{ grid-area:1 / 1; }
    .portfolio-more{
      grid-area:1 / 1; align-self:end;
      justify-content:flex-end; margin-top:0;
    }
    .track-viewport{ grid-area:2 / 1; }
  }

  /* pinned gallery: the section holds still while the photos travel sideways with the scroll */
  @media (min-width:900px){
    #portfolio.is-pinned{ padding-block:0; }
    #portfolio.is-pinned .portfolio-stage{
      position:sticky; top:0; height:100vh;
      gap:clamp(1.5rem, 1rem + 2vw, 2.75rem);
      overflow:hidden;
    }
    /* Заголовок, кнопка и лента идут по той же колонке, что и вся страница.
       Прежде портфолио из неё выходило: по краям стояли стеклянные панели,
       колонка внутри них была почти вровень с общей, и разницы не читалось.
       Панелей больше нет, страница занимает всю ширину, и портфолио на её
       фоне выглядело шире остальных разделов. */
    #portfolio.is-pinned .track-viewport{ overflow:hidden; }
    /* Ширину заголовку и кнопке приходится задавать явно. Колонка страницы
       центрируется полями «auto», а у элемента сетки такие поля отменяют
       растяжение и оставляют его шириной по содержимому: заголовок съёживался
       до длины своей строки, и кнопка уезжала к середине вместо правого края
       ленты. */
    #portfolio.is-pinned .portfolio-head,
    #portfolio.is-pinned .portfolio-more{ width:100%; }
    #portfolio.is-pinned .portfolio-grid{
      display:flex; width:max-content;
      border:0; gap:2px;
      will-change:transform;
    }
    /* explicit width: a flex item sized only by aspect-ratio can collapse to zero */
    #portfolio.is-pinned .p-item{
      /* Потолок высоты в ремах, а не в точках: на большом мониторе корневой
         кегль растёт вместе с окном, и вместе с ним растёт вся страница.
         Прежние 560 точек не росли — на четырёх тысячах карточка занимала
         двенадцатую часть ширины вместо пятой, как на ноутбуке, и лента
         выглядела набором марок посреди пустой полосы. */
      --item-h:min(52vh, 35rem);
      flex:none;
      aspect-ratio:auto;
      height:var(--item-h);
      width:calc(var(--item-h) * 4 / 5);
    }
    #portfolio.is-pinned .section-head{ margin-bottom:0; }
  }



  /* ============ ЛЕНТА СНИМКОВ ПОД ЭТАПАМИ ============
     Снимки едут сами, слева направо, и останавливаются под курсором — чтобы
     кадр можно было рассмотреть и открыть во весь экран. Тот же приём, что у
     ленты партнёров: дорожка сдвинута на половину своей длины и едет к нулю, а
     список повторён дважды, поэтому стык не виден.

     Края растворяются в фоне: снимок не обрезается на полуслове, а уходит в
     темноту — так лента читается бесконечной.

     Лента держится колонки страницы: во всю ширину окна она читалась отдельной
     полосой, не связанной с разделами выше и ниже. Колонку задаёт обёртка в
     разметке, та же, что у всех прочих разделов. */
  #strip{ padding-block:clamp(1.5rem, 1rem + 2vw, 3rem); }
  .strip-viewport{
    overflow:hidden;
    -webkit-mask-image:linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
    mask-image:linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
  }
  .strip-track{
    display:flex; align-items:stretch; gap:2px;
    width:max-content;
    animation:strip-run 60s linear infinite;
    will-change:transform;
  }
  @keyframes strip-run{
    from{ transform:translate3d(-50%,0,0); }
    to  { transform:translate3d(0,0,0); }
  }
  /* Под курсором лента замирает: иначе кадр пришлось бы ловить на ходу. */
  .strip-viewport:hover .strip-track{ animation-play-state:paused; }
  /* Палец останавливает ленту так же, как курсор: пока её листают, ехать ей
     незачем. Отметку ставит скрипт — прикосновение и прокрутку стилям не
     увидеть. */
  .strip-viewport.is-held .strip-track{ animation-play-state:paused; }
  @media (prefers-reduced-motion:reduce){
    /* Движение отключено — лента становится обычным рядом. Половина копий
       лишняя, но она помечена aria-hidden и просто уезжает за край. */
    .strip-track{ animation:none; }
  }

  @media (max-width:819px){
    /* На телефоне лента едет вдвое быстрее: в окно помещается полтора кадра, и
       на прежнем ходу следующий приходилось ждать. */
    .strip-track{ animation-duration:30s; }
    /* И слушается пальца: ленту можно пролистать самому, не дожидаясь, пока
       нужный кадр подъедет. Полоса прокрутки скрыта — лента и так видна, а
       полоса под снимками читалась бы обрывком. */
    .strip-viewport{
      overflow-x:auto;
      overscroll-behavior-x:contain;
      -webkit-overflow-scrolling:touch;
      scrollbar-width:none;
    }
    .strip-viewport::-webkit-scrollbar{ display:none; }
  }

  /* Высота одна на всю ленту, ширина у каждого кадра своя — по его пропорции.
     Так верх и низ снимков идут по одной линии, а между ними остаётся только
     шов в две точки; обрезать при этом ничего не приходится.

     Высота в ремах: на большом мониторе корневой кегль растёт вместе с окном, и
     лента растёт вместе со страницей, а не остаётся полосой марок. */
  .strip-shot{
    flex:none; display:block; position:relative; overflow:hidden;
    height:clamp(17rem, 46vh, 32rem);
    text-decoration:none;
  }
  .strip-shot img{
    width:auto; height:100%; max-width:none; display:block;
    filter:saturate(.82) contrast(1.07) brightness(.95) sepia(.06);
    transition:filter .35s ease;
  }
  .strip-shot:hover img{ filter:saturate(.92) contrast(1.08) brightness(1) sepia(.03); }

  /* ---- contacts ---- */
  #contacts{ background:var(--surface); border-top:1px solid var(--line); }
  .contact-grid{ display:grid; gap:2.5rem; }
  @media (min-width:820px){ .contact-grid{ grid-template-columns:1fr 1fr; } }
  /* Внутри блока один шаг отступа — между строками контактов, между полями
     формы и между её частями. Раньше он был разный: полтора ректа в списке
     против семи десятых в форме, отчего колонки шли вразнобой. */
  #contacts{ --contact-gap:1rem; }

  /* Подписи и значения — общая сетка на две колонки, а не отдельная строка
     под каждую пару. Колонка подписей получает ширину самой длинной из них,
     и значения выстраиваются по одной линии.

     Прежде подписи сидели в коробке шириной 8ch. «ТЕЛЕФОН» в неё не влезал —
     75 точек против 63 — и переносился на вторую строку, заметнее всего на
     телефоне, где эта пара занимает половину экрана. */
  .contact-list{
    display:grid; grid-template-columns:auto 1fr;
    gap:var(--contact-gap);
  }
  .contact-item{ display:contents; }
  .contact-item .label{ white-space:nowrap; padding-top:.2rem; }
  .contact-item a, .contact-item span{ text-decoration:none; font-size:1.05rem; }
  .contact-item a:hover{ color:var(--accent); }
  /* Режим работы: дни и часы одной строкой, разделённые чертой цвета отделки —
     она отделяет их без запятой и держит взгляд на самих часах. Про выходные
     здесь не сказано: «Пн — Пт» говорит это и так. */
  .contact-hours{ display:flex; flex-wrap:wrap; align-items:baseline; gap:.5rem .7rem; }
  .contact-hours b{ font-weight:600; font-size:1.05rem; }
  .contact-hours i{
    font-style:normal; font-size:1.05rem; color:var(--accent);
    padding-left:.7rem; border-left:1px solid var(--line-strong);
  }
  .contact-cta{ border:1px solid var(--line-strong); padding:clamp(1.5rem,1rem + 2vw,2.25rem); display:flex; flex-direction:column; gap:var(--contact-gap); }
  .contact-cta h3{ font-size:1.15rem; }
  .contact-cta p{ color:var(--muted); font-size:.92rem; }

  /* ---- lead form ----
     Laid out like the form on pskb.ru: no labels stacked above the inputs, the
     prompt lives inside the field. That alone removes most of the height. Real
     labels stay in the markup for screen readers, just visually hidden. */
  .sr-only{
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
  }
  /* Шаг тот же, что и в списке контактов слева: раньше форма жила своим. */
  .lead-form{ gap:var(--contact-gap, .7rem); }
  /* Text inside an input starts one border plus the input's own padding in from
     the form's edge. Indent the heading by the same amount so it lines up with
     the fields instead of hugging the frame. */
  .lead-form h3, .lead-intro{ padding-left:calc(1px + .75rem); }
  .lead-intro{ margin-bottom:.15rem; }

  .form-row{ display:grid; gap:var(--contact-gap, .7rem); }
  @media (min-width:480px){ .form-row{ grid-template-columns:1fr 1fr; } }

  .field{ display:flex; flex-direction:column; gap:.3rem; min-width:0; }
  .field input[type="text"],
  .field input[type="tel"],
  .field input[type="email"],
  .field select,
  .field textarea{
    font:inherit; font-size:.94rem; color:var(--ink);
    background:var(--surface-2);
    border:1px solid var(--line-strong);
    border-radius:2px;
    padding:.6rem .75rem;
    width:100%;
    transition:border-color .25s var(--ease-out), background .25s var(--ease-out);
  }
  .field textarea{ resize:vertical; min-height:3.6rem; }
  .field input::placeholder, .field textarea::placeholder{ color:var(--muted); opacity:.7; }
  .field input:focus-visible, .field textarea:focus-visible,
  .field select:focus-visible{
    outline:none; border-color:var(--accent); background:var(--surface);
  }
  /* Пока ответ не выбран, список показывает вопрос — и выглядеть он должен
     подсказкой, как placeholder у соседних полей, а не выбранным ответом.
     Пустое значение при обязательном поле — это и есть :invalid, поэтому
     отдельного класса не нужно. Возвращаем обычный цвет самим пунктам: в
     раскрытом списке они не должны быть бледными. */
  .field select:invalid{ color:var(--muted); opacity:.7; }
  .field select option{ color:var(--ink); opacity:1; }
  .field input[type="file"]{ font:inherit; font-size:.8rem; color:var(--muted); }
  .field input[type="file"]::file-selector-button{
    font:inherit; font-size:.78rem; letter-spacing:.02em;
    color:var(--ink); background:var(--surface-2);
    border:1px solid var(--line-strong); border-radius:2px;
    padding:.42rem .8rem; margin-right:.7rem; cursor:pointer;
    transition:border-color .25s var(--ease-out), color .25s var(--ease-out);
  }
  .field input[type="file"]::file-selector-button:hover{ border-color:var(--accent); color:var(--accent); }
  .field-err{ font-size:.78rem; color:#e08a6a; }
  .field.invalid input, .field.invalid textarea{ border-color:#e08a6a; }

  .consent label{
    display:flex; align-items:flex-start; gap:.55rem;
    font-size:.8rem; color:var(--muted); line-height:1.45;
    cursor:pointer;
  }
  .consent input[type="checkbox"]{
    flex:none; width:1rem; height:1rem; margin-top:.12rem;
    accent-color:var(--accent);
  }
  .consent a{ color:var(--accent); }
  .consent.invalid label span{ color:#e08a6a; }

  /* visible to bots, not to people or screen readers */
  .hp{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

  .lead-foot{ display:flex; flex-wrap:wrap; align-items:center; gap:.6rem 1rem; margin-top:.2rem; }
  .lead-alt{ font-size:.82rem; color:var(--muted); }
  .lead-alt a{ color:var(--accent); text-decoration:none; white-space:nowrap; }

  .lead-done{
    display:flex; flex-direction:column; gap:.3rem;
    border:1px solid var(--accent);
    background:var(--accent-soft);
    padding:1rem 1.1rem;
  }
  .lead-done b{ font-family:var(--font-display); font-size:1rem; }
  .lead-done span{ font-size:.85rem; color:var(--muted); }
  .lead-done a{ color:var(--accent); text-decoration:none; }
  .lead-form.sent > *:not(.lead-done){ display:none; }

  /* ============================================================
     GLASS SURFACES
     Everything above sets layout. This block re-skins the page's
     surfaces as panes of glass: translucent, refracting what lies
     behind them, lit along the top edge where light would catch.
     It comes last so it wins on order rather than on specificity.
     ============================================================ */

  /* The scene the facade looks out onto. Fixed to the viewport and sized to it
     exactly, so the side panes can show their own slices of the same view and
     the whole thing reads as one continuous window wall. */
  /* Копия по размеру экрана, а не оригинал: снимок с камеры на несколько
     мегабайт тянулся целиком даже на телефон. Ступени идут снизу вверх, и
     каждая следующая перекрывает предыдущую; последняя — сам оригинал, для
     экранов, которым не хватает и самой большой копии, чтобы качество не
     упало. Запасное значение у каждой ступени — оригинал: у снимка, залитого
     до появления копий, их попросту нет.

     Плотность экрана учитывается наравне с шириной: на телефоне с тройной
     плотностью узкая копия расплылась бы. */
  .nature-bg{
    position:fixed; inset:0; z-index:0; pointer-events:none;
    background-image:var(--page-bg-s, var(--page-bg));
    /* Пропорции снимка сохраняются: вертикальную обложку прежнее растяжение
       на всё окно плющило до неузнаваемости. */
    background-size:cover;
    background-position:center;
  }
  @media (min-width:620px), (min-resolution:2dppx){
    .nature-bg{ background-image:var(--page-bg-m, var(--page-bg)); }
  }
  @media (min-width:1700px), (min-width:1100px) and (min-resolution:2dppx){
    .nature-bg{ background-image:var(--page-bg-l, var(--page-bg)); }
  }
  @media (min-width:2600px), (min-width:1700px) and (min-resolution:2dppx){
    .nature-bg{ background-image:var(--page-bg); }
  }

  /* Enough shade for text to stay legible without losing the greens. */
  .nature-bg::after{
    content:""; position:absolute; inset:0;
    background:linear-gradient(180deg,
      rgba(10,13,14,var(--scrim-1)),
      rgba(10,13,14,var(--scrim-2)) 40%,
      rgba(10,13,14,var(--scrim-3)));
  }

  /* Страница работы: за ней не природа, а обложка объекта. Обложки светлее и
     подробнее, поэтому шторка плотнее — иначе текст поверх снимка читается
     хуже, чем на главной. Числа выводятся из яркости кадров, см. :root. */
  body.has-cover{
    --scrim-1: .77;
    --scrim-2: .70;
    --scrim-3: .83;
  }

  /* Light living behind the glass. Without something to refract, a
     translucent pane just looks like flat, slightly paler paint. */
  .glass-bg{
    position:fixed; inset:-20vh -10vw; z-index:0; pointer-events:none;
    background:
      radial-gradient(48vw 52vh at 14% 18%, rgba(201,167,105,.16), transparent 62%),
      radial-gradient(44vw 46vh at 88% 62%, rgba(95,128,131,.20), transparent 64%),
      radial-gradient(60vw 40vh at 46% 106%, rgba(201,167,105,.10), transparent 68%);
    filter:blur(12px);
  }
  @media (prefers-reduced-motion:no-preference){
    .glass-bg{ animation:glass-bg-drift 46s ease-in-out infinite alternate; }
  }
  @keyframes glass-bg-drift{
    from{ transform:translate3d(-2%, -1.5%, 0) scale(1); }
    to  { transform:translate3d(2.5%, 2%, 0) scale(1.08); }
  }

  :root{
    /* Real glass is clear through the middle; what you actually see is its
       edges. So the body tint is almost nothing and the drama lives at the rim. */
    --pane: linear-gradient(180deg,
      rgba(255,255,255,.055) 0%,
      rgba(255,255,255,.012) 14%,
      rgba(255,255,255,.004) 55%,
      rgba(255,255,255,.020) 100%);

    /* Soda-lime glass is green when you look into its thickness — that faint
       green rim is the single most recognisable "this is glass" cue. */
    --glass-rim: 168,222,212;

    --pane-bevel:
      inset 0 1px 0 rgba(228,248,243,.24),
      inset 0 0 0 1px rgba(var(--glass-rim),.085),
      inset 0 -1px 0 rgba(255,255,255,.05),
      inset 0 0 14px -4px rgba(var(--glass-rim),.16),
      inset 0 26px 44px -40px rgba(255,255,255,.4),
      inset 0 -34px 48px -44px rgba(0,0,0,.75);
  }

  /* full-width bands read as large panes laid over the light */
  #contacts{
    position:relative;
    background:
      var(--pane),
      color-mix(in srgb, var(--surface) 52%, transparent);
    /* glass does not just blur — it tints and lifts saturation slightly */
    backdrop-filter:blur(22px) saturate(1.34) brightness(1.05);
    -webkit-backdrop-filter:blur(22px) saturate(1.34) brightness(1.05);
    box-shadow:var(--pane-bevel);
    border-block:1px solid rgba(var(--glass-rim),.16);
  }
  #contacts{ border-bottom:0; }
  /* buttons as small panes */
  .btn-ghost, .btn-on-photo, .menu-toggle, .socials-inline a{
    background:var(--pane);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.14);
  }
  .socials-inline a:hover{ box-shadow:none; }

  header{
    background:
      var(--pane),
      color-mix(in srgb, var(--bg) 62%, transparent);
    backdrop-filter:blur(22px) saturate(1.2);
    -webkit-backdrop-filter:blur(22px) saturate(1.2);
    box-shadow:inset 0 -1px 0 rgba(255,255,255,.06);
  }

  /* Where the browser cannot blur what is behind, fall back to solid
     surfaces so nothing is ever left as pale text on a pale ground. */
  @supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
    #contacts{ background:var(--surface); }
    .btn-ghost, .btn-on-photo, .menu-toggle, .socials-inline a{ background:var(--surface-2); }
    header{ background:var(--bg); }
  }

  /* Золотое кольцо на нажимаемом.

     Обычная стрелка остаётся системной: подменённый указатель по всей
     странице отвлекал. Кольцо появляется только там, где есть что нажать, —
     и работает как подсказка, а не как украшение.

     Точка отсчёта — середина кольца (14 14), иначе нажатие приходилось бы не
     туда, куда целится глаз. Тёмная обводка под золотом обязательна: без неё
     кольцо пропадало бы на светлых участках фотографий.

     Запасной указатель через запятую тоже обязателен: если браузер не
     возьмёт картинку, останется обычная рука, а не пустое место. */
  a, button, summary, label, .btn, [role="tab"], [data-zoom], .p-item{
    cursor:url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2228%22%20height%3D%2228%22%20viewBox%3D%220%200%2028%2028%22%3E%3Ccircle%20cx%3D%2214%22%20cy%3D%2214%22%20r%3D%229%22%20fill%3D%22none%22%20stroke%3D%22rgba%288%2C11%2C13%2C.55%29%22%20stroke-width%3D%223.2%22%2F%3E%3Ccircle%20cx%3D%2214%22%20cy%3D%2214%22%20r%3D%229%22%20fill%3D%22none%22%20stroke%3D%22%23f0deb4%22%20stroke-width%3D%221.4%22%2F%3E%3Ccircle%20cx%3D%2214%22%20cy%3D%2214%22%20r%3D%221.8%22%20fill%3D%22%23f0deb4%22%2F%3E%3C%2Fsvg%3E") 14 14, pointer;
  }

  /* В полях ввода — обычная каретка: там курсор показывает место набора,
     и подменять его нельзя. */
  input, textarea, select{ cursor:auto; }

  /* Подсветка краёв на узком экране.

     Коснулись — у обоих краёв загорается свет и медленно гаснет. На широком
     Прежде по краям стояли стеклянные панели с потёками, и подсветка работала
     только на узком экране, где их не было. Панелей нет нигде, и подсветка
     стала единственным откликом краёв — на всех размерах экрана.

     Светит внутрь, но недалеко: это подсветка самой кромки, а не заливка
     половины экрана. Ярче всего у края, к середине сходит на нет. У самого
     края волосяная линия — без неё пятно выглядит размытым, а не кромкой
     стекла.

     Загорается быстро, гаснет долго — так ведёт себя лампа, а не вспышка.
     Разная длительность получается тем, что переход объявлен дважды: в
     погашенном состоянии он медленный, в горящем быстрый, и браузер берёт
     тот, что записан в состоянии, к которому идёт. */
  .side-glow{
    position:fixed; top:0; bottom:0;
    width:clamp(1.625rem, 8vw, 3.75rem);
    z-index:6; pointer-events:none;
    opacity:0;
    transition:opacity 1.8s ease-in;   /* гаснет медленно */
  }
  .side-glow.lit{
    opacity:1;
    transition:opacity .18s ease-out;  /* загорается сразу */
  }
  .side-glow.left{
    left:0;
    background:linear-gradient(90deg, rgba(201,167,105,.20) 0%, rgba(201,167,105,.05) 40%, transparent 100%);
    box-shadow:inset 1px 0 0 rgba(240,222,180,.6);
  }
  .side-glow.right{
    right:0;
    background:linear-gradient(270deg, rgba(201,167,105,.20) 0%, rgba(201,167,105,.05) 40%, transparent 100%);
    box-shadow:inset -1px 0 0 rgba(240,222,180,.6);
  }

  /* При «уменьшить движение» убираем именно движение — плавное затухание, —
     а не сам отклик. Совсем гасить его нельзя: этот признак включается и сам
     собой, например когда на iPhone включён режим энергосбережения, и тогда
     касание переставало давать хоть что-то. */
  @media (prefers-reduced-motion:reduce){
    .side-glow, .side-glow.lit{ transition:none; }
  }

  footer{
    padding-block:1.75rem;
    background:var(--surface); border-top:1px solid var(--line);
  }
  .foot-row{ display:flex; flex-wrap:wrap; gap:1rem; justify-content:space-between; align-items:center; font-size:.78rem; color:var(--muted); }

  /* ============ ОБЛОЖКА РАБОТЫ ============
     Снимок с отметкой «Основная» во весь экран, поверх — название и
     приглашение читать дальше.

     Обложка занимает ровно остаток окна под шапкой: открыв страницу, человек
     видит один снимок и ничего больше — начало рассказа не выглядывает снизу
     и не спорит с ним за внимание. Остальное проявляется по мере прокрутки,
     как на главной.

     svh, а не vh: на телефоне vh считается по окну без адресной строки, и
     пока строка видна, снизу вылезала бы полоса следующего блока. svh берёт
     окно в его наименьшем виде — тогда лишнего не видно ни до, ни после
     того, как строка уедет. Строкой выше — запасной вариант для браузеров,
     которые svh ещё не знают. */
  .pcover{
    position:relative;
    min-height:calc(100vh - var(--header-h));
    min-height:calc(100svh - var(--header-h));
    display:flex; align-items:flex-end;
    overflow:hidden;
    margin-bottom:clamp(1.5rem, 1rem + 1.5vw, 2.5rem);
  }
  /* Снимок закрывает весь экран. Пропорция сохраняется, а лишнее по одной из
     сторон уходит за край: полоса окна и пропорция кадра совпадают редко, и
     выбор здесь только между обрезкой и полями.

     Прежде кадр вписывался целиком, и по сторонам оставались тёмные поля.
     На первом экране они читались не рамкой, а недоделкой: снимок висел
     посередине, а треть ширины занимал фон. Целиком кадр по-прежнему виден
     ниже, в блоках рассказа, и в просмотрщике по щелчку. */
  .pcover img{
    position:absolute; inset:0;
    width:100%; height:100%; object-fit:cover; object-position:center;
  }
  /* Затемнение снизу: без него светлый снимок съедал бы белый текст. Растяжка
     короткая — сходит на нет к двум пятым высоты обложки, а не к трём
     четвертям, как было раньше: та закрывала треть снимка, ради которого
     страницу и открывают, этой же хватает ровно на полосу с названием и
     приглашением читать.

     Укоротить её позволяет тень под самими буквами: она отделяет текст от фона
     там, где он стоит, и растяжке остаётся лишь приглушить фон под ним, а не
     затемнять кадр во всю высоту. */
  .pcover-veil{
    position:absolute; inset:0;
    background:linear-gradient(0deg, rgba(6,9,10,.72) 0%, rgba(6,9,10,.32) 25%, transparent 45%);
  }
  /* Название и приглашение читать — столбиком, одно под другим. Прежде они
     стояли в строку по краям обложки: на широком экране между ними зияла
     пустота во всю ширину, и приглашение читалось не продолжением названия, а
     отдельной кнопкой в углу. */
  .pcover-inner{
    position:relative; z-index:1; width:100%;
    padding-block:clamp(1.5rem, 1rem + 2vw, 2.75rem);
    display:flex; flex-direction:column; align-items:flex-start;
    gap:clamp(.9rem, .6rem + .8vw, 1.5rem);
  }
  .pcover-inner h1{
    color:#fff; margin:0;
    text-shadow:0 1px 2px rgba(6,9,10,.55), 0 2px 18px rgba(6,9,10,.45);
  }
  .pcover-more{
    flex:none;
    display:inline-flex; align-items:center; gap:.6rem;
    color:#fff; text-decoration:none;
    text-shadow:0 1px 2px rgba(6,9,10,.55), 0 2px 18px rgba(6,9,10,.45);
    font-size:var(--fs-label); letter-spacing:.14em; text-transform:uppercase;
    padding-bottom:.35rem; border-bottom:1px solid rgba(244,242,236,.45);
    transition:color .25s var(--ease-out), border-color .25s var(--ease-out);
  }
  .pcover-more:hover{ color:var(--accent); border-bottom-color:var(--accent); }
  .pcover-more svg{
    width:1rem; height:.625rem; fill:none; stroke:currentColor; stroke-width:2;
  }
  /* Якорь для «Читать о проекте». Отступ сверху — чтобы липкая шапка не
     накрывала начало рассказа. */
  #story{ scroll-margin-top:5.5rem; }

  /* ============ РАССКАЗ О ПРОЕКТЕ БЛОКАМИ ============
     Страница проекта собирается из блоков: у каждого свой заголовок, текст,
     свои снимки и раскладка. Разметка у всех одна, различает их только класс —
     иначе пять почти одинаковых кусков вёрстки разошлись бы при первой правке.

     Сетка из шести колонок нужна ради раскладки «Колонка»: обычный блок
     занимает всю ширину, а «Колонка» — две доли, и три таких подряд сами
     встают в ряд. Флексом такого не выйдет: там пришлось бы заранее знать,
     сколько их идёт подряд. */
  .blocks{
    /* Свой зазор, меньше общего шага страницы: блоки — части одного рассказа,
       и разносить их так же далеко, как самостоятельные разделы, незачем.
       Отдельная переменная нужна ещё и врезке: она подтягивается наверх ровно
       на эту величину, и стоит развести их — перестанет прилегать. */
    /* Зазор минимальный — тот же, что между снимками внутри блока: страница
       читается сплошной мозаикой, как в макете. Не ноль: встык блоки
       слипаются в одно полотно, и где кончился один рассказ и начался
       следующий, становится непонятно. Двух точек хватает на шов. */
    --blocks-gap: 2px;
    /* Насколько блок наезжает на предыдущий — долей ширины страницы. Число без
       единицы ставит разметка, здесь только запасное значение.

       Доли, а не точки: на узком экране наезд уменьшается сам, и одно и то же
       число не приходится подбирать заново под каждый размер. */
    /* Зазор между колонкой текста и снимками — один на все раскладки. */
    --pblock-gap: clamp(1rem, .75rem + 1.5vw, 2.25rem);

    --overlap: 0;
    /* Ширина, от которой считается эта доля. Проценты в самом отступе не
       годятся: они меряются по ширине блока, и у блока в полширины одно и то же
       число значило бы вдвое меньше. Здесь мера одна на всех — колонка
       страницы, а на узком экране всё окно. */
    --overlap-base: min(var(--content-w), 100vw);
    display:grid;
    grid-template-columns:repeat(6, 1fr);
    grid-auto-rows:min-content;   /* строка ровно по содержимому, без запаса */
    align-content:start;
    /* По горизонтали зазор такой же, как между снимками внутри блока: блоки,
       стоящие в одном ряду, — части одной мозаики, и разводить их дальше, чем
       кадры внутри них, не за что. Прежние полтора рема резали ряд надвое. */
    gap:var(--blocks-gap) 2px;
    margin-bottom:var(--pg-step);
  }
  /* Блок не тянется на высоту строки. Когда в ряду стоят два блока разной
     высоты — две «Половины» или три «Колонки», — низкий растягивался до
     высокого, и под его содержимым повисала пустота. */
  /* Обёртка ряда. На большом экране растворяется: сетка рассказа должна
     видеть сами блоки, иначе их доли ряда не сработают. На телефоне
     превращается в ленту, которая листается вбок. */
  .blocks-row{ display:contents; }

  .pblock{ align-self:start; }

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

     Правило стоит вне полос по ширине: прежде оно лежало в десктопной, и на
     телефоне наложение не работало вовсе. Там глубина уменьшена вдвое, ниже.

     Свой слой обязателен: без него наехавший блок уходил бы под соседа — у них
     общая нумерация, и при равенстве побеждает тот, кто ниже в разметке.
     У первого блока отметка не действует: наезжать не на что. */
  .pblock.is-overlapping{
    margin-top:calc(-1 * var(--overlap, 0) * var(--overlap-base) / 100);
    position:relative; z-index:1;
  }
  /* Один зазор между текстом и снимками на все раскладки. Прежде каждая
     задавала свой: «Ряд» и «Ряд с подъёмом» — 20 точек, «Треть ряда» и
     «Половина ряда» — 16, остальные 36. Со стороны это читалось не разными
     приёмами, а неровной вёрсткой.

     Зазор между самими снимками — другое дело, он свой: два пикселя, мозаикой. */
  .pblock{ grid-column:1 / -1; display:grid; gap:var(--pblock-gap); }

  /* Колонка текста бывает узкой, и слово длиннее её ширины выходило за край
     прямо поверх соседнего снимка. Перенос внутри слова — некрасиво, но это
     крайняя мера: он срабатывает, только когда слово не помещается целиком
     ни при каком переносе по пробелам. */
  .pblock-copy{ overflow-wrap:break-word; }
  .pblock-copy h2{ font-size:var(--fs-h3); margin-bottom:.75rem; }
  /* Интервал плотнее общего по сайту. Описание в блоке стоит узкой колонкой
     рядом со снимком, и на общих 1.65 оно вытягивалось вниз заметно дальше
     кадра: колонка выходила длиннее того, что она подписывает. */
  .pblock-copy p{ color:var(--muted); font-size:.94rem; line-height:1.45; }
  .pblock-copy p + p{ margin-top:.75rem; }

  /* Описание блока под стрелкой у заголовка. Заголовок лежит внутри summary —
     потому и виден всегда, свёрнут блок или раскрыт. Слова «подробнее» нет:
     стрелка рядом с названием понятна сама, а лишняя строка в каждом блоке
     из десятка складывалась бы в отдельный столбец шума.

     На большом экране раскрыто всегда — отметку ставит скрипт, — и стрелка
     там не нужна. Без скриптов она остаётся видимой и работает вручную:
     описание доступно в любом случае. */
  .pblock-more > summary{
    display:flex; align-items:center; gap:.75rem; cursor:pointer;
    list-style:none;
  }
  .pblock-more > summary::-webkit-details-marker{ display:none; }
  .pblock-more > summary h2{ margin-bottom:0; }
  .pblock-more > summary:hover .pblock-more-icon{ color:var(--accent); }
  .pblock-more > summary + p{ margin-top:.75rem; }
  .pblock-more-icon{
    flex:none; width:.94rem; height:.625rem;
    fill:none; stroke:currentColor; stroke-width:2; color:var(--muted);
    transition:transform .3s var(--ease-out), color .25s var(--ease-out);
  }
  /* Раскрытое описание сворачивается обратно тем же нажатием — стрелка
     переворачивается, показывая, куда оно уедет. */
  .pblock-more[open] .pblock-more-icon{ transform:rotate(180deg); }
  @media (min-width:820px){
    .pblock-more[open] > summary{ cursor:default; }
    .pblock-more[open] .pblock-more-icon{ display:none; }
  }

  /* Выделение в тексте, набранном в админке. Одного веса мало: основной текст
     набран приглушённым серым, и жирное слово на нём почти не отличается от
     соседних. Вместе с весом поднимаем яркость — выделенное выходит на цвет
     заголовков и читается как выделенное, а не как случайная опечатка.

     Вес 600, а не 700: 700 на приглушённом фоне выглядит криком, а перечислять
     им по три слова в абзаце менеджер будет наверняка. */
  .project-summary strong,
  .project-body strong,
  .pblock-copy strong,
  .kind strong{ font-weight:600; color:var(--ink); }

  /* Дорожка ленты и копии кадров в ней. Лента живёт только на телефоне — в
     раскладках «два снимка углом» и «высокий и широкий снимки рядом»; на большом
     экране дорожка растворяется, чтобы сетка видела сами кадры, а копии убраны
     вовсе — там раскладки прежние. */
  .pblock-ribbon{ display:contents; }
  .pblock-shot.is-echo{ display:none; }
  /* Дорожка сдвинута на половину своей длины и едет к нулю — то есть слева
     направо. Половина — это ровно один полный список кадров: в миг возврата на
     месте первого оказывается его копия, и стык не виден. */
  @keyframes pblock-ribbon{
    from{ transform:translate3d(-50%,0,0); }
    to  { transform:translate3d(0,0,0); }
  }
  @media (prefers-reduced-motion:reduce){
    /* Движение отключено — лента становится обычным рядом. Копии лишние, но они
       помечены aria-hidden и просто уезжают за край. */
    .pblock-ribbon{ animation:none; }
  }

  .pblock-shots{ display:grid; gap:2px; align-content:start; }
  .pblock-shot{ display:block; position:relative; overflow:hidden; text-decoration:none; }
  .pblock-shot img{
    width:100%; height:auto; display:block;
    filter:saturate(.82) contrast(1.07) brightness(.95) sepia(.06);
    transition:transform .6s var(--ease-out), filter .35s ease;
  }
  .pblock-shot:hover img{
    transform:scale(1.04);
    filter:saturate(.92) contrast(1.08) brightness(1) sepia(.03);
  }

  /* Кадр во врезке не выше доли экрана. Врезка — заметка на полях, и снимок в
     ней вытягивал карточку выше окна: высокий кадр в узкой колонке уходит и за
     верхний, и за нижний край, а прочесть заметку целиком становится нельзя.

     Уменьшается весь кадр, а не обрезается: обе границы — и ширина колонки, и
     этот потолок — стоят разом, а размеры отданы браузеру. У картинки со своей
     пропорцией он берёт ту границу, что жёстче, и уменьшает снимок целиком.
     Ширина при этом задана как auto: с ней и потолком высоты браузер сохраняет
     пропорцию, а с width:100% он бы кадр сплющил.

     Правило стоит вне полос по ширине: на телефоне карточка занимает всю
     ширину, и высокий кадр перерастает окно там даже раньше, чем на большом
     экране.

     Уменьшенный кадр встаёт по центру своей колонки: прижатый к краю, он
     оставлял бы всю пустоту с одной стороны, и рамка карточки выглядела бы
     перекошенной. */
  .pblock--inset .pblock-shot img{
    width:auto; height:auto; max-width:100%; max-height:60svh;
    margin-inline:auto;
  }

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

       Вступление: текст и два снимка внахлёст. Второй сдвинут вниз и влево —
       смещение в долях, а не в точках, чтобы держаться любой ширины. */
    /* Колонке текста отдаётся всё, что осталось от снимков, а не заранее
       отмеренные восемнадцать ректов. Полоса снимков теперь сжимается под
       потолок высоты и часто не выбирает свою долю до конца — прежде этот
       остаток пропадал впустую, а заголовок в узкой колонке рвался по одному
       слову на строку.

       Колонка снимков идёт по содержимому, текстовая забирает остаток. Нижняя
       граница у неё есть: ниже четырнадцати ректов текст снова превращается в
       ленту слов. */
    .pblock--intro{ grid-template-columns:minmax(14rem, 1fr) auto; align-items:center; }
    .pblock--intro.is-mirrored{ grid-template-columns:auto minmax(14rem, 1fr); }
    /* Кадры одного размера. Отрицательное поле у второго — не сдвиг, а
       наложение: в сетке оно уширяло кадр на свою величину, и нижний выходил
       ровно в 1.3 раза крупнее верхнего. В строке гибкой раскладки то же поле
       уходит в общий запас ширины и делится между обоими поровну — кадры
       остаются равными, а наложение сохраняется.

       Доли нулевые: ширина должна идти только от деления полосы, иначе к ней
       примешивался бы собственный размер снимка. */
    .pblock--intro .pblock-shots{ display:flex; gap:0; align-items:flex-start; }
    .pblock--intro .pblock-shot{ flex:1 1 0; min-width:0; }
    /* Сдвинутый вниз кадр компенсируется отрицательным полем снизу. Без
       этого он растил высоту всей строки на величину сдвига, и под первым
       снимком повисала пустота в добрую сотню точек — со стороны она
       выглядела огромным зазором между блоками, хотя зазор тут ни при чём.

       Доли считаются от всей полосы, а не от доли кадра, как было в сетке, —
       поэтому вдвое меньше при том же сдвиге на глаз. */
    .pblock--intro .pblock-shot:nth-child(2){
      margin-top:6%; margin-bottom:-6%; margin-left:-30%; z-index:1;
      box-shadow:var(--shadow);
    }
    /* Потолок по высоте. Кадры тут стоят внахлёст со сдвигом вниз, и высота
       блока складывается из сдвига и высоты нижнего снимка: у вертикальной
       пары она доходила до целого экрана, и вступление приходилось
       прокручивать, чтобы увидеть целиком.

       Ограничение по высоте, а не по ширине: ширина у кадров разная, а высота
       блока зависит именно от неё. Снимок уменьшается целиком — браузер сам
       ужимает ширину вслед за высотой, — и встаёт по центру своей доли, чтобы
       не липнуть к краю. Обрезки нет. */
    /* Потолок по высоте. Кадры тут стоят внахлёст со сдвигом вниз, и высота
       блока складывается из сдвига и высоты нижнего снимка: у вертикальной пары
       она доходила до целого экрана, и вступление приходилось прокручивать.

       Ограничение наложено на всю полосу разом, а не на каждый кадр отдельно.
       По отдельности они упирались в потолок каждый сам по себе, съёживались
       внутри своих долей и расползались по широкой полосе с пустотой вокруг.
       Полоса же сжимается целиком: кадры заполняют свои доли, наложение и
       разница в размере держатся сами собой.

       Ширина полосы выводится из потолка высоты через сумму пропорций кадров —
       она приходит из разметки. Делим на 1.3, потому что наложение в тридцать
       долей возвращает кадрам эту же ширину: каждый выходит в 0.65 полосы, а
       не в половину.

       Восемьдесят долей окна, а не сто: к высоте нижнего кадра прибавляется
       ещё сдвиг, и вся раскладка выходит примерно в 1.07 потолка у вертикальной
       пары и в 1.12 у горизонтальной. При восьмидесяти блок занимает 85–90%
       экрана — чуть меньше его высоты, как и задумано. */
    .pblock--intro{ --intro-h: clamp(16rem, 80svh, 44rem); }
    .pblock--intro .pblock-shots{
      max-width:min(44rem, calc(var(--shots-ar, 2.6666) * var(--intro-h) / 1.3));
      margin-inline:auto;
    }
    /* Лесенка растит строку тем же способом — гасим и её. */
    .pblock--ladder .pblock-shot:nth-child(2){ margin-bottom:-12%; }
    .pblock--ladder .pblock-shot:nth-child(3){ margin-bottom:-24%; }
    .pblock--ladder .pblock-shot:nth-child(n+4){ margin-bottom:-36%; }

    /* Ряд снимков и текст сбоку. Снимкам отдана широкая доля: раньше на них
       оставалось около трёхсот точек на весь ряд, и при трёх кадрах каждый
       выходил с ноготь. Колонка текста не уже десяти ректов — ниже она
       сжимается в вертикальную ленту слов.

       Потолок колонки — шестнадцать ректов. Одиннадцати с половиной оказалось
       мало: в строку помещалось два десятка знаков, текст рвался на обрывки,
       а длинное слово в заголовке — «ПЕРЕГОРОДКА» занимает под две сотни
       точек — в колонку не влезало вовсе и выходило поверх снимка.

       Зазор до снимков оставлен своим, 1.25 рема вместо общего блочного:
       колонка забрала ширину у кадров, и отдавать им ещё и зазор незачем. */
    .pblock--row{
      grid-template-columns:minmax(10rem, 16rem) 1fr;
      align-items:center;
    }
    .pblock--row.is-mirrored{ grid-template-columns:1fr minmax(10rem, 16rem); }

    /* Крупный снимок и текст напротив. Высоту задаёт сам снимок: обложки
       бывают и вертикальные, и горизонтальные, и подгонять их под общую
       рамку значило бы резать кадр. */
    .pblock--feature{ grid-template-columns:1fr minmax(0, .8fr); align-items:center; }
    .pblock--feature.is-mirrored{ grid-template-columns:minmax(0, .8fr) 1fr; }

    /* Колонка: треть ряда. Внутри — снимок сверху, под ним заголовок с
       текстом. */
    .pblock--column{ grid-column:span 2; }
    .pblock--column .pblock-copy{ order:2; }
    .pblock--column .pblock-shots{ order:1; }

    /* Врезка: узкая карточка у правого края. Подложка обязательна — иначе она
       читалась бы продолжением текста, а не отдельной заметкой.

       Подъём к предыдущему блоку был зашит сюда же и срабатывал всегда, из-за
       чего отметка наезда у врезки ничего не меняла: она задавала ту же
       величину, что уже стояла в раскладке. Теперь врезка наезжает по общему
       правилу, как любой другой блок, и глубину выбирает менеджер. */
    .pblock--inset{
      grid-column:4 / -1;
      grid-template-columns:1fr 1fr; align-items:center;
      background:var(--surface); border:1px solid var(--line);
      padding:clamp(1rem, .75rem + 1vw, 1.75rem);
    }

    /* Снимки во всю ширину, текст над ними. Текст не растягивается на всю
       строку: длинная строка читается тяжело, а снимкам ширина нужна.

       Кадры выравниваются по высоте, а не по равным долям. При равных долях
       высота у каждого своя: вертикальный вытягивается вдвое против
       горизонтального, и ряд встаёт вразнобой. Ширина делится между ними по
       их же пропорциям — тогда высота у всех выходит одна, ряд занимает всю
       строку без остатка, и ни один кадр не режется: меняется только размер.

       Тот же приём, что в «Ряду с подъёмом»; пропорция каждого снимка
       приходит из разметки. */
    .pblock--full{ grid-template-columns:1fr; }

    /* Снимок, текст, снимок. Обёртка снимков растворяется: только так каждый
       кадр становится отдельной ячейкой, и текст встаёт между ними. В разметке
       он идёт первым, поэтому порядок расставляется явно. */
    .pblock--split{ grid-template-columns:1fr minmax(12rem, 18rem) 1fr; align-items:center; }
    .pblock--split .pblock-shots{ display:contents; }
    .pblock--split .pblock-copy{ order:2; }
    .pblock--split .pblock-shot:first-child{ order:1; }
    .pblock--split .pblock-shot:not(:first-child){ order:3; }

    /* Текст в рамке поверх снимка. Карточка стоит у нижнего края, отметка
       «текст справа» переносит её к другому краю.

       Рамка со своей подложкой обязательна: текст прямо на фотографии
       читается только на удачном кадре, а кадры приходят разные. Подложка
       размыта — так снимок под ней виден, но буквы не спорят с деталями.

       Ширина ограничена и в ректах, и в долях: на узком блоке карточка не
       должна закрывать снимок целиком, на широком — растягиваться в строку
       во весь кадр. */
    /* Блок остаётся во всю ширину, а колонка со снимком ограничена дважды.

       По ширине — сорока шестью ректами: кадр без обрезки во всю страницу
       выходил огромным.

       По высоте — семьюдесятью долями окна. Ширины одной мало: вертикальный
       кадр при тех же сорока шести ректах вытягивается вниз на полторы
       страницы, и целиком его не видно ни в какой момент прокрутки. Высоту
       напрямую сетке не задать, поэтому ограничение переведено в ширину через
       пропорцию кадра — она приходит из разметки. Что жёстче, то и сработает:
       горизонтальный упрётся в ректы, вертикальный — в долю окна.

       Кадр при этом стоит целиком и не обрезается: меняется только то, сколько
       места ему отведено. */
    .pblock--overlay{
      /* Доля ряда приходит из разметки: она зависит от того, сколько таких же
         блоков стоит подряд, а этого стилям не узнать. Один занимает строку
         целиком, два — по половине, три — по трети.

         Отметка «текст справа» переносит кадр к другому краю его доли: пара
         из левого и правого сходится к середине разворота. */
      /* Потолок высоты. Ширина колонки не превосходит «пропорция на потолок»,
         поэтому потолок и решает, дотянется ли кадр до краёв блока.

         Считается он от ширины блока, а не от высоты окна: это высота кадра
         тринадцать к десяти, развёрнутого во всю ширину колонки страницы. Всё,
         что шире, — а это любой обычный горизонтальный снимок, — в потолок не
         упирается и занимает ширину блока целиком. Всё, что уже, упирается и
         стоит уже блока: вертикальный кадр во всю ширину вытянулся бы на две
         высоты экрана.

         Кадр во всю ширину выходит за верхний и нижний край экрана, и это
         выбрано намеренно: заполнить блок по ширине и уместиться в экран по
         высоте одному снимку нельзя, если его не резать. На окне ноутбука в 1518
         на 734 кадр четыре к трём занимает всю ширину блока при высоте в 831
         точку.

         Долей высоты окна тут не годится: с ней кадр дотягивался бы до краёв
         блока на приземистом окне и не дотягивал на вытянутом. Ширина блока
         известна: колонка страницы, ограниченная сверху, минус поля по краям. */
      --ovl-h: calc((min(var(--content-w), 100vw) - 2 * var(--space)) / 1.3);
      /* Ширина кадра идёт от его высоты, а высота ограничена долей окна —
         иначе вертикальный снимок вытягивался бы на полторы страницы.
         Отдельного потолка по ширине больше нет: его роль исполняет сама
         доля ряда, шире которой колонка всё равно не станет. */
      grid-template-columns:minmax(0, calc(var(--ovl-ar, 1.3333) * var(--ovl-h)));
      justify-content:start;
    }
    .pblock--overlay.is-span-6{ grid-column:1 / -1; }
    .pblock--overlay.is-span-3{ grid-column:span 3; }
    .pblock--overlay.is-span-2{ grid-column:span 2; }
    /* Кадр не всегда заполняет свою долю: высота ограничена, и у вертикального
       снимка сбоку остаётся запас. Прижимать все к одному краю нельзя — запас
       копится между блоками и читается широкой границей. Крайние в ряду
       прижимаются к середине, средний остаётся по центру: запас уходит к полям
       страницы, где он не мешает, а между блоками остаётся один шов.

       Какой блок в ряду крайний, стилям не видно — отметка приходит из
       разметки вместе с долей. */
    .pblock--overlay.is-row-first{ justify-content:end; }
    .pblock--overlay.is-row-last{ justify-content:start; }
    .pblock--overlay.is-row-mid{ justify-content:center; }
    .pblock--overlay.is-mirrored{ justify-content:end; }
    /* Карточка и снимок делят одну ячейку сетки, а не привязаны к блоку через
       absolute. Так карточка меряется по снимку, а не по всей ширине строки:
       у сузившегося вертикального кадра она прежде вылезала за его край,
       потому что доли считались от блока. */
    /* Снимков в наложении может быть несколько: они встают в ряд и
       выравниваются по высоте — ширина делится по их пропорциям, как в «Ряду
       с подъёмом». Ширину всей колонки задаёт сумма пропорций, поэтому ряд
       заполняет её без остатка и ни один кадр не режется. Прежде второй
       снимок уходил под первый, а колонка считалась только по первому. */
    .pblock--overlay .pblock-shots{
      grid-area:1 / 1; display:flex; gap:2px; align-items:flex-start;
    }
    /* Сотня — не размер, а множитель: доля роста меньше единицы раздаёт лишь
       свою часть свободного места, и одинокий вертикальный кадр с долей в три
       четверти занимал три четверти колонки вместо всей — карточка с текстом
       оказывалась за краем снимка. Пропорции между кадрами от общего
       множителя не меняются. */
    .pblock--overlay .pblock-shot{ flex:calc(var(--ar, 1.3333) * 100) 1 0; min-width:0; }
    .pblock--overlay .pblock-copy{
      grid-area:1 / 1; position:relative; z-index:1;
      align-self:end; justify-self:end;
      margin:clamp(.5rem, 1.2vw, 1.15rem);
      max-width:min(26rem, 70%);
      padding:clamp(1rem, .75rem + 1vw, 1.75rem);
      background:color-mix(in srgb, var(--bg) 86%, transparent);
      backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
      border:1px solid var(--line-strong);
    }
    /* Отметка «текст справа» переносит карточку к другому углу: по умолчанию
       она в правом нижнем, с отметкой — в левом. */
    .pblock--overlay.is-mirrored .pblock-copy{ justify-self:start; }
    /* Там, где браузер не умеет размывать фон, подложка становится плотной:
       иначе текст лёг бы прямо на снимок. */
    @supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
      .pblock--overlay .pblock-copy{ background:var(--bg); }
    }

    /* Текст в углу, снимки вокруг. Первый кадр держит левую колонку во всю
       высоту, текст встаёт справа сверху, остальные кадры уходят под него.
       Обёртка снимков растворяется — иначе они не разошлись бы по ячейкам. */
    /* Кадры стоят своим размером и не обрезаются.

       Раскладка рассчитана на два горизонтальных кадра. Первый занимает
       широкую колонку, текст стоит в углу, а второй — под текстом и заходит
       на первый: он сдвинут в его сторону отрицательным полем.

       Сдвиг задан в долях от колонки, а не в точках: тогда наложение
       держится любой ширины экрана. Отрицательное поле не только двигает
       кадр, но и уширяет его — второй снимок получается заметно крупнее
       своей колонки, и это здесь нужно.

       Свой слой обязателен: без него нижний кадр ушёл бы под первый — у них
       общая нумерация, и при равенстве побеждает тот, кто ниже в разметке. */
    .pblock--aside{ grid-template-columns:1.3fr .7fr; align-items:start; }
    .pblock--aside .pblock-shots{ display:contents; }
    .pblock--aside .pblock-shot:first-child{ grid-column:1; grid-row:1 / span 2; }
    .pblock--aside .pblock-copy{ grid-column:2; grid-row:1; }
    .pblock--aside .pblock-shot:nth-child(2){
      grid-column:2; grid-row:2;
      margin-left:-45%;
      position:relative; z-index:1;
      box-shadow:var(--shadow);
    }
    /* Третий и дальше идут обычным порядком под вторым, без наложения:
       раскладка задумана на два кадра, но лишний снимок не должен пропадать. */
    .pblock--aside .pblock-shot:nth-child(n+3){ grid-column:2; }

    .pblock--aside.is-mirrored{ grid-template-columns:.7fr 1.3fr; }
    .pblock--aside.is-mirrored .pblock-shot:first-child{ grid-column:2; }
    .pblock--aside.is-mirrored .pblock-copy{ grid-column:1; }
    .pblock--aside.is-mirrored .pblock-shot:nth-child(2){
      grid-column:1; margin-left:0; margin-right:-45%;
    }
    .pblock--aside.is-mirrored .pblock-shot:nth-child(n+3){ grid-column:1; }

    /* Низ вровень: высокий кадр сбоку, текст в углу, под текстом второй
       снимок. Оба кадра заканчиваются на одной линии — в этом весь приём.

       Ровный низ здесь получается сам, без подгонки высот. Боковой кадр
       занимает обе строки, поэтому его высота и высота пары «текст + снимок»
       — это одно и то же число: сетка растягивает строки под того, кто выше.
       Остаётся прижать содержимое к низу, и линия сходится.

       Что именно оказывается выше, роли не играет. Обычно выше боковой кадр —
       тогда лишняя высота уходит в просвет между текстом и нижним снимком,
       как в макете. Если текста много и перевешивает правая колонка, боковой
       кадр опускается к общей нижней линии, а просвет оказывается над ним.
       В обоих случаях кадры стоят своим размером: ничего не режется и не
       растягивается — ровно то, чем эта раскладка отличается от подгонки
       снимка под рамку.

       Пропорция колонок та же, что у «Текста в углу»: боковой кадр —
       главный в блоке и должен читаться крупным. */
    .pblock--flush{
      grid-template-columns:1.3fr .7fr; gap:2px;
      /* Вторая строка гибкая: в неё уходит вся высота, которую боковой кадр
         не забрал под текст. Именно она и делает обе линии ровными — нижний
         снимок занимает остаток ровно до низа блока.

         Нижняя граница у строки есть, иначе при длинном тексте остаток ушёл
         бы в ноль и снимок пропал бы вовсе. */
      grid-template-rows:auto minmax(clamp(2.5rem, 4vw, 5rem), 1fr);
    }
    .pblock--flush .pblock-shots{ display:contents; }
    /* Боковой кадр стоит своим размером и задаёт высоту всего блока: он
       занимает обе строки, и растянуть его было бы нечем, кроме обрезки. */
    .pblock--flush .pblock-shot:first-child{
      grid-column:1; grid-row:1 / span 2; align-self:start;
    }
    /* Зазор до текста тут держится отступом самой колонки: общий зазор блока
       сведён к двум точкам, чтобы кадры стояли почти встык, и текст без отступа
       лип бы к снимку. Отступ берётся с обеих сторон — слева до бокового кадра
       и снизу до нижнего, — и из него вычтены те самые две точки, чтобы в сумме
       вышел ровно общий зазор, как во всех остальных раскладках. */
    .pblock--flush .pblock-copy{
      grid-column:2; grid-row:1; align-self:start;
      padding:0 0 calc(var(--pblock-gap) - 2px) calc(var(--pblock-gap) - 2px);
    }
    /* Нижний снимок вписывается в остаток высоты. Обе границы — и по высоте
       строки, и по ширине колонки — стоят разом, а размеры отданы браузеру:
       у картинки со своей пропорцией он берёт ту границу, что жёстче, и
       уменьшает кадр целиком. Ни обрезки, ни растяжения не будет ни при каком
       раскладе — этим приём и отличается от подгонки под рамку.

       Просторно — упирается в ширину колонки и стоит во всю ширину, а
       свободная высота становится просветом под текстом, как в макете.
       Тесно — упирается в высоту и уменьшается вся, сохраняя пропорцию.
       В обоих случаях низ прижат к нижней линии блока. */
    .pblock--flush .pblock-shot:nth-child(2){
      grid-column:2; grid-row:2; min-height:0;
      display:flex; align-items:flex-end; justify-content:flex-end;
      overflow:visible;   /* рамка шире кадра: обрезать нечего и незачем */
    }
    .pblock--flush .pblock-shot:nth-child(2) img{
      width:auto; height:auto; max-width:100%; max-height:100%;
    }
    /* Раскладка рассчитана на два кадра. Третий и дальше идут под парой во всю
       ширину: втискивать их в правую колонку значило бы либо сбить общую
       нижнюю линию, либо посадить два снимка в одну ячейку друг на друга. */
    .pblock--flush .pblock-shot:nth-child(n+3){ grid-column:1 / -1; }

    .pblock--flush.is-mirrored{ grid-template-columns:.7fr 1.3fr; }
    .pblock--flush.is-mirrored .pblock-shot:first-child{ grid-column:2; }
    .pblock--flush.is-mirrored .pblock-copy{
      grid-column:1;
      padding:0 calc(var(--pblock-gap) - 2px) calc(var(--pblock-gap) - 2px) 0;
    }
    /* В зеркальном варианте наружным становится другой край — к нему кадр и
       прижимается, иначе при уменьшении он отошёл бы от края блока внутрь. */
    .pblock--flush.is-mirrored .pblock-shot:nth-child(2){
      grid-column:1; justify-content:flex-start;
    }
    .pblock--flush.is-mirrored .pblock-shot:nth-child(n+3){ grid-column:1 / -1; }

    /* Пара вровень. Слева высокий кадр, справа текст и широкий кадр под ним.
       Обе горизонтальные границы блока проходят разом по двум снимкам: верхняя
       по кромке высокого кадра и по первой строке текста, нижняя — по низу
       обоих кадров. При этом ни один из них не обрезан и не растянут: оба
       стоят во всю ширину своей колонки со своей пропорцией.

       Одновременно и то и другое даёт единственное деление ширины, и его
       считает эта строка. Пусть W — ширина блока без зазора между колонками,
       T — высота полосы под текстом, a и b — пропорции высокого и широкого
       кадров (ширина к высоте). Кадр во всю ширину колонки имеет высоту
       «ширина колонки на пропорцию», правый начинается на T ниже, и общий низ
       значит

           wl / a = T + wr / b,   wl + wr = W,

       откуда wl = (a·b·T + a·W) / (a + b). Два множителя при T и при W —
       единственное, что зависит от самих снимков; их подставляет разметка,
       пропорции знает только база. Запасные значения — вертикаль три к
       четырём рядом с горизонталью четыре к трём.

       Высота полосы под текстом задана здесь и в неё же заложен зазор до
       нижнего кадра: строка текста фиксированная, поэтому вертикального
       зазора у сетки нет, его добирает отступ самой колонки. Текст длиннее
       полосы её растянет — тогда высокий кадр останется своей высоты, и низ
       разойдётся. Это единственный случай, когда границы не сходятся, и
       выбирать между этим и обрезкой кадра не приходится: описание в этой
       раскладке — подпись на несколько строк. */
    .pblock--pair{
      --pair-t:clamp(8rem, 5rem + 4vw, 12rem);
      grid-template-columns:
        calc(var(--pair-la, .48) * var(--pair-t)
             + var(--pair-lw, .36) * (100% - var(--pblock-gap)))
        minmax(0, 1fr);
      grid-template-rows:minmax(var(--pair-t), auto) auto;
      gap:0 var(--pblock-gap);
    }
    .pblock--pair .pblock-shots{ display:contents; }
    /* Высокий кадр занимает обе строки и стоит своим размером — растянуть его
       было бы нечем, кроме обрезки. */
    .pblock--pair .pblock-shot:first-child{
      grid-column:1; grid-row:1 / span 2; align-self:start;
    }
    .pblock--pair .pblock-copy{
      grid-column:2; grid-row:1; align-self:start;
      padding-bottom:var(--pblock-gap);
    }
    .pblock--pair .pblock-shot:nth-child(2){ grid-column:2; grid-row:2; }
    /* Раскладка рассчитана на два кадра, и деление ширины считано по ним. Третий
       и дальше идут под парой во всю ширину: в правую колонку они встали бы
       друг на друга, а общий низ сбился бы. Шов в две точки — тот же, что между
       снимками внутри любого блока; у сетки вертикального зазора нет, он весь
       заложен в полосу под текст. */
    .pblock--pair .pblock-shot:nth-child(n+3){ grid-column:1 / -1; margin-top:2px; }

    .pblock--pair.is-mirrored{
      grid-template-columns:
        minmax(0, 1fr)
        calc(var(--pair-la, .48) * var(--pair-t)
             + var(--pair-lw, .36) * (100% - var(--pblock-gap)));
    }
    .pblock--pair.is-mirrored .pblock-shot:first-child{ grid-column:2; }
    .pblock--pair.is-mirrored .pblock-copy{ grid-column:1; }
    .pblock--pair.is-mirrored .pblock-shot:nth-child(2){ grid-column:1; }
    .pblock--pair.is-mirrored .pblock-shot:nth-child(n+3){ grid-column:1 / -1; }

    /* Половина ряда. Два таких блока подряд собираются в один разворот —
       тот же приём, что у «Колонки», только надвое. Так из двух блоков
       складывается одна составная секция: слева своя пара «снимок с текстом»,
       справа своя.

       Внутри снимок сверху, текст под ним: на половине ширины ставить их
       рядом уже тесно. */
    .pblock--half{ grid-column:span 3; }
    /* Снимок не выше доли экрана: половина ряда — это кадр, заголовок и
       описание одной колонкой, и вместе они должны помещаться в окно. На
       ноутбуке 1262 на 704 блок выходил 699 точек при 620 свободных под шапкой:
       заголовок читался, а описание уже нет.

       Уменьшается весь кадр, а не обрезается: ширина колонки и потолок высоты
       стоят разом, размеры отданы браузеру, и он берёт ту границу, что жёстче.
       Ширина при этом auto — с width:100% браузер сплющил бы кадр. */
    .pblock--half .pblock-shot img{
      /* Кадр занимает колонку целиком — и только если при этом он вылезет выше
         потолка, берётся та ширина, при которой он в потолок укладывается.
         Пропорция кадра известна разметке, поэтому вторая величина считается
         прямо здесь: высота, умноженная на пропорцию, и есть нужная ширина.

         Через width:auto с потолком высоты не выходило: кадр вставал в размер
         своей копии — 462 точки при колонке 476, — и до края не доставал. */
      width:min(100%, calc(72svh * var(--ar, 1.3333)));
      height:auto; margin-inline:auto;
    }
    .pblock--half .pblock-shots{ order:1; }
    .pblock--half .pblock-copy{ order:2; }
    .pblock--half.is-mirrored .pblock-shots{ order:2; }
    .pblock--half.is-mirrored .pblock-copy{ order:1; }

    /* На невысоком экране блок отдаёт снимку часть своих полей. Поля у блоков
       общие для всего сайта и заданы с запасом — на ноутбуке это под девяносто
       точек сверху и столько же снизу. Там, где кадр, заголовок и описание
       должны уместиться в окно разом, эти поля дороже самого кадра: ужав их,
       снимку отдаём почти полторы сотни точек высоты.

       Касается только двух раскладок, где потолок высоты и стоит. */
    @media (max-height:900px){
      .pblock--half, .pblock--full{
        padding-block:clamp(1.25rem, .75rem + 2vw, 3rem);
      }
    }

    /* Снимок, текст, снимок — сверху вниз. Текст стоит между кадрами и
       ограничен по ширине: он здесь пауза между двумя видами, а не колонка. */
    .pblock--stack{ grid-template-columns:1fr; justify-items:center; }
    .pblock--stack .pblock-shots{ display:contents; }
    .pblock--stack .pblock-shot:first-child{ order:1; }
    .pblock--stack .pblock-copy{ order:2; max-width:42rem; }
    .pblock--stack .pblock-shot:not(:first-child){ order:3; }

    /* Снимки лесенкой: каждый следующий опущен ниже предыдущего, текст сбоку.
       Сдвиг задан в долях от ширины кадра, а не в точках, — тогда ступени
       держатся любой ширины экрана и любого числа снимков. */
    .pblock--ladder{ grid-template-columns:minmax(10rem, 14rem) 1fr; align-items:start; }
    .pblock--ladder .pblock-shots{
      grid-auto-flow:column; grid-auto-columns:1fr; align-items:start;
    }
    /* Все кадры встают в одинаковые рамки. Без этого ступеней не видно: при
       одной ширине высота у каждого снимка своя, вертикальный вытягивается
       вдвое против горизонтального, и ряд читается свалкой, а не лесенкой.

       Пропорция рамки приходит из разметки — она считается по всем снимкам
       блока разом, см. _blocks.html. Ширины у колонок равные, значит и высоты
       выходят равными: ступень получается одна на всех.

       Снимок вписывается в рамку целиком и уменьшается, если не помещается;
       обрезки нет. Прижат к верху рамки, а не к середине: ступень задаётся
       верхней кромкой, и от центрирования кромки гуляли бы у каждого кадра
       по-своему. */
    .pblock--ladder .pblock-shot{ aspect-ratio:1 / var(--shot-box, .75); }
    .pblock--ladder .pblock-shot img{
      width:100%; height:100%;
      object-fit:contain; object-position:center top;
    }
    .pblock--ladder .pblock-shot:nth-child(2){ margin-top:12%; }
    .pblock--ladder .pblock-shot:nth-child(3){ margin-top:24%; }
    .pblock--ladder .pblock-shot:nth-child(n+4){ margin-top:36%; }
    .pblock--ladder.is-mirrored{ grid-template-columns:1fr minmax(10rem, 14rem); }

    /* «Ряд снимков» и «Ряд с подъёмом»: кадры делят полосу целиком. Рядом с
       ними стоит колонка текста, полоса и без того вдвое уже строки, и
       уменьшать кадры там нечем — они и так невелики. */
    .pblock--row .pblock-shots{ grid-auto-flow:column; grid-auto-columns:1fr; }
    .pblock--lift .pblock-shots{ display:flex; gap:0; align-items:flex-start; }
    .pblock--lift .pblock-shot{ flex:calc(var(--ar, 1.3333) * 100) 1 0; min-width:0; }

    /* «Во всю ширину»: и текст, и ряд кадров занимают полосу целиком, до самых
       полей страницы. Ширина между кадрами делится по их пропорциям — тогда
       высота у всех выходит одна, и низ ряда идёт по одной линии. Прежде высота
       была задана числом, а ряд стоял по центру: кадры не дотягивались до краёв,
       и отступы у заголовка и у снимков получались разные. */
    .pblock--full .pblock-shots{
      display:flex; gap:2px; align-items:flex-start;
      /* Потолок высоты — доля экрана, и держится он не обрезкой, а шириной.
         Ряд, выровненный по высоте, шире своей высоты ровно во столько раз,
         какова сумма пропорций его кадров (--shots-ar): значит потолок высоты,
         умноженный на неё, и есть та ширина, при которой ряд в потолок
         упирается. Кадры от этого мельчают целиком, ни один не режется.

         Без потолка блок из двух вертикальных кадров уезжал за нижний край
         экрана: на полосе 1115 точек ряд выходил 743 высотой, и вместе с
         заголовком блок не помещался в окно ноутбука. */
      /* Ширина явная: у ряда есть боковые поля auto — они центруют его, когда
         упирается в потолок, — а с ними ячейка сетки его больше не растягивает,
         и ряд садился по своему содержимому. На экране 1006 это было 928 точек
         при колонке 955: кадры брались в копии по 462 точки и ровно столько и
         занимали. */
      width:100%;
      max-width:calc(72svh * var(--shots-ar, 2.6667));
      margin-inline:auto;
    }
    .pblock--full .pblock-shot{ flex:calc(var(--ar, 1.3333) * 100) 1 0; min-width:0; }
    .pblock--full .pblock-shot img{ width:100%; height:auto; max-width:100%; }

    /* Ряд с подъёмом: три кадра в строку, текст сбоку, и под курсором снимок
       поднимается над соседями.

       Кадры стоят в одинаковых рамках, как в лесенке. Ряд — как раз тот
       случай, где разнобой высот виден сильнее всего: соседние снимки стоят
       на разных уровнях, и подъём читается вкривь, потому что подниматься
       не от чего. С общей рамкой у всех одна нижняя кромка, и движение
       становится заметным.

       Доли колонок те же, что у «Ряда снимков», — это он и есть, только с
       откликом на курсор. */
    .pblock--lift{
      grid-template-columns:minmax(10rem, 16rem) 1fr;
      align-items:center;
    }
    .pblock--lift.is-mirrored{ grid-template-columns:1fr minmax(10rem, 16rem); }
    /* Ряд выравнивается по высоте, а не по ширине колонок. Равные колонки
       давали равные рамки, но снимок внутри рамки стоял своей формой: у
       вертикального сверху оставался пустой запас, и верхние кромки шли
       вразнобой. Обрезать, чтобы их сравнять, нельзя.

       Ширина делится между кадрами по их же пропорциям: чем шире кадр, тем
       больше он забирает. Высота при таком делении у всех выходит одна —
       ровно ширина ряда, поделённая на сумму пропорций. Кромки сходятся и
       сверху, и снизу, кадры стоят целиком, а ряд занимает всю ширину без
       остатка.

       flex-basis нулевой: ширина должна идти только от пропорции, иначе к ней
       примешивался бы собственный размер снимка. min-width тоже нужен — без
       него кадр не сожмётся уже своей картинки, и деление сорвётся. */
    /* Зазор снят совсем: кадры в этом ряду стоят встык, и поднятый под
       курсором отделяется от соседей сам — движением, а не щелью. Прежний
       просвет между снимками был не зазором, а пустым запасом рамки. */

    /* Сам подъём. Только там, где курсор есть: на телефоне навести нельзя, а
       касание оставляло бы кадр поднятым до перехода на другую страницу.

       Поднимается рамка целиком, а не снимок внутри неё, — иначе кадр уехал
       бы из-под своей же обрезки. Тень появляется вместе с подъёмом: без неё
       кадр просто сдвигается, а должен оторваться от ряда.

       Своя ступень наложения обязательна: у кадров общая нумерация, и без неё
       поднятый уходил бы под соседа справа. */
    @media (hover:hover){
      .pblock--lift .pblock-shot{
        transition:transform .6s var(--ease-out), box-shadow .6s var(--ease-out);
      }
      .pblock--lift .pblock-shot:hover{
        transform:translateY(-24px);
        box-shadow:var(--shadow);
        position:relative; z-index:1;
      }
      /* Внутреннее приближение здесь отключено: вместе с подъёмом получается
         два движения разом, и ни одно не читается. */
      .pblock--lift .pblock-shot:hover img{ transform:none; }
    }
    /* Кому движение мешает — тому только тень. */
    @media (prefers-reduced-motion:reduce){
      .pblock--lift .pblock-shot{ transition:box-shadow .35s ease; }
      .pblock--lift .pblock-shot:hover{ transform:none; }
    }

    /* Зеркальный вариант: текст меняется местами со снимками. Отдельная
       отметка вместо удвоения списка раскладок. */
    .pblock.is-mirrored .pblock-copy{ order:2; }
    .pblock.is-mirrored .pblock-shots{ order:1; }
    .pblock--column.is-mirrored .pblock-copy{ order:1; }
    .pblock--column.is-mirrored .pblock-shots{ order:2; }
  }

  /* ============ ТЕ ЖЕ РАСКЛАДКИ НА ТЕЛЕФОНЕ ============
     На узком экране раскладки раньше просто складывались в столбик: текст,
     под ним снимки друг за другом. Все различия между четырнадцатью
     раскладками при этом пропадали — страница выглядела одинаковой лентой.

     Здесь композиции снимков возвращаются: ряд остаётся рядом, лесенка —
     лесенкой, наложение — наложением. Меняется только одно, и по необходимости:
     текст выходит из боковой колонки наверх, во всю ширину. Колонка рядом со
     снимками на трёхстах пятидесяти точках оставляет тексту около восьмидесяти
     — восемь знаков в строке, вертикальная лента слов вместо абзаца.

     Наложения и сдвиги заданы в долях, поэтому уменьшаются сами; там, где на
     телефоне они выходили резкими, доли занижены. */
  @media (max-width:819px){

    /* Ряд снимков, ряд с подъёмом, снимки во всю ширину и тройка в ряд —
       кадры остаются в строку, текст встаёт над ними. */
    .pblock--split .pblock-shots{ grid-auto-flow:column; grid-auto-columns:1fr; }
    /* Ряды снимков ведут себя тем же образом, что на большом экране, только
       ниже: одна высота на ряд, ширина по пропорции кадра, ряд по центру, а не
       поместился — листается вбок. Ужимать кадры под ширину экрана нельзя: два
       снимка на трёхстах пятидесяти точках вышли бы по сто семьдесят.
       Одно правило на все три раскладки: разные механики давали разные поля по
       краям, и ряд то прилипал к левому краю, то оставлял пустое место справа. */
    .pblock--row .pblock-shots,
    .pblock--lift .pblock-shots{
      display:flex; gap:2px; align-items:flex-start;
      /* safe: центрирование только пока ряд помещается. Без него не поместившийся
         ряд центрировался всё равно — и уезжал за оба края поровну, а к левому
         краю прокрутка уже не возвращает: за нулём её нет. Первый кадр так и
         оставался наполовину срезанным. */
      justify-content:safe center;
      padding-block:20px; margin-block:-20px;
      overflow-x:auto;
      overscroll-behavior-x:contain;
      scroll-snap-type:x proximity;
      scrollbar-width:none;
    }
    .pblock--row .pblock-shots::-webkit-scrollbar,
    .pblock--lift .pblock-shots::-webkit-scrollbar{ display:none; }
    /* Ширина делится по пропорциям кадров: ряд заполняет полосу целиком, и его
       края совпадают с краями заголовка. Прежде высота была задана, а ряд
       центрировался — пара кадров не дотягивала до краёв, и отступы у текста и
       у снимков выходили разные.

       Нижняя граница считается от той же пропорции, поэтому при любом числе
       кадров высота у всех одна: не помещаются — упираются в неё все разом и
       ряд уезжает под прокрутку, а не мельчает до неразличимого. */
    .pblock--row .pblock-shot,
    .pblock--lift .pblock-shot{
      flex:calc(var(--ar, 1.3333) * 100) 1 0;
      min-width:calc(var(--ar, 1.3333) * 9.5rem);
      scroll-snap-align:start;
    }



    /* Лесенка. Ступени мельче десктопных: на узком экране полная ступень
       съедала бы под треть высоты ряда. */
    .pblock--ladder .pblock-shots{
      grid-auto-flow:column; grid-auto-columns:1fr; align-items:start;
    }
    .pblock--ladder .pblock-shot{ aspect-ratio:1 / var(--shot-box, .75); }
    .pblock--ladder .pblock-shot img{
      width:100%; height:100%;
      object-fit:contain; object-position:center top;
    }
    .pblock--ladder .pblock-shot:nth-child(2){ margin-top:9%; margin-bottom:-9%; }
    .pblock--ladder .pblock-shot:nth-child(3){ margin-top:18%; margin-bottom:-18%; }
    .pblock--ladder .pblock-shot:nth-child(n+4){ margin-top:27%; margin-bottom:-27%; }

    /* Вступление: два кадра внахлёст. */
    .pblock--intro .pblock-shots{ display:flex; gap:0; align-items:flex-start; }
    .pblock--intro .pblock-shot{ flex:1 1 0; min-width:0; }
    .pblock--intro .pblock-shot:nth-child(2){
      margin-top:8%; margin-bottom:-8%; margin-left:-22%; z-index:1;
      box-shadow:var(--shadow);
    }

    /* Две раскладки, где кадры стоят рядом друг с другом, — «текст в углу» и
       «высокий и широкий снимки рядом» — на телефоне едут лентой слева направо,
       сами, без прикосновения. Текст у обеих уходит наверх во всю ширину.

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

       Высота у кадров одна, ширина у каждого своя — по его пропорции. Так
       верхняя и нижняя границы снимков идут по одной линии, а между ними
       остаётся только шов в две точки. Прежде рамки были одинаковыми и кадр
       вписывался в рамку целиком: широкий снимок в квадратной рамке оставлял по
       полю сверху и снизу, высокий — слева и справа, и эти поля читались
       огромным зазором между снимками, хотя сам зазор всё это время был в две
       точки.

       Высоту задаёт больший кадр: она равна доле окна, умноженной на пропорцию,
       усреднённую по снимкам блока, — у блока из одних горизонтальных кадров
       это их собственная высота, у пары с высоким кадром высота идёт по нему.
       Кадр при этом не режется и не тянется: рамка повторяет его пропорцию.

       Лента выходит на поля страницы: движение читается только тогда, когда
       кадру есть куда ехать. Ширина рамки — доля окна, а не доля блока: за
       полями блока ширины уже нет. */
    .pblock--aside,
    .pblock--pair,
    .pblock--full{ grid-template-columns:1fr; }
    .pblock--aside .pblock-copy,
    .pblock--pair .pblock-copy,
    .pblock--full .pblock-copy{ grid-column:1; grid-row:1; }
    .pblock--aside .pblock-shots,
    .pblock--pair .pblock-shots,
    .pblock--full .pblock-shots{
      grid-column:1; grid-row:2;
      display:block; overflow:hidden;
      margin-inline:calc(-1 * var(--space));
    }
    .pblock--aside .pblock-ribbon,
    .pblock--pair .pblock-ribbon,
    .pblock--full .pblock-ribbon{
      display:flex; gap:2px; width:max-content;
      animation:pblock-ribbon 26s linear infinite;
      will-change:transform;
    }
    .pblock--aside .pblock-shot,
    .pblock--aside .pblock-shot.is-echo,
    .pblock--pair .pblock-shot,
    .pblock--pair .pblock-shot.is-echo,
    .pblock--full .pblock-shot,
    .pblock--full .pblock-shot.is-echo{
      display:block; flex:none;
      /* Высота кадров — та, при которой самый широкий из них занимает почти всю
         ширину экрана. Прежде она считалась от доли окна на среднюю пропорцию, и
         кадры выходили мельче, чем позволяет экран: у пары горизонтальных
         снимков — 360 точек на 500 при возможных 480.

         Два предела, берётся меньший. Первый — доля окна на среднюю пропорцию
         блока: он держит высоту у блоков из одних вертикальных кадров, где
         иначе строка вытянулась бы на весь экран. Второй — ширина экрана,
         делённая на самую широкую пропорцию блока: он не даёт ни одному кадру
         вылезти за край, а значит и высоте — вырасти сверх той, при которой
         кадр виден целиком. */
      /* Третий предел — доля экрана по высоте: у блока из вертикальных кадров
         лента занимала экран почти целиком, и заголовок с описанием уходили за
         верхний край. Первые два предела оставлены как были, только чуть
         убавлены: кадры стали мельче, но по-прежнему видны целиком — режется
         здесь по-прежнему ничего. */
      height:min(calc(100vw * var(--shot-box, .75)),
                 calc(96vw / var(--shot-max, 1.3333)),
                 62svh);
      width:auto; aspect-ratio:var(--ar, 1.3333);
      margin:0; box-shadow:none;
    }
    .pblock--aside .pblock-shot img,
    .pblock--pair .pblock-shot img{
      width:100%; height:100%; object-fit:contain;
    }

    /* Один кадр в блоке «высокий и широкий снимки рядом» — ленты нет: ехать
       нечему, и копия в разметке не заводится.

       Правила выше рассчитаны на вереницу: кадру задаётся высота, а ширину
       считает пропорция. Одинокому снимку это выходило боком — он вставал
       узкой полосой у левого края, а справа оставалось пустое поле в треть
       экрана; на 504 точках кадр занимал 348.

       Поэтому одному кадру задаём ширину, а высоту считает пропорция.
       Контейнер уже выходит на поля страницы, поэтому снимок занимает всю
       ширину экрана — и ровно поэтому же стоит по центру. */
    .pblock--pair .pblock-shot:only-child{
      width:100%; height:auto; aspect-ratio:var(--ar, 1.3333);
    }
    .pblock--pair .pblock-shot:only-child img{ height:auto; }

    /* Низ вровень. Текст наверх, кадры рядом и оба прижаты к нижней линии —
       без второй строки сетки это выходит проще, чем на большом экране. */
    .pblock--flush{ grid-template-columns:1.3fr .7fr; gap:2px; }
    .pblock--flush .pblock-shots{ display:contents; }
    /* Тот же вычет двух точек, что и на большом экране: общий зазор блока здесь
       сведён к мозаичному, и до текста его добирает отступ колонки. */
    .pblock--flush .pblock-copy{
      grid-column:1 / -1; grid-row:1;
      padding:0 0 calc(var(--pblock-gap) - 2px);
    }
    .pblock--flush .pblock-shot:first-child{ grid-column:1; grid-row:2; }
    .pblock--flush .pblock-shot:nth-child(2){ grid-column:2; grid-row:2; align-self:end; }
    .pblock--flush .pblock-shot:nth-child(n+3){ grid-column:1 / -1; }
    .pblock--flush.is-mirrored{ grid-template-columns:.7fr 1.3fr; }
    .pblock--flush.is-mirrored .pblock-shot:first-child{ grid-column:2; }
    .pblock--flush.is-mirrored .pblock-shot:nth-child(2){ grid-column:1; }

    /* Текст поверх снимка. Тут менять нечего: текст и так на кадре, а не
       рядом с ним. Кадр занимает всю ширину — доли ряда на телефоне не
       делятся, два блока рядом дали бы по сто семьдесят точек. */
    .pblock--overlay{ --ovl-h: 70svh; }
    .pblock--overlay .pblock-shots{ grid-area:1 / 1; }
    /* Несколько кадров в наложении листаются вбок: ужать их под экран значило
       бы получить по сто семьдесят точек на снимок. Высота у всех одна, ширина
       по пропорции — ряд остаётся ровным, и ничего не режется.

       За поля страницы лента здесь не выходит: карточка с текстом стоит над
       снимком, и уехавший под край кадр увёл бы её за собой. */
    .pblock--overlay.is-row-shots .pblock-shots{
      display:flex; gap:2px; align-items:flex-start;
      overflow-x:auto; overflow-y:hidden;
      -webkit-overflow-scrolling:touch;
      overscroll-behavior-x:contain;
      scroll-snap-type:x proximity;
      scrollbar-width:none;
    }
    .pblock--overlay.is-row-shots .pblock-shots::-webkit-scrollbar{ display:none; }
    .pblock--overlay.is-row-shots .pblock-shot{
      flex:none; height:clamp(14rem, 52svh, 24rem); scroll-snap-align:start;
    }
    .pblock--overlay.is-row-shots .pblock-shot img{
      width:auto; height:100%; max-width:none;
    }
    .pblock--overlay .pblock-copy{
      grid-area:1 / 1; position:relative; z-index:1;
      align-self:end; justify-self:end;
      margin:.5rem;
      max-width:min(20rem, 82%);
      padding:.75rem .9rem;
      background:color-mix(in srgb, var(--bg) 86%, transparent);
      backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
      border:1px solid var(--line-strong);
    }
    .pblock--overlay.is-mirrored .pblock-copy{ justify-self:start; }
    @supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
      .pblock--overlay .pblock-copy{ background:var(--bg); }
    }

    /* Наезд на телефоне вдвое мельче заданного. Доля ширины и так уменьшает его
       втрое, но этого мало: на большом экране наезжающий блок обычно узкий и
       закрывает соседу угол, а здесь он во всю ширину и перекрыл бы половину. */


    /* Текст занимает всю ширину блока при любой раскладке. На большом экране
       он стоит боковой колонкой, и у части раскладок ограничен по ширине —
       на телефоне и то и другое лишнее: колонки нет, а строка и так коротка. */
    .pblock-copy{ grid-column:1 / -1; max-width:none; }

    /* Блоки, стоящие на ноутбуке в одном ряду, здесь не расходятся по строкам,
       а листаются вбок одной лентой: ряд из трёх на трёхстах пятидесяти точках
       дал бы по кадру в сто точек, а тремя строками потерялся бы сам приём —
       что это один разворот, а не три отдельных куска рассказа.

       Лента выходит за поля страницы и заводит их себе отступом: крайние кадры
       упираются в край экрана, как в макете, а не висят в рамке.

       Прилипание по проксимити, а не обязательное: обязательное мешает
       пролистнуть ленту насквозь одним движением. */
    .blocks-row{
      grid-column:1 / -1;
      display:flex; gap:2px;
      /* Лента выходит на поля страницы, как и снимки во всех прочих раскладках:
         иначе одни блоки шли бы от края до края, а лента висела бы в рамке. */
      margin-inline:calc(-1 * var(--space));
      overflow-x:auto; overflow-y:hidden;
      -webkit-overflow-scrolling:touch;
      overscroll-behavior-x:contain;
      scroll-snap-type:x proximity;
      scrollbar-width:none;
    }
    .blocks-row::-webkit-scrollbar{ display:none; }
    .blocks-row > .pblock{
      flex:none; width:min(78vw, 20rem);
      scroll-snap-align:start;
      /* Карточки одной высоты: без этого каждая тянулась по своему тексту, и
         три доли одного разворота стояли лесенкой разной длины. Общий рост
         задаёт самая высокая.

         Содержимое при этом остаётся наверху: сетка блока по умолчанию тянет
         свои строки на всю высоту, и подпись уезжала к нижнему краю карточки —
         тем дальше от снимка, чем короче был её текст. */
      align-self:stretch; align-content:start;
    }
    /* Ряд из долей делит одну сетку на всю ленту: подписи в первой строке,
       кадры во второй. Строки общие, поэтому заголовок в две строки поднимает
       первую строку у всех разом, а кадры остаются на одной линии.

       Прежде кадр стоял выше подписи — только так он не съезжал вслед за
       заголовком, — но подпись в карточке должна быть сверху, как во всех
       прочих раскладках на телефоне. Общая сетка снимает выбор между тем и
       другим. Карточка занимает обе строки и повторяет их у себя: своих
       размеров у неё нет, они приходят из ленты.

       Прочим лентам вторая строка ни к чему — отметку ставит разметка. */
    /* Ряд из половин — не лента: половина ряда это крупная карточка со своим
       заголовком и описанием, и листать её вбок незачем — читают их подряд.
       Поэтому блоки встают друг под друга во всю ширину страницы, как обычные. */
    .blocks-row.is-pairs{
      display:grid; grid-template-columns:1fr;
      gap:var(--pblock-gap);
      margin-inline:0; overflow:visible;
    }
    .blocks-row.is-pairs > .pblock{ width:auto; align-self:auto; }

    .blocks-row.is-thirds{
      display:grid; grid-auto-flow:column;
      grid-auto-columns:min(78vw, 20rem);
      grid-template-rows:auto auto;
      gap:var(--pblock-gap) 2px;
    }
    .blocks-row.is-thirds > .pblock{
      width:auto;
      /* Доля сетки рассказа блоку здесь не нужна и мешает: в ленте он сам —
         клетка, и «во всю ширину сетки» сваливало все карточки в первый
         столбец друг под друга. */
      grid-column:auto;
      grid-row:span 2; grid-template-rows:subgrid;
    }
    /* Высота кадров общая на весь ряд: ширина карточки, делённая на самую
       широкую пропорцию в ряду. Самый широкий кадр занимает карточку целиком,
       остальные выходят уже её, но той же высоты и по центру — верх и низ у
       всех трёх идут по одной линии, и обрезать ничего не приходится.

       Пропорцию ряда ставит разметка: соседей по ряду стили не видят. */
    .blocks-row > .pblock--column .pblock-shot{
      height:calc(min(78vw, 20rem) / var(--row-ar, 1.3333));
      display:flex; align-items:center; justify-content:center;
      overflow:hidden;
    }
    .blocks-row > .pblock--column .pblock-shot img{
      width:auto; height:100%; max-width:100%; object-fit:contain;
    }
    /* В ленте наезжать не на что: соседи стоят рядом, а не выше. */
    /* Наезд на телефоне вдвое мельче заданного. Доля ширины и так уменьшает его
       втрое, но этого мало: на большом экране наезжающий блок обычно узкий и
       закрывает соседу угол, а здесь он во всю ширину и перекрыл бы половину. */
    .pblock.is-overlapping{
      margin-top:calc(-.5 * var(--overlap, 0) * var(--overlap-base) / 100);
    }

    .blocks-row > .pblock.is-overlapping{ margin-top:0; }

    /* Единственный снимок в блоке. Многоколонные раскладки ему только мешают:
       «низ вровень» и «текст в углу» держат его в своей узкой доле, а обёртка
       снимков у них растворена, и отступ ей задать нечем. Возвращаем блок к
       одной колонке.

       Высокий кадр разворачивается на всю ширину наравне с широким. Прежде он
       оставался в полях и вставал по центру — с потолком высоты в семь десятых
       окна, — и на странице, где почти все снимки вертикальные, поля не уходили
       почти нигде: кадр выходил 422 точки из 500 и висел в рамке.

       Наложение сюда не входит: у него своя мера, от пропорции кадра. */
    .pblock.is-solo-wide:not(.pblock--overlay),
    .pblock.is-solo-tall:not(.pblock--overlay){ grid-template-columns:1fr; }
    .pblock.is-solo-wide:not(.pblock--overlay) .pblock-shots,
    .pblock.is-solo-tall:not(.pblock--overlay) .pblock-shots{
      display:grid; grid-column:1 / -1;
    }
    .pblock.is-solo-wide:not(.pblock--overlay) .pblock-shot,
    .pblock.is-solo-tall:not(.pblock--overlay) .pblock-shot{
      grid-column:1 / -1; grid-row:auto; margin:0;
    }

    /* Снимки выходят на поля страницы во всех раскладках: поля съедают у кадра
       по шестнадцать точек с каждой стороны, а кадр на телефоне — главное, что
       есть в блоке. Текст поля себе оставляет: строка от края до края читается
       тяжело.

       Врезка — единственное исключение: у неё рамка с подложкой, и кадр,
       развёрнутый на поля, вылезал бы из собственной карточки.

       Раскладкам, где полоса снимков растворена или делит ячейку с текстом,
       поля отдаёт сам блок — сдвинуть одну полосу там нечем. Им же возвращается
       отступ у текста. */
    .pblock:not(.pblock--inset):not(.pblock--overlay):not(.pblock--flush)
      .pblock-shots{ margin-inline:calc(-1 * var(--space)); }
    .pblock--overlay,
    .pblock--flush{ margin-inline:calc(-1 * var(--space)); }
    .pblock--flush .pblock-copy{ padding-inline:var(--space); }
    /* В ленте блок занимает не всю ширину, а карточку в ней, и разворачивать
       кадр на поля страницы там нечем: он просто вылезал из своей карточки на
       ширину поля в обе стороны, а соседние карточки оставались с отступами. */
    .blocks-row .pblock:not(.pblock--inset):not(.pblock--overlay):not(.pblock--flush)
      .pblock-shots{ margin-inline:0; }
    .blocks-row .pblock{ margin-inline:0; }
    .blocks-row .pblock--flush .pblock-copy{ padding-inline:0; }
    /* Врезка остаётся заметкой на подложке, только во всю ширину: без рамки
       она читалась продолжением соседнего текста. */
    .pblock--inset{
      background:var(--surface); border:1px solid var(--line);
      padding:clamp(.85rem, .6rem + 1vw, 1.25rem);
    }
  }


  /* ============ СТРАНИЦА ПОРТФОЛИО ============
     Все работы одной сеткой. В отличие от ленты на главной, где карточки
     идут в ряд и листаются, здесь важно охватить всё сразу — поэтому сетка
     подстраивается под ширину сама, без точных порогов: колонок столько,
     сколько помещается карточек шириной от 17rem. */
  .portfolio-page{ padding-block:clamp(1rem, .75rem + 1vw, 1.75rem) clamp(3rem, 2rem + 4vw, 6rem); }
  .portfolio-page .section-head{ margin-bottom:clamp(1.75rem, 1.25rem + 2vw, 3rem); }
  .portfolio-page h1{ font-size:var(--fs-h2); }

  .works-grid{
    display:grid; gap:clamp(1rem, .75rem + 1vw, 1.75rem);
    grid-template-columns:repeat(auto-fill, minmax(17rem, 1fr));
  }
  .work-card{
    display:flex; flex-direction:column;
    text-decoration:none; color:inherit;
    border:1px solid var(--line);
    background:var(--surface);
    transition:border-color .3s var(--ease-out), box-shadow .35s ease;
  }
  .work-card:hover{ border-color:var(--accent); box-shadow:var(--shadow); }

  /* Пропорция задана, чтобы ряд карточек стоял ровно: обложки бывают и
     вертикальные, и горизонтальные, и без неё сетка шла бы уступами. */
  .work-shot{ position:relative; aspect-ratio:4/3; overflow:hidden; }
  .work-shot img{
    width:100%; height:100%; object-fit:cover; display:block;
    filter:saturate(.82) contrast(1.07) brightness(.95) sepia(.06);
    transition:transform .6s var(--ease-out), filter .35s ease;
  }
  .work-card:hover .work-shot img{
    transform:scale(1.04);
    filter:saturate(.92) contrast(1.08) brightness(1) sepia(.03);
  }

  .work-body{ padding:clamp(.9rem, .7rem + .6vw, 1.25rem); display:flex; flex-direction:column; gap:.6rem; }
  .work-body h2{ font-size:1.02rem; line-height:1.3; }
  .work-card:hover h2{ color:var(--accent); }

  /* Дизайнер и объект — подпись сверху, значение под ней: в узкой карточке
     строка «Дизайнер — Бюро такое-то» переносилась бы вразнобой. */
  .work-meta{ display:flex; flex-wrap:wrap; gap:.5rem 1.5rem; margin:0; }
  .work-meta dt{
    font-size:.68rem; letter-spacing:.14em; text-transform:uppercase;
    color:var(--muted); margin-bottom:.15rem;
  }
  .work-meta dd{ margin:0; font-size:.86rem; color:var(--ink); }

  .works-empty{ color:var(--muted); }

  /* ============ СТРАНИЦА ОБЪЕКТА ============ */
  /* Шапка липкая, а не поверх страницы, — своё место в потоке у неё уже есть,
     и большой отступ сверху только отодвигал содержимое вниз впустую. */
  .project-page{
    /* Один шаг между блоками на всю страницу: вступление, фотографии, факты,
       заявка. Прежде у каждого был свой, и низ страницы «дышал» вдвое шире
       верха. Половинный шаг — для заголовка над своим же блоком. */
    --pg-step: clamp(2rem, 1.5rem + 1.5vw, 3rem);
    --pg-half: clamp(1rem, .75rem + .75vw, 1.5rem);
    padding-block:clamp(1rem, .75rem + 1vw, 1.75rem) clamp(3rem, 2rem + 4vw, 6rem);
  }

  .back-link{
    display:inline-flex; align-items:center; gap:.6rem;
    font-size:var(--fs-label); letter-spacing:.1em; text-transform:uppercase;
    color:var(--muted); text-decoration:none; margin-bottom:2rem;
    transition:color .3s var(--ease-out), gap .3s var(--ease-out);
  }
  .back-link:hover{ color:var(--accent); gap:1rem; }
  .back-link svg{ width:1rem; height:.625rem; stroke:currentColor; fill:none; stroke-width:1.2; }

  /* Название держится в одну строку: размер подбирается под ширину экрана,
     а перенос разрешён только на узких, где строка иначе стала бы нечитаемо
     мелкой. Заголовки в .section-head балансируют переносы — здесь это
     выключено, балансировать нечего. */
  .project-page h1{
    font-size:clamp(1.45rem, .95rem + 2vw, 2.6rem);
    white-space:nowrap; text-wrap:nowrap;
  }
  /* Подписи стояли далеко от названия: складывались два отступа — снизу у
     заголовка и сверху у самих подписей, вместе под девяносто точек. Здесь
     они одна группа, между ними нужен зазор, а не разрыв. */
  .project-page .section-head{
    max-width:none; margin-bottom:0;
    /* Подпись раздела и название — одна группа, между ними нужен зазор, а не
       разрыв: на общем для сайта отступе они читались как два разных блока. */
    gap:.3rem;
  }
  @media (max-width:620px){
    .project-page h1{ white-space:normal; text-wrap:balance; }
  }
  .project-summary{ color:var(--muted); font-size:var(--fs-lead); max-width:56ch; }

  /* Дизайнер слева, объект справа, оба сдвинуты от краёв к середине.
     Колонки закреплены за полями, а не расставлены потоком: если объект не
     указан, дизайнер иначе съехал бы на его место. Линия снизу идёт во всю
     ширину — она держит общий левый край страницы. */
  /* Подписи и описание стоят одной строкой: слева объект с дизайнером,
     справа рассказ о работе. Черта под ними теперь у всей строки, а не у
     одних подписей — иначе она обрывалась бы на середине. */
  .project-intro{
    display:grid; gap:clamp(1.25rem, 1rem + 1vw, 2rem);
    /* Сверху отступ заметно больше, чем внутри пары «подпись — значение»:
       так видно, что это отдельный блок, а не продолжение названия. */
    margin:clamp(2.5rem,2rem + 1.75vw,4rem) 0 var(--pg-step);
    padding-bottom:clamp(1.5rem,1rem + 1.5vw,2.25rem);
    border-bottom:1px solid var(--line);
  }
  /* Три колонки в ряд: дизайнер, объект, описание. Колонка подписей идёт по
     содержимому, но не шире 30rem — иначе длинное название объекта отжало бы
     текст к правому краю. Остальное достаётся описанию. */
  @media (min-width:820px){
    .project-intro{
      grid-template-columns:fit-content(30rem) 1fr;
      gap:clamp(2rem, 1.5rem + 2.5vw, 4.5rem);
      align-items:start;
    }
  }

  /* Дизайнер и объект стоят рядом, а не друг под другом. Перенос разрешён:
     на узком экране пара всё же встанет в столбик, а не срежет названия. */
  .project-meta{
    display:flex; flex-wrap:wrap;
    gap:clamp(1rem, .85rem + .5vw, 1.5rem) clamp(1.75rem, 1.25rem + 2vw, 3.5rem);
    margin:0; padding:0;
  }
  /* Пара стоит по центру и разворачивается лицом к нему: дизайнер прижат
     к середине справа, объект — слева. Сама линия снизу остаётся во всю
     ширину и держит общий левый край страницы. */
  .project-meta .fact-designer, .project-meta .fact-object{ text-align:left; }

  .project-facts{
    display:grid; gap:1.5rem 2rem; margin:0 0 var(--pg-step);
    padding-block:1.75rem; border-block:1px solid var(--line);
    grid-template-columns:repeat(auto-fit, minmax(11rem, 1fr));
  }
  /* Подпись — строка в одну строку, и интервал абзаца ей ни к чему: он
     добавлял сверху и снизу по пять точек пустоты, из-за чего подпись
     отрывалась от своего значения и липла к тому, что стоит выше. */
  .project-facts dt, .project-meta dt{
    font-size:var(--fs-label); letter-spacing:.12em; text-transform:uppercase;
    color:var(--accent); margin-bottom:.45rem; line-height:1.3;
  }
  .project-facts dd, .project-meta dd{ margin:0; font-size:1.02rem; }
  /* Крупнее фактов под фотографиями, но мельче заголовка объекта —
     иначе строка спорит с названием за внимание. */
  .project-meta dd{
    font-family:var(--font-display); font-weight:600; letter-spacing:-.01em;
    font-size:clamp(1.35rem, 1.1rem + 1vw, 2rem); line-height:var(--lh-head);
  }
  .project-meta dt{ margin-bottom:.3rem; }


  /* Описание идёт до фотографий и во всю ширину блока, а не колонкой:
     так его просили. Оговорка: на широком мониторе строка выходит длинной,
     и для длинных текстов читать её труднее — короткие описания в один-два
     абзаца это не задевает. */
  .project-body{ color:var(--muted); }
  .project-body p + p{ margin-top:1rem; }

  .project-gallery-title{
    font-size:var(--fs-h3); margin-bottom:var(--pg-half);
  }
  /* Ровно четыре в ряд — и это не auto-fill: при одной-двух фотографиях
     auto-fill растянул бы их на всю ширину, а нужен один и тот же мелкий
     размер независимо от того, сколько снимков у объекта. */
  .project-gallery{
    /* Поле снизу такое же, как сверху: сверху его задаёт нижний отступ
       строки вступления, и значения должны совпадать. */
    display:grid; gap:2px; margin-bottom:var(--pg-step);
    grid-template-columns:repeat(4, 1fr);
  }
  .project-gallery .p-item{
    aspect-ratio:4/3; display:block; position:relative;
    cursor:zoom-in; text-decoration:none;
  }
  @media (max-width:900px){ .project-gallery{ grid-template-columns:repeat(3, 1fr); } }
  @media (max-width:560px){ .project-gallery{ grid-template-columns:repeat(2, 1fr); } }

  /* Рамка отступала внутрь на свои поля, и текст блока начинался правее
     названия, фотографий и всего остального на странице. Вместо рамки —
     линии сверху и снизу, как у фактов: левый край общий со страницей. */
  .project-cta{
    border:0; border-block:1px solid var(--line-strong);
    padding:clamp(1.75rem,1.25rem + 2vw,3rem) 0;
    display:flex; flex-direction:column; gap:.75rem;
    margin-bottom:var(--pg-step);
  }
  .project-cta h2{ font-size:var(--fs-h3); }
  .project-cta p{ color:var(--muted); }
  .project-cta-actions{ display:flex; flex-wrap:wrap; gap:1rem; margin-top:.75rem; }

  .project-others{
    display:grid; gap:2px;
    grid-template-columns:repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
  }
  .project-others .p-item{ aspect-ratio:4/5; text-decoration:none; display:block; position:relative; }

  /* ============ ПРОСМОТР ФОТОГРАФИИ ВО ВЕСЬ ЭКРАН ============ */
  /* Пока просмотрщик открыт, страница под ним не прокручивается: иначе на
     телефоне палец уводит её вбок вместо перелистывания снимков. */
  html.zoom-open, html.zoom-open body{ overflow:hidden; }

  /* Раскладка сеткой с именованными местами, а не абсолютными отступами:
     у каждого элемента своя клетка, и наложиться друг на друга они не могут
     ни при каком размере окна. Снимок занимает середину и ужимается под неё. */
  .zoombox{
    position:fixed; inset:0; z-index:200;
    display:grid;
    grid-template-columns:auto minmax(0, 1fr) auto;
    grid-template-rows:auto minmax(0, 1fr) auto;
    grid-template-areas:
      ".    close  close"
      "prev stage  next"
      "cap  cap    cap";
    align-items:center; justify-items:center;
    gap:clamp(.5rem, .25rem + 1vw, 1.25rem);
    padding:clamp(.75rem, .5rem + 1.5vw, 2rem);
    background:rgba(6,9,10,.94);
    backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
    animation:zoomboxIn .3s var(--ease-out) both;
  }
  @keyframes zoomboxIn{ from{ opacity:0; } to{ opacity:1; } }

  .zoombox-stage{
    grid-area:stage;
    margin:0; min-width:0; min-height:0;
    display:flex; align-items:center; justify-content:center;
    width:100%; height:100%;
  }
  .zoombox-stage img{
    max-width:100%; max-height:100%;
    width:auto; height:auto; object-fit:contain;
    border:1px solid var(--line-strong);
    box-shadow:0 40px 90px -30px rgba(0,0,0,.9);
    animation:zoomboxImg .35s var(--ease-out) both;
  }
  @keyframes zoomboxImg{ from{ opacity:0; transform:scale(.97); } to{ opacity:1; transform:none; } }

  .zoombox-close, .zoombox-nav{
    display:grid; place-items:center;
    width:3rem; height:3rem; flex:none;
    border:1px solid var(--line-strong);
    background:rgba(10,13,14,.6);
    color:var(--ink);
    transition:color .25s var(--ease-out), border-color .25s var(--ease-out),
               background .25s var(--ease-out);
  }
  .zoombox-close{ grid-area:close; justify-self:end; align-self:start; }
  .zoombox-nav.prev{ grid-area:prev; }
  .zoombox-nav.next{ grid-area:next; }
  .zoombox-close:hover, .zoombox-nav:hover{
    color:var(--accent); border-color:var(--accent); background:rgba(10,13,14,.9);
  }
  .zoombox-close svg, .zoombox-nav svg{
    width:20px; height:20px; fill:none; stroke:currentColor;
    stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round;
  }

  .zoombox-caption{
    grid-area:cap;
    display:flex; flex-direction:column; align-items:center; gap:.25rem;
    text-align:center; font-size:.85rem; color:var(--muted);
    /* подпись под снимком, а не поверх него — читается при любом фоне */
    padding-block:.35rem;
  }
  .zoombox-caption b{
    font-family:var(--font-display); font-size:.8rem; letter-spacing:.14em;
    color:var(--accent); font-weight:400;
  }

  @media (max-width:640px){
    /* На узком экране стрелки по бокам съедали бы ширину снимка,
       поэтому уезжают под него в один ряд. */
    .zoombox{
      grid-template-columns:1fr 1fr;
      grid-template-rows:auto minmax(0, 1fr) auto auto;
      grid-template-areas:
        "close close"
        "stage stage"
        "prev  next"
        "cap   cap";
    }
    .zoombox-nav.prev{ justify-self:end; }
    .zoombox-nav.next{ justify-self:start; }
    .zoombox-close, .zoombox-nav{ width:2.75rem; height:2.75rem; }
  }

  @media (prefers-reduced-motion:reduce){
    .zoombox, .zoombox-stage img{ animation:none; }
  }

  /* ============ ПРАВОВЫЕ СТРАНИЦЫ ============ */
  .foot-policy{ color:var(--muted); text-decoration:none; border-bottom:1px solid var(--line); }
  .foot-policy:hover{ color:var(--accent); border-bottom-color:var(--accent); }

  .legal-page{ padding-block:clamp(7rem, 5rem + 6vw, 10rem) clamp(3rem, 2rem + 4vw, 6rem); }
  .legal-page h1{ font-size:var(--fs-h2); }

  /* Договорной текст читают подряд, а не просматривают: узкая колонка и
     крупный межстрочный интервал здесь важнее плотности. */
  .legal-body{
    max-width:72ch;
    margin-top:clamp(2rem, 1.5rem + 2vw, 3rem);
    display:flex; flex-direction:column; gap:1.1rem;
    color:var(--muted); line-height:1.8;
  }
  .legal-body h2{
    font-size:var(--fs-h3); color:var(--ink);
    margin-top:clamp(1.5rem, 1rem + 1.5vw, 2.5rem);
    padding-top:clamp(1.5rem, 1rem + 1.5vw, 2.5rem);
    border-top:1px solid var(--line);
  }
  .legal-body h2:first-child{ margin-top:0; padding-top:0; border-top:0; }
  .legal-body ul{ display:flex; flex-direction:column; gap:.55rem; padding-left:1.4rem; }
  .legal-body li{ position:relative; }
  .legal-body li::before{
    content:""; position:absolute; left:-1.4rem; top:.75em;
    width:.45rem; height:1px; background:var(--accent);
  }
  .legal-body a{ color:var(--accent); text-decoration:none; border-bottom:1px solid var(--line-strong); }
  .legal-body a:hover{ border-bottom-color:var(--accent); }

  .legal-meta{
    max-width:72ch;
    margin-top:clamp(2rem, 1.5rem + 2vw, 3rem);
    padding-top:1.25rem;
    border-top:1px solid var(--line);
    font-size:.85rem; color:var(--muted);
  }
  .legal-meta a{ color:var(--accent); text-decoration:none; }

  /* ============================================================
     ТЕЛЕФОН И ПЛАНШЕТ
     Раскладка везде выше построена от узкого экрана к широкому, поэтому
     здесь только то, что порогом «от такой ширины» не выражается: лента
     портфолио, размеры под палец и шапка на тесном экране.
     ============================================================ */

  /* ---- портфолио лентой ----
     Закреплённая галерея работает от 900px: её ведёт вертикальная прокрутка,
     а на телефоне это ощущается как залипание страницы. Ниже 900px тот же
     ряд листается пальцем вбок — одна строка с прилипанием к карточкам. */
  @media (max-width:899px){
    .track-viewport{
      max-width:none; margin-inline:0;      /* лента идёт от края до края */
      overflow-x:auto; overflow-y:hidden;
      -webkit-overflow-scrolling:touch;
      overscroll-behavior-x:contain;        /* не «пролистывать» историю назад */
      scroll-snap-type:x proximity;
      scroll-padding-inline-start:var(--space);
      scrollbar-width:none;
    }
    .track-viewport::-webkit-scrollbar{ display:none; }

    .portfolio-grid{
      display:flex; width:max-content;
      grid-template-columns:none;
    }
    /* Карточка заметно уже экрана: край следующей всегда виден,
       и по нему понятно, что ряд листается дальше. */
    .portfolio-grid .p-item{
      flex:0 0 auto;
      width:clamp(190px, 62vw, 300px);
      scroll-snap-align:start;
    }

    /* «Другие объекты» на странице работы — тот же приём: одна строка,
       листается пальцем. Сеткой они занимали пол-экрана в высоту, а
       посмотреть предлагается мимоходом, не прокручивая всю страницу. */
    .project-others{
      display:flex; width:max-content;
      grid-template-columns:none;
      overflow:visible;
    }
    .project-others .p-item{
      flex:0 0 auto;
      width:clamp(190px, 62vw, 300px);
      scroll-snap-align:start;
    }
    /* Обёртка со своей прокруткой: сам список её не получает, иначе
       карточки обрезались бы по краю страницы. */
    .others-strip{
      max-width:none; margin-inline:calc(-1 * var(--space));
      padding-inline:var(--space);
      overflow-x:auto; overflow-y:hidden;
      -webkit-overflow-scrolling:touch;
      overscroll-behavior-x:contain;
      scroll-snap-type:x proximity;
      scrollbar-width:none;
    }
    .others-strip::-webkit-scrollbar{ display:none; }
  }

  /* ---- размеры под палец ----
     Палец попадает точнее курсора только по крупной цели, поэтому на
     сенсорных экранах всё нажимаемое подрастает. Условие по способу ввода,
     а не по ширине: планшет в альбомной ориентации шире иного ноутбука. */
  @media (hover:none){
    /* Указателя на сенсорном экране нет вовсе — подменять нечего. */
    a, button, summary, label, .btn, [role="tab"], [data-zoom], .p-item{ cursor:auto; }
    .btn{ padding:.85rem 1.3rem; font-size:.85rem; }
    .socials-inline a{ width:2.75rem; height:2.75rem; }
    .socials-inline svg{ width:18px; height:18px; }
    .socials-inline .mark{ font-size:.62rem; }
    /* Но не в шапке. Там строка упирается в 1180px, и прибавка к иконкам и
       кнопке её переполняла: на планшете в альбомной ориентации меню
       наезжало на телефон уже с 1280px. В шапке остаются настольные
       размеры — кнопка при этом всё равно около 41px в высоту, то есть
       под палец годится, а иконки крупнее только в выдвижном меню, где
       места сколько угодно. */
    .header-row .btn{ padding:.65rem 1.15rem; font-size:.82rem; }
    .header-row .socials-inline a{ width:2.1rem; height:2.1rem; }
    .header-row .socials-inline svg{ width:15px; height:15px; }
    .header-row .socials-inline .mark{ font-size:.55rem; }
    .mobile-nav a{ padding-block:.95rem; }
    .hero-dot{ width:52px; height:32px; }
    .p-item{ -webkit-tap-highlight-color:transparent; }
  }

  /* ---- шапка на тесном экране ---- */
  @media (max-width:520px){
    /* Шапка здесь ниже — значит и остаток окна под обложку другой. */
    :root{ --header-h: calc(1.4rem + 2.25rem + 1px); }
    .header-row{ gap:.6rem; padding-block:.7rem; }
    .logo{ gap:.6rem; }
    .logo-mark{ height:2.25rem; }
    .logo-text{ font-size:1.3rem; }
    .logo-text small{ font-size:.5rem; letter-spacing:.14em; }
    .header-actions{ gap:.5rem; }
  }
  @media (max-width:430px){
    /* Кнопка рядом с названием уже не помещается и жмёт логотип.
       Заявку оставляют из меню — там она первым пунктом. */
    .header-actions .btn-primary{ display:none; }
  }
  /* `.mobile-nav a` задаёт цвет и поля всем ссылкам меню и по весу перебивает
     `.btn-primary` — кнопка иначе вышла бы светлым текстом по золоту, без
     боковых полей и с лишней чертой снизу. */
  .mobile-nav a.btn{
    justify-content:center;
    padding:.85rem 1.3rem;
    margin-bottom:.85rem;
    border-bottom:0;
  }
  .mobile-nav a.btn-primary{ color:var(--accent-ink); }

  /* ---- первый экран на невысоком окне ----
     Содержимое первого экрана набрано от ширины: кегль, поля и зазоры растут
     вместе с ней. На широком, но невысоком окне ноутбука — 1518 на 734, к
     примеру, — оно набирало 767 точек при 640 доступных и вылезало за край.

     Здесь те же величины считаются от высоты окна и потому ужимаются вместе с
     ним. Пределы снизу оставлены прежние: ниже них строка перестаёт читаться. */
  @media (min-width:621px) and (max-height:900px){
    .hero-inner{ padding-block:clamp(1rem, 2.4vh, 2.5rem); }
    .hero h1{ font-size:clamp(1.7rem, 5.4vh, 3.4rem); }
    .hero-eyebrow{ margin-bottom:clamp(.5rem, 1.2vh, 1.1rem); font-size:.9rem; }
    .hero-actions{ margin-top:clamp(1rem, 2.6vh, 2.25rem); }
  }

  /* ---- первый экран на телефоне ---- */
  @media (max-width:620px){
    .hero h1{ max-width:none; }
    .hero-actions{ gap:.65rem; margin-top:1.75rem; }
    .hero-actions .btn{ flex:1 1 100%; justify-content:center; }
    /* пустоты сверху на маленьком экране особенно жаль */
    .legal-page{ padding-block:1.25rem 3rem; }
    .project-cta-actions .btn{ flex:1 1 100%; justify-content:center; }
  }

  /* ============ СТРАНИЦА «СОТРУДНИЧЕСТВО» ============ */
  /* ---- первый экран страницы ---- */
  /* Снимок во всю ширину полосы, текст поверх него. Высота задана полосе, а не
     снимку: он растягивается под неё и обрезается по краям, зато на любом
     экране полоса одинаковой пропорции, а дом остаётся в кадре. */
  /* Снимок в шапке — на весь экран, как на главной: страница открывается
     кадром во всю высоту окна, а не полосой под шапкой. Это минимум, а не
     жёсткая высота: если содержимому тесно — на невысоком окне или при крупном
     шрифте у посетителя, — полоса вырастет, и ничего не обрежется. */
  .coop-hero{
    position:relative; isolation:isolate; overflow:hidden;
    padding-block:0; background:var(--surface);
    min-height:calc(100svh - var(--header-h));
    display:flex; align-items:center;
  }
  .coop-hero-bg{
    position:absolute; inset:0; z-index:-2;
    width:100%; height:100%; object-fit:cover; object-position:center 42%;
    /* Приглушён тем же приёмом, что кадры на главной: свой он яркий — синее
       небо и зелёный газон, — и рядом с тёмной страницей смотрелся бы
       вклеенным из другого места. */
    filter:saturate(.8) contrast(1.05) brightness(.82) sepia(.05);
  }
  /* Затемнение гуще у краёв, где лежат буквы, и почти сходит на нет в
     середине, где смотрят на сам дом. Снизу добавлено отдельно: там проходит
     нижняя строка цифр. */
  .coop-hero-scrim{
    position:absolute; inset:0; z-index:-1;
    background:
      linear-gradient(90deg, rgba(8,11,12,.92) 0%, rgba(8,11,12,.72) 32%,
                             rgba(8,11,12,.28) 58%, rgba(8,11,12,.66) 100%),
      linear-gradient(0deg, rgba(8,11,12,.55), transparent 45%);
  }
  /* Заголовок слева и приписка справа начинаются с одной высоты: столбцы
     выровнены по верху, и ни у того, ни у другого нет своего отступа сверху.
     Прежде приписка висела выше заголовка — глаз цеплялся за уступ. */
  .coop-hero-inner{
    position:relative; width:100%;
    display:grid; align-items:start;
    gap:clamp(1.25rem, 1rem + 1.5vw, 2.5rem);
    padding-block:clamp(2rem, 1.5rem + 3vw, 4rem);
  }
  @media (min-width:900px){
    .coop-hero-inner{ grid-template-columns:minmax(0, 1fr) minmax(8rem, 11rem); }
  }
  /* Кегль заголовка считается от ширины окна, а не задан ступенями: слово
     «Сотрудничество» не переносится, и при слишком крупном кегле оно вылезало
     из своей колонки — на окне 1400 обрезалось на «Сотрудничест». */
  .coop-hero h1{
    font-size:clamp(1.9rem, 4.6vw, 3.6rem); line-height:1.05;
    margin-top:0;
  }
  .coop-lead{
    color:#d7dbd8; font-size:var(--fs-lead); line-height:1.5;
    max-width:40ch; margin-top:clamp(.8rem, .6rem + .6vw, 1.2rem);
  }
  /* Цифры рядом с обещанием, а не отдельным блоком: поодиночке они читаются
     как заявление, здесь — как его подтверждение. */
  .coop-stats{
    display:flex; flex-wrap:wrap;
    gap:clamp(1rem, .6rem + 1.4vw, 2.25rem);
    margin-top:clamp(1.5rem, 1rem + 2vw, 2.5rem);
  }
  .coop-stat{ display:grid; gap:.3rem; max-width:13rem; }
  .coop-stat b{
    font-family:var(--font-display); font-weight:600; line-height:1;
    font-size:clamp(1.8rem, 1.4rem + 1.5vw, 2.6rem);
  }
  .coop-stat span{ color:#c3cac6; font-size:.84rem; line-height:1.4; }
  .coop-stat + .coop-stat{
    border-left:1px solid var(--line-strong);
    padding-left:clamp(1rem, .6rem + 1.4vw, 2.25rem);
  }
  .coop-stat--wide{ align-content:center; }
  /* На узком экране цифры переносятся на две строки, и черта, стоявшая только
     между соседями, оказывалась в начале второй строки, а первая цифра
     оставалась без неё — ряд читался кривым. Поэтому на телефоне черта у
     каждой цифры, включая первую. */
  @media (max-width:899px){
    .coop-stat{ border-left:1px solid var(--line-strong); padding-left:.9rem; }
    .coop-stat + .coop-stat{ padding-left:.9rem; }
    .coop-stats{ gap:.9rem 1.1rem; }
  }

  /* Приписка у правого края: не заголовок, а реплика вполголоса — отсюда
     мелкий кегль и разрядка. На телефоне она встаёт под текстом, и черта
     переезжает наверх: сбоку от неё там уже ничего нет. */
  .coop-hero-note{
    display:grid; gap:.5rem; align-content:start;
    font-size:.72rem; letter-spacing:.14em; text-transform:uppercase;
    line-height:1.6; color:#b9c0bc;
    border-top:1px solid var(--line-strong); padding-top:.9rem;
  }
  .coop-hero-note b{ color:var(--ink); font-weight:600; }
  @media (min-width:900px){
    .coop-hero-note{
      border-top:0; border-left:1px solid var(--line-strong);
      padding:0 0 .2rem clamp(1rem, .7rem + 1vw, 1.75rem);
    }
  }

  /* Середина страницы — сплошная тёмная полоса, а не прозрачная: за страницей
     лежит снимок, и длинный текст с формой поверх него читался бы с трудом.
     Полосы чередуются по тону с первым экраном и концовкой — тот же приём, что
     у разделов на главной. */
  /* Полосы приглушают снимок за страницей, но не закрывают его наглухо: он и
     поставлен затем, чтобы страница не была плоской. Плотность подобрана под
     шторку, которая и так лежит поверх снимка у страниц с обложкой, — вместе
     они дают тот же запас читаемости, что сплошная заливка. */
  .coop-main, .coop-works{ background:rgba(10,13,14,.58); }

  /* Шторка над снимком здесь легче, чем на странице работы: там за страницей
     обложка объекта и поверх неё идёт длинный текст, а тут снимок — часть
     оформления. Запас читаемости держат сами полосы содержимого. */
  body.coop-page{
    --scrim-1: .55;
    --scrim-2: .48;
    --scrim-3: .62;
  }

  /* ---- доводы и заявка рядом ---- */
  /* Доводы слева, заявка справа: человек читает, чем мы удобны, и тут же, не
     уходя глазами со страницы, пишет. На узком экране колонки распрямляются в
     тот же порядок, в каком они лежат в разметке. */
  .coop-main{ padding-block:clamp(1.5rem, 1rem + 2vw, 3rem); }
  .coop-main-row{
    display:grid; align-items:start; width:100%;
    gap:clamp(1.5rem, 1.2rem + 2vw, 3rem);
  }
  /* Полоса занимает ровно один экран и не больше: доводы и заявка должны
     умещаться целиком, без прокрутки — человек видит и то, и другое разом.
     Отсюда и ужатые отступы внутри: содержимое подогнано под высоту окна, а не
     наоборот. На узком экране колонки распрямляются, и высоту задавать нечему —
     там полоса растёт под содержимое. */
  @media (min-width:1040px){
    .coop-main{
      min-height:calc(100svh - var(--header-h));
      display:flex; align-items:center;
    }
    .coop-main-row{ grid-template-columns:minmax(0, 1fr) minmax(0, 1fr); }
  }
  .coop-benefits{
    display:grid;
    gap:clamp(1.1rem, .9rem + .8vw, 1.6rem) clamp(1.25rem, 1rem + 1.4vw, 2.25rem);
    margin-top:clamp(1.1rem, .9rem + 1vw, 1.75rem);
  }
  /* На невысоком экране — ноутбук на 768 точек и ниже — полоса в один экран не
     влезала: доводам и форме нужно около шестисот точек, а свободных остаётся
     под шестьсот. Поэтому там всё сжимается ещё раз: отступы, зазоры, поля
     ввода и кегль заголовков. Кегль самого текста не трогаем — читать его
     всё равно придётся. */
  @media (min-width:1040px) and (max-height:820px){
    .coop-main{ padding-block:1rem; }
    .coop-main-row{ gap:1.25rem; }
    .coop-why .section-head h2{ font-size:1.5rem; }
    .coop-benefits{ gap:.7rem 1.25rem; margin-top:.7rem; }
    .coop-form{ padding:1.1rem 1.25rem; gap:.4rem; }
    .coop-form h2{ font-size:1rem; margin-bottom:.2rem; }
    .coop-form .field input, .coop-form .field textarea, .coop-form select{
      padding:.45rem .7rem;
    }
    .coop-form textarea{ min-height:3.2rem; }
  }
  /* Совсем невысокий экран — ноутбук на 1366 на 768 с панелью задач и вкладками
     остаётся с шестью сотнями точек. Здесь ужимается и текст доводов: иначе
     полоса всё равно вылезает за экран, а вылезти она не должна. */
  @media (min-width:1040px) and (max-height:700px){
    .coop-main{ padding-block:.6rem; }
    .coop-main-row{ gap:1rem; }
    .coop-benefits{ gap:.5rem 1rem; }
    .coop-benefit p{ font-size:.8rem; line-height:1.4; }
    .coop-benefit-icon{ width:1.4rem; height:1.4rem; margin-bottom:.2rem; }
    .coop-form{ padding:.9rem 1.1rem; gap:.3rem; }
  }
  /* Два столбца, а не три: доводы теперь занимают половину ширины страницы, и
     третий столбец ужал бы каждый до двух слов в строке. */
  @media (min-width:640px){ .coop-benefits{ grid-template-columns:repeat(2, 1fr); } }
  .coop-benefit{ display:grid; gap:.5rem; align-content:start; }
  /* Рамка значка равна самому рисунку: тогда расстояние до заголовка у всех
     шести одинаковое, его задаёт один зазор сетки, а не пустота внутри рамки. */
  .coop-benefit-icon{
    display:block; width:1.65rem; height:1.65rem; color:var(--ink);
    margin-bottom:.35rem;
  }
  .coop-benefit-icon svg{ width:100%; height:100%; }
  .coop-benefit h3{ font-size:.97rem; line-height:1.25; }
  .coop-benefit p{ color:var(--muted); font-size:.85rem; line-height:1.45; max-width:30ch; }
  /* Заголовок раздела мельче общего: рядом с ним в ту же высоту должны влезть
     шесть доводов, а сам он и так набран крупнее всего на полосе. */
  .coop-why .section-head h2{ font-size:clamp(1.5rem, 1.2rem + 1.1vw, 2.15rem); }

  /* ---- заявка ---- */
  .coop-apply{ display:grid; }
  .coop-form{
    background:var(--surface);
    padding:clamp(1.25rem, 1rem + 1.1vw, 1.9rem);
    display:grid; gap:.5rem; align-content:start;
  }
  .coop-form h2{
    font-size:clamp(1.05rem, .95rem + .45vw, 1.3rem);
    text-transform:uppercase; letter-spacing:.04em; line-height:1.25;
    margin-bottom:.35rem;
  }
  .coop-form textarea{ min-height:4.5rem; }
  /* Поля берутся общие для сайта — те же, что в форме на главной. Своё правило
     только у списка: обычных полей это касается, а списка среди них нет. */
  .coop-form select{
    width:100%; font:inherit; font-size:.94rem; color:var(--ink);
    background:var(--surface-2); border:1px solid var(--line-strong);
    border-radius:2px; padding:.6rem .75rem; cursor:pointer;
    appearance:none; padding-right:2.2rem;
    transition:border-color .25s var(--ease-out), background .25s var(--ease-out);
    /* Своя стрелка: родная у каждого браузера выглядит по-своему и ломает
       ровный ряд полей. */
    background-image:linear-gradient(45deg, transparent 50%, var(--muted) 50%),
                     linear-gradient(135deg, var(--muted) 50%, transparent 50%);
    background-position:calc(100% - 1.05rem) 1.05rem, calc(100% - .75rem) 1.05rem;
    background-size:.35rem .35rem, .35rem .35rem;
    background-repeat:no-repeat;
  }
  .coop-form select:focus-visible{
    outline:none; border-color:var(--accent); background-color:var(--surface);
  }
  .coop-form .btn{ width:100%; justify-content:center; margin-top:.3rem; }
  .coop-form .lead-done{ color:var(--accent); font-size:.9rem; }

  /* ---- работы ---- */
  .coop-works{ padding-bottom:clamp(2.5rem, 2rem + 3vw, 5rem); }
  .coop-works-head{
    display:flex; flex-wrap:wrap; gap:.6rem 1.5rem;
    align-items:baseline; justify-content:space-between;
  }
  .coop-works-head h2{
    font-size:clamp(1.3rem, 1.1rem + 1vw, 1.9rem);
    text-transform:uppercase; letter-spacing:.04em;
  }
  .coop-works-all{
    font-size:.78rem; letter-spacing:.1em; text-transform:uppercase;
    color:var(--ink); border-bottom:1px solid var(--line-strong); padding-bottom:.15rem;
  }
  .coop-works-all i{ font-style:normal; margin-left:.4rem; }
  .coop-works-lead{ color:var(--muted); margin-top:.6rem; max-width:60ch; }
  .coop-works-row{
    display:grid; grid-template-columns:repeat(2, 1fr);
    gap:clamp(.8rem, .5rem + 1vw, 1.4rem);
    margin-top:clamp(1.2rem, 1rem + 1.5vw, 2rem);
  }
  @media (min-width:900px){ .coop-works-row{ grid-template-columns:repeat(4, 1fr); } }
  /* На телефоне работы идут одной строкой, которую листают пальцем: сеткой из
     двух столбцов они занимали два экрана в высоту, а посмотреть их
     предлагается мимоходом. Тот же приём, что у «других объектов» на странице
     работы. Лента выходит за поля страницы к самым краям экрана — обрезанная
     по краю колонки, она не читалась бы продолжающейся. */
  @media (max-width:899px){
    .coop-works-row{
      display:flex; grid-template-columns:none;
      margin-inline:calc(-1 * var(--space));
      padding-inline:var(--space);
      overflow-x:auto; overflow-y:hidden;
      -webkit-overflow-scrolling:touch;
      overscroll-behavior-x:contain;
      scroll-snap-type:x proximity;
      scrollbar-width:none;
    }
    .coop-works-row::-webkit-scrollbar{ display:none; }
    .coop-work{
      flex:0 0 auto; width:clamp(190px, 62vw, 300px);
      scroll-snap-align:start;
    }
  }
  .coop-work{ text-decoration:none; }
  .coop-work img{ display:block; width:100%; aspect-ratio:3 / 4; object-fit:cover; }
  .coop-work-name{
    display:flex; justify-content:space-between; gap:.5rem; align-items:baseline;
    margin-top:.7rem; color:var(--ink);
    font-size:.76rem; letter-spacing:.08em; text-transform:uppercase; line-height:1.4;
  }
  .coop-work-name i{ font-style:normal; color:var(--muted); }
  @media (hover:hover){
    .coop-work:hover .coop-work-name{ color:var(--accent-2); }
  }

  /* ---- последняя полоса ---- */
  .coop-final{ background:var(--surface); padding-block:clamp(2rem, 1.5rem + 2.5vw, 3.5rem); }
  .coop-final-row{ display:grid; gap:clamp(1rem, .8rem + 1vw, 2rem); align-items:center; }
  @media (min-width:900px){
    .coop-final-row{ grid-template-columns:minmax(0, 1fr) minmax(0, 1fr) auto; }
  }
  .coop-final h2{
    font-size:clamp(1.4rem, 1.2rem + 1vw, 2.1rem);
    text-transform:uppercase; letter-spacing:.03em; line-height:1.2;
  }
  .coop-final p{ color:var(--muted); font-size:.95rem; line-height:1.55; max-width:40ch; }

  /* ============ ПЛАШКА ПРО COOKIE ============ */
  .cookie-note{
    position:fixed; z-index:120;
    left:var(--space); right:var(--space); bottom:var(--space);
    max-width:var(--content-w); margin-inline:auto;
    display:flex; flex-wrap:wrap; align-items:center; gap:1rem 1.5rem;
    padding:1rem clamp(1rem, .75rem + 1vw, 1.5rem);
    background:color-mix(in srgb, var(--surface) 92%, transparent);
    backdrop-filter:blur(12px); -webkit-backdrop-filter:blur(12px);
    border:1px solid var(--line-strong);
    box-shadow:var(--shadow);
  }
  /* Отметку о согласии ставит скрипт в голове страницы — до первой
     отрисовки, чтобы вернувшийся посетитель не видел мельканья. */
  html.cookie-ok .cookie-note{ display:none; }
  .cookie-note p{ flex:1 1 22rem; font-size:.86rem; color:var(--muted); line-height:1.55; }
  .cookie-note a{ color:var(--accent); text-decoration:none; border-bottom:1px solid var(--line-strong); }
  .cookie-note a:hover{ border-bottom-color:var(--accent); }
  .cookie-note .btn{ flex:none; }

  @media (max-width:560px){
    .cookie-note{ left:0; right:0; bottom:0; border-inline:0; border-bottom:0; }
    .cookie-note .btn{ flex:1 1 100%; justify-content:center; }
    /* Плашка прибита к нижнему краю и во всю ширину — она закрывала собой
       подвал и ленту партнёров, пока её не приняли. Пока она видна,
       странице добавляется запас снизу, чтобы низ можно было докрутить. */
    html:not(.cookie-ok) .site{ padding-bottom:12rem; }
  }

  /* ============ ЛЕНТА ЛОГОТИПОВ ПАРТНЁРОВ ============ */
  /* Заливка та же, что у «продукции»: лента партнёров и подвал под ней
     завершают страницу одной полосой, отделённой волосяной линией. */
  .partners{
    padding-block:clamp(1.4rem, 1.1rem + 1vw, 2.25rem);
    background:var(--surface); border-top:1px solid var(--line);
  }
  .partners .label{ display:block; margin-bottom:clamp(.9rem, .7rem + .8vw, 1.35rem); }

  /* Края растворяются в фоне: логотип не обрезается на полуслове,
     а уходит в темноту — так лента читается бесконечной. */
  .partners-viewport{
    overflow:hidden;
    -webkit-mask-image:linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
    mask-image:linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  }
  .partners-track{
    display:flex; align-items:center;
    gap:clamp(2rem, 1.6rem + 2.4vw, 4rem);
    width:max-content;
    /* дорожка сдвинута на половину и едет к нулю — то есть слева направо */
    animation:partners-run 45s linear infinite;
    will-change:transform;
  }
  @keyframes partners-run{
    from{ transform:translate3d(-50%,0,0); }
    to  { transform:translate3d(0,0,0); }
  }
  .partners-viewport:hover .partners-track{ animation-play-state:paused; }

  .partner{ flex:none; }
  .partner img{
    height:clamp(1.25rem, 1.3vw + .875rem, 1.75rem); width:auto;
    /* Логотипы нарисованы тёмным по прозрачному — на нашем фоне их бы
       не было видно. Перекрашиваем в светлое: сначала всё в чёрный,
       затем инверсия. Прозрачные места инверсия не трогает. */
    filter:brightness(0) invert(1);
    opacity:.5;
    transition:opacity .35s var(--ease-out);
  }
  .partner img:hover{ opacity:1; }

  @media (prefers-reduced-motion:reduce){
    /* Движение отключено — лента становится обычным рядом. Половина копий
       лишняя, но она помечена aria-hidden и просто уезжает за край. */
    .partners-track{ animation:none; }
  }

  /* ============ ПРАВКИ РИТМА ГЛАВНОЙ ============ */

  /* «Шесть направлений — раскройте нужное» ломалось надвое не из-за нехватки
     места на экране, а из-за общего ограничения .section-head в 42rem. Здесь
     оно снято, а размер подобран так, чтобы строка помещалась с запасом. */
  #products .section-head{ max-width:none; }
  @media (min-width:700px){
    #products .section-head h2{
      white-space:nowrap; text-wrap:nowrap;
      font-size:clamp(1.45rem, .75rem + 2.2vw, 2.6rem);
    }
  }

  /* Секции с заливкой (достижения, продукция, контакты) прячут свои поля
     внутри цветной полосы, и между блоками читается один отступ. У секций
     без заливки поля складываются — получался двойной провал.

     Между лентой портфолио и «Процессом» к этому добавлялась пустота от
     самой ленты: она стоит по центру экрана высотой в 100vh, и снизу под
     ней всегда остаётся свободное место. */
  #portfolio.is-pinned + #process{ padding-top:0; }
  #process + #strip{ padding-top:0; }

  /* ============ НАПРАВЛЕНИЯ ВКЛАДКАМИ ============ */

  /* Полоса переключателей. Без скриптов вкладки не работают, поэтому до
     включения режима она не показывается, а разделы видны все сразу — со
     своими заголовками. Класс tabs-ready ставит скрипт. */
  .tab-strip{ display:none; }
  .tabs-ready .tab-strip{
    display:flex; gap:1px;
    margin-bottom:clamp(1.5rem, 1.25rem + 1vw, 2.5rem);
    background:var(--line); border:1px solid var(--line);
    overflow-x:auto; scrollbar-width:none;
  }
  .tabs-ready .tab-strip::-webkit-scrollbar{ display:none; }

  /* flex:1 1 auto, а не 1 0 auto: длинные названия вроде «Двери и
     перегородки» должны иметь право ужаться, иначе их обрезает край. */
  .tab-btn{
    flex:1 1 auto; padding:.7rem .85rem;
    background:var(--surface); color:var(--muted);
    font-size:.7rem; line-height:1.25; letter-spacing:.04em; text-transform:uppercase;
    border-bottom:2px solid transparent;
    transition:color .25s var(--ease-out), background .25s var(--ease-out),
               border-color .25s var(--ease-out);
  }
  .tab-btn:hover{ color:var(--ink); }
  .tab-btn[aria-selected="true"]{
    color:var(--ink); background:var(--surface-2); border-bottom-color:var(--accent);
  }

  .tabs-ready .tab-panel-title{ display:none; }
  /* без скриптов разделы идут подряд, и им нужен воздух между собой */
  .tabs:not(.tabs-ready) .tab-panel + .tab-panel{ margin-top:clamp(2rem,1.5rem + 2vw,3rem); }
  .tab-panel-title{ font-size:var(--fs-h3); margin-bottom:1.25rem; }

  .tab-body{ display:grid; gap:clamp(1.5rem, 1.25rem + 1.5vw, 2.75rem); }
  @media (min-width:900px){
    .tab-body{
      grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
      grid-template-rows:auto auto;
    }
    /* В разметке кнопка стоит после снимков — так она встаёт правильно на
       телефоне, где колонка одна. Здесь сетка возвращает её под перечень. */
    .kind-list{ grid-column:1; grid-row:1; }
    .kind-cta{ grid-column:1; grid-row:2; align-self:start; }
    .kind-shots{ grid-column:2; grid-row:1 / span 2; align-self:center; }
  }

  .kind-list{ display:flex; flex-direction:column; }
  details.kind{ border-bottom:1px solid var(--line); }
  details.kind summary{
    list-style:none; cursor:pointer;
    display:flex; align-items:center; justify-content:space-between; gap:1rem;
    padding-block:.7rem;
    font-family:var(--font-body); font-size:.72rem; font-weight:600;
    letter-spacing:.08em; text-transform:uppercase; color:var(--accent);
    line-height:1.3;
    transition:color .25s var(--ease-out);
  }
  details.kind summary::-webkit-details-marker{ display:none; }
  details.kind summary:hover{ color:var(--ink); }
  .kind-plus{ flex:none; width:14px; height:14px; transition:transform .25s var(--ease-out); }
  details.kind[open] .kind-plus{ transform:rotate(45deg); }
  .kind p{
    color:var(--muted); font-size:.85rem; line-height:1.45; max-width:46ch;
    padding-bottom:.85rem;
  }
  .kind-intro{ color:var(--muted); font-size:.85rem; line-height:1.45; }
  .kind-cta{ justify-self:start; align-self:start; margin-top:.6rem; }
  /* На телефоне её убираем: ниже, в блоке «Знаковые проекты», стоит своя
     кнопка на все работы, и две подряд ведут в одно и то же место. */
  @media (max-width:619px){ .kind-cta{ display:none; } }

  /* Карусель снимков: карточки не уезжают за край, а ходят по кругу.
     Передняя крупнее всех и перекрывает соседей, дальние мельче и глубже.
     Раскладку считает скрипт — она зависит от того, какая карточка сейчас
     впереди, а не от порядка в разметке. */
  /* z-index:0 обязателен, хотя ничего не поднимает. Карточкам внутри скрипт
     раздаёт слои до десятого — чтобы передняя перекрывала соседние. Без своего
     слоя у ленты эти номера соревнуются со всей страницей, в том числе с
     шапкой: у неё тоже десятый, а при равенстве побеждает та, что ниже в
     разметке. Передняя карточка выезжала поверх шапки при прокрутке.

     Собственный слой замыкает эту нумерацию внутри ленты: снаружи она вся
     считается нулевой и уходит под шапку целиком. */
  .kind-shots{
    --card: clamp(9.375rem, 16vw, 15rem);
    position:relative; z-index:0;
    height:calc(var(--card) * 4 / 3);
    overflow:hidden;
    /* Горизонтальный смах — наш: им листают карусель, и браузер не должен
       принимать его за прокрутку. Вертикальный остаётся ему: страницу
       пролистывают и через карусель тоже. */
    touch-action:pan-y;
  }

  .kind-shot{
    position:absolute; left:50%; top:50%;
    display:block;
    width:var(--card); aspect-ratio:3/4;
    margin:0;
    border:1px solid var(--line-strong);
    box-shadow:var(--shadow);
    background:var(--surface);
    cursor:zoom-in;
    /* сдвиг к середине задан здесь, остальное дописывает скрипт */
    transform:translate(-50%, -50%);
    transition:transform .7s var(--ease-out), opacity .7s var(--ease-out);
  }
  .kind-shot img{ width:100%; height:100%; object-fit:cover; display:block; }
  .kind-shot:hover{ box-shadow:var(--shadow-gold); }

  /* Стрелки у карусели. Ход по кругу идёт сам, но посмотреть хочется тот
     кадр, что уехал, — стрелки дают его вернуть.

     Свой слой поверх карточек: те раздают себе номера до десятого. Стоят
     внутри рамки, с отступом от края, — у рамки overflow:hidden, и снаружи
     их срезало бы. */
  .kind-nav{
    position:absolute; top:50%; z-index:11;
    display:grid; place-items:center;
    width:2.375rem; height:2.375rem;
    transform:translateY(-50%);
    border:1px solid var(--line-strong);
    background:rgba(10,13,14,.55);
    color:var(--ink);
    cursor:pointer;
    /* Курсор над стрелкой карусель придерживает — это делает mouseenter самой
       рамки, и отдельного правила не нужно. */
    transition:color .25s var(--ease-out), border-color .25s var(--ease-out),
               background .25s var(--ease-out), box-shadow .25s var(--ease-out);
  }
  .kind-nav.prev{ left:.5rem; }
  .kind-nav.next{ right:.5rem; }
  /* Золотом — как и всё, на что наводят: ссылки, кнопки просмотрщика, рамка
     самого кадра. Вместе с цветом загорается и свечение, иначе стрелка на
     подробном снимке едва отличима от обводки. */
  .kind-nav:hover{
    color:var(--accent); border-color:var(--accent);
    background:rgba(10,13,14,.85);
    box-shadow:var(--shadow-gold);
  }
  .kind-nav svg{
    width:17px; height:17px; fill:none; stroke:currentColor;
    stroke-width:1.6; stroke-linecap:round; stroke-linejoin:round;
  }

  /* Запасная раскладка: без скриптов карточки легли бы одна на другую.
     Ряд без нахлёста некрасив, но читаем — а это главное. Со скриптами
     карусель работает на всех экранах: на ноутбуке её листают стрелками,
     на телефоне — пальцем. */
  .kind-shots:not(.carousel-ready){
    display:flex; gap:2px; height:auto; overflow-x:auto; scrollbar-width:none;
  }
  .kind-shots:not(.carousel-ready) .kind-shot{
    position:static; flex:0 0 auto; transform:none;
  }

  /* ============ ЭТАПЫ РАБОТЫ ============ */
  /* Блок идёт в общей колонке страницы. Прежде он разворачивался во всю
     ширину: по краям стояли стеклянные панели, и «вся ширина» была почти
     вровень с колонкой. Панелей нет, и блок стал заметно шире соседних
     разделов. Шестиколоночная раскладка ниже как раз на такую ширину и
     рассчитана. */

  .stages{
    --stage-gap: clamp(1.5rem, .8rem + 2vw, 3.5rem);
    list-style:none; margin:0; padding:0; display:grid; gap:2rem;
  }
  /* Вертикальная черта идёт вдоль всего этапа слева от цифры — как в
     макете. Это рамка самого блока, а не отдельная засечка: рамка сама
     тянется на его высоту, сколько бы строк ни было в описании. */
  .stage{
    position:relative;
    /* Черта отдельным слоем, а не рамкой блока: рамке свечение не задать —
       тень у блока обходит его со всех четырёх сторон, и вокруг каждого этапа
       проступал светлый прямоугольник. Слой шириной в точку светится сам, и
       свет идёт только от линии.

       Светится потому, что тонкая линия поверх подробного снимка теряется в его
       собственных рёбрах и бликах: свечение отделяет её от кадра — так же, как
       подсвеченная кромка стекла отделяется от интерьера за ней. */
    /* Поля с двух сторон равны: слева текст отступает от пунктирной черты,
       справа на столько же не доходит до края своей колонки. У последнего
       этапа край колонки совпадает с краем блока — поэтому его строка
       заканчивается ровно там же, где начинается строка первого. */
    padding-inline:.9rem .9rem;
  }
  /* Тёмная обводка в точку прижата к линии, светящийся ореол идёт дальше. Без
     обводки видимость линии зависела от того, что за ней на снимке: на тёмной
     стороне лестницы она горела, а на светлой стене почти пропадала — рядом с
     цифрой 3 линия читалась вдвое ярче остальных. Обводка отделяет линию от
     светлого места, на тёмном её самой не видно. */
  .stage::after{
    content:""; position:absolute; left:0; top:0; bottom:0; width:1px;
    background:rgba(244,242,236,.9);
    box-shadow:0 0 0 1px rgba(6,9,10,.55), 0 0 14px rgba(244,242,236,.8);
  }
  /* Ширину заголовку задаём явно, а не оставляем на усмотрение содержимого:
     со свёрнутым описанием ширина этапа определялась длиной строки, и
     линейка выглядела уже, чем при раскрытых описаниях. Теперь она одна и
     та же в обоих состояниях. */
  .stage details, .stage-top{ width:100%; }
  .stage-top h3{ flex:1; }
  .stage details > summary{ list-style:none; }
  .stage summary::-webkit-details-marker{ display:none; }

  /* Номер и заголовок — одной строкой с общей базовой линией. Раньше номер
     стоял отдельно и отсчитывался от края блока, а у нижнего ряда там ещё
     отступ до оси — номер уезжал выше заголовка, и вся линейка выглядела
     разъехавшейся. */
  .stage-top{
    display:flex; align-items:baseline; gap:.7rem;
    cursor:pointer; padding-bottom:.5rem;
    transition:color .25s var(--ease-out);
  }
  .stage-top h3, .stage-n{ transition:color .25s var(--ease-out); }
  .stage-top:hover h3{ color:var(--accent); }
  .stage-top:hover .stage-n{ color:var(--accent); }
  /* точка на оси откликается вместе с этапом — видно, к какому месту
     линии относится наведённый заголовок */
  .stage:hover::before{ transform:scale(1.6); }
  .stage::before{ transition:transform .25s var(--ease-out); }
  /* Ширина номера постоянная: по ней выравнивается описание, и цифры
     разной ширины не сдвигали бы его туда-сюда. */
  /* Раздел прозрачный: за ним снимок страницы, и текст этапов лежит прямо на
     нём. Светло-серый номер в три десятых прозрачности на светлых местах кадра
     пропадал вовсе, а названия читались с трудом.

     Подложки под текст не ставим — снимок за страницей должен оставаться
     виден. Читаемость держат сам текст и тень под буквами: тот же приём, что на
     обложке работы и на первом экране. Тень видна только там, где буквы, и
     кадра не трогает.

     Номер набран цветом отделки: он не спорит с белым названием рядом, а по
     цвету сразу отделяется от него — видно, что это номер, а не часть строки. */
  .stage-n{
    flex:none; min-width:1.6rem;
    font-family:var(--font-display); font-weight:600;
    font-size:2.5rem; line-height:1;
    color:var(--accent);
    text-shadow:0 1px 2px rgba(6,9,10,.7), 0 2px 16px rgba(6,9,10,.55);
  }
  .stage h3{
    font-size:1.02rem; line-height:1.3; color:var(--ink);
    text-shadow:0 1px 2px rgba(6,9,10,.7), 0 2px 16px rgba(6,9,10,.55);
  }
  /* Описание встаёт под заголовок, а не под номер: отступ равен ширине
     номера вместе с зазором до заголовка. */
  .stage p{
    color:rgba(244,242,236,.82); font-size:.88rem; line-height:1.45;
    padding-left:calc(1.6rem + .7rem);
    text-shadow:0 1px 2px rgba(6,9,10,.7), 0 2px 16px rgba(6,9,10,.5);
  }

  /* На широком экране — ось с шахматным порядком, как в макете: внизу
     первый, третий и пятый, сверху между ними второй и четвёртый.
     Здесь колонок шесть и каждый этап занимает две. Начиная с настольной
     ширины сетка перестраивается — см. ниже, там же и причина. */
  @media (min-width:900px){
    .stages{
      grid-template-columns:repeat(6, 1fr);
      grid-template-rows:auto 1px auto;
      column-gap:var(--stage-gap);
      row-gap:0;
    }
    /* Ось. height обязателен: строка сетки высотой в пиксель задаёт место,
       но не размер самого элемента — с выравниванием по центру он оставался
       нулевой высоты, то есть невидимым. */
    /* Линия идёт через всю ширину блока и продолжается за последнюю точку,
       как в макете. Обрыв на пятой точке делал линейку незаконченной:
       правая треть оставалась без оси, и блок читался как обрезанный. */
    /* Ось заметнее: на снимке за страницей волосяная линия в цвет границы
       терялась, и этапы читались набором подписей без порядка. */
    .stages::before{
      content:""; grid-row:2; grid-column:1 / -1;
      height:1px; align-self:center;
      background:rgba(244,242,236,.9);
      box-shadow:0 0 0 1px rgba(6,9,10,.55), 0 0 14px rgba(244,242,236,.75);
    }
    .stage:nth-child(1){ grid-column:1 / span 2; grid-row:3; }
    .stage:nth-child(2){ grid-column:2 / span 2; grid-row:1; }
    .stage:nth-child(3){ grid-column:3 / span 2; grid-row:3; }
    .stage:nth-child(4){ grid-column:4 / span 2; grid-row:1; }
    .stage:nth-child(5){ grid-column:5 / span 2; grid-row:3; }

    .stage:nth-child(even){ align-self:end; padding-bottom:1.75rem; }
    .stage:nth-child(odd){ padding-top:1.75rem; }
    /* Со свёрнутыми описаниями ряды сходились к оси вплотную и линейка
       сплющивалась. Запас держит расстояние независимо от того, раскрыт
       этап или нет. */
    .stage details{ min-height:3.25rem; }

    /* Текст уже своей колонки. Заголовки короче отведённого им места, и
       строка из пяти этапов выглядела недотянутой до края: между ними
       оставались случайные пустоты. С ограничением заголовок переносится
       на две строки, как в макете, и заполняет своё место. */
    .stage details{ max-width:20rem; }

    /* Точка на конце засечки, ровно на оси. Отступ равен половине её
       размера: край блока лежит на линии, и точку надо сдвинуть наружу на
       свой радиус, чтобы её середина совпала с линией. Раньше здесь стояла
       ещё и длина засечки — точки уезжали от оси на её высоту. */
    .stage::before{
      content:""; position:absolute; left:-3.5px; z-index:1;
      width:6px; height:6px; border-radius:50%;
      background:var(--accent);
      /* Точка — самое яркое место линии, узел, где этап встречается с осью. */
      box-shadow:0 0 0 1px rgba(6,9,10,.5), 0 0 14px rgba(201,167,105,1);
    }
    .stage:nth-child(even)::before{ bottom:-3px; }
    .stage:nth-child(odd)::before{ top:-3px; }
  }

  /* Пятиколоночная раскладка отсюда убрана вместе с ней. Она включалась от
     1500px ширины окна и рассчитывала на блок во всю страницу; теперь блок
     всегда в общей колонке, около 1100 точек, и до неё дело не доходило бы
     ни при каком экране. Шестиколоночная выше на эту ширину и настроена.

     Если блок когда-нибудь снова расширят, стоит помнить, ради чего она
     была: при шести колонках последний этап тянулся до самого края, а его
     короткая строка до края не доставала — справа зияла пустота впятеро
     шире отступа слева. */

