[{"data":1,"prerenderedAt":270},["ShallowReactive",2],{"api:\u002Fapi\u002Fconfig:en":3,"api:\u002Fapi\u002Fprofile:":8,"api:\u002Fapi\u002Fcategories:en":19,"api:\u002Fapi\u002Fprojects\u002Fletterpress-ts:en":177,"api:\u002Fapi\u002Fnotes:en":218},{"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,144],{"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.","drop","ice","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.","merge","violet","frames",8,[58],{"lang":59,"count":60},"TypeScript",5,[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],"Cloudflare Pages","axe",{"slug":81,"title":82,"blurb":83,"variant":84,"palette":85,"layout":86,"count":27,"langs":87,"preview":90,"stack":105},"apps","Web apps","Apps you use: with a server, auth and e-mail, or offline-first PWAs where the data stays in the browser.","torus","mint","phone",[88],{"lang":59,"count":89},4,[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,79,107],"Vite","vitest",{"slug":109,"title":110,"blurb":111,"variant":112,"palette":113,"layout":114,"count":115,"langs":116,"preview":125,"stack":141},"backend","Backends","Services with an API, a database and tests: in Go, Rust, Python and TypeScript. A language filter sits inside the section.","crystal","steel","list",24,[117,120,122,124],{"lang":118,"count":119},"Go",12,{"lang":121,"count":89},"Python",{"lang":123,"count":89},"Rust",{"lang":59,"count":89},[126,131,136],{"slug":127,"title":128,"shot":129,"hue":130},"kanban-tasks","Amber: kanban board","\u002Fshots\u002Fkanban-tasks-desktop-sm.webp",344,{"slug":132,"title":133,"shot":134,"hue":135},"link-shortener","Short: link shortener","\u002Fshots\u002Flink-shortener-desktop-sm.webp",111,{"slug":137,"title":138,"shot":139,"hue":140},"booking-slots","Appointment booking","\u002Fshots\u002Fbooking-slots-desktop-sm.webp",65,[142,47,143,118],"Docker","SQLite",{"slug":145,"title":146,"blurb":147,"variant":148,"palette":149,"layout":150,"count":27,"langs":151,"preview":158,"stack":174},"cli","CLI tools","Terminal programs: one binary, clear flags, an exit code you can check in a script.","swarm","champagne","terminal",[152,154,156,157],{"lang":118,"count":153},3,{"lang":121,"count":155},1,{"lang":123,"count":155},{"lang":59,"count":155},[159,164,169],{"slug":160,"title":161,"shot":162,"hue":163},"gitpulse","GitPulse: git repository analytics","\u002Fshots\u002Fgitpulse-desktop-sm.webp",7,{"slug":165,"title":166,"shot":167,"hue":168},"logslice","logslice: log analysis with no dependencies","\u002Fshots\u002Flogslice-desktop-sm.webp",160,{"slug":170,"title":171,"shot":172,"hue":173},"envguard","envguard: a linter and a safe for .env files","\u002Fshots\u002Fenvguard-desktop-sm.webp",35,[47,175,142,176],"CLI","stdlib only",{"slug":178,"title":179,"type":180,"category":50,"lang":59,"live_url":181,"stack":182,"description":185,"highlights":186,"run":191,"hue":173,"og":192,"shots":193,"case":201,"case_lang":5,"prev":212,"next":215},"letterpress-ts","letterpress-ts: \"Reglet\", a letterpress workshop site concept","site","https:\u002F\u002Fewsirdev-letterpress.pages.dev",[59,106,183,184,45,79,78,47],"Multi-page","DOM","A concept for a multi-page site of a fictional letterpress workshop in plain TypeScript with no framework: Vite multi-page with 8 HTML entries, components as typed functions returning DOM. A type composer turns text into sorts and an impression, a calculator prices a run, state is kept in the link. Do not trust the workshop or the prices: sums come from demo formulas.",[187,188,189,190],"No framework: 8 Vite HTML entries, components as functions returning DOM","A type composer: typeface, ink, paper, size, spacing; state in the link with parameter validation","The price table on the home page is computed at build time with the same formulas as the calculator","User text enters the DOM through textContent, innerHTML is banned by the linter","cd letterpress-ts && npm ci && npm run dev","\u002Fog\u002Fen\u002Fletterpress-ts.jpg",{"desktop":194,"desktop_sm":195,"mobile":196,"dw":197,"dh":198,"mw":199,"mh":200},"\u002Fshots\u002Fletterpress-ts-desktop.webp","\u002Fshots\u002Fletterpress-ts-desktop-sm.webp","\u002Fshots\u002Fletterpress-ts-mobile.webp",1280,800,390,844,{"about":202,"features":203,"run":204,"notes":209},[185],[187,188,189,190],[205,206,207,208],"git clone \u003Crepository on request> letterpress-ts","cd letterpress-ts","npm ci","npm run dev",[210,211],"A concept: the pricing formulas are made up, an order builds a mailto letter you have to send yourself.","There is no English version.",{"slug":213,"title":214},"landscape-bureau-nuxt","landscape-bureau-nuxt: \"Izolinia\", a landscape bureau site concept",{"slug":216,"title":217},"ceramics-vue","ceramics-vue: \"Muffle\", a ceramics studio site concept",[219,225,230,235,240,245,250,255,260,265],{"slug":220,"lang":5,"title":221,"date":222,"summary":223,"project":213,"minutes":224},"csp-no-inline-nuxt","CSP without inline scripts: what had to be switched off in Nuxt","2026-10-30","Why a strict script-src self policy breaks static Nuxt, which three steps made the site compatible, and the build check that keeps it so.",2,{"slug":226,"lang":5,"title":227,"date":222,"summary":228,"project":229,"minutes":224},"hash-chain-tail-truncation","A hash-chained journal: what it catches and what it does not","How ledger-core builds a SHA-256 chain and checks it through \u002Fv1\u002Fverify, why tampering in the middle shows but tail truncation does not.","ledger-core",{"slug":231,"lang":5,"title":232,"date":222,"summary":233,"project":234,"minutes":224},"qr-from-scratch","A QR code from scratch: Reed-Solomon, masks and proving it right","How a zero-dependency QR encoder in TypeScript is built, and four independent ways to check that the matrix is correct.","qr-svg",{"slug":236,"lang":5,"title":237,"date":222,"summary":238,"project":239,"minutes":224},"sm2-day-boundaries","SM-2 and day boundaries: why the schedule is stored as dates","How flashcards-sm2 and habit-tracker avoid shifts from time zones and daylight saving: dates instead of timestamps and a movable day boundary.","flashcards-sm2",{"slug":241,"lang":5,"title":242,"date":222,"summary":243,"project":244,"minutes":224},"ssrf-after-dns","SSRF in webhooks: checking the address after DNS","Why a webhook delivery service checks the addresses after DNS resolution, not the URL string, and how hook-relay connects to the very address it checked.","hook-relay",{"slug":246,"lang":5,"title":247,"date":222,"summary":248,"project":249,"minutes":224},"token-bucket-integers","A token bucket on integers: what property tests check","How rate-gate computes a token bucket without floats, why milli-tokens, and which three properties the proptest property tests prove.","rate-gate",{"slug":251,"lang":5,"title":252,"date":253,"summary":254,"project":137,"minutes":155},"double-booking","Double booking: a transaction plus a partial UNIQUE index","2026-10-20","How booking-slots protects against two clients booking the same time at once, and how that protection is tested.",{"slug":256,"lang":5,"title":257,"date":253,"summary":258,"project":259,"minutes":224},"lease-and-fencing","Lease and fencing: how a queue neither loses a job nor lets a zombie finish it twice","A worker can crash, stall, or wake up after its job was taken by someone else. How the lease and the lease token in job-queue deal with that on SQLite.","job-queue",{"slug":261,"lang":5,"title":262,"date":253,"summary":263,"project":264,"minutes":224},"p95-nearest-rank","p95 in an uptime monitor: the nearest-rank method and what it does not hide","How uptime-monitor computes p50\u002Fp95 latency and uptime, why the nearest-rank method was chosen, and where those numbers can mislead.","uptime-monitor",{"slug":266,"lang":5,"title":267,"date":253,"summary":268,"project":269,"minutes":224},"websocket-from-scratch","WebSocket on the standard library: what really happens under new WebSocket()","Why realtime-rooms has its own RFC 6455 layer, which protocol violations it checks, and why a library is still the better choice for production.","realtime-rooms",1793371810511]