A production-ready personal website built on the TanStack ecosystem: TanStack Start (SSR + server functions), TanStack Router (file-based routing, search params), TanStack Query, and TanStack Store — with UnoCSS (wind4 preset, a drop-in Tailwind v4 replacement), shadcn/ui, Motion, and a lazy-loaded React Three Fiber 3D scene on the home page.
| Area | Highlights |
|---|---|
| Home | Full-viewport hero with lazy 3D workspace scene (R3F), animated gradient blobs, marquee, stats counters, mouse parallax |
| Sections | About, interactive skills (tabs + orbit constellation + tooltips), experience timeline (scroll-grown line, accordions), featured projects, contact CTA |
| Projects | /projects — search-param driven category pills + debounced search, responsive grid, empty state |
| Blog | /blog + /blog/:slug — MDX via content-collections, Shiki code blocks, KaTeX math, Mermaid diagrams, TOC, share buttons, related posts, giscus comments |
| Resume | Downloadable PDF (/resume.pdf) via header/hero/terminal links |
| Contact | Home section — interactive card: copy-email chip with clipboard feedback, availability badge, and a magnetic mailto CTA |
| Extras | ⌘K command palette, light/dark/system theme, smooth scrolling (Lenis), scroll progress, custom 404 + error pages, RSS, sitemap, robots.txt, JSON-LD |
compare.md), shadcn/ui, CSS-variable design tokensNote on Radix imports: UI primitives are imported exclusively from the
radix-uimonolith (e.g.import { Slot } from 'radix-ui'), not from individual@radix-ui/react-*packages — those are transitive deps and not listed inpackage.json. Keep it that way when adding components.
pnpm install
pnpm dev # http://localhost:3000
Required first run (content generation):
pnpm content:build
Other scripts:
| Script | Purpose |
|---|---|
pnpm dev |
Start the Vite dev server |
pnpm build |
Production build (.output/) |
pnpm start |
Run the production server |
pnpm typecheck |
tsc --noEmit |
pnpm content:build |
Regenerate content-collections output |
pnpm test:snapshots |
Build + compare visual snapshots against the Tailwind v4 baseline goldens |
pnpm test:snapshots:baseline |
Regenerate baseline goldens from the Tailwind v4 build (HEAD) |
A Playwright suite guards against visual regressions by comparing the rendered site, pixel-for-pixel, against goldens captured from the previous Tailwind CSS v4 build. This is how the UnoCSS migration was verified as visually 1:1 (see compare.md for the full audit).
pnpm test:snapshots # build + run the suite (18 tests)
snapshots job) uploads a Playwright report with the expected/actual/diff images.Goldens live in tests/e2e/__screenshots__/visual.spec.ts/ and were originally captured from the Tailwind v4 baseline (8b4b624) via scripts/snapshot-baseline.mjs HEAD. They were since regenerated from the UnoCSS build after the WebP image conversion (see below); tests/snapshots/baseline.json records the current provenance (UnoCSS build + original Tailwind commit). Regenerate them only when the baseline intentionally changes (e.g. a deliberate redesign):
pnpm test:snapshots:baseline # builds HEAD in a temp worktree, serves it on :3199, captures goldens
pnpm exec playwright test --update-snapshots=all # or: regenerate in place against the current build
Note:
--update-snapshots(default mode) only rewrites missing or failing snapshots — use--update-snapshots=allto bake in an intentional visual change (e.g. the image-format conversion), since the old goldens may still pass within the pixel threshold.
The app needs no environment variables — all content, fonts, and imagery are build-time static data.
content/blog/my-post.mdx---
title: "My Post Title"
date: "2026-08-01"
description: "One-liner used in cards, meta, and RSS."
tags: ["react", "tanstack"]
category: "engineering"
featured: false
---
$$...$$), Mermaid diagrams (```mermaid), standard markdown, custom Mermaid component.pnpm content:build — the post appears automatically at /blog/my-post. No routing or registration changes.All site content is data-driven in src/data/:
| File | Controls |
|---|---|
site.ts |
Name, role, email, URL, socials, SEO defaults |
nav.ts |
Header/footer navigation |
profile.ts |
Hero bio, highlights, interests, philosophy |
skills.ts |
Skill categories, levels, years, related projects |
experience.ts |
Work history + achievements + technologies |
projects.ts |
Portfolio projects (categories, tech, links) |
testimonials.ts |
Quotes |
Edit a file, save, and the UI updates — no component changes required.
pnpm build, output .output/public.content/blog/ MDX posts (one file = one post)
content-collections.config.ts
public/ robots.txt, og.png, projects/*.webp, blog/**/*.webp (all imagery is WebP — converted from PNG/JPG, see `compare.md`)
scripts/ content build, smoke test, snapshot-baseline helpers
tests/e2e/ Playwright visual snapshot suite + Tailwind v4 goldens
src/
components/
animations/ Reveal, MagneticButton, TiltCard, Counter, TextReveal
blog/ MDX renderer, code blocks, TOC, share, giscus…
home/ Hero, sections, marquee
layout/ Header, footer, command palette, theme, lenis…
projects/ Project card + filters
three/ Lazy 3D scene
ui/ Primitives: button, badge, tabs, dialog, sheet, dropdown-menu,
command, tooltip, accordion, input, skeleton, toast, toaster,
brand-icons, responsive-image
data/ All editable site content
lib/ cn util, constants, seo helpers
routes/ File-based routes (incl. sitemap.xml, rss.xml)
server/ Server functions (blog)
stores/ Theme + command palette stores
styles/globals.css Design tokens, prose, print styles
types/ Shared TypeScript types
Before going public, consider:
src/lib/constants.ts (GISCUS_*) so giscus threads work.public/resume.pdf (or point siteConfig.resumePdfUrl at a hosted PDF).public/og.png (currently a generated placeholder) with a designed 1200×630 asset.prefers-reduced-motion disables the 3D scene, Lenis smooth-scroll, heavy reveals, marquee animation, and giscus.localStorage.Private — all rights reserved.