Каталог на 320 позиций: фасетный поиск по русской морфологии целиком в браузере и расчёт раскладки плитки по нормам СП. Статика — на сервере не появляется ни одного процесса.
→ Открыть: stroy.lstmind.ru
Демонстрационная работа для портфолио, а не действующий магазин: компания, ассортимент и цены вымышлены. Что здесь настоящее и что придумано — на странице «О проекте» и в ATTRIBUTION.md.
Автор — Алексей, веб-разработчик · lstmind.ru
Поиск понимает, как люди пишут. Падежи, опечатки, латиница, забытая
раскладка, профессиональные аббревиатуры: «ломинат», «kfvbyfn», «гкл
влагостойкий», «кг 600», «эппс». Индекс MiniSearch сериализуется на сборке и
весит 11,9 КБ brotli; стеммер — русский Snowball из @orama/stemmers.
Поверх стеммера — доменный словарь на 156 строк, и он там не для красоты.
Snowball схлопывает падежи, но не схлопывает существительное и относительное
прилагательное: «плитка» → плитк, а «плиточный» → плиточн. Расстояние три,
fuzzy не спасает и не должен. Лемматизатор решил бы задачу и потянул словарь
на мегабайты — в браузер такое не грузят.
Транслитерация и починка раскладки включаются только как запасной вариант, если основной проход дал меньше трёх результатов: иначе короткие токены превращаются в мусор и тащат в выдачу чужие категории.
Фасеты дизъюнктивные. Счётчики группы считаются со всеми фильтрами, кроме фильтров самой этой группы, — поэтому мультивыбор внутри группы работает, а у невыбранных значений остаются живые числа. Всё за один проход по типизированным массивам, без аллокаций на кадр. Спиннеров нет намеренно: пересчёт занимает доли миллисекунды, и любой лоадер читался бы как имитация работы.
Каталог фильтруется и с отключённым JavaScript: каждый фасет — ссылка на статически собранную страницу одного фильтра.
Расчёт считает раскладку, а не площадь. Симметрирует ряды, держит правило «подрезка не уже половины плитки», учитывает шов между полем и подрезкой, знает про выход двух кусков из одной плитки только с противоположных краёв одной оси — и про то, что направленный рисунок это правило отменяет. Клей по высоте гребёнки, обратное намазывание отдельным слагаемым, а не множителем. Затирка по формуле, оба контрольных примера зафиксированы тестами до третьего знака. Округление до упаковок происходит один раз и в конце. Переплата показывается всегда.
Схема рисуется одним SVG-паттерном для поля целых плиток и индивидуально только для подрезок. Иначе ванная, выложенная мозаикой, — это десять тысяч узлов, и бюджетный Android умирает на первом перетаскивании.
Astro 7 в режиме чистой статики, острова на Svelte 5 (руны), Tailwind v4 для утилит, TypeScript, Vitest. React отсутствует полностью — это второе демо в портфолио, и оно доказывает гибкость после первого на Next.js.
Данные — YAML в репозитории. Сборка валидирует ссылки и реестр характеристик, детерминированно генерирует артикулы, считает упаковку и вес из размеров и плотности. Ни одна упаковка не вбита руками.
npm install
npm run dev # сборка данных + astro dev
npm run build # dist/, 609 страниц
npm test # 79 тестов: расчёт, развёртка стен, поиск, фасеты
npm run precompress # .br и .gz сайдкары — без них brotli-цифры вымышлены
npm run budget # замер бюджетов, а не «на глаз»
npm run deploy # rsync → симлинк → health-check → откат при сбоеАссеты тянутся отдельными командами и коммитятся, поэтому обычная сборка не ходит в сеть:
npm run fonts # woff2-подмножества по алфавитам + реестр
npm run textures # 89 фактур CC0, кроп по физическому размеру плиты
npm run photos # 21 фотография: первый кадр, кадры страниц, предметная съёмка
npm run forms # 5 чертежей упаковки — запас, если кадра нет
npm run social # og.jpg 1200×630 и apple-touch-iconИзображения устроены в три слоя, и это не эстетика, а разные обещания.
Фактуры — поверхность материала, вырезанная по физическому размеру: провайдер сообщает, сколько миллиметров покрывает текстура, и кроп считается от этого числа. Плита 600 мм выглядит плитой 600 мм.
Предметная съёмка — то, у чего фактуры нет: гребёнка клея, торец штабеля
листов, россыпь саморезов. Кадры раздаются коллекциям по кругу и по
существу: у кадра в реестре стоит match, и каменная вата не может показаться
пенополистиролом. Ни одного логотипа и ни одной брендированной упаковки —
кадры с читаемой этикеткой отбракованы.
Чертежи упаковки — запас на случай, когда пригодного кадра нет: линия, штриховка, миллиметровая сетка в языке схемы раскладки.
search.json |
11,9 КБ brotli (бюджет 14) |
catalog.json |
9,3 КБ brotli (бюджет поднят с 9 до 10, см. ниже) |
| JS островов | 58,1 КБ gzip (бюджет 60) |
| Страниц в сборке | 609 |
| Процессов Node на сервере | 0 |
Два честных отклонения от плана:
facets.json— 6,4 КБ brotli вместо оценки 2–3 КБ. Оценка не учитывала, что реестр характеристик хранит подписи, единицы и подсказки для 69 полей. Файл грузится лениво, вместе с каталогом, и на первый экран не влияет.catalog.json— 9,3 КБ вместо 9. Позиции разложены витринным порядком (по кругу между коллекциями), а не пачками, поэтому длинные серии одинаковых идентификаторов, на которых экономил brotli, разорваны. Плата за то, что первый экран категории не выглядит четырьмя кадрами одной фактуры подряд.
- Ни одного реального бренда и ни одного логотипа в кадре. Все 13 производителей придуманы. Фотографии — лицензии Pexels, Unsplash, CC0 и CC-BY-SA; кадры с читаемой этикеткой или маркой на инструменте отбракованы.
- Персональные данные не собираются вообще: нет формы с именем и телефоном, поэтому 152-ФЗ не возникает — ни оператора, ни политики, ни галочки согласия.
- Каталог и карточки отдают
noindex, follow. Микроразметка ограниченаWebSiteиBreadcrumbList: отдавать поисковикамProductиOfferс вымышленными ценами — прямой риск для домена, который кормит. - Выдуманные названия остаётся проверить по базам ФИПС (классы 19 и 35) и ЕГРЮЛ — это ручной шаг, и он ещё не сделан.
- PRODUCT.md — что это за продукт и для кого
- DESIGN.md — визуальный контракт: мир, палитра, правила
- ATTRIBUTION.md — источники и лицензии
- PLAN.md — исходный технический план
