How this site is built.
A short tour of the stack, the design decisions, and the AI bits behind the scenes. The whole thing is open-source — fork, copy, learn from it, ship your own.
The stack
Design decisions
Dark by default, no apologies
The site ships dark. The light theme exists for users who prefer it but
the design is built for the dark palette first. prefers-color-scheme
is intentionally ignored on first load — only an explicit click switches.
Tokens all the way down
Every colour, font, and easing curve is a CSS custom property in
:root. The accent picker swaps eight tokens; the font picker
swaps three. Light theme is a single [data-theme="light"]
override on the same tokens — no rule duplication.
One stylesheet and focused scripts
No bundler, no code-splitting. The CSS is hand-written and lives in
assets/css/main.css. The JS is split into a few focused
files (main.js, theme.js, accent.js,
fonts.js, chatbot.js, terminal.js,
spotlight.js, card-tilt.js,
agent-visualiser.js, ml-demo.js,
project-filter.js) — all loaded with defer,
none required for first paint.
No flash of anything
The stylesheet loads before the page is painted. A small external script reads the saved theme, accent and typography preferences before the body renders. The default uses Space Grotesk and DM Sans.
The AI features
Talk to Paul (chatbot)
The portfolio assistant retrieves relevant passages from a bundled knowledge base using keyword matching, then asks Cloudflare Workers AI’s Llama 3.1 8B model for a short answer. A source panel shows the retrieved passages. Persistent limits allow 100 inference attempts per UTC day across the site, with 10 per hour and 20 per day per IP address. Replies are capped at 256 tokens.
Live Lab (TF.js sentiment)
A real CNN running in your browser. The sentiment_cnn_v1
model from the TensorFlow.js model zoo, lazy-loaded only when the
Live Lab section enters the viewport. Tokenises with the model's
metadata vocab, runs inference on WebGL, reports timing in milliseconds.
Total cold payload ~1.8 MB, cached after first visit. No data leaves
your device.
Agent visualiser
A hand-rolled SVG flow showing how a research agent decomposes a task — Input → Planning → Tool Selection → Execution → Memory → Synthesis → Output. Auto-plays on viewport entry, pauses when scrolled out, supports play/pause/step/reset and 1×/2× speed.
Terminal
A CLI-style portfolio explorer. Type paul --help to start.
Pure frontend — all data comes from _data/*.yml serialised
into a JSON blob at build time. Up/Down history, Tab completion,
Ctrl-L to clear, and a matrix easter egg.
Performance & privacy
- No tracking pixels. No Google Analytics. No advertising.
- Case-study videos use lazy-loaded YouTube embeds.
- Static pages and assets are served through GitHub Pages and browser caching.
- The chatbot doesn't log or retain messages.
- See the privacy page for the full picture.
Open source
Source: github.com/mcneillium/paulmcneill.github.io.
The chatbot worker code is in workers/portfolio-bot/.
Deployment guide: docs/chatbot-deployment.md.