Colophon

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

Jekyll 3.10Static site generator with explicitly selected plugins
Pure CSSCustom properties, grid, clamp(). No Tailwind, no PostCSS.
Vanilla JavaScriptNo framework, no bundler. ES2017+.
GitHub PagesHosting, free, builds on push.
Cloudflare WorkersChatbot RAG backend at the edge.
TensorFlow.jsIn-browser sentiment model in the Live Lab.

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

Open source

Source: github.com/mcneillium/paulmcneill.github.io. The chatbot worker code is in workers/portfolio-bot/. Deployment guide: docs/chatbot-deployment.md.