2026Next.js 16React 19TypeScriptTailwind CSSFramer Motionnext-themespnpm
What I Built
- Next.js 16 App Router with generateStaticParams pre-rendering all project and lab detail pages at build time — zero server-side runtime required in production
- Custom locale context exposing a t() getter typed against a single translations.ts constant — no external i18n library, locale persisted to localStorage with no flash on load
- Framer Motion entrance animations defined as reusable variant objects in lib/motion.ts; stagger delays composed per-section without repeating animation configuration
- Dark / light mode with next-themes: system preference honoured on first render, user override stored in localStorage, colour tokens applied via CSS custom properties
- Infinite marquee built with CSS @keyframes translateX(-50%) on a doubled item list; animation-play-state: paused on hover, animation disabled entirely under prefers-reduced-motion
- Responsive layout with Tailwind CSS and centralised design tokens; no custom CSS outside of the marquee keyframes and a handful of global resets
How It Works
- 1The root layout wraps the tree in ThemeProvider and LocaleProvider; locale is resolved from localStorage on the client, falling back to the browser language.
- 2Project and lab detail pages are statically generated from typed TypeScript data modules — adding a new project is a single data entry with no routing changes needed.
- 3Framer motion variants in lib/motion.ts are spread onto motion.div elements; each section adds a stagger delay offset to create a sequenced reveal.
- 4The marquee duplicates the items array before rendering so the CSS loop appears seamless regardless of how many cards are in the list.
- 5Translations are plain objects keyed by locale; the t() function does a single property lookup and falls back to an empty string for missing keys.