072
AI ready Refreshed

3D Product Showcase Website Template

£29
or the whole Vault, £99

Nine-page product brand website template with a real-time three.js glass bottle on the home page, then a product page, a four-item shop, a working bag, a live map of stockists and a journal. Worked as a fragrance house; suits skincare, spirits, candles and any single hero product.

ScrollTrigger Lenis advanced
11 more details
webgl-shadervideo-playbackscrubscroll-revealload-sequenceview-transitionscrossfadefilterprogressive-enhancementkeyboard-navigationfocus-management

Updated

About this template

A complete website for a brand with one beautiful product, built around two materials at once: live footage and a real-time 3D object. On the home page a glass bottle rendered in three.js hangs in the middle of full-bleed video and behaves like glass, so the moving water and the giant wordmark behind it are refracted, split into faint colour at the edges and bent as it turns. Scroll, and the stage holds while three chapters pass and the glass moves from clear salt to fig green to cedar amber. It ships as a finished nine-page site for Oriel, a small fictional fragrance house on the Atlantic coast.

Read the full template overview

The other eight pages load no three.js and stay fast. The product page has a size picker whose pictures, price and bag line all agree, and a slider that moves through a day on skin over the same footage. The collection shows four products in one quiet index, with a sprays-a-day slider that rewrites how long each one lasts. The atelier has three portraits and a ledger you can search by the batch number on your bottle. The formula page publishes every material and recalculates each amount for any size. Stockists is a real tiled map with a filterable list. The journal has two long articles and an archive of short letters.

A bag panel follows the visitor across every page, with quantities, a vial credit and totals, and it says plainly that there is no checkout behind it. Castoro serif carries the headings and Red Hat Text carries the rest, on salt-coloured paper between dark footage bands.

Making it yours means three video clips, the photographs, the words on the bottle label, one list of products with their prices and a short token block. Without WebGL, under reduced motion or with no JavaScript, the stage becomes stacked sections and every other page still reads in full.

3D Product Showcase Template - website template preview

What's included

16 items
  • Nine pages: home, product, collection, atelier, formula, stockists, a journal and two articles, on ordinary page loads with a short crossfade
  • Real-time three.js glass bottle on the home page, with transmission, dispersion and tinted attenuation, refracting the footage and wordmark behind it
  • Full-bleed video stage: three muted, looping clips dissolved into each other as scroll moves through three note chapters
  • Cursor-driven water-drop lens, a bottle that tilts towards the pointer, and press-and-hold spray with a keyboard-accessible button
  • WebGL and three.js load on the home page only, so inner pages parse no 3D code
  • Product page with sizes, refill, shipping and care, where the picture, price and bag count follow the chosen size
  • How-it-wears slider: a native range moves through a day on skin, crossfading the footage and redrawing three note strengths
  • Collection index that changes its large picture on hover, focus or tap, with a sprays-a-day slider rewriting how long each item lasts
  • Slide-out bag that persists across pages: quantities, removal, an emptied state, a vial credit and totals, with honest front-end wording
  • Batch ledger on the atelier page: a swell chart of buttons plus a number lookup with helpful misses
  • Published formula: a proportional strip, amounts recalculated for any size, rows rearranged by note or by place, and a photograph and story per material
  • Stockists on a real MapLibre map with OpenFreeMap tiles, loaded only when reached, with region and service filters and an empty state
  • Journal with a filterable letters archive and two articles, each with a small hands-on control inside the text
  • Full-screen menu as the site navigation, with the current page marked, focus held and returned, and a footer page list without JavaScript
  • Designed fallbacks: composed stills and stacked chapters for reduced motion, no WebGL, no JavaScript or a blocked CDN, with the menu and bag still working without GSAP
  • Bundled re-encoded clips, baked WebP photographs and phone-sized image rungs on every page's first picture

Perfect for

5 use cases
  • Perfume, skincare and beauty brands with one hero product and a small range
  • Craft gin, spirits and beverage bottles with a story about ingredients and place
  • Candle and home fragrance studios with stockists
  • Olive oil, tea or any maker that wants to publish where things come from
  • Any single-product launch with a physical object and brand footage

