A fast, static, dependency-free portfolio. Content lives in JSON and is rendered to plain HTML at build time — so the page ships as pre-rendered markup with no framework, no client-side rendering, and no runtime CSS/JS tooling. A single small script adds progressive enhancement (theme, nav, reveal).
- Overview
- Features
- Tech Stack
- Project Structure
- How It Works
- Getting Started
- Deployment
- Updating Content
- Accessibility & SEO
- Contact
- License
This is a hand-built, framework-free portfolio. Content is authored as JSON and compiled into a single static index.html by a tiny Node script — there is no client-side rendering and no third-party runtime. The browser downloads pre-rendered HTML, two small stylesheets, and one ~7.5KB progressive-enhancement script. Icons are inline SVG; utility CSS is a small hand-authored file (no Tailwind runtime).
Edit a JSON file, run
npm run render, and the static HTML is regenerated. The content exists in HTML before any JavaScript runs — good for crawlers, reader mode, and no-JS visitors.
The earlier version shipped the Tailwind browser runtime (~407KB) and the full Lucide icon library (~356KB) as blocking scripts, then re-rendered every section on the client — throwing away HTML it had already pre-rendered. This version compiles everything ahead of time: the JS payload went from ~885KB to ~7.5KB, the critical path is pure HTML/CSS, and the CSP no longer needs unsafe-eval.
| ⚡ Static-first | Content is pre-rendered to HTML at build time — meaningful markup before any JS runs. |
| 📦 JSON-driven content | Hero, stats, projects, skills, experience, and philosophy all come from data/*.json. |
| 🪶 Tiny runtime | One ~7.5KB defer script for theme, mobile nav, scroll-spy, and reveal. No framework, no CDN. |
| 🌗 Dark / light theme | One-tap toggle with localStorage persistence, OS-preference fallback, and no flash on load. |
| 📐 Case-study proof | Real metrics, achievements, and architecture notes surfaced directly in HTML — not hidden behind JS. |
| 🔎 Native disclosures | Per-project "Architecture & decisions" via <details> — accessible and JS-free. |
| ✨ Motion, done right | Scroll-reveal, a color-shifting scrollbar, and full prefers-reduced-motion support. |
| 🪟 Glassmorphism UI | Layered blur, gradient mesh background, and subtle noise texture — all CSS. |
| ♿ Accessible | Semantic landmarks, keyboard navigation, visible focus states, and a skip link. |
| 🔎 SEO-ready | Open Graph, Twitter Cards, JSON-LD structured data, sitemap, and robots.txt. |
- Markup & styling — HTML5, a small hand-authored utility stylesheet (
css/utilities.css) plus a component stylesheet (css/styles.css) for glassmorphism, CSS-variable theming, and animations. No Tailwind runtime. - Build — One Node ESM script (
scripts/render-content.mjs) compilesindex.template.html+data/*.json→index.html - Scripting — One ~7.5KB vanilla-JS file (
js/main.js),defer-loaded, for progressive enhancement only - Icons — Inline SVG (no icon library)
- Content — Static JSON files (
/data) - Hosting — Static assets deployed to Cloudflare (configured via
wrangler.jsonc)
Portfolio/
├── index.template.html # Page shell with __TOKENS__ for each section
├── index.html # Generated — do not edit by hand (run `npm run render`)
├── error.html # Custom, self-contained 404 page (theme-aware)
├── css/
│ ├── utilities.css # Small static layout helpers (replaces Tailwind runtime)
│ └── styles.css # Theme variables, glassmorphism, components, animations
├── js/
│ └── main.js # Progressive enhancement: theme, mobile nav, scroll-spy, reveal
├── scripts/
│ └── render-content.mjs # Build: template + data/*.json → index.html
├── data/
│ ├── profile.json # Name, tagline, "currently", philosophy, contact
│ ├── stats.json # Headline metrics band
│ ├── projects.json # Project cards, links, architecture notes
│ ├── skills.json # Skills grouped by category, with proficiency
│ └── experience.json # Work history + quantified achievements
├── assets/
│ └── images/ # Profile photo, logo, favicon
├── .well-known/
│ └── security.txt # Responsible-disclosure contact
├── .htaccess # Security headers & caching (Apache hosts)
├── robots.txt # Crawler directives
├── sitemap.xml # SEO sitemap
└── wrangler.jsonc # Cloudflare deployment config
Rendering happens once, at build time — not in the browser:
scripts/render-content.mjsreadsdata/*.json, escapes all values, and fills the__TOKENS__inindex.template.htmlto produce a fully staticindex.html. Real metrics, achievements, and per-project architecture notes are baked straight into the markup.js/main.jsruns after load (defer) and only enhances that static HTML: theme toggle (persisted, with<meta name="theme-color">sync), the mobile menu, IntersectionObserver scroll-spy, and scroll-reveal. If it never runs, the page is still complete and readable.- Project deep-dives use native
<details>elements — no modal JS, and the content is present for crawlers.
data/*.json ─┐
├─▶ render-content.mjs ─▶ index.html (static, pre-rendered)
index.template.html ─┘ │
└─▶ main.js enhances in place
The output is static files. The only "build" is regenerating index.html from JSON, which needs Node (no dependencies to install):
# Regenerate index.html after editing anything in data/ or the template
npm run render # == node scripts/render-content.mjsThen serve the folder with any static server:
# Python 3
python -m http.server 8000
# Node.js
npx serve .
# Cloudflare (mirrors production)
npx wrangler devThen open http://localhost:8000 (or the port your tool prints).
The repo is configured for Cloudflare static-asset hosting via wrangler.jsonc:
npx wrangler deploywrangler.jsonc serves the project directory as static assets. The included .htaccess adds security headers and caching rules for Apache-based hosts.
All content lives in /data — edit the JSON, then run npm run render:
| File | Controls |
|---|---|
data/profile.json |
Name, title, tagline, "currently" line, engineering philosophy, contact |
data/stats.json |
Headline metrics band |
data/projects.json |
Project cards, links, and "Architecture & decisions" notes |
data/skills.json |
Skills, categories, and proficiency |
data/experience.json |
Work-history timeline and quantified achievements |
Heads-up:
index.htmlis generated. Never edit it by hand — change the JSON (orindex.template.html) and re-runnpm run render, or your edits will be overwritten on the next build.
- Semantic landmarks, ARIA labels, a skip link, and an aria-live region for section changes
- Full keyboard support, visible focus styles, and native
<details>disclosures - Honors
prefers-reduced-motion(disables reveal, float, and scrollbar animation) - Content is real HTML before JS runs — usable in reader mode, by crawlers, and with JS disabled
- Open Graph + Twitter Card metadata, JSON-LD
Personschema,sitemap.xml, androbots.txt
Released under the MIT License.