Штамп первого экрана на непрозрачной подложке, цифры под мягким
фильтром, зазор кнопка→фото 40px. Ленты-наклейки сняты, у штампов
на этикетках разные повороты и сдвиги. Ряд цифр в «Кто обжаривает»
заменён одной tag-строкой. Дескрипторы сортов — поля этикетки,
«в месяц» после цены набрано Golos. Дрип-пакеты на десктопе: три зоны
по центру с вертикальными линейками. Доставка в колонках 7–12, на
мобиле без фото мешка. Интерес в форме — радиокнопки. Хелпер typo:
неразрывные пробелы и дефисы. Скрипт скриншотов: кадр 390 со скроллом.
opengraph-image и twitter-image 1200×630 через next/og: знак, заголовок,
штамп «ЧТ · обжарка», лента недели с ЧТ/ПТ красным, без фото и дат.
Статичные TTF Golos Text и Martian Mono для Satori. Метаданные из
site.meta: title, description, openGraph, twitter, metadataBase
kvarta.unuchak.com, canonical. Иконка — SVG-знак из семи штрихов.
Оттиск штампа при загрузке и при успехе формы, заполнение ленты
недели по IntersectionObserver, мигающие точки при отправке.
Только CSS и transform/opacity; анимации включает скрипт в head,
поэтому без JS и при prefers-reduced-motion всё сразу в финале.
Липкая CTA на мобиле: «Заказать кофе», прячется у формы, при фокусе
в ней и при открытой клавиатуре. Ритм секций плотнее.
Шрифты Golos Text и Martian Mono через next/font, токены @theme,
все блоки по плану: первый экран со штампом даты ближайшей обжарки,
лента недели, этикетки сортов, подписка, кто мы, доставка, форма,
подвал. Три фото Unsplash локально, priority только у первого экрана.
LeadForm перекрашена под токены, логика и data-атрибуты те же.
В site.ts и form.ts добавлены служебные подписи для вёрстки.
С VPS api.telegram.org недоступен. deploy/tg-relay: nginx на адресе
Tailscale домашнего сервера, пропускает только запросы нашего бота из
tailnet, без логов доступа (токен в URL). api: базовый URL Bot API из
TELEGRAM_API_BASE; сетевые сбои polling пишутся в лог раз в 10 минут.
site.ts: все блоки страницы только из фактов ТЗ. form.ts: правки,
принятые автором (без «помола», понятнее успех, уточнение и 429).
slopotron: убраны повторы «обжариваем по четвергам» и «пока обжарка
свежая» между первым экраном и блоком свежести.
backup.ts: db.backup() → gzip в backups/ рядом с базой, имя по времени
Минска (Intl), 14 ежедневных и pre-restore на 30 дней; планировщик
в 03:00 по Минску и при старте, если сегодня копии нет; файл админу,
итог в «Систему». Восстановление: gunzip → integrity_check → версия
схемы → пауза приёма (503) → pre-restore → атомарная подмена файла →
миграции; при сбое возвращаем прежнюю базу. createApp и очередь берут
базу через геттер, чтобы увидеть подменённую.
Бот в личке: /backups с кнопками, /backup, присланный .sqlite.gz,
подтверждение со списком пропадающих заявок, живёт 10 минут.
telegram/client: Bot API на fetch с таймаутом 5 с; ошибки делятся на
повторяемые (сеть, таймаут, 429, 5xx) и конфигурационные (400/403…),
токен не попадает ни в тексты ошибок, ни в tg_last_error.
format: заявка и уточнение по шаблону плана, HTML с экранированием,
источник из utm / referrer, первый визит.
outbox: Notifier с отправкой после ответа и тиком раз в минуту, пауза
1, 2, 4… мин, уточнение только после своей заявки, после 10 попыток —
в «Систему». system: тема «Система» с троттлингом ×N за период.
bot: long polling, только TELEGRAM_ADMIN_ID, /setup создаёт темы и
присылает id в личку. config: всё окружение в одном месте.
rateLimit.ts: 5 запросов / 10 минут на IP в памяти, фиксированное окно,
чистка устаревших записей по ходу запросов, 429 с Retry-After. IP из
X-Real-IP только от loopback-сокета (плюс TRUSTED_PROXIES для шлюза
docker), иначе адрес сокета; в базу IP не пишем. Honeypot → 201 без
записи и уведомления. isPaused() → 503 на время восстановления базы.
Тесты на каждый код ответа из таблицы плана.
saveLead в одной IMMEDIATE-транзакции: заявка с тем же телефоном
моложе 10 минут → уточнение, если имя, интерес или комментарий
отличаются от последней версии, иначе повтор без записи. Окно от
created_at заявки, уточнения его не продлевают. Ответ 200 duplicate,
notifier получает id уточнения и список изменившихся полей.
db/: better-sqlite3 по DB_PATH (WAL, foreign_keys), миграции через
PRAGMA user_version, схема leads и lead_clarifications по плану.
createApp(deps) с инъекцией базы, часов и notifier (пока no-op, зовётся
после ответа). POST /api/lead: только JSON (415), тело до 16 КБ (413),
validateLead → 400, запись → 201. index.ts: env и graceful shutdown.