How This Site Works: A Tour Under the Hood

You're reading a page that has no backend server - and yet it has a real database, a login, and an analytics dashboard. Here's how that's possible, what you can do here, and the tricks worth stealing.

Most portfolio sites are a stack of static pages. This one looks like a stack of static pages - that's deliberate, because static is fast and search engines love it - but the project cards, the contact form, the visitor analytics, and the password-protected admin panel are all wired to a live Postgres database. There is no API server in the middle that I wrote and maintain. The browser talks to the database directly.

That sounds reckless until you see how it's locked down. The rest of this post is the guided tour: skim the diagrams, poke the interactive bits, and open the "for the curious" drawers if you want the gritty detail.

01 The one idea that explains everything

There is no custom backend server. My JavaScript calls a database API directly, and a security layer inside the database decides who can read or write.

Deep dive: The whole site in one sentence
// The whole site in one sentence

There is no custom backend server. My JavaScript, running in your browser, calls a database's auto-generated API directly - and a security layer inside the database decides, row by row, what each visitor is allowed to read or write.

Everything else is a detail of that idea. The "API key" shipped in the page is public on purpose; it only says "this request is an anonymous visitor." It grants no powers by itself. The actual gatekeeper is Row Level Security (RLS) in Postgres, and we'll play with it in section 07.

02 The stack, in plain terms

No framework, no build step, no bundler. It's hand-written HTML, CSS, and JavaScript - the "vanilla" stack - with Supabase (managed Postgres) doing the heavy lifting.

Deep dive: The stack breakdown
LayerWhat it isIts job here
PagesHand-written HTMLOne file per page. Server-rendered-by-default = great SEO, with JSON-LD, a sitemap, and an llms.txt.
StylingPlain CSSA small design system built on CSS variables (colour, spacing). No Tailwind, no Sass.
BehaviourVanilla JavaScriptCustom cursor, carousels, overlays, the project reveal, two little games. No React or Vue.
BackendSupabase (managed Postgres 17)Database + login + an auto-generated REST API, all in one. This is the only moving part.
HostingVercelServes the static files from a global CDN. A git push deploys.
AnalyticsHome-grownCookieless, no personal data, honours Do-Not-Track. The data is mine, not a third party's.
Why bother going framework-free? A recruiter opening this on hotel Wi-Fi gets plain HTML that paints instantly. There's no hydration, no megabyte of JavaScript, nothing to "warm up." The one thing that needs a network round-trip - the project list - has an offline fallback baked in, so the page never shows up empty.

03 The architecture map

Three zones: Your browser runs the app, Vercel hosts files, and Supabase handles all data (projects, messages, analytics, skills, and certs).

Deep dive: Architecture diagram

Your browser runs the whole app. Vercel only hands over files. Supabase is the entire backend. The teal lines are the live data calls that travel straight from the browser to the database.

Your browser The pages (HTML + CSS) home · projects · contact · blog · admin The JavaScript missions.js · analytics.js · contact.js main.js · admin.js · the games + a tiny database client localStorage anonymous id · your login token Offline fallback a bundled copy of the project list, used if the database is unreachable Loaded from public CDNs the database client and the web fonts Vercel - file host Global CDN serves the static files, fast git push → deploy push to main, it auto-builds Supabase - the backend Auto REST API every table becomes an HTTPS endpoint automatically Login (Auth) email + password, for the admin Postgres database projects & skills - interactive UI messages - the contact inbox events - the analytics log + one function that builds the dashboard 🔒 Row Level Security the real gatekeeper - decides per row who may read or write what GET files read the projects log in (admin) send a message / log an event
The browser is the app. Vercel hands over files; Supabase is the backend. Teal lines = live data calls.

04 What happens on one page load

The browser fetches static files, scripts load in order, analytics logs silently, data is fetched, and the UI renders - with offline fallbacks if the database is down.

Deep dive: The load sequence

