Static Site • GitHub Pages • Vanilla JS

Portfolio Website

This site itself: a static, framework-free build with one shared design system across every page. Originally a Squarespace site with each page pasted in as a Code Block, now a plain git repo of HTML, CSS, and JavaScript served from GitHub Pages, with a shared nav, footer, and related-projects system that renders itself from a handful of JS registries instead of hand-edited markup repeated on every page.

Status Live, Actively Maintained

Fully migrated off Squarespace with the subscription cancelled. Grows with ordinary maintenance as new projects ship.

Project Summary

Project Overview

Purpose

One consistent design system and workflow for adding new project pages and content quickly, without fighting a page builder or carrying a recurring subscription.

Problem

Squarespace's workflow meant pasting HTML, CSS, and JS by hand into a Code Block on every page: no git history, no real diffs, and a monthly bill for hosting a site that doesn't need a page builder at all.

Approach

Move to GitHub Pages: every page becomes a real file in a git repo, deployed by a push, sharing one design system and a small set of registry-driven scripts instead of copy-pasted markup.

Inspiration

Where the Homepage's Personality Came From

The homepage's tile hero, hover-driven Kevin spritesheet, and KevinOS terminal trace back to a long-time favorite: Jesse Schell's personal site. Schell led the creation of Disney's Toontown Online, a childhood favorite, before founding Schell Games and writing The Art of Game Design. His homepage swaps a conventional nav bar for a single illustrated, sleeping version of himself; interacting with it wakes him up and reveals speech-bubble links out to his book, studio, and talks.

Reference

An Illustrated, Interactive Homepage

jesseschell.com replaces a standard nav with one character whose state changes on interaction, surfacing links as speech bubbles instead of a menu bar.

Applied Here

A Spritesheet Hero Instead of a Static Banner

kevinlewis.net's homepage borrows the same idea: a 9-frame Kevin spritesheet and KevinOS terminal that respond to hover instead of sitting still, turning the hero into something explorable rather than decorative.

Visit jesseschell.com
Architecture

How It Works

Pages01

Clean URLs, No Build Step

Every route is a plain <slug>/index.html file (About, Projects, 404, and one folder per project), so URLs stay clean with no .html extension and no bundler sits between editing a file and it going live.

Design System02

One Shared CSS + JS Pair

Every interior page pulls the same kl-design-system.css and site.js, wrapped in a .kl-page class. The homepage is the one deliberate exception, kept fully isolated with no .kl-page wrapper so interior changes can never touch it.

Registries03

Nav, Footer, and Related Projects Render Themselves

The top nav, the footer, and each project page's "Keep Exploring" suggestions are all driven by small JS registries instead of markup repeated across 20-plus pages. Add one entry once and every page picks it up on load.

Hosting04

GitHub Pages + a Real Domain

Static files served straight from the main branch of a private repo, with a CNAME file and Pages' custom-domain setting pointing kevinlewis.net at GitHub's edge, HTTPS enforced.

Companion System

Admin Hub

A separate subdomain hosts a private links hub to every project's live, admin, or status page, grouped Live, In Progress, and Planned. It's architecturally distinct from the main site: not GitHub Pages, but a small zero-framework Node server deployed to the same always-on EC2 box as Bob-a-Bot, behind a Caddy reverse proxy handling automatic HTTPS.

Hosting01

Node Server on Shared EC2

A zero-framework Node server built on the built-in http and crypto modules, deployed via scp and pm2 alongside Bob-a-Bot on the same EC2 box, with DNS pointed at it via an A record rather than a GitHub Pages CNAME.

Identity02

AWS Cognito for User Management

Gated by an AWS Cognito Hosted UI user pool with self-registration off. The server runs the OAuth2 authorization-code exchange, verifies the returned ID token against the pool's JWKS, and sets an HMAC-signed session cookie, no passwords or session state handled by the server itself.

Tech Stack

Tools + Build

HTMLCSSVanilla JavaScriptGitHub PagesGitGitHubGoogle FontsOpen SansJetBrains MonoContent Security PolicyDNS (A Records)
Engineering + Design

Decisions Worth Calling Out

Platform01

GitHub Pages Over Squarespace

Moved off a paid page-builder subscription onto free static hosting already used for other projects, trading a visual editor for git-based version control, real diffs, and a push-to-deploy workflow.

