/* ============================================================================
   Sipat — Blank System
   Чёрно-белая система для платформы психологического тестирования.
   Один графический приём в двух масштабах: отметка на сегментированной дорожке.
   Ответ — чёрный блок на дорожке из пяти делений. Сессия — чёрный залив на
   дорожке из десяти сегментов. Ваш ответ и ваша сессия нарисованы одинаково.

   ПРАВИЛА ФАЙЛА
   · Ни одного цветного пикселя: все значения ахроматичны (R=G=B).
   · Отступ между блоками объявляет САМ блок, ровно один раз (margin-top).
     Ни одно правило не переопределяет чужой отступ — гасить друг друга нечему.
   · Анимируются только transform, opacity, filter: blur(2px), clip-path
     и краска (background / box-shadow / stroke-dashoffset).
   · transition: all — ни разу. ease-in — ни разу. Длительности выше 300ms — нет.

   ------------------------------------------------------------------------
   КОНТРАКТ КЛАССОВ ДЛЯ HTML
   ------------------------------------------------------------------------
   ОБЩЕЕ    .sheet · .sheet-run · .screen[hidden] / .screen.is-out
            [data-entered] на контейнере экрана — разрешает stagger
            .t-d1 .t-d2 .t-q .t-t .t-a .t-b .t-s .t-n .t-c .t-btn — роли набора
            .sr-only · .btn · .btn-wide · .back · .link-plain · .mark
   ПОДПИСЬ  .ruler[data-full] > .seg > .seg-fill    (10 сегментов)
   1        .brand > .brand-word · .lead · .tests > .test[aria-pressed] >
            .test-head > .test-meta, .test-desc, .mark · .field · .cta · .cta-note
   2        .screen-top .screen-title .screen-sub · .terms > .term
            .consent > input + .box[data-point] > svg > path[pathLength="1"]
   3        .sets > .set[aria-pressed] > .mark, .set-list > span · .set-foot
   4        .sheet-run > .ruler, .run-head > .counter > .counter-num,
              .milestone[data-on], .q-zone > .q-box > .q[data-out|data-enter],
              .rail, .run-foot > .back + .next[aria-disabled] + .hint[data-gone]
            .rail — это <fieldset>; внутри <legend class="sr-only">утверждение</legend>,
              .rail-lines + .carriage[data-on|data-fade]
              + .rows > .row[data-on|data-press] > input.sr-only + .row-n + .row-l
              + .rows-inv[data-fade] — дубль .rows, aria-hidden="true"
            [data-series] на .sheet-run — режим серии, кроссфейд 0ms
            .panel-scrim[data-on] · .panel[data-on][data-drag] > .panel-handle,
              .panel-title, .panel-list > .seg-row[data-here] >
              .seg-range + .seg-state, .panel-foot
   5        .final-block ×3 · .final-note · .final-more
   ПРОЧЕЕ   .confirm > .confirm-note (подтверждение необратимого)
            .send-step (отчёт о попытке отправки)

   JS ПИШЕТ transform / clip-path НАПРЯМУЮ НА ЭЛЕМЕНТЕ, никогда через
   переменную родителя (иначе рекалк стилей всем детям каждый кадр):
     carriage.style.transform = `translate3d(0,${y}px,0)`
     rowsInv.style.clipPath   = `inset(${top}px 0 ${bottom}px 0)`
     segFill.style.transform  = `scaleX(${x})`
     panel.style.transform    = `translate3d(0,${y}px,0)`   (мобильный драг)
   will-change:transform ставится на каретку на pointerdown и снимается по rest.
   ========================================================================= */

/* --- Гарнитура ------------------------------------------------------------
   Noto Sans, сабсет (лат + кир + типографика), 21 КБ на начертание.
   Та же гарнитура, что печатает PDF-отчёт: экран и A4 набраны одним голосом.
   Оба файла — в <link rel="preload" as="font" crossorigin>. */

@font-face{
  font-family:"Noto Sans";
  src:url(/fonts/noto-400.woff2) format("woff2");
  font-weight:400; font-style:normal; font-display:swap;
}
@font-face{
  font-family:"Noto Sans";
  src:url(/fonts/noto-700.woff2) format("woff2");
  font-weight:700; font-style:normal; font-display:swap;
}

/* Метрически подогнанный фолбэк: подмена шрифта не двигает ни одной строки.
   Overrides выведены из метрик файла: upem 1000, asc 1069, desc −293, gap 0. */
@font-face{
  font-family:"Noto Fallback";
  src:local("Arial"), local("Roboto"), local("Segoe UI"), local("Helvetica Neue"), local("Helvetica");
  ascent-override:106.9%; descent-override:29.3%; line-gap-override:0%; size-adjust:99%;
}

/* --- Токены --------------------------------------------------------------- */

:root{
  color-scheme: light;
  /* Последний шлюз против цветного пикселя: если нативный контрол где-то всё же
     отрисуется (Android/Windows красят чекбокс и радио системным акцентом),
     он будет чёрным. Одна строка страхует весь бриф. */
  accent-color:var(--ink-max);

  --font:"Noto Sans","Noto Fallback",system-ui,Roboto,"Segoe UI",Arial,sans-serif;

  /* Шкала серого. Контрасты посчитаны к фактическому листу #FAFAFA, не к белому:
     #767676 даёт 4.54 на #FFFFFF, но 4.35 на #FAFAFA — то есть проваливает AA. */
  --desk:#F1F1F1;    /* стол вокруг листа, только десктоп */
  --paper:#FAFAFA;   /* лист — единственная поверхность контента */
  --raise:#FFFFFF;   /* приподнятое: панель, выбранная карточка */
  --well:#F2F2F2;    /* утопленное: деление шкалы, hover строки */
  --hair:#E6E6E6;    /* волосяная линия, 1.20 — декоративная */
  --rule:#D8D8D8;    /* контур, 1.35 */
  --edge:#8F8F8F;    /* граница = смысл, 3.10 — порог WCAG 1.4.11 пройден */
  --ink-4:#9B9B9B;   /* 2.66 — не текст. только disabled */
  --ink-3:#6B6B6B;   /* 5.11 — самый светлый допустимый текст */
  --ink-2:#4A4A4A;   /* 8.49 — тело */
  --ink-1:#1F1F1F;   /* 15.79 — заголовки и утверждения. не #000 */
  --ink-max:#0A0A0A; /* 18.97 — только то, что активно сейчас */

  /* Радиусы. Пилюль нет: пилюля читается как «приложение», мягкий прямоугольник — как бумага. */
  --r-1:4px; --r-2:8px; --r-3:14px; --r-cut:2px;

  /* Тень ровно одна: «эта штука физически выше листа». Такая штука одна — панель. */
  --shadow-panel:0 1px 2px rgba(0,0,0,.05), 0 12px 32px -12px rgba(0,0,0,.14);

  /* Сетка отступов, база 4px, всё в rem — под Dynamic Type */
  --s1:.25rem; --s2:.5rem; --s3:.75rem; --s4:1rem; --s5:1.5rem;
  --s6:2rem;   --s7:3rem;  --s8:4rem;   --s9:6rem; --s10:8rem;

  /* Геометрия */
  --sheet:44rem;        /* 704px, поле 64px, колонка текста 34rem */
  --col:34rem;          /* ≈59 знаков кириллицей при 17px */
  --col-q:31rem;        /* ≈30 знаков при 30px */
  --row:3.5rem;         /* 56px — строка шкалы, десктоп. В rem: при увеличенном
                           системном кегле строка растёт вместе с подписью */

  /* Блок утверждения считается из строки, а не задаётся круглым числом.
     Круглое число верно ровно на одной ширине: 6rem держали 375px, но на 320px
     самое длинное утверждение банка занимает четыре строки (126px) и вылезало
     из блока прямо на шкалу, а на 430px кегль подрастал и не хватало двух
     пикселей. Высота обязана быть кратна строке того же кегля, иначе обещание
     «утверждение стоит в блоке фиксированной высоты» держится не везде.
     +2px — допуск на округление строки браузером. */
  --q-line:calc(clamp(1.5rem, 1.1rem + 1.7vw, 1.875rem) * 1.30);
  --q-box:calc(var(--q-line) * 3 + 2px);
  --run-h:100dvh;       /* высота экрана прохождения; витрина задаёт 44rem */

  /* Кривые. ease-in не встречается ни разу. */
  --ease-out:cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out:cubic-bezier(0.77, 0, 0.175, 1);

  /* Длительности. Ни одна не превышает 300ms. */
  --t-press:90ms; --t-release:200ms;
  --t-out:70ms;   --t-in:110ms;
  --t-panel:180ms; --t-screen:180ms; --t-hint:200ms; --t-final:300ms;
  --t-hover:120ms;
}

/* --- База ----------------------------------------------------------------- */

*,*::before,*::after{ box-sizing:border-box; }

html{ -webkit-text-size-adjust:100%; background:var(--desk); }

body{
  margin:0;
  background:var(--desk);
  color:var(--ink-1);
  font-family:var(--font);
  font-size:1.0625rem;
  line-height:1.6;
  /* Кириллический x-height сильно разный: SF ≈0.517, Roboto ≈0.528, Segoe ≈0.50.
     Лечим не размером, а метрикой. 0.536 — фактический sxHeight Noto Sans. */
  font-size-adjust: ex-height 0.536;
  hyphens:none;                            /* переносы меняют скорость чтения */
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
  touch-action:manipulation;               /* снимает 300ms tap-delay */
  -webkit-tap-highlight-color:transparent; /* свой отклик есть; чужой серый бокс — нет */
}

p,h1,h2,h3,figure,fieldset,legend{ margin:0; }
ul,ol{ margin:0; padding:0; list-style:none; }
button{ font:inherit; color:inherit; background:none; border:0; padding:0; text-align:left; cursor:pointer; }
/* Ниже 17px iOS Safari зумит страницу при фокусе и ломает лист. */
input{ font:inherit; color:inherit; font-size:max(1.0625rem, 1em); }
a{ color:inherit; }
svg,img{ display:block; max-width:100%; }

::selection{ background:var(--ink-1); color:var(--paper); }

.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0;
}

/* Кольцо фокуса — одно правило на всё, 0ms.
   Фокус это состояние, а не событие: анимированный фокус запаздывает там, где нужен компасом.
   Двойное кольцо читается и на листе, и на чёрной каретке.
   Радиус здесь не задаётся: иначе фокус скруглял бы углы кнопки по-своему. */
