A minimal, single-page personal portfolio built with React + Vite. Light/dark themes, no backend, no external services — deploys to Vercel with zero configuration.
Sections: Hero · About · Experience · Projects · Notes · Contact.
- React 19 + Vite (no router — single page with smooth anchor scrolling)
- Plain, modular CSS with custom properties for theming (light/dark)
- Content lives in plain data files under
src/data/— no CMS - Contact is a
mailto:link — no API keys, nothing to run server-side
Requires Node.js 20+.
npm install
npm run dev # http://localhost:3000Other scripts:
npm run build # production build → dist/
npm run preview # serve the built dist/ locally
npm run lint # ESLint (react, react-hooks) — keep it at zeroCI runs lint + build on every push and pull request
(.github/workflows/ci.yml).
- Push this repo to GitHub.
- In Vercel, Add New → Project and import the repo.
- Vercel auto-detects the Vite preset (build
vite build, outputdist/). Just click Deploy — no environment variables, novercel.jsonneeded.
Everything you need to edit is content + a few tokens:
| What | Where |
|---|---|
| Name, headline, intro, initials (hole cards), clock city | src/data/heroData.js |
| Bio paragraphs | src/data/aboutData.js |
Skills — the GTO range chart (raise = expert, call = proficient) |
src/data/skills.js |
| Session stats (the big figures under About) | src/data/statsData.js |
| Work history — the dial / order book | src/data/experiences.js |
| Projects — the hand of cards | src/data/projects.js |
| Field notes | src/data/notesData.js |
| Contact email, links, location, résumé URL (⌘K command) | src/data/contactData.js |
| Ticker tape items | src/data/tickerData.js |
| Footer sign-off | src/data/footerData.js |
| Nav labels / logo (drives navbar, footer, and ⌘K palette) | src/data/navbarData.js |
| Colors (palette) | src/styles/base/variables.css — edit --accent-color, --gold, backgrounds, and text tokens for light + dark |
| Favicon / touch icon | public/favicon.svg, public/apple-touch-icon.png |
| Page title / social preview | index.html (<title>, og:*), public/manifest.json |
| Share card (link previews) | public/og-image.png — once your domain is live, change og:image in index.html to the absolute URL (https://yourdomain.com/og-image.png) so every scraper resolves it |
The theme also reads two hardcoded browser-chrome colors in
src/contexts/ThemeContext.jsx (THEME_COLOR_LIGHT /
THEME_COLOR_DARK) — update them if you change the background colors.
PWA icons are served from the single SVG favicon. If you want raster PNG icons (
192×192,512×512) for installability, export them fromfavicon.svgintopublic/and add them back tomanifest.json.
MIT.