Stack02

No Framework, No Build Step

Every page is plain HTML, CSS, and JS behind one shared stylesheet and script. For a mostly-static content site, a framework and bundler would add tooling without solving a real problem here.

Structure03

Registry-Driven Nav, Footer, and Related Projects

Rather than hand-editing a nav block on every page, the nav, footer, and "Keep Exploring" suggestions render at runtime from three JS registries, so adding a link or a project happens in one place instead of 20-plus.

Security04

CSP Delivered as a Meta Tag

GitHub Pages serves static files with no way to set custom HTTP response headers, so the Content-Security-Policy ships as a meta tag in every page's head instead of a real header, the only mechanism available without adding a server or CDN layer.

URLs05

Clean Slug URLs Over .html

Every route is <slug>/index.html rather than <slug>.html, so links read as /projects/pi-tether instead of carrying a file extension.

Assets06

Self-Hosted Images, No Hotlinking

All images live in the repo's own assets folder rather than pointing at another repo or CDN, so the site never depends on an external source staying online or public.

Platform Migration

Squarespace → GitHub Pages

The site ran on Squarespace for years with its built-in navigation disabled and every page rebuilt as a full-page Code Block. The migration replaced that entirely: no paste step, no subscription, no page builder standing between an edit and the live site.

Rebuild01

22 Interior Pages, Rebuilt as Real Files

Every page converted from a pasted Code Block into its own <slug>/index.html, plus a from-scratch homepage rebuild since it was Squarespace's native block editor, not a Code Block, so nothing could be reused directly.

DNS02

A Records Repointed at GitHub's Edge

Squarespace's own DNS Settings dropped the Squarespace Defaults A records and added GitHub Pages' four A records for the apex domain, leaving Google Workspace email untouched.

Verify03

A Live Sweep Before Cancelling

Before cancelling Squarespace: every page checked for a 200 response, every referenced image checked for resolving, a console-error sweep, and a grep for any remaining Squarespace reference across every served file.

Cutover04

Subscription Cancelled

Domain registration and Google Workspace email were never billed through Squarespace, so cancelling it left both untouched. Only the recurring site-hosting subscription went away.

Problem Solving

Notable Challenges

A few problems surfaced only because the migration forced a genuinely close look at what was actually deployed, not just what looked right locally.

Symptom01

Fixes That Never Left Scratch Work

Cancelling Squarespace forced a full recheck of the deployed files, which surfaced that a few features built earlier in the migration, including an image lightbox and a photo crossfade, had only ever existed in in-session scratch copies, never the actual committed files.

Fix02

Rebuilt and Reverified Against the Live Site

All of them were rebuilt from scratch and confirmed live, not just locally, which is now the standing rule for this project: verify against what GitHub Pages is actually serving, not the local working copy.

Symptom03

A Dropdown That Closed Before the Mouse Arrived

The Interactive Projects nav dropdown closed before a real mouse path could reach an item inside it.

Fix04

CSS Gap-Bridge, Then a JS Grace Timer

A CSS-only gap-bridge fix helped but wasn't reliable against real mouse movement, replaced with a JS grace-period timer keyed off the same class the mobile tap-toggle already used.

Symptom05

Stale DNS Cache Looked Like a Failed Cutover

After repointing DNS, the apex domain still resolved to the old Squarespace answer well after the change went through.

Fix06

Checked a Public Resolver Instead

The local machine's DNS resolver was serving a stale cached answer; querying a public DNS resolver directly confirmed the change had actually propagated.

What Changed

Cost

Hosting

$0 a Month

GitHub Pages hosting is free, replacing a paid Squarespace plan.

Unaffected

Domain + Email, Unchanged

Domain registration and Google Workspace email were never billed through Squarespace, so cancelling it did not touch either.

Ongoing

Time, Not Money

The only ongoing cost is maintenance time, writing new project pages and keeping the design system consistent, the same as before, just without a recurring bill attached to it.

Roadmap

What's Next

Backlog01

Ongoing Content

New project pages and image updates as new projects ship, the same maintenance rhythm as before the migration.

Backlog02

Automated Checks

A lightweight check for broken links or malformed HTML before a push goes live, instead of relying on manual sweeps.

Keep Exploring

More projects like this

← Back to all projects