:focus-visible{
  outline:none;
  box-shadow:0 0 0 2px var(--paper), 0 0 0 4px var(--ink-max);
}

/* --- Типографика ----------------------------------------------------------
   Иерархия из размера + интерлиньяжа + трекинга + ступени серого.
   Вес 700 живёт ровно в двух местах: подпись кнопки и одна клауза в согласии.
   Прописных и курсива в системе нет вообще: кириллический капс шумит и режет
   скорость чтения, статического курсива у Noto нет, а синтетический наклон — брак.
   Трекинг — функция кегля: от −0.030em на 56px до +0.014em на 13px;
   ниже 19px отрицательного трекинга нет никогда. */

.t-d1{
  /* Нижняя граница 2.25rem (36px) не влезала в 375px: строка «Про вас — словами,»
     шире экрана, а у листа overflow:hidden — заголовок срезался справа.
     30px держит ту же строку с запасом и остаётся крупнее всего на экране. */
  font-size:clamp(1.875rem, 1.1rem + 3.4vw, 3.5rem);
  line-height:1.06; letter-spacing:-0.028em; font-weight:400;
  color:var(--ink-1); text-wrap:balance; hyphens:none;
}
.t-d2{
  font-size:clamp(1.75rem, 1.35rem + 1.7vw, 2.25rem);
  line-height:1.12; letter-spacing:-0.022em; font-weight:400;
  color:var(--ink-1); text-wrap:balance; hyphens:none;
}
.t-q{
  font-size:clamp(1.5rem, 1.1rem + 1.7vw, 1.875rem);
  line-height:1.30;                    /* тугой намеренно: утверждение схватывается
                                          одним взглядом, а не читается построчно */
  letter-spacing:-0.014em; font-weight:400;
  color:var(--ink-1); text-wrap:balance; hyphens:none;
  max-width:var(--col-q);
}
.t-t{ font-size:1.1875rem; line-height:1.35; letter-spacing:-0.006em; color:var(--ink-1); }
.t-a{ font-size:1.0625rem; line-height:1.35; letter-spacing:0;      color:var(--ink-1); }
.t-b{ font-size:1.0625rem; line-height:1.60; letter-spacing:0;      color:var(--ink-2); max-width:var(--col); }
.t-s{ font-size:0.9375rem; line-height:1.50; letter-spacing:0.004em; color:var(--ink-2); }
.t-n{ font-size:0.9375rem; line-height:1;    letter-spacing:0.020em; color:var(--ink-2);
      font-variant-numeric:tabular-nums lining-nums; }
.t-c{ font-size:0.8125rem; line-height:1.40; letter-spacing:0.014em; color:var(--ink-3);
      font-variant-numeric:tabular-nums lining-nums; }
.t-btn{ font-size:0.9375rem; line-height:1.20; letter-spacing:0.008em; font-weight:700; }

b,strong{ font-weight:700; color:var(--ink-1); }

/* --- Витрина: стол, подпись экрана, лист ---------------------------------- */

.showcase-bar{
  max-width:var(--sheet); margin:0 auto; padding:var(--s6) var(--s4) var(--s2);
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--s4);
}
.showcase-bar .mark-word{ font-size:1.25rem; letter-spacing:-0.024em; color:var(--ink-1); }

.stage{ padding:var(--s4) var(--s4) var(--s8); }
.stage-label{
  max-width:var(--sheet); margin:0 auto var(--s3);
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--s4);
}
.stage-reset{ text-decoration:underline; text-decoration-color:var(--edge); text-underline-offset:3px; }

/* Экраны приложения. Слайдов между ними нет: слайд создал бы ложное ощущение
   «страниц» и нарушил бы тезис «лист не двигается». Только кроссфейд. */
.screen{ transition:opacity var(--t-screen) var(--ease-out); }
.screen[hidden]{ display:none; }
.screen.is-out{ opacity:0; }

/* Лист. От стола отделён разницей поверхностей и волосяной кромкой, а не тенью. */
.sheet{
  max-width:var(--sheet); margin:0 auto;
  background:var(--paper);
  border:1px solid var(--hair);
  border-radius:var(--r-cut);
  padding:0 var(--s8) var(--s9);
  position:relative;
  overflow:hidden;
}

/* --- Дорожка сессии: тот же приём, что и каретка, в масштабе сессии --------
   Показывает ОСТАВШЕЕСЯ: серая непройденная часть укорачивается на глазах.
   Делает прогресс счётным: 16 вопросов до конца сегмента — цель, 160 — нет.
   Делитель: HEXACO 60 → 10×6 · Когнитивный 16 → 8×2 · Психотипы 160 → 10×16 ·
   Архетипы 128 → 8×16 · Матрицы: total = max(оценка, отвечено+1), и линейка
   никогда не едет назад — прогресс не имеет права отбирать пройденное. */

.ruler{
  display:flex; gap:3px; height:2px;
  margin:0 calc(-1 * var(--s8)) 0;
}
.seg{ position:relative; flex:1 1 0; height:2px; }
.seg::before{                       /* будущий сегмент: 1px --edge */
  content:""; position:absolute; left:0; right:0; bottom:0; height:1px; background:var(--edge);
}
/* Пройденное: сплошной 2px --ink-max. transform ведёт rAF-пружина
   SPRING.settle (ζ=1.0, T=0.35) и пишет его напрямую на этом элементе.
   Овершут здесь запрещён отдельно: перелёт на прогрессе — ложь о пройденном. */
.seg-fill{
  position:absolute; inset:0; background:var(--ink-max);
  transform:scaleX(0); transform-origin:left center;
}
/* Остаток = 0 и «документ собран» — это одна и та же картинка */
.ruler[data-full] .seg-fill{ transform:scaleX(1); }

/* --- Кнопки ---------------------------------------------------------------
   Отклик на нажатии, не на отпускании, и асимметрично:
   вход 90ms — палец уже на экране, система обязана ответить сейчас;
   выход 200ms — после отпускания можно выдохнуть. */

.btn{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:52px; padding:0 var(--s6);
  background:var(--ink-max); color:var(--paper);
  border-radius:var(--r-2); text-decoration:none;
  /* Краска переходит: момент, когда согласие поставлено и кнопка ожила, —
     единственное продуктовое событие этого экрана, и оно не должно щёлкать. */
  transition:transform var(--t-release) var(--ease-out),
             background-color var(--t-hover) ease, color var(--t-hover) ease;
}
.btn:active{ transform:scale(0.97); transition-duration:var(--t-press); }
.btn[aria-disabled="true"], .btn[disabled]{
  background:var(--well); color:var(--ink-4); cursor:default; transform:none;
}
.btn-wide{ width:100%; max-width:var(--col); }

.back{
  display:inline-flex; align-items:center; gap:var(--s2);
  min-width:44px; min-height:44px; margin-left:calc(-1 * var(--s2)); padding:0 var(--s2);
  color:var(--ink-3);
  transition:transform var(--t-release) var(--ease-out), color var(--t-hover) ease;
}
.back:active{ transform:scale(0.97); transition-duration:var(--t-press); }
@media (hover:hover) and (pointer:fine){ .back:hover{ color:var(--ink-1); } }

.link-plain{
  text-decoration:underline; text-decoration-color:var(--edge); text-underline-offset:4px;
  transition:text-decoration-color var(--t-hover) ease, color var(--t-hover) ease;
}
@media (hover:hover) and (pointer:fine){
  .link-plain:hover{ text-decoration-color:var(--ink-1); color:var(--ink-1); }
}

/* --- 1. Лендинг ----------------------------------------------------------- */

.brand{ padding-top:var(--s6); }
/* Знак вместо слова: 32px — размер, на котором миндаль и зрачок ещё читаются
   как глаз, а не как точка в скобке. currentColor тянет цвет темы. */
.brand-mark{ width:32px; height:32px; display:block; color:var(--ink-1); }

.lead{ margin-top:var(--s9); }
.lead p{ margin-top:var(--s5); }

/* Пять тестов строками списка, как оглавление документа. Ноль карточек, ноль теней. */
.tests{ margin-top:var(--s8); }
/* width:100% у кнопки считается от КОЛОНКИ, а не от строки с выносками:
   ширина оставалась колоночной, обе отрицательные margin сдвигали строку влево,
   и оглавление стояло со сдвигом в 48px — вылет слева, недобор справа.
   Ширину надо выдать явно вместе с выносками. */
.test{
  position:relative; display:block; text-align:left;
  margin-inline:calc(-1 * var(--s5)); width:calc(100% + var(--s5) * 2);
  padding:var(--s4) var(--s5);
  border-bottom:1px solid var(--hair);
  transition:background-color var(--t-hover) ease, transform var(--t-release) var(--ease-out);
}
/* Верхняя линейка живёт на первой строке, а не на контейнере: иначе она короче
   всех остальных ровно на две выноски (48px на десктопе, 48px на мобильном),
   и оглавление начинается со сбитой линии. */
.test:first-child{ border-top:1px solid var(--hair); }
.test:active{ transform:scale(0.99); transition-duration:var(--t-press); }
/* Строка выходит за поле листа ровно на выноску, а лист обрезает переполнение.
   Наружное кольцо здесь срезалось бы по бокам — на мобильном полностью.
   Поэтому у строк оно внутреннее; структура та же: 2px бумага, 2px чернила. */
.test:focus-visible{
  box-shadow:inset 0 0 0 2px var(--paper), inset 0 0 0 4px var(--ink-max);
}
@media (hover:hover) and (pointer:fine){
  .test:hover{ background:var(--well); }   /* без подъёма и без тени: строка лежит на листе */
}
.test[aria-pressed="true"]{ background:var(--well); }
.test-head{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s4); }
.test-meta{ flex:none; }
.test-desc{ display:block; margin-top:var(--s1); max-width:var(--col); }

/* Отметка — тот же чёрный блок, что и каретка, в масштабе строки */
.mark{
  position:absolute; left:calc(var(--s5) - 14px); top:calc(var(--s4) + 4px);
  width:3px; height:22px; background:var(--ink-max); border-radius:var(--r-1);
  opacity:0;                        /* состояние, а не событие: 0ms */
}
[aria-pressed="true"] > .mark{ opacity:1; }

/* Поле имени. Плейсхолдеров в системе нет ни одного:
   плейсхолдер исчезает ровно тогда, когда он нужен. */
