Skip to content

Instantly share code, notes, and snippets.

@StarKnightt
Last active July 12, 2026 13:26
Show Gist options
  • Select an option

  • Save StarKnightt/215b05092c898ead0530a61c2b685e62 to your computer and use it in GitHub Desktop.

Select an option

Save StarKnightt/215b05092c898ead0530a61c2b685e62 to your computer and use it in GitHub Desktop.
Prompt I used to build a 3D soda landing page with Grok 4.5 in one shot
# Recreate this interactive 3D soda landing page in Next.js + React + TypeScript + Tailwind
# Design and 3D assets from getlayers.ai (Soda template). This prompt recreates the design using Grok 4.5.
You are an expert creative front-end developer. Build a **Next.js (App Router) project** with React, TypeScript, and Tailwind CSS that reproduces the design below exactly — same layout, visuals, motion, and interaction.
Use `pnpm` as package manager. Use GSAP for animations and Google's `@google/model-viewer` for 3D models. All 3D assets are hosted on a CDN (URLs below). The page should be a single full-viewport (no-scroll) hero landing page.
## Project Setup
```bash
pnpm create next-app@latest soda-landing --typescript --tailwind --app --eslint
cd soda-landing
pnpm add gsap
pnpm add @google/model-viewer
```
## What it is
A full-viewport (no-scroll) hero landing page for a fictional "Diet Soda" beverage. A dark radial gradient background (teal for the default "Classic" flavor, blue for the "Zero Lime" flavor) fills the screen. A large 3D soda can floats in the center, rendered with `<model-viewer>`, and **tilts toward the cursor in real time**. Around it, dozens of 3D cherry/leaf models float and drift with parallax, and are **repelled by the pointer** like a force field. Translucent PNG bubbles rise endlessly from the bottom. A glassmorphism header sits on top; a left column has a giant cursive headline + CTA + award badge; a right column has a two-card flavor carousel and a second headline.
Clicking a flavor card triggers a choreographed transition: the background color morphs, the can spins 720° with a motion blur and swaps its base-color texture at the peak, and all the berry models implode toward center, swap their model (cherry ↔ blueberry), then explode back out to new random positions.
## Fonts
Add to `layout.tsx` via `next/font/google`:
- **Body**: `Inter` (weights 400, 500)
- **Headings**: `Galada` (cursive, weight 400)
- **Nav**: `Manrope` (weights 400, 700)
- **Logo/Headings alt**: `Outfit` (weights 400, 700, 900)
## Color Palette
- Near-black: `#0a0a0a`
- Classic teal: inner `#0b8a78`, mid `#044e3b`, outer `#011411`
- Blue theme: inner `#0b4f8a`, mid `#04294e`, outer `#010c14`
- Pink accent: `#fbcfe8`
- Accent text on pink: `#011d17`
- Glass bg: `rgba(255, 255, 255, 0.05)`
- Glass border: `rgba(255, 255, 255, 0.1)`
- Muted text: `rgba(255, 255, 255, 0.7)`
## Component Structure
```
app/
layout.tsx — fonts, metadata, global styles
page.tsx — main page component
globals.css — custom CSS (gradients, animations, model-viewer styles)
components/
Header.tsx — glassmorphism nav bar
HeroLeft.tsx — "Pure Zero" title, description, CTA, award badge
HeroRight.tsx — flavor carousel cards, "Refreshingly Clean" title
ProductViewer.tsx — center 3D can with model-viewer
FloatingBerries.tsx — foreground + background berry model-viewers
FloatingLeaves.tsx — background leaf model-viewers
BubblesContainer.tsx— rising bubble animation
FlavorCard.tsx — individual flavor card
```
## Page Layout
The page does NOT scroll. `overflow: hidden; height: 100vh;` on body.
The body background is a radial gradient that transitions between teal (Classic) and blue (Zero Lime) themes using GSAP CSS variable animation.
The hero is a flex row spanning the viewport. Inside it (in DOM order): floating leaves (far background), left column, background berries, center product (3D can), foreground berries, right column.
## Key Interactions
### 1. Mouse cursor tilt on 3D can
Per animation frame, smoothly interpolate mouse position and update `model-viewer` camera orbit:
```
cameraOrbit = `${(currentMouse.x * 40) + switchSpin}deg ${90 + (currentMouse.y * 20)}deg 380%`
```
Mouse smoothing lerp factor: `0.05`
### 2. Parallax on floating elements
- Foreground berries: `translate(mouse.x * 60px, mouse.y * 60px)`
- Background berries: `translate(mouse.x * -30px, mouse.y * -30px)`
- Leaves: `translate(mouse.x * -15px, mouse.y * -15px)`
### 3. Berry pointer repulsion
Radius: `400px`, strength: `-80`, lerp: `0.1`, spin: `angle += 0.2 * speedMult`, `speedMult = 1 + force * 5`. Berry float amplitude: `15px` Y / `6deg`. Per-index durations: `[5, 7, 6, 8, 5.5, 6.5, 9, 11, 10]`.
### 4. Flavor switch choreography
On card click:
1. **Background morph**: GSAP animates CSS variables `--bg-inner`, `--bg-mid`, `--bg-outer` over `1.5s ease power2.inOut`
2. **Can spin**: Phase 1 → `360deg`, `blur 15px`, `0.6s`, `power2.in`. At peak, swap texture. Phase 2 → `720deg`, `blur 0`, `1.5s`, `back.out(0.7)`
3. **Berry implode/explode**: Implode to center `0.5s power2.in`, `scale 0.1 opacity 0`. Hold `0.3s`. Swap `.src` (cherry ↔ blueberry). Explode to random `±100px`, `0.9s back.out(1.5)`
### 5. Rising bubbles
Spawn bubble `<img>` every `400ms`. Size: `10-30px`, opacity: `0.2-0.6`, rise duration: `4-10s`, drift `+30px` X, `360deg` rotation over `-110vh`.
### 6. Floating leaves
Amplitude: `20px` Y, `15px` X, `15deg` rotation. Durations: `10 + i*2` seconds.
## model-viewer Setup
Since `@google/model-viewer` is a web component, in Next.js you need to handle it as a client component. Create a wrapper:
```tsx
'use client'
import '@google/model-viewer'
// Declare the custom element for TypeScript
declare global {
namespace JSX {
interface IntrinsicElements {
'model-viewer': any
}
}
}
```
Center can settings:
- `camera-orbit="0deg 90deg 380%"`
- `field-of-view="30deg"`
- `exposure="1.5"`
- `environment-image="neutral"`
- `camera-controls disable-zoom shadow-intensity="0"`
- CSS: `transform: translate(-50%, -50%) rotate(25deg)`, size `80vw × 80vh`
- `--progress-bar-color: transparent; --poster-color: transparent;`
## Texture Swapping
On model load, preload both textures using `modelViewer.createTexture()`. Do a one-frame shader warm-up (apply blue then green) so first real swap is instant.
```ts
const blueTexture = await modelViewer.createTexture(ASSETS.BLUE_BASE_COLOR)
const greenTexture = await modelViewer.createTexture(ASSETS.GREEN_BASE_COLOR)
```
On flavor switch, apply via:
```ts
material.pbrMetallicRoughness.baseColorTexture.setTexture(texture)
```
## Header
Glassmorphism nav bar with:
- Logo: SVG circle + cross icon with "Soda" text in Galada font
- Nav pills: Home (active), Ingredients, Taste, Eco, Reviews
- Active/hover state: `bg-[#fbcfe8] text-[#011d17]`
- Contact Us button: `bg-black/50` rounded pill
- Glass effect: `backdrop-blur-[20px] bg-white/8 border border-white/20`
## Left Column
- Main title: "Pure" (outline) + "Zero" in Galada, `clamp(5rem, 10vw, 12rem)`, `line-height: 0.8`
- Description: "Unleash the crisp taste of zero sugar..." in Inter, muted color
- CTA: "Shop Now" pill button with pink `+` circle icon
- Award badge: glass icon + "DESIGN AWARDS" / "PREMIUM BEVERAGE 2025"
- Award badge pushed to bottom with `margin-top: auto`
## Right Column
- Two flavor cards: "Diet Classic" ($2.99) and "Zero Lime" ($2.99)
- Cards: glass bg, `border-radius: 28px`, hover lifts image `-30px rotate(-12deg) scale(1.15)`
- Active card: `border-color: #fbcfe8`
- Zero Lime card image: `filter: brightness(0.7)`
- Carousel nav arrows: glass circles with ← →
- Side title: "Refreshingly" (outline) + "Clean" in Galada, right-aligned
## Berry Positions
Foreground (z-index above text and can):
- b1: `220px` at `top: 25%; left: 30%`
- b2: `100px` at `top: 60%; left: 42%`
- b3: `250px` at `top: 30%; left: 62%`
- b4: `140px` at `top: 15%; left: 48%`
- b5: `120px` at `top: 75%; left: 20%`
- b6: `180px` at `top: 45%; left: 75%`
Background (behind everything):
- b7: `80px` at `top: 15%; left: 40%` opacity `0.7`
- b8: `70px` at `top: 50%; left: 55%` opacity `0.6`
- b9: `75px` at `top: 80%; left: 35%` opacity `0.7`
## Leaf Positions
- l1: `60px` at `top: 10%; left: 15%`
- l2: `140px` at `top: 40%; left: 80%` opacity `0.4`
- l3: `80px` at `top: 70%; left: 75%`
- l4: `120px` at `top: 85%; left: 20%` opacity `0.3`
## Responsive (max-width: 1200px)
- Product viewer: `width: 100vw; height: 60vh; top: 40%`
- Hero content: single column, `padding-top: 8rem`
- Titles: `font-size: 5rem`
- Right column: center-aligned
## Assets
All assets are hosted on a CDN. Create a constants file:
```ts
// lib/assets.ts
export const ASSETS = {
LEAVES: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/leaves.glb',
CHERRY: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/cherry.glb',
BLUEBERRY: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/blueberry.glb',
SODA_CAN: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/deit_soda2.glb',
GREEN_SODA_IMG: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/Green%20Soda.png',
BLUE_SODA_IMG: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/Blue%20Soda.png',
GREEN_BASE_COLOR: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/green%20base%20color.jpg',
BLUE_BASE_COLOR: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/blue%20base%20color.jpg',
BUBBLE: 'https://api.getlayers.ai/storage/v1/object/public/public/assets/soda-14ff8a788d/bubble.png',
} as const
```
## Animation Loop (requestAnimationFrame)
Run a single `requestAnimationFrame` loop that handles:
1. Mouse interpolation (lerp 0.05)
2. Can camera orbit update (mouse tilt + switch spin)
3. Parallax container transforms
4. Berry repulsion + floating (only when not switching flavors)
5. Leaf floating
Use `useEffect` with cleanup in the main page component or a custom `useAnimationLoop` hook.
## Important Notes
- All `model-viewer` components must be in `'use client'` components
- URL-encode spaces in asset filenames (`%20`)
- The can entrance: `.hero-center` starts `opacity: 0`, fades in over `1.5s` with `0.3s` delay, then floats ±20px infinitely
- Hide model-viewer defaults: `--progress-bar-color: transparent; --poster-color: transparent`
- Preload berry models in a hidden div to avoid lag during flavor switch
- No separate loading screen needed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment