Editorial, motion-led personal site for a Full Stack Engineer & AI Product Builder.
A single-page portfolio composed in app/page.tsx as a vertical stack of editorial sections — hero, projects, experience, journal, contact — with carefully tuned motion (Lenis smooth scroll + GSAP ScrollTrigger) and live status signals (Spotify "now playing", latest tweet, latest commit) in the navbar.
It's intentionally one page, intentionally static, and intentionally distinctive.
- Editorial typography — Cormorant Garamond display + Inter body, CSS-first Tailwind v4 theme tokens in
app/globals.css. - WebGL hero — pointer-driven fluid simulation (Navier-Stokes style) reveals a photo through dye splats. See
src/components/FluidPhotoReveal.tsx. - Noise-lines backdrop — fixed-layer simplex-noise GLSL field rendered once in the root layout.
- Live status pills — Spotify (polls every 60s), X (3h cache), GitHub (5m cache). All server-cached via Next route handlers; tokens never touch the client. See Live status integrations.
- Custom cursor — JS-driven, replaces the native cursor on fine-pointer devices; gracefully falls back on touch.
- shadcn/ui + Radix — tooltips and any future primitives are headless + themed inline.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router, React Compiler enabled) |
| UI | React 19 + TypeScript (strict) |
| Styling | Tailwind CSS v4 (CSS-first config in globals.css, no tailwind.config.*) |
| Animation | GSAP + ScrollTrigger, Lenis smooth scroll |
| Primitives | shadcn/ui on Radix |
| Validation | Zod for API response schemas |
| Fonts | next/font — Cormorant Garamond (display), Inter (body), Geist (sans) |
| Package manager | pnpm |
# 1. clone
git clone https://github.com/sasuke007/portfolio.git
cd portfolio
# 2. install
pnpm install
# 3. env — optional for the live-status pills (skip and they'll just not render)
cp .env.example .env.local
# fill in SPOTIFY_*, X_*, GITHUB_* if you want the navbar pills live
# 4. dev server
pnpm dev
# → http://localhost:3000
# 5. production build
pnpm build && pnpm start| Command | Purpose |
|---|---|
pnpm dev |
Next dev server on :3000 |
pnpm build |
Production build |
pnpm start |
Serve the production build |
pnpm doctor |
react-doctor over the project |
pnpm scan |
react-scan against running dev server |
Type checking is wired into pnpm build (tsconfig.json has noEmit: true, strict: true). There is no separate linter / formatter / test runner.
app/
page.tsx ← the entire site, composed as a vertical stack
layout.tsx ← root layout: providers, navbar, noise background
globals.css ← Tailwind v4 @theme tokens, custom utilities
api/
now-playing/ ← Spotify currently-playing route handler (s-maxage=30)
x/latest/ ← X latest tweet (s-maxage=10800)
github/latest/ ← GitHub latest commit (s-maxage=300)
src/
components/ ← Hero, Projects, Journal, Navbar, status pills, etc.
ui/tooltip.tsx ← shadcn-installed Radix tooltip primitive
content.ts ← all copy + structured data (identity, projects, …)
lib/
gsap.ts ← single GSAP namespace; call registerGsap() before plugin use
spotify*.ts ← server-only fetch + Zod schemas
x*.ts ← server-only fetch + Zod schemas
github*.ts ← server-only fetch + Zod schemas
public/
assets/ ← social icon SVGs (Spotify, X, GitHub, LinkedIn, email)
CLAUDE.md ← deeper architecture notes for contributors / AI agents
The navbar's right cluster shows three live signals. The architectural pattern is identical for each: browser → our Next route handler → upstream API. Tokens stay server-side.
┌────────────┐ ┌────────────────────┐ ┌──────────────────┐
│ Browser │───▶│ /api/now-playing │───▶│ Spotify API │
│ (poll) │ │ (edge-cached 30s) │ │ + token refresh │
└────────────┘ └────────────────────┘ └──────────────────┘
| Pill | Route | Cache (s-maxage) |
Poll interval | Auth |
|---|---|---|---|---|
| Spotify | /api/now-playing |
30s | 60s | OAuth refresh-token flow |
| X | /api/x/latest |
3h | 3h + visibility | App-only Bearer token |
| GitHub | /api/github/latest |
5m | 5m | Personal access token |
Each pill renders as a 32px collapsed icon and slides open on hover to reveal the live content. They're all defensive — if upstream errors, the pill simply doesn't render and the navbar collapses gracefully.
See src/lib/spotify.ts, src/lib/x.ts, src/lib/github.ts for the patterns.
All variables are server-only. None are exposed to the browser; the route handlers proxy every upstream call.
# .env.local — never commit this file (it's gitignored)
# Spotify — Now Playing
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=
SPOTIFY_REFRESH_TOKEN=
# X — Latest Tweet
X_BEARER_TOKEN=
X_USER_ID=
X_HANDLE=
# GitHub — Latest Commit
GITHUB_TOKEN=
GITHUB_USERNAME=If any variable is missing, the corresponding pill returns null from its data layer and the navbar gracefully omits it. There's no hard requirement to set any of these to run the site.
For deeper architecture detail — the GSAP / Lenis / ScrollTrigger contract, the content-as-data pattern in src/content.ts, how the dev-only tooling tree-shakes, and the conventions for client vs server components — see CLAUDE.md.
Three rules to keep coordinated when editing:
- GSAP namespace. Always import from
@/lib/gsapand callregisterGsap()before using a plugin. Never callgsap.registerPlugindirectly. - Lenis. Instantiated once in
LenisProvider, which driveslenis.raffromgsap.tickerwithlagSmoothing(0). Do not start another rAF loop or instantiate Lenis elsewhere. - Content layer. Copy and structured data live in
src/content.ts. Edit data there, not in components.
Issues, suggestions, and PRs are welcome — even though this is a personal site. See CONTRIBUTING.md for the contribution flow, and CODE_OF_CONDUCT.md for community norms. Security reports go to SECURITY.md.
If you're forking this as a starting point for your own portfolio: go for it. The MIT license below permits it, no attribution required (though a star is appreciated).
- shadcn/ui — for the Radix-on-top-of-Tailwind pattern that lets headless behavior coexist with bespoke design.
- Lenis — for smooth scrolling that doesn't feel like a hack.
- GSAP — for the animation primitives that make scroll-pinned hero sections feel inevitable.
- The fluid-simulation hero borrows ideas from Pavel Dobryakov's WebGL-Fluid-Simulation, rewritten in TypeScript and adapted to a photo-reveal use case.
MIT © Rohit Pandit