i refused to build another boring portfolio
every developer portfolio looks the same. hero section, "hi, i'm x", three project cards, a contact form nobody will ever use, and a dark mode toggle as if that counts as a personality. i looked at all that and just tho
every developer portfolio looks the same. hero section, "hi, i'm x", three project cards, a contact form nobody will ever use, and a dark mode toggle as if that counts as a personality.
i looked at all that and just thought, no.
so i built my own. not to get hired, not to impress anyone, just because i wanted a corner of the internet that actually feels like me. if a recruiter stumbles in, hi, sorry about the mess.
what's in there
- a blog with an rss feed, because i have opinions and nowhere to put them
- six themes (manga is the default, then midnight, sakura, matcha, orange and a gameboy one) plus cursor effects, because one color scheme was never going to be enough
- sound effects made entirely with the web audio api, no audio files, off by default because i'm not a monster, and a music player that keeps playing as you move between pages
- a game house with five games: snake, flappy, memory match, dino, and "waifu or not?", where you swipe left or right and get judged. it's exactly as unserious as it sounds
- reactions on projects and posts (skull, fire, alien, lol), a guestbook with replies, and a visitor counter
- a contact form that takes attachments up to 10mb and sends them straight to my telegram
- chatguys, a public chat room where messages disappear after a week, with two bots living in it
- a page where claude wrote its honest opinion about me. it called me a developer-otaku hybrid, which is rude and accurate
- an admin page where i change my status, moderate the guestbook and manage a private photo vault
how it's built
no framework. plain html, css and vanilla js on github pages. i looked at the usual stack and decided my homepage did not need a build pipeline the size of a small country.
- backend: one cloudflare worker (about 56kb of js, because splitting files is for people with a plan) sits in front of supabase. it proxies requests, guards reserved names, and handles the contact form, link previews and the bots.
- database: supabase (postgres) with row level security. guestbook, reactions, visits, status, chat messages and presence each have their own migration file. a pg_cron job deletes chat messages older than 7 days every night.
-
bots:
@botanswers fixed commands (joke, define, weather, time, github lookups).@aiis a groq-backed reply that remembers the last 8 messages. it's limited to 5 per minute per session and 200 a day across everyone, tracked in cloudflare kv. the api key only exists as a worker secret, never in the browser. - files: the worker forwards contact attachments to telegram, and the vault talks to backblaze b2 through the worker, which signs the requests itself (aws sigv4) instead of pulling in an sdk.
-
content:
projects.jsonandblog.jsonare the source of truth. a python script (standard library only) bakes them into the html on every push through a github action, so crawlers and ai agents that don't run javascript still see everything. there's also anllms.txt, so even the robots get a tour. - staying awake: another github action pings supabase every 5 days so the database doesn't doze off.
the telegram bot that runs my site
the worker also doubles as a telegram bot that only i control. every contact message, guestbook entry, reply and reaction lands in my chat, so the site basically texts me when someone shows up.
it also takes commands:
-
/pingchecks the whole infrastructure: the worker, the site, supabase, and the cdns i depend on, with latency for each -
/statsshows today's visits, guestbook entries, reactions, and my current status -
/status <message>changes the "ovi says" banner on the homepage. i can update my own website from a chat app, in bed, like a professional -
/aboutand/helpexplain what it does
/status is locked to my chat id. anyone else who tries gets a polite "unauthorized".
![]() |
reactions, guestbook entries, replies, and /stats on demand. |
the parts that fought back
- link previews: browsers can't read other sites' open graph tags (cors), so the worker fetches them. that meant writing an ssrf guard that re-checks every redirect hop. nobody warns you that a chat room can quietly become a "fetch any url for me" machine.
-
impersonation: the second you add a public chat, someone tries to be "admin". reserved names are blocked for everyone except me with a real session token, and
botandaiare blocked even for me, since their replies are written server-side.
the honest part
this was supposed to be a weekend project. it was not a weekend project.
i kept redesigning it. i'd get the look to a place i liked, open it the next day, hate it, and start over. the color direction changed more times than i want to admit. somewhere in there i also decided a static site wasn't enough, so i bolted on a cloudflare worker and supabase, and then spent way too long on stuff the visitor will never see.
the worst part is that i'd already burned out on programming once, and this almost did it again. what fixed it was admitting it's a hobby. no deadline, no audience, nobody to impress. once i stopped treating it like a deliverable, it got fun again, and the site got better too.
what i learned
if your portfolio could belong to anyone, it doesn't really belong to you. put the weird stuff in. keep the sarcasm. make something you'd actually want to open on a random tuesday.
go look at it
live site: https://iamovi.github.io
repo: https://github.com/iamovi/iamovi.github.io
a quick look
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
it's cc by 4.0, so take it, change it, build on it, just give me credit. even sarcastic people like credit.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.











