A static, zero-backend personal site: home/about, work (pulled live from GitHub), writing (blog), and a password-gated admin panel so I can manage posts myself.
-
Set my own admin password. In
js/admin.js:const ADMIN_PASSWORD = '...';
Read the security note below first - this is a light gate, not real auth.
-
Update contact links. In
index.html, swap the placeholdermailto:...and LinkedIn URL for my real ones. -
Swap in real photos. Each dashed "photo placeholder" box in the "Off duty" section on
index.htmlis a plain<div class="photo-slot">. Replace it with an<img>tag once I've picked images, e.g.:<img src="assets/img/golf.jpg" alt="On the course" style="width:100%; border-radius:2px; margin-top:16px;">
Images go in
assets/img/.
Plain HTML/CSS/JS - no build step. Easiest free options:
- GitHub Pages: push this folder to a repo, enable Pages on the
mainbranch, done. - Netlify / Vercel: drag-and-drop the folder or connect the repo; both auto-detect static sites.
Point the domain's DNS at whichever I choose.
Posts are stored in data/posts.json and loaded into the browser's localStorage on first visit. The admin panel at /admin.html reads and writes to that same local storage - so I can draft, edit, and delete posts entirely in the browser.
The catch: localStorage is per-browser, per-device. If I write a post on my laptop, it won't appear for site visitors (or on my phone) until I:
- Click Export posts.json in the admin panel.
- Replace
data/posts.jsonin the repository with the downloaded file. - Push/redeploy.
That's the trade-off of having no backend or database - free hosting, no dependencies, at the cost of a manual publish step. Fine for now since I'm the only one writing.
Swap the storage layer for something with a small always-on backend, e.g.:
- Supabase or Firebase (free tier) - a
poststable, admin panel writes directly to it,blog.html/post.htmlread from it. - A tiny serverless function (Cloudflare Worker / Vercel function) backed by KV or a JSON file in a private repo, triggered by the admin panel.
Either removes the export/import step and the localStorage limitation entirely.
The password check in js/admin.js runs entirely in the browser - anyone who views page source can read the password. It's enough to stop a casual visitor from finding the editor UI, but it is not real authentication. Not reusing a real password here, and not relying on this to protect anything sensitive. Real login is part of the Supabase/Firebase upgrade path above if I need it later.
- Favicon:
assets/img/favicon.svg(modern browsers) withfavicon-512.png/apple-touch-icon.pngas fallbacks - a "DS" monogram with the same status-dot accent as the nav logo. - Every page has Open Graph + Twitter Card tags so links preview properly on LinkedIn, Slack, iMessage, etc. They currently point at
https://www.danshaw.dev/...- update the domain in theog:url/og:imagetags once the real domain's live, otherwise the preview image won't load. assets/img/og-image.pngis the 1200×630 share image used everywhere. Easy to regenerate or swap for something else any time - it's a plain static PNG.post.htmlupdates its own title/description via JS for anyone browsing in a real browser, but most link-preview bots (LinkedIn especially) don't execute JavaScript, so shared post links currently show the generic site preview rather than that post's own title/excerpt. Fixing that properly needs server-side rendering or a prerender step - a contained follow-up, not a rebuild, if it starts to matter.
A small canvas animation on the homepage: a robot moves at constant speed along a single-stroke path, drawing a trail that spells out "DAN SHAW," lifting its pen between letters like a real plotter, then pausing, fading, and looping.
- Lives in
js/robot-trail.js, rendered inside.signature-striponindex.htmlonly. - Respects
prefers-reduced-motion- draws the finished, static trail with no animation for anyone with that OS setting on. - To change the wording it plots, edit the
WORDconstant near the top ofjs/robot-trail.js. OnlyD A N S H A Wand space have letterforms defined right now - extend theLETTERSobject for other letters.
index.html Home - hero, experience timeline, personal section, contact
projects.html Work - live GitHub repos (fallback data if the API is rate-limited)
blog.html Writing - post list
post.html Single post view (?slug=your-post-slug)
admin.html Password-gated post editor
css/style.css Design system
js/ Shared logic (nav, GitHub fetch, post storage, admin, markdown render)
data/posts.json Seed posts (loaded into localStorage on first visit)