Follow a single visitor from typing the URL to seeing project cards. This is the order things actually run in:

  1. The browser asks Vercel for the page.

    It gets back the HTML, CSS, and images straight from the nearest CDN edge - no server thinking required.

  2. The scripts load, in a fixed order.

    The database client first, then the config, then analytics, then the UI, then the data layer. Each one needs the one before it.

  3. Analytics fires a quiet "pageview."

    One write to the database. If it fails, nothing breaks - analytics is designed to fail silently.

  4. The data layer asks for the projects.

    It requests the published project rows. The database returns only those - drafts never leave the building.

  5. The cards render, and the page is interactive.

    If the database was unreachable, it quietly falls back to a bundled copy of the list so you still see cards.

Built to not break: if the database is down or the key is blank, the page still renders from the offline fallback and analytics turns into a no-op. The site is designed to work even with the backend completely unplugged.

05 The three data flows

Everything dynamic is a direct round-trip from the browser to the database, governed by strict read/write rules.

Deep dive: The data flows

Everything dynamic on the site is one of three round-trips to the database. Tap each one:

Reading the projects

Public · happens on every homepage load

The data layer asks for projects where published = true. The database applies the read rule and returns only those rows.

missions.js
"give me the projects"
🔒 read rule
"public sees published only"
projects table
returns the visible rows

Drafts stay invisible to the public - not hidden by CSS, actually filtered out before they leave the database.

Logging an analytics event

Public · fire-and-forget, write-only

A pageview, a project click, time-on-page - each becomes one insert into the events log. The public can write events but can never read them back.

analytics.js
click / pageview / tab-hide
🔒 write rule
size + numeric guards
events table
append-only

No read-back, and errors are swallowed - analytics can never slow down or break the page.

Sending a contact message

Public · guarded write you can't read back

The form runs client-side checks first (a honeypot for bots, a disposable-email blocklist, a typo-fixer), then inserts one message. Only the logged-in admin can ever read the inbox.

contact.js
honeypot + email checks
🔒 write rule
body length 1–5000
messages table
admin reads it later

If the insert ever fails, the form builds a pre-filled email instead - so it's never a dead end.

06 The database

Six tables. Three are simple ledgers (projects, messages, events), while the rest power the new dynamic Skills & Certifications modules.

Deep dive: The schema

projects 10 rows

The cards on the homepage. Public can read the published ones; admin has full control.
  • code · MSN-01
  • title · summary
  • role · method · outcome
  • chips · metric pills
  • github_url · optional
  • published · the gate

messages 5 rows

The contact inbox. Public can insert; only admin can read, triage, or delete.
  • name · email · phone
  • body
  • status · received…
  • flagged · priority star
  • created_at

events ~2,000 rows

The analytics log. Public can insert (write-only); admin reads it as aggregates.
  • type · pageview…
  • path · source
  • session_id · anon id
  • meta · flexible JSON
  • created_at

skills & certs

Powers the interactive canvas. Admin curates; public reads.
  • skill_categories
  • skill_nodes
  • certifications
For the curious: the one clever bit

The admin dashboard could run a dozen separate queries against the raw events. Instead, a single database function does all the counting and grouping server-side and hands back one tidy JSON blob - KPIs, top pages, a daily series, the game leaderboard.

It's set up so the public can write events but can never read the analytics back out: permission to run that function is granted to logged-in users only.

-- the whole dashboard, one call, admin-only
select intel_dashboard();
-- → { kpis, traffic, topPages, daily, leaderboard }

07 Who can do what - try it

A live simulator showing how Row Level Security blocks unauthorized reads and writes.

Deep dive: Interactive RLS simulator

This is the part that makes "no backend server" safe. Pick who you are, pick what you want to do, and see whether the database lets you - and why. This mirrors the actual rules.

// Permission simulator
I am…
I want to…
ALLOWED Published projects are public, so anyone can read them.
The takeaway: the public key in the page can't do anything dangerous, because every request is checked against these rules inside the database. The powerful key (the one that would bypass them) never touches the browser - it lives only in the Supabase dashboard.

08 The signature 3-tier reveal

Projects unfold in three stages (card → hover → modal) without extra network calls.

Deep dive: The reveal pattern

The nicest interaction on the site is how a project unfolds in three stages. Each tier shows more of the same record that was already loaded - so there are zero extra network calls. Try it for real on the homepage project grid; here's the anatomy:

TIER 1 - ALWAYS VISIBLE MSN-01 Fraud Triage XGBoost / Gemini 0.978 AUC; caught fraud the base model missed. in the grid → TIER 2 - ON HOVER MSN-01 Fraud Triage A two-stage pipeline that catches what the model misses. 0.978 AUC 84% RECALL a floating preview · no network TIER 3 - ON CLICK ← ALL MISSIONS MSN-01 Fraud Triage A pipeline that catches what the model misses. ROLESolo build, end to end METHODWeighted-loss model + triage OUTCOMEAuditable reasoning trail SKILLS USED / LEARNT XGBoost · Google Gemini · Python 0.978 AUC 284K RECORDS hover click
One record, revealed in three depths: card → hover preview → full modal. The click is the only one that logs an event.

It's a classic UX pattern - progressive disclosure - done without a single extra request, because the full record is already in memory from the first load.

09 The admin console

The protected CMS where I edit projects, manage skills & certs, read messages, and view analytics.

Deep dive: The Ops Console

Behind a login at /admin is the one page only I can use. To the public it's just a login screen; once authenticated, the database's rules flip open and the page becomes a small content-management system for the whole site.

// WHAT THE PUBLIC SEES // RESTRICTED OPS CONSOLE Authenticate to continue. OPERATOR EMAIL PASSPHRASE login → // WHAT I SEE ONCE LOGGED IN OPS CONSOLE ▸ PROJECTS MESSAGES INTEL SKILLS CERTS SIGNED IN AS admin Projects + new MSN-01 · Fraud Triage edit MSN-02 · Corporate Analytics edit MSN-03 · Heart Disease Prediction edit VISITORS · LAST 7 DAYS SAVE CHANGES
Left: the login everyone sees. Right: the unlocked console - edit projects, read the inbox, and view visitor stats.

From the console I can:

  • Edit the live site's content. Add, edit, reorder, publish, or unpublish a project - and the homepage updates instantly. Same goes for the new inline Skills and Certifications editor canvases!
  • Read and triage the inbox. See contact messages, flag the important ones, mark them handled, delete spam. The public can send mail but can never see this.
  • Watch the visitors. A dashboard of KPIs, top pages, traffic sources, a daily trend, and even a leaderboard for the hidden games - all from that one server-side function.
Why this is genuinely safe: the console doesn't have secret powers baked into the page. It logs in, gets a token, and from then on the database recognises the request as "the owner" and applies the owner's rules. Log out and the exact same page can do nothing but show the login box again.

10 Shipping & little tricks

A few details I'm fond of, for anyone building something similar.

Deep dive: The little details

Two separate update paths. Design and code changes ship through git push → Vercel. Content changes (project copy, reading messages) happen live in the database via the admin console. That separation means a recruiter never catches the site mid-redeploy.

Analytics you actually own. No Google Analytics, no third-party script watching visitors. A tiny home-grown tracker writes cookieless events - no IP, no name, just a random id in your browser's storage - and it honours Do-Not-Track. The data lives in my database, and I read it through the admin dashboard.

Hidden games. There's a little driving game and a tic-tac-toe tucked into the site as easter eggs. Beat them and your score quietly joins the leaderboard in the admin dashboard - the same analytics pipe, just having fun with it.

Custom crosshair cursor Hover-swap headings Focus-trapped modals Reduced-motion fallbacks Skip link + AA contrast JSON-LD + sitemap + llms.txt Offline fallback Pre-filled mailto safety net

Accessibility and SEO weren't afterthoughts. Skip link, visible focus rings, a reduced-motion mode, and colour contrast that clears the standard. Because the pages are real HTML (not rendered by JavaScript), search engines and even language models can read them cleanly - there's a hand-written llms.txt for exactly that.

The whole point: a portfolio that's fast and simple on the surface, but quietly runs on a real database - and stays safe doing it because the rules live where the data lives.

↑ back to top

MORE NOTES

More on how things actually got built.

ALL POSTS