Промо-страница для Pinch, Telegram Mini App для знакомств. Бот: @pinchdatingbot
Сам Pinch — отдельный проект, живёт внутри Telegram. В этой репе только лендинг к нему.
Next.js 16 (App Router), React 19, TypeScript, SCSS-модули. Без Tailwind и
без UI-китов — вся вёрстка руками, на CSS Grid и Flexbox. Шрифты через
next/font, картинки через next/image. Анимации только на CSS и SVG.
Нужен Node 20.9+.
npm install
npm run dev # http://localhost:3000
npm run build # прод-сборка
npm run start
Лендинг сделан в том же языке рендеринга, что и само приложение: рисованные чернильные панели, печатные стикеры, растр Бен-Дэя (полутоновые точки), смещённые цветовые прогоны и жёсткие тени без блюра. Правила, которым подчиняется вся страница:
- Никакой случайности. Ни одного
Math.random(): все дрожания, разрывы и повороты заданы вручную — фиксированные пути,stroke-dasharray, маски,nth-child. Разметка побайтово стабильна между рендерами. - Геометрия ровная, рендер — нет. Обрезка фото, попадания по кнопкам и капсулы остаются математически чистыми; «рисованность» живёт в отдельных декоративных слоях поверх и вокруг.
- Разделение CSS и SVG. CSS отвечает за поверхности, точечные поля, маски, печатные смещения и состояния. Inline SVG — за каждую «выразительную» форму: волнистые контуры, вспышки, искры, молнии, кольца. Выразительную форму никогда не подделываем ровным CSS-полигоном.
- Иерархия текстуры. Фоновая атмосфера < материал компонента < декоративные акценты. Максимум интенсивности никогда не стоит везде.
- Сдержанность вокруг контента. Фото, имена и основной текст остаются чистыми, полутон всегда гаснет до того, как дойдёт до букв.
styles/variables.scss токены обеих схем (тёмные чернила + бумага)
styles/mixins.scss sticker(), print(), dots(), mask(), wavy(), marker()
styles/ink.scss покраска авторских SVG + рамки border-image
styles/parts.scss кнопки, теги, панели, типографика
components/ink/ SVG-инструментарий, лого-локап, переключатель темы
components/ секции лендинга, у каждой свой .module.scss
lib/theme.ts состояние схемы; lib/themeScript.ts — стамп до отрисовки
public/screens скриншоты приложения, по паре на каждую схему
public/people фото для карточек в секции «Кто в ленте»
Секции по порядку: hero, вайб, возможности, как это работает, кто в ленте, что внутри, скриншоты, финальный CTA.
Тёмные чернила — основная схема (как в проде). Переключатель в шапке
(рисованный стикер солнце/луна) поднимает бумажную: тёплый газетный фон,
чёрный контур, кобальтовая структура, коралловые микро-акценты. Схема
сохраняется в localStorage и ставится инлайновым скриптом до первой
отрисовки, поэтому вспышки не будет. Скриншоты приложения переключаются
вместе с ней — грузится только та пара, что видна.
mask-imageвсегда пишется вместе с-webkit-близнецом.background:шорткат сбрасываетbackground-clip— после негоpadding-boxнадо повторить, иначе заливка вылезет за волнистую рамку.border-imageтолько сround:stretchрасплющивает волны на широких кнопках.- Общий
<mask>требуетmaskUnits="userSpaceOnUse"и явныхx/y/w/h, иначе область маски считается от каждого элемента отдельно. - Размеры тегов и «принтов» задаются переменными, а не переопределением: глобальный файл и CSS-модуль имеют одинаковую специфичность, и порядок их подключения не гарантирован.
- Цветовой прогон иконки обводится шире чернильного — иначе на тёмном фоне силуэт «худеет» на пол-штриха.
- Заливка стикера — это путь внутри SVG, а не фон элемента: скруглённый прямоугольник вылезает за волнистую линию по углам.
Продукт написан на JavaScript:
- фронт — Telegram Mini App на Vite
- бэк — Node.js + Express, REST API
- база — PostgreSQL
- медиа — S3 + Sharp
- модерация фото — TensorFlow.js (nsfwjs)
- авторизация через Telegram init data, Sentry, Docker
На лендинге это собрано в блоке «Что внутри». Скриншоты в public/screens
сняты с приложения, анкеты на них — заглушки.