A config-driven developer portfolio. Edit one file, get your own site — no component code required to personalize it.
Most portfolio templates make you hunt through five files to change your
name. This one doesn't — everything content-wise lives in
src/config/site.js: your name, bio, projects,
skills, links, and even the accent color. Swap that file, keep the
components untouched.
No image assets are required to get a working site — projects without an
image field render a generated gradient block with their initials instead,
and the avatar works the same way. See "Images" below for the full fallback
chain, including automatic GitHub repo preview cards.
Also included: a light/dark theme toggle (persists across visits, respects
system preference on first load), glassmorphism on the nav bar and expanded
project panels, an animated liquid-blob background derived from your accent
color, and scroll-reveal animations on every section — all of it respects
prefers-reduced-motion.
npm install
npm run devThen open src/config/site.js and replace the example content with yours.
npm run buildDeploys as a static site — Vercel, Netlify, GitHub Pages, Cloudflare Pages, all work with zero extra config.
Everything in src/config/site.js:
export const site = {
name: "Your Name",
role: "Your Title",
tagline: "One line about what you do.",
bio: "A few sentences for the About section.",
accentColor: "79 70 229", // "R G B", no commas
email: "you@example.com",
links: [{ label: "GitHub", url: "https://github.com/you" }],
skills: ["TypeScript", "React", "..."],
projects: [
{
title: "Project Name",
role: "Your role on it",
year: "2025",
description: "What it does and what you built.",
tags: ["Node.js", "PostgreSQL"],
url: "https://...",
image: "", // local path or full URL, see "Images" below
repo: "", // or "yourname/project-repo" for an automatic GitHub preview
},
],
};Change accentColor — one value, reskins the whole site (buttons, hover
states, the availability dot, project placeholder gradients). It's an RGB
triplet so it can plug into Tailwind's alpha-value opacity syntax.
accentColor: "79 70 229", // indigo (default)
accentColor: "16 130 92", // green
accentColor: "190 60 40", // rust
accentColor: "190 24 93", // pinkThe liquid background blobs, avatar/project placeholder gradients, and hover states all derive from this one value — nothing else to touch.
Set avatar in the config to an image path or URL. Leave it null for a
generated initials avatar (same gradient logic as the project placeholders,
so it always matches your accent color).
Each project's picture comes from a three-tier fallback, first match wins.
Set image/repo to "" (the default) to skip a tier — no need to comment
anything out:
image— a local path (/assets/projects/name.png) or a full image URL (https://...), both work exactly the same way.repo— a GitHub repo in"owner/name"form. Pulls that repo's social-preview card straight from GitHub (opengraph.githubassets.com) with no image file, API key, or auth needed. If the repo doesn't exist or the image fails to load, it falls through to tier 3 automatically.- Both empty — a generated gradient block using the project title's
initials, tinted from your
accentColor.
Avatars follow the same idea minus the repo tier: set avatar to a local
path, a full URL, or leave it "" for a generated initials avatar. See
public/assets/README.md for folder
conventions and recommended image sizes.
The toggle in the nav switches data-theme on <html> between light and
dark, persisted to localStorage under the key portfolio-theme. A small
inline script in index.html sets it before first paint so there's no
flash of the wrong theme on load. To change the dark-mode palette, edit the
[data-theme="dark"] block in src/index.css.
The "numbered index" project list (src/components/ProjectRow.jsx) is the
signature layout choice — click a row to expand it. If you'd rather have a
card grid, that's the one component to rewrite; everything else reads from
the same site.projects array regardless of how you render it.
"Get in touch" (nav) and "Send a message" (footer) both open a glass modal
(src/components/ContactDialog.jsx) instead of jumping straight to your
email client. Submitting it opens a mailto: link pre-filled with the
visitor's name, email, and message — there's no backend collecting anything.
If you'd rather capture messages without relying on the visitor's email
client being configured, swap the handleSubmit body in ContactDialog.jsx
for a fetch POST to a form service — Formspree or
EmailJS both work with a single call and have
free tiers.
To add the dialog trigger anywhere else, wrap any label in it:
<ContactDialog className="your-classes-here">Say hello</ContactDialog>public/
assets/
avatar/ your profile photo goes here
projects/ your project screenshots go here
src/
config/
site.js ← edit this
components/
Nav.jsx
Hero.jsx
ProjectsIndex.jsx renders the list
ProjectRow.jsx single expandable row
ProjectPlaceholder.jsx image → GitHub repo preview → gradient fallback chain
Avatar.jsx generated fallback avatar (or your image)
ThemeToggle.jsx light/dark switch
ContactDialog.jsx glass contact modal (mailto-based)
LiquidBackground.jsx animated blob background
Reveal.jsx scroll-reveal wrapper
About.jsx
Footer.jsx
hooks/
useTheme.js theme state + persistence
utils/
placeholder.js shared initials/hue helpers
App.jsx applies accentColor as a CSS variable on load
Vite + React + Tailwind. No backend, no database, no build step beyond
vite build.
MIT — use it, fork it, ship it as your own. Credit is appreciated but not required.