About Skills Work Journey Blog Contact
Dev Tools · 6 min read · December 2025

Building My Portfolio From Scratch — No Frameworks, No Templates

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.
Share

More Writing