/* Jimmy Writing — стили продукта.
   Перенесены из `mockups/styles.css` без мокапной обвязки: `.mocknav`, `.stage`,
   `.frame`, `.notes`, `.variant-label`, страница-указатель и боковая панель
   варианта в продукт не переезжают. Фальшивая каретка `.caret` тоже: она
   рисовалась `&nbsp;` внутри спана и уехала бы в текст пользователя.

   Визуальный язык: раскладка iA Writer + палитра jimmyword.com (лавандовые фоны,
   фиолетовый акцент). Категории: grammar — rust, word choice — ochre, style —
   ocean blue.

   Контраст: здесь стояло «проверен в Фазе 0», и это было неправдой. Замер по всем
   парам «текст на своём фоне» дал шесть мест ниже порога NFR-A3 (4.5:1): тихие
   подписи 3.35, акцент как текст 3.60, белый на кнопке 3.88, белый на квадратике
   номера 3.61–3.88, текст `del` на розовой подсветке 4.12, чипы статусов на своих
   заливках 3.83–4.16. Каждый тон опущен на один шаг с сохранением оттенка; ярусы
   серого остались тремя. Пары держит прогон `e2e/palette.spec.js` — он считает
   отношения по токенам живой страницы, чтобы обещание в этом комментарии больше не
   расходилось с делом. */

:root {
  color-scheme: light;         /* NFR-C5: тёмной темы в MVP нет */
  --paper: #F7F6FA;            /* фон приложения */
  --sheet: #FFFFFF;
  --ink: #1C1B22;
  --ink-dim: #5A5766;          /* было #6F6C7E: 4.73 — проходило, но не оставляло
                                  места яруса тихим подписям, когда те поднялись */
  --ink-faint: #686173;        /* было #8A8496 — 3.35 при пороге 4.5 */
  --line: #E1DEEC;
  --field-line: #918D9D;   /* рамка поля ввода: 3.0 к бумаге, порог WCAG для контролов */
  --accent: #8177C9;           /* только заливки, маркер и рамки: как текст даёт 3.60 */
  --accent-ink: #6357BC;       /* акцент как текст и как фон под белым текстом */
  --accent-hover: #5145AD;     /* было #766DB9 — светлее нового базового, то есть
                                  ховер осветлял кнопку вместо того, чтобы углубить */
  --marker: #D8CEF7;
  --grammar: #93271D;
  --word: #7A6311;            /* было #8A7113 — 4.38 как текст, 3.90 на своей заливке */
  --style: #006D9E;
  --ok: #2C7248;              /* было #2F7A4D — 4.16 на своей заливке */
  --del-bg: #F6E3DF;
  --ins-bg: #E1EFE4;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, "Cascadia Code", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.55;
}

a { color: var(--accent-ink); text-decoration: none; }
a:hover { text-decoration: underline; color: var(--accent-hover); }
::selection { background: var(--marker); }
/* NFR-A2: видимый фокус — на всём, что может его получить, включая подсвеченные
   предложения (им нужен `tabindex`, иначе клавиатурный проход невозможен). */
a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

/* ---------- обвязка приложения ---------- */

.appbar {
  display: flex; align-items: center; gap: 20px;
  padding: 14px 28px;
  border-bottom: 1px solid var(--line);
  font-family: var(--mono); font-size: 13px;
}
.appbar__brand { font-weight: 700; color: var(--ink); letter-spacing: .02em; }
/* Выход — тихое действие в конце строки: нужен редко, но найти его надо не глазами по
   всему экрану, а в одном предсказуемом месте. */
.appbar__out { margin-left: auto; font-family: var(--mono); font-size: 12.5px; }
.appbar__brand::before {
  content: ""; display: inline-block;
  width: 12px; height: 12px; border-radius: 4px;
  margin-right: 9px; vertical-align: -1px;
  background: linear-gradient(135deg, #9D8FE0, #6F5BD6);
}

/* Контейнер страницы вместо мокапных `.stage` + `.frame` + `.app`. */
.page {
  max-width: 960px; width: 100%;
  margin-inline: auto;
  padding: 36px 44px 40px;
  display: flex; flex-direction: column; gap: 28px;
  /* Точка отсчёта для закрытия ожиданием: оно накрывает содержимое экрана и ничего
     больше — шапка приложения остаётся видимой. */
  position: relative;
}
@media (max-width: 560px) { .page { padding: 24px 20px 28px; } }

/* Содержимое экрана. Свой контейнер, потому что над экраном оболочка ставит строку
   ожидания и сообщение об отказе: без него блоки экрана и блоки оболочки делили бы
   один поток, и вертикальные отступы разъезжались бы от появления сообщения. */
.screen { display: flex; flex-direction: column; gap: 28px; }

.eyebrow {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent-ink);
}

h1.topic-title {
  font-family: var(--sans); font-size: 28px; line-height: 1.2;
  font-weight: 700; letter-spacing: -.015em; text-wrap: balance; margin-top: 8px;
}

.chip {
  display: inline-block; padding: 2px 9px;
  border: 1px solid var(--line); border-radius: 999px;
  font-family: var(--mono); font-size: 11px; color: var(--ink-dim);
  white-space: nowrap;
}
.chip--grammar { color: var(--grammar); border-color: color-mix(in srgb, var(--grammar) 35%, transparent); }
.chip--word    { color: var(--word);    border-color: color-mix(in srgb, var(--word) 40%, transparent); }
.chip--style   { color: var(--style);   border-color: color-mix(in srgb, var(--style) 40%, transparent); }
.chip--ok      { color: var(--ok);      border-color: color-mix(in srgb, var(--ok) 40%, transparent); }

