How I designed this portfolio
The references, the rules, the small details, and every library this site runs on.
I rebuilt this site from an empty folder. Here's how I designed it, what I borrowed, and every library it runs on.
Where it started
I had benji.org open the whole time. One narrow column, short first-person lines, no hero, no buzzwords. It reads like a person talking.
For the components I wanted small things that make you stay a second longer: a heatmap that feels native, link previews on hover, a little life in the footer.
The content came from my old portfolio. Same facts, fewer words.
The rules I set first
Before any component, I wrote down a few rules and kept to them.
- One column, about 580px wide, on every page.
- Hierarchy comes from colour and weight, not from big type.
- Every colour and every duration is a token. No one-off values.
- Every animation has a reduced-motion fallback.
- Nothing scrolls sideways, on any screen.
Most design decisions after that were easy. I just checked them against the list.
Type
Body text is 14px with a line height of 1.7 and slightly tight tracking (-0.01em).
On Apple devices the site uses San Francisco, the system font, so it feels like apple.com. Everywhere else it falls back to Inter. Dates and code use the system monospace. A serif italic (Newsreader) shows up only for post summaries.
Titles aren't bigger. They're medium weight. That one choice keeps the page calm.
Colour
Light mode is near-white with soft grey text for anything secondary. Dark mode is near-black, not pure black. The GitHub greens come in both versions.
There's almost no accent colour. Blue appears in two places: the verified tick and the highlighter behind "Best Paper Award". Because it's rare, it gets noticed.
Motion
I used transitions.dev as a reference for timing. Its snippets gave me consistent durations and easing, so the modal, the menus and the page slide all feel like one system.
- Sections fade up in a short stagger when a page opens.
- Pages slide in a few pixels with a soft blur.
- The ⌘K menu scales in and closes faster than it opens.
- Numbers pop in digit by digit.
- The "Best Paper Award" highlighter draws in from left to right.
The GK logo loops like a GIF: the G spins once, then the K's arm and leg spring out and back. The browser tab icon plays the same loop. Browsers won't animate an SVG favicon, so the site redraws it on a canvas and swaps the tab icon frame by frame. The tab title types itself in too.
The small details
These took the most time and they're my favourite part.
- Inline logos. Every company or profile in the about text has its logo in front of it. Hover one and you get a preview card: a banner for projects, an avatar for people.
- A bunny in the footer. It's made of text characters. It sleeps with little z's at night in Pondicherry and blinks during the day.
- Sound. Opening ⌘K, switching the theme, reacting to a post and copying a link make a soft sound. There's a toggle in the footer if you'd rather not.
- A paper plane in the blog sidebar that flies to the section you're reading.
- Emoji reactions on every post, with counts.
Mobile
I tested every page from 280px folding phones up to 1440px desktops.
- On phones the nav collapses into a menu button and a search icon.
- The blog sidebar becomes a floating pill at the bottom that shows where you are and opens the full list.
- Dates move under titles, so long titles get the full width.
- Every tap target is at least 32px.
- Hover previews switch off on touch screens, where you can't hover anyway.
The stack
Here's everything the site runs on, grouped by job.
Framework
next(16), App Router with Cache Components, so most pages are prebuilt.react(19).typescript.
Styling
tailwindcss(v4), with design tokens as CSS variables.tw-animate-cssfor a few utility animations.next-themesfor light, dark and system themes.next/fontto load Inter and Newsreader.
Content
@next/mdx,@mdx-js/loaderand@mdx-js/react, so each post is an MDX file.rehype-slugandgithub-sluggerto give headings stable IDs for the sidebar.
Components
shadcnto set up the project and pull in components.- rare-ui's
rail-toc(the paper-plane sidebar) andemoji-reaction(post reactions), installed through shadcn. - A blog grid with category filters, adapted to fit this column.
cmdkfor the ⌘K command menu.
Motion and sound
motion(Framer Motion) for the reactions, the sidebar and the blog grid.- transitions.dev for CSS transitions and timing tokens.
cuelumefor the interface sounds. They're generated in the browser, so there are no audio files.
Icons and logos
lucide-reactfor interface icons.simple-iconsfor the tech stack logos and the IEEE wordmark.react-apple-emojisfor the reaction emoji.- Hand-drawn SVGs for the GK monogram, the footer bunny and the single-colour company marks.
Data
@upstash/redisfor reaction counts, page views and contact messages.- Umami for cookieless analytics and the per-page view counts.
- The public GitHub contributions API for the heatmap.
- The contact form can also email messages through Resend, if a key is set.
What I'd tell you
Pick one reference and study why it works. Mine was a site with great writing, and I added the details I'd always wanted.
Write your rules down before you design. They save you from a hundred small arguments with yourself.
And spend time on the small stuff. Nobody remembers the grid. They remember the bunny.