Builds
Things I've built, why they exist, and what I learned making them.
01
This Site
I wanted a place to put things before I forget them. That's it. That was the whole brief.
It turned into a rabbit hole. Static backgrounds felt dead, so I built a canvas starfield with twinkling stars and occasional shooting stars. The greeting on the homepage changes based on what time you visit — "good morning" at 9 AM, "you're up late too?" at 2 AM. The site name switches to katakana if you click it 10 times. The LIVE indicator cycles through status messages if you click it.
There are 30+ hidden easter eggs. Type anime character names or TV show catchphrases on your keyboard — each one triggers a different visual effect. Bakugo causes an explosion. Zoro redirects you to a random page (because he's lost). The Konami code triggers a star shower. There's a Matrix rain effect, a Gojo domain expansion, a Tsukuyomi grayscale drain.
Built with Astro + Tailwind. Dark glassmorphism theme. CSS scroll-driven animations where supported, IntersectionObserver fallback where not. View transitions between pages so it feels like an SPA without being one.
The interesting thing I learned: view transitions break inline scripts. Every script that touches the DOM needs to listen for astro:page-load instead of running on DOMContentLoaded, or it dies on the second page navigation.
02
Ad Copy That Writes Itself (Almost)
The problem: how do you write hundreds of ad variants across 50+ campaigns without them all sounding the same? Google Ads headlines are 30 characters max. That's not a lot of room to be interesting.
What I tried: I ranked 20 literary devices by compression efficiency — how much meaning you can pack into 30 characters. Antithesis ("Not X. Y.") turned out to be the best: maximum contrast in minimum space. Zeugma ("Pack light. Live large.") was second — one verb, two meanings. Then I grouped real-world ad performance data into 7 tone clusters based on what actually converts, not what sounds good in a doc.
Each tone has a personality. There's an "evergreen workhorse" tone that runs year-round, a "philosopher" tone that reframes the category ("Rethink PGs."), a "provocateur" tone that uses paraprosdokian endings, a "wise friend" tone that leads with empathy. Each tone is tagged with which literary devices it uses and which Cialdini principles it leans on.
The fun discoveries:
- Sound symbolism works in ads. "Cl-" words (clean, clear, clever) trigger purity associations. Hard consonants (k, t, p) convey authority. This is measurable in click-through rates.
- The best quality test for ad copy turned out to be "could a competitor use this headline?" If yes, it's too generic. Every headline gets run through this filter.
- Character counting with dynamic placeholders is a surprisingly hard problem. A headline that looks fine in a spreadsheet gets truncated in Google Ads because the city name is longer than you assumed.
The system generates 15 headlines + 4 descriptions per ad group, handles gender variants automatically, runs an audit that found 9,000+ issues in one pass, and ties budget allocation to real-time occupancy data in a closed loop.
The whole thing runs off a CLI with 50 commands and a 270MB SQLite database. No dashboard. Dashboards are where data goes to be looked at and not acted on.
03
Detecting AI Writing (and Making Content Not Sound Like AI)
The problem: I was building a content pipeline that uses AI at various stages. The output needed to not read like AI. Human-written content gets 5.4x more organic traffic — this isn't a style preference, it's a measurable business impact.
What didn't work: phrase blacklists. Flagging words like "delve" and "leverage" catches the obvious stuff but misses the real pattern. AI-written content can avoid every flagged word and still feel robotic.
What actually works: sentence length standard deviation. AI writes with eerie uniformity — nearly every sentence lands around 18 words, std_dev of 3-4. Humans are all over the place: a 5-word punch, then a 28-word flowing sentence, then a 12-word bridge. Std_dev 8-12. One metric, more reliable than any phrase list.
I also learned that AI tools embed invisible Unicode watermarks in their output — zero-width spaces, byte-order marks, zero-width non-joiners. These survive copy-paste. Built a scrubber that strips them.
The full pipeline runs 7 phases and 17 specialized agents. There's an 8-dimension quality scorer with a pass threshold of 72/100. If it fails, the system auto-revises (apply top 3-5 fixes, re-score). The 8 dimensions are weighted by what actually matters for search performance: humanity/voice is 20%, GEO citability is 15%, specificity is 15%. SEO compliance is only 10% — it's table stakes, not a differentiator.
The threshold was calibrated, not arbitrary. 70 let mediocre content through. 75 rejected articles that were clearly fine with minor edits. 72 is where auto-revision reliably pushes content over the bar.
04
Making 22,000 Pages Not Suck
The problem: a database of 400 properties across 9 cities needs to become ~22,000 web pages. City pages, locality pages, landmark pages, audience variants, room-type variants. Most will be templated. How do you make templated pages not feel templated?
The answer wasn't better writing. It was a knowledge graph. 15,000+ edges connecting every entity — cities, localities, landmarks, properties — to related entities. Two pages using the same template get completely different internal links, landmark references, and neighborhood context because the graph knows what's near what, what's semantically related, and where authority should flow.
Content uniqueness at scale comes from unique relationship data, not unique prose.
The graph took 5 tries:
- Started as a flat GSC dump — 5,000 URLs with click counts, no relationships
- Became an intent-annotated CSV — 49,000 edges with types and weights
- Got turned into a JSON graph by Gemini — worked once, couldn't be reproduced, had 7 major gaps (zero property nodes, 82% of landmarks were dead ends)
- Patched with a Python script that added missing edges — fixed the CSV but not the JSON
- Rebuilt from scratch with a deterministic 900-line Node.js script that derives everything from raw property data. Reproducible, version-controlled, runs in seconds
The graph now has 12 edge types: geographic proximity (haversine distance between property clusters), semantic similarity (preserved from an ML model — can't be re-derived), structural hierarchy, landmark connections, authority flow, and PageRank boosting for underperforming nodes.
The whole content ops app is client-side, zero API cost. Enter a URL, it resolves the entity, builds an intel snapshot, generates pre-written FAQs from real data, and fills content modules with actual property names and prices. An FAQ checker validates ChatGPT output against 11 quality rules before it's accepted.
The key insight from all this: if you need 22,000 pages to be unique, the uniqueness has to come from the data layer, not the presentation layer. Same template + different graph context = genuinely different pages.
05
Coaching App
The problem: every fitness/habit app either oversimplifies (just check a box) or overcomplicates (log every macro, track every rep). I wanted something in between: track the boring daily stuff, make it feel like a game.
XP for working out. XP for logging meals. Combo multipliers for doing multiple things in a day. Streaks that don't break on planned rest days (you mark them in advance). Loot drops with rarity tiers when you hit milestones. Levels that mean something because the XP curve is calibrated to real consistency, not just showing up once.
Built with Next.js + Supabase. It's for me, not a product. But the gamification design was a fun problem — how do you make a streak system that rewards consistency without punishing life? The answer was planned breaks: mark a day as "off" before it happens and your streak survives. Miss it without planning? Streak resets.
The loot system uses weighted randomness. Common drops are frequent enough to feel rewarding, legendaries are rare enough to feel earned. Tuned the drop rates over a few weeks of actually using it.
The Common Thread
Most of this was built because the team is small and the problem isn't. One person with a few hours a day, operating at a scale that usually requires a department. The answer every time is the same: build a system, not a process. Automate the repeatable parts. Put quality gates where humans would get tired. Make the data do the differentiation.
Also, I build stuff at 3 AM because I can't sleep and my brain won't shut off. That part is less strategic and more compulsive. But the output is the same.
These are the interesting ones. There are others that are less interesting and more "I needed a script to clean up my Gmail inbox." Those don't get a page.