[{"data":1,"prerenderedAt":844},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fprofile:":8,"api:\u002Fapi\u002Fcategories:en":19,"api:\u002Fapi\u002Fprojects:en":171,"api:\u002Fapi\u002Fstats:":736},{"email":4,"lang":5,"name":6,"role":7},"djrciv@gmail.com","en","Ewsirdev","Web developer: websites, landing pages and Go services",{"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,49,80,108,142],{"slug":21,"title":22,"blurb":23,"variant":24,"palette":25,"layout":26,"count":27,"preview":28,"stack":44},"landings","Landing pages","One page, one job. Hand-written markup where possible, plus one WebGL experiment.","ink","cold","shots",6,[29,34,39],{"slug":30,"title":31,"shot":32,"hue":33},"landing-studio","Halftone: lighting studio","\u002Fshots\u002Flanding-studio-desktop-sm.webp",212,{"slug":35,"title":36,"shot":37,"hue":38},"landing-saas","Kill Switch: subscription tracker","\u002Fshots\u002Flanding-saas-desktop-sm.webp",182,{"slug":40,"title":41,"shot":42,"hue":43},"landing-event","Small Scale: conference","\u002Fshots\u002Flanding-event-desktop-sm.webp",124,[45,46,47,48],"CSS","HTML","GitHub Actions","JS",{"slug":50,"title":51,"blurb":52,"variant":53,"palette":54,"layout":55,"count":56,"langs":57,"preview":61,"stack":77},"sites","Multi-page sites","Sites with their own structure, routing and forms: from a client project to a set of niche pages.","honey","mono","frames",4,[58],{"lang":59,"count":60},"TypeScript",1,[62,67,72],{"slug":63,"title":64,"shot":65,"hue":66},"legal-multipage-site","Multi-page legal services website","\u002Fshots\u002Flegal-multipage-site-desktop-sm.webp",250,{"slug":68,"title":69,"shot":70,"hue":71},"niche-landings","Catalog of 10 niche demo landing pages","\u002Fshots\u002Fniche-landings-desktop-sm.webp",32,{"slug":73,"title":74,"shot":75,"hue":76},"docs-engine","Sverlo: a documentation generator in Node.js","\u002Fshots\u002Fdocs-engine-desktop-sm.webp",25,[47,59,78,79],"Vue 3","Astro",{"slug":81,"title":82,"blurb":83,"variant":84,"palette":85,"layout":86,"count":87,"langs":88,"preview":90,"stack":105},"apps","Web apps","Interface plus server: auth, data, e-mail. Apps you sign in to.","beads","warm","phone",3,[89],{"lang":59,"count":60},[91,96,101],{"slug":92,"title":93,"shot":94,"hue":95},"bad-club-booking-app","Bad Club: seat booking for a gaming coworking space","\u002Fshots\u002Fbad-club-booking-app-desktop-sm.webp",150,{"slug":97,"title":98,"shot":99,"hue":100},"typing-lab","typing-lab: a touch-typing trainer","\u002Fshots\u002Ftyping-lab-desktop-sm.webp",45,{"slug":102,"title":103,"shot":104,"hue":100},"budget-split","budget-split: shared expense tracker","\u002Fshots\u002Fbudget-split-desktop-sm.webp",[59,106,78,107],"Vite","axe",{"slug":109,"title":110,"blurb":111,"variant":112,"palette":113,"layout":114,"count":115,"langs":116,"preview":123,"stack":139},"backend","Backends","Services with an API, a database and tests: mostly Go, with some in Python and TypeScript. A language filter sits inside the section.","ferro","acid","list",14,[117,120,122],{"lang":118,"count":119},"Go",12,{"lang":121,"count":60},"Python",{"lang":59,"count":60},[124,129,134],{"slug":125,"title":126,"shot":127,"hue":128},"kanban-tasks","Amber: kanban board","\u002Fshots\u002Fkanban-tasks-desktop-sm.webp",344,{"slug":130,"title":131,"shot":132,"hue":133},"link-shortener","Short: link shortener","\u002Fshots\u002Flink-shortener-desktop-sm.webp",111,{"slug":135,"title":136,"shot":137,"hue":138},"booking-slots","Appointment booking","\u002Fshots\u002Fbooking-slots-desktop-sm.webp",65,[140,118,141,47],"Docker","SQLite",{"slug":143,"title":144,"blurb":145,"variant":146,"palette":25,"layout":147,"count":87,"langs":148,"preview":152,"stack":168},"cli","CLI tools","Terminal programs: one binary, clear flags, an exit code you can check in a script.","film","terminal",[149,151],{"lang":118,"count":150},2,{"lang":121,"count":60},[153,158,163],{"slug":154,"title":155,"shot":156,"hue":157},"gitpulse","GitPulse: git repository analytics","\u002Fshots\u002Fgitpulse-desktop-sm.webp",7,{"slug":159,"title":160,"shot":161,"hue":162},"logslice","logslice: log analysis with no dependencies","\u002Fshots\u002Flogslice-desktop-sm.webp",160,{"slug":164,"title":165,"shot":166,"hue":167},"envguard","envguard: a linter and a safe for .env files","\u002Fshots\u002Fenvguard-desktop-sm.webp",35,[169,140,47,170],"CLI","stdlib only",[172,191,205,218,233,247,260,280,299,320,344,367,391,417,444,468,489,508,528,548,564,578,597,613,630,650,670,689,705,719],{"slug":125,"title":126,"type":109,"category":109,"lang":118,"kind":173,"live_url":174,"stack":175,"description":176,"highlights":177,"run":182,"hue":128,"og":183,"shots":184},"api","https:\u002F\u002Fkanban-tasks.ewsir.space",[118,141,140],"A multi-user kanban board: sign-up, boards, columns, drag-and-drop cards. Go + SQLite without CGO, the front end is embedded in the binary.",[178,179,180,181],"PBKDF2 and sessions in an HttpOnly cookie","Per-user data isolation","API tests","Multi-stage Docker","cd kanban-tasks && go run .","\u002Fog\u002Fen\u002Fkanban-tasks.jpg",{"desktop":185,"desktop_sm":127,"mobile":186,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fkanban-tasks-desktop.webp","\u002Fshots\u002Fkanban-tasks-mobile.webp",1280,800,390,844,{"slug":130,"title":131,"type":109,"category":109,"lang":118,"kind":173,"live_url":192,"stack":193,"description":194,"highlights":195,"run":200,"hue":133,"og":201,"shots":202},"https:\u002F\u002Flink-shortener.ewsir.space",[118,141,140],"Link shortening with aliases and expiry, click statistics by day, browser and device, a token-protected dashboard. IP addresses are not stored.",[196,197,198,199],"Redirect and click tracking","Rate limiting","Private statistics (salted hash)","Tests and Docker","cd link-shortener && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver","\u002Fog\u002Fen\u002Flink-shortener.jpg",{"desktop":203,"desktop_sm":132,"mobile":204,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Flink-shortener-desktop.webp","\u002Fshots\u002Flink-shortener-mobile.webp",{"slug":135,"title":136,"type":109,"category":109,"lang":118,"kind":173,"live_url":206,"stack":207,"description":208,"highlights":209,"run":213,"hue":138,"og":214,"shots":215},"https:\u002F\u002Fbooking-slots.ewsir.space",[118,141,140],"Online booking: free slots computed from service duration and working hours, double-booking protection, an admin panel. A fictional studio, demo.",[210,211,212,199],"Free slot calculation","No double bookings","Admin for services and schedule","cd booking-slots && go run .","\u002Fog\u002Fen\u002Fbooking-slots.jpg",{"desktop":216,"desktop_sm":137,"mobile":217,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fbooking-slots-desktop.webp","\u002Fshots\u002Fbooking-slots-mobile.webp",{"slug":30,"title":31,"type":219,"category":21,"live_url":220,"stack":221,"description":222,"highlights":223,"run":228,"hue":33,"og":229,"shots":230},"landing","https:\u002F\u002Fewsirdev-studio.pages.dev",[46,45,48],"A landing page for a fictional interior lighting studio: warm palette, serif type, SVG illustration, FAQ and a demo form. Responsive, no external images.",[224,225,226,227],"Responsive 390-1280","SVG instead of images","Accessible FAQ and form","An honest demo form","cd landing-studio && python -m http.server 8091","\u002Fog\u002Fen\u002Flanding-studio.jpg",{"desktop":231,"desktop_sm":32,"mobile":232,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Flanding-studio-desktop.webp","\u002Fshots\u002Flanding-studio-mobile.webp",{"slug":35,"title":36,"type":219,"category":21,"live_url":234,"stack":235,"description":236,"highlights":237,"run":242,"hue":38,"og":243,"shots":244},"https:\u002F\u002Fewsirdev-saas.pages.dev",[46,45,48],"A landing page for a fictional SaaS product: dark gradient hero, a UI mock-up in CSS, pricing, FAQ, a demo form. All prices and names are made up.",[238,239,240,241],"UI mock-up in CSS","Pricing grid","Responsive","Demo form that sends no data","cd landing-saas && python -m http.server 8092","\u002Fog\u002Fen\u002Flanding-saas.jpg",{"desktop":245,"desktop_sm":37,"mobile":246,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Flanding-saas-desktop.webp","\u002Fshots\u002Flanding-saas-mobile.webp",{"slug":40,"title":41,"type":219,"category":21,"live_url":248,"stack":249,"description":250,"highlights":251,"run":255,"hue":43,"og":256,"shots":257},"https:\u002F\u002Fewsirdev-event.pages.dev",[46,45,48],"A landing page for a fictional one-day conference: poster typography, schedule, placeholder speakers, terms, a demo registration form.",[252,253,240,254],"Day schedule","Poster typography","No invented speaker names","cd landing-event && python -m http.server 8093","\u002Fog\u002Fen\u002Flanding-event.jpg",{"desktop":258,"desktop_sm":42,"mobile":259,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Flanding-event-desktop.webp","\u002Fshots\u002Flanding-event-mobile.webp",{"slug":261,"title":262,"type":109,"category":109,"lang":118,"kind":263,"live_url":264,"stack":265,"description":268,"highlights":269,"run":273,"hue":274,"og":275,"shots":276},"uptime-monitor","Pulse: uptime monitor","infra","https:\u002F\u002Fuptime-monitor.ewsir.space",[118,141,266,267,140],"SSE","REST API","An uptime monitor: scheduled HTTP and TCP checks, uptime, p50\u002Fp95, an incident detector, a live SSE dashboard and a public status page. Go + SQLite without CGO.",[270,271,272,199],"A worker goroutine per check","Incidents after a series of failures","SSE dashboard and a public \u002Fstatus","cd uptime-monitor && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",101,"\u002Fog\u002Fen\u002Fuptime-monitor.jpg",{"desktop":277,"desktop_sm":278,"mobile":279,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fuptime-monitor-desktop.webp","\u002Fshots\u002Fuptime-monitor-desktop-sm.webp","\u002Fshots\u002Fuptime-monitor-mobile.webp",{"slug":281,"title":282,"type":109,"category":109,"lang":118,"kind":263,"live_url":283,"stack":284,"description":286,"highlights":287,"run":292,"hue":293,"og":294,"shots":295},"job-queue","Conveyor: job queue","https:\u002F\u002Fjob-queue.ewsir.space",[118,141,267,266,285,140],"Prometheus","A background job queue: priorities, delayed and cron jobs, retries with backoff and jitter, dead-letter, idempotency keys, leases with fencing, Prometheus metrics.",[288,289,290,291],"At-least-once with lease and fencing","Retries with backoff and jitter + dead-letter","Custom cron parser","Prometheus metrics and graceful shutdown","cd job-queue && set API_TOKEN=demo&& set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",249,"\u002Fog\u002Fen\u002Fjob-queue.jpg",{"desktop":296,"desktop_sm":297,"mobile":298,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fjob-queue-desktop.webp","\u002Fshots\u002Fjob-queue-desktop-sm.webp","\u002Fshots\u002Fjob-queue-mobile.webp",{"slug":300,"title":301,"type":109,"category":109,"lang":118,"kind":302,"live_url":303,"stack":304,"description":307,"highlights":308,"run":313,"hue":314,"og":315,"shots":316},"realtime-rooms","Rooms: realtime chat and board","realtime","https:\u002F\u002Frealtime-rooms.ewsir.space",[118,305,141,306,140],"WebSocket","Canvas","Realtime rooms: chat with SQLite history, an online list, a typing indicator, a shared board with strokes and cursors, anonymous nicknames. WebSocket (RFC 6455) written on the stdlib.",[309,310,311,312],"Own WebSocket layer (RFC 6455) on the stdlib","Slow-client disconnects and rate limits","Graceful shutdown with close code 1001","Load test: 360 connections in 6 rooms","cd realtime-rooms && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",46,"\u002Fog\u002Fen\u002Frealtime-rooms.jpg",{"desktop":317,"desktop_sm":318,"mobile":319,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Frealtime-rooms-desktop.webp","\u002Fshots\u002Frealtime-rooms-desktop-sm.webp","\u002Fshots\u002Frealtime-rooms-mobile.webp",{"slug":321,"title":322,"type":109,"category":109,"lang":118,"kind":263,"live_url":323,"stack":324,"description":331,"highlights":332,"run":337,"hue":338,"og":339,"shots":340},"api-gateway","Sluice: API gateway","https:\u002F\u002Fapi-gateway.ewsir.space",[118,170,325,326,327,266,285,328,329,330,140,47],"Reverse proxy","Circuit breaker","Token bucket","slog","Vanilla JS","embed","An API gateway on the pure stdlib: host\u002Fprefix routes from a JSON config with hot reload that keeps connections alive, round-robin and least-conn balancing, health checks, a per-node circuit breaker, retries of idempotent requests, rate limiting, hashed API keys, Prometheus metrics and a live SSE dashboard.",[333,334,335,336],"Zero dependencies: stdlib only","Config hot reload without dropping connections","Per-node circuit breaker and retries of idempotent requests","Load test: 4000 requests, 40 clients (a demo run)","cd api-gateway && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fgateway",200,"\u002Fog\u002Fen\u002Fapi-gateway.jpg",{"desktop":341,"desktop_sm":342,"mobile":343,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fapi-gateway-desktop.webp","\u002Fshots\u002Fapi-gateway-desktop-sm.webp","\u002Fshots\u002Fapi-gateway-mobile.webp",{"slug":345,"title":346,"type":109,"category":109,"lang":118,"kind":347,"live_url":348,"stack":349,"description":355,"highlights":356,"run":361,"hue":162,"og":362,"shots":363},"notes-search","Card Index: notes with search","data","https:\u002F\u002Fnotes-search.ewsir.space",[118,141,350,351,352,267,353,354,329,330,140,47],"FTS5","bm25","modernc.org\u002Fsqlite (no CGO)","Markdown (custom safe renderer)","SVG force-layout","A Markdown notes base on Go and SQLite: [[wiki]] links with backlinks and a link graph, FTS5 full-text search (bm25, highlighting, simplified Russian morphology), version history with its own diff, autosave with conflict resolution, .md export and import.",[357,358,359,360],"FTS5 search with bm25 and highlighting","Wiki links, backlinks and a link graph","Version history with its own diff","A custom safe Markdown renderer with an XSS test","cd notes-search && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver","\u002Fog\u002Fen\u002Fnotes-search.jpg",{"desktop":364,"desktop_sm":365,"mobile":366,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fnotes-search-desktop.webp","\u002Fshots\u002Fnotes-search-desktop-sm.webp","\u002Fshots\u002Fnotes-search-mobile.webp",{"slug":368,"title":369,"type":219,"category":21,"live_url":370,"stack":371,"description":378,"highlights":379,"run":384,"hue":385,"og":386,"shots":387},"landing-3d","OKSO T1: 3D landing page","https:\u002F\u002Fewsirdev-3d.pages.dev",[372,373,329,374,375,376,377,47],"HTML5","CSS3","WebGL2","GLSL ES 3.00","Raymarching\u002FSDF","Scroll choreography","A landing page for a fictional air sensor (demo): the object is drawn by one fragment shader on plain WebGL2 with no libraries, four SDF shapes morph on scroll, with an SVG fallback, reduced-motion support and a pause when off screen.",[380,381,382,383],"One fragment shader, WebGL2 without libraries","Four SDF shapes morphing on scroll","SVG fallback and prefers-reduced-motion","Rendering pauses off screen","cd landing-3d && python -m http.server 8094",90,"\u002Fog\u002Fen\u002Flanding-3d.jpg",{"desktop":388,"desktop_sm":389,"mobile":390,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Flanding-3d-desktop.webp","\u002Fshots\u002Flanding-3d-desktop-sm.webp","\u002Fshots\u002Flanding-3d-mobile.webp",{"slug":392,"title":393,"type":109,"category":109,"lang":118,"kind":347,"live_url":394,"stack":395,"description":404,"highlights":405,"run":410,"hue":411,"og":412,"shots":413},"raft-kv","Raft KV: a key-value store on Raft","https:\u002F\u002Fraft-kv.ewsir.space",[118,396,397,398,170,399,266,400,401,402,403,329,140,47],"Raft","Distributed systems","Consensus","HTTP\u002FJSON","WAL + fsync","Snapshots","Linearizability","Deterministic simulation testing","A from-scratch teaching implementation of Raft on the pure stdlib: leader election, log replication, quorum commit, a log and term\u002Fvote on disk with fsync, snapshots with compaction, linearizable reads (read-index) and follower-to-leader redirects. It is tested with a deterministic network and time simulator that checks linearizability; the dashboard can kill the leader and cut the network (demo).",[406,407,408,409],"Raft from scratch on the stdlib, no raft libraries","A deterministic network and time simulator + a linearizability check","Log and term\u002Fvote on disk with fsync, snapshots and compaction","A dashboard to kill the leader and cut the network (demo)","cd raft-kv && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fraft-kv",290,"\u002Fog\u002Fen\u002Fraft-kv.jpg",{"desktop":414,"desktop_sm":415,"mobile":416,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fraft-kv-desktop.webp","\u002Fshots\u002Fraft-kv-desktop-sm.webp","\u002Fshots\u002Fraft-kv-mobile.webp",{"slug":418,"title":419,"type":109,"category":109,"lang":118,"kind":420,"live_url":421,"stack":422,"description":431,"highlights":432,"run":437,"hue":438,"og":439,"shots":440},"auth-service","Keyring: an authentication service","auth","https:\u002F\u002Fauth-service.ewsir.space",[118,423,141,352,424,425,426,427,428,429,430,329,330,140,47],"stdlib crypto","JWT EdDSA\u002FEd25519","JWKS","PBKDF2","TOTP","RBAC","REST","OpenAPI 3","An authentication and authorization service in Go: EdDSA JWTs with key rotation and JWKS, refresh tokens with rotation and theft detection, TOTP 2FA (RFC 6238) with backup codes, RBAC, lockout on brute force, password reset by a one-time token, an audit log, a device list, openapi.yaml. Not independently audited, not a production IdP.",[433,434,435,436],"EdDSA JWTs with key rotation and JWKS","Refresh tokens with rotation and reuse detection","TOTP per RFC 6238, checked against the RFC test vectors","An append-only audit log (database triggers)","cd auth-service && set SEED_DEMO=1&& go run .\u002Fcmd\u002Fserver",20,"\u002Fog\u002Fen\u002Fauth-service.jpg",{"desktop":441,"desktop_sm":442,"mobile":443,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fauth-service-desktop.webp","\u002Fshots\u002Fauth-service-desktop-sm.webp","\u002Fshots\u002Fauth-service-mobile.webp",{"slug":445,"title":446,"type":109,"category":109,"lang":118,"kind":173,"live_url":447,"stack":448,"description":455,"highlights":456,"run":461,"hue":462,"og":463,"shots":464},"finance-tracker","Ledger: personal finance tracker","https:\u002F\u002Ffinance-tracker.ewsir.space",[118,141,352,267,449,78,59,450,451,452,453,140,47,426,454],"Nuxt 4","Pinia","SVG charts","vitest","ESLint","CSV import\u002Fexport","Personal finance tracking: accounts in different currencies with manual exchange rates, income, expenses and transfers, budgets, payments on a cron-like schedule, CSV import with a preview, reports. Money is stored only in integer minor units, users' data is isolated, and the charts are drawn in SVG with no library. Go + SQLite with a Nuxt 4 front end.",[457,458,459,460],"Money only in integer minor units","Multi-currency with manual exchange rates","CSV import with a preview and a dry run","Hand-made SVG charts, no library","cd finance-tracker && set SEED_DEMO=1&& go run .",140,"\u002Fog\u002Fen\u002Ffinance-tracker.jpg",{"desktop":465,"desktop_sm":466,"mobile":467,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Ffinance-tracker-desktop.webp","\u002Fshots\u002Ffinance-tracker-desktop-sm.webp","\u002Fshots\u002Ffinance-tracker-mobile.webp",{"slug":469,"title":470,"type":109,"category":109,"lang":118,"kind":302,"live_url":471,"stack":472,"description":476,"highlights":477,"run":482,"hue":483,"og":484,"shots":485},"pixel-wall","Pixel Wall: a shared canvas","https:\u002F\u002Fpixel-wall.ewsir.space",[118,170,305,473,329,330,474,475,140,47],"Canvas 2D","Binary protocol","Snapshot + journal","A shared 256x256 canvas in the spirit of r\u002Fplace: 16 colors, a cooldown per session and IP, an own WebSocket layer on the stdlib (RFC 6455) with a binary protocol (4 bits per pixel), a snapshot plus deltas, a snapshot with a journal and recovery, a timelapse, zoom and pan, a minimap, PNG export and admin tools. A side project.",[478,479,480,481],"Own WebSocket (RFC 6455) on the stdlib","A binary protocol: 4 bits per pixel","Snapshot + journal with crash recovery","A timelapse over the change journal","cd pixel-wall && set SEED_DEMO=1&& set ADMIN_TOKEN=secret&& go run .\u002Fcmd\u002Fserver",320,"\u002Fog\u002Fen\u002Fpixel-wall.jpg",{"desktop":486,"desktop_sm":487,"mobile":488,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fpixel-wall-desktop.webp","\u002Fshots\u002Fpixel-wall-desktop-sm.webp","\u002Fshots\u002Fpixel-wall-mobile.webp",{"slug":154,"title":155,"type":143,"category":143,"lang":118,"live_url":490,"stack":491,"description":497,"highlights":498,"run":503,"hue":157,"og":504,"shots":505},"https:\u002F\u002Fgitpulse.ewsir.space",[118,170,169,492,493,494,495,496,47,140],"git","html\u002Ftemplate","SVG","ANSI","os\u002Fexec","A Go CLI that analyzes git repositories with no external dependencies (only git is needed): stats with colors and sparklines in the terminal, report, a self-contained HTML report with an activity heatmap, churn and hot files, author contributions, bus factor per directory, code growth by language and quiet periods (light and dark themes), diff-hotspots between revisions and a local serve on 127.0.0.1. History is read as a stream through git log -z --numstat -M. A side project.",[499,500,501,502],"Go stdlib only, a single external binary: git","A git log -z --numstat -M stream with .mailmap and renames handled","A self-contained HTML report: heatmap, churn, bus factor, light and dark themes","A synthetic history of 100,000 commits is analyzed in about 5 seconds","cd gitpulse && go run .\u002Fcmd\u002Fgitpulse stats .","\u002Fog\u002Fen\u002Fgitpulse.jpg",{"desktop":506,"desktop_sm":156,"mobile":507,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fgitpulse-desktop.webp","\u002Fshots\u002Fgitpulse-mobile.webp",{"slug":63,"title":64,"type":509,"category":50,"stack":510,"description":515,"highlights":516,"run":523,"hue":66,"og":524,"shots":525},"site",[449,78,59,511,512,513,514,140,47],"Bootstrap 5","Zod","Vitest","Playwright","A delivered website for a legal and expert-appraisal firm built with Nuxt 4: 65 server-rendered pages, JSON-LD markup and 301 redirects from the previous site. Lead requests go through a server endpoint that checks Origin, caps body size and rate-limits clients.",[517,518,519,520,521,522],"65 SSR routes, generated sitemap.xml and robots.txt; indexing is switched on by an explicit env flag","Lead form: server endpoint with Origin check, 10 KB body cap, 3 requests per 10 minutes per IP, honeypot and optional captcha","JSON-LD: LegalService, WebSite, BreadcrumbList","Single-hop 301 redirects from legacy URLs and trailing slashes, security headers (CSP, HSTS)","CI\u002FCD: lint, typecheck, Vitest, build, Playwright; then a Docker image and server deploy","210 unit tests in 13 files and 4 e2e spec files","npm install && npm run build && node .output\u002Fserver\u002Findex.mjs","\u002Fog\u002Fen\u002Flegal-multipage-site.jpg",{"desktop":526,"desktop_sm":65,"mobile":527,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Flegal-multipage-site-desktop.webp","\u002Fshots\u002Flegal-multipage-site-mobile.webp",{"slug":92,"title":93,"type":529,"category":81,"stack":530,"description":537,"highlights":538,"run":543,"hue":95,"og":544,"shots":545},"app",[78,106,450,531,532,533,534,535,536],"Vue Router","GSAP","Express","node:sqlite","JWT (httpOnly cookie)","Resend","A demo app on Vue 3, Express and SQLite: an interactive hall map with seat booking, a user account and an admin panel with a layout editor. Passwordless sign-in via a one-time e-mail code.",[539,540,541,542],"Hall map with zones (standard, quiet, VIP, dual-monitor), a type filter and seat status","Admin panel: overview, bookings, seats, computers, users and a drag-and-drop layout editor","OTP sign-in with attempt and rate limits; the admin role is bootstrapped via an env variable","Demo data is created by a seed script on first start","npm install && npm run dev","\u002Fog\u002Fen\u002Fbad-club-booking-app.jpg",{"desktop":546,"desktop_sm":94,"mobile":547,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fbad-club-booking-app-desktop.webp","\u002Fshots\u002Fbad-club-booking-app-mobile.webp",{"slug":549,"title":550,"type":219,"category":21,"live_url":551,"stack":552,"description":553,"highlights":554,"run":543,"hue":558,"og":559,"shots":560},"dovod-concept","Dovod: expert-firm landing concept","https:\u002F\u002Fewsirdev-dovod.pages.dev",[449,78,59,45,47],"A Nuxt 4 landing concept for a fictional expert-appraisal firm: service areas, a consultation form with a phone mask, a reviews carousel and a policy page. The company, reviews, figures and contacts are made up; the form sends nothing.",[555,556,557],"Form with a phone mask, validation and error hints; in the demo it states that nothing is sent","Endless reviews carousel with swipe and autoplay","Mobile action bar, responsive layout, skeleton overlay on top of server-rendered HTML",215,"\u002Fog\u002Fen\u002Fdovod-concept.jpg",{"desktop":561,"desktop_sm":562,"mobile":563,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fdovod-concept-desktop.webp","\u002Fshots\u002Fdovod-concept-desktop-sm.webp","\u002Fshots\u002Fdovod-concept-mobile.webp",{"slug":68,"title":69,"type":509,"category":50,"live_url":565,"stack":566,"description":569,"highlights":570,"run":543,"hue":71,"og":574,"shots":575},"https:\u002F\u002Fewsirdev-niche.pages.dev",[78,567,568,47],"Vue Router 4","Vite 8","A Vue catalog of ten standalone landing pages for different small-business niches, each with its own palette, fonts and section structure. Every landing loads as a separate chunk; all content is demonstration content.",[571,572,573],"10 niches: medicine, furniture, renovation, online school, glamping, legal, auto service, beauty, cafe, fitness","Route-level code splitting, a shared SVG icon set","A \"demo content\" badge on the catalog and every landing","\u002Fog\u002Fen\u002Fniche-landings.jpg",{"desktop":576,"desktop_sm":70,"mobile":577,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fniche-landings-desktop.webp","\u002Fshots\u002Fniche-landings-mobile.webp",{"slug":579,"title":580,"type":219,"category":21,"live_url":581,"stack":582,"description":585,"highlights":586,"run":590,"hue":591,"og":592,"shots":593},"dental-concept","Kvarts: dental clinic landing concept","https:\u002F\u002Fewsirdev-dental.pages.dev",[46,45,583,473,584,47],"JavaScript","htmlhint","A single-page landing concept for a fictional dental clinic in the Liquid Chrome Dark style: glass cards, chrome headings and a canvas-drawn 3D tooth blob. One HTML file with no build step or dependencies.",[587,588,589],"Animated canvas blob that respects prefers-reduced-motion and viewport visibility","Booking form with a phone mask and accessible error messages","CI: htmlhint and a local links and anchors check","python -m http.server 8080",190,"\u002Fog\u002Fen\u002Fdental-concept.jpg",{"desktop":594,"desktop_sm":595,"mobile":596,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fdental-concept-desktop.webp","\u002Fshots\u002Fdental-concept-desktop-sm.webp","\u002Fshots\u002Fdental-concept-mobile.webp",{"slug":159,"title":160,"type":143,"category":143,"lang":121,"stack":598,"description":602,"highlights":603,"run":608,"hue":162,"og":609,"shots":610},[121,170,169,494,599,600,601,47,140],"pytest","ruff","mypy","A Python 3.11+ CLI on the standard library only: it streams nginx\u002Fapache (combined), JSON-lines, logfmt and syslog logs (gz, bz2, xz too), computes statistics and tops, clusters similar errors, finds load spikes by z-score and EWMA and builds a single HTML report with SVG charts and a heatmap. A side project.",[604,605,606,607],"Standard library only, pip install pulls nothing","Error clusters by template and anomalies with two methods (z-score and EWMA)","One self-contained HTML report: SVG, heatmap, light and dark themes","53 tests, ruff and mypy --strict; 1 million combined lines in 8.2 s (a single run, Windows, CPython 3.12.10)","logslice report access.log -o report.html","\u002Fog\u002Fen\u002Flogslice.jpg",{"desktop":611,"desktop_sm":161,"mobile":612,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Flogslice-desktop.webp","\u002Fshots\u002Flogslice-mobile.webp",{"slug":164,"title":165,"type":143,"category":143,"lang":118,"stack":614,"description":619,"highlights":620,"run":625,"hue":167,"og":626,"shots":627},[118,170,169,615,426,616,617,618,47,140],"AES-256-GCM","fuzzing","govulncheck","gitleaks","A Go CLI with no external modules: check compares .env with a schema, scan looks for secrets in the tree and git history, diff and doctor check environments and git hygiene, seal and unseal encrypt .env into .env.sealed (AES-256-GCM, PBKDF2 with 600k iterations), run starts a command with a clean environment, report and serve build a report. A secret value never reaches the output, the log or the HTML. A side project.",[621,622,623,624],"Secret values are never printed: only the finding type, file, line and a 4-character mask","seal and unseal: AES-256-GCM, PBKDF2 with 600k iterations, any flipped bit of the file is rejected","Fuzz tests: 4 targets of 20 seconds each without crashes; govulncheck and gitleaks are clean","Builds for windows, linux and darwin, standard library only","go run .\u002Fcmd\u002Fenvguard check","\u002Fog\u002Fen\u002Fenvguard.jpg",{"desktop":628,"desktop_sm":166,"mobile":629,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fenvguard-desktop.webp","\u002Fshots\u002Fenvguard-mobile.webp",{"slug":631,"title":632,"type":109,"category":109,"lang":121,"live_url":633,"stack":634,"description":637,"highlights":638,"run":643,"hue":644,"og":645,"shots":646},"catalog-api","catalog-api: a catalog API with search","https:\u002F\u002Fcatalog-api.ewsir.space",[121,635,141,350,599,600,601,636,140,47],"FastAPI","bandit","A product catalog API on Python 3.12 and FastAPI: SQLite with FTS5 full-text search (bm25 ranking), facets by category, price, availability, tags and attributes, cursor pagination, ETag, CSV import and export, price history, API keys with roles and an audit log. The storefront and the admin panel are served by the app itself, with no framework. A side project.",[639,640,641,642],"FTS5 search with bm25, facets and cursor pagination","API keys with the roles admin, editor and reader; only key hashes are stored","511 tests, 96% coverage; ruff, mypy --strict, bandit, pip-audit and gitleaks are clean","Load: 339 requests\u002Fs, p95 50 ms at 8 connections (Windows, a single process)","SEED_DEMO=1 python -m app   # http:\u002F\u002F127.0.0.1:8110",265,"\u002Fog\u002Fen\u002Fcatalog-api.jpg",{"desktop":647,"desktop_sm":648,"mobile":649,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fcatalog-api-desktop.webp","\u002Fshots\u002Fcatalog-api-desktop-sm.webp","\u002Fshots\u002Fcatalog-api-mobile.webp",{"slug":651,"title":652,"type":109,"category":109,"lang":59,"live_url":653,"stack":654,"description":659,"highlights":660,"run":543,"hue":558,"og":665,"shots":666},"paste-vault","Konvert: notes encrypted in the browser","https:\u002F\u002Fewsirdev-paste.ewsirdev.workers.dev",[59,655,656,657,106,452,658,47],"Cloudflare Workers","D1","Web Crypto","fast-check","A one-time note service on TypeScript, Cloudflare Workers and D1: the text is encrypted in the browser (AES-256-GCM), the key lives only in the URL fragment and never reaches the server. A note has a lifetime, an optional password and burns after the first read. A side project.",[661,662,663,664],"Encryption in the browser: AES-256-GCM, the key only in the URL fragment","Burn after reading is atomic: exactly one success out of 40 parallel reads","52 tests in the workerd runtime, including property tests and schema fuzzing","A live version on Cloudflare Workers + D1","\u002Fog\u002Fen\u002Fpaste-vault.jpg",{"desktop":667,"desktop_sm":668,"mobile":669,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fpaste-vault-desktop.webp","\u002Fshots\u002Fpaste-vault-desktop-sm.webp","\u002Fshots\u002Fpaste-vault-mobile.webp",{"slug":73,"title":74,"type":509,"category":50,"live_url":671,"stack":672,"description":678,"highlights":679,"run":684,"hue":76,"og":685,"shots":686},"https:\u002F\u002Fewsirdev-docs.pages.dev",[673,674,675,676,677,47],"Node.js","JavaScript (ESM)","zero dependencies","Markdown","Cloudflare Pages","A static documentation site generator written from scratch in Node.js with no dependencies: its own Markdown parser, code highlighting and offline search. The demo site about a fictional library, Sverlo, is built from 43 pages in two languages (RU and EN) and two versions (v1 and v2). A side project.",[680,681,682,683],"Own Markdown parser, code highlighting and client-side search with no libraries","43 pages, RU and EN, versions v1 and v2, hreflang, sitemap and RSS","131 tests; broken links and anchors are checked in CI","The site reads without JavaScript, the search works offline","npm test && npm run build","\u002Fog\u002Fen\u002Fdocs-engine.jpg",{"desktop":687,"desktop_sm":75,"mobile":688,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fdocs-engine-desktop.webp","\u002Fshots\u002Fdocs-engine-mobile.webp",{"slug":97,"title":98,"type":529,"category":81,"live_url":690,"stack":691,"description":695,"highlights":696,"run":543,"hue":100,"og":701,"shots":702},"https:\u002F\u002Fewsirdev-typing.pages.dev",[449,78,59,692,693,694,494,452,107,677],"IndexedDB","WebAudio","PWA","A touch-typing trainer that runs entirely in the browser and makes no server requests: Nuxt 4, history in IndexedDB, key sounds on WebAudio, offline through a PWA. Time, words, quotes, custom text, code and \"weak spots\" modes, six layouts, WPM, accuracy and consistency metrics, charts in plain SVG. A side project.",[697,698,699,700],"No server and no accounts: history in IndexedDB, JSON export and import","Six layouts, modes up to adaptive drills built from your mistakes","59 unit tests and 65 e2e checks, axe without violations, CLS 0.000","A PWA: works offline after the first visit","\u002Fog\u002Fen\u002Ftyping-lab.jpg",{"desktop":703,"desktop_sm":99,"mobile":704,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Ftyping-lab-desktop.webp","\u002Fshots\u002Ftyping-lab-mobile.webp",{"slug":102,"title":103,"type":529,"category":81,"lang":59,"live_url":706,"stack":707,"description":709,"highlights":710,"run":543,"hue":100,"og":715,"shots":716},"https:\u002F\u002Fewsirdev-budget.ewsirdev.workers.dev",[59,106,655,656,708,452,658,107,47],"WebGL","A web app for a group's shared expenses without sign-up: a room behind a secret link, four ways to split, and a \"who pays whom\" settlement in at most n-1 transfers. A side project.",[711,712,713,714],"No accounts: a room lives behind a 128-bit secret link, with separate view and edit links","Four ways to split and multi-currency with a manual rate; settlement in at most n-1 transfers","Optimistic locking: of 4 simultaneous edits to one expense exactly one wins","60 tests (vitest in workerd, fast-check property tests), green CI, npm audit 0, axe 0 violations","\u002Fog\u002Fen\u002Fbudget-split.jpg",{"desktop":717,"desktop_sm":104,"mobile":718,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fbudget-split-desktop.webp","\u002Fshots\u002Fbudget-split-mobile.webp",{"slug":720,"title":721,"type":509,"category":50,"lang":59,"live_url":722,"stack":723,"description":725,"highlights":726,"run":543,"hue":100,"og":731,"shots":732},"kladka-site","kladka-site: Kladka, a demo site for a timber-frame studio","https:\u002F\u002Fewsirdev-kladka.pages.dev",[79,59,724,494,708,107,584,47],"Content collections","A demo multi-page site for a fictional timber-frame studio: 5 projects with generated drawings, services, prices with an estimator, a journal. Company, prices and timelines are made up.",[727,728,729,730],"24 pages (19 RU, 4 EN, 404) on Astro 7 with static output and content collections","All 20 drawings are generated by code as SVG from project data; 24 OG images are rendered by a script","Strict CSP with no inline code, robots, sitemap with hreflang, llms.txt, security.txt, JSON-LD Organization\u002FService\u002FArticle\u002FFAQPage","756 links and anchors without errors, htmlhint 0, axe 0 on every page at 390 and 1280, npm audit 0","\u002Fog\u002Fen\u002Fkladka-site.jpg",{"desktop":733,"desktop_sm":734,"mobile":735,"dw":187,"dh":188,"mw":189,"mh":190},"\u002Fshots\u002Fkladka-site-desktop.webp","\u002Fshots\u002Fkladka-site-desktop-sm.webp","\u002Fshots\u002Fkladka-site-mobile.webp",{"generated_at":737,"tests_method":738,"note":739,"totals":740,"projects":750},"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":741,"go_projects":742,"go_lines":743,"go_test_lines":744,"tests":745,"endpoints":746,"html":747,"css":748,"js":749},17,13,26503,12027,418,230,1973,3525,4815,[751,760,767,774,781,785,792,798,803,806,809,812,815,820,827,833,838],{"slug":321,"kind":752,"go_lines":753,"go_test_lines":754,"tests":755,"endpoints":756,"html":757,"css":758,"js":759},"go",2334,1179,48,11,115,139,163,{"slug":418,"kind":752,"go_lines":761,"go_test_lines":762,"tests":763,"endpoints":167,"html":764,"css":765,"js":766},2826,1393,50,23,166,500,{"slug":135,"kind":752,"go_lines":768,"go_test_lines":769,"tests":119,"endpoints":770,"html":771,"css":772,"js":773},891,338,18,146,123,309,{"slug":445,"kind":752,"go_lines":775,"go_test_lines":776,"tests":777,"endpoints":100,"html":778,"css":779,"js":780},3741,1371,30,24,1148,72,{"slug":154,"kind":752,"go_lines":782,"go_test_lines":783,"tests":71,"endpoints":87,"html":784,"css":784,"js":784},2133,735,0,{"slug":281,"kind":752,"go_lines":786,"go_test_lines":787,"tests":76,"endpoints":788,"html":789,"css":790,"js":791},2161,833,19,138,188,331,{"slug":125,"kind":752,"go_lines":793,"go_test_lines":794,"tests":157,"endpoints":770,"html":795,"css":796,"js":797},871,272,102,156,328,{"slug":368,"kind":799,"go_lines":784,"go_test_lines":784,"tests":784,"endpoints":784,"html":800,"css":801,"js":802},"static",201,162,377,{"slug":40,"kind":799,"go_lines":784,"go_test_lines":784,"tests":784,"endpoints":784,"html":771,"css":804,"js":805},122,59,{"slug":35,"kind":799,"go_lines":784,"go_test_lines":784,"tests":784,"endpoints":784,"html":807,"css":808,"js":805},172,136,{"slug":30,"kind":799,"go_lines":784,"go_test_lines":784,"tests":784,"endpoints":784,"html":810,"css":811,"js":805},187,109,{"slug":130,"kind":752,"go_lines":768,"go_test_lines":797,"tests":119,"endpoints":756,"html":813,"css":801,"js":814},119,317,{"slug":345,"kind":752,"go_lines":816,"go_test_lines":793,"tests":71,"endpoints":788,"html":817,"css":818,"js":819},2027,174,192,517,{"slug":469,"kind":752,"go_lines":821,"go_test_lines":822,"tests":823,"endpoints":824,"html":825,"css":818,"js":826},2715,1910,77,16,106,625,{"slug":392,"kind":752,"go_lines":828,"go_test_lines":829,"tests":830,"endpoints":742,"html":831,"css":817,"js":832},2646,895,27,91,213,{"slug":300,"kind":752,"go_lines":834,"go_test_lines":835,"tests":755,"endpoints":157,"html":836,"css":837,"js":819},1822,1313,103,184,{"slug":261,"kind":752,"go_lines":839,"go_test_lines":840,"tests":770,"endpoints":841,"html":842,"css":807,"js":843},1445,589,15,126,369,1793297826829]