# Phase 3 — Full Premium Site Build all sections using Next.js App Router, shadcn/ui, and brand tokens from Phase 1. ## Project Setup ```bash npx create-next-app@latest -site --typescript --tailwind --app --src-dir cd -site npm install gsap @gsap/react framer-motion lenis npx shadcn@latest init -d npx shadcn@latest add button card input textarea badge separator ``` ## Global Layout `src/app/layout.tsx`: ```tsx import { Geist, Geist_Mono } from 'next/font/google' import { GSAPProvider } from '@/components/gsap-provider' import { brand } from '@/lib/brand' import './globals.css' const geist = Geist({ subsets: ['latin'], variable: '--font-geist' }) const geistMono = Geist_Mono({ subsets: ['latin'], variable: '--font-geist-mono' }) export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ) } ``` `src/app/globals.css` — add after Tailwind directives: ```css :root { --color-primary: #18181B; --color-secondary: #3F3F46; --color-accent: #6366F1; --color-bg: #FAFAFA; } body { background-color: var(--color-bg); color: var(--color-secondary); font-family: var(--font-geist), system-ui, sans-serif; } ``` ## Navigation — Floating Glass Pill `src/components/Nav.tsx` — `'use client'`: ```tsx // Floating pill navbar detached from top // Closed: glass pill with logo + links + CTA // Mobile: hamburger morphs to X, full-screen overlay with staggered link reveals // Uses backdrop-blur ONLY because it is position:fixed (sticky element rule) ``` Key CSS: `position: fixed`, `top: 1.5rem`, `left: 50%`, `transform: translateX(-50%)`, `backdrop-blur-xl`, `bg-white/80 dark:bg-black/80`, `rounded-full`, `px-6 py-3` Hamburger morph: two `` bars, on open: - Bar 1: `rotate-45 translate-y-[7px]` - Bar 2: `-rotate-45 -translate-y-[7px]` Transition: `duration-300 ease-[cubic-bezier(0.32,0.72,0,1)]` ## Services Section — 2-Col Zig-Zag Bento `src/components/ServicesSection.tsx`: **Never** use 3 equal columns. Use alternating 2-col layout where content and visual swap sides. ```tsx // Each service = one "row" that alternates layout // Even rows: [content left (60%) | visual right (40%)] // Odd rows: [visual left (40%) | content right (60%)] // On mobile: always stack, content first // scroll reveal with IntersectionObserver (never window.scroll) useEffect(() => { const observer = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('revealed') observer.unobserve(entry.target) } }) }, { threshold: 0.15 } ) document.querySelectorAll('.service-row').forEach((el) => observer.observe(el)) return () => observer.disconnect() }, []) ``` CSS for reveal: ```css .service-row { opacity: 0; transform: translateY(40px); transition: opacity 0.7s cubic-bezier(0.32,0.72,0,1), transform 0.7s cubic-bezier(0.32,0.72,0,1); } .service-row.revealed { opacity: 1; transform: translateY(0); } ``` Stagger via CSS `transition-delay`: `calc(var(--index) * 0.1s)` ## About Section — Editorial Split with Parallax `src/components/AboutSection.tsx`: Layout: massive typography on left, parallax image stack on right. - Left: large serif-style stat numbers, short paragraph about the business - Right: 2-3 overlapping images with slight rotation (`-2deg`, `1deg`, `3deg`) that straighten on hover - On mobile: remove rotations and overlaps, stack vertically Image rotation on mount (GSAP): ```ts gsap.set('.about-img', { rotation: (i) => [-2, 1, 3][i] || 0 }) // On hover: straighten // On leave: restore rotation ``` ## Testimonials Section — Infinite Marquee `src/components/TestimonialsSection.tsx` — `'use client'`: Duplicate the testimonial cards array to create a seamless loop. ```tsx // CSS-only infinite marquee (no JS animation loop — better perf) // Use transform: translateX animation // Pause on hover via animation-play-state: paused ``` CSS: ```css .marquee-track { display: flex; width: max-content; animation: marquee 30s linear infinite; } .marquee-track:hover { animation-play-state: paused; } @keyframes marquee { from { transform: translateX(0) } to { transform: translateX(-50%) } } ``` Testimonial card: white bg, whisper border (`border border-zinc-200/50`), rounded-3xl, `p-8`. Avatar: squircle (not circle) — `rounded-2xl` on `w-10 h-10`. Use organic-looking names and messy numbers (e.g., "47% faster", "+$2.3k/mo"). ## Contact Section — Form with Validation `src/components/ContactSection.tsx` — `'use client'`: Fields: Name, Email, Message (textarea), Submit button. Validation (inline, no `window.alert`): ```tsx const [errors, setErrors] = useState>({}) const validate = () => { const errs: Record = {} if (!name.trim()) errs.name = 'Name is required' if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errs.email = 'Enter a valid email' if (message.trim().length < 20) errs.message = 'Message must be at least 20 characters' return errs } ``` Error display: red text below each input, `text-sm text-red-500`. Success state: replace form with a composed "Thank you" panel (not a toast or alert). Submit: `useTransition` for pending state — button shows "Sending..." during submission. ## Footer Section `src/components/FooterSection.tsx`: Clean, minimal. No "link farm" with 4 columns. Structure: ``` [Logo + tagline] [Nav links: 4 max] [Legal: Privacy · Terms] Copyright © {year} {brand.name} ``` ## Page Assembly `src/app/page.tsx`: ```tsx import Nav from '@/components/Nav' import HeroSection from '@/components/HeroSection' import ServicesSection from '@/components/ServicesSection' import AboutSection from '@/components/AboutSection' import TestimonialsSection from '@/components/TestimonialsSection' import ContactSection from '@/components/ContactSection' import FooterSection from '@/components/FooterSection' export default function Home() { return (
) } ``` ## Component File Rules - Each section = its own file in `src/components/` - Never define components inside other components (`rerender-no-inline-components`) - `MagneticButton` lives in `src/components/ui/magnetic-button.tsx` - GSAP provider lives in `src/components/gsap-provider.tsx` - Brand tokens live in `src/lib/brand.ts` - Heavy libraries (GSAP): dynamically import via `next/dynamic` in page-level components where possible (`bundle-dynamic-imports`) - All interactive components have `'use client'` at top - Server Components are default — only add `'use client'` for interactivity or browser APIs ## Accessibility Checklist - [ ] All images have meaningful `alt` text - [ ] Skip-to-content link in Nav - [ ] Focus rings visible on all interactive elements - [ ] Form fields have associated `