Skip to content

Repository files navigation

Proba

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:4322

⚠️ Before this goes live

Keep 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.

Where things are

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.

Placeholder media

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.

Motion

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.

Waitlist

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.

Verified

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 noModule and 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: 0 in normal or reduced-motion rendering.
  • Waitlist verified end to end: accept, duplicate, invalid, honeypot, rate limit.

Still to do

  • 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.

Releases

Packages

Contributors

Languages