Most developer portfolios look the same. Dark background, hero section, a few cards, contact form. You can feel the template underneath. I wanted mine to feel like it was built by someone who actually cares about the craft — so I started from a blank HTML file and didn't reach for a framework.
Why No Framework
The honest reason: a portfolio is a static document. It doesn't have complex state, it doesn't need server components, it doesn't need hot module replacement. What it needs is to load fast, look good, and communicate clearly. A single HTML file, a stylesheet, and a small script file do all of that without a 200MB node_modules folder.
There's also something valuable about doing it the hard way. When you have no abstractions to lean on, you actually learn the platform. After building this, I understand CSS layout, the cascade, and browser rendering in a way I didn't before.
Building a Design System in CSS Variables
Before writing a single component, I defined the entire visual language as CSS custom properties:
:root {
--bg: #07070c;
--surface: #0d0d16;
--gold: #f0c84a;
--text: #f0ece2;
--muted: rgba(240, 236, 226, 0.45);
--border: rgba(240, 236, 226, 0.07);
--font-serif: 'Cormorant', Georgia, serif;
--font-sans: 'Outfit', system-ui, sans-serif;
--font-mono: 'DM Mono', monospace;
}
Everything in the stylesheet references these variables. If I want to change the gold accent, I change one line. The entire site updates. This is the CSS equivalent of a design token system — without needing Figma Tokens or Style Dictionary.
The Star Canvas Background
The animated star field in the background is a plain <canvas> element driven by about 60 lines of vanilla JS. No library. The key is using requestAnimationFrame instead of setInterval — it syncs to the display refresh rate and pauses automatically when the tab is hidden.
function drawStars() {
ctx.clearRect(0, 0, W, H)
stars.forEach(s => {
s.y += s.speed
if (s.y > H) { s.y = 0; s.x = Math.random() * W }
ctx.beginPath()
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2)
ctx.fillStyle = `rgba(240,236,226,${s.opacity})`
ctx.fill()
})
requestAnimationFrame(drawStars)
}
requestAnimationFrame(drawStars)
Project Card Visuals
Each project card has a custom visual in the thumbnail — not a screenshot (screenshots go stale and look inconsistent) but a purpose-built CSS illustration that captures the project's colour palette and vibe. Mapleins gets a red and white Canadian-flavoured card. Solstice Watches gets a gold luxury treatment. stalejs gets a dark terminal-style code block.
This forced me to actually think about each project's identity rather than just dumping a screenshot in. It's more work, but the result is a grid that feels intentional.
Performance
Because there's no framework, there's no JavaScript bundle to ship. The only JS file is a small script.js that handles the cursor, nav scroll state, and star canvas. It's loaded with defer so it never blocks rendering.
Fonts are the only external dependency. I use the Google Fonts CSS API which serves a single preconnect-optimised CSS file. Combined with font-display: swap, text renders immediately in the fallback font and swaps in when the web fonts arrive — no layout shift.
Deployment
The entire site is deployed on Vercel from a GitHub repo. Push to main, it's live in 30 seconds. No build step. No CI configuration. Because it's static files, the deployment is just a file copy to a CDN — it can't fail.
I own the domain rohankakkar.com and pointed it to Vercel's nameservers. SSL is automatic. The whole infrastructure setup took about 10 minutes.
What I Learned
- Constraints force creativity. No framework meant I had to really understand what I was building. The code is more intentional as a result.
- CSS is more powerful than people give it credit for. Custom properties, grid, clip-path, and modern selectors cover 95% of what you'd reach for a library for.
- The website is never finished. I've shipped probably 15 iterations. Small improvements compound. The best time to push a tweak is right now.
The site is open source — you can see every line at github.com/kptaan13/Portfolio.