.qlist { padding-left: 20px; color: var(--ink-dim); display: flex; flex-direction: column; gap: 4px; }
.meta-row { font-family: var(--mono); font-size: 12.5px; color: var(--ink-faint); }

/* ---------- Today: центрированный хиро ---------- */

.hero {
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
  padding: 34px 0 10px;
  gap: 20px;
}
.hero__eyebrow {
  font-family: var(--sans); font-size: 13px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent-ink);
}
.hero__title {
  font-family: var(--sans);
  font-size: clamp(30px, 4.5vw, 44px);
  font-weight: 700; line-height: 1.16;
  letter-spacing: -.02em; text-wrap: balance;
  margin: 2px 0;
}
.hero__questions {
  list-style: none;
  display: flex; flex-direction: column; gap: 5px;
  color: var(--ink-faint); font-size: 14.5px; line-height: 1.55;
  max-width: 52ch;
  margin-top: 2px;
}
.hero__meta {
  font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 6px;
}
.btn.btn--pill {
  font-family: var(--sans); font-size: 16.5px; font-weight: 600;
  border-radius: 999px;
  padding: 16px 72px;
}
.hero__reroll { font-size: 13px; color: var(--ink-faint); }

/* ---------- история за тихой складкой ---------- */

.histfold { margin-top: 8px; }
.histfold summary {
  list-style: none;
  text-align: center;
  font-family: var(--mono); font-size: 12.5px;
  color: var(--ink-faint);
  cursor: pointer;
  padding: 10px 0;
}
.histfold summary::-webkit-details-marker { display: none; }
.histfold summary:hover { color: var(--ink-dim); }
.histfold .when-open { display: none; }
.histfold[open] .when-open { display: inline; }
.histfold[open] .when-closed { display: none; }
.histfold[open] summary { margin-bottom: 10px; }

/* ---------- кнопки ---------- */

