Editorial premium fashion · WhatsApp checkout
RJjstore

Problem
Branded clothing stores sell via Instagram photos, without a storefront that feels like a fashion house and conveys desire and exclusivity.
Solution
Fashion e-commerce with a luxury-studio aesthetic: cinematic hero, horizontal-scroll lookbook, cursor-following dynamic lighting, product quick-view, persistent cart and orders closed straight on WhatsApp, with a switchable Sand and White theme.
How it was built
Next.js 14 (App Router) with zero heavy e-commerce dependencies: no third-party cart, no CMS, no external state manager. Just framer-motion, Lenis (smooth scroll) and next/font (Cormorant Garamond for editorial headings, Inter for the rest). The README documents its own performance budget: First Load JS ≈ 156 kB on the homepage, with every animation resolved in CSS and Framer Motion instead of a canvas or WebGL library. It's the right call for a project where the product is the photography, not a 3D effect.
The cart is a plain Context (CartProvider) holding { id, size, qty }[]. A line's identity key is the product and size pair, not just the product, so the same item in P and G becomes two independent lines. localStorage persistence uses a useRef as a hydration guard: the first useEffect reads storage and populates state, and only a second useEffect, gated on that ref, writes back. Without the guard, the write effect would fire on the very first render with an empty array and wipe the saved cart before it was ever read.
Size selection gates conversion. ProductDetail has an ensureSize() that blocks "Add to bag" and flashes an inline warning if nothing was picked, but the WhatsApp button is never disabled: with no size chosen it just builds the message with "to confirm" instead, so the lead isn't lost to friction. In the full cart, orderMessage() aggregates every line into one formatted text (product, brand, size, subtotal, bold-markdown total) and opens wa.me with encodeURIComponent. The entire conversion flow ends outside the app, with no backend and no payment gateway.
The "luxury editorial" look is built from coordinated motion layers, not a single effects library. LookbookScroll pins the section (position: sticky) and uses Framer Motion's useScroll and useTransform to map scroll progress into three independent transforms: the horizontal rail slides one way, the image inside each card counter-slides another way (scale-125 plus imgX), and the caption moves a third way, producing multilayer parallax with plain CSS transforms. Spotlight (a cursor-following light in soft-light blend mode) is built with useMotionValue and useSpring, and turns itself off via matchMedia('(hover: hover) and (pointer: fine)') and useReducedMotion. It's visual luxury that doesn't penalize mobile or accessibility. The theme toggle between Sand/Espresso and White avoids a wrong-theme flash with a blocking inline script in head that reads localStorage and applies data-theme before React hydrates. And the "neo-skeuomorphic" details (subtle swatch relief, solid-button depth) live isolated in a single token file (lib/theme.ts) so they don't leak into the rest of the UI, which is deliberately flat.
Per-variant cart
Composite id+size key in the Context, persisted to localStorage with an anti-overwrite hydration guard.
Frictionless WhatsApp
Size required for the cart, but the WhatsApp link never blocks: with no selection it falls back to "to confirm".
3-layer lookbook
Sticky scroll with useScroll/useTransform moving rail, image and caption in opposite directions.
No-flash theme
Blocking inline script applies data-theme from localStorage before React hydrates.
More screens
Stack
- Next.js
- TypeScript
- Tailwind
- Framer Motion
- Lenis


