Next.js + React Three Fiber site for SerSan — production-grade AI systems studio.
- Next.js 16 (App Router, Turbopack)
- React 19, TypeScript
- Tailwind CSS v4 (CSS-first config)
- React Three Fiber + drei (hero 3D scene)
- IntersectionObserver-based reveals (CSS keyframes, not Framer Motion)
- Radix primitives for the mobile nav dialog
bun install # or npm install / pnpm install
bun run dev # http://localhost:3000
bun run build # production build
bun run start # serve the production buildNo environment variables required.
src/
app/ Next.js App Router pages + metadata + globals.css
components/
hero.tsx Hero with CSS fade-up reveals
navbar.tsx Fixed nav (Work / Services / Process / Trust / Contact)
footer.tsx Footer (newsletter, social, legal)
scene/
hero-scene.tsx R3F procedural topology (lightweight placeholder)
sections/ Homepage sections (server components)
credibility-strip.tsx
services-section.tsx
fit-section.tsx
process-section.tsx
case-studies-section.tsx
trust-section.tsx
final-cta.tsx
ui/
button.tsx CVA variants (hero, heroOutline, outline, ghost, link...)
reveal.tsx IntersectionObserver fade-up wrapper
section-heading.tsx Eyebrow + title + description
data/ Translations, founder data, etc.
When the 3D artist delivers the Blender-exported GLB:
- Drop the file at
public/models/sersan-topology.glb. - Update
src/components/scene/hero-scene.tsxto load it viauseGLTF("/models/sersan-topology.glb")(currently the scene is procedural). - Tune scale, position, lighting under the
// --- Tunables ---block at the top of the file.
Target sizes: under 5 MB compressed; absolute max 10–15 MB for the first homepage version. Mobile already short-circuits to a static gradient — keep that fallback so phones never pay the GLB cost.
The project builds cleanly with bun run build and has no runtime env vars.
Push to GitHub and connect the repo to Vercel — the default Next.js
configuration works.
- No Framer Motion on the homepage. Framer Motion 12 + Next 16 +
Turbopack + React 19 had a bug where
motion.*elements withinitial/animategot stuck in their initial state. The homepage uses CSS keyframefade-upplus anIntersectionObserverwrapper (components/ui/reveal.tsx) instead. The dependency is still inpackage.jsonand used by other pages. - Brass tokens kept.
--accent-warmis still defined for the existing audit/consulting/case-study pages. The new homepage uses--accent(cyan) as the primary.