Last active
July 12, 2026 13:26
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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