Portfoliopersonal site

Personal site

One résumé, rendered three different ways.

My own site. The same career data — jobs, side projects, writing — is drawn by three separate React components: a dark visual theme, a terminal theme, and a deliberately silly one.

  • Next.js 16 · React 19
  • 3 themes wired live
  • Installs as a PWA
  • One data file
View as

Note: the panel above is a CSS recreation of each theme, built from the colour and font constants in components/themes/. It is not a screenshot. The real thing is at karthikrajan.info.

What it is

A résumé site that renders three ways

A personal site is a solved problem, which is exactly why building one is dull. This one started as an ordinary dark portfolio and turned into an argument with myself about how much of a website is layout and how much is personality. So I built the same site three times.

Every job, project, shipped system and blog post lives in one TypeScript file, lib/portfolio-data.ts. Each theme is a separate component that imports that file and draws it its own way. Switching themes swaps the component, not the content, and the choice is saved and shareable as a URL.

Tour

Three themes, one data file

CSS recreation of the Midnight project grid — palette and type taken from midnight.tsx.

Theme 01

The default one

Dark, violet, framer-motion. Floating orbs behind a dot grid, a gradient surname over a masked portrait, then career, shipped systems, and a grid of side projects that links to each one’s app, landing page and source.

  • Tailwind classes, gradient text, scroll reveals
  • Career timeline, shipped work, blog teasers
  • Project grid pointing at every other repo

CSS recreation of the Terminal theme — colours lifted from terminal.tsx.

Theme 02

The same data, piped

Every section becomes a shell command. Career is cat career.log, side projects are ls -l ~/side-projects/, and each project’s three links become --app, --page and --src flags on the line below it.

  • Inline style objects only, no Tailwind
  • Green prompt, cyan links, grey metadata
  • Images become links, because terminals

CSS recreation of Goblin Mode — sticker borders and palette from goblin.tsx.

Theme 03

The goofy one

Comic Sans, cream paper, 3px ink borders and hard offset shadows. The same career, rewritten as jokes: “turned 1 engineer into 15 like some kind of sourdough starter.” Click the job title and it cycles through four worse ones.

  • Spring-loaded stickers via framer-motion
  • Same résumé data, different captions

CSS recreation of the mobile hero layout.

Sharing

Send someone a look

The theme you are on is a URL. “Share this look” copies karthikrajan.info/?theme=goblin, or opens the native share sheet on phones that have one. Opening that link lands you in that theme, ahead of any saved preference.

  • Choice persists in localStorage
  • Separate hero layout below the large breakpoint

How it works

What happens when you flip the switch

Four steps, in the order a first-time visitor actually meets them.

01

Midnight loads

The dark theme renders first. On mount, a saved preference or a ?theme= parameter takes over — the parameter wins.

02

Pick a look

The “View as” switch in the hero offers Midnight, Terminal and Goblin. Each one is a different component, not a restyled copy.

03

It sticks

The choice is written to localStorage and onto data-theme on the html element, so your next visit opens the same way.

04

Share the look

“Share this look” copies a link with the current theme in the query string, so whoever opens it sees what you saw.

Features

What’s actually in it

Three live themes

Midnight, Terminal and Goblin Mode are wired into the page router. Five more theme files sit in the repo, unrouted.

One data file

Jobs, projects, shipped systems and tech groups all come from lib/portfolio-data.ts. No theme owns its own copy.

A CLI in inline styles

The Terminal theme uses no Tailwind at all — every rule is an inline style object, which keeps its look self-contained.

Shareable theme links

One button copies a ?theme= URL, or opens the native share sheet where the browser supports it.

Motion throughout

framer-motion drives the hero stagger, the scroll reveals, and Goblin Mode’s spring-loaded stickers.

A blog

A /blog route with its own layout and metadata. One post so far: “K1: Logging Out”, on leaving Amazon.

Installs to a phone

A generated web manifest plus a service worker that precaches the shell, so it opens standalone from a home screen.

Résumé download

The PDF lives in public/ and downloads straight from the hero. No form, no email gate.

Built with

The stack, and where the data lives

  • Next.js 16
  • React 19
  • TypeScript
  • Tailwind CSS v4
  • framer-motion
  • lucide-react
  • Vercel

Deployed on Vercel at karthikrajan.info. There is no database, no API route and no sign-in — every word on the site is a string in the repo. The only state stored anywhere is your theme choice, in your own browser.

FAQ

Reasonable questions

Can I use it as a template?

Not really. There is no config file and no CMS — my name, jobs and projects are hard-coded in lib/portfolio-data.ts. Fork it and the first thing you do is rewrite that file.

Does it track me?

No analytics or tag scripts ship with it. The dependency list is Next, React, framer-motion and lucide-react. The one thing stored is your theme choice, in your browser.

Why are there theme files that never load?

Aurora, Neon, Brutal and two Claude-CLI themes were built and then left out of the router. They are still in components/themes/; the switch only exposes three.

Does it work on a phone?

Yes. The hero has its own mobile layout, and the manifest and service worker let it install to a home screen and open standalone.