How it works

6 sections

Glass that refracts the page

On the home page the footage and the wordmark are drawn on a plane behind the bottle rather than in the page, so the glass material can refract them. The bottle is built from simple modelled parts with a canvas-drawn paper label, so there is no model file to load and every dimension is a number you can change.

Pointer, press and scroll

On a mouse the pointer drives a magnifying lens in the footage and a gentle tilt of the bottle. Pressing the stage, or using the Spray button, releases a short mist and sends a ring through the water. The stage is a tall section with a sticky frame: scroll chooses the chapter, the clip dissolves, the wordmark changes and the bottle completes a full turn.

A site around the stage

Every other page is plain HTML on an ordinary page load, with a short crossfade in browsers that support it. The 3D code sits in its own file that only the home page loads, so inner pages stay light. The full-screen menu is the navigation everywhere, and a small header appears as you scroll back up the inner pages.

One bag on every page

Products and prices live in one list. The size pickers read from it and the bag panel draws from it, and the choice is kept in the browser between pages, so the count, the line on the product page and the totals in the panel always agree. Nothing is reserved or charged, and the panel says so.

Something to handle on every page

Each inner page has its own control: a slider through a day on skin, a sprays-a-day slider, a batch ledger, a formula that recalculates for any bottle, a filtered map, a letters archive and small controls inside the articles. All of them are native inputs and buttons, so they work by keyboard and touch, and without JavaScript the same content prints as a plain list.

Fallbacks that are finished pages

If WebGL or three.js is missing, the home stage renders as four full-height sections with its own footage. Under reduced motion the clips hold on their poster frames and every reveal is visible. If the animation library is blocked, the menu, the bag and the hands-on controls keep working.

Plugins ScrollTrigger
Difficulty Advanced
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this template's demo (9 pages, lowest score shown), from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Questions about this template

What kinds of products does this 3D showcase suit besides perfume?

Anything sold as one beautiful object with a mood behind it and a small range around it: skincare and serums, craft gin and spirits, candles and home fragrance, olive oil, tea. Oriel is a fictional fragrance house; replace the three clips, the label words, the product list and the copy, then change the glass tints to suit your bottle or jar.

Can I use my own product model instead of the built bottle?

Yes. The bottle is a group of simple meshes in one function in the stage script, so you can swap it for a glTF loaded with three.js's GLTFLoader and keep the same glass material, tilt, turn and mist. The README shows where the group is built and which numbers position and scale it for desktop and portrait screens.

What do I do about the product pictures on the shop pages?

The pictures in the range show Oriel's bottles, flask and vials, rendered from the same 3D bottle as the home page and set over the footage or a photograph. Replace them with your own pack shots at the same file names and proportions, ideally on a similar dark or textured ground. The README lists each file, and the label wording on each is fictional.

Is the bag connected to a shop or a checkout?

No. The bag is a front end only: it keeps quantities in the visitor's browser, works out totals and a vial credit, and its checkout button only says that nothing has been charged. No order is saved or reserved. To sell for real, point the product list and checkout button at Shopify Buy Button, Stripe Payment Links, Snipcart or your own cart.

Does the stockist map need an API key, and how do I add my own shops?

No key or account is needed: it uses MapLibre with OpenFreeMap tiles, which are OpenStreetMap data and allow commercial use. Each shop is one list item carrying its longitude, latitude and services, so adding a shop gives it a pin and puts it in the filters. Keep the map's attribution, which the licence requires.

Do the newsletter form and batch ledger send or store anything?

No. The footer form collects one email address and shows a thank-you message without sending it anywhere; give it an action for Mailchimp, Buttondown, ConvertKit or Formspree and remove the demo handler. The batch ledger reads a short list in the script, so you can repurpose it as vintages, harvests or production runs, or delete the section.

How heavy is the site, and how does it behave on phones?

