Personal portfolio website hosted on GitHub Pages.
Live site: triunitystudios.com
- Main site (
index.html) — Data-driven portfolio. Content is loaded from JSON files (data/) and rendered dynamically with vanilla JS. web-projects/— Standalone mini-apps, separate from the main site.blog/— Long-form posts as plain HTML pages, each with its own look. Listed at triunitystudios.com/blog/, with an Atom feed atblog/feed.xml.
No build step. Serve files with any HTTP server.
Recommended: Live Server for VS Code — install the extension, then either click "Go Live" in the bottom status bar or right-click index.html and select "Open with Live Server". It auto-reloads the browser on file changes.
Alternatively, from the terminal:
python -m http.server 8000sitemap.xml, blog/feed.xml and the GENERATED comment-marked blocks inside index.html, web-projects/index.html, blog/index.html and each web-project page are derived from the data/ JSON and from the blog posts' own <head> tags, so search engines can read the content without running JavaScript. Never edit them by hand. After changing data/ or a post, regenerate them with Bun:
bun scripts/generateSitemap.js
bun scripts/generateSeoBlocks.js
bun scripts/generateFeed.js
bun scripts/generateWebProjectMeta.jsTo make this automatic on every commit, install lefthook once (winget install evilmartians.lefthook on Windows, brew install lefthook on macOS) and run lefthook install in the repo. If you skip this, CI fails with a message telling you which script to run.
Self-contained mini-apps in web-projects/. Browse them all in the Playground at triunitystudios.com/web-projects/, an index page that lists every project automatically from the portfolio data. "Playground" is the visitor-facing name of that page; the folder and the URL keep the web-projects name.
Projects that keep something you made can save it to one private GitHub repository you own, shared by every project on the site, through the Cloud storage page linked at the bottom of the Playground. Without it, each project keeps its data in your browser only.
terrain-print/— Pick any area of the Moon, Mars or Earth and download a 3D-printable terrain model from real NASA, USGS and open elevation data: one tile, pieces that fit your printer bed, or a jigsaw puzzle in a frame, with a sea level you can raise or lowerghana-ludo/— Ludo with the Ghanaian house rules: back, side and home kicks. Play alone against bots, or with friends on their own phones: they pair with a QR code and play directly to each other, with no serverai-world-gen/— Describe a place and watch an AI build its map one tile at a time: a language model invents the world's vocabulary, a fast decision model (Jev) places every cell from its neighbours; bring your own OpenRouter keygithub-work-board/— A personal work board on your GitHub issues, with private notes only you can see; the notes are saved in your browser and, through the site's cloud storage, in a private repository you own, so they follow you between devices, and the page runs with no servermancala/— Two mancala games on one board: Kalah, and Ba-awa, the Ghanaian four-seed game with relay sowing and pits won between rounds; six AI opponents ranked by a tournament the project runs itselfwhatsapp-sticker-creator/— Turn a photo into a WhatsApp sticker: background removal you can paint over, framing, colour, captions and animated frames, all held to WhatsApp's own sticker rules; the animated WebP writer is its own, so nothing is fetched and nothing leaves the deviceglobal-news-map/— A world map of the day's news, pinned where it happened, read from Wikipedia's Current Events portal; any past day, with the original sources on every storyakwaaba-monsters/— A handheld-style creature-collecting game set in tropical Ghana, from the first village to the first gym; no image or audio files anywhere, every creature and note is generated in the browserwildfire-watch/— A live wildfire map for Europe: satellite hotspots, wind, the official fire danger, burnt areas and towns at risk, plus a demo modespanish-random-word/— A random everyday Spanish word from Spain; the definition stays hidden until you press the buttonunit-converter/— Type an amount and a unit in one box (100 km,5'10",1 1/2 cup,100 USD) and see every unit it can be at once, currencies includedsudoku-screenshot-coach/— Reads a sudoku out of a screenshot and explains the next best move, in English or SpanishChatGPTPong/— Canvas-based Pong gamegravity-sandbox/— Interactive N-body gravitational simulationphoto-editor/— Mobile photo editor with drawing, stickers, filters, and croppingseasonal-color-classifier/— Classify hex colors into seasonal palettesgithub-stats-dashboard/— GitHub repository analytics dashboardrandom-option-picker/— Slot-machine style picker with shareable URLs and reproducible seedstaboo-game/— Deterministic multiplayer Taboo game across phones, no server requiredliga-under-tkd/— Live taekwondo tournament site (standings, combats, athlete profiles) read from a shared Google Sheet, in CA/ES/ENrps-mind-reader/— Rock-paper-scissors against an AI that learns your habits and adaptsstreet-name-history/— Search any street, or tap one on a map, for all its names across languages, its former names, and its etymology (OpenStreetMap + Wikidata + OpenHistoricalMap)prime-sieve-arcs/— The Sieve of Eratosthenes as an animation: every prime hops over its multiples in glowing arcswhatsapp-no-contact/— Open a WhatsApp chat with any phone number without saving it as a contact: searchable country selector, number field, one button
Posts live in blog/<slug>/index.html as plain HTML: the text is in the file, nothing is rendered by a script, and each post carries its own stylesheet. Every fact, including the numbers behind each chart, is readable text, because the posts are written to be read by search engines and language models as much as by people. The list at triunitystudios.com/blog/ and the Atom feed are generated from each post's <head>.
why-a-blog-now/— Why writing for the open web makes sense again now that language models read it, and why these posts are plain HTML. The reference for the usual shape and length of a postai-world-gen-thousands-of-tiny-decisions/— How the AI World Gen map generator asks a decision model (Jev) one question per tile, compared with two faster methods, and the Galtea evaluation loop that measured every change, with charts and every version's maps. The reference for charts and data
GitHub Pages with custom domain. Push to main deploys automatically.
Pull requests are tested automatically: a GitHub Actions workflow runs the full test suite (bun test . -- the web-project tests plus the portfolio data validation) on every pull request, and a pull request cannot be merged while a test fails.