.field{ margin-top:var(--s8); max-width:var(--col); }
.field label{ display:block; margin-bottom:var(--s2); }
.field input{
  width:100%; padding:var(--s2) 0; font-size:1.5rem; letter-spacing:-0.010em;
  background:none; border:0; border-radius:0;
  /* Подчёркивание краской, а не border: рост 1 → 1.5px не двигает строку. */
  box-shadow:inset 0 -1px 0 0 var(--edge);
  transition:box-shadow var(--t-hover) ease;
}
.field input:focus{ outline:none; box-shadow:inset 0 -1.5px 0 0 var(--ink-max); }

.cta{ margin-top:var(--s6); }
.cta-note{ margin-top:var(--s4); }

/* Вход списка тестов: один @keyframes на систему, срабатывает раз за сессию.
   Гейт [data-entered] ставится на контейнер экрана; без него строки просто
   видимы. Взаимодействие не блокируется никогда — stagger декоративен. */
[data-entered] .test{ animation:rise 260ms var(--ease-out) both; }
[data-entered] .test:nth-child(2){ animation-delay:50ms; }
[data-entered] .test:nth-child(3){ animation-delay:100ms; }
[data-entered] .test:nth-child(4){ animation-delay:150ms; }
[data-entered] .test:nth-child(5){ animation-delay:200ms; }
@keyframes rise{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:translateY(0); } }

/* --- 2. Согласие ---------------------------------------------------------- */

.screen-top{ padding-top:var(--s5); }
.screen-title{ margin-top:var(--s7); }
.screen-sub{ margin-top:var(--s4); }

.terms{ margin-top:var(--s7); background:var(--well); border-radius:var(--r-3); overflow:hidden; }
.term{ padding:var(--s5); }
.term + .term{ border-top:1px solid var(--hair); }
.term p{ margin-top:var(--s2); }

.consent{
  display:flex; align-items:flex-start; gap:var(--s3);
  margin-top:var(--s7); margin-inline:calc(-1 * var(--s3));
  padding:var(--s3); min-height:44px; max-width:calc(var(--col) + var(--s6));
  border-radius:var(--r-2); cursor:pointer;
  transition:background-color var(--t-hover) ease;
}
@media (hover:hover) and (pointer:fine){ .consent:hover{ background:var(--well); } }
/* Хит-зона — вся строка лейбла; сам input не виден, но фокусируем. */
.consent input{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}
.box{
  flex:none; width:22px; height:22px; margin-top:2px;
  background:var(--paper); border-radius:var(--r-1);
  display:grid; place-items:center;
  /* Рамка краской: рост 1.5 → 2.5px не вызывает пересчёт раскладки. */
  box-shadow:inset 0 0 0 1.5px var(--edge);
  transition:box-shadow var(--t-hint) var(--ease-out), background-color var(--t-hover) ease;
}
.box svg{ width:14px; height:14px; display:block; }
/* У <path> обязателен pathLength="1": длина штриха не зависит от геометрии. */
.box path{
  fill:none; stroke:var(--paper); stroke-width:2.2; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:1; stroke-dashoffset:1;
  transition:stroke-dashoffset var(--t-panel) var(--ease-out);
}
.consent input:checked ~ .box{ background:var(--ink-max); box-shadow:inset 0 0 0 1.5px var(--ink-max); }
.consent input:checked ~ .box path{ stroke-dashoffset:0; }
.consent input:focus-visible ~ .box{
  box-shadow:inset 0 0 0 1.5px var(--ink-max), 0 0 0 2px var(--paper), 0 0 0 4px var(--ink-max);
}
/* Тап по неактивной кнопке: интерфейс показывает пальцем, а не упрекает тряской.
   Тряска — упрёк; здесь фокус уезжает на чекбокс, рамка 1.5 → 2.5px и обратно. */
.box[data-point]{ box-shadow:inset 0 0 0 2.5px var(--ink-max); }

/* --- 3. Выбор набора образов ---------------------------------------------- */

.sets{ display:grid; grid-template-columns:1fr 1fr; gap:var(--s4); margin-top:var(--s7); }
.set{
  position:relative; text-align:left;
  padding:var(--s5); background:var(--well);
  border:1px solid var(--rule); border-radius:var(--r-3);
  transition:background-color var(--t-hover) ease, border-color var(--t-hover) ease,
             transform var(--t-release) var(--ease-out);
}
.set:active{ transform:scale(0.99); transition-duration:var(--t-press); }
@media (hover:hover) and (pointer:fine){ .set:hover{ border-color:var(--edge); } }
/* Выбрано — карточка поднялась на лист. Компенсация паддинга держит текст
   на месте при росте рамки: выбор не должен двигать список фигур. */
.set[aria-pressed="true"]{
  background:var(--raise); border-color:var(--ink-max);
  border-width:1.5px; padding:calc(var(--s5) - 0.5px);
}
.set .mark{ left:calc(var(--s5) - 14px); top:calc(var(--s5) + 3px); }
.set > .t-t{ display:block; }
.set-list{ display:block; margin-top:var(--s4); }
.set-list span{ display:block; }
.set-list span + span{ margin-top:var(--s1); }
.set-foot{ display:flex; align-items:baseline; justify-content:space-between; gap:var(--s4); margin-top:var(--s6); }

/* --- 4. Прохождение -------------------------------------------------------
   Скролла нет никогда, вплоть до 320×568. Это же снимает конфликт
   вертикального драга каретки со скроллом страницы. */

.sheet-run{
  height:var(--run-h); padding:0 var(--s8) var(--s6);
  display:flex; flex-direction:column;
}
.run-head{
  display:flex; align-items:baseline; justify-content:space-between; gap:var(--s4);
  margin-top:var(--s5);
}
/* Счётчик инвертирован: «Осталось 123», а не «37 / 160». Данные те же,
   эмоция противоположная. Не анимируется: tabular-nums + мгновенная подмена. */
.counter{
  min-height:32px; padding:0 var(--s2); margin-right:calc(-1 * var(--s2));
  border-radius:var(--r-1); color:var(--ink-3);
  transition:background-color var(--t-hover) ease;
}
@media (hover:hover) and (pointer:fine){ .counter:hover{ background:var(--well); } }
.counter-num{ display:inline-block; min-width:3ch; text-align:right; }

/* Веха: одна неблокирующая строка, тапа не требует, живёт 2.4с.
   Высота зарезервирована — появление вехи не двигает ни утверждение, ни шкалу. */
.milestone{
  margin-top:var(--s1); min-height:1.2rem; text-align:right;
  opacity:0; transform:translateY(-2px);
  transition:opacity 260ms var(--ease-out), transform 260ms var(--ease-out);
}
.milestone[data-on]{ opacity:1; transform:none; transition-duration:var(--t-hint); }

/* Блок утверждения фиксированной высоты и оптический центр 1 : 1.15.
   Ровные 50/50 читаются как «чуть низко». */
.q-zone{ flex:1 1 auto; display:flex; flex-direction:column; min-height:0; }
.q-zone::before{ content:""; flex:1 1 0; }
.q-zone::after{ content:""; flex:1.15 1 0; }
.q-box{ flex:none; height:var(--q-box); display:flex; align-items:flex-start; }

/* Смена утверждения — 160 повторов за сессию, бюджета на украшение нет.
   Законная цель — preventing a jarring change, по минимальной цене.
   Блюр только на уходящем слое: входящий текст обязан читаться немедленно,
   это контент; уходящий обязан перестать конкурировать.
   Выход 70ms быстрее входа 110ms. Задержки между вопросами нет — ноль. */
.q{
  transition:opacity var(--t-in) var(--ease-out), transform var(--t-in) var(--ease-out);
}
.q[data-out]{
  opacity:0; filter:blur(2px);
  transition:opacity var(--t-out) linear, filter var(--t-out) linear;
}
/* Вперёд текст приходит снизу (+3px), назад — сверху (JS ставит --dir:-3px).
   Ниже вестибулярного порога: за один раз не заметит никто, через сто повторов
   «вперёд» и «назад» становятся разными на ощупь. */
.q[data-enter]{ opacity:0; transform:translateY(var(--dir, 3px)); transition:none; }

/* Режим серии: три ответа подряд быстрее 400ms → кроссфейд 0ms.
   Мотивированный человек идёт ~2 пункта в секунду, на этой скорости
   110ms-мостик превращается в мигание. Задержка защищает первый раз
   и мешает на десятом — тот же приём, что «тултипы после первого мгновенны». */
[data-series] .q,
[data-series] .q[data-out],
[data-series] .carriage,
[data-series] .rows-inv{ transition-duration:0ms; }

/* Дорожка ответа: пять строк во всю ширину, подпись у каждого деления.
   Неподписанные средние точки 5-балльной шкалы деградируют данные,
   а на 160 пунктах ошибка накапливается. Вертикаль = шкала,
   горизонталь = документ. Шкала стоит пиксель в пиксель все 160 вопросов. */
.rail{
  position:relative; flex:none;
  margin:0; padding:0; border:0; min-inline-size:0;   /* сброс fieldset */
  height:calc(var(--row) * 5);
  background:var(--well); border-radius:var(--r-2);
  overflow:hidden;                 /* каретка за краем уходит под кромку, а не в подвал:
                                      резинку видно по движению блока, раскладка цела */
  /* Драг каретки вертикальный, поэтому вертикальный пан отдавать нельзя.
     Но `none` заодно отбирает и пинч — то есть масштабирование страницы (1.4.4)
     на самом большом блоке экрана. Отдаём назад pinch-zoom; первая строка —
     фолбэк для движков без ключевого слова. */
  touch-action:none;
  touch-action:pan-x pinch-zoom;
  -webkit-user-select:none; user-select:none;
}
/* Ринг фокуса на дорожке: нативная радиогруппа скрыта визуально,
   но её фокус обязан быть виден. Кольцо живёт на самой дорожке, а не на ::after:
   ::after — потомок, и overflow:hidden срезал бы его тень начисто.
   :focus-within безопасен именно здесь: клик по label отменён preventDefault,
   поэтому пальцем радио не фокусируется никогда — только клавиатурой и AT. */
