diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..00d36ff --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,556 @@ +--- +name: Кварта +description: Лендинг минской обжарки — «недельный календарь и штамп даты» +colors: + cream: "#F3EBDD" + paper: "#FBF7F0" + kraft: "#DCC7A1" + roast: "#1E120C" + ink: "#2A1810" + muted: "#5E4636" + placeholder: "#7A6352" + rule-soft: "#C9B595" + cherry: "#B3221B" + cherry-deep: "#8F1A14" + cherry-on-dark: "#E8604F" + muted-on-dark: "#BFA88A" + ok: "#2E5E3A" +typography: + display: + fontFamily: "Golos Text, system-ui, sans-serif" + fontSize: "clamp(2.75rem, 1.6rem + 5.2vw, 6rem)" + fontWeight: 900 + lineHeight: 0.92 + letterSpacing: "-0.035em" + headline: + fontFamily: "Golos Text, system-ui, sans-serif" + fontSize: "clamp(2rem, 1.4rem + 2.8vw, 3.5rem)" + fontWeight: 800 + lineHeight: 1 + letterSpacing: "-0.03em" + title: + fontFamily: "Golos Text, system-ui, sans-serif" + fontSize: "clamp(1.5rem, 1.3rem + 0.6vw, 1.875rem)" + fontWeight: 800 + lineHeight: 1.05 + letterSpacing: "-0.02em" + lead: + fontFamily: "Golos Text, system-ui, sans-serif" + fontSize: "clamp(1.125rem, 1rem + 0.5vw, 1.375rem)" + fontWeight: 400 + lineHeight: 1.45 + body: + fontFamily: "Golos Text, system-ui, sans-serif" + fontSize: "1.0625rem" + fontWeight: 400 + lineHeight: 1.55 + small: + fontFamily: "Golos Text, system-ui, sans-serif" + fontSize: "0.875rem" + fontWeight: 500 + lineHeight: 1.45 + mono-tag: + fontFamily: "Martian Mono, ui-monospace, monospace" + fontSize: "0.75rem" + fontWeight: 500 + lineHeight: 1.3 + letterSpacing: "0.08em" + mono-price: + fontFamily: "Martian Mono, ui-monospace, monospace" + fontSize: "clamp(1.375rem, 1.2rem + 0.8vw, 2rem)" + fontWeight: 600 + lineHeight: 1 + letterSpacing: "-0.02em" + fontFeature: "\"tnum\" 1" + mono-date: + fontFamily: "Martian Mono, ui-monospace, monospace" + fontSize: "clamp(1.75rem, 1.2rem + 2.2vw, 3.25rem)" + fontWeight: 700 + lineHeight: 1 + letterSpacing: "-0.04em" + fontVariation: "\"wdth\" 87.5" + stamp: + fontFamily: "Martian Mono, ui-monospace, monospace" + fontSize: "1rem" + fontWeight: 700 + lineHeight: 1 + letterSpacing: "0.04em" + fontVariation: "\"wdth\" 112.5" +rounded: + none: "0px" + hair: "2px" +spacing: + "1": "4px" + "2": "8px" + "3": "12px" + "4": "16px" + "5": "20px" + "6": "24px" + "8": "32px" + "12": "48px" + "18": "72px" + "32": "128px" +components: + button-primary: + backgroundColor: "{colors.cherry}" + textColor: "{colors.paper}" + rounded: "{rounded.none}" + padding: "0 24px" + height: "52px" + button-primary-hover: + backgroundColor: "{colors.cherry-deep}" + textColor: "{colors.paper}" + button-secondary: + backgroundColor: "transparent" + textColor: "{colors.ink}" + rounded: "{rounded.none}" + padding: "0 20px" + height: "52px" + button-secondary-hover: + backgroundColor: "{colors.ink}" + textColor: "{colors.cream}" + input: + backgroundColor: "{colors.paper}" + textColor: "{colors.ink}" + rounded: "{rounded.none}" + padding: "0 14px" + height: "52px" + label-card: + backgroundColor: "{colors.kraft}" + textColor: "{colors.ink}" + rounded: "{rounded.none}" + padding: "20px" + ribbon: + backgroundColor: "{colors.cherry}" + textColor: "{colors.paper}" + typography: "{typography.mono-tag}" +--- + +# Design System: Кварта + +## Overview + +**Creative North Star: «Четверг на этикетке»** + +«Кварта» — четвёртая, и весь сайт стоит на четверге. Страница читается как настенный календарь и крафтовая пачка одновременно: неделя ПН…ВС набрана плотным гротеском с тонкими линейками, четверг (обжарка) и пятница (доставка) выделены красным кофейной ягоды, а на пачках и в первом экране стоит резиновый штамп с датой ближайшей обжарки. Свежесть доказывается датой, а не словом «свежий». Ассортимент — этикетки пачек: название гротеском, ноты строкой через «•», вес и цена моноширинным, по диагонали лента-наклейка «обжарено в чт». + +Плотно, плоско, печатно. Никаких теней, градиентов и скруглений: глубину дают линейки, крафт и штамп. Красный — только там, где речь о дне, действии или ошибке. + +**Key Characteristics:** +- Кремовый фон, тёмно-коричневые текст и линейки, один акцент — красный ягоды. +- Golos Text для всего текста, Martian Mono только для дат, цен, веса, часов и штампов. +- Радиус 0. Тени 0. Линейки 1px и 2px цвета `ink`. +- Подпись бренда — штамп даты с «неровной краской» (SVG-фильтр) и лента недели. +- Две авторские анимации: оттиск штампа и «заполнение» недели. Остальное — состояния. + +### Словарь приёмов + +| Приём | Что это | Где используется | +|---|---|---| +| **Лента недели** (`WeekStrip`) | 7 дней «буква + число», тонкие линейки, ЧТ и ПТ красным, подписи «обжарка»/«доставка» на линейке, скобка «1–3 день — в чашке» под ПТ–ВС | Блок «почему свежесть» (полная), OG-картинка (статичная), микро-версия в логотипе (7 штрихов, 4-й красный) | +| **Штамп** (`Stamp`) | Двойная прямоугольная рамка, текст Martian wdth 112.5 капсом, поворот −7…+5°, смещение, неровность краски | Первый экран (дата ближайшей обжарки, анимирован), поле «дата обжарки» на этикетках (статичный), успех формы «ПРИНЯТО» (анимирован), OG | +| **Этикетка** (`Label`) | Крафт-прямоугольник пачки, контент разделён линейками на поля, как на печатной этикетке | Карточки сортов, дрип-пакеты, подписка | +| **Лента-наклейка** (`Ribbon`) | Красная полоса 45° в правом верхнем углу этикетки, «ОБЖАРЕНО В ЧТ» | Только на этикетках ассортимента (4 шт.). Больше нигде | +| **Поле этикетки** | Подпись mono-tag слева, значение справа, линейка 1px снизу | Внутри Label, в блоке подписки, в часах самовывоза | + +## Colors + +Палитра печатной пачки: крем бумаги, крафт, кофейные чернила и одна красная краска. + +### Primary +- **Красный ягоды** (`cherry` #B3221B): основная кнопка, ЧТ/ПТ в ленте недели, штамп, лента-наклейка, линия ошибки. Текст `cherry` допустим на `cream` (5.61:1) и `paper` (6.22:1), но не на `kraft` (4.03:1 — только крупный текст ≥24px или 700 ≥18.66px). +- **Красный глубокий** (`cherry-deep` #8F1A14): hover/active основной кнопки, текст ошибок полей (8.45:1 на `paper`, 7.63:1 на `cream`), красный мелкий текст на крафте (5.47:1). +- **Красный на тёмном** (`cherry-on-dark` #E8604F): акцентный текст и штрихи на `roast` (5.42:1). Только в тёмных секциях. + +### Neutral +- **Крем** (`cream` #F3EBDD): фон страницы. Сплошной, без градиентов и шума. +- **Бумага** (`paper` #FBF7F0): фон полей формы, блока формы, уведомлений; текст на красных кнопках и ленте. +- **Крафт** (`kraft` #DCC7A1): поверхность этикеток и блока подписки. Только сплошной цвет, без текстуры-картинки. +- **Обжарка** (`roast` #1E120C): фон секции «доставка/самовывоз» и подвала. +- **Чернила** (`ink` #2A1810): весь основной текст, линейки, рамки, фокус-обводка на светлом. +- **Приглушённый** (`muted` #5E4636): второстепенный текст, подписи полей этикетки, рамка полей формы. +- **Плейсхолдер** (`placeholder` #7A6352): только `::placeholder` на `paper` (5.27:1). +- **Линейка мягкая** (`rule-soft` #C9B595): декоративные разделители внутри одной группы (1.69:1 к крему — не несёт смысла, не годится для рамок контролов). Фон disabled-кнопки. +- **Приглушённый на тёмном** (`muted-on-dark` #BFA88A): второстепенный текст на `roast` (8.01:1). +- **Успех** (`ok` #2E5E3A): только иконка-галочка и текст в уведомлении «заявка принята» (6.38:1 на `cream`, 7.08:1 на `paper`). Нигде больше. + +### Контраст (WCAG 2.x, проверено) + +| Пара | Контраст | Использование | +|---|---|---| +| ink / cream | 14.34 | основной текст | +| ink / paper | 15.90 | поля, форма | +| ink / kraft | 10.29 | этикетки | +| muted / cream | 7.37 | второстепенный | +| muted / kraft | 5.29 | подписи на этикетке | +| muted / paper | 8.18 | рамка поля (≥3:1 ок) | +| placeholder / paper | 5.27 | плейсхолдер | +| cherry / cream | 5.61 | красный текст, ЧТ/ПТ | +| paper / cherry | 6.22 | основная кнопка, лента | +| paper / cherry-deep | 8.45 | hover кнопки | +| cherry-deep / kraft | 5.47 | красный мелкий текст на крафте | +| cream / roast | 15.46 | текст на тёмном | +| muted-on-dark / roast | 8.01 | второстепенный на тёмном | +| cherry-on-dark / roast | 5.42 | акцент на тёмном | +| ok / paper | 7.08 | успех | + +### Named Rules +**Правило одной краски.** Красный занимает ≤10% экрана. Если на экране уже есть красная кнопка, штамп рядом делается без анимации. +**Правило чёрного по крафту.** На `kraft` мелкий красный текст — только `cherry-deep`. + +### Tailwind 4 `@theme` (вставить в `apps/landing/src/app/globals.css`) + +```css +@import 'tailwindcss'; + +@theme { + --color-*: initial; /* отключить палитру Tailwind: никаких neutral-*/red-* */ + --color-cream: #F3EBDD; + --color-paper: #FBF7F0; + --color-kraft: #DCC7A1; + --color-roast: #1E120C; + --color-ink: #2A1810; + --color-muted: #5E4636; + --color-placeholder: #7A6352; + --color-rule-soft: #C9B595; + --color-cherry: #B3221B; + --color-cherry-deep: #8F1A14; + --color-cherry-on-dark: #E8604F; + --color-muted-on-dark: #BFA88A; + --color-ok: #2E5E3A; + --color-transparent: transparent; + --color-current: currentColor; + + --radius-*: initial; + --radius-none: 0px; + --radius-hair: 2px; + --shadow-*: initial; + + --text-display: clamp(2.75rem, 1.6rem + 5.2vw, 6rem); + --text-display--line-height: 0.92; + --text-display--letter-spacing: -0.035em; + --text-headline: clamp(2rem, 1.4rem + 2.8vw, 3.5rem); + --text-headline--line-height: 1; + --text-headline--letter-spacing: -0.03em; + --text-title: clamp(1.5rem, 1.3rem + 0.6vw, 1.875rem); + --text-title--line-height: 1.05; + --text-title--letter-spacing: -0.02em; + --text-lead: clamp(1.125rem, 1rem + 0.5vw, 1.375rem); + --text-lead--line-height: 1.45; + --text-body: 1.0625rem; + --text-body--line-height: 1.55; + --text-small: 0.875rem; + --text-small--line-height: 1.45; + --text-tag: 0.75rem; + --text-tag--line-height: 1.3; + --text-tag--letter-spacing: 0.08em; + --text-price: clamp(1.375rem, 1.2rem + 0.8vw, 2rem); + --text-price--line-height: 1; + --text-date: clamp(1.75rem, 1.2rem + 2.2vw, 3.25rem); + --text-date--line-height: 1; + + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); + --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); + --ease-in-quart: cubic-bezier(0.5, 0, 0.75, 0); + + --container-page: 1280px; +} + +@theme inline { + --font-sans: var(--font-golos), system-ui, sans-serif; + --font-mono: var(--font-martian), ui-monospace, monospace; +} + +@layer base { + html { background: var(--color-cream); color: var(--color-ink); scroll-padding-top: 16px; } + body { font-family: var(--font-sans); font-size: var(--text-body); line-height: 1.55; -webkit-tap-highlight-color: transparent; } + ::selection { background: var(--color-cherry); color: var(--color-paper); } + :focus-visible { outline: 2px solid var(--color-ink); outline-offset: 3px; } + .on-dark :focus-visible { outline-color: var(--color-cream); } + input, textarea { caret-color: var(--color-cherry); } + a { text-decoration-thickness: 1px; text-underline-offset: 3px; } +} +``` + +После `--color-*: initial` существующие `border-neutral-*`, `text-red-700` в `LeadForm.tsx` перестанут работать — классы заменить на токены при вёрстке (только `className`, логику и data-атрибуты не трогать). + +## Typography + +**Display/Body:** Golos Text (variable, wght 400–900), `--font-golos`. +**Mono:** Martian Mono (variable, wght 100–800, wdth 75–112.5), `--font-martian`. + +```ts +// app/layout.tsx +import { Golos_Text, Martian_Mono } from 'next/font/google'; +const golos = Golos_Text({ subsets: ['cyrillic', 'latin'], variable: '--font-golos', display: 'swap' }); +const martian = Martian_Mono({ subsets: ['cyrillic', 'latin'], variable: '--font-martian', axes: ['wdth'], display: 'swap' }); +// +``` + +**Character:** плотный гротеск с тяжёлыми заголовками, как на настенном календаре; моно — как цифры датера и весов. Курсива нет вообще, serif нет вообще. + +### Hierarchy (mobile 390 → desktop 1440) + +| Роль | Шрифт | Размер 390 → 1440 | LH | Tracking | Вес | Где | +|---|---|---|---|---|---|---| +| display | Golos | 44 → 96px | 0.92 | −0.035em | 900 | только H1 первого экрана; `text-wrap: balance` | +| headline | Golos | 32 → 56px | 1.0 | −0.03em | 800 | H2 секций | +| title | Golos | 24 → 30px | 1.05 | −0.02em | 800 | название сорта, «Доставка»/«Самовывоз», H3 | +| lead | Golos | 18 → 22px | 1.45 | 0 | 400 | подзаголовок под H1/H2, ≤ 46ch | +| body | Golos | 17px | 1.55 | 0 | 400 (500 — выделение) | абзацы, ≤ 62ch | +| small | Golos | 14px | 1.45 | 0 | 500 | регион сорта, примечания, ошибки полей | +| label (UI) | Golos | 15px | 1.3 | 0 | 600 | подписи полей, кнопки (кнопки 16px/700) | +| tag | Martian | 12px | 1.3 | 0.08em | 500, wdth 100, CAPS | подписи полей этикетки, ноты, «обжарка»/«доставка» | +| price | Martian | 22 → 32px | 1.0 | −0.02em | 600, wdth 100, `tnum` | цены «32 BYN», вес «250 г» (вес — 16px/500) | +| date | Martian | 28 → 52px | 1.0 | −0.04em | 700, **wdth 87.5** | числа в ленте недели | +| day-letter | Golos | 14 → 16px | 1 | 0.04em | 800, CAPS | «ПН», «ВТ» в ленте | +| stamp | Martian | 14–22px (по размеру штампа) | 1 | 0.04em | 700, **wdth 112.5**, CAPS | текст штампа | +| hours | Martian | 15px | 1.4 | 0 | 500, wdth 100, `tnum` | часы самовывоза, телефон | + +`wdth` задавать через `font-stretch: 87.5%` / `112.5%`. Если next/font не объявит диапазон `font-stretch`, использовать `font-variation-settings: "wdth" 87.5` (вес продолжает задаваться `font-weight`). + +### Named Rules +**Правило моно-фактов.** Martian Mono — только для чисел, дат, веса, цен, часов, телефона и коротких CAPS-меток. Предложения и заголовки моно не набираются. +**Правило без сплошного капса.** CAPS — только tag, day-letter, stamp и лента-наклейка. Заголовки — в предложном регистре. + +## Layout + +- **Mobile-first.** Брейкпоинты Tailwind по умолчанию: `md` 768 (две колонки), `lg` 1024 (12-колоночная сетка), `xl` 1280. +- **Сетка:** 390 — 4 колонки, гаттер 16px, поля 20px. ≥1024 — 12 колонок, гаттер 24px, поля 48px, контейнер `max-w-page` 1280px по центру (на 1440 поля ≈ 80px). +- **Ритм секций:** padding-y 72px (390) → 128px (1440). Отступ над H2 больше, чем под ним: H2 → лид 16px, лид → контент 40px (390) / 64px (1440). +- **Разделение секций:** не фоном-плашкой, а линейкой 2px `ink` по ширине контейнера сверху секции + сменой поверхности только у тёмного блока и формы. +- **Шкала отступов:** 4, 8, 12, 16, 20, 24, 32, 48, 72, 128. + +## Elevation & Depth + +Плоско. `box-shadow` не используется нигде (токены теней обнулены). Глубину дают: смена поверхности (cream → kraft → paper → roast), линейки 1/2px, наложение штампа поверх фото и края колонки, лента-наклейка, срезанная краем этикетки (`overflow: hidden`). + +**Правило печати.** Если хочется тени — нужна линейка или смена поверхности. + +## Shapes + +- Радиус 0 у всего: кнопки, поля, этикетки, фото, уведомления, фокус-обводка. `hair` 2px — запас для мелких SVG-квадратиков (маркер выбранного интереса), больше нигде. +- Линейки: `1px ink` — внутри этикеток и ленты недели; `1.5px` — рамки кнопок secondary и полей формы (`muted`, в фокусе/ошибке 2px); `2px ink` — верх секций, рамка штампа (внешняя 2px + внутренняя 1px, зазор 3px), рамка блока формы. +- Перфорация дрип-пакета: верхний край — SVG пунктир (dash 6, gap 4, 1px ink). + +## Components + +### Stamp +Резиновый оттиск даты. Inline SVG, чтобы фильтр краски жил внутри SVG, а анимировалась обёртка. +- **Props:** `lines: string[]` (1–3 строки, CAPS), `size: 'sm' | 'md' | 'lg'` (ширина 112 / 168 / 232px), `rotate` (−7…+5°, по умолчанию −6), `animate?: boolean`, `tone: 'cherry' | 'cherry-on-dark'`. +- **Вид:** двойная рамка (2px снаружи, 1px внутри, зазор 3px), цвет `cherry`, `opacity: 0.92`; текст Martian 700, wdth 112.5; строки разделены линейкой 1px. Смещение «мимо приводки»: внутренний текст сдвинут на `translate(1px, -1px)` относительно рамки. +- **Неровность краски:** один общий `` в layout: `feTurbulence type=fractalNoise baseFrequency=0.9 numOctaves=2 seed=4` → `feDisplacementMap scale=1.6` (дрожание края) + вторая `feTurbulence baseFrequency=0.06` → `feColorMatrix` в альфу → `feComposite operator=in` (пропуски краски ~10%). Фильтр статичен, не анимируется. +- **Экземпляры:** hero — `lg`, строки «ОБЖАРКА» / «ЧТ 22.10» (дата вычисляется) / «МИНСК»; этикетка — `sm`, «ЧТ 22.10»; успех формы — `md`, «ПРИНЯТО» и «ДОПИСАЛИ» для `clarified`. +- **Доступность:** SVG `aria-hidden`, рядом визуально скрытый текст «Ближайшая обжарка — четверг, 22 октября». + +### Дата ближайшей обжарки +`lib/roastWeek.ts`: `getRoastWeek(now: Date)` в часовом поясе Europe/Minsk → `{ days: {letter, date, iso}[7] (пн–вс), roast: четверг (сегодня, если сегодня чт, иначе следующий), delivery: roast + 1 }`. Страница — серверный компонент с `export const revalidate = 3600`. Формат: `ЧТ 22.10`. Это следует из факта «обжарка по четвергам», ничего не добавляет. + +### WeekStrip +- **390 (вертикально, «дни столбиком»):** 7 строк по 48px. Строка: слева колонка 64px — day-letter над числом date; справа линейка 1px `ink` по базовой линии, на ней tag-подпись. ЧТ: буква и число `cherry`, подпись «ОБЖАРКА · ПАРТИЯ 5 КГ». ПТ: `cherry`, «ДОСТАВКА ПО МИНСКУ». СБ, ВС и ПТ справа охвачены вертикальной скобкой 2px `cherry` с подписью «1–3 ДЕНЬ — ЗЕРНО У ВАС». Остальные дни `muted`, без подписи. +- **1440 (горизонтально):** 7 равных колонок на 12-колоночной сетке, высота ~200px. Сверху day-letter, под ним date (52px), снизу общая линейка 1px через всю ширину; подписи tag под линейкой в колонках ЧТ и ПТ. Под ПТ–ВС — горизонтальная скобка (2px, загнутые концы 8px) с подписью «1–3 ДЕНЬ ПОСЛЕ ОБЖАРКИ — ЗЕРНО У ВАС». Вертикальные разделители колонок — 1px `rule-soft`. +- **Разметка:** `
    ` из 7 `
  1. `; у ЧТ и ПТ подпись — текст в `
  2. `, не только цвет. Числа в `