All posts

How this portfolio is built

ยท by Sk Masum Ali

This is the short tour of how masum.tech is put together, and why. The colophon page covers the fonts, colours and credits. This one is about the engineering.

The stack

It is a Next.js App Router site in TypeScript, styled with Tailwind and colour tokens written in oklch, so the light and dark palettes are tuned by eye instead of by hex. Components come from shadcn/ui on top of Radix, with a few from Magic UI and Aceternity. Headlines use Instrument Serif and everything else uses Geist. The guestbook runs on Supabase, and GitHub and Monkeytype stats come in through TanStack Query.

One clock for all the motion

Scroll animation is GSAP with ScrollTrigger, and smooth scrolling is Lenis. Left alone, each would run its own animation frame loop and the two could drift out of step. So GSAP owns the clock: Lenis is advanced from GSAP's ticker, and every Lenis scroll event updates ScrollTrigger.

const lenis = new Lenis({ lerp: 0.1, smoothWheel: true });
lenis.on("scroll", ScrollTrigger.update);

const tick = (time: number) => lenis.raf(time * 1000);
gsap.ticker.add(tick);
gsap.ticker.lagSmoothing(0);

Panels that scroll on their own, like the command palette, are marked with data-lenis-prevent so Lenis leaves them alone and the page underneath doesn't move. Everything that animates is wrapped in a matchMedia check, so people who ask for reduced motion get the content without the movement.

A theme switch that reveals

The light and dark toggle uses the View Transitions API. The browser snapshots the old page, applies the new theme underneath, and a circular clip-path grows out of the button to uncover it. Browsers without the API, and anyone who prefers reduced motion, simply get an instant switch.

One source of truth

Projects, hackathons, open-source contributions, education and links each live in one data file. The home page, the project pages, the recruiter summary, the sitemap, the structured data for search engines and the llms.txt files all read from those same files. Change a fact once and every page agrees. The numbers on the recruiter page, such as how many contributions were merged, are counted from the data, not typed in.

A share image that is allowed to be boring

The link preview is a plain JPEG. A generated PNG came out at roughly 480 KB, and WhatsApp skips large previews, so the nice image never showed up. A static JPEG is small enough everywhere, and it never needs to be regenerated.

Keeping the server work small

Only the guestbook needs a session, so the Supabase proxy runs on that one route and nowhere else. The rest of the site is static.

Command menu

Jump to a page, open a profile, or run an action