.rail:focus-within{
  box-shadow:0 0 0 2px var(--paper), 0 0 0 4px var(--ink-max);
}
.rail::after{
  content:""; position:absolute; inset:0; z-index:4; pointer-events:none;
  border:1px solid var(--rule); border-radius:inherit;
}
.rail-lines{
  position:absolute; left:0; right:0; top:0; height:calc(var(--row) * 4); z-index:0;
  background:repeating-linear-gradient(to bottom,
    transparent 0, transparent calc(var(--row) - 1px),
    var(--hair) calc(var(--row) - 1px), var(--hair) var(--row));
}
/* КАРЕТКА — единственный объект с настоящей физикой.
   Вопрос не отвечен → каретки нет вообще: выделение по умолчанию на нейтрали
   сместило бы ответы к центру. Ответ дан → строка заливается за 0ms:
   это не вход элемента, это заливка существующей строки.
   Перемещение внутри одного вопроса → rAF-пружина (ui ζ=1.0 T=0.30;
   после броска |v|>110 px/s — flick ζ=0.82 T=0.28, project(v, 0.99)).
   transform пишет интегратор напрямую; CSS-перехода на transform здесь нет. */
.carriage{
  position:absolute; left:0; right:0; top:0; z-index:1;
  height:var(--row); background:var(--ink-max); border-radius:var(--r-2);
  visibility:hidden; opacity:1;
  transition:opacity var(--t-out) linear;
}
.carriage[data-on]{ visibility:visible; }
/* Каретка принадлежит утверждению, которое отвечает, и уходит вместе с ним */
.carriage[data-fade], .rows-inv[data-fade]{ opacity:0; }
.rows{ position:relative; z-index:2; }
.row{
  position:relative; display:flex; align-items:center; gap:var(--s4);
  height:var(--row); padding:0 var(--s5);
  cursor:pointer;
}
/* Тронутая строка поднимается на --raise, а не уходит в --well: дорожка
   целиком уже --well («утопленное»), и заливка тем же тоном была бы
   не-событием. Направление читается как «строка вышла под палец», и оба
   токена сохраняют по одной работе. 0ms в обе стороны: тень мыши проходит
   тут сотни раз, а палец обязан получить ответ сейчас. */
@media (hover:hover) and (pointer:fine){
  .row:not([data-on]):hover{ background:var(--raise); }
}
/* Отклик на pointerdown за 0ms; коммит на pointerup; увели палец со строки,
   не отпуская, — коммита нет (гистерезис 10px). */
.row[data-press]:not([data-on]){ background:var(--raise); }
.row-n{ flex:none; width:1ch; }
/* Инверсия подписи под кареткой — клипом дублированного слоя, а не интерполяцией цвета:
   два слоя ведёт одно значение пружины, поэтому инверсия идеальна в такт. */
.rows-inv{
  position:absolute; left:0; right:0; top:0; z-index:3;
  pointer-events:none; clip-path:inset(0 0 100% 0);
  transition:opacity var(--t-out) linear;
}
.rows-inv .row-n,
.rows-inv .row-l{ color:var(--paper); }

.run-foot{
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  margin-top:var(--s5); min-height:44px;
}
/* Подсказка нужна тому, кто её ещё не знает: исчезает навсегда после первой клавиши.
   Пальцу она не нужна никогда: на сенсорном устройстве это строка, которая
   объясняет клавиши, которых нет, и занимает место на самом узком экране. */
.hint{ transition:opacity var(--t-in) var(--ease-out); }
.hint[data-gone]{ opacity:0; pointer-events:none; }
@media (pointer:coarse){ .hint{ display:none; } }

/* Панель сегментов — единственный объект в системе, который выше листа.
   10 строк, а не 160 точек: 160 точек — тепловая карта, которую никто не читает.
   Десктоп: растёт из счётчика, который её открыл. */
.panel-scrim{ position:absolute; inset:0; z-index:8; display:none; }
.panel-scrim[data-on]{ display:block; }
.panel{
  position:absolute; z-index:9; top:calc(var(--s5) + 34px); right:var(--s8);
  width:19rem; background:var(--raise); border-radius:var(--r-3);
  box-shadow:var(--shadow-panel);
  transform-origin:top right;
  opacity:0; transform:scale(0.96); visibility:hidden;   /* никогда не scale(0) */
  transition:opacity var(--t-panel) var(--ease-out), transform var(--t-panel) var(--ease-out),
             visibility 0s linear var(--t-panel);
}
.panel[data-on]{ opacity:1; transform:scale(1); visibility:visible; transition-delay:0s; }
.panel-title{ padding:var(--s4) var(--s5) var(--s3); }
.panel-list{ border-top:1px solid var(--hair); }
.seg-row{
  position:relative;
  display:flex; align-items:center; justify-content:space-between; gap:var(--s4);
  width:100%; min-height:44px; padding:var(--s3) var(--s5); text-align:left;
  transition:background-color var(--t-hover) ease;
}
.seg-row + .seg-row{ border-top:1px solid var(--hair); }
@media (hover:hover) and (pointer:fine){ .seg-row:hover{ background:var(--well); } }
.seg-row[data-here] .seg-range{ color:var(--ink-1); }
.seg-row[data-here]::before{
  content:""; position:absolute; margin-left:calc(-1 * var(--s3));
  width:3px; height:16px; background:var(--ink-max); border-radius:var(--r-1);
}
.panel-foot{ padding:var(--s3) var(--s5) var(--s4); border-top:1px solid var(--hair); }
.panel-handle{ display:none; }

/* --- 5. Финал -------------------------------------------------------------
   Дорожка сплошная: остаток = 0 и «документ собран» — одна и та же картинка.
   Виден один раз за жизнь — здесь единственный бюджет delight в системе.
   Ни конфетти, ни галочки в кружке, ни «анализируем ваши ответы». */

/* Стартовое состояние живёт в кадрах, а не на элементе: иначе экран, до которого
   не дошёл JS, остался бы пустым листом с одной кнопкой в невидимости. */
[data-entered] .final-block{ animation:rise-final var(--t-final) var(--ease-out) both; }
[data-entered] .final-block:nth-of-type(2){ animation-delay:60ms; }
[data-entered] .final-block:nth-of-type(3){ animation-delay:120ms; }
@keyframes rise-final{ from{ opacity:0; transform:translateY(6px); } to{ opacity:1; transform:translateY(0); } }
.final-note{ margin-top:var(--s4); }
.final-more{ margin-top:var(--s6); padding-top:var(--s6); border-top:1px solid var(--hair); }

/* --- Планшет 600–899 ------------------------------------------------------ */

@media (min-width:600px) and (max-width:899px){
  .stage{ padding:var(--s4) var(--s7) var(--s8); }
  .sheet{ padding:0 var(--s7) var(--s9); }
  .ruler{ margin:0 calc(-1 * var(--s7)); }
  .sheet-run{ padding:0 var(--s7) var(--s6); }
  .panel{ right:var(--s7); }
}

/* --- Мобильный ------------------------------------------------------------
   Раскладка под большой палец: шкала — самый нижний блок экрана,
   порядок 1→5 сверху вниз совпадает с десктопом и с чтением. */

@media (max-width:599px){
  :root{ --row:3.75rem; }                 /* 60px — самый крупный тач-таргет */

  body{ background:var(--paper); }
  .stage{ padding:0 0 var(--s7); }
  .stage-label{ padding:var(--s5) var(--s5) 0; }
  .showcase-bar{ padding:var(--s5) var(--s5) var(--s2); }

  .sheet{
    border:0; border-radius:0; max-width:none;
    padding:0 var(--s5) var(--s8);
    padding-left:max(var(--s5), env(safe-area-inset-left));
    padding-right:max(var(--s5), env(safe-area-inset-right));
  }
  .ruler{ margin-inline:0; }

  .sets{ grid-template-columns:1fr; }
  .set-list{ columns:2; column-gap:var(--s4); }

  /* Высота не переопределяется: она и так var(--run-h), а в бою --run-h = 100dvh.
     Переопределение мешало витрине отдать листу ровно тот рост, что остаётся
     под её собственной подписью экрана. */
  .sheet-run{
    padding-bottom:max(var(--s5), env(safe-area-inset-bottom));
  }
  .run-head{ margin-top:var(--s4); }

  /* Панель приходит снизу и уходит вниз, откуда пришла.
     Драг: JS пишет transform напрямую, [data-drag] снимает переход.
     Dismiss при v > 0.11 px/ms или смещении > 40% высоты; вверх — резинка 0.55. */
  .panel{
    position:fixed; inset:auto 0 0 0; width:auto; top:auto;
    border-radius:var(--r-3) var(--r-3) 0 0;
    transform:translateY(100%); opacity:1;
    padding-bottom:max(var(--s4), env(safe-area-inset-bottom));
    transition:transform var(--t-panel) var(--ease-out), visibility 0s linear var(--t-panel);
    touch-action:none;
    touch-action:pan-x pinch-zoom;   /* вертикаль забираем под драг, пинч отдаём странице */
  }
  .panel[data-on]{ transform:translateY(0); opacity:1; }
  .panel[data-drag]{ transition:none; }
  .panel-scrim{ position:fixed; }
  .panel-handle{
    display:block; width:36px; height:4px; margin:var(--s3) auto var(--s1);
    background:var(--rule); border-radius:var(--r-1);
  }
}

/* --- Узкий телефон, ≤359px ------------------------------------------------
   Здесь длинному утверждению нужна четвёртая строка, а экран короткий.
   Тридцать лишних пикселей берём у шкалы: 52px — всё ещё строка во всю ширину,
   в которую не надо целиться, и всё ещё выше порога 44px. Шкала теряет высоту,
   утверждение сохраняет целостность — это правильный размен, а не наоборот. */

@media (max-width:359px){
  :root{ --row:3.25rem; --q-box:calc(var(--q-line) * 4 + 2px); }
}

/* --- Доступность движения -------------------------------------------------
   Не «ноль», а мягче. Драг каретки и свайп ОСТАЮТСЯ: это управление,
   его причиняет сам человек, а не интерфейс. Гасится только несопровождаемая
   досадка пружины. Отклик на нажатие тоже остаётся — это обратная связь,
   без неё интерфейс перестаёт отвечать. */

@media (prefers-reduced-motion: reduce){
  .q{ transition:opacity 120ms var(--ease-out); }
  .q[data-out]{ filter:none; transition:opacity 120ms linear; }
  .q[data-enter]{ transform:none; }

  /* Каретка перескакивает за 0ms: интегратор JS отключает, драг 1:1 остаётся.
     Переход по opacity у неё уже базовый — дублировать его здесь нечем. */
  .seg-fill{ transition:transform 160ms var(--ease-out); }

  .milestone{ transform:none; }

  [data-entered] .test,
  [data-entered] .final-block{ animation:none; opacity:1; transform:none; }

  .screen{ transition:opacity 120ms var(--ease-out); }
  /* Смягчение, а не обнуление: панель не едет, но и не выстреливает.
     opacity здесь обязателен — на мобильном базовое правило пинует её в 1,
     и без этой строки панель появлялась бы рывком. */
  .panel{ opacity:0; transform:none; transition:opacity 160ms var(--ease-out), visibility 0s linear 160ms; }
  .panel[data-on]{ transform:none; }
}

