[{"data":1,"prerenderedAt":1377},["ShallowReactive",2],{"project-glacies":3,"siblings-glacies":126},{"id":4,"title":5,"body":6,"date":94,"description":95,"extension":96,"featured":97,"githubUrl":98,"liveUrl":99,"meta":100,"navigation":97,"path":101,"role":102,"screenshots":103,"seo":104,"status":105,"stem":106,"techs":107,"thumbnail":124,"__hash__":125},"projects\u002Fprojects\u002Fglacies.md","Glacies",{"type":7,"value":8,"toc":87},"minimark",[9,14,18,47,51,54,57,61],[10,11,13],"h2",{"id":12},"overview","Overview",[15,16,17],"p",{},"A public NHL analytics site built on the league's own public API. A Hono ingest service pulls games on a schedule, a Python model service scores every shot for expected goals, and the Nuxt app turns it into standings, leaderboards, and dashboards for every team and player.",[19,20,21,29,35,41],"ul",{},[22,23,24,28],"li",{},[25,26,27],"strong",{},"Live standings"," and sortable stat leaderboards",[22,30,31,34],{},[25,32,33],{},"Team and player dashboards"," with game logs",[22,36,37,40],{},[25,38,39],{},"Expected goals"," from its own logistic-regression model, plus Corsi and Fenwick",[22,42,43,46],{},[25,44,45],{},"Win probability and playoff odds"," from nightly Monte Carlo simulations",[10,48,50],{"id":49},"what-it-solves","What it solves",[15,52,53],{},"Advanced hockey stats are scattered across sites that are either buried in ads, locked behind a signup, or built for people who already know what Fenwick means. The official NHL site has the raw numbers but almost none of the models.",[15,55,56],{},"Glacies puts the models next to the numbers. Standings, xG, and playoff odds are free for everyone; a Pro tier adds the deep tools: a matchup predictor, side-by-side player compare, and CSV \u002F JSON export.",[10,58,60],{"id":59},"highlights","Highlights",[19,62,63,69,75,81],{},[22,64,65,68],{},[25,66,67],{},"In-house xG model"," trained with scikit-learn on NHL shot data",[22,70,71,74],{},[25,72,73],{},"Nightly Monte Carlo playoff odds"," for every team",[22,76,77,80],{},[25,78,79],{},"Pro tier"," with a matchup predictor, player compare, and data export via Dodo Payments",[22,82,83,86],{},[25,84,85],{},"Three languages",": English, Czech, and Slovak",{"title":88,"searchDepth":89,"depth":89,"links":90},"",2,[91,92,93],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-06-27","NHL stats and predictions. Standings, leaders, expected goals from its own xG model, win probability, and nightly playoff odds. Free, no signup.","md",true,null,"https:\u002F\u002Fglacies.app",{},"\u002Fprojects\u002Fglacies","Solo full-stack",[],{"title":5,"description":95},"Live","projects\u002Fglacies",[108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123],"Turborepo","Nuxt 4","Vue 3","TypeScript","tRPC","TanStack Query","Drizzle ORM","Postgres","better-auth","Dodo Payments","Hono","Python","scikit-learn","Redis","Unovis","i18n","\u002Fprojects\u002Fglacies.webp","JxFiGsEYK6FRFLyEk3B4QGKW7tFNV4W32jEtWZXrU4k",[127,187,285,419,519,613,707,800,890,991,1091,1187,1284],{"id":4,"title":5,"body":128,"date":94,"description":95,"extension":96,"featured":97,"githubUrl":98,"liveUrl":99,"meta":183,"navigation":97,"path":101,"role":102,"screenshots":184,"seo":185,"status":105,"stem":106,"techs":186,"thumbnail":124,"__hash__":125},{"type":7,"value":129,"toc":178},[130,132,134,152,154,156,158,160],[10,131,13],{"id":12},[15,133,17],{},[19,135,136,140,144,148],{},[22,137,138,28],{},[25,139,27],{},[22,141,142,34],{},[25,143,33],{},[22,145,146,40],{},[25,147,39],{},[22,149,150,46],{},[25,151,45],{},[10,153,50],{"id":49},[15,155,53],{},[15,157,56],{},[10,159,60],{"id":59},[19,161,162,166,170,174],{},[22,163,164,68],{},[25,165,67],{},[22,167,168,74],{},[25,169,73],{},[22,171,172,80],{},[25,173,79],{},[22,175,176,86],{},[25,177,85],{},{"title":88,"searchDepth":89,"depth":89,"links":179},[180,181,182],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},{},[],{"title":5,"description":95},[108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123],{"id":188,"title":189,"body":190,"date":269,"description":270,"extension":96,"featured":97,"githubUrl":98,"liveUrl":271,"meta":272,"navigation":97,"path":273,"role":102,"screenshots":274,"seo":275,"status":276,"stem":277,"techs":278,"thumbnail":283,"__hash__":284},"projects\u002Fprojects\u002Fsonuslab-storage.md","Sonuslab Storage",{"type":7,"value":191,"toc":264},[192,194,202,228,230,233,236,238],[10,193,13],{"id":12},[15,195,196,197,201],{},"Sonuslab Storage started as the file layer behind Sonuslab and became a standalone service. It stores files on Cloudflare R2, serves them through a Workers CDN, and ships as the ",[198,199,200],"code",{},"sonuslab-storage"," npm package. PDFmod, SmartzQuiz, Decibel Demon, and Shipnit all moved onto it.",[19,203,204,210,216,222],{},[22,205,206,209],{},[25,207,208],{},"npm SDK"," with adapters for Vue, Svelte, and React \u002F Next.js",[22,211,212,215],{},[25,213,214],{},"Presigned uploads"," straight from the browser to R2",[22,217,218,221],{},[25,219,220],{},"CDN delivery"," at cdn.sonuslab.dev, or from your own domain",[22,223,224,227],{},[25,225,226],{},"Dashboard"," for projects, keys, files, and webhooks",[10,229,50],{"id":49},[15,231,232],{},"Hosted upload services are convenient until the bill, the vendor lock-in, or the bundle size shows up, and raw S3 clients mean writing presigning, validation, and delivery again in every app. I was doing exactly that across half a dozen projects.",[15,234,235],{},"Sonuslab Storage is the one I wrote once. Each app gets a project and an API key, uploads go direct to R2, and files come back through the CDN. A built-in migration tool moved existing files over from UploadThing without touching user-facing URLs.",[10,237,60],{"id":59},[19,239,240,246,252,258],{},[22,241,242,245],{},[25,243,244],{},"Framework adapters"," for Vue, Svelte, and React \u002F Next.js",[22,247,248,251],{},[25,249,250],{},"Custom domains",": the dashboard shows the DNS records to add",[22,253,254,257],{},[25,255,256],{},"Webhooks and client-side encryption"," for sensitive files",[22,259,260,263],{},[25,261,262],{},"UploadThing migration tool"," used to move every existing app over",{"title":88,"searchDepth":89,"depth":89,"links":265},[266,267,268],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-06-24","File storage service with an npm SDK. Presigned uploads, a Cloudflare CDN, custom domains, and adapters for Vue, Svelte, and React. Used across all my apps.","https:\u002F\u002Fstorage.sonuslab.dev",{},"\u002Fprojects\u002Fsonuslab-storage",[],{"title":189,"description":270},"Active","projects\u002Fsonuslab-storage",[108,118,279,111,114,115,280,281,282],"Bun","Cloudflare R2","Cloudflare Workers","Nuxt 3","\u002Fprojects\u002Fsonuslab-storage.webp","hWW6czCWAJrSANc_hkET8kUkXY1XQXmM7CghKH7DGKc",{"id":286,"title":287,"body":288,"date":401,"description":402,"extension":96,"featured":403,"githubUrl":98,"liveUrl":404,"meta":405,"navigation":97,"path":406,"role":102,"screenshots":407,"seo":408,"status":409,"stem":410,"techs":411,"thumbnail":98,"__hash__":418},"projects\u002Fprojects\u002Ffootball-inspector.md","Football Inspector",{"type":7,"value":289,"toc":396},[290,292,295,346,348,351,354,356],[10,291,13],{"id":12},[15,293,294],{},"A match-day journal for Czech amateur football. Inspectors rate each fixture across three categories (food: sausage and beer; atmosphere: crowd, chants, quotes; and the 47th-minute gem), then write a short post-match note. Every inspection rolls into a public feed, a personal diary, and a team directory that doubles as institutional memory for local football culture.",[19,296,297,303,309,319,325,331,340],{},[22,298,299,302],{},[25,300,301],{},"Three-axis rating",": food, atmosphere, standout moment (0.5–5 stars)",[22,304,305,308],{},[25,306,307],{},"Match metadata",": home\u002Faway teams, score, league, region",[22,310,311,314,315,318],{},[25,312,313],{},"Personal diary"," at ",[198,316,317],{},"\u002Fme"," plus a public chronological feed",[22,320,321,324],{},[25,322,323],{},"Team directory"," scraped from official league sources (regional through fourth division)",[22,326,327,330],{},[25,328,329],{},"Photo uploads"," with a carousel on each inspection",[22,332,333,314,336,339],{},[25,334,335],{},"Public profiles",[198,337,338],{},"\u002Fuser\u002F[slug]"," with nicknames and notes",[22,341,342,345],{},[25,343,344],{},"Google sign-in"," via better-auth",[10,347,50],{"id":49},[15,349,350],{},"Official match statistics flatten everything that makes amateur football worth showing up for: the queue at the snack bar, the one guy with the trumpet, the goal nobody filmed. Football Inspector is the opposite of a stats sheet: a subjective, on-the-spot record of what the match actually felt like, written in the ten minutes after the final whistle.",[15,352,353],{},"Each inspection is short by design. Three star ratings, a couple sentences, and a result. The aggregate is a living archive of Czech grassroots football, searchable by team, by region, by inspector.",[10,355,60],{"id":59},[19,357,358,364,370,384,390],{},[22,359,360,363],{},[25,361,362],{},"SvelteKit + Svelte 5 runes"," with tRPC mutations replacing form actions for inspection edits and deletes",[22,365,366,369],{},[25,367,368],{},"PostgreSQL + Drizzle ORM"," schema covering leagues, teams, inspections, photos, and better-auth tables",[22,371,372,375,376,379,380,383],{},[25,373,374],{},"Cheerio + Playwright scrapers"," that seed the team directory from official league sites (",[198,377,378],{},"fetch-league",", ",[198,381,382],{},"fetch-kraj",")",[22,385,386,389],{},[25,387,388],{},"Tailwind 4 design system"," built on cream paper, pitch green, and amber tokens. Feels like a programme, not an app",[22,391,392,395],{},[25,393,394],{},"sveltekit-superforms + Zod"," for type-safe inspection submission flows",{"title":88,"searchDepth":89,"depth":89,"links":397},[398,399,400],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-05-07","Match-day journal for Czech amateur football. Rate the sausage, the atmosphere, and the 47th-minute gem. Keep every inspection forever.",false,"https:\u002F\u002Ffootball-inspector.vercel.app",{},"\u002Fprojects\u002Ffootball-inspector",[],{"title":287,"description":402},"Shipping","projects\u002Ffootball-inspector",[412,413,111,112,113,114,115,116,414,415,416,417],"SvelteKit","Svelte 5","UploadThing","Zod","Tailwind","bits-ui","1m1GlkACUWJq1iH7xK-tmijSOSuC2-CU0npOTRro938",{"id":420,"title":421,"body":422,"date":504,"description":505,"extension":96,"featured":403,"githubUrl":506,"liveUrl":507,"meta":508,"navigation":97,"path":509,"role":510,"screenshots":511,"seo":512,"status":105,"stem":513,"techs":514,"thumbnail":98,"__hash__":518},"projects\u002Fprojects\u002Fgit-rewind.md","Git Rewind",{"type":7,"value":423,"toc":499},[424,426,429,459,461,464,467,469],[10,425,13],{"id":12},[15,427,428],{},"A file-history panel for VS Code. Open any file, hit the shortcut, and step through every commit that touched it on a horizontal timeline, with syntax-highlighted, word-level diffs for each step.",[19,430,431,437,447,453],{},[22,432,433,436],{},[25,434,435],{},"Horizontal commit timeline"," with keyboard stepping",[22,438,439,442,443,446],{},[25,440,441],{},"Rename-aware history"," across ",[198,444,445],{},"git mv"," and detected renames",[22,448,449,452],{},[25,450,451],{},"Compare any two commits"," side by side",[22,454,455,458],{},[25,456,457],{},"Pickaxe search",": find the commit that added or removed a string",[10,460,50],{"id":49},[15,462,463],{},"The popular git-history extensions either cap how much history you can see, push you toward a paid tier, or phone home. The built-in timeline lists commits but makes you open each diff one at a time, and loses the trail the moment a file was renamed.",[15,465,466],{},"Git Rewind does the one job well and stays local: it runs plain git, has no external services and no telemetry, and follows the file wherever it moved.",[10,468,60],{"id":59},[19,470,471,477,483,493],{},[22,472,473,476],{},[25,474,475],{},"Word-level diffs"," highlighted with Shiki in the current theme",[22,478,479,482],{},[25,480,481],{},"Conventional Commits cues",": coloured stripes per commit type",[22,484,485,488,489,492],{},[25,486,487],{},"Filter by author, message, or SHA",", plus ",[198,490,491],{},"git log -S"," pickaxe search",[22,494,495,498],{},[25,496,497],{},"Per-file memory"," of the last commit you were looking at",{"title":88,"searchDepth":89,"depth":89,"links":500},[501,502,503],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-05-06","VS Code extension for browsing a file's git history. Horizontal timeline, rename-aware word-level diffs, pickaxe search, and compare-any-two-commits.","https:\u002F\u002Fgithub.com\u002FJiriMachynka\u002Fgit-rewind","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=JiriMachynka.git-file-rewind",{},"\u002Fprojects\u002Fgit-rewind","Solo",[],{"title":421,"description":505},"projects\u002Fgit-rewind",[111,515,516,517],"VS Code Extension API","Shiki","diff","PV1odszsmxlZwXcbGsZn1Ed2_pg-OM9FdCe6ut9T0uY",{"id":520,"title":521,"body":522,"date":596,"description":597,"extension":96,"featured":403,"githubUrl":98,"liveUrl":598,"meta":599,"navigation":97,"path":600,"role":102,"screenshots":601,"seo":602,"status":105,"stem":603,"techs":604,"thumbnail":98,"__hash__":612},"projects\u002Fprojects\u002Fanimal-match.md","Animal Match",{"type":7,"value":523,"toc":591},[524,526,529,555,557,560,563,565],[10,525,13],{"id":12},[15,527,528],{},"A text-first guessing game that feels like chatting with someone who actually wants you to figure it out. The AI picks an animal and paces its hints, turning 20 Questions into a conversation with rhythm.",[19,530,531,537,543,549],{},[22,532,533,536],{},[25,534,535],{},"Pick-an-animal sessions",": AI selects, you guess through chat",[22,538,539,542],{},[25,540,541],{},"Progressive hints"," that escalate as you stall",[22,544,545,548],{},[25,546,547],{},"Animal facts"," surfaced mid-game to teach while you play",[22,550,551,554],{},[25,552,553],{},"Session stats",": accuracy, attempts, streaks",[10,556,50],{"id":49},[15,558,559],{},"Classic 20 Questions is a rigid yes\u002Fno format that breaks the second anyone wants to riff on a guess. Most AI-driven versions swing the other way: they either spoil the answer in the first hint or stonewall you until it stops being fun.",[15,561,562],{},"Animal Match sits between the two. The AI plays host instead of opponent: it nudges you forward when you stall, stays quiet when you're close, and drops a fact every so often so you leave knowing something even if you lose.",[10,564,60],{"id":59},[19,566,567,573,579,585],{},[22,568,569,572],{},[25,570,571],{},"Hint pacing"," that escalates with how many guesses have passed",[22,574,575,578],{},[25,576,577],{},"Mid-game facts"," so every round teaches something",[22,580,581,584],{},[25,582,583],{},"Accuracy + streak tracking"," across sessions",[22,586,587,590],{},[25,588,589],{},"Multi-language UI"," with dark and light modes",{"title":88,"searchDepth":89,"depth":89,"links":592},[593,594,595],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-04-10","A conversational 20 Questions game hosted by an LLM. The AI picks an animal; you ask, deduce, and guess your way to it through chat, hints, and facts.","https:\u002F\u002Fanimal-match.com",{},"\u002Fprojects\u002Fanimal-match",[],{"title":521,"description":597},"projects\u002Fanimal-match",[605,606,111,114,115,116,607,608,609,610,611,416],"Next.js 15","React 19","OpenRouter","Vercel AI SDK","next-intl","Zustand","Radix UI","WQo0XjNsL2fTB90P8sDBO2fcz9YulkNLrSq4Q9VTFBM",{"id":614,"title":615,"body":616,"date":690,"description":691,"extension":96,"featured":97,"githubUrl":98,"liveUrl":692,"meta":693,"navigation":97,"path":694,"role":102,"screenshots":695,"seo":696,"status":276,"stem":697,"techs":698,"thumbnail":705,"__hash__":706},"projects\u002Fprojects\u002Fpdfmod.md","PDFmod",{"type":7,"value":617,"toc":685},[618,620,623,649,651,654,657,659],[10,619,13],{"id":12},[15,621,622],{},"A PDF toolbox that keeps your documents instead of forgetting them. Quick edits run in the browser with pdf-lib; the heavy operations (OCR, compression, redaction, encryption) run on a Python service built on PyMuPDF, pikepdf, and Tesseract.",[19,624,625,631,637,643],{},[22,626,627,630],{},[25,628,629],{},"Merge \u002F split \u002F reorder"," pages with drag and drop",[22,632,633,636],{},[25,634,635],{},"Compress, flatten, and OCR"," scanned documents",[22,638,639,642],{},[25,640,641],{},"Redact, encrypt, sign",", and fill forms",[22,644,645,648],{},[25,646,647],{},"Saved documents"," with version history and full-text search",[10,650,50],{"id":49},[15,652,653],{},"Every \"free online PDF tool\" is a gamble. The lucky outcome is ads; the unlucky one is your document quietly uploaded to a server nobody can name, and the moment you close the tab, the edited file is gone.",[15,655,656],{},"PDFmod is account-based on purpose. Documents live in your workspace, every edit saves a new version you can roll back to, and OCR'd pages become searchable text. Simple edits never leave the browser; the rest go to a server you know the name of.",[10,658,60],{"id":59},[19,660,661,667,673,679],{},[22,662,663,666],{},[25,664,665],{},"Python processing service",": PyMuPDF, pikepdf, Tesseract OCR",[22,668,669,672],{},[25,670,671],{},"Version history"," on every saved document, with a per-account storage limit",[22,674,675,678],{},[25,676,677],{},"Full-text search"," across documents, including OCR'd scans",[22,680,681,684],{},[25,682,683],{},"Redaction, encryption, and signatures"," for contract-grade work",{"title":88,"searchDepth":89,"depth":89,"links":686},[687,688,689],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-04-06","Online PDF toolbox. Merge, split, compress, OCR, redact, sign, and fill forms, with saved documents, version history, and full-text search.","https:\u002F\u002Fpdf-mod.com",{},"\u002Fprojects\u002Fpdfmod",[],{"title":615,"description":691},"projects\u002Fpdfmod",[282,110,111,699,113,114,115,116,200,700,701,702,703,416,704],"oRPC","pdf-lib","pdfjs-dist","FastAPI","PyMuPDF","reka-ui","\u002Fprojects\u002Fpdfmod.webp","7YyHjYy1cHDXAStRL0ZEewDAiCD1J9uxag-piBEcxJE",{"id":708,"title":709,"body":710,"date":787,"description":788,"extension":96,"featured":403,"githubUrl":98,"liveUrl":789,"meta":790,"navigation":97,"path":791,"role":102,"screenshots":792,"seo":793,"status":276,"stem":794,"techs":795,"thumbnail":798,"__hash__":799},"projects\u002Fprojects\u002Fsonuslab.md","Sonuslab",{"type":7,"value":711,"toc":782},[712,714,717,746,748,751,754,756],[10,713,13],{"id":12},[15,715,716],{},"An API platform for the audio work my other projects kept rebuilding. Clip a segment from a video link, turn text into speech, or clone a voice from a sample through a published SDK, metered API keys, and a dashboard for trying it all by hand.",[19,718,719,728,734,740],{},[22,720,721,727],{},[25,722,723,726],{},[198,724,725],{},"sonuslab"," SDK",": one call each for clips, text-to-speech, and voice cloning",[22,729,730,733],{},[25,731,732],{},"Audio clipping"," from links on 16 platforms",[22,735,736,739],{},[25,737,738],{},"Usage-metered API keys"," per project",[22,741,742,745],{},[25,743,744],{},"Dashboard + docs site"," with waveform previews",[10,747,50],{"id":49},[15,749,750],{},"Every app that touches audio ends up with the same plumbing: a yt-dlp + ffmpeg pipeline that breaks when a platform changes, a TTS vendor SDK that dictates your data model, and storage for the results. Rebuilding that per project is slow, and switching vendors means rewriting it.",[15,752,753],{},"Sonuslab puts it behind one vendor-neutral API. Apps call the SDK, the speech engine underneath can change without them noticing, and results land in Sonuslab Storage. Decibel Demon's clipping and TTS run on it today.",[10,755,60],{"id":59},[19,757,758,764,770,776],{},[22,759,760,763],{},[25,761,762],{},"Published SDK"," with typed clip, TTS, and clone calls",[22,765,766,769],{},[25,767,768],{},"16-platform clipper"," built on yt-dlp and ffmpeg",[22,771,772,775],{},[25,773,774],{},"Vendor-neutral speech engine",": swapped providers without breaking clients",[22,777,778,781],{},[25,779,780],{},"Metered API keys"," and a standalone documentation site",{"title":88,"searchDepth":89,"depth":89,"links":783},[784,785,786],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-04-03","Audio processing platform and SDK. Clip audio from links, generate speech, and clone voices programmatically, one call each.","https:\u002F\u002Fsonuslab.dev",{},"\u002Fprojects\u002Fsonuslab",[],{"title":709,"description":788},"projects\u002Fsonuslab",[108,282,110,111,118,279,112,116,796,114,115,280,416,704,797],"wavesurfer.js","motion-v","\u002Fprojects\u002Fsonuslab.webp","Q5lazATIScb-tCXD3nZar-VW2KbH4uzXTfArlxPdBHo",{"id":801,"title":802,"body":803,"date":876,"description":877,"extension":96,"featured":97,"githubUrl":98,"liveUrl":878,"meta":879,"navigation":97,"path":880,"role":102,"screenshots":881,"seo":882,"status":276,"stem":883,"techs":884,"thumbnail":888,"__hash__":889},"projects\u002Fprojects\u002Fdecibel-demon.md","Decibel Demon",{"type":7,"value":804,"toc":871},[805,807,810,836,838,841,844,846],[10,806,13],{"id":12},[15,808,809],{},"A soundboard for Discord servers that lives on the web. The bot joins your voice channel, and the dashboard holds the shared sound vault: clip a moment from a video, trim it on a waveform, and fire it into the call from your phone or desktop.",[19,811,812,818,824,830],{},[22,813,814,817],{},[25,815,816],{},"Play into voice"," from the web dashboard or a mobile tap grid",[22,819,820,823],{},[25,821,822],{},"Clip tool",": paste a link, trim on a waveform, save as a sound",[22,825,826,829],{},[25,827,828],{},"Text-to-speech and voice cloning"," powered by Sonuslab",[22,831,832,835],{},[25,833,834],{},"Play queue"," with pause, resume, and skip",[10,837,50],{"id":49},[15,839,840],{},"Discord's built-in soundboard is tiny, hard to fill, and stuck inside the client. Getting a new sound in means finding the clip, downloading it, cutting it in an editor, and uploading a file under the size limit, by which point the joke is dead.",[15,842,843],{},"Decibel Demon turns that into one step. Paste a link, drag the trim handles, and the sound is in the server's vault for everyone. Need a line nobody actually said? Type it and play it back in a cloned voice.",[10,845,60],{"id":59},[19,847,848,854,860,866],{},[22,849,850,853],{},[25,851,852],{},"Link-to-sound clipping"," with waveform trimming",[22,855,856,859],{},[25,857,858],{},"TTS and voice cloning"," through the Sonuslab API",[22,861,862,865],{},[25,863,864],{},"Mobile soundboard"," with big tap tiles for playing mid-game",[22,867,868],{},[25,869,870],{},"Per-server vaults, settings, and a usage heatmap",{"title":88,"searchDepth":89,"depth":89,"links":872},[873,874,875],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-03-30","Discord soundboard with a web dashboard. Clip sounds from any link, generate TTS in cloned voices, and play them straight into a voice channel.","https:\u002F\u002Fdecibel-demon.com",{},"\u002Fprojects\u002Fdecibel-demon",[],{"title":802,"description":877},"projects\u002Fdecibel-demon",[108,885,606,111,112,113,116,886,887,114,115,200,796,610,416],"Next.js 16","discord.js","@discordjs\u002Fvoice","\u002Fprojects\u002Fdecibel-demon.webp","3V-Y-RptNz5t6wAVQm4coqwBdGkbql9eOyusVg1HhF0",{"id":891,"title":892,"body":893,"date":980,"description":981,"extension":96,"featured":403,"githubUrl":98,"liveUrl":982,"meta":983,"navigation":97,"path":984,"role":102,"screenshots":985,"seo":986,"status":409,"stem":987,"techs":988,"thumbnail":989,"__hash__":990},"projects\u002Fprojects\u002Fshipnit.md","Shipnit",{"type":7,"value":894,"toc":975},[895,897,900,930,932,935,938,940],[10,896,13],{"id":12},[15,898,899],{},"A changelog SaaS that drafts the first version for you. Connects to a GitHub repo, turns merged PRs and releases into entries via AI, and publishes them on a branded page customers can subscribe to.",[19,901,902,912,918,924],{},[22,903,904,907,908,911],{},[25,905,906],{},"Public changelog pages"," on your own ",[198,909,910],{},"\u003Cslug>.shipnit.dev"," subdomain",[22,913,914,917],{},[25,915,916],{},"GitHub webhooks"," auto-draft from PRs, releases, and pushes",[22,919,920,923],{},[25,921,922],{},"AI summaries"," turn raw diffs into user-facing copy",[22,925,926,929],{},[25,927,928],{},"Review + edit UI"," before anything goes live",[10,931,50],{"id":49},[15,933,934],{},"Keeping a changelog current is the easiest thing on a roadmap to drop. By Friday the week's PRs are fuzzy, the context is gone, and writing \"we fixed a few bugs\" feels worse than writing nothing. Most products just… stop, and customers never hear what's new.",[15,936,937],{},"Shipnit does the first pass for you. It reads merged PRs and releases, drafts user-facing summaries, and drops them into a review UI. The work stops being \"write a changelog\" and starts being \"approve one\", a much smaller ask on a Friday afternoon.",[10,939,60],{"id":59},[19,941,942,948,953,958,964,969],{},[22,943,944,947],{},[25,945,946],{},"GitHub webhook auto-drafts"," from PRs, releases, and pushes",[22,949,950,952],{},[25,951,922],{}," tuned for end-user copy, not commit messages",[22,954,955,957],{},[25,956,928],{}," so nothing publishes uninspected",[22,959,960,963],{},[25,961,962],{},"Subscriber notifications"," when a new entry goes live",[22,965,966,739],{},[25,967,968],{},"Public roadmap and feedback pages",[22,970,971,974],{},[25,972,973],{},"Weekly dependency digest"," emails listing outdated packages per repo",{"title":88,"searchDepth":89,"depth":89,"links":976},[977,978,979],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-03-27","Changelog autopilot for GitHub. Reads merged PRs and releases, drafts polished entries with AI, and publishes them to a branded page.","https:\u002F\u002Fshipnit.dev",{},"\u002Fprojects\u002Fshipnit",[],{"title":892,"description":981},"projects\u002Fshipnit",[412,413,111,112,114,115,116,608,607,200,416,417],"\u002Fprojects\u002Fshipnit.webp","WggzLDrJ0e0EcjGLxuM5mojw-jy7kEU_wQCWLuNhHC4",{"id":992,"title":993,"body":994,"date":1079,"description":1080,"extension":96,"featured":403,"githubUrl":98,"liveUrl":1081,"meta":1082,"navigation":97,"path":1083,"role":102,"screenshots":1084,"seo":1085,"status":409,"stem":1086,"techs":1087,"thumbnail":1089,"__hash__":1090},"projects\u002Fprojects\u002Fracecast.md","RaceCast",{"type":7,"value":995,"toc":1074},[996,998,1001,1027,1029,1032,1035,1037],[10,997,13],{"id":12},[15,999,1000],{},"Four LLMs (Claude, GPT, Gemini, and Grok) compete to predict every Formula 1 race of the season. Pre-race picks are scored against real results and roll into a season-long leaderboard, settling the \"which model is smartest\" debate race by race.",[19,1002,1003,1009,1015,1021],{},[22,1004,1005,1008],{},[25,1006,1007],{},"Pre-race predictions"," from 4 models with distinct prompts",[22,1010,1011,1014],{},[25,1012,1013],{},"Side-by-side comparison"," of each model's picks",[22,1016,1017,1020],{},[25,1018,1019],{},"Accuracy scoring",": winner, podium, top 10, fastest lap",[22,1022,1023,1026],{},[25,1024,1025],{},"Live race dashboard",": weather, tires, pit stops from OpenF1",[10,1028,50],{"id":49},[15,1030,1031],{},"Every time a new LLM drops, the internet argues over which one is actually the smartest. Benchmarks are abstract and hard to feel. Real-world prediction is the opposite: either the model called the podium right or it didn't.",[15,1033,1034],{},"RaceCast turns four models loose on every Formula 1 race of the season. Each one commits picks before the lights go out, the results score them automatically, and the leaderboard grows in public. No curated test set, no cherry-picking, just 24 races of being wrong or right.",[10,1036,60],{"id":59},[19,1038,1039,1045,1051,1056,1062,1068],{},[22,1040,1041,1044],{},[25,1042,1043],{},"Four models"," with distinct prompting strategies predicting side by side",[22,1046,1047,1050],{},[25,1048,1049],{},"Consensus indicator"," flags when all four agree (and when they split)",[22,1052,1053,1055],{},[25,1054,1025],{}," pulling weather, tires, and pit stops from OpenF1",[22,1057,1058,1061],{},[25,1059,1060],{},"Season + championship leaderboards"," that update themselves",[22,1063,1064,1067],{},[25,1065,1066],{},"Timing-board-style calendar"," with redesigned race pages",[22,1069,1070,1073],{},[25,1071,1072],{},"Current-gen models",": Claude Sonnet 5, GPT-5.5, Gemini 3.5 Flash, Grok 4.3",{"title":88,"searchDepth":89,"depth":89,"links":1075},[1076,1077,1078],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-03-19","Four AI models (Claude, GPT, Gemini, and Grok) compete to predict every Formula 1 race of the season. Scores update against real results on a season-long leaderboard.","https:\u002F\u002Fracecast.app",{},"\u002Fprojects\u002Fracecast",[],{"title":993,"description":1080},"projects\u002Fracecast",[1088,606,111,699,113,114,115,608,607,416,611],"TanStack Start","\u002Fprojects\u002Fracecast.webp","6Vx6h8NrUg7RsT8i1gyJOm1QBV94306-hzMkoRUc45o",{"id":1092,"title":1093,"body":1094,"date":1172,"description":1173,"extension":96,"featured":403,"githubUrl":98,"liveUrl":1174,"meta":1175,"navigation":97,"path":1176,"role":102,"screenshots":1177,"seo":1178,"status":409,"stem":1179,"techs":1180,"thumbnail":98,"__hash__":1186},"projects\u002Fprojects\u002Fquikpic.md","QuikPic",{"type":7,"value":1095,"toc":1167},[1096,1098,1101,1133,1135,1138,1141,1143],[10,1097,13],{"id":12},[15,1099,1100],{},"Screenshot capture, polish, and share in a single keystroke. A browser extension grabs the shot, a canvas editor styles it, and a cloud gallery keeps it. No Paint detour, no sketchy upload host.",[19,1102,1103,1109,1115,1121,1127],{},[22,1104,1105,1108],{},[25,1106,1107],{},"Area \u002F full-page capture"," via extension hotkey, with scroll-and-stitch",[22,1110,1111,1114],{},[25,1112,1113],{},"QuikPic Capture",": macOS app with global shortcuts and its own region picker",[22,1116,1117,1120],{},[25,1118,1119],{},"Canvas editor",": gradient backgrounds, rounded corners, shadows, window chrome",[22,1122,1123,1126],{},[25,1124,1125],{},"Blur brush"," for redacting sensitive content",[22,1128,1129,1132],{},[25,1130,1131],{},"Day-grouped gallery"," with clipboard copy and batch delete",[10,1134,50],{"id":49},[15,1136,1137],{},"Taking a presentable screenshot is a five-tool workflow for something that should be one keystroke. Capture in one app, crop in another, add a background in a third, upload to a host, copy the link, hope nobody notices the timestamp watermark. Most people skip the styling step entirely and ship the raw PNG, which looks rough.",[15,1139,1140],{},"QuikPic collapses the whole loop into one flow. A hotkey takes the shot, the editor opens already knowing how to make it look good, and share happens from the same page: clipboard or public link, your choice.",[10,1142,60],{"id":59},[19,1144,1145,1151,1157,1162],{},[22,1146,1147,1150],{},[25,1148,1149],{},"Hotkey capture"," from the browser or anywhere on macOS via the Tauri app",[22,1152,1153,1156],{},[25,1154,1155],{},"Auto-gradient backgrounds"," picked from the screenshot's own colors",[22,1158,1159,1161],{},[25,1160,1125],{}," for redacting tokens and personal data",[22,1163,1164,1166],{},[25,1165,1131],{}," with batch delete for cleanup",{"title":88,"searchDepth":89,"depth":89,"links":1168},[1169,1170,1171],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-03-18","Screenshot pipeline in four parts: browser extension and macOS app to capture, canvas editor to style, cloud gallery to share.","https:\u002F\u002Fquikpic.dev",{},"\u002Fprojects\u002Fquikpic",[],{"title":1093,"description":1173},"projects\u002Fquikpic",[108,606,1181,111,118,279,1182,280,1183,1184,416,1185],"TanStack Router","Clerk","Tauri","Base UI","Chrome Extension API","wK8yLJa9lCk-vHI4zibsgKZo7qpGEYOgiuMYGskNru4",{"id":1188,"title":1189,"body":1190,"date":1270,"description":1271,"extension":96,"featured":403,"githubUrl":98,"liveUrl":1272,"meta":1273,"navigation":97,"path":1274,"role":102,"screenshots":1275,"seo":1276,"status":409,"stem":1277,"techs":1278,"thumbnail":1282,"__hash__":1283},"projects\u002Fprojects\u002Fsmartzquiz.md","SmartzQuiz",{"type":7,"value":1191,"toc":1265},[1192,1194,1197,1223,1225,1228,1231,1233],[10,1193,13],{"id":12},[15,1195,1196],{},"AI-generated flashcards with real spaced repetition. Upload a PDF or paste text, drill the generated cards in three study modes, and retain them through a Leitner 5-box system that actually schedules reviews.",[19,1198,1199,1205,1211,1217],{},[22,1200,1201,1204],{},[25,1202,1203],{},"AI card generation"," from PDFs or pasted text",[22,1206,1207,1210],{},[25,1208,1209],{},"3 study modes",": multiple choice, reveal, free-form",[22,1212,1213,1216],{},[25,1214,1215],{},"Leitner 5-box spaced repetition"," with daily scheduling",[22,1218,1219,1222],{},[25,1220,1221],{},"Analytics",": forgetting curve, activity calendar, projected scores",[10,1224,50],{"id":49},[15,1226,1227],{},"Most flashcard apps make you hand-type every card, which burns all your momentum before studying even begins. Most AI learning apps solve the opposite half: they generate cards and hope retention happens on its own, which it doesn't.",[15,1229,1230],{},"SmartzQuiz combines both. Drop in a PDF, get cards back, and drill them on a Leitner schedule that decides what to re-test and when. You never write a single card by hand, and the system still makes the material stick.",[10,1232,60],{"id":59},[19,1234,1235,1241,1247,1253,1259],{},[22,1236,1237,1240],{},[25,1238,1239],{},"Generate cards from PDFs or pasted text"," in one step, with OCR for scanned PDFs",[22,1242,1243,1246],{},[25,1244,1245],{},"Source citations"," shown on every card",[22,1248,1249,1252],{},[25,1250,1251],{},"Three study modes",": multiple choice, reveal, and type-your-answer",[22,1254,1255,1258],{},[25,1256,1257],{},"Leitner 5-box scheduling"," with skipped-day handling",[22,1260,1261,1264],{},[25,1262,1263],{},"Activity and forgetting-curve analytics"," to track progress over time",{"title":88,"searchDepth":89,"depth":89,"links":1266},[1267,1268,1269],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2026-02-27","AI flashcards with spaced repetition. Drop in a PDF, get a deck, drill it across MCQ \u002F reveal \u002F free-form modes. Leitner 5-box keeps what you forget.","https:\u002F\u002Fsmartzquiz.com",{},"\u002Fprojects\u002Fsmartzquiz",[],{"title":1189,"description":1271},"projects\u002Fsmartzquiz",[282,110,111,112,114,115,116,608,607,1279,200,1280,416,1281,123],"Polar","Pinia","shadcn-nuxt","\u002Fprojects\u002Fsmartzquiz.webp","gIcHzOGcVBxOGuMWfo0DqzSKpTsTJ01Z7fcX3DHDSRw",{"id":1285,"title":1286,"body":1287,"date":1366,"description":1367,"extension":96,"featured":97,"githubUrl":98,"liveUrl":1368,"meta":1369,"navigation":97,"path":1370,"role":102,"screenshots":1371,"seo":1372,"status":276,"stem":1373,"techs":1374,"thumbnail":98,"__hash__":1376},"projects\u002Fprojects\u002Ftipovacka.md","Tipovacka",{"type":7,"value":1288,"toc":1361},[1289,1291,1294,1320,1322,1325,1328,1330],[10,1290,13],{"id":12},[15,1292,1293],{},"A tournament prediction pool for groups of friends. Start from a template like World Cup 2026, invite players, collect predictions before kickoff, and let a real-time leaderboard settle scores. No more arguments over spreadsheets.",[19,1295,1296,1302,1308,1314],{},[22,1297,1298,1301],{},[25,1299,1300],{},"Tournament templates"," with fixture sync from football-data.org",[22,1303,1304,1307],{},[25,1305,1306],{},"Invite players"," via share link, predictions locked before kickoff",[22,1309,1310,1313],{},[25,1311,1312],{},"Drag-to-rank"," finals and knockouts",[22,1315,1316,1319],{},[25,1317,1318],{},"Live leaderboard"," across multiple tournaments",[10,1321,50],{"id":49},[15,1323,1324],{},"Every friend group's World Cup pool ends the same way: a messy spreadsheet nobody trusts, a group chat full of conflicting score rules, and one person insisting their prediction for the final absolutely counted. By the knockouts half the players have stopped submitting anyway.",[15,1326,1327],{},"Tipovacka makes the whole thing a website. Everyone submits picks before kickoff, the leaderboard updates itself as real results come in, and nobody has to remember how many points an exact score was supposed to be worth. The group argues less and watches more.",[10,1329,60],{"id":59},[19,1331,1332,1338,1344,1349,1355],{},[22,1333,1334,1337],{},[25,1335,1336],{},"One-click results",": final scores and goal scorers pulled from football-data.org",[22,1339,1340,1343],{},[25,1341,1342],{},"Invite by link"," with predictions locked the moment a match starts",[22,1345,1346,1348],{},[25,1347,1312],{}," for knockouts and finals",[22,1350,1351,1354],{},[25,1352,1353],{},"Auto-lock worker"," on Postgres LISTEN\u002FNOTIFY, plus XLSX export of every tip",[22,1356,1357,1360],{},[25,1358,1359],{},"Multi-tournament support"," so several pools can run side by side",{"title":88,"searchDepth":89,"depth":89,"links":1362},[1363,1364,1365],{"id":12,"depth":89,"text":13},{"id":49,"depth":89,"text":50},{"id":59,"depth":89,"text":60},"2024-04-22","Prediction pool for sports tournaments. Players forecast match outcomes, drag their picks into order, and battle live on the leaderboard.","https:\u002F\u002Fmoje-tipovacka.cz",{},"\u002Fprojects\u002Ftipovacka",[],{"title":1286,"description":1367},"projects\u002Ftipovacka",[109,110,111,112,113,114,115,116,1375,416,1281,415],"Resend","fdMJYLInfbuQJdVdDLoYty4Qs2ie7NLQhKf-OkRIZKY",1791487912607]