# Дизайн экрана подбора и сравнения поездок

Экран: `/home/deploy/ostrovok/prototype/app.html`, контракт `odin.v1.1`.
Дата: 2026-10-26. Основа правил: skill `taste-skill` (репозиторий https://github.com/Leonxlnx/taste-skill, коммит 12af471), файлы `skills/taste-skill/SKILL.md` и `references/app-screens.md`, `type.md`, `colour.md`, `layout.md`, `motion.md`, `imagery.md`, `copy.md`, `tells.md`, `critique.md`.

Скрипты skill не запускались (`draw.mjs`, `inspect.mjs`, `lint.mjs` не использовались). Библиотека `library/app.json` не читалась: выбор стиля сделан по таблице в `app-screens.md` §2.3 и по значениям по умолчанию из §9.5. Если в описании встречается «правило skill не задано», значит в skill нет правила для этого места, и решение принято отдельно и помечено как таковое.

Обозначения: «skill §X» означает `references/app-screens.md`, если не указан другой файл. «Решение» означает решение без правила в skill.

---

## 1. Направление и почему

### 1.1 Класс экрана

Экран рабочий: пользователь ищет, сравнивает, выбирает вариант и проверяет корзину. По SKILL.md (раздел «Page or app screen») это **app screen**, а не страница. Поэтому действуют правила `references/app-screens.md`, а правила прогона страницы (hero, два строки заголовка, motion floor) не применяются. Правило: SKILL.md, «An app screen is worked in».

### 1.2 Стиль

Светлая тема: стиль **`achromatic-ledger`** из таблицы skill §2.3: «money homes, reports, settings with usage; no colour in the chrome; charts in ink; one small signal where a figure changed». Причина: экран показывает деньги (итог, предоплата, варианты по цене), хром без цвета, цвет только на данных.

Отклонённые варианты:
- `accent-ladder`: для админок творцов и малого бизнеса с тарифами, не подходит.
- `editorial-greeting`: приветствие в h1 запрещено (skill §13, строка «A hero header, a greeting»).

Тёмная тема: тот же ход (цвет только на данных). Токены тёмной темы взяты по §9.3 (нейтральные тёмные, без navy и slate). Стиль `true-black-flat` по таблице §2.3 относится к медиа и событиям, поэтому не выбран как основа.

### 1.3 Лицо продукта

Skill §2.3 («Give the product a face») требует, чтобы экран не выглядел как стандартный кит. Решения:
- Фон тёплый камень `#F6F4EF` (низкая насыщенность). Skill §9 и colour.md §9 допускают тёплые фоны из «low-chroma stone» и запрещают только связку «кремовый фон плюс терракота». Акцент здесь глубокий зелёный, поэтому связка запрещённая не возникает.
- Шрифт Manrope (текущий выбор проекта). Одна гротеск-семья, как требует skill §9.1.
- Главная линия бренда: глубокий зелёный `--accent` стоит только на данных (выбранный вариант, лучший итог, позитивные метки).
- Знак продукта: монограмма 20px в `--ink`, не в акценте. Акцент на хроме запрещён (skill §13, строка «The accent on chrome»). Это уточнение к правилу §2.3, где монограмма названа «in the accent».
- Сигнатурный элемент: «Квитанция выбора». Сводка оформлена как чек: строки состава вида «Отель, Перелёт туда, Трансфер» с суммами справа, сумма строк совпадает с итогом. Элемента в библиотеке skill с такими словами нет, поэтому он придуман из вещей продукта (skill §2.3, «otherwise invent one»).

### 1.4 Вопрос экрана и главная работа

- Вопрос экрана (skill §2.2): «Сколько стоит поездка и какой вариант выбрать?»
- Самая большая цифра экрана: итог выбранного варианта в сводке (30px, 700). Правило §2.2: самая большая цифра отвечает на вопрос заголовка.
- Главная работа: «Проверить корзину» (кнопка сводки, единственная заливка). Правило §2.2: главная работа в одном клике от первого экрана.
- Факт, который важнее всего (skill §2.2, «one finding»): статус итога («Итог неполный» или сумма, которая не покрывает бюджет), выводится строкой под заголовком или в сводке рядом с действием.

---

## 2. Композиция экрана

### 2.1 Зоны

Экран состоит из четырёх зон: верхняя панель, помощник, результаты, сводка. Порядок зон зависит от ширины (таблица 2.3).

Верхняя панель: статичная, высота 48px (skill §3.3: 44–51px, медиана 48), без `backdrop-filter`, без плавающей пилюли, без полупрозрачности. Содержимое: монограмма и название продукта, две ссылки («Поиск», «Бронирования» в неактивном виде), без кнопки действия. Правило: skill §7 (navigation), SKILL.md «No glass. No backdrop blur panels, no floating pill navigation».

Заголовок результатов:
- Надпись «Поездка» над h1 убрать. Правило: SKILL.md, «No eyebrows», и skill §4.3, «a noun, not a greeting».
- h1 «Варианты поездки» 24px, 600 (skill §4.3: 18–32px, медиана 24).
- Строка статуса под h1, 14/20, `--ink-2`: состояние и версия в формате «[состояние] · версия 3». Текст состояния берётся из STATE_LABEL.

Баннер данных (`data_mode` и `simulation_notice`): всегда первый блок под заголовком, до списка и до чипов. Не закрывается, не уходит в раскрывающийся блок, рендерится при любом состоянии, включая ошибку сети (в коде `render()` вызывает `renderBanner()` всегда, это нужно сохранить).

### 2.2 Сетка и ширины

Внешние поля равны слева и справа при каждой ширине: 24px на десктопе и планшете, 16px на телефоне (skill §5: «one pair of equal margins»; app-screens §4.1: боковые поля 17–64px, медиана 29).

Ширина контента на 1440: 1440 − 2 × 24 = 1392px.

**Сетка 1440 и шире** (три колонки):

| Зона | Ширина | Примечание |
|---|---|---|
| Помощник | 296 | sticky, высота `100vh − 48` |
| Зазор | 24 | |
| Результаты | 712 | 1fr |
| Зазор | 24 | |
| Сводка | 336 | sticky сверху на 24px |

Проверка: 296 + 24 + 712 + 24 + 336 = 1392. Совпадает.

Почему три колонки только от 1440: при 1280 результаты получают 552px. Карточка варианта с колонками медиа 152, цена 184 и зазорами 32 оставляет под текст 166px, что недопустимо. Правило «карточка должна быть читаемой» вытекает из skill §4.2 (narrow blocks, «ragged rows») и §12 (сжатие). Ширины 296, 712, 336 выбраны этим расчётом. Правило skill на конкретные ширины не задано.

**Сетка 1100–1439** (две колонки):
- Помощник переходит в блок над результатами на всю ширину результатов. В ленте показываются последние 2 сообщения, полная переписка раскрывается по кнопке «Вся переписка» (ghost-кнопка).
- Результаты 1fr и сводка 320, sticky сверху. Расчёт на 1100: 1100 − 48 − 320 − 24 = 708px.

**Планшет 641–1099** (одна колонка, skill §12: «The tablet (641–1100px) is its own band»):
- Порядок: помощник (композер плюс последние 2 сообщения), параметры, сводка, список, сравнение.
- Сводка не sticky. Она стоит над списком как блок «Выбор и итог», потому что блок с действием поднимается вверх (skill §12: «Blocks that carry an action move up, not down»).
- Карточка варианта: медиа становится полосой 160px сверху, затем текст, затем цена строкой, затем подвал.

**Телефон до 640** (одна колонка, skill §12):
- Поля 16px.
- Сводка становится однострочным блоком над списком: «Выбрано: [название отеля] · [итог] · Проверить корзину». По нажатию на строку раскрывается полная сводка (`details`).
- Чипы параметров уходят в раскрывающийся блок «Параметры» только на телефоне. На десктопе они видны всегда.
- Помощник: один композер (56px) и последнее сообщение бота. Полная переписка в раскрывающемся блоке.
- Таблица сравнения ниже 640 остаётся таблицей с горизонтальной прокруткой внутри своей рамки, первая колонка зафиксирована. Страница не скроллится по горизонтали (skill §12: «A table that must stay a table scrolls inside its own frame with its first column fixed»).

Порог «первая строка списка в верхних 60% экрана» (skill §12, правило про первую строку списка). Расчёт для iPhone 390×844: баннер (две строки) плюс h1 плюс композер плюс однострочная сводка и отступы дают примерно 480px, граница 60% равна 506px. Проверить на макете. Если не проходит, чипы параметров уходят в раскрывающийся блок на любой ширине ниже 1100.

### 2.3 Контрольная точка 1440×900

Всё, что назвал бриф, должно быть на первом экране (skill §2.2): баннер, заголовок, чипы параметров, первые 2 карточки (заголовок, теги, итог), сводка с выбором или пустым состоянием. Высота блоков: заголовок и баннер около 180px, параметры около 110px, карточка 220px.

### 2.4 Разделы и группы вариантов

Варианты группируются по `suitability` (skill §2.2 о блоках и вопросах):
- «Подходят» (`ready`): основной список.
- «Условно подходят» (`conditional`): отдельная группа с заголовком и счётчиком.
- «Не подходят» (`excluded`): скрыта по умолчанию, раскрывается кнопкой «Показать не подходящие». Ссылка с числом: «Не подходят: 3».

Заголовок группы: 18/600, счётчик 14/20 `--ink-2`.

---

## 3. Типографика

### 3.1 Шрифт

- Семья: **Manrope**, Google Fonts (уже подключена в `app.html`).
- Веса: только те, что используются. Сейчас ссылка грузит 400;500;600;700. Нужно оставить 400, 600 и 700. Правило: type.md §5, «Request only the weights you use».
- Цифры: `font-variant-numeric: tabular-nums` для всех сумм, времён, дат и счётчиков (skill §9.1 и type.md §7). Правило skill также требует проверить, что шрифт реально меняет ширину цифр. Эта проверка не выполнена, её нужно сделать в браузере перед выпуском.
- Кириллица: Google Fonts отдаёт её автоматически через unicode-range, отдельный параметр не нужен. Правило skill не задано для локализации шрифта.

### 3.2 Шкала

Используются только шесть размеров: **12, 14, 16, 18, 24, 30**. Это совпадает с тем, что skill называет чистыми шкалами (app-screens §9.1: «12/14/16/24», «12/14/16/30», «12/14/18/24»), и с типичным числом размеров экрана (4–9, медиана 6, §9.1).

Всё, что в app.html использует 11, 20, 28, 36, 40 и других размеров вне шкалы, меняется (таблица 3.4).

### 3.3 Роли

| Роль | Размер / высота строки | Вес | Трекинг | Цвет |
|---|---|---|---|---|
| Заголовок страницы (h1) | 24 / 32 | 600 | 0 | `--ink` |
| Заголовок секции и группы (h2) | 18 / 24 | 600 | 0 | `--ink` |
| Название карточки (h3), название блока сводки | 18 / 24 | 600 | 0 | `--ink` |
| Итог карточки | 18 / 24 | 700 | 0, tabular | `--ink` |
| Итог в сводке (ответ экрана) | 30 / 36 | 700 | −0.02em, tabular | `--ink` |
| Цифра «Сейчас к оплате» | 16 / 24 | 600 | 0, tabular | `--ink` |
| Текст (абзацы, строки компаратора, значения) | 14 / 20 | 400 | 0 | `--ink` |
| Вторичный текст (мета, строки перелёта) | 14 / 20 | 400 | 0 | `--ink-2` |
| Подписи и служебный текст | 12 / 16 | 400 | 0 | `--ink-3` |
| Кнопки и чипы | 14 / 20 | 600 | 0 | по роли |
| Поле ввода | 16 / 24 | 400 | 0 | `--ink` |

Пояснения:
- Веса: «700 does one job per screen» (skill §9.1). Здесь 700 только у денег: итог карточки и итог сводки. Заголовки 600.
- Размер поля ввода 16px: держит iOS от зума при фокусе. Это решение, skill не упоминает.
- Ссылка «Подробнее» в карточке получает 14/600, цвет `--ink`, подчёркивание при hover (см. раздел 6.6).

### 3.4 Высоты строк, для которых skill правила не задаёт

Skill даёт высоты строк только для 14px (20px) и 16px (24px) (app-screens §9.1). Остальные значения принимаются как решение:
- 12 / 16 (решение, по аналогии с 14/20);
- 18 / 24 (решение);
- 24 / 32 (решение, текущий код тоже использует 32);
- 30 / 36 (решение).

Правило skill не задано для высоты строк 12, 18, 24 и 30. Значения выше — решение.

### 3.5 Соответствие текущего app.html шкале

| Место в app.html | Сейчас | Новое | Причина |
|---|---|---|---|
| `.intro h1` | 36/40, 700, градиентный текст | 24/32, 600, сплошной `--ink` | Заголовок 18–32 (§4.3). Градиент текста запрещён (SKILL.md, «No gradients»). |
| `.eyebrow` (над h1 и над заголовком сводки) | 12/16, 600 | удалить | «No eyebrows» (SKILL.md) |
| `.group-head h2` | 20/28, 700 | 18/24, 600 | 20 вне шкалы |
| `.chat-head h2` | 18/28, 700 | 18/24, 600 | 700 используется не по назначению |
| `.summary h2` | 20/28, 700 | 18/24, 600 | 20 вне шкалы |
| `.summary .big` | 24/32, 700 (в сводке) | 30/36, 700 | Ответ экрана, см. 1.4 |
| `.total` (карточка) | 24/32, 700 | 18/24, 700 | Цифра карточки должна быть ниже ответа в сводке минимум в 1.3 раза (skill §5: «at least 1.3 times its sibling figures»). 30 / 18 = 1.67. |
| `.info h3` | 18/28, 700 | 18/24, 600 | 700 только у денег |
| `.lede` | 16/24 | 16/24 | без изменений |
| `.btn` | 16/24, 600 | 14/20, 600 | Кнопки 13–16, медиана 14 (app-screens §9.1) |
| `.composer input` | 16/24 | 16/24 | без изменений |

---

## 4. Цвет

### 4.1 Пропорции (skill colour.md §3 и app-screens §9.2)

- Фон: около 85% поверхности.
- Текст: `--ink` и `--ink-2`.
- Акцент: не более 3–5% пикселей. Места акцента перечислены в 4.4.
- Фон карточки и сводки отличается от фона страницы одним шагом тона, без теней (skill §9.4 и app-screens §13: «A border or a shadow, never both»).

### 4.2 Токены: светлая тема

| Токен | Значение | Роль | Контраст |
|---|---|---|---|
| `--ground` | `#F6F4EF` | фон страницы | база |
| `--surface` | `#FFFFFF` | карточки, сводка, помощник | база |
| `--raised` | `#F0EDE5` | активный пункт навигации, код пользователя в чате, hover вторичной кнопки, неактивная кнопка | |
| `--line` | `#E2DDD1` | 1px границы карточек и разделители | 1.23:1 к фону (допустимо, app-screens §13: 1.1–1.3:1) |
| `--control` | `#B5AEA0` | граница вторичной кнопки, поля ввода, чекбоксы, пунктирная граница пустого блока | решение (правило границ контролов в skill не задано, границы интерактивных элементов «brighter», §9.3) |
| `--ink` | `#1A1813` | основной текст, фон первичной кнопки | ~16:1 на белом |
| `--ink-2` | `#4F4B42` | вторичный текст | ~8:1 на белом, ~7.4:1 на фоне |
| `--ink-3` | `#6B665B` | подписи, плейсхолдеры, мета | 5.7:1 на белом, 5.2:1 на фоне |
| `--primary` | `#1A1813` | заливка единственной первичной кнопки | белый текст |
| `--accent` | `#1F5C4B` | данные: выбранный и лучший итог, позитивная метка, кольцо выбранной карточки, фокус | 7.9:1 на белом |
| `--accent-ink` | `#FFFFFF` | текст на `--accent` | 7.9:1 |
| `--accent-soft` | `#E3EEE9` | фон позитивной метки | |
| `--warn` | `#7A5300` | текст предупреждения | 6.1:1 на `--warn-soft` |
| `--warn-soft` | `#FBF0DB` | фон предупреждения | |
| `--bad` | `#9B2C1F` | текст ошибки | 6.4:1 на `--bad-soft` |
| `--bad-soft` | `#FBE9E5` | фон ошибки | |

Замечание о текущих значениях. В `app.html` сейчас `--ink-2: #5C584E` (4.2:1 на белом, 3.9:1 на фоне) и `--ink-3: #7F7A6E` (4.3:1 на белом, 3.9:1 на фоне). Оба не проходят порог 4.5:1 (app-screens §9.2, «every tier that carries words reaches 4.5:1»). Замена на значения из таблицы выше.

### 4.3 Токены: тёмная тема

Тёмная тема включается по `prefers-color-scheme: dark` и по `data-theme="dark"`. Оба правила нужны (как сейчас). Добавить на корень `color-scheme: dark`, чтобы чекбоксы и полосы прокрутки тоже были тёмными (colour.md §6).

| Токен | Значение | Роль | Контраст |
|---|---|---|---|
| `--ground` | `#111111` | фон страницы | база |
| `--surface` | `#1A1A1A` | карточки и сводка | 1 шаг от фона |
| `--raised` | `#222222` | активный пункт, код пользователя, hover | |
| `--line` | `#2E2E2E` | границы карточек и разделители | §9.3: 22–31 уровень над фоном |
| `--control` | `#444444` | граница вторичной кнопки и полей | §9.3: интерактивные контуры #373737–#4C4C4C |
| `--ink` | `#FFFFFF` | основной текст | |
| `--ink-2` | `#B4B4B4` | вторичный текст | 8.4:1 на `--surface` |
| `--ink-3` | `#949494` | подписи | 5.8:1 на `--surface`, 5.2:1 на `--raised` |
| `--primary` | `#FFFFFF` | заливка первичной кнопки, текст `#111111` | |
| `--accent` | `#7FC2A9` | данные и фокус | 9.2:1 на `--ground` |
| `--accent-ink` | `#0F1A15` | текст на `--accent` | |
| `--accent-soft` | `#1E3A30` | фон позитивной метки | |
| `--warn` | `#E8C27A` | текст предупреждения | |
| `--warn-soft` | `#3A2E14` | фон предупреждения | |
| `--bad` | `#F09A8A` | текст ошибки | |
| `--bad-soft` | `#3D2019` | фон ошибки | |

Почему не тёплая тёмная тема из `app.html` (`#131209`, `#1F1F1F`, `#272727`): skill (app-screens §13, строка «a navy or slate dark theme») требует «true neutral darks». Тёмный фон с заметным тоном не нейтрален. Решение: переход на нейтральные. Если бренд требует тёплую тёмную тему, это решение заказчика (см. раздел 9). Текущий `--ink-3: #8C887E` на `--raised: #272727` даёт около 4.3:1, то есть не проходит порог. Замена на `#949494` даёт 5.2:1.

### 4.4 Где стоит акцент

Акцент стоит только в этих местах:
1. Итог выбранного варианта (в карточке и в сводке).
2. Лучший итог в сравнении (только цифра и подпись «Ниже всех»; заливки ячейки нет).
3. Кольцо выбранной карточки (1px граница и 1px кольцо, см. 6.6).
4. Позитивные метки (`Бесплатная отмена`, `Закреплён`).
5. Фокус (`:focus-visible`, 2px, отступ 2px).

Акцент не стоит на кнопках, ссылках, навигации, иконках, чекбоксах. Правило: app-screens §13, строка «The accent on chrome», и SKILL.md «one accent on the action and perhaps one other thing» (для страниц; для app-screens §9.2 «Accent placement: on data only with greyscale chrome»).

Исключение: чекбоксы сравнения окрашиваются `accent-color: var(--ink)`, не акцентом.

### 4.5 Статусы

- Позитивный: текст `--accent` на `--accent-soft`. Пара токенов одинаковая в обеих темах, значения разные.
- Предупреждение: текст `--warn` на `--warn-soft`, плюс иконка и слова.
- Ошибка: текст `--bad` на `--bad-soft`, плюс иконка и слова.

Смысл статуса никогда не передаётся только цветом: рядом всегда слова и иконка. Правило в skill: app-screens §7.6 («one status mark per row», состояния выражены словами) и §9.4 («Colour says good or bad» в §5 через слова). Прямой формулировки «цвет не единственный носитель смысла» в skill нет, пункт опирается на эти места.

---

## 5. Отступы и радиусы

### 5.1 Отступы

База 4px (app-screens §9.4: «Spacing on a 4px base»). Используемые значения: 4, 8, 12, 16, 20, 24, 32, 40, 64.

| Место | Значение | Правило |
|---|---|---|
| Внешние поля страницы | 24 (16 на телефоне) | §2.2 |
| Зазор между карточками | 16 | app-screens §9.4: card gap 6–24, медиана 16 |
| Внутренний отступ карточки варианта | 12 | app-screens §9.4: card padding 12–34. Сейчас 8: ниже диапазона, исправить. |
| Внутренний отступ карточки сводки | 24 | §9.4 (медиана 20, допустимо 24) |
| Внутренний отступ панели помощника | 16 | §9.4 |
| Отступ секции | 40 | §9.4: section gap 24–64 |
| Высота кнопки | 36 (small 32) | §8.3: 27–40, медиана 34, 32 чаще |
| Высота поля ввода | 40 | §8.1: 38–40 в формах |
| Высота чипа | 28 | решение (чип не кнопка, §8.1 не задаёт) |
| Высота пилюли и метки | 24 | §13: 16–24px |
| Высота строки компаратора (одна строка) | 40 | §7.1: 36–53, медиана 45, принимается 40 |
| Высота строки двустрочной | 56–72 | §7.1: 56–92 |
| Высота строки навигации | 34 | §3.2: 34 pitch |

### 5.2 Радиусы

Правило skill (app-screens §9.4, §13 и `assets/app.css` комментарий): радиусы двухмодальные. Либо tight (4–8, контролы 6), либо soft (16–24, контролы full pill). **Не 11 и не 12.** Выбран soft (ход «потребительский продукт»).

| Элемент | Радиус | Правило |
|---|---|---|
| Карточка варианта, сводка, помощник, компаратор, пустой блок | 16 | soft (app-screens §9.4: 16–24) |
| Вложенные медиа, блоки уведомлений, подложка лимитов | 8 | ступень вложенности (см. 5.3) |
| Кнопка, чип, поле ввода, кнопка отправки | полная пилюля (999) | «full pills with soft ones» (§8.3 и §9.4) |
| Пилюля или метка | полная пилюля | §7.6 и §13 (16–24px tall) |
| Пузырь пользователя в чате | 8 | вложенный в карточку помощника |
| Чекбокс | 4 | решение (правило для чекбокса в skill: «3px radius», §7.8) |
| Фокус | 4 | решение |

Текущий `app.html` использует 12px для `.btn`, `.composer input`, `.send`, `.notice` (запрещено: «never 11–12»). Заменить на 999 для кнопок и 8 для уведомлений.

### 5.3 Вложенный радиус

Формулы в skill нет. Правило skill не задано для вычисления внутреннего радиуса по внешнему и отступу. Skill описывает только наблюдаемую лестницу: «Nested radii step down (20, 10, 8, 5, 4)» (app-screens §9.4). Здесь принята ступень 16 → 8 для карточки с отступом 12 (медиа) и 24 (блок сводки), и 16 → 8 для уведомления внутри сводки.

Если нужна формула (например, внутренний радиус равен внешнему минус отступ), это решение, не правило skill, и его нужно пометить в ревью.

---

## 6. Компоненты

Для каждого компонента указаны состав, состояния (hover, active, focus, loading, empty, error, disabled) и поведение по ширинам. Если состояние неприменимо, написано «не применяется».

### 6.1 Верхняя панель

- Состав: монограмма 20px (`--ink`), название «Один шаг до идеального отеля» 14/20 600, ссылки «Поиск» (текущая) и «Бронирования» (неактивная).
- Высота 48, фон `--ground`, нижняя граница `--line` 1px (разрешено для app-screens §13 «1px borders»).
- Текущая: `position: fixed`, `backdrop-filter: blur(12px)`, полупрозрачный фон, скругление 16. Убрать все четыре. Правило: SKILL.md «No glass», skill §7 «a plain bar».
- Ссылка, текущая: фон `--raised`, цвет `--ink`, `aria-current="page"`.
- Ссылка, hover: фон `--raised`, 180ms ease. Не сдвиг и не смена веса.
- Ссылка, focus: 2px контур `--accent`, отступ 2px.
- Ссылка, неактивная (`aria-disabled`): цвет `--ink-3`, курсор `not-allowed`, `title` с текстом «Недоступно в прототипе». Этот `title` не виден на тач-экранах: добавить видимую подпись «Недоступно» в `aria-describedby` или текстом рядом.
- Ширина до 640: ссылки скрыть, оставить монограмму и название.

### 6.2 Заголовок результатов

- Состав: h1 (24/600), строка статуса (14/20 `--ink-2`). Надпись «Поездка» удалена.
- Состояния: без загрузки строка статуса показывает «Опишите поездку, чтобы получить варианты» (текущая логика). При отправке: «Ищем варианты». Ошибка сети не меняет заголовок, ошибка выводится в блоке результатов (6.9).
- Empty: строка статуса указывает, что сделать.
- Фокус: не применяется (текст).

### 6.3 Баннер данных

- Состав: две строки-уведомления с иконкой `ph-info` 16px. Первая: «Данные: синтетические. Набор не отражает реальные цены и наличие.» Вторая: текст `simulation_notice` или запасной текст «Оформление имитация. Реальные бронирования и платежи не выполняются.»
- Текущий код выводит `Данные: SYNTHETIC`. Английский код в видимом тексте недопустим для русского интерфейса. Заменить на перевод: `SYNTHETIC` → «синтетические». Значения, не переведённые в словарь, выводятся как есть, но в продукт этого не должно попадать (значения `data_mode` в контракте ожидаются из ограниченного набора; это подтверждается отдельно, см. раздел 9).
- Тон: нейтральный (`.notice.neutral`: фон `--raised`, граница `--line`). Не предупреждение: это не ошибка.
- Состояния: видно при загрузке, при ошибке и при пустом ответе. Не закрывается. Не сворачивается на телефоне.
- Hover, focus: не применяются (текст без действия).
- Ширины: на десктопе две строки, на телефоне две строки с переносом. Размер текста 14/20, как у остальных уведомлений.

### 6.4 Чипы параметров

- Состав: подпись (`--ink-3`, 14/20 400) и значение (`--ink`, 14/20 600). Пример: «Даты 10.02 – 15.02».
- Тип: неинтерактивный элемент. Текущий CSS задаёт `:hover` и `:active` на `.chip`, но это `span`, на него нельзя нажать. Убрать hover и active. Правило: skill §8.1 и SKILL.md «A control explains how to use it by its form». Если чип станет кнопкой, сначала описать действие.
- Форма: пилюля высотой 28, граница `--line`, фон `--surface`.
- Пустой набор: блок не выводится.
- Ширины: переносятся на несколько строк с зазором 8. На телефоне скрываются за раскрывающимся блоком «Параметры» (раздел 2.2).

### 6.5 Помощник (чат)

- Состав: заголовок «Помощник» (18/600), подзаголовок «Ответы приходят с сервера» (12/16 `--ink-3`), лента сообщений, композер (поле ввода и кнопка отправки).
- Сообщение пользователя: фон `--raised`, радиус 8, отступ 12/16, текст 14/20. Выравнивание вправо.
- Сообщение помощника: без фона и без пузыря, текст 14/20 `--ink`. Выравнивание влево. Правило: skill §8 о том, что ornaments не нужны.
- Композер: поле ввода 40px, пилюля, граница `--control`, фон `--ground`, текст 16/24, плейсхолдер `--ink-3` («Например, шерегеш вдвоём 10–15 февраля из Москвы»). Кнопка отправки 40×40, круг, иконка `ph-paper-plane-right` 16px, `aria-label` «Отправить».
- Состояния поля ввода:
  - default: граница `--control`;
  - hover: граница `--ink-3`;
  - focus: 2px контур `--accent`, отступ 2px;
  - disabled: не применяется. Поле остаётся редактируемым даже во время запроса. Во время запроса блокируется только кнопка отправки (текущая логика `#send.disabled`).
  - error: текст ошибки под полем (14/20 `--bad`, иконка `ph-x-circle`). Не применяется к пустому вводу: пустой ввод просто не отправляется.
- Состояния кнопки отправки:
  - default: пилюля 40×40, заливка `--primary`, иконка `--primary-ink` (белая на светлой, `#111111` на тёмной);
  - hover: заливка темнее на 1 шаг (контраст растёт);
  - active: `scale(0.97)`, 140ms;
  - focus: 2px контур `--accent`;
  - disabled (во время `busy`): заливка `--raised`, иконка `--ink-3`, 180ms.
- Лента, состояния:
  - empty (до первого сообщения): без текста-заглушки. Подсказка находится в плейсхолдере композера и в пустом блоке результатов (6.10).
  - loading: отдельного индикатора нет. Статус «Ищем варианты» в заголовке результатов.
  - error: уведомление `bad` внизу ленты с текстом ошибки и текстовой кнопкой «Повторить» (6.11).
- Ширины: 296 (колонка на 1440 и шире), блок над результатами (1100–1439), блок над списком (до 1099), на телефоне лента сворачивается до 2 сообщений (раздел 2.2).
- Фокус ленты: `tabindex="0"` для прокручиваемой области, чтобы клавиатура могла прокрутить ленту.

### 6.6 Карточка варианта

Состав (сетка колонок 152 / 1fr / 184 на десктопе, см. 2.2):
1. Медиа-заглушка 152px (на телефоне полоса 160px сверху).
2. Информация: название отеля (18/24 600), строка мета (14/20 `--ink-2`: «Номер · 4 звезды · оценка 8.4»), теги, строки перелёта (`Туда`, `Обратно`, `Трансфер`), строка проверок.
3. Цена: «Итог поездки» (12/16 `--ink-3`), сумма (18/24 700), «Сейчас к оплате» (12/16 `--ink-3`), сумма (16/24 600).
4. Подвал: кнопка «Выбрать» (вторичная, см. 6.11), кнопка «Закрепить» (ghost), раскрывающийся блок «Подробнее».

Правила содержимого:
- Медиа: нейтральная заглушка, фон `--raised`, радиус 8, текст «Фото появится из каталога» 12/16 `--ink-3`. Иконка `ph-image` убрать: иконка в квадрате запрещена (app-screens §13, «An icon in a coloured circle or tinted square on every KPI or card: banned»). Это решение по требованию заказчика «заглушки для фото допустимы». Skill (imagery.md §9 и app-screens §13) предпочитает реальную картинку или ничего, поэтому отмечено в разделе 9.
- Мета: звёзды выводить текстом «4 звезды», не символом «★». Символы-звёзды могут отображаться как эмодзи, а эмодзи запрещены (SKILL.md «No emoji»). Оценка: выводить только если пришла из ответа сервера. Не выдумывать.
- Метки: `tag` (пилюля, высота 24, 12/16 600). Цвета: позитивная (`.ok`), предупреждение (`.warn`), ошибка (`.bad`), нейтральная (`.raised`). Текущий `.tag` задаёт радиус 8: заменить на полную пилюлю.
- Итог: «Итог поездки» (12 подпись), сумма (18/700 tabular). Если итог не полный: «Итог неполный» 14/600 `--warn` с иконкой, и «Известные части: X» 14/20 `--ink-2`. Сумма известных частей не выдаётся за итог (это правило из текущего кода, сохранить).
- Цифры: округление до рублей. Суммы строк в сводке должны складываться в показанный итог (см. 9, «округление»).
- Прочерк: если «Сейчас к оплате» не пришло, вывести серый прочерк `—` цветом `--ink-3` 16/24 (skill §11: «Never a dash at full value size»). Текущий код использует прочерк в полном цвете `--ink`, исправить.
- Тег «Закреплён»: позитивный.

Состояния карточки:
- **default**: фон `--surface`, граница `--line` 1px, радиус 16, без тени.
- **hover**: сама карточка не меняется (skill §8 и motion.md §6: «Do not lift or scale every card on hover»). Hover только на кнопках внутри.
- **focus**: карточка не фокусируется. Фокус на кнопке «Выбрать» (2px контур `--accent`).
- **selected** (`.is-selected`): граница `--accent` 1px и кольцо `--accent` 1px (всего 2px). Совпадает с правилом выбранного элемента в списке (app-screens §7.8, «a 1px outline at 6px radius»). Переход цвета 180ms, без сдвига.
- **partial** (`suitability: conditional`): тег `warn` «Условно подходит». Заголовок остаётся `--ink`, а не `--ink-2`. Текущий код ставит `--ink-2` на заголовке: это состояние передаётся только цветом, исправить, см. правило «colour alone» (app-screens §9.4, «Colour says good or bad»).
- **excluded** (`suitability: excluded`): карточки в основной группе нет (раздел 2.4). Причина выводится текстом.
- **loading** (до прихода данных): вариант `skeleton`. Карточка в реальных размерах с серыми полосами `--raised` на месте заголовка, мета и цены, без пульсации и без shimmer (app-screens §11: «no shimmer gradient»; motion: нет бесконечных циклов без смысла).
- **empty**: блок «Вариантов пока нет.» (см. 6.10).
- **error**: блок ошибки внутри списка, см. 6.9.
- **disabled** (кнопка «Выбрать», когда `canSelect` = false): кнопки нет, на её месте текст «Выбор недоступен» 14/20 `--ink-3`. Не серый на сером (иначе нечитаемо, app-screens §8.1: «Disabled controls stay legible and say why»).

Поведение по ширинам:
- ≥1100: три поля в одну строку (медиа, информация, цена). Подвал на всю ширину карточки.
- 641–1099: медиа полосой сверху 160px, затем информация, затем цена строкой (итог слева, «Сейчас к оплате» справа), затем подвал.
- ≤640: то же, что 641–1099, но медиа 120px, кнопки подвала по ширине.

Раскрывающийся блок «Подробнее» (`details`):
- Заголовок: «Подробнее», 14/600 `--ink`, подчёркивание при hover, иконка `ph-caret-down` 14px.
- Содержимое: две колонки (состав цены и расписание перелётов). На телефоне одна колонка.
- Текущий `summary` имеет цвет `--accent`. Это ссылка-действие, акцент здесь не нужен. Заменить на `--ink`.

### 6.7 Сводка выбора («Квитанция выбора»)

Состав (сверху вниз):
1. Заголовок блока: «Выбор и итог» (18/600). Надпись «Выбор» над ним удалить.
2. Статус состояния: текст из STATE_LABEL, 14/20 `--ink-2`.
3. Итог выбранного варианта: «Итог поездки» (12/16 `--ink-3`), сумма (30/36 700 tabular). Это ответ экрана.
4. Состав: строки «Отель», «Перелёт туда», «Перелёт обратно», «Трансфер», «Итог» (`dl`, 14/20; суммы справа, tabular, 14/400). Строки разделены 1px `--line`. Итоговая строка 16/600 без разделителя сверху. Сумма строк в сводке должна точно совпадать с показанным итогом (правило app-screens §2.1: «Totals and deltas come from the parts as shown»).
5. «Позже: X» 14/20 `--ink-2`, если есть `due_later_minor`.
6. «Снимок действует до ЧЧ:ММ ДД.ММ» 14/20 `--ink-2`.
7. Блок «Не пройдено»: уведомления `warn` с иконкой `ph-warning` 16px.
8. Блок «Имитация оформления»: уведомление, текст зависит от `op.status` (см. состояния).
9. Кнопки действий (раздел 6.11).

Состояния сводки (одна таблица, все варианты из кода и контракта):

| Состояние | Что выводится | Кнопка |
|---|---|---|
| Пустой выбор (`!resp` или нет `plan`) | «Пока ничего не выбрано» (h2), «Выберите вариант, чтобы увидеть состав и итог.» 14/20 `--ink-2` | нет |
| Выбор сделан, итог полный (`CART_REVIEW`) | итог 30, состав, снимок | «Проверить корзину» (первичная), если разрешено |
| Итог неполный | «Итог неполный» 14/600 `--warn`, «Известные части: X» 14/20, пояснение «Показана сумма известных частей» | как выше |
| Перестройка (`REBUILDING`) | «Пересобираем корзину» 14/20, блок состава с серыми полосами | кнопки disabled |
| Ожидание подтверждения (`AWAIT_CONFIRM`) | как «CART_REVIEW», плюс снимок с временем | «Подтвердить имитацию оформления» (первичная) |
| Имитация подтверждена (`DEMO_DONE`, `SIMULATED_CONFIRMED`) | уведомление `neutral`: «Имитация подтверждена. Реальное бронирование не создано.» | «Остановить» (вторичная) если разрешено |
| Имитация не выполнена (`DEMO_FAILED`, `SIMULATED_FAILED`) | уведомление `warn`: «Имитация не выполнена. Ничего не создано, выбор сохранён.» | «Проверить корзину» (первичная), если разрешено |
| Результат неизвестен (`RECOVER_RESULT`) | уведомление `warn`: «Результат неизвестен. Проверьте результат и не покупайте повторно.» | «Проверить результат» (первичная) |
| Пауза (`PAUSED`) | «Пауза» 14/20 | «Остановить» если разрешено |
| Ошибка запроса (сетевая или без состояния) | блок ошибки внутри сводки (6.9) | «Повторить» (текстовая) |

Правило: в сводке не больше одной первичной (заливка) кнопки одновременно. Если разрешены две, первая первичная, вторая вторичная (app-screens §4.3 и SKILL.md «One primary per screen»).

Состояния блока сводки (контейнера):
- default: `--surface`, граница `--line`, радиус 16, отступ 24.
- hover, focus: не применяются к контейнеру, только к кнопкам.
- loading: серые полосы в реальных размерах.
- empty: см. таблицу.
- error: блок ошибки внутри сводки.
- disabled: не применяется к контейнеру, только к кнопкам.

Поведение по ширинам:
- ≥1440: колонка 336, sticky сверху 24, высота `calc(100vh − 48)`, прокрутка внутри блока, если содержимое длиннее.
- 1100–1439: колонка 320, sticky сверху 24.
- 641–1099: блок над списком, не sticky.
- ≤640: однострочная строка над списком (`details`): «Выбрано: название · итог · Проверить корзину». По нажатию раскрывается полная сводка.

Сигнатура «Квитанция выбора»: строки состава выровнены по двум краям (название слева, сумма справа, tabular), между строками 1px `--line`, итог внизу блока крупнее строк. Знаков и иллюстраций нет.

### 6.8 Сравнение

Сравнение в `app.html` пока не выводится: CSS-правила `table`, `.table-wrap`, `td.best` есть, а рендера нет. Описание ниже задаёт целевой вид.

Состав:
- Выбор вариантов: чекбокс «Сравнить» на каждой карточке (16px, граница `--control`, галочка `--ink`). Разрешено до 4 вариантов.
- Панель над списком: кнопка «Сравнить (N)» (вторичная) и текст «Выберите от 2 до 4 вариантов» (14/20 `--ink-2`), если выбрано меньше двух. Кнопка disabled с этим текстом рядом (app-screens §8.1: «say why»).
- Таблица: первая колонка 184 (sticky слева), далее по колонке на вариант (минимум 160). Заголовок колонки: название отеля (14/600) и подпись комнаты (12/16 `--ink-3`). Строки: отель и номер, питание, отмена, перелёт туда, перелёт обратно, трансфер, проверки, итог, сейчас к оплате.

Оформление таблицы (skill §7.1–§7.5, app-screens):
- Заголовок: 12/16 400, `--ink-2` (app-screens §7.2: «11–14px, median 12, mostly regular weight, grey, sentence case»). Без заливки. Нижняя граница 1px `--line` (допустимо, но не самая тёмная линия на экране).
- Строки: высота 40, разделители 1px `--line`. Зебры нет (app-screens §7.3: «No zebra striping»).
- Числа: `td.num`, выравнивание вправо, tabular, фиксированные два знака для сумм? Здесь рубли без копеек (см. раздел 9).
- Лучший итог: цифра `--accent` 16/600 и подпись «Ниже всех» 12/16 `--accent` под цифрой. Заливка `td.best` (текущий `background: var(--accent-soft)`) убрать: заливка ячейки не показывает смысл, его несёт текст и цифра. Правило: app-screens §7.4 «Two measures stack in one cell», §9.2 «accent on data only».
- Пустая ячейка (нет данных): серый прочерк `—` `--ink-3` (skill §7.5: «A missing value is a grey dash»).

Состояния:
- default: таблица в рамке `.table-wrap` (радиус 16, граница `--line`).
- hover строки: не применяется (нет действий внутри строки). Если строки станут кликабельными, hover: фон `--raised`, 180ms (app-screens §7.8: «hover and selected: a 1px outline at 6px radius», допустимо как альтернатива).
- focus на ячейке-заголовке с сортировкой: не применяется (сортировки нет).
- loading: серые полосы в ячейках.
- empty (выбрано меньше двух): таблица не выводится, вместо неё текст подсказки (см. выше).
- error: блок ошибки над таблицей (6.9).
- disabled: кнопка «Сравнить» (вторичная disabled, 6.11).
- сверх лимита (5-й вариант): чекбокс disabled, рядом текст «Не больше 4» 12/16 `--ink-3`.

Поведение по ширинам:
- ≥640: таблица полной ширины результатов. При ширине меньше `min-width: 620` появляется горизонтальная прокрутка внутри рамки, первая колонка sticky.
- ≤640: то же, что выше. Страница не скроллится по горизонтали (skill §12).

### 6.9 Предупреждения, уведомления и ошибки

Виды уведомлений (`.notice`):
- `neutral`: фон `--raised`, граница `--line`. Для данных и статусов, которые ничего не требуют.
- `warn`: фон `--warn-soft`, текст `--warn`, иконка `ph-warning` 16px. Для невыполненных проверок, неизвестного результата, неполного итога.
- `bad`: фон `--bad-soft`, текст `--bad`, иконка `ph-x-circle` 16px. Для ошибки запроса.

Общие правила:
- Радиус 8, отступ 12, текст 14/20.
- Иконка и слова вместе. Цвет не единственный носитель смысла.
- Без левой полосы, без тени.
- Пометки `data_mode` и `simulation_notice` используют только `neutral`. Их нельзя выводить как предупреждение, потому что это не ошибка. Их видимость важнее любого предупреждения: баннер (6.3) и строка «Имитация» в сводке рендерятся всегда.

Блок ошибки (`.error-box` в текущем коде, `#error` над списком): по правилу skill (app-screens §11, «Error (house rule): inside the block that failed, never as a full-screen banner») ошибка выводится внутри блока, который не сработал:
- Ошибка поиска или чата: внутри ленты помощника (уведомление `bad` внизу ленты) и внутри блока результатов (блок «Не удалось получить варианты. Последний ответ: 09:14.» плюс «Повторить»).
- Ошибка корзины или оформления: внутри сводки (уведомление `bad` и кнопка «Повторить»).
- Текст ошибки из контракта (`error.message`) выводится как есть. Если `retryable: true`, дописывается «Можно повторить.».
- Текущий `.error-box` с фоном, границей 1px `--bad` и радиусом 16 заменить на уведомление `bad`. Блок внутри карточки и без обводки на всю страницу.

### 6.10 Пустые состояния

Правило skill: пустой блок сохраняет рамку и заголовок и добавляет одно предложение во всю ширину блока (app-screens §11). Текущий `.empty` (пунктирная рамка, радиус 16, отступ 24, текст 14/20) соответствует этому правилу, но пунктир должен быть 1px `--control`, а не `--line` (иначе почти не видно).

| Состояние | Текст | Вид |
|---|---|---|
| Ещё не отправлен запрос (`!resp`) | «Опишите поездку в поле слева. Например: «Шерегеш вдвоём 10–15 февраля из Москвы, с завтраком, до 250 тысяч на всё».» | пунктирный блок, на всю ширину результатов |
| `COMPARE` без вариантов | «Вариантов пока нет.» | то же |
| `NO_MATCH` | «Подходящих вариантов нет.» плюс подсказка, что изменить (текстом, не кнопкой) | то же |
| `DATA_LIMIT` | «Данных для этого запроса нет.» | то же |
| `CLARIFY` | «Нужно уточнение.» плюс вопрос из ленты | то же |
| `INSPIRE` | «Идеи для поездки.» | то же |
| `PAUSED` | «Пауза.» | то же |
| Фильтр скрыл все группы | «В этой группе вариантов нет.» | то же |

Текст «Вариантов для показа нет.» в коде уже есть в виде «[состояние]. Вариантов для показа нет.». Сохранить формат «[состояние]. [пояснение]».

### 6.11 Кнопки и контролы

Единая иерархия: одна первичная кнопка на экран, вторичные и текстовые без заливки (app-screens §8.3: «One filled primary per screen, usually near-black; a secondary is white with a 1px border; a tertiary is text»).

**Первичная** (`.btn.primary`): заливка `--primary`, текст белый на светлой теме (на `#1A1813`) и `#111111` на тёмной (на `#FFFFFF`). Высота 36, пилюля, 14/20 600.
- default: заливка `--primary`.
- hover: заливка на 1 шаг контрастнее (светлая: `#000000`; тёмная: `#E6E6E6`). Контраст растёт (skill: «Hover should raise contrast»).
- active: `scale(0.97)`, 140ms.
- focus: 2px контур `--accent`, отступ 2px.
- disabled: заливка `--raised`, текст `--ink-3`, курсор `not-allowed`, причина выводится текстом рядом (например, «Сначала выберите вариант»).
- loading (после нажатия): disabled, текст кнопки не меняется, статус в заголовке результатов показывает «Выполняем…».

**Вторичная** (`.btn`): фон `--surface`, граница 1px `--control`, текст `--ink`, высота 36, пилюля.
- hover: фон `--raised`, 180ms.
- active: `scale(0.97)`, 140ms.
- focus: 2px контур `--accent`.
- disabled: граница `--line`, текст `--ink-3`, фон `--raised`, причина текстом.

**Текстовая** (`.btn.ghost`, «Закрепить», «Открепить», «Повторить», «Подробнее»): без рамки и заливки, текст `--ink` 14/20 600, подчёркивание при hover (`text-underline-offset: 0.18em`, `text-decoration-thickness: 0.07em`, skill type.md §7).
- focus: 2px контур `--accent`.
- disabled: текст `--ink-3`, без подчёркивания.

**Кнопка «Выбрать»** в карточке: вторичная (не первичная), чтобы на экране не было нескольких заливок. Выбранный вариант: кнопка «Выбрано» вторичная disabled, или убрать кнопку и показать метку «Выбрано» (тег позитивный).

**Кнопка «Остановить»**: вторичная. Не красная, потому что это остановка сценария, а не необратимое действие. Если позже появится необратимое действие, оно получит текст `--bad` (app-screens §8.2: «Destructive actions go last, in a section of their own, as a text or outlined button in the negative colour»).

**Чекбокс** (сравнение): 16px, `accent-color: var(--ink)`, граница `--control`, радиус 4, отступ 8 до подписи. Состояния: default, hover (граница `--ink-3`), focus (2px контур `--accent`), disabled (граница `--line`, подпись `--ink-3`, текст причины рядом).

**Раскрывающийся блок** (`details`): заголовок 14/600, иконка `ph-caret-down` 14px, поворот 180ms `--ease-out`. Focus: 2px контур.

**Поле ввода и композер**: см. 6.5.

### 6.12 Пометки данных и имитации

Обязательные пометки (ТЗ заказчика):
- `data_mode`: «Данные: синтетические» (6.3). Видна всегда.
- `simulation_notice`: «Оформление — имитация. Реальные бронирования и платежи не выполняются.» (6.3). Видна всегда.
- В сводке блок «Имитация оформления» и уведомление с текстом «Имитация подтверждена» или «Имитация не выполнена» (6.7).
- На кнопке подтверждения: «Подтвердить имитацию оформления», а не «Подтвердить оформление». Слово «имитация» на самой кнопке.
- В карточке варианта цифры не помечаются отдельно, потому что баннер и сводка уже объясняют данные. Отдельная пометка на каждой карточке создаёт шум (skill §2.1, «a fact shown twice is one variable»).

Все пометки используют нейтральный стиль (`neutral`), не `warn`, потому что это не ошибка. Иконка `ph-info` 16px.

---

## 7. Изображения и иконки

### 7.1 Иконки

- Набор: Phosphor Icons, регулярный (outline), уже подключён (`@phosphor-icons/web@2.1.1`). Правило skill: «outline on 22 of 23», «one set» (app-screens §9.4).
- Размеры: 14px для инлайна и кнопок (chevron, caret), 16px для уведомлений и кнопки отправки. Правило: app-screens §9.4 («inline icons 12–14px»; «nav icons 14–20px»), уведомления 16 выбрано в диапазоне 14–20.
- Цвет: серый `--ink-2` или `--ink-3` по роли. В уведомлении цвет иконки совпадает с цветом текста уведомления.
- Контейнера у иконки нет. Иконка в квадрате, в круге или в подложке запрещена (app-screens §9.4: «Never in a saturated circle (0 of 23)»; §13: «An icon in a coloured circle or tinted square on every KPI or card: banned»).
- Иконки, которые используются: `ph-info`, `ph-warning`, `ph-x-circle`, `ph-paper-plane-right`, `ph-caret-down`. Иконка `ph-image` в медиа-заглушке убрана (см. 6.6).
- Иконки не заменяют текст. Каждая кнопка с иконкой без текста имеет `aria-label` (skill §6, пункт 9).
- Эмодзи запрещены везде, включая звёзды в мете (SKILL.md «No emoji», app-screens §13).

### 7.2 Заглушки фото

- Заказчик: «Заглушки для фото допустимы» (ТЗ). Skill: «No image is better than a placeholder» (colour.md / imagery.md §9), «no grey box» (critique.md §3). Это конфликт, решён в пользу ТЗ (раздел 9).
- Заглушка: блок 152×152 (на телефоне 120px высотой), фон `--raised`, радиус 8, без иконки, без рамки, без рисунка и без градиента. Текст: «Фото появится из каталога» 12/16 `--ink-3`, по центру.
- Заглушка не выводит фото, которое не пришло из контракта. Если в контракте появится `image_url`, заглушка заменяется картинкой с `alt` и `width`/`height` (правило skill imagery.md §10, «Always width and height»). Вывод картинки такой же, как у любой другой (радиус 8).

### 7.3 Отели, отзывы, рейтинги

Все названия отелей, номера, рейтинги, оценки и цены берутся только из контракта. Придумывать и подставлять нельзя (ТЗ). Если поле пусто, элемент не выводится (мета) или выводится серым прочерком (таблица сравнения).

---

## 8. Движение

### 8.1 Что анимируется

| Элемент | Что происходит | Длительность | Кривая | Пояснение |
|---|---|---|---|---|
| Появление карточек списка | opacity 0 → 1, translateY 16px → 0, по одной карточке с шагом 80ms, не больше 4 шагов подряд, остальные сразу | 800ms | `--ease-expo` (`cubic-bezier(0.16, 1, 0.3, 1)`) | motion.md §4: reveals 800ms и дольше; motion.md §10, п. 5 |
| Появление нового сообщения помощника | opacity 0 → 1, translateY 16px → 0 | 240ms | `--ease-out` | motion.md §3: menus and popovers 150–250ms |
| Нажатие кнопки | scale 1 → 0.97 | 140ms | `--ease-out` | motion.md §6, Press and hover |
| Hover кнопки и ссылки | цвет фона и текста | 180ms | `ease` | motion.md §4: `--t-hover` 180ms, цвет через `ease` |
| Выбор карточки (кольцо и граница) | цвет | 180ms | `ease` | цвет, не сдвиг |
| Открытие `details` (раскрывающиеся блоки) | поворот иконки | 240ms | `--ease-out` | motion.md §6, Menu or dialog |
| Смена состояния сводки | только цвет и opacity | 180ms | `ease` | без перемещения |
| Фокус | outline появляется мгновенно | 0 | — | доступность |

### 8.2 Что не анимируется

- Нет бесконечных циклов. Текущий `@keyframes pulse` у скелета убрать. Правило: motion.md §9, «Endless loops with no off switch» и «Infinite loops that mean nothing: pulsing dots». Скелет остаётся статичным серым блоком (app-screens §11: «no shimmer gradient»).
- Нет анимации суммы при обновлении. Цифра меняется сразу (skill §11 и motion.md §9: «Rolling figures» не выбрано, так как экран не про отчёт).
- Нет анимации баннера данных.
- Нет анимации таблицы сравнения.
- Нет параллакса, скролл-сцен, курсора.
- Нет пружинящих и bounce-кривых (motion.md §1 и §10, п. 3: «No overshoot»).

### 8.3 Reduced motion

`@media (prefers-reduced-motion: reduce)`:
- Сдвиги (translateY) и масштаб убираются. Остаётся opacity 300ms `ease-out`, как в motion.md §8 («keep opacity and colour changes, drop travel, scale»).
- `transition-duration` для hover и press сокращается до 1ms (как в текущем файле). Конечное состояние совпадает с финальным, контент не скрыт (motion.md §6: «Every recipe keeps the finished state as the reduced state»).
- Скелет остаётся статичным.

### 8.4 Скрытое содержимое

Элементы `.reveal` в текущем файле скрыты через `opacity: 0`, а класс `.in` добавляет JS, которого в `app.html` нет. Сейчас класс не используется, но его нельзя включать без работающего JS-пути: контент должен быть видим при отключённом JS (motion.md §6: «the hidden state only applies under html.js»). Правило: motion.md §6, «All recipes keep the content visible when JavaScript is off».

---

## 9. Конфликты с требованиями заказчика и предложения

### 9.1 «Очень круто» и запреты

Конфликтов с запретами (фиолетовые и голубые градиенты, glow, glassmorphism, `backdrop-filter`, декоративные AI-эффекты, пустой hero, бессмысленные метрики, декоративные карточки, эмодзи) между ТЗ и skill нет. Оба требуют одного и того же. Но желаемое «очень круто» конфликтует с частью приёмов, которые визуально выглядят эффектно. Что вместо них:

| Хотелось бы | Почему нельзя | Что делаем вместо |
|---|---|---|
| Градиентный заголовок (сейчас `linear-gradient` на h1) | Градиент в тексте запрещён (SKILL.md «No gradients») | Заголовок сплошной `--ink`, 24/600. Эффект даёт сводка с крупной цифрой (30/700) |
| Стеклянная плавающая панель навигации с размытием | «No glass», «no floating pill navigation» | Статичная панель 48px с нижней границей |
| Свечение или цветной ореол у выбранной карточки | «No glow» | Кольцо 1px цвета `--accent` и граница того же цвета (контур, не свечение) |
| Пульсация и shimmer на загрузке | Бесконечные циклы без смысла (motion.md §9) | Статичные серые полосы в реальных размерах |
| Зелёная заливка кнопок | Акцент на хроме запрещён (app-screens §13) | Первичная кнопка почти чёрная (`--primary`), акцент только на итоге |
| Иконки в цветных кругах у карточек | «An icon in a coloured circle on every card: banned» | Иконок нет, есть цифры и слова |
| Фото и картинки отелей, которых нет | Придумывать нельзя (ТЗ) | Нейтральная заглушка с текстом (см. 9.2) |

Что остаётся для эффекта, без запрещённых приёмов:
1. Квитанция выбора (сводка в виде чека с суммой строк): собственный элемент продукта (раздел 1.3).
2. Крупная цифра итога (30/700), которая отвечает на вопрос экрана (раздел 1.4).
3. Цвет только на данных: выбранный итог и лучший итог в сравнении. На остальном экране цвета нет, что даёт контраст.
4. Появление списка вариантов по одной карточке с шагом 80ms, один раз (раздел 8.1).
5. Панель помощника, в которой сообщения пользователя и бота различаются формой (пузырь и без пузыря), а не цветом.

### 9.2 Заглушки фото

- ТЗ: «заглушки для фото допустимы».
- Skill: `imagery.md` §9 и `critique.md` §3: «No image is better than a placeholder» и «Any placeholder (grey box...): content cannot exceed 5». Для app-screens §13: «A real picture, a real interface fragment, or nothing».
- Решение: делаем заглушку по ТЗ, но в нейтральном виде без иконки и без рисунка (раздел 7.2). Скилл-запрет на «серый блок» в критике относится к страницам, а заглушка здесь не заменяет смысл экрана. Если заказчик хочет полностью убрать заглушки, карточку делаем без медиа-колонки (высота карточки станет ниже, сетка 1fr).

### 9.3 Тёмная тема

- Текущий `app.html` использует тёплую тёмную тему (`#131209`, `#1F1F1F`).
- Skill (app-screens §13): «navy or slate dark theme» запрещена, нужны «true neutral darks». Тёплый тёмный фон с заметным тоном нейтральным не считается.
- Решение: нейтральная тёмная тема (раздел 4.3). Если бренд требует тёплую тёмную тему, это решение заказчика, потому что правило skill его не допускает.

### 9.4 Денежный формат

- Skill (app-screens §7.4): «Fixed decimals per metric: money 2».
- Продукт: округление до рублей (`Math.round(minor / 100)`). Так в контракте, и это обычный формат для потребительского бронирования.
- Решение: оставить рубли без копеек. Конфликт назван явно. Главное правило skill выполнено: строки сводки складываются в показанный итог (app-screens §2.1, «Sum the rounded pennies the screen displays»). Если строки округляются отдельно, сумма может разойтись на 1 рубль. Тогда итог считается как сумма округлённых строк, либо округление делается один раз на сервере. Решение нужно принять до выпуска.
- Пробелы: `rub()` сейчас заменяет неразрывный пробел (U+00A0) обычным пробелом (`.replace(/\s/g, ' ')`). Это ошибка: между числом и знаком ₽ должен стоять неразрывный пробел (copy.md §7, «Non-breaking space between a number and its unit»). Исправить: не заменять U+00A0.

### 9.5 Один первичный и вторичный «Выбрать»

- Skill: одна заливка на экран. В карточках по кнопке «Выбрать» (5 штук), значит 5 заливок.
- Решение: «Выбрать» вторичная, первичная только в сводке (раздел 6.11).

### 9.6 Точность текста без дефисов

Требование ТЗ: видимый текст без дефисов между словами. Для текста, который пишет дизайн, проверяется (см. 3.5 и 6.x). Для текста, который приходит из контракта, гарантию дать нельзя: в данных могут быть слова с дефисом («Бизнес-класс», «Эконом-класс», «все-включено» и т. п.). Варианты:
1. Словарь отображения в клиенте: значения из контракта проходят через таблицу замен только при выводе. Таблица нужна заказчику, потому что меняется смысл слова, а не только написание.
2. Требовать от сервера значения без дефисов.

Решение не принято. Нужен ответ заказчика до выпуска.

Тире и диапазоны: диапазоны дат и времени пишутся коротким тире «–» (как в текущем коде), это не дефис между словами. Слэш в «Ski in/out» (код, `hl.ski_in_ski_out`) не дефис. Правило skill: copy.md §7, «an en dash for ranges».

### 9.7 Другие расхождения текущего `app.html` с правилами

Список для исправления, по приоритету:
1. `backdrop-filter` и полупрозрачный фон у `.topbar` (glass). Убрать.
2. `linear-gradient` на `.intro h1` (градиент текста). Убрать.
3. `@keyframes pulse` на скелете. Убрать.
4. Иконка `ph-image` в заглушке медиа (иконка в квадрате). Убрать.
5. `.eyebrow` над h1 и над заголовком сводки. Удалить.
6. Звезда «★» в мете. Заменить на «N звезд» (словом; склонение по числу: «4 звезды», «5 звёзд» по правилам русского языка).
7. Текст `SYNTHETIC` в баннере. Перевести.
8. Прочерк в полном цвете для «Сейчас к оплате». Сделать серым.
9. `--ink-2` и `--ink-3` ниже 4.5:1. Заменить (раздел 4.2).
10. `--ink-3` на `--raised` в тёмной теме ниже 4.5:1. Заменить (раздел 4.3).
11. Размеры вне шкалы: 20, 28, 36, 40 (раздел 3.5).
12. Радиус 12 у `.btn`, `.composer input`, `.send`, `.notice`. Заменить (раздел 5.2).
13. Радиус 8 у `.tag`, `.media`. Тег заменить на пилюлю, медиа оставить на 8 (раздел 5.2).
14. Отступ карточки 8. Заменить на 12 (раздел 5.1).
15. Hover и active на `.chip` (неинтерактивный элемент). Убрать.
16. `transition` 300ms на hover и 300ms на `.btn:hover`. Заменить на 180ms.
17. `.card.is-selected` с `box-shadow` (кольцо 1px). Оставить: это контур выбранного элемента, а не тень. Документировать в комментарии, чтобы не приняли за glow.
18. `.btn:disabled { opacity: 0.5 }`. Заменить на явные цвета disabled (раздел 6.11), иначе акцентная кнопка теряет контраст.
19. `.summary .big` 24 (сейчас). Сделать 30.
20. `.intro h1` 36/40. Сделать 24/32.
21. Ссылка «Подробнее» цветом `--accent`. Сделать `--ink`.
22. `.error-box` с рамкой и радиусом 16 вместо уведомления внутри блока. Заменить (раздел 6.9).
23. Ссылка Google Fonts грузит лишние веса 500. Оставить 400, 600, 700.
24. `color-scheme: dark` не задан. Добавить.
25. Вывод таблицы сравнения не реализован. Реализовать по разделу 6.8.
26. `.reveal` без JS-пути. Добавить `html.js` (раздел 8.4).
27. `title` у неактивной ссылки «Бронирования» не виден на тач-экранах. Добавить текст (раздел 6.1).

---

## 10. Что skill не покрывает

Пункты, по которым правило skill не задано или задано не полностью. Решения принимаются отдельно, их нужно проверить.

1. **Формула вложенного радиуса.** Правила нет. Использована только наблюдаемая лестница 20/10/8/5/4 (раздел 5.3).
2. **Высоты строк для 12, 18, 24 и 30px.** Правила нет. Решения в разделе 3.4.
3. **Конкретные ширины колонок и точки перелома для трёх колонок.** Skill даёт медианы и примеры (app-screens §4.1, §3.2), но не таблицу брейкпоинтов для экрана с тремя колонками. Решение в разделе 2.2, расчёт дан.
4. **Чат как элемент интерфейса.** Skill описывает экраны, панели, таблицы, списки, но не чат и не поток сообщений. Правила для пузырей, композера и ленты не заданы. Решения в разделе 6.5.
5. **Русская типографика.** Skill даёт curly quotes (copy.md §7), но не даёт правила для «ёлочек», даты, склонений и формата «N звёзд». Решения: 9.6 и 6.6.
6. **Денежный формат с нулевыми копейками.** Skill требует 2 знака (раздел 9.4).
7. **Иконочный набор Phosphor через CDN.** Правило skill про набор иконок есть (outline, один набор), про загрузку через CDN правила нет.
8. **Цвет неактивной (disabled) кнопки с контрастом.** Skill требует читаемость и причину текстом (app-screens §8.1), но числового порога для disabled не задаёт. Принят порог 4.5:1 для текста подписи причины.
9. **Цвет границы интерактивного контрола.** Skill говорит «brighter», без значения для светлой темы (app-screens §9.3 описывает только тёмную). Значение `#B5AEA0` взято как решение и требует проверки.
10. **Тёплый стоун-фон по «saturation under 15%».** Skill (colour.md §9) задаёт порог насыщенности без уточнения, HSL или chroma. Фон `#F6F4EF` по HSL-насыщенности около 27%, по chroma очень низкий. Решение оставить фон, проверка в inspector не запускалась.
11. **Сравнение на телефоне.** Skill различает два варианта: таблица в рамке с прокруткой (§12) или список двухстрочных строк (§12). Выбран первый вариант, потому что сравнение должно оставаться таблицей. Это решение.
12. **Текст из контракта без дефисов.** Skill не задаёт правила для текста, который приходит с сервера (раздел 9.6).
13. **Скорость анимации списка.** Skill даёт шаг 60–90ms для секции (motion.md §1 и §10, п. 5). Для динамического списка карточек это применено как «не больше 4 шагов», но прямого правила для списков, которые приходят после запроса, нет.
14. **Состояние «Фильтр скрыл все группы».** Фильтров в текущем экране нет. Текст пустого состояния добавлен по аналогии с правилом skill «a filtered block shrinks to its rows» (app-screens §11). Прямого правила для такого текста нет.
15. **Проверка шрифта на табличные цифры в Manrope.** Skill требует проверить (app-screens §9.1). Проверка не выполнена.
16. **Проверка по inspector и lint.** Не запускалась: скрипты skill не выполнялись по требованию. Перед выпуском нужно запустить `inspect.mjs --app` на 1440, 768 и 390 и исправить все FAIL.
