RES-027intermediateprompt

FIZZ — Procedural beverage showcase

Build an original single-file beverage concept with a procedural WebGL can, two flavor worlds, SVG fallback, functional dialogs and motion controls. The complete reference implementation is included.

#single-html#WebGL#procedural-assets#beverage#variant-switcher#responsive#reduced-motion

FIZZ — working source project

A procedural WebGL beverage showcase with an SVG fallback. Includes the complete canonical source, prompt, README and file manifest. A reconstruction blueprint, not a promise of identical results from prose alone.

Prompt
# Recreate this site as a single HTML file: FIZZ You are a creative frontend developer. Write a complete index.html using the specification and canonical source below. The result must be an executable file, not an explanation, screenshot or pseudocode. In a coding workspace create the file. In chat return one complete HTML code block. If output would be truncated, write the file through the tool's file workflow; never replace important sections with comments such as "rest unchanged". This is original THE VAULT material. Its structure is a precise reproduction blueprint: shell, tokens, ordered layers, responsive rules, interaction state, reveal, fixed parameters and assets. FIZZ is a fictional concept. Keep the result free from invented sales, awards, testimonials and product-performance promises. ## What it is A full-screen editorial beverage showcase on a warm paper-colored stage. Large serif type sits to the left; a real procedural WebGL can floats in the middle; two compact flavor cards sit on the right. Floating citrus-like slices become berry clusters when the flavor changes. The second flavor changes the world to deep plum with a pale violet can. Native dialogs make the story and ingredient-note controls work without pretending to sell a product. The layout is one-screen on a normal desktop viewport where content fits. Mobile deliberately scrolls: headline and CTA, object, flavor cards, footer. Content must never be hidden to satisfy an arbitrary viewport height. ## Page shell & libraries Use semantic HTML with lang=en, UTF-8, viewport metadata and the exact title/description. No package manager, framework, CDN, font request, image download or API key is needed. All styling and scripting live in the file. Use browser-native Canvas 2D, WebGL 1, ResizeObserver, IntersectionObserver, dialog and matchMedia. System serif headings are Georgia/Times; body is Arial/Helvetica. ### Global CSS (root variables, reset and body) Preserve the CSS variables and color-scheme logic. Default paper #eee7d8, ink #2d281f, accent #f96f36. Berry paper #25172d, ink #f5e8d5, accent #c4c6f3. Use explicit box-sizing, horizontal-overflow handling, focus-visible styles, a skip link and visually hidden status text. Do not globally suppress focus outlines or browser zoom. ## Layout & sections (in order) ### 1. Atmospheric and decorative containers Inside main, place the radial ambient layer, inline noise texture and #decorations. Decorative slices/berries are generated from inline SVG and have aria-hidden. Six deterministic positions are used, not random asset URLs. Keep their stacking below text and controls. ### 2. Header Use the original FIZZ mark, three real in-page buttons (Flavors, Ingredients, Our story) and a Meet FIZZ story button. On narrow screens hide the third navigation button and header action, retaining working access through the other controls. The mark links to #main. ### 3. Hero and its ordered layers 3a. Left: eyebrow, one h1 "Good days. Great fizz.", variant description, Find your flavor CTA and small concept note. 3b. Center: figure .product-stage, #product-canvas, inline #fallback SVG and caption. The fallback is hidden only after WebGL succeeds. SVG uses an explicit hidden attribute; do not rely on assigning an arbitrary hidden property to an SVG node. 3c. Right: #flavors with Mango Sun/Berry Moon buttons, aria-pressed state, previous/next controls and a short secondary heading. No real prices or purchase buttons. 3d. Footer: concept label and a visible Pause/Resume motion button. The footer joins normal flow on mobile. ### 4. Hidden/support markup Keep the skip link, polite live status and one native dialog with title, content and close control. Shader source and vector filters are inline. There is no media preloader because there are no network assets. ### Responsive Retain desktop, max-width1100, max-width640 and short-desktop rules. Header desktop100px/mobile76px. Desktop uses three columns; medium uses two plus the finish panel; mobile uses a vertical layout. Mobile can stage440px, with page scrolling. Validate360/390/430px and1440px. Do not substitute overflow:hidden on body height100vh for genuine responsive layout. ## The interactions / animation logic Keep variants in one data table. A selection updates document flavor, copy, aria-pressed, status text, fallback and GPU texture together. Texture labels must read normally, never mirrored. A new click replaces the spin timestamp, so rapid switching ends on the final selected variant without stale callbacks. The two arrow buttons alternate flavors. Find your flavor scrolls to the selector and focuses the active variant. Dialog controls open real story/notes content. Escape, the close button and outside-backdrop click close the dialog and restore focus. Never replace these actions with dummy href=# behavior. Pointer parallax runs only for fine hovering pointers. Motion can be paused by the user, reduced-motion preference, document visibility or offscreen state. Static mode keeps all content and controls. Resize re-renders; pagehide cancels loops and observers; pageshow restores them for a retained browser page. WebGL failures and context loss show the vector fallback. No hidden external state, analytics, commerce or authentication is added. ## The loader / reveal Render text and controls immediately. Use the small CSS reveal and float only when motion is permitted. Initialize the procedural can after DOM markup exists. Show the SVG while the renderer is unavailable; there is no endless loader or blank asset placeholder. ## Fixed parameters (bake these in) - Desktop container padding5%, column ratio1:1.15:.8, gap24px. - Hero heading clamp65px/6vw/90px; mobile70px. Keep the deliberate line break. - Can body radius.7, approximate vertical extent-1.6 to1.6,96 radial segments. - Perspective31degrees, camera offset-7.2, pixel ratio capped2. - Base can tilt-.24radians aroundZ; .17radians aroundX shows the cap. - Fine-pointer smoothing.05; X/Y tilt limits derived from .2/.13 factors. - One1000ms eased full spin on flavor switch; paused mode switches instantly. - Six vector decorations; mobile hides the three small distant ornaments. - Shared native dialog widthmin560px/viewport-minus32px; close target44px. - Two explicitly fictional flavor profiles; no nutrition, volume-sales or award claims. ## Assets All assets are embedded or generated by the source below. Texture labels are generated in Canvas2D; geometry in JavaScript; fruit and fallback in SVG; grain as a data-URI filter. The two flavor names and all brand marks are concept artwork for this example. There are no GetLayers assets, GLB files, stock photos or remote font files. ## Canonical implementation Use the complete source below as the implementation reference. Preserve behavior, public copy, visual hierarchy and fallback. Do not reduce it to a static image or omit working controls. The inclusion of code is explicit; do not describe the output as generated from a one-line brief. ```html <!doctype html> <html lang="en" data-flavor="mango"> <head> <meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="description" content="FIZZ — an original interactive beverage concept with a procedural 3D can and two flavor worlds."> <title>FIZZ — A little unexpected.</title> <style> :root{--paper:#eee7d8;--ink:#2d281f;--muted:#655d50;--accent:#f96f36;--accent-dark:#8e3313;--glass:rgba(255,255,255,.34);--line:rgba(45,40,31,.17);--scene-glow:#ffc386;--can:#ff843f;--cream:#fff6dd;--label-ink:#fff5d9;--mx:0;--my:0;color-scheme:light} *{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:Arial,Helvetica,sans-serif;transition:background .7s,color .7s;overflow-x:hidden}button,a{-webkit-tap-highlight-color:transparent}button{font:inherit;color:inherit;cursor:pointer}a{color:inherit}button:focus-visible,a:focus-visible{outline:3px solid var(--accent);outline-offset:5px}button{touch-action:manipulation}button:disabled{cursor:wait}.skip{position:fixed;left:20px;top:-100px;z-index:300;background:var(--ink);color:var(--paper);padding:12px}.skip:focus{top:12px}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap} html[data-flavor=berry]{--paper:#25172d;--ink:#f5e8d5;--muted:#c8b8c7;--accent:#c4c6f3;--accent-dark:#33354f;--glass:rgba(244,229,244,.065);--line:rgba(245,232,213,.22);--scene-glow:#734078;--can:#b4b7e5;--label-ink:#292a46;color-scheme:dark} .header{height:100px;padding:0 5%;display:flex;align-items:center;justify-content:space-between;position:relative;z-index:20;gap:20px}.logo{display:flex;align-items:center;gap:10px;text-decoration:none;font-size:30px;font-weight:900;letter-spacing:-2px}.logo-mark{width:20px;height:30px;border:2px solid currentColor;border-radius:7px;transform:rotate(-14deg);position:relative}.logo-mark:after{content:"";position:absolute;inset:7px 5px;border-top:2px solid currentColor;border-bottom:2px solid currentColor}.nav{display:flex;border:1px solid var(--line);border-radius:999px;padding:5px;background:var(--glass);backdrop-filter:blur(20px);gap:3px}.nav button{background:none;border:0;padding:12px 18px;border-radius:999px;font-size:13px}.nav button:hover{background:var(--accent);color:var(--accent-dark)}.header-action{border:1px solid var(--line);border-radius:30px;padding:14px 21px;background:transparent;font-size:13px}.header-action:hover{background:var(--ink);color:var(--paper)} .hero{position:relative;min-height:calc(100svh - 100px);padding:20px 5% 70px;display:grid;grid-template-columns:1fr 1.15fr .8fr;gap:24px;align-items:center;isolation:isolate}.ambient{position:absolute;inset:-100px 0 0;overflow:hidden;pointer-events:none;z-index:-2;background:radial-gradient(ellipse at 52% 50%,var(--scene-glow),transparent 47%)}.ambient:after{content:"";position:absolute;width:660px;height:660px;left:50%;top:50%;transform:translate(-50%,-50%);border:1px solid var(--line);border-radius:50%;opacity:.6}.ambient:before{content:"";position:absolute;width:790px;height:790px;left:50%;top:50%;transform:translate(-50%,-50%);border:1px solid var(--line);border-radius:50%;opacity:.28}.grain{position:absolute;inset:0;z-index:-1;opacity:.035;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' opacity='.6' filter='url(%23g)'/%3E%3C/svg%3E");pointer-events:none}.eyebrow{font-size:11px;letter-spacing:2px;text-transform:uppercase;display:flex;align-items:center;gap:10px}.eyebrow:before{content:"";height:7px;width:7px;border-radius:50%;background:var(--accent)}.hero-left{position:relative;z-index:5;align-self:center}.hero-left h1{font-family:Georgia,'Times New Roman',serif;font-weight:400;font-size:clamp(65px,6vw,90px);letter-spacing:-6px;line-height:.92;margin:32px 0 26px}.hero-left h1 em{font-weight:400;color:var(--accent);white-space:nowrap}.description{font-size:15px;line-height:1.7;max-width:290px;color:var(--muted)}.cta{margin-top:22px;display:flex;align-items:center;gap:28px;background:var(--ink);color:var(--paper);border:0;border-radius:999px;padding:15px 18px 15px 24px;min-height:54px;font-size:13px}.cta b{height:25px;width:25px;border-radius:50%;background:var(--paper);color:var(--ink);font-size:18px;display:grid;place-items:center;font-weight:400}.small-note{margin:30px 0 0;font-size:11px;letter-spacing:1px;color:var(--muted);line-height:1.6}.small-note strong{display:block;font-size:12px;color:var(--ink);letter-spacing:0;margin-bottom:3px} .product-stage{height:min(650px,76svh);min-height:490px;position:relative;z-index:3;margin:0 -55px;pointer-events:none}.product-stage:after{content:"";position:absolute;width:60%;height:22px;left:21%;bottom:6%;background:var(--ink);filter:blur(23px);opacity:.18;transform:rotate(-4deg)}.product-canvas,.fallback{width:100%;height:100%;display:block;position:absolute;inset:0;z-index:2}.fallback{padding:30px;transform:rotate(-13deg);filter:drop-shadow(15px 22px 18px rgba(0,0,0,.16))}.fallback[hidden]{display:none}.fallback text{fill:var(--label-ink)}.fallback circle{stroke:var(--label-ink)}.fallback .can-color{fill:var(--can)}.product-caption{position:absolute;bottom:5px;width:100%;text-align:center;font-size:10px;letter-spacing:2px;color:var(--muted)} .decorations{position:absolute;inset:0;pointer-events:none;z-index:1;overflow:hidden}.fruit{position:absolute;width:var(--size);left:var(--x);top:var(--y);opacity:var(--opacity,1);filter:drop-shadow(5px 16px 12px rgba(25,12,3,.13));animation:float 7s ease-in-out infinite;animation-delay:var(--delay);transform:translate(calc(var(--mx)*var(--depth)),calc(var(--my)*var(--depth))) rotate(var(--rotation));transition:opacity .4s}.fruit svg{display:block;width:100%;height:auto}.fruit.berry-art{display:none}html[data-flavor=berry] .fruit.mango-art{display:none}html[data-flavor=berry] .fruit.berry-art{display:block}@keyframes float{0%,100%{translate:0 0}50%{translate:0 -15px}}.fruit-1{--x:43%;--y:7%;--size:115px;--rotation:-24deg;--delay:-1s;--depth:18px;--opacity:.6}.fruit-2{--x:70%;--y:42%;--size:132px;--rotation:20deg;--delay:-3s;--depth:-26px}.fruit-3{--x:35%;--y:72%;--size:110px;--rotation:15deg;--delay:-5s;--depth:34px}.fruit-4{--x:65%;--y:5%;--size:54px;--rotation:-12deg;--delay:-2s;--depth:20px;--opacity:.6}.fruit-5{--x:28%;--y:24%;--size:55px;--rotation:18deg;--delay:-4s;--depth:-16px;--opacity:.35}.fruit-6{--x:61%;--y:83%;--size:57px;--rotation:-5deg;--delay:-6s;--depth:10px;--opacity:.45} .hero-right{position:relative;z-index:6;padding-left:12px}.collection-label{font-size:11px;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin:0 0 18px}.flavor-card{display:grid;grid-template-columns:50px 1fr 22px;align-items:center;gap:14px;text-align:left;width:100%;border:1px solid var(--line);border-radius:17px;padding:17px;background:var(--glass);margin-bottom:12px;min-height:92px;backdrop-filter:blur(12px);transition:border .3s,transform .3s}.flavor-card:hover{transform:translateX(-4px)}.flavor-card[aria-pressed=true]{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.swatch{height:53px;width:37px;border-radius:8px;transform:rotate(-9deg);background:linear-gradient(90deg,#bd4b20,#ffac65 28%,#f57e3c 75%,#b0431a);box-shadow:2px 6px 7px #0002;position:relative;margin:auto}.swatch:before{content:"FIZZ";font-size:12px;letter-spacing:-1px;font-weight:900;position:absolute;top:19px;width:100%;text-align:center;color:#fff8dd}.swatch:after{content:"";position:absolute;left:3px;right:3px;top:2px;border-top:2px solid #d8d3c9;border-radius:5px}.swatch.purple{background:linear-gradient(90deg,#6e73ab,#d5d6f7 30%,#a3a6d7 75%,#535782)}.swatch.purple:before{color:#282b46}.flavor-name{display:block;font-size:14px;font-weight:600}.flavor-info{display:block;font-size:11px;color:var(--muted);margin-top:7px}.selected-marker{height:18px;width:18px;border-radius:50%;border:1px solid var(--line);display:grid;place-items:center;font-size:11px}.flavor-card[aria-pressed=true] .selected-marker{background:var(--accent);color:var(--accent-dark);border-color:var(--accent)}.switch-row{display:flex;align-items:center;justify-content:space-between;margin:17px 0 40px;font-size:11px;color:var(--muted)}.switch-controls{display:flex;gap:7px}.round{height:38px;width:38px;border:1px solid var(--line);border-radius:50%;background:transparent;font-size:17px}.round:hover{background:var(--accent);color:var(--accent-dark)}.side-title{font-family:Georgia,serif;font-size:clamp(24px,2.6vw,40px);font-weight:400;line-height:1.12;letter-spacing:-1px;margin:0;max-width:260px}.side-title em{color:var(--accent)}.footer{position:absolute;left:5%;right:5%;bottom:25px;display:flex;justify-content:space-between;align-items:center;gap:15px;border-top:1px solid var(--line);padding-top:17px;font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--muted);z-index:8}.motion-button{border:0;background:transparent;padding:8px 0;font-size:11px;letter-spacing:0;min-height:32px;text-transform:none;text-decoration:underline;text-underline-offset:4px}.footer a{text-underline-offset:4px} dialog{width:min(560px,calc(100% - 32px));border:1px solid var(--line);border-radius:24px;background:var(--paper);color:var(--ink);padding:36px;box-shadow:0 30px 100px #0005}dialog::backdrop{background:rgba(20,12,18,.58);backdrop-filter:blur(5px)}dialog h2{font-family:Georgia,serif;font-weight:400;font-size:38px;margin:6px 0 20px}dialog p{font-size:15px;line-height:1.7;color:var(--muted)}.dialog-top{display:flex;justify-content:space-between;gap:20px;align-items:center;font-size:10px;letter-spacing:2px}.close-dialog{background:transparent;border:1px solid var(--line);border-radius:50%;width:44px;height:44px;font-size:20px}.dialog-detail{padding:14px 0;border-top:1px solid var(--line);display:grid;grid-template-columns:110px 1fr;gap:15px;font-size:13px;line-height:1.6}.dialog-detail strong{font-weight:500;color:var(--muted)} body.motion-paused .fruit{animation-play-state:paused}body.motion-paused *{scroll-behavior:auto!important}body.motion-paused .hero-left,body.motion-paused .hero-right{animation:none}.hero-left{animation:reveal .85s both}.hero-right{animation:reveal 1s .12s both}@keyframes reveal{from{opacity:.3;transform:translateY(12px)}to{opacity:1;transform:translateY(0)}} @media(min-width:1100px) and (max-height:780px){.hero{padding-top:4px;padding-bottom:65px}.hero-left h1{font-size:clamp(60px,6.6vw,96px);margin-top:20px}.product-stage{min-height:450px;height:calc(100svh - 165px)}.header{height:80px}.small-note{margin-top:20px}.switch-row{margin-bottom:25px}} @media(max-width:1100px){.header{height:86px;padding:0 4%}.hero{padding:20px 4% 90px;grid-template-columns:1fr 1fr;gap:18px}.hero-left h1{font-size:clamp(64px,9vw,95px)}.hero-right{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:12px;padding:0;max-width:750px;width:100%;margin:0 auto}.collection-label{grid-column:1/-1;margin-bottom:0}.switch-row{grid-column:1/-1;margin:0 0 6px}.side-title{display:none}.flavor-card{margin:0}.product-stage{margin:0 -30px;min-height:450px;height:510px}.footer{left:4%;right:4%;bottom:18px}.fruit-2{--x:78%}.fruit-3{--x:45%;--y:55%}.nav button{padding:10px 13px}.header-action{display:none}} @media(max-width:640px){.header{height:76px;padding:0 20px;gap:12px}.logo{font-size:27px}.nav{gap:0;padding:3px}.nav button{padding:10px 12px;font-size:12px}.nav button:last-child{display:none}.hero{display:flex;flex-direction:column;align-items:stretch;padding:22px 20px 0;gap:0;min-height:unset}.hero-left h1{font-size:70px;letter-spacing:-4px;margin:21px 0 18px;max-width:350px}.description{max-width:320px;font-size:14px;margin-bottom:0}.cta{margin-top:19px;min-height:48px}.small-note{display:none}.eyebrow{font-size:10px;letter-spacing:1.6px}.product-stage{margin:-12px -20px 0;height:440px;min-height:unset}.product-stage:after{bottom:11%}.product-caption{bottom:21px}.hero-right{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:0}.flavor-card{grid-template-columns:1fr;padding:13px;gap:10px;text-align:center;min-height:159px}.flavor-name{font-size:13px}.flavor-info{font-size:10px}.selected-marker{display:none}.collection-label{margin:4px 0 9px}.switch-row{margin-top:6px}.round{width:44px;height:44px}.footer{position:relative;inset:auto;flex-wrap:wrap;margin-top:25px;padding-top:14px;padding-bottom:22px;letter-spacing:.4px;font-size:9px}.footer span:nth-child(2){display:none}.motion-button{min-height:44px}.ambient{inset:-76px 0 0;background:radial-gradient(ellipse at 55% 51%,var(--scene-glow),transparent 37%)}.ambient:after{width:500px;height:500px;top:49%}.ambient:before{width:590px;height:590px;top:49%}.fruit-1{--x:4%;--y:42%;--size:82px}.fruit-2{--x:75%;--y:58%;--size:88px}.fruit-3{--x:9%;--y:66%;--size:65px}.fruit-4,.fruit-5,.fruit-6{display:none}dialog{padding:24px}dialog h2{font-size:32px}.dialog-detail{grid-template-columns:75px 1fr}} @media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important;scroll-behavior:auto!important}.fruit{translate:0 0}} </style> </head> <body> <a class="skip" href="#main">Skip to product</a> <header class="header"><a class="logo" href="#main" aria-label="FIZZ home"><i class="logo-mark" aria-hidden="true"></i>FIZZ</a><nav class="nav" aria-label="Explore FIZZ"><button type="button" data-action="flavors">Flavors</button><button type="button" data-dialog="ingredients">Ingredients</button><button type="button" data-dialog="story">Our story</button></nav><button type="button" class="header-action" data-dialog="story">Meet FIZZ ↗</button></header> <main class="hero" id="main" tabindex="-1"> <div class="ambient" aria-hidden="true"></div><div class="grain" aria-hidden="true"></div> <div class="decorations" id="decorations" aria-hidden="true"></div> <section class="hero-left" aria-labelledby="headline"><div class="eyebrow">A little unexpected.</div><h1 id="headline">Good days.<br><em>Great fizz.</em></h1><p class="description" id="description">Sun-ripened mango meets a brighter kind of bubble. A small escape from the ordinary.</p><button class="cta" type="button" data-action="flavors">Find your flavor <b aria-hidden="true">↗</b></button><p class="small-note"><strong>A playful beverage concept</strong>Two original flavor worlds. Made to explore.</p></section> <figure class="product-stage" aria-label="Interactive FIZZ can, Mango Sun flavor"><canvas class="product-canvas" id="product-canvas" aria-hidden="true"></canvas><svg class="fallback" id="fallback" viewBox="0 0 400 600" role="img" aria-label="FIZZ can illustration"><defs><linearGradient id="canShade"><stop stop-color="#000" stop-opacity=".28"/><stop offset=".27" stop-color="#fff" stop-opacity=".12"/><stop offset=".73" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#000" stop-opacity=".32"/></linearGradient></defs><rect class="can-color" x="100" y="70" width="200" height="450" rx="45"/><rect x="100" y="70" width="200" height="450" rx="45" fill="url(#canShade)"/><ellipse cx="200" cy="75" rx="86" ry="18" fill="#c9c6bf"/><ellipse cx="200" cy="75" rx="74" ry="12" fill="#aaa9a4"/><rect x="182" y="69" width="36" height="12" rx="6" fill="#dedad1"/><ellipse cx="200" cy="512" rx="88" ry="15" fill="#c9c6bf"/><text x="200" y="270" text-anchor="middle" font-family="Arial,sans-serif" font-size="83" font-weight="900" fill="#fff5d9">FIZZ</text><text id="fallback-flavor" x="200" y="325" text-anchor="middle" font-family="Arial,sans-serif" font-size="17" letter-spacing="3" fill="#fff5d9">MANGO SUN</text><circle cx="200" cy="405" r="29" fill="none" stroke="#fff5d9" stroke-width="2"/><text x="200" y="411" text-anchor="middle" font-family="Arial,sans-serif" font-size="14" fill="#fff5d9">330</text></svg><figcaption class="product-caption">A NEW WAY TO LOOK AT EVERYDAY</figcaption></figure> <section class="hero-right" aria-label="Choose a flavor" id="flavors"><p class="collection-label">The concept collection / 01</p><button class="flavor-card" type="button" data-flavor="mango" aria-pressed="true"><span class="swatch" aria-hidden="true"></span><span><span class="flavor-name">Mango Sun</span><span class="flavor-info">Bright · tropical · playful</span></span><span class="selected-marker" aria-hidden="true">✓</span></button><button class="flavor-card" type="button" data-flavor="berry" aria-pressed="false"><span class="swatch purple" aria-hidden="true"></span><span><span class="flavor-name">Berry Moon</span><span class="flavor-info">Deep · mellow · unexpected</span></span><span class="selected-marker" aria-hidden="true">✓</span></button><div class="switch-row"><span id="variant-count">01 / 02</span><div class="switch-controls"><button class="round" type="button" data-action="previous" aria-label="Previous flavor">←</button><button class="round" type="button" data-action="next" aria-label="Next flavor">→</button></div></div><h2 class="side-title">Two flavors.<br><em>One good mood.</em></h2></section> <footer class="footer"><span>FIZZ / concept collection 2026</span><span>Small bubbles. Big personality.</span><button class="motion-button" type="button" data-dialog="story">Concept story</button><button class="motion-button" type="button" id="motion-toggle" aria-pressed="false">Pause motion</button></footer> </main> <p class="sr-only" role="status" aria-live="polite" id="status"></p> <dialog id="detail-dialog" aria-labelledby="dialog-title"><div class="dialog-top"><span>FIZZ / CONCEPT NOTES</span><button class="close-dialog" type="button" id="close-dialog" aria-label="Close details">×</button></div><h2 id="dialog-title">A brighter kind of bubble.</h2><div id="dialog-content"></div></dialog> <script> /* Original procedural art and WebGL renderer. No libraries or remote files. */ (() => { 'use strict'; const variants={mango:{name:'Mango Sun',label:'MANGO SUN',color:'#ff843f',ink:'#fff5d9',description:'Sun-ripened mango meets a brighter kind of bubble. A small escape from the ordinary.'},berry:{name:'Berry Moon',label:'BERRY MOON',color:'#b4b7e5',ink:'#292a46',description:'Blackcurrant-inspired notes meet a softer kind of sparkle. Find a little wonder after hours.'}}; const root=document.documentElement,stage=document.querySelector('.product-stage'),canvas=document.getElementById('product-canvas'),fallback=document.getElementById('fallback'),motionButton=document.getElementById('motion-toggle'),reduced=matchMedia('(prefers-reduced-motion: reduce)'),fine=matchMedia('(hover: hover) and (pointer: fine)'); let active='mango',userPaused=false,paused=reduced.matches,visible=true,targetX=0,targetY=0,smoothX=0,smoothY=0,spinStart=-10000,frame=0,gl=null,render=()=>{},upload=()=>{},destroyed=false; const citrus='<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"><defs><radialGradient id="c"><stop stop-color="#ffd895"/><stop offset="1" stop-color="#ef8938"/></radialGradient></defs><ellipse cx="80" cy="82" rx="65" ry="67" fill="#df7930"/><ellipse cx="78" cy="76" rx="64" ry="65" fill="#fae8a9"/><circle cx="78" cy="76" r="57" fill="url(#c)"/><g stroke="#ffeac0" stroke-width="3" opacity=".75"><path d="M78 21V131M23 76H133M39 37L117 115M39 115L117 37"/></g><circle cx="78" cy="76" r="7" fill="#ffeed0"/></svg>'; const berries='<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"><defs><radialGradient id="b"><stop stop-color="#a897d9"/><stop offset=".55" stop-color="#514269"/><stop offset="1" stop-color="#251629"/></radialGradient></defs><g fill="url(#b)"><circle cx="65" cy="46" r="30"/><circle cx="101" cy="61" r="34"/><circle cx="45" cy="86" r="33"/><circle cx="83" cy="109" r="34"/><circle cx="111" cy="105" r="29"/></g><g fill="#c1abd4" opacity=".6"><ellipse cx="56" cy="36" rx="8" ry="5"/><ellipse cx="94" cy="49" rx="9" ry="5"/><ellipse cx="37" cy="74" rx="9" ry="5"/></g><path d="M70 37Q78 6 102 17Q96 37 70 37" fill="#8b987b"/></svg>'; for(let i=1;i<=6;i++){for(const [className,art] of [['mango-art',citrus],['berry-art',berries]]){const node=document.createElement('div');node.className=`fruit fruit-${i} ${className}`;node.innerHTML=art.replaceAll('id="c"',`id="c${i}"`).replaceAll('url(#c)',`url(#c${i})`).replaceAll('id="b"',`id="b${i}"`).replaceAll('url(#b)',`url(#b${i})`);document.getElementById('decorations').append(node);}} function updateMotion(){paused=userPaused||reduced.matches;motionButton.disabled=reduced.matches;const modalOpen=document.getElementById('detail-dialog').open;document.body.classList.toggle('motion-paused',paused||document.hidden||!visible||modalOpen);motionButton.textContent=reduced.matches?'Motion reduced':paused?'Resume motion':'Pause motion';motionButton.setAttribute('aria-pressed',String(paused));if(paused){smoothX=targetX=0;smoothY=targetY=0;root.style.setProperty('--mx','0');root.style.setProperty('--my','0');}cancelAnimationFrame(frame);frame=0;if(!paused&&!document.hidden&&visible&&!modalOpen&&!destroyed){frame=requestAnimationFrame(loop);}else{render(performance.now(),false);}} function choose(key){if(!variants[key]||key===active)return;active=key;root.dataset.flavor=key;root.style.setProperty('--can',variants[key].color);document.getElementById('description').textContent=variants[key].description;document.getElementById('fallback-flavor').textContent=variants[key].label;stage.setAttribute('aria-label',`Interactive FIZZ can, ${variants[key].name} flavor`);document.getElementById('variant-count').textContent=key==='mango'?'01 / 02':'02 / 02';document.querySelectorAll('button[data-flavor]').forEach(button=>button.setAttribute('aria-pressed',String(button.dataset.flavor===key)));document.getElementById('status').textContent=`Selected ${variants[key].name}.`;spinStart=paused?-10000:performance.now();upload();render(performance.now(),!paused);} document.querySelectorAll('button[data-flavor]').forEach(button=>button.addEventListener('click',()=>choose(button.dataset.flavor))); function moveFlavor(){choose(active==='mango'?'berry':'mango');} document.querySelectorAll('[data-action]').forEach(button=>button.addEventListener('click',()=>{if(button.dataset.action==='flavors'){document.getElementById('flavors').scrollIntoView({behavior:paused?'instant':'smooth',block:'center'});document.querySelector(`button[data-flavor="${active}"]`).focus({preventScroll:true});}else moveFlavor();})); motionButton.addEventListener('click',()=>{userPaused=!userPaused;updateMotion();});reduced.addEventListener('change',updateMotion);document.addEventListener('visibilitychange',updateMotion);const observer=new IntersectionObserver(entries=>{visible=entries[0].isIntersecting;updateMotion();});observer.observe(stage); window.addEventListener('pointermove',event=>{if(!fine.matches||paused)return;targetX=Math.max(-1,Math.min(1,event.clientX/innerWidth*2-1));targetY=Math.max(-1,Math.min(1,event.clientY/innerHeight*2-1));},{passive:true});document.addEventListener('pointerleave',()=>{targetX=targetY=0;}); const dialog=document.getElementById('detail-dialog');let dialogOpener=null;const content={ingredients:{title:'Flavor, imagined.',body:'<p>These are illustrative tasting notes for a fictional beverage, not a product formulation or nutrition claim.</p><div class="dialog-detail"><strong>Mango Sun</strong><span>Mango, citrus brightness and a rounded finish.</span></div><div class="dialog-detail"><strong>Berry Moon</strong><span>Blackcurrant-inspired depth, a softer sparkle and an evening palette.</span></div><p>No real drink is offered for sale on this concept page.</p>'},story:{title:'A little unexpected.',body:'<p>FIZZ explores how one everyday object can inhabit two completely different visual worlds: the warmth of a sunny afternoon and the quieter color of an evening.</p><p>The cans, flavor names and product stories are fictional. This is an interactive design concept, with no ordering or payment service.</p>'}}; document.querySelectorAll('[data-dialog]').forEach(button=>button.addEventListener('click',()=>{dialogOpener=button;const data=content[button.dataset.dialog];document.getElementById('dialog-title').textContent=data.title;document.getElementById('dialog-content').innerHTML=data.body;dialog.showModal();updateMotion();document.getElementById('close-dialog').focus();}));document.getElementById('close-dialog').addEventListener('click',()=>dialog.close());dialog.addEventListener('click',event=>{if(event.target===dialog){const r=dialog.getBoundingClientRect();if(event.clientX<r.left||event.clientX>r.right||event.clientY<r.top||event.clientY>r.bottom)dialog.close();}});dialog.addEventListener('close',()=>{updateMotion();const target=dialogOpener&&dialogOpener.isConnected&&dialogOpener.getClientRects().length?dialogOpener:document.getElementById('main');target.focus({preventScroll:true});}); function setupGL(){ if(new URLSearchParams(location.search).get('renderer')==='svg')return; gl=canvas.getContext('webgl',{alpha:true,antialias:true,premultipliedAlpha:false});if(!gl)return; const vertex=`attribute vec3 aPosition;attribute vec3 aNormal;attribute vec2 aUV;attribute float aMetal;uniform mat4 uProjection;uniform vec3 uAngle;uniform float uBob;varying vec3 vNormal;varying vec3 vPosition;varying vec2 vUV;varying float vMetal;vec3 rot(vec3 p){float a=uAngle.x; p=vec3(p.x,p.y*cos(a)-p.z*sin(a),p.y*sin(a)+p.z*cos(a));a=uAngle.y;p=vec3(p.x*cos(a)+p.z*sin(a),p.y,-p.x*sin(a)+p.z*cos(a));a=uAngle.z;return vec3(p.x*cos(a)-p.y*sin(a),p.x*sin(a)+p.y*cos(a),p.z);}void main(){vec3 p=rot(aPosition);vPosition=p;vNormal=rot(aNormal);vUV=aUV;vMetal=aMetal;gl_Position=uProjection*vec4(p+vec3(0.,uBob,-7.2),1.);}`; const fragment=`precision mediump float;varying vec3 vNormal;varying vec3 vPosition;varying vec2 vUV;varying float vMetal;uniform sampler2D uLabel;void main(){vec3 n=normalize(vNormal);vec3 l=normalize(vec3(-2.,4.,5.));float d=max(dot(n,l),0.);vec3 base=vMetal>.5?vec3(.68,.69,.67):texture2D(uLabel,vUV).rgb;float spec=pow(max(dot(reflect(-l,n),normalize(vec3(0.,0.,7.)-vPosition)),0.),vMetal>.5?45.:55.);float rim=pow(1.-max(dot(n,normalize(vec3(0.,0.,7.)-vPosition)),0.),3.);vec3 col=base*(.64+.36*d)+vec3(spec*(vMetal>.5?.48:.19))+vec3(rim*.035);gl_FragColor=vec4(col,1.);}`; function shader(type,source){const s=gl.createShader(type);gl.shaderSource(s,source);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))throw Error(gl.getShaderInfoLog(s));return s;} const program=gl.createProgram();gl.attachShader(program,shader(gl.VERTEX_SHADER,vertex));gl.attachShader(program,shader(gl.FRAGMENT_SHADER,fragment));gl.linkProgram(program);if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw Error('WebGL link failed');gl.useProgram(program); const data=[];const N=96;function vertexData(x,y,z,nx,ny,nz,u,v,m){data.push(x,y,z,nx,ny,nz,u,v,m);}function ring(y0,y1,r0,r1,metal){for(let i=0;i<N;i++){const a=i/N*Math.PI*2,b=(i+1)/N*Math.PI*2;const points=[[a,y0,r0],[b,y0,r0],[b,y1,r1],[a,y0,r0],[b,y1,r1],[a,y1,r1]];for(const [angle,y,r] of points){const u0=1-((i/N+.25)%1);const u=angle===a?u0:u0-1/N;vertexData(Math.cos(angle)*r,y,Math.sin(angle)*r,Math.cos(angle),0,Math.sin(angle),u,(y+1.6)/3.2,metal);}}}function disk(y,r,normal){for(let i=0;i<N;i++){const a=i/N*Math.PI*2,b=(i+1)/N*Math.PI*2;vertexData(0,y,0,0,normal,0,0,0,1);for(const angle of normal>0?[b,a]:[a,b])vertexData(Math.cos(angle)*r,y,Math.sin(angle)*r,0,normal,0,0,0,1);}} ring(-1.49,1.4,.7,.7,0);ring(1.4,1.52,.7,.61,0);ring(-1.56,-1.49,.61,.7,0);ring(1.52,1.58,.61,.62,1);ring(-1.6,-1.56,.63,.61,1);disk(1.58,.62,1);disk(-1.6,.63,-1);ring(1.583,1.607,.14,.14,1);disk(1.607,.14,1); const buffer=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,buffer);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array(data),gl.STATIC_DRAW);const stride=9*4;for(const [name,size,offset] of [['aPosition',3,0],['aNormal',3,12],['aUV',2,24],['aMetal',1,32]]){const loc=gl.getAttribLocation(program,name);gl.enableVertexAttribArray(loc);gl.vertexAttribPointer(loc,size,gl.FLOAT,false,stride,offset);} const texture=gl.createTexture();gl.bindTexture(gl.TEXTURE_2D,texture);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE);gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,true);const label=document.createElement('canvas');label.width=1024;label.height=1024;const ctx=label.getContext('2d'); upload=()=>{const v=variants[active];ctx.fillStyle=v.color;ctx.fillRect(0,0,1024,1024);ctx.fillStyle=v.ink;ctx.textAlign='center';ctx.font='900 175px Arial';ctx.fillText('FIZZ',512,470);ctx.font='bold 31px Arial';ctx.fillText(v.label,512,568);ctx.font='17px Arial';ctx.fillText('A LITTLE UNEXPECTED',512,628);ctx.beginPath();ctx.arc(512,785,47,0,Math.PI*2);ctx.lineWidth=3;ctx.strokeStyle=v.ink;ctx.stroke();ctx.font='bold 26px Arial';ctx.fillText('330',512,795);ctx.font='14px Arial';ctx.fillText('CONCEPT COLLECTION / 2026',512,878);ctx.textAlign='left';ctx.font='17px Arial';ctx.fillText('FIZZ',52,320);ctx.font='13px Arial';for(let y=360;y<540;y+=28)ctx.fillText('A playful beverage concept',52,y);gl.bindTexture(gl.TEXTURE_2D,texture);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,label);};upload(); const projectionLoc=gl.getUniformLocation(program,'uProjection'),angleLoc=gl.getUniformLocation(program,'uAngle'),bobLoc=gl.getUniformLocation(program,'uBob');gl.enable(gl.DEPTH_TEST);gl.clearColor(0,0,0,0); render=(now,animate)=>{if(gl.isContextLost())return;const ratio=Math.min(devicePixelRatio||1,2),w=Math.round(canvas.clientWidth*ratio),h=Math.round(canvas.clientHeight*ratio);if(!w||!h)return;if(canvas.width!==w||canvas.height!==h){canvas.width=w;canvas.height=h;gl.viewport(0,0,w,h);}const f=1/Math.tan(31*Math.PI/360),aspect=w/h,n=.1,far=50;gl.uniformMatrix4fv(projectionLoc,false,new Float32Array([f/aspect,0,0,0,0,f,0,0,0,0,(far+n)/(n-far),-1,0,0,2*far*n/(n-far),0]));const progress=animate?Math.max(0,Math.min(1,(now-spinStart)/1000)):1,ease=1-Math.pow(1-progress,3),spin=progress<1?ease*Math.PI*2:0;gl.uniform3f(angleLoc,(animate?smoothY*.13:0)+.17,(animate?smoothX*.2:0)+spin,-.24);gl.uniform1f(bobLoc,animate?Math.sin(now*.0011)*.05:0);gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);gl.drawArrays(gl.TRIANGLES,0,data.length/9);};fallback.setAttribute('hidden',''); } function loop(now){frame=0;if(paused||document.hidden||!visible||document.getElementById('detail-dialog').open||destroyed)return;smoothX+=(targetX-smoothX)*.05;smoothY+=(targetY-smoothY)*.05;root.style.setProperty('--mx',smoothX.toFixed(3));root.style.setProperty('--my',smoothY.toFixed(3));render(now,true);frame=requestAnimationFrame(loop);} try{setupGL();}catch(error){fallback.removeAttribute('hidden');canvas.hidden=true;console.warn('FIZZ: static illustration fallback.',error.message);} canvas.addEventListener('webglcontextlost',event=>{event.preventDefault();fallback.removeAttribute('hidden');canvas.hidden=true;cancelAnimationFrame(frame);frame=0;render=()=>{};upload=()=>{};});canvas.addEventListener('webglcontextrestored',()=>{canvas.hidden=false;try{setupGL();updateMotion();}catch{fallback.removeAttribute('hidden');canvas.hidden=true;}}); const resize=new ResizeObserver(()=>render(performance.now(),!paused));resize.observe(stage);window.addEventListener('pagehide',()=>{destroyed=true;cancelAnimationFrame(frame);observer.disconnect();resize.disconnect();});window.addEventListener('pageshow',event=>{if(event.persisted){destroyed=false;observer.observe(stage);resize.observe(stage);updateMotion();}});updateMotion(); })(); </script> </body> </html> ```

How to use it

Paste the complete prompt into a fresh Claude or Codex session and request index.html. Serve over local HTTP, verify both variants and dialogs, and record any repair prompts. Requires no external assets or package installation.