A free, open-source portfolio template: dark, motion-forward, and shamelessly in love with the em-dash (until you change it).
It's not another minimalist dev-blog starter; it's a full personal-site system: a single-page scroller for the homepage, plus real sub-pages for essays, experiments, and password-gated case studies. Every page is hand-written static HTML; no build step, no framework, no dependencies. Open index.html in a browser and it just works.
The demo copy is Em-Dash Ipsum: placeholder text written entirely in em-dashes and "it's-not-X—it's-Y" sentences. It's a joke you can read, and a working demo of the type system at the same time. Replace it with your own words and the jokes leave with it.
Live demo persona: "Your Name." That's your cue; search for it and swap in yours.
portfolio-starter/
├── index.html # Home — single-page scroller (Who, Thinking, AI Lab, Selected Work, Personal)
├── 404.html
├── robots.txt · sitemap.xml · llms.txt # discovery files (edit the domain)
├── LICENSE # MIT
├── thinking/
│ ├── the-em-dash-manifesto.html # essay page format (TOC rail, pull-quotes, figures)
│ └── its-not-this-its-that.html
├── lab/
│ ├── em-dash-ipsum.html # a real, working placeholder-text generator
│ └── the-dashinator.html # "making-of" write-up format
├── work/
│ └── case-study.html # gated case study as a STAR scroll-snap slide deck
└── assets/
├── favicon.svg
├── resume.pdf # placeholder — swap for your own (homepage links to it)
└── aww/ # cute-animal placeholder images (swap for real art)
- Floating liquid-glass navigation: a borderless, blurred sidebar on desktop that collapses to a hamburger on mobile.
- Per-section accent shifts: the accent color (
--pop) and background tint change as you scroll between sections, and cycle on the hero. - Animated word-rolls: the rotating word in the headline and the status line in the nav.
- Reveal-on-scroll: content fades up as it enters the viewport (and respects
prefers-reduced-motion). - Scroll-progress nav fills + scroll-spy: nav items fill as you read; the active section is highlighted.
- In-essay TOC rail that auto-syncs to your
<section id>s. - A client-side "velvet rope" password gate for private case studies.
It's all vanilla HTML/CSS/JS. No React, no Tailwind, no CDN scripts (except Google Fonts, which you can self-host or remove).
The desktop sidebar collapses to a hamburger, blur effects switch off under 900px (they can freeze scroll on some Android devices), and every layout reflows to a single column.
- Your name. Find-and-replace
Your Nameacross all.htmlfiles. (Also check the wordmark inindex.html: it's split asYour<br>Name.) - Your words. Replace the Em-Dash Ipsum copy. Every page is plain HTML; just type over it. Keep an em-dash or two out of respect.
- Your images. Drop real art into
assets/and update thesrc/width/height/alton each<img>. Delete theassets/aww/placeholders when you don't need them. (Image-size cheatsheet below.) - Your links. Search for
data-user="you"/data-domain="example.com"(email) andyour-handle/example.com/yoursite.com/your-other-thing.com(socials + canonical URLs). Update the JSON-LD blocks in<head>too. - Your analytics. Each page has an
<!-- Analytics: drop your snippet here -->marker in<head>. Paste GA4 / Plausible / Fathom / nothing.
| Slot | Used on | Aspect | Suggested px |
|---|---|---|---|
| Home hero | index.html |
portrait, anchored right, full-bleed | ~1300×2300+ |
| Essay hero | thinking/, lab/the-dashinator |
wide banner | ~2400×620 |
| Case-study cover | work/case-study.html |
wide (sits faint behind the title slide) | ~2400×620 |
Floated figure (figure.tall) |
essays | portrait | ~600×1200 |
In-body figure (figure.media) |
essays | wide | ~1000×800 |
| Before/after pair | work/case-study.html |
square-ish | ~1000×830 |
work/case-study.html is locked behind a lightweight password gate. The demo password is demo.
It is not real security; it's a "DM me for access" convenience that also keeps the page out of search results (<meta name="robots" content="noindex">). Don't gate anything genuinely confidential with it.
To set your own password, compute the SHA-256 of your password and paste it into the HASH constant in the page's gate <script>:
printf 'yourpassword' | shasum -a 256…or in a browser console:
crypto.subtle.digest('SHA-256', new TextEncoder().encode('yourpassword'))
.then(b => console.log([...new Uint8Array(b)].map(x => x.toString(16).padStart(2,'0')).join('')))Optional GA "gate funnel" events (gate_view, gate_return, gate_unlock, gate_fail) are already wired via the gaGate() helper; they no-op until you add an analytics tag.
No build step. Either:
# just open the file
open index.html
# …or serve it (nicer; root-absolute /assets paths resolve correctly)
python3 -m http.server 8000
# then visit http://localhost:8000Serving it (rather than file://) is recommended because the pages use root-absolute paths like /assets/favicon.svg.
It's static files; host them anywhere: GitHub Pages, Cloudflare Pages, Netlify, Vercel, an S3 bucket, your own server. No configuration required. Point the host at the folder and you're live.
- Skip links, visible focus rings,
aria-currenton the active nav item, andprefers-reduced-motionsupport are all built in; keep them. - Mobile: the CSS avoids
background-attachment:fixedand disablesbackdrop-filterblur under 900px on purpose; both can freeze scrolling on some Android devices. Useoverflow-x:clip(nothidden) onhtml/bodyfor the same reason.
- Built by Benjamin Mullins. If this template saved you some time, a thank-you or a link back means a lot (but is never required).
- Placeholder images in
assets/aww/are AI-generated cute animals, included under the MIT license with everything else. Swap them for your own work before you ship; they're scaffolding, not a portfolio. - Fonts: Archivo + Inter via Google Fonts (Open Font License).
MIT. Use it, fork it, sell what you build with it. Credit is appreciated but never required.
Made with care (and a thank-you to Benjamin Mullins). Built with Portfolio Starter. Now with 100% more em-dashes.