.btn {
  display: inline-block; padding: 11px 26px;
  font-family: var(--sans); font-size: 14.5px; font-weight: 600;
  border-radius: 999px; border: 1px solid var(--accent-ink);
  background: var(--accent-ink); color: #FFFFFF;
  cursor: pointer; text-decoration: none;
}
.btn:hover { text-decoration: none; background: var(--accent-hover); border-color: var(--accent-hover); color: #FFFFFF; }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.btn--ghost:hover { background: transparent; border-color: var(--ink-dim); color: var(--ink); }
/* Недоступная кнопка остаётся в потоке и читается словом, а не только цветом. */
.btn:disabled, .btn[aria-disabled="true"] {
  background: transparent; color: var(--ink-faint); border-color: var(--line);
  cursor: default; opacity: .8;
}
.btn:disabled:hover, .btn[aria-disabled="true"]:hover {
  background: transparent; border-color: var(--line); color: var(--ink-faint);
}
.btn-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.btn-hint { font-size: 12.5px; color: var(--ink-faint); }

/* Тихое действие, выглядящее ссылкой. Именно кнопка, а не `<a href="#">`: ссылка без
   адреса не получает фокус, а с `#` меняет адрес страницы ни за чем. */
/* Тихая кнопка-ссылка. Высота — зона нажатия, а не размер шрифта: замерено на 360 px, при
   `padding: 0` тут выходило 19–20 px, то есть меньше и порога WCAG 2.2 SC 2.5.8 (24), и
   рекомендации Apple (44). Нажимают их пальцем, и мобильные браузеры в требованиях —
   `must` (NFR-C3). Ширина не трогается: строки переносятся, а не растягиваются. */
.linky {
  font: inherit; background: none; border: 0; padding: 0;
  color: var(--accent-ink); cursor: pointer;
  min-height: 44px; display: inline-flex; align-items: center;
}
.linky:hover { text-decoration: underline; color: var(--accent-hover); }

.divider { border: 0; border-top: 1px solid var(--line); }

/* ---------- редактор ---------- */

.editor {
  font-family: var(--mono);
  font-size: 16.5px; line-height: 1.75;
  max-width: 62ch;
  margin-inline: auto;
  width: 100%;
  display: flex; flex-direction: column; gap: 1em;
  /* Без переноса длинный токен (ссылка, слово из 60 символов) даёт горизонтальную
     прокрутку страницы на 360 px — измерено во всех трёх движках (NFR-C2). */
  overflow-wrap: break-word;
}

/* Поле письма — `textarea` (решение 4 плана Фазы 2): plain text по построению,
   вставка приходит текстом, нативный undo жив, `\n\n` доезжает до сервера ровно
   так, как он его ждёт. Цена — типографику `.editor` приходится продублировать. */
textarea.editor {
  display: block;
  min-height: 58vh;
  /* `outline: none` здесь стоять не может: он побеждает правило фокуса выше по
     одинаковой специфичности, и у поля письма не остаётся видимого фокуса (NFR-A2). */
  border: 0; resize: vertical;
  background: transparent; color: inherit;
  font-family: var(--mono); font-size: 16.5px; line-height: 1.75;
  padding: 0;
}

mark {
  background: transparent; color: inherit;
  border-radius: 2px; padding: 1px 0;
}
/* Подсветка предложения. Имя роли приходит вью (`tone`), а не выводится клиентом из
   статуса: цвет — продуктовое решение, и второй его копии в браузере быть не должно. */
mark.hl-grammar { background: color-mix(in srgb, var(--grammar) 15%, transparent); box-shadow: inset 0 -2px var(--grammar); }
mark.hl-word    { background: color-mix(in srgb, var(--word) 16%, transparent);    box-shadow: inset 0 -2px var(--word); }
mark.hl-style   { background: color-mix(in srgb, var(--style) 15%, transparent);   box-shadow: inset 0 -2px var(--style); }
mark.hl-ok      { background: color-mix(in srgb, var(--ok) 10%, transparent);      box-shadow: inset 0 -2px color-mix(in srgb, var(--ok) 55%, transparent); }
mark.hl-hint    { background: color-mix(in srgb, var(--accent) 10%, transparent);  box-shadow: inset 0 -2px color-mix(in srgb, var(--accent) 55%, transparent); }
mark.hl-wait    { background: color-mix(in srgb, var(--accent) 14%, transparent);  box-shadow: inset 0 -2px var(--accent); }
mark.hl-skip    { background: color-mix(in srgb, var(--ink-faint) 12%, transparent); box-shadow: inset 0 -2px color-mix(in srgb, var(--ink-faint) 45%, transparent); }

.statusbar {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding-top: 18px; border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: 12.5px; color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}
.statusbar .grow { flex: 1; }
.goalbar {
  width: 90px; height: 3px; border-radius: 2px;
  background: var(--line); position: relative; overflow: hidden;
}
.goalbar i {
  position: absolute; inset: 0; right: auto;
  background: var(--accent); border-radius: 2px;
}

.topicline {
  display: flex; align-items: baseline; gap: 14px;
  font-family: var(--sans); font-size: 13.5px; color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.topicline .grow { flex: 1; }

.write-col {
  max-width: 62ch; width: 100%;
  margin-inline: auto;
  display: flex; flex-direction: column; gap: 22px;
}

/* ---------- Review: раунды и карточки ---------- */

.steps {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  font-family: var(--sans); font-size: 13.5px;
}
.step { display: flex; align-items: center; gap: 7px; color: var(--ink-faint); }
.step__dot {
  width: 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); font-size: 11px;
}
.step.is-active { color: var(--ink); font-weight: 600; }
.step.is-active .step__dot { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.step.is-done .step__dot { border-color: var(--ok); color: var(--ok); }
.steps .sep { color: var(--line); }

/* Текст раунда — **один** контейнер с `pre-wrap` (решение 7 плана Фазы 2).
   Куски `pieces` режут сырой текст, и приглушённый кусок содержит `\n\n`:
   собирать из этого абзацы значит писать на клиенте настоящую логику. */
.editor--locked {
  display: block;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: var(--ink-dim);
}
/* Полным цветом — только проблемные предложения; они же и кликабельны. */
.editor--locked mark { color: var(--ink); cursor: text; }

/* Место, где было предложение (FR-5.5a). Пунктир — потому что это не текст, а пропуск;
   на нём же держится читаемость без цвета: в grayscale от подсветки остаётся серое, а
   пунктирная рамка и слово внутри — нет (NFR-A4).

   Цвет — `--ink`, а не приглушённый: слово внутри пропуска это **единственный** носитель
   статуса не цветом, и на подсветке `hl-wait` приглушённый давал 4.08:1 при пороге 4.5
   (NFR-A3, замерено).

   Вертикальные отступы подняты так, чтобы зона нажатия прошла порог 24 px (WCAG 2.5.8):
   замерено на 360 px в трёх движках — было 19, стало 25.0 / 26.0 / 24.7. У строчного
   элемента вертикальный отступ в строку не входит, поэтому рамка растёт, а абзац не
   переливается и номер рядом не сдвигается; в строку контейнера (28.9 px) она укладывается,
   так что и наезда на соседние строки нет. */
.editor--locked mark.gone {
  box-shadow: none;
  border: 1px dashed color-mix(in srgb, currentColor 45%, transparent);
  border-radius: 4px;
  padding: 4px 6px;
  color: var(--ink);
  font-family: var(--sans); font-size: 12.5px;
  letter-spacing: 0.02em;
  cursor: pointer;
  /* Слово статуса переносу не подлежит: разорванный пополам пропуск читается как две
     разные метки. */
  white-space: nowrap;
}

/* ---------- Review: фокус-панель правки ---------- */

/* Правка живёт в оверлее, а не в потоке текста, и это причина существования панели: и
   вставка подсказки в поток, и поле, меняющее длину строки, переливали абзац между
   `mousedown` и `mouseup` — кнопка уезжала из-под курсора, и нажатие не доезжало
   никуда (замерено в WebKit на 360 px). У оверлея своей геометрии в потоке нет, и
   двигать он не может ничего по построению (`plans/focus-mode.md`).

   `fixed`, а не `absolute`: текст раунда на восьмистах словах длиннее экрана, и панель,
   привязанная к странице, уезжала бы вверх вместе с прокруткой. */
.focus-scrim {
  position: fixed; inset: 0; z-index: 3;
  background: color-mix(in srgb, var(--paper) 55%, transparent);
  backdrop-filter: blur(1.5px); -webkit-backdrop-filter: blur(1.5px);
}
.focus {
  position: fixed; z-index: 4;
  left: 50%; transform: translateX(-50%);
  /* Прижата к верху, а не центрирована по вертикали: ступень лестницы, вердикт и
     заслуженный ответ приезжают **вниз**, и у центрированной панели каждый приезд
     сдвигал бы вверх и поле, и всё, что человек в этот момент читает.

     Отступ сверху — **замеренное** место для плашки отказа: она висит над всей страницей
     (см. `[data-notice]`), и на 1280 px её высота с одной строкой текста и рядом кнопок —
     99 px. При прежних 72 px шапка панели начиналась с 93-й, то есть зона нажатия × резалась
     плашкой сверху. Отступ фиксирован, а не зависит от наличия плашки: сдвигать панель под
     нажатием — ровно тот дефект, из-за которого правка вообще ушла из потока текста. */
  top: 112px;
  max-height: calc(100vh - 132px);
  overflow-y: auto;
  width: min(560px, calc(100vw - 40px));
  background: var(--sheet);
  border: 1px solid var(--line); border-radius: 14px;
  box-shadow: 0 2px 6px color-mix(in srgb, var(--ink) 8%, transparent),
              0 18px 50px color-mix(in srgb, var(--ink) 18%, transparent);
  padding: 20px 24px 22px;
  display: flex; flex-direction: column; gap: 14px;
}

.focus-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Статус стоит сразу за категорией, а вправо уходит счёт замечаний. */
.focus-head .pill { margin-left: 0; }
.focus-count {
  margin-left: auto;
  font-family: var(--mono); font-size: 12px; color: var(--ink-faint);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* Зона нажатия — не размер знака: `×` в 18 px даёт 21 px, то есть ниже порога WCAG 2.2
   SC 2.5.8 (24). Отступы поднимают её, ничего вокруг не сдвигая. */
.focus-close {
  font: inherit; background: none; border: 0;
  padding: 6px 8px; cursor: pointer;
  font-size: 18px; line-height: 1; color: var(--ink-faint);
}
.focus-close:hover { color: var(--ink); }

/* Соседи-контексты: читаются, но не зовут себя править. Обрезает их CSS, а не клиент, —
   иначе на клиенте появилась бы вторая правда о том, какая часть текста человеку видна.

   Обрезается **дальний от предложения** край, и это не косметика. Кусок `pieces` между
   карточками — часто целый абзац: клампом с конца сосед сверху показывал бы своё начало,
   то есть текст, к правимому предложению не прилегающий, а `¶`, приписанный в его конец,
   срезался бы первым. Высота задана двумя строками, содержимое — один `<span>`, и
   прижимается он к нужному краю: у соседа сверху к низу, у соседа снизу к верху. Лишнее
   уезжает за `overflow: hidden` — ровно по границе строки, потому что высота кратна
   `line-height`. */
.ctx {
  font-family: var(--mono); font-size: 13.5px; line-height: 1.6; color: var(--ink-faint);
  display: flex; flex-direction: column;
  max-height: 3.2em; overflow: hidden;
}
.ctx[data-ctx="before"] { justify-content: flex-end; }
.ctx__para { color: var(--ink-faint); font-weight: 700; margin-inline: 4px; }

/* Предложение — обычное поле ввода: plain text по построению, нативный undo жив, каретку
   по клику ставит браузер (NFR-C4). `resize: none` не косметика: растущее поле сдвигало
   бы лестницу подсказок под пальцем — тот же дефект, из-за которого правка ушла из
   потока текста, только на один уровень внутрь. */
textarea.focus-field {
  display: block; width: 100%;
  font-family: var(--mono); font-size: 16px; line-height: 1.65;
  background: var(--sheet); color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
  border-radius: 8px; padding: 10px 14px;
  resize: none;
}
/* Панель без поля: предложение убрано или карточка закрыта. Пунктир у пропуска — тот же
   язык, что у плейсхолдера в тексте. */
.focus-gone, .focus-closed {
  font-family: var(--mono); font-size: 15px; line-height: 1.6;
  border-radius: 8px; padding: 10px 14px;
}
.focus-gone {
  border: 1px dashed color-mix(in srgb, var(--ink-faint) 55%, transparent);
  color: var(--ink-dim);
}
.focus-closed { border: 1px solid var(--line); color: var(--ink); }

/* Подсказка внутри панели: тот же язык, что у `.hint-note`, но без своей рамки-карточки —
   рамка тут уже есть, у панели. */
.focus-hint {
  border-left: 3px solid var(--accent);
  padding: 2px 0 2px 14px;
  display: flex; flex-direction: column; gap: 8px;
  font-size: 13.5px; color: var(--ink-dim);
}

.focus-actions {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding-top: 14px; border-top: 1px solid var(--line);
}
.focus-actions .grow { flex: 1; }
/* Тихое рядом с главным обязано быть тихим: `Undo my typing` и переходы между карточками
   иначе выходили акцентным sans 15 px — то есть вторым главным действием рядом с Save.
   Класс `.quiet` до этого правила не доставал: он объявлен только внутри `.ecard__actions`
   и `.hint-note__actions`. */
.focus-actions .quiet, .focus-nav button {
  font-family: var(--mono); font-size: 12.5px;
}
.focus-actions .quiet { color: var(--ink-faint); }
.focus-nav { display: flex; gap: 16px; }

/* Телефон: панель занимает весь экран, кнопки прижаты к нижнему краю — над клавиатурой,
   а соседи ужимаются до одной строки (мокап `03b-review-focus.html`, состояние В).

   Известное ограничение, записанное сразу: в iOS Safari экранная клавиатура визуальный
   вьюпорт уменьшает, а `position: fixed; inset: 0` считается от **разметочного**, — то
   есть нижняя строка кнопок может оказаться под клавиатурой. Выход у человека есть и он
   не окольный: `Enter` на самой клавиатуре отправляет правку (FR-5.4), а тап по
   приглушённому соседу клавиатуру убирает. Правильная починка — `interactive-widget` в
   мета-вьюпорте — трогает и экран письма (там высота поля задана в `vh`), поэтому её
   место — ручной проход по iOS Фазы 5, с замером обоих экранов. */
@media (max-width: 560px) {
  .focus {
    inset: 0; width: auto; max-width: none; max-height: none;
    transform: none;
    border: 0; border-radius: 0;
    padding: 16px 16px 14px;
  }
  .focus-actions { margin-top: auto; }
  /* × выносится из потока шапки в её угол, и это не косметика: на 360 px шапка переносится
     на две строки, и × уезжал под плашку отказа **слева** — то есть место, свободное от
     плашки, зависело от длины названия категории. Из угла оно не зависит ни от чего.
     Отступ справа держит для него место, чтобы счёт замечаний не заезжал под знак. */
  .focus-head { padding-right: 36px; }
  .focus-close { position: absolute; top: 10px; right: 8px; }
  /* Сосед — в одну строку: место на телефоне уходит полю и лестнице. */
  .ctx { max-height: 1.6em; }
  /* Поле выше, чем на десктопе, и это замер, а не вкус: на 360 px в строку влезает около
     31 знака mono 16 px, а предложения раунда бывают по 120 (`drafts/07-messy.txt`) — в три
     строки они не укладываются, и хвост собственного предложения человек искал бы
     прокруткой внутри поля. Пять строк держат такое предложение целиком; высота при этом
     по-прежнему фиксирована и от набора не растёт. */
  textarea.focus-field { min-height: 8.4em; }
}

.ecard {
  border: 1px solid var(--line); border-left: 3px solid var(--ink-faint);
  border-radius: 8px; padding: 14px 18px;
  display: flex; flex-direction: column; gap: 8px;
  font-size: 14px;
}
.ecard--grammar { border-left-color: var(--grammar); }
.ecard--word    { border-left-color: var(--word); }
.ecard--style   { border-left-color: var(--style); }
.ecard--ok      { border-left-color: var(--ok); }
.ecard--hint    { border-left-color: color-mix(in srgb, var(--accent) 75%, var(--ok)); }
.ecard--wait    { border-left-color: var(--accent); }
.ecard--skip    { border-left-color: var(--ink-faint); opacity: .55; }

.ecard__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ecard__frag { font-family: var(--mono); font-size: 13.5px; font-weight: 600; }
.ecard__frag s { color: var(--ink-dim); }
.ecard__explain { color: var(--ink-dim); }
.ecard__actions { display: flex; gap: 18px; font-size: 13px; }
.ecard__actions a, .ecard__actions button { font-family: var(--mono); font-size: 12.5px; }
.ecard__actions .quiet { color: var(--ink-faint); }

.pill {
  margin-left: auto;
  font-family: var(--mono); font-size: 11px; padding: 2px 9px;
  border-radius: 999px; white-space: nowrap;
}
.pill--ok      { color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }
.pill--hint    { color: var(--accent-ink); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.pill--wait    { color: var(--accent-ink); background: color-mix(in srgb, var(--accent) 12%, transparent); }
.pill--skip    { color: var(--ink-faint); background: color-mix(in srgb, var(--ink-faint) 14%, transparent); }
.pill--grammar { color: var(--grammar); background: color-mix(in srgb, var(--grammar) 10%, transparent); }
.pill--word    { color: var(--word); background: color-mix(in srgb, var(--word) 12%, transparent); }
.pill--style   { color: var(--style); background: color-mix(in srgb, var(--style) 10%, transparent); }

.reveal {
  border: 1px dashed color-mix(in srgb, var(--ok) 45%, transparent);
  background: color-mix(in srgb, var(--ok) 6%, transparent);
  border-radius: 6px; padding: 8px 12px;
  font-family: var(--mono); font-size: 13.5px;
}
.reveal small { display: block; font-family: var(--sans); color: var(--ink-faint); margin-top: 3px; }

.cards { display: flex; flex-direction: column; gap: 10px; }

.hint-note {
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  background: var(--sheet);
  border-radius: 8px;
  padding: 12px 16px;
  font-family: var(--sans); font-size: 13.5px; color: var(--ink-dim);
  display: flex; flex-direction: column; gap: 8px;
}
.hint-note__head { display: flex; align-items: center; gap: 8px; }
.hint-note__actions { display: flex; flex-wrap: wrap; gap: 4px 16px; font-family: var(--mono); font-size: 12px; }
.hint-note__actions .quiet { color: var(--ink-faint); }

/* Номера-квадратики: одно и то же число в тексте и на карточке. */
.num {
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; border-radius: 4px;
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  color: #FFFFFF; flex-shrink: 0;
  font-style: normal;
}
.num--grammar { background: var(--grammar); }
.num--word    { background: var(--word); }
.num--style   { background: var(--style); }
.num--ok      { background: var(--ok); }
.num--hint    { background: color-mix(in srgb, var(--accent-ink) 75%, var(--ok)); }
.num--wait    { background: var(--accent-ink); }
.num--skip    { background: var(--ink-faint); }
sup.num { vertical-align: 6px; margin-left: 3px; width: 15px; height: 15px; font-size: 9.5px; }

.ecard--compact { padding: 11px 14px; gap: 6px; font-size: 13px; }
.ecard--compact .ecard__frag { font-size: 12.5px; }
.ecard--compact .ecard__explain { font-size: 12.5px; }
.ecard--compact .ecard__actions a { font-size: 12px; }
.ecard--compact .reveal { font-size: 12.5px; padding: 6px 10px; }

/* ---------- Result ---------- */

.result-head { text-align: center; padding-top: 8px; }

.selffix {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  text-align: center; padding: 14px 0 2px;
}
.selffix__num {
  font-family: var(--mono); font-size: 52px; font-weight: 700;
  line-height: 1; color: var(--ok); font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
}
.selffix__label {
  font-family: var(--sans); font-size: 11.5px; font-weight: 600;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-dim);
}
.selffix__sub { font-size: 13px; color: var(--ink-faint); }

.sw {
  display: inline-block; width: 10px; height: 10px;
  border-radius: 3px; flex-shrink: 0;
}

/* Цветовые роли. Имя роли приходит вью (`tone`), цвет живёт здесь: клиент рисует, но
   не решает, каким цветом какой раунд, и палитра меняется без правки JS. */
.fill--grammar { background: var(--grammar); }
.fill--word    { background: var(--word); }
.fill--style   { background: var(--style); }
.fill--ok      { background: var(--ok); }
.fill--hint    { background: var(--accent); }
.fill--skip    { background: var(--ink-faint); }
.fill--left    { background: var(--ink-dim); }

/* Длина бара — число замечаний относительно самого объёмного раунда. Пропорция
   считается здесь: масштаб диаграммы — оформление, и арифметике над числами вью в
   коде экрана не место. */
.catrow__track i { width: calc(100% * var(--value, 0) / var(--scale, 1)); }

.result-foot {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding-top: 4px;
}

.outcomebar {
  display: flex; gap: 2px;
  height: 10px; border-radius: 5px; overflow: hidden;
}
.outcomebar i { display: block; height: 100%; }

.legend {
  display: flex; justify-content: center; gap: 22px; flex-wrap: wrap;
  font-family: var(--mono); font-size: 12px; color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
  margin-top: 12px;
}
.legend__item { display: inline-flex; align-items: center; gap: 7px; }

.catrows { display: flex; flex-direction: column; gap: 14px; }
.catrow {
  /* Первая колонка растягивается под содержимое: у пропущенного раунда рядом с именем
     стоит чип «not counted», и в жёстких 118 px он вылезал на 45 px вправо, под полосу.
     Полоса рисуется позже в дереве и накрывала последние буквы (замерено: перекрытие
     29×6 px в chromium и firefox, 31×6 в webkit) — то есть пряталось единственное
     объяснение, почему числа по раундам не сходятся с self-fix (FR-10.8). */
  display: grid; grid-template-columns: minmax(118px, max-content) 1fr auto;
  align-items: center; gap: 16px;
  font-size: 13.5px;
}
.catrow__name { display: flex; align-items: center; gap: 9px; font-weight: 500; }
.catrow__track { height: 6px; border-radius: 3px; background: var(--line); overflow: hidden; }
.catrow__track i { display: block; height: 100%; border-radius: 3px; }
.catrow__meta {
  font-family: var(--mono); font-size: 12px; color: var(--ink-dim);
  font-variant-numeric: tabular-nums; text-align: right;
}
@media (max-width: 560px) {
  /* Места на строку не хватает, полоса уходит под имя. Раскладка задана явно, а не
     автоматическим размещением: с ним трёх элементов хватало на **три** строки — имя,
     полоса, число, — и число оказывалось в 16 px от своей полосы и в 14 px от имени
     следующего раунда, то есть читалось подписью не к тому раунду (замерено на 390 px
     во всех трёх движках). */
  .catrow { grid-template-columns: 1fr auto; }
  .catrow__name { grid-area: 1 / 1; }
  .catrow__meta { grid-area: 1 / 2; }
  .catrow__track { grid-area: 2 / 1 / 3 / -1; }
}
del { background: var(--del-bg); color: var(--ink-dim); text-decoration: line-through; text-decoration-thickness: 1px; border-radius: 2px; padding: 1px 2px; }
ins { background: var(--ins-bg); text-decoration: none; border-radius: 2px; padding: 1px 2px; }

.difftabs { display: flex; gap: 16px; font-family: var(--sans); font-size: 13.5px; font-weight: 500; flex-wrap: wrap; }
@media (max-width: 560px) {
  /* Если в строку не влезло всё, переносится метрика целиком, а не последняя вкладка.
     Замерено на 390 px: «Original» уезжала под цифры и читалась отдельной строкой, будто
     это уже не вкладка. Правило адресовано только строке вкладок — в такой же строке на
     экране письма живёт топик, и переносить там нечего. */
  .topicline:has(.difftabs) { flex-wrap: wrap; }
  .difftabs { flex-wrap: nowrap; }
}
.difftabs button {
  font: inherit; background: none; border: 0; padding: 0 0 1px;
  color: var(--ink-faint); cursor: pointer;
  /* Та же зона нажатия, что у тихих кнопок: замерено 22–24 px на 360 px, а вкладки на
     телефоне переключают пальцем чаще всего остального (NFR-C3). Подчёркивание выбранной
     остаётся у текста — оно на `border-bottom` содержимого, а не всей зоны. */
  min-height: 44px; display: inline-flex; align-items: flex-end;
}
.difftabs button[aria-selected="true"] { color: var(--ink); font-weight: 600; border-bottom: 2px solid var(--ink); }

/* Вкладка Result — свиток из трёх блоков: цифры, советы, текст с правками. Промежуток
   больше, чем внутри блока, иначе разбор читается одной сплошной простыней. */
.report { display: flex; flex-direction: column; gap: 30px; }
/* Советов может не быть вовсе: вызов необязателен и сессия закрывается без него (FR-8.5).
   Метка в разметке остаётся — она говорит, что советов нет, а не что их забыли, — но
   пустой узел в колонке с промежутками оставлял бы дыру на 30 px. */
.report > [data-notes='none'] { display: none; }
/* То же и с составом исходов: на чистой сессии складывать нечего (FR-8.3a). */
.catrows > [data-outcomes='none'] { display: none; }
/* После советов промежуток чуть шире: они обращены к завтрашнему дню, а текст под ними —
   к сегодняшнему. «Чуть» — потому что настоящий совет это два абзаца в рамке, и добавка
   в 18 px, выбранная по короткой фикстуре, на них читалась уже обрывом страницы. */
.report > [data-notes='some'] { margin-bottom: 6px; }
/* Заголовок и его содержимое: `*` в начале файла снял отступы у всего, поэтому без
   этого блока подпись липнет к тексту под ней. */
.block { display: flex; flex-direction: column; gap: 10px; }

.coach {
  border-left: 3px solid var(--accent);
  padding: 4px 0 4px 16px;
  display: flex; flex-direction: column; gap: 8px;
  color: var(--ink-dim);
}
.coach strong { color: var(--ink); }

.sec-label {
  font-family: var(--mono); font-size: 11px; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint);
}

/* Тексты на вкладках Fixed и Original — тот же `pre-wrap`, что и в Review. */
.prose {
  font-family: var(--mono); font-size: 15.5px; line-height: 1.75;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  max-width: 62ch; width: 100%; margin-inline: auto;
}

/* ---------- списки (лента истории) ---------- */

.rows { display: flex; flex-direction: column; }
.row {
  display: flex; align-items: baseline; gap: 14px;
  padding: 12px 2px; border-top: 1px solid var(--line);
  font-size: 14px;
  text-align: left; width: 100%;
  font-family: inherit; color: inherit;
  background: none; border-left: 0; border-right: 0; border-bottom: 0;
  cursor: pointer;
}
.row:first-child { border-top: 0; }
.row:hover .row__title { color: var(--accent); }
.row__date { font-family: var(--mono); font-size: 12px; color: var(--ink-faint); width: 58px; flex-shrink: 0; }
.row__title { font-weight: 500; flex: 1; min-width: 0; }
.row__meta { font-family: var(--mono); font-size: 12px; color: var(--ink-dim); font-variant-numeric: tabular-nums; white-space: nowrap; }

/* Живая область (NFR-A5): её читают, а не смотрят. Прячется не `display: none` и не
   `visibility: hidden` — с ними читалка не объявляет ничего вовсе; прячется размером с
   обрезкой, как принято для «visually hidden». */
.said {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ---------- ожидание и сообщения ---------- */

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

   `fixed`, а не `sticky`, и это исправление дефекта, а не вкус. `sticky` остаётся **в
   потоке**: появление и снятие плашки сдвигало всё, что ниже, на её высоту. Отсюда два
   следствия, оба замеренные на живом проходе: поток текста двигался под нажатием (тот
   самый класс дефектов, из-за которого правка ушла в панель), а нажатие после действия,
   снявшего плашку, попадало в соседний элемент — плашка исчезала, и страница
   подпрыгивала между двумя нажатиями. У `fixed` в потоке нет ничего, и двигать он не
   может.

   `z-index` выше закрытия ожиданием (6): плашка приходит и во время чужого запроса —
   отказ второго Skip рядом с живым вызовом модели, — и под закрытием её нельзя было бы
   ни прочесть, ни убрать. Таймера у неё нет и не будет: сообщение висит, пока человек с
   ним не разобрался (NFR-R3).

   Правило адресовано `[data-notice]` — метке плашки **оболочки**. Класс `.notice` носит
   ещё и блок «раунд не проверялся» внутри экрана: он часть потока, и вынести его из
   него значило бы приклеить к верху окна кусок содержимого. */
.notice[data-notice] {
  position: fixed; left: 0; right: 0; top: 0; z-index: 7;
  max-width: 960px; margin-inline: auto;
  border-radius: 0 0 8px 8px;
}
/* На телефоне фокус-панель занимает весь экран, и плашка накрывала её шапку вместе с ×:
   замерено на 360 px — высота плашки 120 px, × стоит на 49–79. Esc там нет, затемнения из-под
   полноэкранной панели не видно, то есть закрыть панель было нечем, пока плашка на экране.
   Плашка не доезжает до правого края ровно настолько, чтобы × остался открытым; сама она от
   этого теряет полторы буквы в строке, а не смысл. */
@media (max-width: 560px) {
  .notice[data-notice] { right: 52px; border-radius: 0 0 8px 0; }
}

/* Ожидание закрывает экран, а не висит полоской над ним: вызов модели идёт до 65 с
   (замерено в Фазе 0), и всё это время текст трогать незачем — а выглядел он рабочим.

   Появление отложено на 150 мс, и отложено в CSS, без таймера в состоянии: локальные
   записи отвечают за миллисекунды, и мигать закрытием на них нечем. `visibility` в
   анимации не для красоты — без неё прозрачное закрытие ловило бы нажатия. */
.waiting {
  position: absolute; inset: 0; z-index: 6;
  display: flex; justify-content: center;
  background: color-mix(in srgb, var(--paper) 86%, transparent);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden;
  animation: waiting-in .22s ease .15s forwards;
}
@keyframes waiting-in { to { opacity: 1; visibility: visible; } }

/* Подпись держится в виду, даже если текст длиннее экрана. */
.waiting__panel {
  position: sticky; top: 38vh; align-self: flex-start;
  display: flex; align-items: center; gap: 14px;
  padding: 8px 8px 8px 20px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--sheet);
  box-shadow: 0 8px 28px color-mix(in srgb, var(--ink) 10%, transparent);
  font-family: var(--mono); font-size: 12.5px; color: var(--ink-dim);
}
.waiting__panel .btn { padding: 8px 18px; font-size: 13px; }

/* Полоса, идущая по закрытому тексту: «модель его читает». */
.waiting__sweep { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.waiting__sweep::before {
  content: ""; position: absolute; left: 0; right: 0; height: 38%;
  background: linear-gradient(
    180deg,
    transparent,
    color-mix(in srgb, var(--accent) 13%, transparent),
    transparent
  );
  animation: waiting-sweep 2.4s cubic-bezier(.4, 0, .6, 1) infinite;
}
@keyframes waiting-sweep {
  from { transform: translateY(-100%); }
  to { transform: translateY(300%); }
}
/* Здравый смысл и `prefers-reduced-motion`: у кого движение выключено — тому статичное
   закрытие. Номера требования здесь нет намеренно: раньше стояла ссылка на NFR-A5, а он
   теперь про живую область читалки — ссылка на несуществующее правило хуже её отсутствия. */
@media (prefers-reduced-motion: reduce) {
  .waiting { animation-delay: .15s; animation-duration: .01s; }
  .waiting__sweep { display: none; }
}
.notice {
  border: 1px solid color-mix(in srgb, var(--grammar) 30%, var(--line));
  background: color-mix(in srgb, var(--grammar) 6%, var(--paper));
  border-radius: 8px; padding: 12px 16px;
  display: flex; flex-direction: column; gap: 10px;
  font-size: 14px;
}
.notice__actions { display: flex; gap: 14px; flex-wrap: wrap; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------- вход и регистрация (пятый экран, Фаза 6) ---------- */

/* Узкая колонка: на форме входа читать нечего, поэтому 65 символов ей не нужны —
   нужна близость полей к кнопке. */
.auth {
  max-width: 380px; margin: 0 auto; padding: 8px 0 40px;
  display: flex; flex-direction: column; gap: 20px;
}
.auth__title { font-size: 25px; line-height: 1.25; letter-spacing: -.01em; font-weight: 600; }
.auth__sub { color: var(--ink-dim); margin-top: 6px; }

.authtabs { display: flex; gap: 20px; border-bottom: 1px solid var(--line); }
.authtabs button {
  /* Зона нажатия, а не коробка глифов: боковые отступы компенсируются отрицательным
     margin, чтобы подчёркивание осталось ровно под словом. */
  background: none; border: 0; padding: 12px 10px; margin: 0 -10px -1px;
  font-family: var(--mono); font-size: 12.5px; color: var(--ink-faint);
  border-bottom: 2px solid transparent; cursor: pointer;
}
.authtabs button:hover { color: var(--ink-dim); }
.authtabs button[aria-selected="true"] { color: var(--ink); font-weight: 600; border-bottom-color: var(--ink); }

.authform { display: flex; flex-direction: column; gap: 14px; }
/* `display: flex` у класса сильнее браузерного правила для `[hidden]`, поэтому скрытая
   форма оставалась на экране: атрибут стоял, а вёрстка его перебивала. Ловушка не
   теоретическая — на ней покраснел прогон вкладок. */
.authform[hidden] { display: none; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field__label {
  font-family: var(--mono); font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-faint);
}
.field input {
  font-family: var(--sans); font-size: 15px; padding: 11px 13px;
  /* Рамка своим токеном, а не `--line`: NFR-A3 говорит про контраст **элементов
     управления**, и хайрлайн разделителей (1.23 к бумаге) для них не годится — поля
     на ярком экране просто не видно. `--line` при этом не тронут: под разделителями
     действительно не набирают текст, и затемнять их значило бы менять весь продукт
     ради одного экрана. */
  border: 1px solid var(--field-line); border-radius: 10px;
  background: var(--sheet); color: var(--ink);
}
.field input::placeholder { color: var(--ink-faint); }
.field input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }

.authnote { font-size: 12.5px; color: var(--ink-dim); }
/* Показать пароль — тихая кнопка-ссылка под полями. Отрицательные отступы гасят половину
   зазора колонки: зона нажатия у `.linky` и так 44 px (WCAG 2.5.8), и со штатным шагом
   14 px строка висела бы в воздухе, разрывая пару «пароль — повтор». */
.authpeek { font-size: 13px; margin: -10px 0 -8px; }
/* Предупреждение читается словами, а не цветом (NFR-A4): полоса слева — только метка. */
.authwarn { font-size: 12.5px; color: var(--ink-dim); border-left: 2px solid var(--marker); padding-left: 10px; }
.autherror { font-size: 13px; color: var(--grammar); }
