[{"data":1,"prerenderedAt":1295},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:ru":3,"api:\u002Fapi\u002Fprofile:":8,"api:\u002Fapi\u002Fprojects:ru":19,"api:\u002Fapi\u002Fcategories:ru":1061,"api:\u002Fapi\u002Fstats:":1138},{"email":4,"lang":5,"name":6,"role":7},"djrciv@gmail.com","ru","Ewsirdev","Веб-разработчик: сайты, сервисы и автоматизация под ключ",{"name":6,"handle":6,"contacts":9,"note":18},{"email":10,"github":12,"telegram":15},{"url":11,"label":4},"mailto:djrciv@gmail.com",{"url":13,"label":14},"https:\u002F\u002Fgithub.com\u002Fewsir247","github.com\u002Fewsir247",{"url":16,"label":17},"https:\u002F\u002Ft.me\u002FEwsirdev","@Ewsirdev","Contacts: Telegram, GitHub, email. This file is the single source of contacts for the site, the contact block, JSON-LD sameAs, security.txt and llms.txt.",[20,47,65,82,105,123,140,160,179,200,226,250,275,303,330,356,377,402,427,453,469,487,506,526,547,567,587,610,631,648,666,690,710,728,747,768,786,806,825,845,865,887,906,925,943,963,982,1002,1021,1041],{"slug":21,"title":22,"type":23,"category":23,"lang":24,"kind":25,"live_url":26,"stack":27,"description":30,"highlights":31,"run":36,"hue":37,"og":38,"shots":39},"kanban-tasks","Янтарь: канбан-доска","backend","Go","api","https:\u002F\u002Fkanban-tasks.ewsir.space",[24,28,29],"SQLite","Docker","Многопользовательская канбан-доска: регистрация, доски, колонки, карточки с перетаскиванием. Go + SQLite без CGO, фронт встроен в бинарник.",[32,33,34,35],"PBKDF2 и сессии в HttpOnly-cookie","Изоляция данных пользователей","Тесты API","Docker multi-stage","cd kanban-tasks && go run .",344,"\u002Fog\u002Fkanban-tasks.jpg",{"desktop":40,"desktop_sm":41,"mobile":42,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fkanban-tasks-desktop.webp","\u002Fshots\u002Fkanban-tasks-desktop-sm.webp","\u002Fshots\u002Fkanban-tasks-mobile.webp",1280,800,390,844,{"slug":48,"title":49,"type":23,"category":23,"lang":24,"kind":25,"live_url":50,"stack":51,"description":52,"highlights":53,"run":58,"hue":59,"og":60,"shots":61},"link-shortener","Короче: сократитель ссылок","https:\u002F\u002Flink-shortener.ewsir.space",[24,28,29],"Сокращение ссылок с алиасами и сроком жизни, статистика кликов по дням, браузерам и устройствам, дашборд под токеном. IP не хранятся.",[54,55,56,57],"Редирект и учёт кликов","Лимит запросов","Приватная статистика (хэш с солью)","Тесты и Docker","cd link-shortener && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",111,"\u002Fog\u002Flink-shortener.jpg",{"desktop":62,"desktop_sm":63,"mobile":64,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flink-shortener-desktop.webp","\u002Fshots\u002Flink-shortener-desktop-sm.webp","\u002Fshots\u002Flink-shortener-mobile.webp",{"slug":66,"title":67,"type":23,"category":23,"lang":24,"kind":25,"live_url":68,"stack":69,"description":70,"highlights":71,"run":75,"hue":76,"og":77,"shots":78},"booking-slots","Запись на приём","https:\u002F\u002Fbooking-slots.ewsir.space",[24,28,29],"Онлайн-запись на услуги: свободные слоты с учётом длительности и рабочих часов, защита от двойной записи, админ-панель. Вымышленная студия, демо.",[72,73,74,57],"Расчёт свободных слотов","Без двойных записей","Админка для услуг и расписания","cd booking-slots && go run .",65,"\u002Fog\u002Fbooking-slots.jpg",{"desktop":79,"desktop_sm":80,"mobile":81,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fbooking-slots-desktop.webp","\u002Fshots\u002Fbooking-slots-desktop-sm.webp","\u002Fshots\u002Fbooking-slots-mobile.webp",{"slug":83,"title":84,"type":85,"category":86,"live_url":87,"stack":88,"description":92,"highlights":93,"run":98,"hue":99,"og":100,"shots":101},"landing-studio","Полутон: студия света","landing","landings","https:\u002F\u002Fewsirdev-studio.pages.dev",[89,90,91],"HTML","CSS","JS","Лендинг вымышленной студии интерьерного света: тёплая палитра, засечный шрифт, SVG-иллюстрация, FAQ и демо-форма. Адаптивный, без внешних картинок.",[94,95,96,97],"Адаптив 390-1280","SVG вместо картинок","Доступные FAQ и форма","Честная демо-форма","cd landing-studio && python -m http.server 8091",212,"\u002Fog\u002Flanding-studio.jpg",{"desktop":102,"desktop_sm":103,"mobile":104,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flanding-studio-desktop.webp","\u002Fshots\u002Flanding-studio-desktop-sm.webp","\u002Fshots\u002Flanding-studio-mobile.webp",{"slug":106,"title":107,"type":85,"category":86,"live_url":108,"stack":109,"description":110,"highlights":111,"run":116,"hue":117,"og":118,"shots":119},"landing-saas","Рубильник: трекер подписок","https:\u002F\u002Fewsirdev-saas.pages.dev",[89,90,91],"Лендинг вымышленного SaaS-продукта: тёмный градиентный hero, макет интерфейса на CSS, тарифы, FAQ, демо-форма. Все цены и названия придуманы.",[112,113,114,115],"Макет интерфейса на CSS","Тарифная сетка","Адаптив","Демо-форма без отправки данных","cd landing-saas && python -m http.server 8092",182,"\u002Fog\u002Flanding-saas.jpg",{"desktop":120,"desktop_sm":121,"mobile":122,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flanding-saas-desktop.webp","\u002Fshots\u002Flanding-saas-desktop-sm.webp","\u002Fshots\u002Flanding-saas-mobile.webp",{"slug":124,"title":125,"type":85,"category":86,"live_url":126,"stack":127,"description":128,"highlights":129,"run":133,"hue":134,"og":135,"shots":136},"landing-event","Малый масштаб: конференция","https:\u002F\u002Fewsirdev-event.pages.dev",[89,90,91],"Лендинг вымышленной однодневной конференции: плакатная типографика, расписание, спикеры-заглушки, условия, демо-форма записи.",[130,131,114,132],"Расписание дня","Плакатная типографика","Без выдуманных имён спикеров","cd landing-event && python -m http.server 8093",124,"\u002Fog\u002Flanding-event.jpg",{"desktop":137,"desktop_sm":138,"mobile":139,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flanding-event-desktop.webp","\u002Fshots\u002Flanding-event-desktop-sm.webp","\u002Fshots\u002Flanding-event-mobile.webp",{"slug":141,"title":142,"type":23,"category":23,"lang":24,"kind":143,"live_url":144,"stack":145,"description":148,"highlights":149,"run":153,"hue":154,"og":155,"shots":156},"uptime-monitor","Пульс: монитор доступности","infra","https:\u002F\u002Fuptime-monitor.ewsir.space",[24,28,146,147,29],"SSE","REST API","Монитор доступности: HTTP и TCP проверки по расписанию, аптайм, p50\u002Fp95, детектор инцидентов, живой дашборд на SSE и публичная статус-страница. Go + SQLite без CGO.",[150,151,152,57],"Горутина-воркер на каждую проверку","Инциденты после серии падений","Дашборд на SSE и публичный \u002Fstatus","cd uptime-monitor && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",101,"\u002Fog\u002Fuptime-monitor.jpg",{"desktop":157,"desktop_sm":158,"mobile":159,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fuptime-monitor-desktop.webp","\u002Fshots\u002Fuptime-monitor-desktop-sm.webp","\u002Fshots\u002Fuptime-monitor-mobile.webp",{"slug":161,"title":162,"type":23,"category":23,"lang":24,"kind":143,"live_url":163,"stack":164,"description":166,"highlights":167,"run":172,"hue":173,"og":174,"shots":175},"job-queue","Конвейер: очередь задач","https:\u002F\u002Fjob-queue.ewsir.space",[24,28,147,146,165,29],"Prometheus","Очередь фоновых задач: приоритеты, отложенные и cron-задачи, ретраи с backoff и jitter, dead-letter, идемпотентные ключи, lease с fencing, метрики Prometheus.",[168,169,170,171],"At-least-once с lease и fencing","Ретраи с backoff и jitter + dead-letter","Собственный cron-парсер","Метрики Prometheus и graceful shutdown","cd job-queue && set API_TOKEN=demo&& set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",249,"\u002Fog\u002Fjob-queue.jpg",{"desktop":176,"desktop_sm":177,"mobile":178,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fjob-queue-desktop.webp","\u002Fshots\u002Fjob-queue-desktop-sm.webp","\u002Fshots\u002Fjob-queue-mobile.webp",{"slug":180,"title":181,"type":23,"category":23,"lang":24,"kind":182,"live_url":183,"stack":184,"description":187,"highlights":188,"run":193,"hue":194,"og":195,"shots":196},"realtime-rooms","Комнаты: чат и доска в реальном времени","realtime","https:\u002F\u002Frealtime-rooms.ewsir.space",[24,185,28,186,29],"WebSocket","Canvas","Комнаты в реальном времени: чат с историей в SQLite, список «кто онлайн», индикатор «печатает», общая доска со штрихами и курсорами, анонимные ники. WebSocket (RFC 6455) написан на stdlib.",[189,190,191,192],"Свой слой WebSocket (RFC 6455) на stdlib","Отключение медленных клиентов и лимиты частоты","Graceful shutdown с кодом 1001","Нагрузочный тест: 360 соединений в 6 комнатах","cd realtime-rooms && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",46,"\u002Fog\u002Frealtime-rooms.jpg",{"desktop":197,"desktop_sm":198,"mobile":199,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Frealtime-rooms-desktop.webp","\u002Fshots\u002Frealtime-rooms-desktop-sm.webp","\u002Fshots\u002Frealtime-rooms-mobile.webp",{"slug":201,"title":202,"type":23,"category":23,"lang":24,"kind":143,"live_url":203,"stack":204,"description":213,"highlights":214,"run":219,"hue":220,"og":221,"shots":222},"api-gateway","Шлюз: API-gateway","https:\u002F\u002Fapi-gateway.ewsir.space",[24,205,206,207,208,146,165,209,210,211,29,212],"stdlib only","Reverse proxy","Circuit breaker","Token bucket","slog","Vanilla JS","embed","GitHub Actions","API-шлюз на чистой stdlib: маршруты по хосту\u002Fпрефиксу из JSON-конфига с горячей перезагрузкой без обрыва соединений, балансировка round-robin и least-conn, health-checks, circuit breaker на узел, ретраи идемпотентных запросов, rate limit, хэшированные API-ключи, метрики Prometheus, живой дашборд на SSE.",[215,216,217,218],"Ноль зависимостей: только stdlib","Горячая перезагрузка конфига без обрыва соединений","Circuit breaker на узел и ретраи идемпотентных запросов","Нагрузочный тест: 4000 запросов, 40 клиентов (демо-замер)","cd api-gateway && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fgateway",200,"\u002Fog\u002Fapi-gateway.jpg",{"desktop":223,"desktop_sm":224,"mobile":225,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fapi-gateway-desktop.webp","\u002Fshots\u002Fapi-gateway-desktop-sm.webp","\u002Fshots\u002Fapi-gateway-mobile.webp",{"slug":227,"title":228,"type":23,"category":23,"lang":24,"kind":229,"live_url":230,"stack":231,"description":237,"highlights":238,"run":243,"hue":244,"og":245,"shots":246},"notes-search","Картотека: заметки с поиском","data","https:\u002F\u002Fnotes-search.ewsir.space",[24,28,232,233,234,147,235,236,210,211,29,212],"FTS5","bm25","modernc.org\u002Fsqlite (без CGO)","Markdown (свой безопасный рендер)","SVG force-layout","База Markdown-заметок на Go и SQLite: вики-ссылки [[заметка]] с обратными ссылками и графом связей, полнотекстовый поиск FTS5 (bm25, подсветка, упрощённая русская морфология), история версий с собственным diff, автосохранение с разрешением конфликтов, экспорт и импорт .md.",[239,240,241,242],"Поиск FTS5 с bm25 и подсветкой","Вики-ссылки, backlinks и граф связей","История версий с собственным diff","Свой безопасный Markdown-рендер с тестом на XSS","cd notes-search && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",160,"\u002Fog\u002Fnotes-search.jpg",{"desktop":247,"desktop_sm":248,"mobile":249,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fnotes-search-desktop.webp","\u002Fshots\u002Fnotes-search-desktop-sm.webp","\u002Fshots\u002Fnotes-search-mobile.webp",{"slug":251,"title":252,"type":85,"category":86,"live_url":253,"stack":254,"description":261,"highlights":262,"run":268,"hue":269,"og":270,"shots":271},"landing-3d","ОКСО Т1: 3D-лендинг","https:\u002F\u002Fewsirdev-3d.pages.dev",[255,256,210,257,258,259,260,212],"HTML5","CSS3","WebGL2","GLSL ES 3.00","Raymarching\u002FSDF","Scroll choreography","Лендинг вымышленного датчика воздуха (демо): объект рисуется одним фрагментным шейдером на чистом WebGL2 без библиотек, четыре SDF-формы перетекают по скроллу, есть SVG-фолбэк, reduced-motion и пауза вне экрана.",[263,264,265,266,267],"Один фрагментный шейдер","WebGL2 без библиотек","Четыре SDF-формы с морфингом по скроллу","SVG-фолбэк и prefers-reduced-motion","Пауза отрисовки вне экрана","cd landing-3d && python -m http.server 8094",90,"\u002Fog\u002Flanding-3d.jpg",{"desktop":272,"desktop_sm":273,"mobile":274,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flanding-3d-desktop.webp","\u002Fshots\u002Flanding-3d-desktop-sm.webp","\u002Fshots\u002Flanding-3d-mobile.webp",{"slug":276,"title":277,"type":23,"category":23,"lang":24,"kind":229,"live_url":278,"stack":279,"description":288,"highlights":289,"run":296,"hue":297,"og":298,"shots":299},"raft-kv","Плот: KV на Raft","https:\u002F\u002Fraft-kv.ewsir.space",[24,280,281,282,205,283,146,284,285,286,287,210,29,212],"Raft","Distributed systems","Consensus","HTTP\u002FJSON","WAL + fsync","Snapshots","Linearizability","Deterministic simulation testing","Учебная реализация Raft с нуля на чистой stdlib: выборы лидера, репликация лога, коммит по кворуму, лог и term\u002Fvote на диске с fsync, снапшоты с компакцией, линеаризуемые чтения (read-index), редирект фолловера на лидера. Тестируется детерминированным симулятором сети и времени с проверкой линеаризуемости; дашборд умеет убивать лидера и рвать сеть (демо).",[290,291,292,293,294,295],"Raft с нуля на stdlib","без raft-библиотек","Детерминированный симулятор сети и времени + проверка линеаризуемости","Лог и term\u002Fvote с fsync","снапшоты и компакция","Дашборд: убить лидера и разорвать сеть (демо)","cd raft-kv && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fraft-kv",290,"\u002Fog\u002Fraft-kv.jpg",{"desktop":300,"desktop_sm":301,"mobile":302,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fraft-kv-desktop.webp","\u002Fshots\u002Fraft-kv-desktop-sm.webp","\u002Fshots\u002Fraft-kv-mobile.webp",{"slug":304,"title":305,"type":23,"category":23,"lang":24,"kind":306,"live_url":307,"stack":308,"description":317,"highlights":318,"run":323,"hue":324,"og":325,"shots":326},"auth-service","Ключница: сервис аутентификации","auth","https:\u002F\u002Fauth-service.ewsir.space",[24,309,28,234,310,311,312,313,314,315,316,210,211,29,212],"stdlib crypto","JWT EdDSA\u002FEd25519","JWKS","PBKDF2","TOTP","RBAC","REST","OpenAPI 3","Сервис аутентификации и авторизации на Go: EdDSA-JWT с ротацией ключей и JWKS, refresh-токены с ротацией и обнаружением кражи, TOTP-2FA (RFC 6238) с резервными кодами, RBAC, блокировка при переборе, сброс пароля по одноразовому токену, журнал аудита, список устройств, openapi.yaml. Не прошёл независимый аудит, не production-IdP.",[319,320,321,322],"JWT EdDSA и ротация ключей с JWKS","Refresh-токены с ротацией и reuse-detection","TOTP по RFC 6238 с проверкой на векторах RFC","Журнал аудита только на дозапись (триггеры БД)","cd auth-service && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",20,"\u002Fog\u002Fauth-service.jpg",{"desktop":327,"desktop_sm":328,"mobile":329,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fauth-service-desktop.webp","\u002Fshots\u002Fauth-service-desktop-sm.webp","\u002Fshots\u002Fauth-service-mobile.webp",{"slug":331,"title":332,"type":23,"category":23,"lang":24,"kind":25,"live_url":333,"stack":334,"description":343,"highlights":344,"run":349,"hue":350,"og":351,"shots":352},"finance-tracker","Книга учёта: личные финансы","https:\u002F\u002Ffinance-tracker.ewsir.space",[24,28,234,147,335,336,337,338,339,340,341,29,212,312,342],"Nuxt 4","Vue 3","TypeScript","Pinia","SVG-графики","vitest","ESLint","CSV import\u002Fexport","Учёт личных финансов: счета в разных валютах с ручными курсами, доходы, расходы и переводы, бюджеты, платежи по cron-подобному расписанию, импорт CSV с превью, отчёты. Деньги только в целых минорных единицах, данные пользователей изолированы, графики нарисованы на SVG без библиотек. Go + SQLite и фронтенд на Nuxt 4.",[345,346,347,348],"Деньги только целыми минорными единицами","Мультивалютность с ручными курсами","Импорт CSV с превью и пробным прогоном","Свои SVG-графики без библиотек","cd finance-tracker && set SEED_DEMO=1&& go run .",140,"\u002Fog\u002Ffinance-tracker.jpg",{"desktop":353,"desktop_sm":354,"mobile":355,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Ffinance-tracker-desktop.webp","\u002Fshots\u002Ffinance-tracker-desktop-sm.webp","\u002Fshots\u002Ffinance-tracker-mobile.webp",{"slug":357,"title":358,"type":23,"category":23,"lang":24,"kind":182,"live_url":359,"stack":360,"description":364,"highlights":365,"run":370,"hue":371,"og":372,"shots":373},"pixel-wall","Пиксельная стена: общий холст","https:\u002F\u002Fpixel-wall.ewsir.space",[24,205,185,361,210,211,362,363,29,212],"Canvas 2D","Binary protocol","Snapshot + journal","Совместный холст 256×256 в духе r\u002Fplace: 16 цветов, кулдаун на сессию и IP, свой WebSocket-слой на stdlib (RFC 6455) с бинарным протоколом (4 бита на пиксель), снимок и дельты, снапшот плюс журнал с восстановлением, таймлапс, зум и пан, мини-карта, экспорт PNG, админ-инструменты. Пет-проект.",[366,367,368,369],"Свой WebSocket (RFC 6455) на stdlib","Бинарный протокол: 4 бита на пиксель","Снапшот + журнал с восстановлением после падения","Таймлапс по журналу изменений","cd pixel-wall && set SEED_DEMO=1&& set ADMIN_TOKEN=secret&& go run .\u002Fcmd\u002Fserver",320,"\u002Fog\u002Fpixel-wall.jpg",{"desktop":374,"desktop_sm":375,"mobile":376,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fpixel-wall-desktop.webp","\u002Fshots\u002Fpixel-wall-desktop-sm.webp","\u002Fshots\u002Fpixel-wall-mobile.webp",{"slug":378,"title":379,"type":380,"category":380,"lang":24,"live_url":381,"stack":382,"description":389,"highlights":390,"run":395,"hue":396,"og":397,"shots":398},"gitpulse","GitPulse: аналитика git-репозиториев","cli","https:\u002F\u002Fgitpulse.ewsir.space",[24,205,383,384,385,386,387,388,212,29],"CLI","git","html\u002Ftemplate","SVG","ANSI","os\u002Fexec","CLI на Go для анализа git-репозиториев без внешних зависимостей (нужен только git): stats с цветами и спарклайнами в терминале, report — самодостаточный HTML-отчёт с понятным интерфейсом (итог из трёх выводов, поиск и фильтры, пояснения к метрикам, улучшенная справка CLI), тепловой картой активности, churn и горячими файлами, вкладом авторов, bus factor по каталогам, ростом кода по языкам и периодами тишины (светлая и тёмная темы), diff-hotspots между ревизиями и локальный serve на 127.0.0.1. История читается потоком через git log -z --numstat -M. Пет-проект.",[391,392,393,394],"Только stdlib Go, внешний бинарник один: git","Поток git log -z --numstat -M с учётом .mailmap и переименований","Понятный HTML-отчёт: итог из трёх выводов, поиск и фильтры, пояснения к метрикам, тепловая карта, churn, bus factor, светлая и тёмная темы","Синтетическая история из 100 000 коммитов анализируется около 5 секунд","cd gitpulse && go run .\u002Fcmd\u002Fgitpulse stats .",7,"\u002Fog\u002Fgitpulse.jpg",{"desktop":399,"desktop_sm":400,"mobile":401,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fgitpulse-desktop.webp","\u002Fshots\u002Fgitpulse-desktop-sm.webp","\u002Fshots\u002Fgitpulse-mobile.webp",{"slug":403,"title":404,"type":405,"category":406,"stack":407,"description":412,"highlights":413,"run":420,"hue":421,"og":422,"shots":423},"legal-multipage-site","Многостраничный сайт юридической компании","site","sites",[335,336,337,408,409,410,411,29,212],"Bootstrap 5","Zod","Vitest","Playwright","Реализованный сайт юридической и экспертной компании на Nuxt 4: 65 страниц с серверным рендерингом, JSON-LD разметка и 301-редиректы со старого сайта. Заявки уходят через серверный эндпоинт с проверкой Origin, лимитом размера тела и ограничением частоты запросов.",[414,415,416,417,418,419],"65 маршрутов с SSR, sitemap.xml и robots.txt; индексация включается явным флагом окружения","Форма заявок: серверный эндпоинт, проверка Origin, лимит тела 10 КБ, 3 запроса за 10 минут с одного IP, honeypot, опциональная капча","JSON-LD: LegalService, WebSite, BreadcrumbList","Одношаговые 301-редиректы со старых URL и со слешей на конце, заголовки безопасности (CSP, HSTS)","CI\u002FCD: lint, typecheck, Vitest, сборка, Playwright; затем Docker-образ и выкладка на сервер","210 модульных тестов в 13 файлах и 4 набора e2e-тестов","npm install && npm run build && node .output\u002Fserver\u002Findex.mjs",250,"\u002Fog\u002Flegal-multipage-site.jpg",{"desktop":424,"desktop_sm":425,"mobile":426,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flegal-multipage-site-desktop.webp","\u002Fshots\u002Flegal-multipage-site-desktop-sm.webp","\u002Fshots\u002Flegal-multipage-site-mobile.webp",{"slug":428,"title":429,"type":430,"category":431,"stack":432,"description":440,"highlights":441,"run":446,"hue":447,"og":448,"shots":449},"bad-club-booking-app","Bad Club: бронирование мест в игровом коворкинге","app","apps",[336,433,338,434,435,436,437,438,439],"Vite","Vue Router","GSAP","Express","node:sqlite","JWT (httpOnly cookie)","Resend","Демо-приложение на Vue 3 и Express с SQLite: интерактивная карта зала с бронированием мест, личный кабинет и админ-панель с редактором расстановки. Вход без паролей, по одноразовому коду на e-mail.",[442,443,444,445],"Карта зала с зонами (обычные, тихая, VIP, два монитора), фильтром по типу и статусом мест","Админ-панель: обзор, брони, столы, компьютеры, пользователи и перетаскиваемая расстановка зала","Вход по OTP с ограничением попыток и частоты запросов, роль admin задаётся через переменную окружения","Данные для демо создаются сид-скриптом при первом запуске","npm install && npm run dev",150,"\u002Fog\u002Fbad-club-booking-app.jpg",{"desktop":450,"desktop_sm":451,"mobile":452,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fbad-club-booking-app-desktop.webp","\u002Fshots\u002Fbad-club-booking-app-desktop-sm.webp","\u002Fshots\u002Fbad-club-booking-app-mobile.webp",{"slug":454,"title":455,"type":85,"category":86,"live_url":456,"stack":457,"description":458,"highlights":459,"run":446,"hue":463,"og":464,"shots":465},"dovod-concept","Довод: концепт лендинга экспертной организации","https:\u002F\u002Fewsirdev-dovod.pages.dev",[335,336,337,90,212],"Концепт лендинга на Nuxt 4 для вымышленной экспертной организации: направления услуг, форма консультации с маской телефона, карусель отзывов и страница политики. Компания, отзывы, цифры и контакты придуманы; форма ничего не отправляет.",[460,461,462],"Форма с маской телефона, валидацией и подсказками по ошибкам; в демо показывает «данные не отправляются»","Бесконечная карусель отзывов со свайпом и автопрокруткой","Мобильная панель действий, адаптивная вёрстка, скелетон поверх серверного HTML",215,"\u002Fog\u002Fdovod-concept.jpg",{"desktop":466,"desktop_sm":467,"mobile":468,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fdovod-concept-desktop.webp","\u002Fshots\u002Fdovod-concept-desktop-sm.webp","\u002Fshots\u002Fdovod-concept-mobile.webp",{"slug":470,"title":471,"type":405,"category":406,"live_url":472,"stack":473,"description":476,"highlights":477,"run":446,"hue":481,"og":482,"shots":483},"niche-landings","Каталог демо-лендингов под 10 ниш","https:\u002F\u002Fewsirdev-niche.pages.dev",[336,474,475,212],"Vue Router 4","Vite 8","Vue-каталог из десяти самостоятельных лендингов под разные ниши малого бизнеса: у каждого своя палитра, шрифты и структура секций. Каждый лендинг загружается отдельным чанком; весь контент демонстрационный.",[478,479,480],"10 ниш: медицина, мебель, ремонт, онлайн-школа, глэмпинг, юристы, автосервис, салон красоты, кафе, фитнес","Код-сплиттинг по маршрутам, общий набор SVG-иконок","Плашка «Демо-контент» на каталоге и каждом лендинге",32,"\u002Fog\u002Fniche-landings.jpg",{"desktop":484,"desktop_sm":485,"mobile":486,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fniche-landings-desktop.webp","\u002Fshots\u002Fniche-landings-desktop-sm.webp","\u002Fshots\u002Fniche-landings-mobile.webp",{"slug":488,"title":489,"type":85,"category":86,"live_url":490,"stack":491,"description":494,"highlights":495,"run":499,"hue":500,"og":501,"shots":502},"dental-concept","Кварц: концепт лендинга стоматологии","https:\u002F\u002Fewsirdev-dental.pages.dev",[89,90,492,361,493,212],"JavaScript","htmlhint","Концепт одностраничного лендинга вымышленной стоматологии в стиле Liquid Chrome Dark: стеклянные карточки, хромированные заголовки и 3D-блоб в виде зуба на canvas. Один HTML-файл без сборки и зависимостей.",[496,497,498],"Анимированный блоб на canvas с учётом prefers-reduced-motion и видимости на экране","Форма записи с маской телефона и доступными сообщениями об ошибках","CI: htmlhint и проверка локальных ссылок и якорей","python -m http.server 8080",190,"\u002Fog\u002Fdental-concept.jpg",{"desktop":503,"desktop_sm":504,"mobile":505,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fdental-concept-desktop.webp","\u002Fshots\u002Fdental-concept-desktop-sm.webp","\u002Fshots\u002Fdental-concept-mobile.webp",{"slug":507,"title":508,"type":380,"category":380,"lang":509,"stack":510,"description":514,"highlights":515,"run":520,"hue":244,"og":521,"shots":522},"logslice","logslice: анализ логов без зависимостей","Python",[509,205,383,386,511,512,513,212,29],"pytest","ruff","mypy","CLI на Python 3.11+ только на стандартной библиотеке: потоково разбирает логи nginx\u002Fapache (combined), JSON-lines, logfmt и syslog (gz, bz2, xz тоже), считает статистику и топы, кластеризует похожие ошибки, ищет скачки нагрузки по z-score и EWMA и собирает один HTML-отчёт с SVG-графиками и тепловой картой. Пет-проект.",[516,517,518,519],"Только стандартная библиотека, pip install ничего не тянет","Кластеры ошибок по шаблону и аномалии двумя методами (z-score и EWMA)","Один самодостаточный HTML-отчёт: SVG, тепловая карта, светлая и тёмная темы","53 теста, ruff и mypy --strict; 1 млн строк combined за 8,2 с (один прогон, Windows, CPython 3.12.10)","logslice report access.log -o report.html","\u002Fog\u002Flogslice.jpg",{"desktop":523,"desktop_sm":524,"mobile":525,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flogslice-desktop.webp","\u002Fshots\u002Flogslice-desktop-sm.webp","\u002Fshots\u002Flogslice-mobile.webp",{"slug":527,"title":528,"type":380,"category":380,"lang":24,"stack":529,"description":534,"highlights":535,"run":540,"hue":541,"og":542,"shots":543},"envguard","envguard: линтер и сейф .env-файлов",[24,205,383,530,312,531,532,533,212,29],"AES-256-GCM","fuzzing","govulncheck","gitleaks","CLI на Go без внешних модулей: check сверяет .env со схемой, scan ищет секреты в дереве и git-истории, diff и doctor проверяют окружения и git-гигиену, seal и unseal шифруют .env в .env.sealed (AES-256-GCM, PBKDF2 600 тыс. итераций), run запускает команду с чистым окружением, report и serve строят отчёт. Значение секрета не попадает ни в вывод, ни в лог, ни в HTML. Пет-проект.",[536,537,538,539],"Значения секретов не печатаются нигде: только тип находки, файл, строка и маска из 4 символов","seal и unseal: AES-256-GCM, PBKDF2 600 тыс. итераций, подмена любого бита файла отвергается","Fuzz-тесты: 4 цели по 20 секунд без падений; govulncheck и gitleaks без находок","Сборки под windows, linux и darwin, только стандартная библиотека","go run .\u002Fcmd\u002Fenvguard check",35,"\u002Fog\u002Fenvguard.jpg",{"desktop":544,"desktop_sm":545,"mobile":546,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fenvguard-desktop.webp","\u002Fshots\u002Fenvguard-desktop-sm.webp","\u002Fshots\u002Fenvguard-mobile.webp",{"slug":548,"title":549,"type":23,"category":23,"lang":509,"live_url":550,"stack":551,"description":554,"highlights":555,"run":560,"hue":561,"og":562,"shots":563},"catalog-api","catalog-api: API каталога с поиском","https:\u002F\u002Fcatalog-api.ewsir.space",[509,552,28,232,511,512,513,553,29,212],"FastAPI","bandit","API каталога товаров на Python 3.12 и FastAPI: SQLite с полнотекстовым поиском FTS5 (ранжирование bm25), фасеты по категории, цене, наличию, тегам и характеристикам, курсорная пагинация, ETag, импорт и экспорт CSV, история цен, API-ключи с ролями и аудит-лог. Витрина и админка без фреймворка отдаются самим приложением. Пет-проект.",[556,557,558,559],"Поиск FTS5 с bm25, фасеты и курсорная пагинация","API-ключи с ролями admin, editor и reader; в базе только хэши ключей","511 тестов, покрытие 96%; ruff, mypy --strict, bandit, pip-audit и gitleaks без замечаний","Нагрузка: 339 запросов\u002Fс, p95 50 мс при 8 соединениях (Windows, один процесс)","SEED_DEMO=1 python -m app   # http:\u002F\u002F127.0.0.1:8110",265,"\u002Fog\u002Fcatalog-api.jpg",{"desktop":564,"desktop_sm":565,"mobile":566,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fcatalog-api-desktop.webp","\u002Fshots\u002Fcatalog-api-desktop-sm.webp","\u002Fshots\u002Fcatalog-api-mobile.webp",{"slug":568,"title":569,"type":23,"category":23,"lang":337,"live_url":570,"stack":571,"description":576,"highlights":577,"run":446,"hue":463,"og":582,"shots":583},"paste-vault","Конверт: записки с шифрованием в браузере","https:\u002F\u002Fewsirdev-paste.ewsirdev.workers.dev",[337,572,573,574,433,340,575,212],"Cloudflare Workers","D1","Web Crypto","fast-check","Сервис одноразовых записок на TypeScript, Cloudflare Workers и D1: текст шифруется в браузере (AES-256-GCM), ключ лежит только во фрагменте URL и на сервер не попадает. У записки есть срок жизни, пароль и сгорание после первого прочтения. Пет-проект.",[578,579,580,581],"Шифрование в браузере: AES-256-GCM, ключ только во фрагменте URL","Сгорание после прочтения атомарно: ровно один успех из 40 параллельных чтений","52 теста в рантайме workerd, включая property-тесты и фаззинг схемы","Живая версия на Cloudflare Workers + D1","\u002Fog\u002Fpaste-vault.jpg",{"desktop":584,"desktop_sm":585,"mobile":586,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fpaste-vault-desktop.webp","\u002Fshots\u002Fpaste-vault-desktop-sm.webp","\u002Fshots\u002Fpaste-vault-mobile.webp",{"slug":588,"title":589,"type":405,"category":406,"live_url":590,"stack":591,"description":597,"highlights":598,"run":603,"hue":604,"og":605,"shots":606},"docs-engine","Сверло: генератор документации на Node.js","https:\u002F\u002Fewsirdev-docs.pages.dev",[592,593,594,595,596,212],"Node.js","JavaScript (ESM)","zero dependencies","Markdown","Cloudflare Pages","Генератор статических сайтов документации, написанный с нуля на Node.js без зависимостей: собственный Markdown-парсер, подсветка кода и офлайн-поиск. Демо-сайт про вымышленную библиотеку «Сверло» собран из 43 страниц на двух языках (RU и EN) и двух версиях (v1 и v2). Пет-проект.",[599,600,601,602],"Свой Markdown-парсер, подсветка кода и клиентский поиск без библиотек","43 страницы, RU и EN, версии v1 и v2, hreflang, sitemap и RSS","131 тест; в CI проверка битых ссылок и якорей","Сайт читается без JavaScript, поиск работает офлайн","npm test && npm run build",25,"\u002Fog\u002Fdocs-engine.jpg",{"desktop":607,"desktop_sm":608,"mobile":609,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fdocs-engine-desktop.webp","\u002Fshots\u002Fdocs-engine-desktop-sm.webp","\u002Fshots\u002Fdocs-engine-mobile.webp",{"slug":611,"title":612,"type":430,"category":431,"live_url":613,"stack":614,"description":619,"highlights":620,"run":446,"hue":625,"og":626,"shots":627},"typing-lab","typing-lab: тренажёр слепой печати","https:\u002F\u002Fewsirdev-typing.pages.dev",[335,336,337,615,616,617,386,340,618,596],"IndexedDB","WebAudio","PWA","axe","Тренажёр слепой печати, который целиком работает в браузере и не обращается к серверу: Nuxt 4, история в IndexedDB, звук клавиш на WebAudio, офлайн через PWA. Режимы времени, слов, цитат, своего текста, кода и «слабых мест», шесть раскладок, метрики WPM, точность и консистентность, графики на чистом SVG. Пет-проект.",[621,622,623,624],"Без сервера и аккаунтов: история в IndexedDB, экспорт и импорт JSON","Шесть раскладок, режимы вплоть до адаптивных упражнений из ваших ошибок","59 unit-тестов и 65 e2e-проверок, axe без нарушений, CLS 0,000","PWA: работает офлайн после первого открытия",45,"\u002Fog\u002Ftyping-lab.jpg",{"desktop":628,"desktop_sm":629,"mobile":630,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Ftyping-lab-desktop.webp","\u002Fshots\u002Ftyping-lab-desktop-sm.webp","\u002Fshots\u002Ftyping-lab-mobile.webp",{"slug":632,"title":633,"type":430,"category":431,"lang":337,"live_url":634,"stack":635,"description":637,"highlights":638,"run":446,"hue":625,"og":643,"shots":644},"budget-split","budget-split: «Поровну», учёт общих расходов","https:\u002F\u002Fewsirdev-budget.ewsirdev.workers.dev",[337,433,572,573,636,340,575,618,212],"WebGL","Веб-приложение учёта общих расходов группы без регистрации: комната по секретной ссылке, деление поровну, по долям, суммам и процентам, расчёт «кто кому» не более n−1 переводов. Пет-проект.",[639,640,641,642],"Без аккаунтов: комната по секретной ссылке в 128 бит, отдельные ссылки «просмотр» и «редактирование»","Четыре способа деления и мультивалюта с ручным курсом; расчёт «кто кому» не более n−1 переводов","Оптимистичная блокировка: из 4 одновременных правок одного расхода проходит одна","60 тестов (vitest в workerd, property-тесты fast-check), CI зелёный, npm audit 0, axe 0 нарушений","\u002Fog\u002Fbudget-split.jpg",{"desktop":645,"desktop_sm":646,"mobile":647,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fbudget-split-desktop.webp","\u002Fshots\u002Fbudget-split-desktop-sm.webp","\u002Fshots\u002Fbudget-split-mobile.webp",{"slug":649,"title":650,"type":405,"category":406,"lang":337,"live_url":651,"stack":652,"description":655,"highlights":656,"run":446,"hue":625,"og":661,"shots":662},"kladka-site","kladka-site: «Кладка», демо-сайт студии каркасного строительства","https:\u002F\u002Fewsirdev-kladka.pages.dev",[653,337,654,386,636,618,493,212],"Astro","Content collections","Демо-сайт вымышленной студии каркасного строительства: 5 проектов с чертежами, услуги, цены с калькулятором-ориентиром, журнал. Компания, цены и сроки выдуманы, на каждой странице есть пометка.",[657,658,659,660],"24 страницы (19 RU, 4 EN, 404) на Astro 7 со статической сборкой и content collections","Все 20 чертежей рисует код: SVG по данным проекта; 24 картинки OG рендерятся скриптом","Строгий CSP без inline, robots, sitemap с hreflang, llms.txt, security.txt, JSON-LD Organization\u002FService\u002FArticle\u002FFAQPage","756 ссылок и якорей без ошибок, htmlhint 0, axe 0 по всем страницам на 390 и 1280, npm audit 0","\u002Fog\u002Fkladka-site.jpg",{"desktop":663,"desktop_sm":664,"mobile":665,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fkladka-site-desktop.webp","\u002Fshots\u002Fkladka-site-desktop-sm.webp","\u002Fshots\u002Fkladka-site-mobile.webp",{"slug":667,"title":668,"type":23,"category":23,"lang":669,"live_url":670,"stack":671,"description":677,"highlights":678,"run":683,"hue":684,"og":685,"shots":686},"rate-gate","rate-gate: ограничитель частоты запросов","Rust","https:\u002F\u002Frate-gate.ewsir.space",[669,672,673,674,675,676,165,29,212],"axum","tokio","proptest","musl","distroless","HTTP-сервис на Rust: один вызов POST \u002Fv1\u002Fcheck отвечает, можно ли пропустить действие. Два алгоритма: токен-бакет на целочисленных милли-токенах (всплески) и скользящее окно по двум счётчикам (строгие лимиты). Правила в JSON, выбор по префиксу ключа, метрики Prometheus, снапшот состояния, страница-демо. Состояние в памяти одного экземпляра: реплики его не делят.",[679,680,681,682],"Токен-бакет без float: милли-токены, cost больше 1, расчёт retry_after_ms и reset_ms","Property-тесты (proptest): за любой период бакет выдаёт не больше capacity + refill * elapsed","20 тестов; образ 8,2 МБ, 3,8 МиБ RAM в покое и 3,9 МиБ после 200 запросов","Контейнер uid 10001, read-only, cap-drop ALL, CSP script-src 'self'","cd rate-gate && cargo run --release",12,"\u002Fog\u002Frate-gate.jpg",{"desktop":687,"desktop_sm":688,"mobile":689,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Frate-gate-desktop.webp","\u002Fshots\u002Frate-gate-desktop-sm.webp","\u002Fshots\u002Frate-gate-mobile.webp",{"slug":691,"title":692,"type":23,"category":23,"lang":669,"live_url":693,"stack":694,"description":697,"highlights":698,"run":703,"hue":704,"og":705,"shots":706},"hook-relay","hook-relay: доставка вебхуков","https:\u002F\u002Fhook-relay.ewsir.space",[669,672,673,28,695,696,674,675,676,29,212],"HMAC-SHA256","SSRF","Сервис доставки вебхуков на Rust: принимает событие, подписывает его HMAC-SHA256, отправляет получателю и не теряет. При сбое повторяет с экспоненциальной задержкой и джиттером, после лимита попыток откладывает в dead-letter, где доставку можно вернуть в очередь вручную. Адрес получателя проверяется на SSRF после DNS: все полученные адреса должны быть публичными. Идемпотентность по Idempotency-Key, состояние в SQLite.",[699,700,701,702],"SSRF-проверка после разрешения DNS: loopback, приватные, link-local, CGNAT, ULA, IPv4-mapped, NAT64 и 6to4 блокируются","HMAC-подпись «t.тело» у каждого получателя свой секрет; ретраи с джиттером, dead-letter, ручной повтор","20 тестов, в том числе proptest: подпись не проходит после изменения любого байта, любой приватный IPv4 блокируется","Образ 14,4 МБ; 3,5 МиБ в покое, 5,5 МиБ после 200 событий с реальной доставкой","cd hook-relay && cargo run --release",28,"\u002Fog\u002Fhook-relay.jpg",{"desktop":707,"desktop_sm":708,"mobile":709,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fhook-relay-desktop.webp","\u002Fshots\u002Fhook-relay-desktop-sm.webp","\u002Fshots\u002Fhook-relay-mobile.webp",{"slug":711,"title":712,"type":23,"category":23,"lang":669,"live_url":713,"stack":714,"description":716,"highlights":717,"run":722,"hue":244,"og":723,"shots":724},"flag-store","flag-store: фича-флаги с процентным rollout","https:\u002F\u002Fflag-store.ewsir.space",[669,672,673,28,146,715,674,675,676,29,212],"SHA-256","Сервис фича-флагов на Rust: флаги по окружениям, правила по атрибутам пользователя, процентный rollout без случайности, журнал аудита и SSE-поток изменений. Корзина пользователя это первые 8 байт SHA-256 от флага и id по модулю 10000, поэтому ответ стабилен, а рост процента никого не выключает. Доли в базисных пунктах, float нигде нет.",[718,719,720,721],"Детерминированный rollout: SHA-256(флаг, id) по модулю 10000; один пользователь всегда получает один ответ","3 property-теста: стабильность, монотонность по проценту, равномерность на 20 000 пользователей с допуском 2 п.п.","15 тестов; образ 11,6 МБ, 2,7 МиБ RAM в покое и 3,5 МиБ после 200 evaluate","SSE-поток изменений с hello, change и resync; журнал аудита с автором и состоянием до и после","cd flag-store && cargo run --release","\u002Fog\u002Fflag-store.jpg",{"desktop":725,"desktop_sm":726,"mobile":727,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fflag-store-desktop.webp","\u002Fshots\u002Fflag-store-desktop-sm.webp","\u002Fshots\u002Fflag-store-mobile.webp",{"slug":729,"title":730,"type":23,"category":23,"lang":669,"live_url":731,"stack":732,"description":734,"highlights":735,"run":740,"hue":741,"og":742,"shots":743},"ledger-core","ledger-core: двойная запись и хэш-цепочка журнала","https:\u002F\u002Fledger-core.ewsir.space",[669,672,28,715,674,675,676,29,733,212],"cargo-audit","Бухгалтерское ядро с двойной записью на Rust: счета, проводки, неизменяемый журнал с хэш-цепочкой, проверка целостности и оборотно-сальдовая ведомость. Деньги только целыми числами (i64, копейки), сумма проводки строго 0, арифметика checked. Каждая запись хранит prev_hash и SHA-256 от своих полей, GET \u002Fv1\u002Fverify пересчитывает цепочку и указывает подменённую запись. Идемпотентность по Idempotency-Key.",[736,737,738,739],"Инвариант проводки: сумма строк 0, от 2 до 100 строк, одна валюта; переполнение даёт 422, а не панику","Хэш-цепочка: любая подмена суммы, комментария или счёта ловится, и указывается именно подменённая запись","19 тестов, property-тесты: сумма проводки и сальдо всех счетов всегда 0","Образ 11,4 МБ; 3,5 МиБ в покое и 4,5 МиБ после 200 запросов, цепочка осталась целой","cd ledger-core && cargo run --release",52,"\u002Fog\u002Fledger-core.jpg",{"desktop":744,"desktop_sm":745,"mobile":746,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fledger-core-desktop.webp","\u002Fshots\u002Fledger-core-desktop-sm.webp","\u002Fshots\u002Fledger-core-mobile.webp",{"slug":748,"title":749,"type":23,"category":23,"lang":509,"live_url":750,"stack":751,"description":756,"highlights":757,"run":762,"hue":350,"og":763,"shots":764},"csv-doctor","csv-doctor: профиль и очистка CSV","https:\u002F\u002Fcsv-doctor.ewsir.space",[509,552,752,753,754,512,755,29,212],"uvicorn","hypothesis","mypy --strict","pip-audit","Сервис проверки и очистки CSV на Python (FastAPI, без pandas). Загружаете файл до 5 МБ: определяются кодировка (utf-8, cp1251, koi8-r, UTF-16 с BOM) и разделитель, строится профиль колонок и список проблем с номерами строк и подсказками, затем можно скачать очищенный CSV. Опасные ячейки (=, +, -, @) ловятся как CSV-injection. Файл разбирается в памяти и нигде не сохраняется.",[758,759,760,761],"Автоопределение кодировки и разделителя, профиль колонок, выбросы по 1.5·IQR, неоднозначные даты","Защита от CSV-injection на входе (ошибка) и на выходе очистки (апостроф), телефоны +7 не ломаются","43 теста; hypothesis: круговой проход кодировок, идемпотентность очистки clean(clean(x)) == clean(x)","CI: ruff, mypy --strict, pytest --cov, pip-audit по хэш-закреплённому requirements.lock; файл 3,8 МБ (95 000 строк) разбирается за 1,5 с","cd csv-doctor && python -m app","\u002Fog\u002Fcsv-doctor.jpg",{"desktop":765,"desktop_sm":766,"mobile":767,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fcsv-doctor-desktop.webp","\u002Fshots\u002Fcsv-doctor-desktop-sm.webp","\u002Fshots\u002Fcsv-doctor-mobile.webp",{"slug":769,"title":770,"type":23,"category":23,"lang":509,"live_url":771,"stack":772,"description":774,"highlights":775,"run":780,"hue":220,"og":781,"shots":782},"cron-clock","cron-clock: объяснение cron и ближайшие запуски","https:\u002F\u002Fcron-clock.ewsir.space",[509,552,752,773,753,754,512,755,29,212],"zoneinfo","Сервис разбора cron-выражений на Python (FastAPI, только стандартная библиотека и zoneinfo). Объясняет выражение по-русски и по-английски, считает ближайшие запуски в заданном часовом поясе с учётом перехода на летнее время, предупреждает об «опасных» расписаниях и сравнивает два выражения. Поддержаны L, W, #, шесть полей с секундами.",[776,777,778,779],"Ближайшие запуски с DST: границы Europe\u002FBerlin 2024-03-31 и 2024-10-27, пояс Lord Howe с получасовым переходом","64 теста; hypothesis по 150 примеров на свойство: запуск удовлетворяет выражению, нет пропущенной минуты (проверка перебором)","Сравнение `* * * * * *` против `* * * * *` на 400 дней за 0,6 с","\u002Fapi\u002Fparse: около 800 запросов в секунду, p95 35 мс; 37 МиБ RAM в покое","cd cron-clock && python -m app","\u002Fog\u002Fcron-clock.jpg",{"desktop":783,"desktop_sm":784,"mobile":785,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fcron-clock-desktop.webp","\u002Fshots\u002Fcron-clock-desktop-sm.webp","\u002Fshots\u002Fcron-clock-mobile.webp",{"slug":787,"title":788,"type":23,"category":23,"lang":509,"live_url":789,"stack":790,"description":793,"highlights":794,"run":799,"hue":800,"og":801,"shots":802},"ical-forge","ical-forge: iCalendar (RFC 5545), RRULE и подписка","https:\u002F\u002Fical-forge.ewsir.space",[509,552,752,28,791,792,753,754,512,29,212],"RFC 5545","ETag","Сервис iCalendar на Python (FastAPI + SQLite, без сторонних библиотек для календаря). Собирает корректный .ics (складывание строк, экранирование, VTIMEZONE из zoneinfo), проверяет чужой файл с номерами строк, разворачивает повторяющиеся события по RRULE и публикует ленты для подписки по ссылке с ETag и ответом 304.",[795,796,797,798],"28 примеров повторов из RFC 5545 §3.8.5.3 сверяются по спискам дат (BYSETPOS, «предпоследний понедельник», пятница 13-е с EXDATE)","119 тестов; hypothesis: круговой проход событие → .ics → событие на произвольном Unicode, unfold(fold(x)) == x, строки не длиннее 75 октетов","POST \u002Fapi\u002Fexpand на 500 вхождений за 0,2 с; лента около 640 запросов в секунду, p95 45 мс","Ключ редактирования хранится только как SHA-256, сравнение за постоянное время","cd ical-forge && python -m app",280,"\u002Fog\u002Fical-forge.jpg",{"desktop":803,"desktop_sm":804,"mobile":805,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fical-forge-desktop.webp","\u002Fshots\u002Fical-forge-desktop-sm.webp","\u002Fshots\u002Fical-forge-mobile.webp",{"slug":807,"title":808,"type":23,"category":23,"lang":337,"live_url":809,"stack":810,"description":812,"highlights":813,"run":818,"hue":819,"og":820,"shots":821},"poll-board","poll-board: опросы в реальном времени","https:\u002F\u002Fpoll-board.ewsir.space",[337,811,146,185,575,340,618,29,212],"Node.js 24","Сервис опросов и голосований на TypeScript без runtime-зависимостей. Опрос на 2–10 вариантов со сроком жизни, ссылка, результаты растут у всех в реальном времени по SSE или WebSocket (кадры WebSocket разобраны вручную). Повторные голоса и накрутка режутся подписанным токеном и хэшем IP; экспорт CSV защищён от формульных ячеек.",[814,815,816,817],"SSE и WebSocket на одном потоке событий; порядок монотонен: seq растёт на 1, closed последним","43 теста; fast-check: сумма голосов равна числу принятых бюллетеней, кадры WebSocket при любой нарезке потока","200 запросов (20 параллельно): p50 6,4 мс, p95 21,7 мс; около 25 МиБ RAM","Запуск read-only, cap-drop ALL, uid 10001; axe 0 в Firefox на 390 и 1280","cd poll-board && npm ci && npm run build && npm start",330,"\u002Fog\u002Fpoll-board.jpg",{"desktop":822,"desktop_sm":823,"mobile":824,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fpoll-board-desktop.webp","\u002Fshots\u002Fpoll-board-desktop-sm.webp","\u002Fshots\u002Fpoll-board-mobile.webp",{"slug":826,"title":827,"type":23,"category":23,"lang":337,"live_url":828,"stack":829,"description":832,"highlights":833,"run":838,"hue":839,"og":840,"shots":841},"md-safe","md-safe: Markdown-парсер и санитайзер","https:\u002F\u002Fmd-safe.ewsir.space",[337,811,595,830,831,575,340,618,29,212],"XSS","allowlist","API и веб-демо безопасного рендера Markdown в HTML на TypeScript. Парсер подмножества CommonMark написан с нуля, результат всегда проходит санитайзер по allowlist (тоже свой): готовых markdown-it, marked и dompurify в проекте нет. Проверка: корпус из 242 XSS-векторов и независимый проверяющий вывода, который не использует код санитайзера.",[834,835,836,837],"242 XSS-вектора (OWASP, PortSwigger, HTML5 Security Cheatsheet) прогоняются как HTML и как Markdown в нескольких режимах","Независимый проверяющий вывода: строгий шаблон, любой лишний тег, атрибут или схема считаются нарушением","829 тестов, в том числе fast-check: вывод проходит проверяющего, санитайзер идемпотентен","Тяжёлые входы (60 тысяч «[», 100 тысяч «\u003Cb>») обрабатываются быстрее 3 секунд: разбор линейный","cd md-safe && npm ci && npm run build && npm start",240,"\u002Fog\u002Fmd-safe.jpg",{"desktop":842,"desktop_sm":843,"mobile":844,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fmd-safe-desktop.webp","\u002Fshots\u002Fmd-safe-desktop-sm.webp","\u002Fshots\u002Fmd-safe-mobile.webp",{"slug":846,"title":847,"type":23,"category":23,"lang":337,"live_url":848,"stack":849,"description":852,"highlights":853,"run":858,"hue":859,"og":860,"shots":861},"qr-svg","qr-svg: QR-коды с нуля","https:\u002F\u002Fqr-svg.ewsir.space",[337,811,850,851,386,575,340,618,29,212],"Reed-Solomon","GF(256)","Генератор QR-кодов в SVG на TypeScript без единой runtime-зависимости: весь стандарт ISO\u002FIEC 18004 для версий 1–40 написан в src\u002Fqr\u002F (поле GF(256), Рид-Соломон, чередование блоков, 8 масок со штрафами). API отдаёт SVG, веб-демо показывает предпросмотр, скачивает SVG и PNG и проверяет контраст цветов по WCAG.",[854,855,856,857],"Версии 1–40, уровни L, M, Q, H; эталонные примеры ISO «01234567» и «HELLO WORLD» совпадают по байтам коррекции","Матрицы сверены бит в бит с независимой реализацией qrcode-generator на 12 текстах × 4 уровня и 80 случайных строках","Все 160 сочетаний «версия × уровень» читаются сторонним декодером jsQR, кроме 23-L (его не читает и эталон); есть и собственный декодер","Образ 244 МБ; запуск read-only, cap-drop ALL, uid 10001","cd qr-svg && npm ci && npm run build && npm start",100,"\u002Fog\u002Fqr-svg.jpg",{"desktop":862,"desktop_sm":863,"mobile":864,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fqr-svg-desktop.webp","\u002Fshots\u002Fqr-svg-desktop-sm.webp","\u002Fshots\u002Fqr-svg-mobile.webp",{"slug":866,"title":867,"type":405,"category":406,"lang":337,"live_url":868,"stack":869,"description":874,"highlights":875,"run":880,"hue":881,"og":882,"shots":883},"bike-workshop-react","bike-workshop-react: «Ось», концепт сайта веломастерской","https:\u002F\u002Fewsirdev-bike.pages.dev",[870,871,337,433,872,873,618,596,212],"React 19","React Router","Prerender","CSP","Концепт многостраничного сайта вымышленной веломастерской на React 19 с пререндером каждой страницы в статический HTML: услуги, прайс-демо с калькулятором, запись с расписанием, журнал из трёх статей. Мастерской, клиентов и реальных цен не существует, форма записи работает в браузере и ничего не отправляет.",[876,877,878,879],"React 19 + React Router + Vite, каждая страница пререндерится в плоский .html (адреса без слэша без редиректа на Pages)","CSP script-src 'self' и style-src 'self' без unsafe-inline: check:security проваливает сборку при inline-коде","Фирменные приёмы: линейка как разделитель, бирка с отрывным корешком для смет и записи","Firefox 390, 768, 1280, 1920: axe 0, CLS 0, горизонтального скролла и ошибок консоли нет","cd bike-workshop-react && npm ci && npm run dev",18,"\u002Fog\u002Fbike-workshop-react.jpg",{"desktop":884,"desktop_sm":885,"mobile":886,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fbike-workshop-react-desktop.webp","\u002Fshots\u002Fbike-workshop-react-desktop-sm.webp","\u002Fshots\u002Fbike-workshop-react-mobile.webp",{"slug":888,"title":889,"type":405,"category":406,"lang":337,"live_url":890,"stack":891,"description":893,"highlights":894,"run":899,"hue":900,"og":901,"shots":902},"landscape-bureau-nuxt","landscape-bureau-nuxt: «Изолиния», концепт сайта ландшафтного бюро","https:\u002F\u002Fewsirdev-landscape.pages.dev",[335,336,337,892,386,873,618,596,212],"Static generation","Концепт многостраничного сайта вымышленного ландшафтного бюро на Nuxt 4 со статической генерацией: шесть страниц проектов по динамическому маршруту, фильтр растений по свету, влажности и форме, бриф-форма. Данные лежат в JSON, планы участков рисует код. Бюро, заказчиков и площади выдуманы.",[895,896,897,898],"Nuxt 4 generate: все динамические маршруты пререндерены","CSP без unsafe-inline на Nuxt: выключены entryImportMap и инлайн-стили, window.__NUXT__ вынесен в файл _nuxt\u002Finline-\u003Chash>.js, check-security проваливает сборку при inline-скрипте","Планы участков генерирует код (SVG по данным проекта)","Firefox 390, 768, 1280, 1920: axe, CLS, горизонтальный скролл, консоль проверены","cd landscape-bureau-nuxt && npm ci && npm run dev",130,"\u002Fog\u002Flandscape-bureau-nuxt.jpg",{"desktop":903,"desktop_sm":904,"mobile":905,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flandscape-bureau-nuxt-desktop.webp","\u002Fshots\u002Flandscape-bureau-nuxt-desktop-sm.webp","\u002Fshots\u002Flandscape-bureau-nuxt-mobile.webp",{"slug":907,"title":908,"type":405,"category":406,"lang":337,"live_url":909,"stack":910,"description":913,"highlights":914,"run":919,"hue":541,"og":920,"shots":921},"letterpress-ts","letterpress-ts: «Реглет», концепт сайта печатной мастерской","https:\u002F\u002Fewsirdev-letterpress.pages.dev",[337,433,911,912,90,618,596,212],"Multi-page","DOM","Концепт многостраничного сайта вымышленной мастерской высокой печати на чистом TypeScript без фреймворка: Vite multi-page с 8 HTML-входами, компоненты как типизированные функции, возвращающие DOM. Конструктор набора превращает текст в литеры и оттиск, калькулятор считает тираж, состояние хранится в ссылке. Мастерской и ценам верить не нужно: суммы по демо-формулам.",[915,916,917,918],"Без фреймворка: 8 HTML-входов Vite, компоненты как функции, возвращающие DOM","Конструктор набора: гарнитура, краска, бумага, кегль, шпации; состояние в ссылке и проверка параметров","Таблица цен на главной считается при сборке теми же формулами, что калькулятор","Пользовательский текст попадает в DOM через textContent, innerHTML запрещён линтером","cd letterpress-ts && npm ci && npm run dev","\u002Fog\u002Fletterpress-ts.jpg",{"desktop":922,"desktop_sm":923,"mobile":924,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fletterpress-ts-desktop.webp","\u002Fshots\u002Fletterpress-ts-desktop-sm.webp","\u002Fshots\u002Fletterpress-ts-mobile.webp",{"slug":926,"title":927,"type":405,"category":406,"lang":337,"live_url":928,"stack":929,"description":931,"highlights":932,"run":937,"hue":324,"og":938,"shots":939},"ceramics-vue","ceramics-vue: «Муфель», концепт сайта керамической студии","https:\u002F\u002Fewsirdev-ceramics.pages.dev",[336,434,337,433,930,386,873,618,596,212],"vite-ssg","Концепт сайта вымышленной керамической студии на Vue 3 с пререндером каждой страницы в статический HTML (vite-ssg): 7 страниц, 5 динамических страниц курсов и 404. Расписание с фильтрами, запись на пробное через mailto, калькулятор обжига с кривой в SVG. Студии, отзывов и цен не существует, формулы помечены «демо».",[933,934,935,936],"Vue 3 + vite-ssg: 7 страниц и 5 страниц курсов пререндерены в HTML","Калькулятор обжига: объём, вес, сушка, режим, длительность и кривая SVG по демо-формулам","CSP script-src 'self' без inline-скриптов; check-security проваливает сборку при inline-коде или стиле","Firefox 390, 768, 1280, 1920: axe 0, CLS 0, консоль чистая; CI: lint, typecheck, test, build, check:links, npm audit, gitleaks","cd ceramics-vue && npm ci && npm run dev","\u002Fog\u002Fceramics-vue.jpg",{"desktop":940,"desktop_sm":941,"mobile":942,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fceramics-vue-desktop.webp","\u002Fshots\u002Fceramics-vue-desktop-sm.webp","\u002Fshots\u002Fceramics-vue-mobile.webp",{"slug":944,"title":945,"type":430,"category":431,"lang":337,"live_url":946,"stack":947,"description":950,"highlights":951,"run":956,"hue":957,"og":958,"shots":959},"habit-tracker","habit-tracker: «Привычка», офлайн-трекер привычек (демо)","https:\u002F\u002Fewsirdev-habit.pages.dev",[948,337,433,617,949,575,340,618,596,212],"Svelte 5","Service Worker","Демо-приложение: трекер привычек на Svelte 5, работает офлайн (PWA), без аккаунта и сервера, данные остаются в браузере. Серии с льготным днём, частота «каждый день», по дням недели или N раз в неделю, тепловая карта года на SVG, напоминания через Notification API, экспорт и импорт JSON со строгой проверкой схемы. День хранится строкой в поясе пользователя, поэтому перелёты и летнее время историю не меняют.",[952,953,954,955],"Серии с льготным днём: один изолированный пропуск не рвёт серию, не чаще раза в 7 дней","Дни как строки ГГГГ-ММ-ДД в поясе пользователя; арифметика через UTC-полночь; границу суток можно сдвинуть","Офлайн: service worker с версионированным кэшем; CSP connect-src 'self', внешних запросов нет","29 тестов vitest, в том числе fast-check; кнопки не меньше 44 px, axe 0","cd habit-tracker && npm ci && npm run dev",75,"\u002Fog\u002Fhabit-tracker.jpg",{"desktop":960,"desktop_sm":961,"mobile":962,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fhabit-tracker-desktop.webp","\u002Fshots\u002Fhabit-tracker-desktop-sm.webp","\u002Fshots\u002Fhabit-tracker-mobile.webp",{"slug":964,"title":965,"type":430,"category":431,"lang":337,"live_url":966,"stack":967,"description":969,"highlights":970,"run":975,"hue":976,"og":977,"shots":978},"flashcards-sm2","flashcards-sm2: «Карточки», картотека с интервальным повторением SM-2","https:\u002F\u002Fewsirdev-cards.pages.dev",[336,337,433,615,617,968,575,340,618,596,212],"SM-2","Концепт-приложение на Vue 3: колоды карточек и повторение по алгоритму SM-2, офлайн и без бэкенда, данные в IndexedDB. Импорт CSV\u002FTSV сам определяет разделитель и кодировку (UTF-8, UTF-16 по BOM, Windows-1251), экспорт защищён от CSV-injection, есть полный JSON-бэкап со строгой валидацией. Статистика на SVG: прогноз на 30 дней, зрелость карточек, ретеншен.",[971,972,973,974],"SM-2 по описанию SuperMemo-2: EF от 1.3, интервалы 1, 6 и далее round(prev·EF), отличия от оригинала описаны","Расписание хранится датами, а не метками времени: пояса и летнее время ничего не сдвигают; границу суток можно перенести","Импорт CSV с автоопределением кодировки и разделителя, лимиты 1 МБ и 5000 строк","30 тестов vitest + fast-check; переворот карточки на чистом CSS, клавиатура (пробел, 1–4), axe 0","cd flashcards-sm2 && npm ci && npm run dev",300,"\u002Fog\u002Fflashcards-sm2.jpg",{"desktop":979,"desktop_sm":980,"mobile":981,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fflashcards-sm2-desktop.webp","\u002Fshots\u002Fflashcards-sm2-desktop-sm.webp","\u002Fshots\u002Fflashcards-sm2-mobile.webp",{"slug":983,"title":984,"type":430,"category":431,"lang":337,"live_url":985,"stack":986,"description":989,"highlights":990,"run":995,"hue":996,"og":997,"shots":998},"scratch-notes","scratch-notes: «Черновик», офлайн-редактор заметок в Markdown","https:\u002F\u002Fewsirdev-scratch.pages.dev",[987,337,433,615,988,617,575,340,618,596,212],"SolidJS","WebCrypto","Демо-концепт офлайн-редактора заметок в Markdown на SolidJS: данные в IndexedDB, сервера и аккаунтов нет. Живой предпросмотр, собственный полнотекстовый поиск (BM25, стемминг RU\u002FEN, исправление раскладки), история правок с построчным diff, шифрование отдельной заметки паролем (PBKDF2 600 000 итераций + AES-256-GCM, WebCrypto), импорт и экспорт .md и .zip со своим ZIP-писателем.",[991,992,993,994],"Парсер Markdown и санитайзер скопированы из md-safe вместе с корпусом XSS-векторов и независимым проверяющим","Шифрование: PBKDF2-HMAC-SHA-256 600 000 итераций, AES-256-GCM, новый IV на каждое сохранение, шифруются и снимки истории; модель угроз описана","Поиск: свой индекс в памяти, BM25 с весами полей, префикс, «фразы», #теги","777 тестов (в том числе корпус XSS); горячие клавиши по e.code на любой раскладке; axe 0","cd scratch-notes && npm ci && npm run dev",55,"\u002Fog\u002Fscratch-notes.jpg",{"desktop":999,"desktop_sm":1000,"mobile":1001,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fscratch-notes-desktop.webp","\u002Fshots\u002Fscratch-notes-desktop-sm.webp","\u002Fshots\u002Fscratch-notes-mobile.webp",{"slug":1003,"title":1004,"type":380,"category":380,"lang":669,"stack":1005,"description":1008,"highlights":1009,"run":1014,"hue":1015,"og":1016,"shots":1017},"dupfind","dupfind: быстрый поиск дубликатов файлов",[669,1006,1007,674,383,212,29],"BLAKE3","rayon","CLI на Rust для поиска дубликатов файлов. Файлы группируются по размеру, затем по частичному хэшу (первые и последние 4 КиБ) и только потом по полному BLAKE3; обход и хэширование параллельные. По умолчанию программа только показывает результат; удаление, жёсткие ссылки и перенос включаются --apply и перепроверяют хэш перед действием. Жёсткие ссылки одного файла дубликатами не считаются, симлинки не обходятся.",[1010,1011,1012,1013],"Демо-каталог 10 000 файлов (307 МБ, треть копии): около 120 мс на 20 потоках, около 200 мс на одном; конвейер find + sha256sum за около 1130 мс","Размер, частичный хэш, полный BLAKE3: уникальные размеры отбрасываются без чтения","Режимы --delete-newer, --hardlink, --move-to по умолчанию dry-run; код выхода 3 для CI","18 интеграционных тестов и proptest: группы в точности равны классам одинакового содержимого","cd dupfind && cargo build --release && .\u002Ftarget\u002Frelease\u002Fdupfind --help",22,"\u002Fog\u002Fdupfind.jpg",{"desktop":1018,"desktop_sm":1019,"mobile":1020,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fdupfind-desktop.webp","\u002Fshots\u002Fdupfind-desktop-sm.webp","\u002Fshots\u002Fdupfind-mobile.webp",{"slug":1022,"title":1023,"type":380,"category":380,"lang":24,"stack":1024,"description":1028,"highlights":1029,"run":1034,"hue":1035,"og":1036,"shots":1037},"sqlcsv","sqlcsv: SQL-запросы к CSV из командной строки",[24,205,1025,1026,531,1027,212],"SQL","CSV","SQLite (oracle)","CLI на Go: SQL-запросы к CSV и TSV. Один статический бинарник, только стандартная библиотека: собственный лексер, парсер и интерпретатор. Потоковая обработка: SELECT, WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, JOIN двух файлов (hash join), подсказки «did you mean» в ошибках, --explain и защита вывода от CSV-injection (--csv-safe).",[1030,1031,1032,1033],"Демо-CSV на 1 000 000 строк (39 МБ): агрегат с GROUP BY за 0,23 с и 13 МБ памяти; ранняя остановка по LIMIT за 0,03 с","Oracle-тест: 600 случайных запросов сверяются с SQLite (отдельный модуль), числа с точностью 1e-9","Fuzz парсера и движка: любая строка как запрос не вызывает панику","Ошибки с позицией и подсказкой имени колонки","cd sqlcsv && go run . \"SELECT * FROM testdata\u002Fdemo-sales.csv\"",195,"\u002Fog\u002Fsqlcsv.jpg",{"desktop":1038,"desktop_sm":1039,"mobile":1040,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Fsqlcsv-desktop.webp","\u002Fshots\u002Fsqlcsv-desktop-sm.webp","\u002Fshots\u002Fsqlcsv-mobile.webp",{"slug":1042,"title":1043,"type":380,"category":380,"lang":337,"stack":1044,"description":1048,"highlights":1049,"run":1054,"hue":1055,"og":1056,"shots":1057},"lockweigh","lockweigh: «взвешивает» lock-файл",[337,592,1045,1046,1047,340,212],"esbuild","Node SEA","YAML parser","CLI на TypeScript без runtime-зависимостей: читает package-lock.json (v2, v3), pnpm-lock.yaml (v9) и yarn.lock (classic v1) и показывает, сколько в проекте пакетов, кто тянет дубликаты версий, какие поддеревья самые тяжёлые, где install-скрипты и какие лицензии, а также почему пакет попал в проект (why) и что изменилось между двумя lock-файлами (diff). Парсеры, включая подмножество YAML, написаны с нуля. Работает офлайн и ничего не выполняет.",[1050,1051,1052,1053],"npm, pnpm и yarn в одном отчёте; на фикстуре express: 73 пакета, глубина графа 7, 2 дубликата версий","Команды why (все пути от корня) и diff двух lock-файлов; коды возврата для CI с порогами","Один бандл esbuild и одиночный исполняемый файл через Node SEA","77 тестов vitest; в runtime-зависимостях пусто, поэтому цепочка поставки это dev-инструменты","cd lockweigh && npm ci && npm run build && node dist\u002Flockweigh.cjs --help",340,"\u002Fog\u002Flockweigh.jpg",{"desktop":1058,"desktop_sm":1059,"mobile":1060,"dw":43,"dh":44,"mw":45,"mh":46},"\u002Fshots\u002Flockweigh-desktop.webp","\u002Fshots\u002Flockweigh-desktop-sm.webp","\u002Fshots\u002Flockweigh-mobile.webp",[1062,1074,1089,1103,1120],{"slug":86,"title":1063,"blurb":1064,"variant":1065,"palette":1066,"layout":1067,"count":1068,"preview":1069,"stack":1073},"Лендинги","Страницы с одной задачей: заявка, запись, подписка. Демо для разных ниш: открывайте и смотрите, как страница ведёт к действию. Вёрстка без фреймворков там, где они не нужны.","drop","ice","shots",6,[1070,1071,1072],{"slug":83,"title":84,"shot":103,"hue":99},{"slug":106,"title":107,"shot":121,"hue":117},{"slug":124,"title":125,"shot":138,"hue":134},[90,89,212,91],{"slug":406,"title":1075,"blurb":1076,"variant":1077,"palette":1078,"layout":1079,"count":1080,"langs":1081,"preview":1084,"stack":1088},"Многостраничные сайты","Сайты из нескольких страниц со своей структурой, маршрутами и формами: концепты для разных ниш. Подходит, когда одной страницы мало.","merge","violet","frames",8,[1082],{"lang":337,"count":1083},5,[1085,1086,1087],{"slug":403,"title":404,"shot":425,"hue":421},{"slug":470,"title":471,"shot":485,"hue":481},{"slug":588,"title":589,"shot":608,"hue":604},[212,337,596,618],{"slug":431,"title":1090,"blurb":1091,"variant":1092,"palette":1093,"layout":1094,"count":1068,"langs":1095,"preview":1098,"stack":1102},"Веб-приложения","Приложения, в которые входят: с сервером, авторизацией и письмами или офлайн-приложения, где данные остаются в браузере. Примеры того, как выглядит первая версия продукта.","torus","mint","phone",[1096],{"lang":337,"count":1097},4,[1099,1100,1101],{"slug":428,"title":429,"shot":451,"hue":447},{"slug":611,"title":612,"shot":629,"hue":625},{"slug":632,"title":633,"shot":646,"hue":625},[337,433,618,340],{"slug":23,"title":1104,"blurb":1105,"variant":1106,"palette":1107,"layout":1108,"count":1109,"langs":1110,"preview":1115,"stack":1119},"Бэкенды","Сервисы с API, базой и тестами на Go, Rust, Python и TypeScript: очереди, авторизация, шлюзы, мониторинг. Работают на моём VPS, их можно открыть и проверить. Фильтр по языку внутри.","crystal","steel","list",24,[1111,1112,1113,1114],{"lang":24,"count":684},{"lang":509,"count":1097},{"lang":669,"count":1097},{"lang":337,"count":1097},[1116,1117,1118],{"slug":21,"title":22,"shot":41,"hue":37},{"slug":48,"title":49,"shot":63,"hue":59},{"slug":66,"title":67,"shot":80,"hue":76},[29,212,28,24],{"slug":380,"title":1121,"blurb":1122,"variant":1123,"palette":1124,"layout":1125,"count":1068,"langs":1126,"preview":1133,"stack":1137},"CLI-утилиты","Программы для терминала: один бинарник, понятные флаги, код выхода, который проверяется в скрипте. Подходят, чтобы убрать ручную рутину: логи, CSV, отчёты по репозиторию.","swarm","champagne","terminal",[1127,1129,1131,1132],{"lang":24,"count":1128},3,{"lang":509,"count":1130},1,{"lang":669,"count":1130},{"lang":337,"count":1130},[1134,1135,1136],{"slug":378,"title":379,"shot":400,"hue":396},{"slug":507,"title":508,"shot":524,"hue":244},{"slug":527,"title":528,"shot":545,"hue":541},[212,383,29,205],{"generated_at":1139,"tests_method":1140,"note":1141,"totals":1142,"projects":1152,"catalog":1244,"code":1250},"2026-10-22","go test -list","Lines are non-blank lines. Endpoints are HandleFunc\u002FHandle route patterns (without \u002Fdemo\u002F*). Tests are top-level Test* functions.",{"projects":1143,"go_projects":1144,"go_lines":1145,"go_test_lines":1146,"tests":1147,"endpoints":1148,"html":1149,"css":1150,"js":1151},17,13,26503,12027,418,230,1973,3525,4815,[1153,1162,1169,1175,1181,1185,1192,1198,1203,1206,1209,1212,1215,1220,1227,1233,1238],{"slug":201,"kind":1154,"go_lines":1155,"go_test_lines":1156,"tests":1157,"endpoints":1158,"html":1159,"css":1160,"js":1161},"go",2334,1179,48,11,115,139,163,{"slug":304,"kind":1154,"go_lines":1163,"go_test_lines":1164,"tests":1165,"endpoints":541,"html":1166,"css":1167,"js":1168},2826,1393,50,23,166,500,{"slug":66,"kind":1154,"go_lines":1170,"go_test_lines":1171,"tests":684,"endpoints":881,"html":1172,"css":1173,"js":1174},891,338,146,123,309,{"slug":331,"kind":1154,"go_lines":1176,"go_test_lines":1177,"tests":1178,"endpoints":625,"html":1109,"css":1179,"js":1180},3741,1371,30,1148,72,{"slug":378,"kind":1154,"go_lines":1182,"go_test_lines":1183,"tests":481,"endpoints":1128,"html":1184,"css":1184,"js":1184},2133,735,0,{"slug":161,"kind":1154,"go_lines":1186,"go_test_lines":1187,"tests":604,"endpoints":1188,"html":1189,"css":1190,"js":1191},2161,833,19,138,188,331,{"slug":21,"kind":1154,"go_lines":1193,"go_test_lines":1194,"tests":396,"endpoints":881,"html":1195,"css":1196,"js":1197},871,272,102,156,328,{"slug":251,"kind":1199,"go_lines":1184,"go_test_lines":1184,"tests":1184,"endpoints":1184,"html":1200,"css":1201,"js":1202},"static",201,162,377,{"slug":124,"kind":1199,"go_lines":1184,"go_test_lines":1184,"tests":1184,"endpoints":1184,"html":1172,"css":1204,"js":1205},122,59,{"slug":106,"kind":1199,"go_lines":1184,"go_test_lines":1184,"tests":1184,"endpoints":1184,"html":1207,"css":1208,"js":1205},172,136,{"slug":83,"kind":1199,"go_lines":1184,"go_test_lines":1184,"tests":1184,"endpoints":1184,"html":1210,"css":1211,"js":1205},187,109,{"slug":48,"kind":1154,"go_lines":1170,"go_test_lines":1197,"tests":684,"endpoints":1158,"html":1213,"css":1201,"js":1214},119,317,{"slug":227,"kind":1154,"go_lines":1216,"go_test_lines":1193,"tests":481,"endpoints":1188,"html":1217,"css":1218,"js":1219},2027,174,192,517,{"slug":357,"kind":1154,"go_lines":1221,"go_test_lines":1222,"tests":1223,"endpoints":1224,"html":1225,"css":1218,"js":1226},2715,1910,77,16,106,625,{"slug":276,"kind":1154,"go_lines":1228,"go_test_lines":1229,"tests":1230,"endpoints":1144,"html":1231,"css":1217,"js":1232},2646,895,27,91,213,{"slug":180,"kind":1154,"go_lines":1234,"go_test_lines":1235,"tests":1157,"endpoints":396,"html":1236,"css":1237,"js":1219},1822,1313,103,184,{"slug":141,"kind":1154,"go_lines":1239,"go_test_lines":1240,"tests":881,"endpoints":1241,"html":1242,"css":1207,"js":1243},1445,589,15,126,369,{"by_category":1245,"by_language":1246,"live":1248,"live_cloudflare":1188,"live_vps":1109,"not_live":1249,"projects":1165},{"apps":1068,"backend":1109,"cli":1068,"landings":1068,"sites":1080},{"Go":1241,"HTML":1158,"Python":1083,"Rust":1083,"TypeScript":1247},14,43,[403,428,507,527,1003,1022,1042],{"by_language":1251,"files":1291,"lines":1292,"method":1293,"repos":1294},[1252,1256,1260,1264,1268,1271,1275,1279,1282,1286],{"files":1253,"lang":24,"lines":1254,"pct":1255},459,70081,38.4,{"files":1257,"lang":337,"lines":1258,"pct":1259},347,31546,17.3,{"files":1261,"lang":492,"lines":1262,"pct":1263},227,19973,10.9,{"files":1265,"lang":90,"lines":1266,"pct":1267},60,18128,9.9,{"files":117,"lang":1269,"lines":1270,"pct":1080},"Vue",14540,{"files":1272,"lang":509,"lines":1273,"pct":1274},96,11865,6.5,{"files":1276,"lang":669,"lines":1277,"pct":1278},47,7941,4.3,{"files":1204,"lang":89,"lines":1280,"pct":1281},6911,3.8,{"files":1283,"lang":653,"lines":1284,"pct":1285},26,1071,0.6,{"files":1287,"lang":1288,"lines":1289,"pct":1290},9,"Svelte",571,0.3,1575,182627,"git ls-files over every project repository and this site; non-blank lines of source files (Go, TypeScript, JavaScript, Vue, Python, Rust, Astro, Svelte, HTML, CSS), comments included; vendored and shared UI-kit copies, node_modules, build output, minified files, generated font-face css and demo reports, lock files, type stubs, fonts, images, JSON and Markdown are excluded",49,1794340625732]