/* --- Прозрачность ---------------------------------------------------------
   backdrop-filter не используется нигде: светло-серая панель на светло-сером
   листе стекло не даёт сработать, а blur-слой роняет кадры на Android
   среднего класса на экране, который открыт 25 минут.
   Единственная альфа в системе — тень панели; здесь она становится кромкой. */

@media (prefers-reduced-transparency: reduce){
  .panel{ box-shadow:inset 0 0 0 1px var(--edge); }
}

/* --- Контраст -------------------------------------------------------------- */

@media (prefers-contrast: more){
  :root{ --hair:#C9C9C9; --rule:#B0B0B0; --edge:#6E6E6E; --ink-3:#545454; }
  :focus-visible{ box-shadow:0 0 0 2px var(--paper), 0 0 0 5px var(--ink-max); }
  .rail:focus-within{ box-shadow:0 0 0 2px var(--paper), 0 0 0 5px var(--ink-max); }
  .test:focus-visible{ box-shadow:inset 0 0 0 2px var(--paper), inset 0 0 0 5px var(--ink-max); }
  .pick[aria-checked="true"]:focus-visible{
    box-shadow:inset 0 0 0 1px var(--ink-max),
               0 0 0 2px var(--paper), 0 0 0 5px var(--ink-max);
  }
  .consent input:focus-visible ~ .box{
    box-shadow:inset 0 0 0 1.5px var(--ink-max), 0 0 0 2px var(--paper), 0 0 0 5px var(--ink-max);
  }
}

/* --- Тёмная тема ----------------------------------------------------------
   Продукт заканчивается белым A4, поэтому дневная поверхность — основная,
   а тёмная существует как ахроматическая инверсия токенов и ничего больше:
   ни одного нового правила раскладки, ни одной новой длительности.
   Контрасты пересчитаны к тёмному листу #131313:
   ink-3 7.6 · ink-2 11.2 · ink-1 15.9 · ink-max 17.3 · edge 4.6. */

@media (prefers-color-scheme: dark){
  :root{
    color-scheme: dark;
    --desk:#0B0B0B; --paper:#131313; --raise:#1B1B1B; --well:#1E1E1E;
    --hair:#2A2A2A; --rule:#3A3A3A; --edge:#7E7E7E;
    --ink-4:#6A6A6A; --ink-3:#A6A6A6; --ink-2:#C9C9C9;
    --ink-1:#EDEDED; --ink-max:#F7F7F7;
    /* Тень в темноте не читается — высоту панели держит кромка. */
    --shadow-panel:0 0 0 1px var(--rule), 0 12px 32px -12px rgba(0,0,0,.6);
  }
}

/* --- Печать ---------------------------------------------------------------
   Экран и присланный A4-отчёт набраны одним шрифтом и одной сеткой — это
   делает «тест + живой специалист» одним продуктом, а не двумя. */

@media print{
  :root{
    --desk:#fff; --paper:#fff; --raise:#fff; --well:#fff;
    --hair:#000; --rule:#000; --edge:#000;
    --ink-3:#000; --ink-2:#000; --ink-1:#000; --ink-max:#000;
    --shadow-panel:none;
  }
  @page{ margin:20mm; }
  html, body{ background:#fff; }
  .stage{ padding:0; break-after:page; }
  .sheet{ max-width:none; border:0; box-shadow:none; padding:0; overflow:visible; }
  .sheet-run{ height:auto; padding:0; }
  .ruler{ gap:0; margin:0; background:#000; }
  .seg::before{ height:.25pt; }
  .seg-fill{ transform:scaleX(1); }
  .btn{ background:none; color:#000; box-shadow:inset 0 0 0 .25pt #000; }
  .panel, .panel-scrim, .showcase-bar, .stage-reset, .hint, .back, .next, .counter{ display:none; }
}

/* ==========================================================================
   БОЕВЫЕ ДОПОЛНЕНИЯ
   Всё, что витрине не требовалось. Ядро выше — побайтно то же, что в
   design/sipat.css (изменены только два пути к шрифтам), поэтому следующая
   правка дизайна переносится диффом, а не переписыванием.
   Правила файла те же: ни одного цветного пикселя, анимируются только
   transform / opacity / краска, ease-in нет, длительности ≤ 300ms.
   ========================================================================== */

/* --- 1. Блок утверждения под боевой банк ----------------------------------
   В витрине самое длинное утверждение — 50 знаков, в боевом банке — 98:
   «Когда что-то не получается, я делаю вывод, что дело во мне как в человеке,
   а не в обстоятельствах». Оно занимает четыре строки на десктопе и пять
   на телефоне — на трёх строках текст ложился прямо на шкалу.

   Круглое кратное здесь верно ровно на одной ширине, и это доказано дважды:
   витрина считала три, боевой банк требует четыре и пять. Поэтому высота
   блока не объявляется числом, а измеряется: JS замеряет самые длинные
   утверждения банка в текущей раскладке и ставит --q-box на #sheet-run.
   Инвариант при этом не ослаблен, а усилен — блок по-прежнему фиксирован
   на всю сессию, но теперь он гарантированно вмещает ЛЮБОЙ пункт, а не тот,
   что помещался у автора на его экране.

   Значения ниже — фолбэк на случай, если JS до подгонки не дошёл. */

:root{
  --q-fs:clamp(1.5rem, 1.1rem + 1.7vw, 1.875rem);
  --q-line:calc(var(--q-fs) * 1.30);
  --q-box:calc(var(--q-line) * 4 + 2px);
}
.t-q{ font-size:var(--q-fs); }

/* Узкий телефон. Ядро уже сделало этот размен один раз: «Тридцать лишних
   пикселей берём у шкалы… утверждение сохраняет целостность — это правильный
   размен». На боевом банке цена размена выросла на два деления: 48px — всё
   ещё строка во всю ширину, в которую не надо целиться, и всё ещё выше
   порога 44px, а утверждению достаётся пятая строка. */
@media (max-width:359px){
  :root{ --row:3rem; --q-box:calc(var(--q-line) * 5 + 2px); }
}
@media (min-width:360px) and (max-width:599px){
  :root{ --q-box:calc(var(--q-line) * 5 + 2px); }
}

/* Движки без dvh (Safari < 15.4): 100vh хуже — при выезде адресной строки
   низ уходит под неё, — но это не «height:auto» со скроллом всего экрана. */
@supports not (height: 100dvh){ :root{ --run-h:100vh; } }

/* --- 2. iPhone: вырез и домашняя полоса -----------------------------------
   Работает только вместе с viewport-fit=cover в <meta>, иначе env() = 0.
   На экранах-листах вырез отдаётся отступом сверху; на экране прохождения —
   листу, а не шапке: линейка сессии не имеет права уехать под чёлку. */

.stage{ padding-top:calc(var(--s4) + env(safe-area-inset-top)); }
.sheet-run{ padding-top:env(safe-area-inset-top); }

@media (max-width:599px){
  .stage{
    padding:0 0 max(var(--s7), env(safe-area-inset-bottom));
    padding-top:env(safe-area-inset-top);
  }
  /* Боковые поля листа в 64px съедали треть экрана телефона: под текст
     оставалось 247px из 375, заголовок в них не влезал и срезался
     (у листа overflow:hidden). На телефоне поля равны отступу страницы. */
  .sheet{
    padding-left:var(--s5); padding-right:var(--s5);
    padding-bottom:var(--s7);
    border-left:0; border-right:0; border-radius:0;
  }
  /* Ядро красит в --paper только body. Фон холста берётся у html, а не у body,
     когда у html свой background, — и под коротким экраном (возобновление,
     финал) вылезала полоса «стола» шириной во весь низ. На телефоне стола
     нет вообще: лист занимает всё. */
  html{ background:var(--paper); }
}

/* --- 3. Экран прохождения не резинит страницу -----------------------------
   Лист ровно в высоту экрана, скроллить нечего — значит вертикальный жест
   принадлежит каретке, а не pull-to-refresh. Класс ставит JS только на время
   вопросов: на листах согласия и финала страница обязана скроллиться. */

html.is-run, html.is-run body{
  height:100%; overflow:hidden; overscroll-behavior:none;
}

/* --- 4. Тач-таргеты ≥44px -------------------------------------------------
   Счётчику высоту добираем невидимой площадкой, а не min-height: min-height
   сдвинул бы базовую линию кнопки, и «Sipat · Психотипы» разъехалось бы
   с «Осталось» в шапке, выровненной по baseline. */

@media (pointer:coarse){
  .counter{ position:relative; }
  .counter::after{
    content:""; position:absolute; left:0; right:0; top:50%;
    height:44px; transform:translateY(-50%);
  }
}

/* --- 5. Ссылка в роли «Назад» --------------------------------------------- */

a.back{ text-decoration:none; }

/* --- 6. Два поля подряд ---------------------------------------------------
   Отступ объявляет сам блок ровно один раз: первое поле держит --s8 от
   предыдущего блока, второе — --s5 от первого. */

.field + .field{ margin-top:var(--s5); }

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

.actions{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s5); margin-top:var(--s5); }
.actions .link-plain{ display:inline-flex; align-items:center; min-height:44px; }

/* --- 8. Отправка ----------------------------------------------------------
   Индикатор неопределённый — процента у нас нет. Материал тот же, что у
   прогресса: сегментированная дорожка. Волна по десяти сегментам сообщает
   «идёт», не обещая доли. Ни спиннера, ни процентов. */

@keyframes sweep{ from{ transform:scaleX(0); } to{ transform:scaleX(1); } }

.ruler[data-busy] .seg-fill{ animation:sweep 900ms var(--ease-in-out) infinite alternate both; }
.ruler[data-busy] .seg:nth-child(2)  .seg-fill{ animation-delay:60ms; }
.ruler[data-busy] .seg:nth-child(3)  .seg-fill{ animation-delay:120ms; }
.ruler[data-busy] .seg:nth-child(4)  .seg-fill{ animation-delay:180ms; }
.ruler[data-busy] .seg:nth-child(5)  .seg-fill{ animation-delay:240ms; }
.ruler[data-busy] .seg:nth-child(6)  .seg-fill{ animation-delay:300ms; }
.ruler[data-busy] .seg:nth-child(7)  .seg-fill{ animation-delay:360ms; }
.ruler[data-busy] .seg:nth-child(8)  .seg-fill{ animation-delay:420ms; }
.ruler[data-busy] .seg:nth-child(9)  .seg-fill{ animation-delay:480ms; }
.ruler[data-busy] .seg:nth-child(10) .seg-fill{ animation-delay:540ms; }

/* --- 9. Строка-предупреждение ---------------------------------------------
   Приватный режим Safari бросает QuotaExceededError на первом же setItem.
   Человек обязан узнать, что вкладку теперь закрывать нельзя. */

.warn{ margin-top:var(--s4); max-width:var(--col); }

/* --- 10. Финал ------------------------------------------------------------
   Код попытки — не результат: это адрес строки в базе. По нему специалист
   находит отчёт, по нему же человек просит удалить свои данные. Ни одной
   цифры результата на этом экране нет и быть не может. */

.final-ref{ margin-top:var(--s6); padding-top:var(--s5); border-top:1px solid var(--hair); }
.final-ref b{ font-variant-numeric:tabular-nums lining-nums; letter-spacing:0.04em; }

/* --- 11. Доступность движения --------------------------------------------- */

@media (prefers-reduced-motion: reduce){
  /* Волна отправки — единственная бесконечная анимация в системе.
     Гасим до статичной дорожки: факт «идёт отправка» несёт текст. */
  .ruler[data-busy] .seg-fill{ animation:none; transform:scaleX(1); opacity:0.35; }
}

/* --- 12. Перенос фокуса при смене экрана ----------------------------------
   Экраны получили tabindex="-1", чтобы JS мог поставить на них фокус: иначе
   после «Продолжить» диктор остаётся на кнопке, которой больше нет в
   документе, а Tab начинает обход заново с адресной строки. Кольцо здесь
   не нужно — фокус переехал не по воле человека, а вслед за содержимым;
   :focus-visible ниже по-прежнему рисует кольцо всему, чему положено. */

.screen:focus{ outline:none; }
/* Кольцо живёт в box-shadow, а не в outline, поэтому одного outline:none мало:
   секция, на которую JS перевёл фокус после нажатия клавиши, проходит проверку
   :focus-visible и получает кольцо на всю свою коробку. Сверху и по бокам его
   срезает вьюпорт, а снизу оно ложится чёрной чертой под листом — измерено на
   390×844: полоса #0A0A0A в 2 px под коротким экраном. Гасим обе половины. */
.screen:focus-visible{ box-shadow:none; }

/* --- 13. Когда содержимое не помещается в экран ---------------------------
   667×375 — это 375 пикселей высоты, в которые нужно уложить дорожку сессии,
   шапку, утверждение, шкалу 5×56 и подвал: 580 пикселей содержимого. То же
   самое случается на обычном телефоне в портрете, когда в системе выставлен
   крупный шрифт: строка шкалы задана в rem и на 150% растёт с 60 до 90 px,
   пять строк съедают 450 из 667, зона утверждения схлопывается в ноль —
   и утверждение уезжает под шкалу. Измерено: 375×667 при 150% — перекрытие
   281 px, человек оценивает текст, которого не видит.

   Держаться в этом режиме за инвариант «лист ровно в высоту экрана» значит
   печатать утверждение поверх шкалы и уводить «Назад» под нижнюю кромку,
   откуда его не достать — скролла-то нет. Дальше ужимать нечего: 5 строк
   по 44px это уже порог тач-таргета, а кегль утверждения и так падает
   четырьмя ступенями. Поэтому листу возвращается своя высота, а странице —
   скролл: единственное, чем можно заплатить, — это скролл, и платить им
   честнее, чем невидимым утверждением.

   Условие не медиазапросом. Причина не в высоте экрана как таковой:
   те же 667 px то вмещают лист, то нет — в зависимости от системного кегля,
   который в медиазапрос не приходит. Помещается ли содержимое, знает только
   измерение: fitsFixed() в scale-app.js сравнивает низ блока утверждения
   с верхом шкалы и низ подвала с кромкой листа, а layout() ставит по ответу
   оба флага ниже.

   Веха здесь НЕ прячется, хотя раньше пряталась на низком экране: строку
   она занимает ту же, но в потоке высота больше не дефицит, а через неё
   приходят предупреждения («Прогресс не сохраняется», «Остался пропуск»),
   которые человеку важнее сэкономленных двадцати пикселей. */

html.is-run.is-flow, html.is-run.is-flow body{ height:auto; overflow:visible; }
.sheet-run[data-flow]{ height:auto; min-height:var(--run-h); }
/* Оптический зазор ::after съеден нехваткой высоты, а утверждение вплотную
   к шкале читается как её заголовок. Возвращаем воздух паддингом зоны:
   margin здесь трогать нельзя — отступ между блоками объявляет сам блок. */
.sheet-run[data-flow] .q-zone{ padding-bottom:var(--s5); }

/* ═══════════════════════════════════════════════════════════════════════════
   14. Заставка
   Показывается один раз за сессию и только на главной. Знак уходит раньше
   фона: иначе переход из чёрного в лист читается как вспышка, а не как
   поднятый занавес. Токены темы здесь не используются намеренно — заставка
   всегда чёрная, в том числе в светлой теме.
   ═══════════════════════════════════════════════════════════════════════════ */
.splash{
  position:fixed; inset:0; z-index:9999;
  background:#000; color:#fff;
  display:grid; place-items:center;
  transition:opacity 380ms var(--ease-out);
}
.splash[data-out]{ opacity:0; pointer-events:none; }

.splash-mark{
  display:grid; place-items:center;
  opacity:0; transform:scale(0.965);
  transition:opacity 420ms var(--ease-out), transform 420ms var(--ease-out);
}
.splash[data-in]  .splash-mark{ opacity:1; transform:none; }
.splash[data-out] .splash-mark{ opacity:0; transform:scale(1.02); }

.splash-mark svg{ width:96px; height:96px; display:block; }

/* Пока заставка на экране, страница под ней не прокручивается: иначе
   инерционный скролл iOS «проваливается» сквозь неё. */
html.is-splash, html.is-splash body{ overflow:hidden; }

/* Кому движение мешает — заставки нет вовсе. Она несёт ноль информации,
   поэтому её пропуск ничего не стоит, а ослабленный вариант был бы
   мельканием чёрного экрана без смысла. */
@media (prefers-reduced-motion: reduce){
  .splash{ display:none; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   15. Воронка версий на главной
   Две карточки вместо одного маршрута. Тот же материал, что у выбора набора
   образов (§3 ядра): --well с контуром, выбранная поднимается на --raise
   с чернильной рамкой, отметка стоит в поле слева. Ничего нового не изобретено —
   человек, дошедший до третьего экрана, узнаёт этот выбор в лицо.

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

   Порядок в файле важен: блок стоит ДО §16, потому что §16 ужимает эти же
   правила той же специфичностью, а при равной специфичности выигрывает то,
   что ниже, — медиазапрос сам по себе веса не добавляет.
   ═══════════════════════════════════════════════════════════════════════════ */

.picks{ margin-top:var(--s6); max-width:var(--col); display:grid; gap:var(--s3); }

.pick{
  position:relative; display:block; width:100%; text-align:left;
  padding:var(--s4) var(--s5);
  background:var(--well);
  border:1px solid var(--rule); border-radius:var(--r-3);
  transition:background-color var(--t-hover) ease, border-color var(--t-hover) ease,
             box-shadow var(--t-hover) ease,
             transform var(--t-release) var(--ease-out);
}
/* Отклик 90ms на вход, 200ms на выход. [data-press] ставит pointerdown —
   он приходит раньше :active и не зависит от капризов iOS; :active оставлен
   как страховка на случай, если PointerEvent недоступен. */
.pick[data-press], .pick:active{ transform:scale(0.99); transition-duration:var(--t-press); }
@media (hover:hover) and (pointer:fine){ .pick:hover{ border-color:var(--edge); } }
/* Выбрано — карточка поднялась на лист. Вторая половина рамки нарисована
   КРАСКОЙ, а не шириной border: ровно тот же приём, что у поля имени
   («рост 1 → 1.5px не двигает строку») и у коробки согласия.
   Иначе — и это измерено — border-width:1.5px с компенсацией паддинга
   в полпикселя даёт выбранной карточке высоту на 1px меньше соседней,
   а её тексту сдвиг на полпикселя: при DSF=1 движок укладывает рамку
   по своему округлению, а паддинг — по моему, и они не сходятся.
   Ширина рамки не меняется вовсе — двигать нечего. */
.pick[aria-checked="true"]{
  background:var(--raise); border-color:var(--ink-max);
  box-shadow:inset 0 0 0 1px var(--ink-max);
}
/* Кольцо фокуса и рамка выбора живут в одном свойстве, поэтому кольцо обязано
   нести рамку с собой — иначе выбранная карточка теряет её под фокусом. */
.pick[aria-checked="true"]:focus-visible{
  box-shadow:inset 0 0 0 1px var(--ink-max),
             0 0 0 2px var(--paper), 0 0 0 4px var(--ink-max);
}
.pick > .mark{ left:calc(var(--s5) - 14px); top:calc(var(--s4) + 4px); }
/* Ядро включает отметку по aria-pressed; здесь роль радио, а не переключателя. */
[aria-checked="true"] > .mark{ opacity:1; }

/* Название и цифры — двумя строками ВСЕГДА, а не «в строку, если влезло».
   Строка с переносом здесь неверна арифметически, и это измерено: на 390px
   «Полный · 164 утверждения · 20 минут» занимает 291px из 294 и стоит одной
   строкой, а «Короткий · 84 утверждения · 10 минут» — 295px и переносится.
   Две карточки одной пары получали разную высоту и разное устройство внутри
   на одной и той же ширине, и ломалось это от одной буквы в слове.
   Пара обязана быть парой на любом экране, поэтому раскладка фиксированная:
   название, под ним цифры, под ними знак — три ступени вниз. */
.pick-name{ display:block; }
.pick-meta{ display:block; margin-top:var(--s1); }

/* ЗНАК. Дорожка сессии в масштабе карточки: сегмент — та же волосяная линия
   --edge, отметка — тот же чернильный блок, что каретка на шкале. Десять
   сегментов против пяти, шаг общий, левый край общий: разница в длине
   и есть разница в тесте. Не анимируется ничем — это подпись, а не индикатор. */
.sign{ margin-top:var(--s3); }
.sign-track rect{ fill:var(--edge); }
.sign-mark{ fill:var(--ink-max); }

/* ═══════════════════════════════════════════════════════════════════════════
   16. Главная на невысоком экране
   Задача одна: поле имени и кнопка должны попадать в первый экран без
   прокрутки. Порог 900px, а не 740: на iPhone 14 (844) полная раскладка
   ставила кнопку ровно на кромку экрана. Все телефоны в портрете ниже 900,
   десктоп — выше, поэтому просторная раскладка остаётся ему.
   Режем воздух, а не содержание: кегли не трогаем, читаемость та же.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (max-height: 900px){
  .brand{ padding-top:var(--s4); }
  .lead{ margin-top:var(--s5); }
  .terms{ margin-top:var(--s4); }
  .term{ padding:var(--s4) var(--s4) var(--s3); }
  .term p{ margin-top:var(--s1); }
  .picks{ margin-top:var(--s5); }
  .field{ margin-top:var(--s5); }
  .cta{ margin-top:var(--s4); }
  .cta-note{ margin-top:var(--s2); }
}

/* Совсем короткий экран (лендскейп, старые телефоны): заголовок в одну
   ступень меньше и без принудительного переноса — иначе он съедает
   две трети высоты. */
@media (max-height: 600px){
  .lead h1{ font-size:1.75rem; line-height:1.12; }
  .lead h1 br{ display:none; }
  .brand{ padding-top:var(--s3); }
  /* Карточки ужимаются по вертикали, но не по смыслу: знак и обе подписи
     остаются, тач-таргет не падает ниже 44px. */
  .picks{ margin-top:var(--s4); gap:var(--s2); }
  .pick{ padding:var(--s3) var(--s4); }
  .pick > .mark{ left:calc(var(--s4) - 14px); top:calc(var(--s3) + 4px); }
  .sign{ margin-top:var(--s2); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   17. Выбор из трёх в одну строку (экран пола)
   Тот же приём, что на шкале ответа, только лёжа: утопленная дорожка --well
   с кромкой --rule, а выбор — чёрная отметка, которая ПЕРЕЕЗЖАЕТ между
   делениями, а не зажигается в каждом своя. Отметка в системе одна: каретка
   на шкале, засечка у строки, здесь — брусок под делением.

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

   Вопрос необязательный и задаётся ОДИН раз за прохождение, поэтому переезд
   отметки — уместная анимация: 200ms, ease-in-out (движение по экрану,
   не вход и не выход). Первая отметка не едет ниоткуда: JS ставит её на место
   без перехода и только потом проявляет.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Сброс fieldset: он приносит свои поля, рамку и min-inline-size:auto,
   из-за которого flex-строка внутри отказывается сжиматься на узком экране. */
.choice{ margin-top:var(--s8); max-width:var(--col); margin-inline:0; padding:0; border:0; min-inline-size:0; }
.choice-legend{ padding:0; }
.choice-row{
  position:relative; display:flex; margin-top:var(--s3);
  background:var(--well); border-radius:var(--r-2);
  overflow:hidden;                   /* отметка за краем уходит под кромку */
}
/* Кромка отдельным слоем, как у .rail: overflow:hidden срезал бы наружное
   кольцо, а border на самой строке подвинул бы деления на полпикселя. */
.choice-row::after{
  content:""; position:absolute; inset:0; z-index:2; pointer-events:none;
  border:1px solid var(--rule); border-radius:inherit;
}
/* display:flex у самого деления — не украшение: подпись обязана занимать его
   ЦЕЛИКОМ. Иначе деление тянется по самому высокому соседу, а подпись остаётся
   своей высоты, и волосяная линия между делениями (она нарисована на подписи)
   не достаёт до низа дорожки. */
.choice-opt{ position:relative; z-index:1; display:flex; flex:1 1 0; min-width:0; cursor:pointer; }
/* Волосяная линия между делениями — краской внутрь, а не border: раскладка
   от неё не растёт, и крайние деления не получают лишний пиксель. */
.choice-opt + .choice-opt .choice-label{ box-shadow:inset 1px 0 0 0 var(--hair); }

.choice-label{
  display:flex; flex:1 1 auto; align-items:center; justify-content:center; text-align:center;
  min-height:52px; padding:var(--s2);
  color:var(--ink-2);
  transition:color var(--t-hover) ease, background-color var(--t-hover) ease;
}
/* Тронутое деление поднимается на --raise — ровно как строка шкалы под мышью:
   дорожка целиком уже --well, и заливка тем же тоном была бы не-событием. */
@media (hover:hover) and (pointer:fine){
  .choice-opt:hover .choice-label{ background:var(--raise); color:var(--ink-1); }
}
.choice-opt input:checked ~ .choice-label{ color:var(--ink-1); }
/* Кольцо внутреннее: наружное срезала бы кромка дорожки. */
.choice-opt input:focus-visible ~ .choice-label{
  box-shadow:inset 0 0 0 2px var(--paper), inset 0 0 0 4px var(--ink-max);
}

.choice-mark{
  position:absolute; left:0; bottom:0; z-index:1;
  width:calc(100% / 3); height:3px;
  background:var(--ink-max); border-radius:var(--r-1);
  opacity:0;                         /* никто не выбран — отметки нет вовсе */
  transition:transform 200ms var(--ease-in-out), opacity var(--t-in) var(--ease-out);
}
.choice-mark[data-on]{ opacity:1; }
.choice-note{ margin-top:var(--s3); }

/* Кегль подписи — функция ширины деления, и это посчитано, а не подобрано.
   Деление равно (ширина экрана − 48) / 3, из него вычитаются два поля.
   «Не указывать» — самая длинная подпись: 98px при 15px, 92px при 14px,
   85px при 13px. Отсюда ровно две ступени, и на каждой она стоит одной
   строкой вплоть до 320px:
     ≥430px  15px, место 111px и больше — десктоп и крупные телефоны;
     375…429 14px, место 93…110px;
     ≤374    13px и поля убраны, место 108px на 374 и 91px на 320.
   Рваная подпись в сегментированной дорожке читается как сбой раскладки,
   а ступень кегля на узком экране — как нормальная типографика. */
@media (max-width:429px){
  .choice-label{ font-size:0.875rem; letter-spacing:0.006em; }
}
@media (max-width:374px){
  .choice-label{ font-size:0.8125rem; letter-spacing:0.008em; padding-inline:0; }
}

@media (prefers-reduced-motion: reduce){
  /* Отметка не едет, но и не мигает: смысл несёт её положение, а не путь. */
  .choice-mark{ transition:opacity var(--t-in) var(--ease-out); }
}

/* ═══════════════════════════════════════════════════════════════════════════
   18. В потоке вертикаль принадлежит странице
   Пока лист равен экрану, скроллить нечего, и вертикальный жест на шкале —
   всегда перетаскивание каретки: touch-action:pan-x забирает вертикаль себе.
   В потоке (§13) страница длиннее экрана, и то же самое движение пальцем
   человек делает, чтобы долистать до «Назад». Оставить вертикаль шкале
   означает превратить попытку прокрутки в ответ: измерено на 667×375 —
   свайп по шкале ставил ответ и перелистывал утверждение. Это порча данных,
   а не неудобство: в протоколе остаётся балл, которого человек не выбирал.

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

.sheet-run[data-flow] .rail{ touch-action:manipulation; }

/* ═══════════════════════════════════════════════════════════════════════════
   19. Главная кнопка до выполнения условия
   Ядро красило неактивную кнопку заливкой --well с подписью --ink-4. Замер:
   подпись 2.48:1 к своей заливке, сама заливка 1.07:1 к листу — то есть
   кнопки на экране просто нет, а человек по ней стучит и не понимает, почему
   ничего не происходит. Правило AA (4.5:1 для текста, 3:1 для границы) здесь
   не «формальность для disabled»: кнопка не disabled, она aria-disabled —
   она принимает нажатие и показывает пальцем на невыполненное условие.

   Чинится без единого цветного пикселя, в том же материале: неактивная
   кнопка — контурная, активная — залитая. Разница между состояниями остаётся
   максимальной (контур против заливки), а читаемость становится законной:
   подпись --ink-3 = 5.10:1 к листу, контур --edge = 3.10:1.
   Правило стоит здесь, а не в ядре: ядро побайтно равно design/sipat.css,
   и следующая правка дизайна обязана переноситься диффом.
   ═══════════════════════════════════════════════════════════════════════════ */

.btn[aria-disabled="true"], .btn[disabled]{
  background:none; color:var(--ink-3);
  box-shadow:inset 0 0 0 1.5px var(--edge);
}
/* Кольцо фокуса и контур кнопки живут в одном свойстве, и контур весит больше
   по специфичности — то есть кнопка, до которой дошли с клавиатуры, осталась
   бы без кольца. Кольцо обязано нести контур с собой; тот же приём, что
   у выбранной карточки версии в §15. */
.btn[aria-disabled="true"]:focus-visible, .btn[disabled]:focus-visible{
  box-shadow:inset 0 0 0 1.5px var(--edge), 0 0 0 2px var(--paper), 0 0 0 4px var(--ink-max);
}
/* Контур гаснет вместе с оживанием кнопки — той же краской и в тот же такт */
.btn{ transition:transform var(--t-release) var(--ease-out),
                 background-color var(--t-hover) ease, color var(--t-hover) ease,
                 box-shadow var(--t-hover) ease; }

/* ═══════════════════════════════════════════════════════════════════════════
   20. Автозаполнение Chrome
   Chrome красит автозаполненное поле в #E8F0FE — единственный цветной
   пиксель во всём продукте, и появляется он ровно на поле имени. Отключать
   автозаполнение нельзя: человек с крупными пальцами и одним именем в
   профиле заполняет поле одним тапом. Гасим краской: собственный фон
   рисуется inset-тенью поверх фона автозаполнения, подпись — text-fill-color
   (обычный color Chrome в этом состоянии игнорирует). Бесконечный переход
   фона — известный приём против отложенной перекраски: Chrome ставит свой
   цвет с задержкой, а доехать ему некуда.
   ═══════════════════════════════════════════════════════════════════════════ */

.field input:-webkit-autofill,
.field input:-webkit-autofill:hover,
.field input:-webkit-autofill:active{
  -webkit-text-fill-color:var(--ink-1);
  caret-color:var(--ink-max);
  -webkit-box-shadow:inset 0 0 0 100px var(--paper), inset 0 -1px 0 0 var(--edge);
  box-shadow:inset 0 0 0 100px var(--paper), inset 0 -1px 0 0 var(--edge);
  transition:background-color 100000s ease;
}
.field input:-webkit-autofill:focus{
  -webkit-box-shadow:inset 0 0 0 100px var(--paper), inset 0 -1.5px 0 0 var(--ink-max);
  box-shadow:inset 0 0 0 100px var(--paper), inset 0 -1.5px 0 0 var(--ink-max);
}

/* ═══════════════════════════════════════════════════════════════════════════
   21. Сообщение на экране согласия
   «Тест обновился, старые ответы не подходят» жило в строке под кнопкой —
   на 375×667 это 500 px ниже кромки экрана. Человек, у которого исчезли
   сорок ответов, читал бодрое «Три вещи, прежде чем начнём» и не понимал
   ничего. Сообщение о судьбе уже данных ответов обязано стоять выше первого
   экрана прокрутки, то есть сразу под шапкой листа.

   Не плашка и не тревога: та же волосяная кромка, что у кризисной строки
   и у кода на финале. Тон системы ровный везде, включая плохие новости.
   ═══════════════════════════════════════════════════════════════════════════ */

.notice{
  margin-top:var(--s5); padding:var(--s4) 0;
  border-top:1px solid var(--rule); border-bottom:1px solid var(--rule);
  max-width:var(--col);
}

/* Строка «для кого этот тест» — сразу под подзаголовком согласия. */
.screen-note{ margin-top:var(--s3); }

/* ═══════════════════════════════════════════════════════════════════════════
   22. Режим высокой контрастности Windows (forced-colors)
   Система выбрасывает все авторские фоны и ВСЕ тени. В этом продукте выбранный
   ответ нарисован ровно ими: чёрный блок каретки — фон, подпись под ним —
   клип второго слоя, рамка коробки согласия и подчёркивание поля — inset-тени,
   кольцо фокуса — тоже тень. В forced-colors всё это исчезает разом: человек
   отвечает и не видит, что ответил, а с клавиатуры вдобавок не видит, где он.

   Возвращаем не своими цветами, а системными ключевыми словами — тогда
   картинка остаётся в палитре, которую человек выбрал сам. Перевод дословный:
   наши чернила --ink-max → CanvasText, наш лист --paper → Canvas, серое
   «этого ещё нет» → GrayText. Пара Canvas/CanvasText — единственная, по
   которой система ручается за контраст, и она же оставляет продукт
   чёрно-белым там, где человек выбрал чёрно-белую тему: Highlight мог бы
   принести в диагностику системный акцент, а цвет здесь читается как оценка.
   forced-color-adjust:none не используется нигде: он вернул бы наши цвета
   поверх выбора человека — то есть ровно то, от чего режим и защищает.
   ═══════════════════════════════════════════════════════════════════════════ */

@media (forced-colors: active){
  /* Выбранный ответ. Заливкой его здесь нарисовать нельзя, и дело не только
     в выброшенных фонах: под каждую строку текста система подкладывает
     плашку цветом Canvas, поэтому инверсия «подпись цветом листа по чёрному
     блоку» превращается в белые прямоугольники на месте слов — проверено
     живьём, подпись исчезает вернее, чем без правки вообще.
     Значит в этом режиме выбор рисуется рамкой поверх обычной строки:
     подпись остаётся собой и остаётся читаемой, а рамка в 3px на строке
     в 60px — событие того же веса, что заливка на листе. */
  .carriage{ background:transparent; border:3px solid CanvasText; }
  .rows-inv{ display:none; }
  /* Кромка дорожки нарисована border — она уцелела; заливка --well нет,
     поэтому дорожка от листа отличается только ею. */
  .rail::after{ border-color:CanvasText; }

  /* Дорожка сессии: пройденное чернилами, будущее — серым. Без этого обе
     части становятся CanvasText и прогресс перестаёт читаться. */
  .seg::before{ background:GrayText; }
  .seg-fill{ background:CanvasText; }

  /* Кнопки. Заливка исчезает, и кнопка становится строкой текста без границ. */
  .btn{ background:ButtonFace; color:ButtonText; border:1px solid ButtonText; }
  .btn[aria-disabled="true"], .btn[disabled]{
    background:ButtonFace; color:GrayText; border-color:GrayText; box-shadow:none;
  }

  /* Коробка согласия: рамка была тенью, галочка — цветом листа по чёрному. */
  .box{ outline:1px solid CanvasText; outline-offset:-1px; }
  .consent input:checked ~ .box{ background:CanvasText; }
  .consent input:checked ~ .box path{ stroke:Canvas; }

  /* Поле имени: подчёркивание было тенью. */
  .field input{ outline:1px solid CanvasText; outline-offset:-1px; }

  /* Отметки выбора: карточка версии на главной, строка списка, деление пола.
     Рамку выбранной карточки система красит тем же, чем невыбранной, —
     разницу несут отметка и удвоенный контур. */
  .mark, .seg-row[data-here]::before{ background:CanvasText; }
  .pick[aria-checked="true"]{ outline:2px solid CanvasText; outline-offset:-2px; }
  .choice-mark{ background:CanvasText; }

  /* Панель выше листа: её высоту держала тень. */
  .panel{ border:1px solid CanvasText; }

  /* Кольцо фокуса. Оно нарисовано двойной тенью — то есть в этом режиме его
     нет вообще, и человек с клавиатурой теряет позицию на первом же Tab.
     Outline система оставляет и красит сама. */
  :focus-visible{ outline:2px solid CanvasText; outline-offset:2px; }
  .rail:focus-within{ outline:2px solid CanvasText; outline-offset:2px; }
  .consent input:focus-visible ~ .box{ outline:2px solid CanvasText; outline-offset:2px; }
  .choice-opt input:focus-visible ~ .choice-label{ outline:2px solid CanvasText; outline-offset:-3px; }
  .test:focus-visible, .pick:focus-visible{ outline:2px solid CanvasText; outline-offset:-3px; }

  /* Шаг вперёд: заливка исчезает вместе с остальными, остаётся строка текста. */
  .next{ background:ButtonFace; color:ButtonText; border:1px solid ButtonText; }
  .next[aria-disabled="true"]{ background:ButtonFace; color:GrayText; border-color:GrayText; }
  /* Указание на неотвеченную шкалу было утолщением кромки — border уцелел,
     но его красит система; разницу несёт толщина. */
  .rail[data-point]::after{ border-width:3px; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   23. Шаг вперёд делает человек
   Автоперехода после выбора нет (§6.2 спеки): экран, уезжающий в момент
   касания, не даёт увидеть, что засчиталось, а случайный тап по дорожке —
   а она занимает весь низ экрана — уносит вперёд молча. Значит нужен
   отдельный шаг, и он нажимается на каждом утверждении: 84 раза в короткой
   версии, 164 в полной.

   Отсюда вес кнопки: заливка, как у главного действия, но ростом 44, а не 52,
   и по содержимому, а не во всю колонку. Полноширинная плита на столько повторений
   превращает лист в конвейер и отбирает у «Назад» половину подвала; текстовая
   ссылка того же веса, что «Назад», не сказала бы, какое из двух действий
   основное. Форма зеркальна «Назад» — стрелка смотрит в свою сторону.

   Неактивное состояние — контурное, как у .btn (§19): кнопка не disabled,
   она aria-disabled, принимает нажатие и показывает пальцем на пустую шкалу.
   ═══════════════════════════════════════════════════════════════════════════ */

.next{
  display:inline-flex; align-items:center; gap:var(--s2);
  min-height:44px; padding:0 var(--s5);
  /* На системном кегле 150% подвал переносится, и «Дальше» уезжает на свою
     строку. Прижимаем вправо: направление шага читается по месту кнопки,
     и терять его на крупном шрифте нельзя. */
  margin-left:auto;
  background:var(--ink-max); color:var(--paper);
  border-radius:var(--r-2);
  transition:transform var(--t-release) var(--ease-out),
             background-color var(--t-hover) ease, color var(--t-hover) ease,
             box-shadow var(--t-hover) ease;
}
.next:active{ transform:scale(0.97); transition-duration:var(--t-press); }
.next[aria-disabled="true"]{
  background:none; color:var(--ink-3);
  box-shadow:inset 0 0 0 1.5px var(--edge);
  transform:none;
}
/* Кольцо фокуса и контур живут в одном свойстве, и контур весит больше:
   тот же приём, что у .btn в §19 — кольцо несёт контур с собой. */
.next[aria-disabled="true"]:focus-visible{
  box-shadow:inset 0 0 0 1.5px var(--edge), 0 0 0 2px var(--paper), 0 0 0 4px var(--ink-max);
}

/* Подвал держит два действия и подсказку. Подсказка уходит на свою строку:
   на 390px рядом с двумя кнопками ей места нет, а пальцу она не нужна вовсе
   (правило ниже по файлу уже прячет её на сенсорном вводе). */
.run-foot{ flex-wrap:wrap; gap:var(--s3) var(--s4); }
.hint{ flex-basis:100%; order:3; text-align:center; }
/* Ширины, на которых подсказка о клавишах не помещается рядом с кнопками
   и не нужна: узкое окно — это либо телефон, либо телефонная колонка. */
@media (max-width:479px){ .hint{ display:none; } }

/* «Дальше» без ответа не молчит и не упрекает: кромка дорожки на мгновение
   тяжелеет — тот же приём, которым согласие показывает на пустую галочку. */
.rail[data-point]::after{ border-color:var(--ink-max); border-width:2px; }

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

.confirm{ max-width:var(--col); }
.confirm-note{ margin-top:var(--s3); margin-bottom:var(--s5); }
.confirm:focus{ outline:none; }
.confirm:focus-visible{ box-shadow:none; }

/* ═══════════════════════════════════════════════════════════════════════════
   25. Отчёт экрана отправки
   Дорожка волной говорит «идёт», но не говорит «сколько ещё» — а ждать здесь
   можно и полторы минуты, и дольше: пять попыток по расписанию 0/3/8/20/45 с,
   и каждая ждёт ответа до тридцати. Строка под ней называет попытку и считает секунды
   до следующей. Табличные цифры — чтобы отсчёт не дёргал строку; резерв
   высоты в две строки — чтобы её появление не двигало лист.
   ═══════════════════════════════════════════════════════════════════════════ */

.send-step{
  margin-top:var(--s6); min-height:2.8em; max-width:var(--col);
  font-variant-numeric:tabular-nums lining-nums;
}
