Marketing site for Proba — a protein shake with real boba pearls.
The layout is a deliberate, component-for-component clone of heydally.com: same sections in the same order, same motion behaviour, same spacing rhythm. What is Proba's own is the palette, the display face, and the copy.
npm install
npm run dev # http://localhost:4322Keep on Truckin — the display face on every headline and the wordmark — is
licensed for personal use only. Read LICENSE-FONTS.md.
Either buy the commercial licence or swap the face; the swap is one line in
src/app/layout.tsx.
src/app/globals.css design tokens, type scale, buttons, marquee
src/app/layout.tsx fonts (⚠️ licence), smooth scroll, motion provider
src/app/page.tsx composes the twelve sections in Dally's order
src/app/api/waitlist/ the one real form on the site
src/components/chrome/ announcement bar, header, footer, stockist modal
src/components/sections/ the eight homepage sections
src/components/ui/ MediaSlot, FlavorCard, TagPoem, BadgeRow, Stars
src/components/motion/ Lenis, GSAP reveal hook, Three.js pearl field
src/data/ all copy and content, typed
All copy lives in src/data/copy.ts and all SKUs in src/data/flavors.ts.
The lineup is three flavours, running light to dark. Each carries an invented name plus the literal flavour underneath it:
| Name | What's in the bottle |
|---|---|
| Tiger Stripe Tango | Tiger milk tea |
| Brown Sugar Blush | Brown sugar |
| Molasses Mirage | Black brown sugar |
Nothing is hardcoded in a component — editing the site's words never means touching JSX.
Proba has no photography yet. Every image and video position renders through
src/components/ui/MediaSlot.tsx as a correctly-proportioned plate with a
visible label ([hero loop 16:9]). Dropping in real assets means changing that
one component, not hunting through twelve sections.
The one exception is the closing section, where Dally autoplays a product video.
That slot is a live WebGL field of boba pearls drifting in taro milk
(src/components/motion/PearlField.tsx) rather than a placeholder.
One library per job — none of them decorative:
| Library | Owns |
|---|---|
| Lenis | global smooth scroll (dynamically imported after paint) |
| GSAP + ScrollTrigger | comparison plates rising, benefit-row stagger |
| anime.js v4 | the mission poem's pill stagger |
| Framer Motion | header disclosure, modal, hero crossfade, card hover |
| Three.js | the closing pearl field |
| (none) | logo marquee — pure CSS, as Dally does it |
Only Framer's core is in the initial payload; its feature bundle, GSAP, anime.js
and Three.js all load on demand. Everything honours prefers-reduced-motion.
POST /api/waitlist validates with Zod, rate-limits by IP (6/hour), carries a
honeypot, and appends to data/waitlist.json (gitignored). No third-party
keys are required to run the site. When a real provider is wired up, the only
file that changes is src/lib/waitlist.ts; .env.example already names the
variables.
There is no cart and no checkout — every "buy" affordance is visual, matching the pre-launch scope.
Production build, measured on the built output rather than estimated:
- Initial JS 159 kb gz for modern browsers (Next's 39 kb legacy polyfill
chunk is
noModuleand never fetched). React 19 + Next 16 account for ~118 kb of that floor. - CSS 7 kb gz.
- No horizontal overflow at 375 / 768 / 1440.
- Nothing left stuck at
opacity: 0in normal or reduced-motion rendering. - Waitlist verified end to end: accept, duplicate, invalid, honeypot, rate limit.
- Real photography and video for every
MediaSlot. - Resolve the font licence.
- Subpages: shop, product detail, about, FAQ, store locator.
- Swap the waitlist file store for a real provider.