The home page carries three.js and the three clips, about six megabytes of footage, though only the first plays until you scroll. It renders at a lower pixel ratio on phones and swaps hover for a slow drift. Inner pages load no 3D code, and their footage and map load only when reached, with a menu built for touch.

The Vault opens this source and the whole library.

Each product gives you the HTML, CSS and JavaScript source, the full documentation and an AI setup prompt, licensed for unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

One payment, over 300 products, and future additions to the library.

Documentation

Quick Start

Open the folder through any static server (videos and ES modules do not load from file:// in every browser), edit the copy and upload it to any static host. There is no build step. Pages are ordinary HTML files that share assets/style.css and assets/script.js; only index.html also loads assets/stage.js and three.js.

Two things in index.html are required and must stay where they are:

  1. The head script before the stylesheet. It adds has-js (gates the scroll reveals) and, when the device has WebGL 2 and no reduced-motion preference, gl (switches the stage from stacked sections to the sticky WebGL frame). Both happen before first paint, so neither version flashes. Every page also adds js, which swaps the no-JavaScript menu and shop links in the bar for their buttons.
  2. The import map and module shim at the end of <body>. They load three.js r180 and its RoomEnvironment lighting helper as ES modules, then hand them to assets/stage.js with an oriel:three-ready event. If the module fails, the shim removes gl and the fallback shows.

Using It With Your Own Design

Start with the token block at the top of assets/style.css: tide (dark ground), salt and paper (reading grounds), ink, foam and one cedar accent, each with an RGB triple. Then open the ORIEL_STAGE settings object at the top of assets/stage.js:

Setting What it does
words Four words drawn behind the bottle: the hero wordmark, then one per chapter
tints Glass tint for the hero and each chapter, as hex colours
label The four lines printed on the bottle's paper label
cap, capFinish Cap shape (block or pebble) and finish (lacquer, white, brass or chrome); add ?cap=brass to the URL to preview a finish
ease How quickly the scroll turn and pointer tilt catch up (per 60Hz frame)
lensRadius Radius of the cursor lens in CSS pixels
dprFine, dprCoarse Pixel-ratio caps for mouse and touch devices; the first performance lever

Your shop is the ORIEL_SHOP list at the top of assets/script.js: one entry per thing you sell, with a name, a detail line, a price, a picture and a link. Prices printed in the HTML must agree with it. The same file holds ORIEL_WEAR (the words on the how-it-wears slider) and ORIEL_LEDGER (the batch ledger).

Keep the data- hooks on elements whose behaviour you want: data-stage, data-stage-frame, data-stage-mount, data-stage-block, data-stage-video, data-stage-index, data-spray, data-hero-fade, data-reveal, data-reveal-group, data-menu*, data-buy, data-bag-open, data-bag-count, data-in-bag, data-silhouette, data-wear*, data-range*, data-ledger*, data-formula*, data-stockists, data-shop, data-letters, data-stepper, data-newsletter. Every feature is guarded: delete a section or a whole page and the rest still runs, but remember to take its link out of the menu and footer on each page.

Sections and Configuration

  • Stage. One hero block and three chapter blocks. Each chapter owns one <video data-stage-video="n">, muted loop playsinline autoplay, with a poster. With WebGL the clips keep playing underneath the canvas as textures and only the ones in view play; without it they are the backgrounds of four stacked sections. The hero's .stage__brand line is visually hidden because the canvas (or the composed still) draws the wordmark; it remains the heading's accessible text. If you replace the still with a picture that has no wordmark, delete that CSS rule.
  • The bottle. Built in buildBottle() in assets/stage.js, and used by both the stage and the Collection section, from an extruded rounded rectangle (body), two cylinders (neck and brass collar), a squashed sphere (stone cap) and a plane with a canvas label. BODY_W, BODY_H, BODY_D, BEVEL and RADIUS set its proportions. To use your own product, load a glTF with three.js's GLTFLoader, add it to the bottle group and reuse the glass material on its glass parts.
  • Pointer and spray. Hover drives the lens and tilt on mouse devices; touch devices get a slow drift. Pressing on the stage (not on a link or button) mists the atomiser and sends a ripple through the footage; the Spray button does the same for keyboard users.
  • Chapter index. The 01 / 02 / 03 links jump to the settled position of each chapter, through Lenis when it is running. It is hidden on the hero and on narrow phones, where each chapter's kicker carries its number.
  • Home sections. Atelier, Formula, Wearing and Stockists are short plain previews with links to their pages. Formula rows are a list; add or remove rows freely. The Collection preview uses the same buy form as the product page, and its bottle is the same 3D model, scaled by each size's data-scale, over the clip in data-silhouette-video; without WebGL the section shows the matching pack picture.
  • Menu. A full-screen overlay and the navigation on every page. It stops Lenis while open (hence data-lenis-prevent), holds Tab inside, closes on Escape and returns focus to the Menu button. The current page carries aria-current="page", which you set per page. Without JavaScript the bar shows a link to the page list in the footer instead.
  • Inner-page bar. Fixed, tucks away on the way down and returns on the way up. bar--dark matches it to a dark first section.
  • Newsletter. A static form that collects one email and shows a thank-you message without sending anything. Give it an action for Mailchimp, Buttondown, ConvertKit or Formspree and delete initNewsletter().

The bag

initBagPanel() builds one slide-out panel, shared by every page. It keeps a list of skus in localStorage (wrapped in try/catch, so a blocked store only means the bag lasts one page), and draws quantities, removal, an emptied state, shipping, a vial credit and a total. The credit rule is ORIEL_SHOP.credit; delete it and its row if you have no equivalent. Nothing is reserved or charged: the checkout button only prints a message. To sell for real, replace the data-bag-checkout handler with your cart, such as Shopify Buy Button, Stripe Payment Links or Snipcart.

Any form[data-buy] adds to the bag. Radios carry data-sku; a single product uses a hidden input with data-sku. The price, any [data-pack-for="sku"] picture and the data-in-bag line follow the choice. salt-cedar.html?size=100 opens with that size chosen.

Inner pages

  • salt-cedar.html. Buy form, then the how-it-wears section: a native range crossfades three stacked clips (each with a lazy poster image underneath) and redraws three strengths. Change the words in ORIEL_WEAR, the colour points in draw(). Then notes, refill (#refill), shipping and care (#shipping).
  • collection.html. Each [data-range-row] names its picture with data-range-key. data-lasts holds millilitres; the slider divides by ORIEL_ML_PER_SPRAY. On phones each row shows its own picture instead of the large one.
  • atelier.html. Offset portraits, then the ledger. The bars are generated from ORIEL_LEDGER, and the height of each is the swell; use it for vintages or harvests with any number that varies.
  • formula.html. Rows hold their own percentage, place and story; the strip and the amounts are generated from them. Keep the percentages summing with the carrier rows to 100. On phones the picture opens under the row you tap.
  • stockists.html. See below.
  • journal.html and the two articles. Letters are native <details> filtered by data-letter. data-stepper steps through written states with a range or radios, and without JavaScript every state prints as text.

Stockists

A real street map: MapLibre GL on OpenFreeMap tiles, loaded only when the map reaches the viewport. There is no key or account. Each shop is a list item with data-lng, data-lat, data-region and optional data-refills, data-loft and data-home; each one gets its own pin, and the filters read the same attributes. The style and library URLs are constants above initStockists(). Keep the OpenStreetMap attribution, which the licence requires. Without WebGL or JavaScript the container keeps a link to the place on openstreetmap.org. Wheel and one-finger touch scroll the page, not the map.

Footage and Photographs

Clips are 1920x1080, 24fps, six seconds, with no audio track, cross-faded end to start so they loop without a jump. Fig and cedar ship as a VP9 WebM, listed first because it is a fraction of the size, with an H.264 MP4 fallback; salt ships as MP4 only, because on dense surf H.264 came out both smaller and sharper. Only the hero clip autoplays: the chapter clips are preload="none" and start a chapter before they are needed, so the page does not fetch footage nobody has scrolled to. When you swap a clip, keep that pattern, or drop the WebM <source> and keep the MP4. Slow, textured footage refracts best; flat or blurred clips make the glass look empty. Photographs are baked WebP derivatives; to use your own, replace the files in assets/img/ at the same sizes.

hero-still.webp and hero-still-portrait.webp are frames rendered from the WebGL stage itself; after changing the bottle or clips, screenshot your own stage with the copy hidden and replace them.

File Source (Pexels licence)
assets/video/salt.mp4 https://www.pexels.com/video/aerial-view-of-waves-crashing-on-black-sand-beach-39296252/
assets/video/fig.webm, .mp4 https://www.pexels.com/video/shadowy-silhouettes-of-leafy-branches-at-dusk-34671612/
assets/video/cedar.webm, .mp4 https://www.pexels.com/video/a-smoke-in-a-dark-room-4320605/
assets/img/atelier.webp https://www.pexels.com/photo/8450352/
assets/img/material-salt.webp https://www.pexels.com/photo/3693292/
assets/img/material-fig.webp https://www.pexels.com/photo/34041533/
assets/img/material-cedar.webp https://www.pexels.com/photo/7906948/
assets/img/wearing.webp https://www.pexels.com/photo/person-on-cliff-during-overcast-4641017/
assets/img/ines.webp https://www.pexels.com/photo/5682650/
assets/img/bench-dark.webp https://www.pexels.com/photo/5682641/
assets/img/hands-vial.webp https://www.pexels.com/photo/5682888/
assets/img/bench-room.webp https://www.pexels.com/photo/8450389/
assets/img/rui.webp https://www.pexels.com/photo/8450218/
assets/img/rui-pour.webp https://www.pexels.com/photo/8450108/
assets/img/joana.webp https://www.pexels.com/photo/7967356/
assets/img/ericeira-harbour.webp https://www.pexels.com/photo/30856160/
assets/img/ericeira-boats.webp https://www.pexels.com/photo/34207521/
assets/img/ing-salt.webp https://www.pexels.com/photo/40265556/
assets/img/ing-bergamot.webp https://www.pexels.com/photo/6021680/
assets/img/ing-ambrette.webp https://www.pexels.com/photo/36485512/
assets/img/ing-fig.webp https://www.pexels.com/photo/34330514/
assets/img/ing-violet.webp https://www.pexels.com/photo/12573961/
assets/img/ing-iris.webp https://www.pexels.com/photo/37900582/
assets/img/ing-cedar.webp https://www.pexels.com/photo/39976361/
assets/img/ing-birch.webp https://www.pexels.com/photo/9907486/
assets/img/ing-vetiver.webp https://www.pexels.com/photo/28721255/
assets/img/saltpans.webp https://www.pexels.com/photo/21036450/
assets/img/foam.webp https://www.pexels.com/photo/10066790/
assets/img/seaweed.webp https://www.pexels.com/photo/9901506/
assets/img/shop-counter.webp https://www.pexels.com/photo/8657297/
assets/img/shop-hand.webp https://www.pexels.com/photo/8657301/

pack-salt-50, pack-salt-100, pack-salt-vial, pack-notes, pack-flask, pack-loft, range and their -800 and -960 sizes are the product pictures. They are rendered from the same 3D bottle as the home page, with simple modelled vials and a flask, over the footage or a photograph, and they carry the Oriel label wording, so replace them with your own pack shots at the same names and proportions. pack-flask uses the foam photograph above as its ground, and pack-loft uses https://www.pexels.com/photo/26976385/. Photographs of people are stock photographs of models, not of anyone named in the copy. foam, range, pack-salt-50, bench-dark, shop-counter and rui-pour have a smaller phone-sized file, used through srcset; replace both.

Fonts

Castoro (regular and italic) and Red Hat Text are self-hosted in assets/fonts/, so the page makes no render-blocking third-party request. The files are the Latin subsets Google Fonts serves; Red Hat Text is one variable file covering weights 400 to 500. They are declared by the @font-face rules at the top of assets/style.css, and the two regular faces are preloaded in the <head>. The canvas wordmark and bottle label draw with the same family names, so keep them if you swap the files.

Licence: SIL Open Font License 1.1, full text in assets/fonts/OFL.txt. Castoro: copyright 2020 The Castoro Project Authors (https://github.com/TiroTypeworks/Castoro). Red Hat Text: copyright 2024 The Red Hat Project Authors (https://github.com/RedHatOfficial/RedHatFont).

To change a typeface, put your .woff2 in assets/fonts/, point the @font-face rule and the --font-display or --font-body token at it, and change or drop the font preload in the <head>. To load a Google font instead, delete the @font-face rules and the preloads and add the Google Fonts <link> to the <head>.

Palette

One palette. Measured contrast pairs are recorded in the :root comment of assets/style.css. Type over footage sits in the lower third, where the stage scrim is strongest; keep new stage copy there. --cedar is never body text on salt; use --cedar-ink.

Accessibility

Content is real HTML throughout: the stage copy, chapter headings and index are in the document whether or not the canvas exists, and the canvas is aria-hidden. All actions are native links and buttons with visible focus, a skip link leads to the content, and the menu manages focus. The pointer lens and tilt are decorative; the spray has a keyboard button. Under reduced motion the WebGL stage never starts, the clips are held on their poster frames and every reveal is visible. With JavaScript off the stacked stage and all pages render without gates, the bar links to a page list in the footer, and the slider and stepper content prints as plain lists. With the CDN blocked the menu, bag and hands-on controls still work. The bag is a labelled dialog that holds focus and returns it; ranges announce their state through aria-valuetext; the formula strip is a pointer convenience alongside the row buttons, which are the real controls. Page transitions are a short CSS crossfade that is turned off under reduced motion.

Dependencies

GSAP 3.15.0 and ScrollTrigger (compatibility floor GSAP 3.12+), Lenis 1.3.17 (optional), three.js r180 through the import map on the home page only, and MapLibre GL 4.7.1 with OpenFreeMap tiles on the stockists page only. Castoro (regular and italic) and Red Hat Text (400, 500) are self-hosted in assets/fonts/ (see Fonts). GSAP owns the matchMedia branches, teardown, stage scroll progress, the hero intro, chapter copy transitions, reveals and the collection picker; the three.js loop on gsap.ticker owns every continuous stage value (pointer chase, bottle turn, footage dissolve, mist). If your site already imports three.js, reuse that module in the shim rather than loading a second copy.

Teardown

Pages are ordinary loads, so nothing needs tearing down in the shipped site. If you move a page into a client-routed app, call window.gsapContext.revert() before removing it. It stops the frame loop, disconnects the observers, disposes every geometry, material and texture, releases the WebGL context, removes the canvas, the map, the bag panel and all owned listeners, destroys the Lenis instance it created and leaves unrelated animations alone.

Files

index.html, salt-cedar.html, collection.html, atelier.html, formula.html, stockists.html, journal.html, journal-salt-has-no-smell.html, journal-six-weeks-in-the-dark.html, assets/style.css, assets/script.js (every page), assets/stage.js (home only), assets/favicon.svg, assets/video/, assets/img/ and assets/fonts/ make up the deployable site.

Browser Support and Performance

The WebGL stage needs WebGL 2 and import maps (current Chrome, Edge, Firefox and Safari). Everything else falls back to the stacked stage. On a device that will run the stage, the probe in the <head> starts three.js downloading straight away (modulepreload), in parallel with the rest of the page; other devices never fetch it. The glass uses three.js transmission, which renders the backdrop a second time each frame; the stage only renders while it is on screen. Lower dprFine first, then set dispersion to 0 in the glass material. The three clips total about six megabytes. Inner pages load no three.js; the footage on the product page starts only when its section is on screen, and the map library loads only when the map is reached.

Also in your download: the full source, worked examples, the events and programmatic API, and the class reference.

£29