*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #050508; color: #f0f4fb; font-family: 'Inter', 'SF Pro Display', system-ui, sans-serif; overflow: hidden; height: 100vh; }
#canvas-container { position: fixed; inset: 0; z-index: 0; }
#canvas-container canvas { display: block; width: 100%; height: 100%; }
.overlay {
position: fixed; inset: 0; z-index: 2; display: flex; align-items: flex-end; padding: 3rem;
pointer-events: none;
}
.info-panel {
pointer-events: auto; max-width: 340px;
background: rgba(12,15,25,0.7); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(134,232,255,0.1); border-radius: 18px; padding: 2rem;
}
.eyebrow { display: inline-block; font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.18em; color: #86e8ff; margin-bottom: 0.6rem; }
h1 { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: 0.5rem; }
.subtitle { font-size: 0.82rem; color: #8a95a8; line-height: 1.5; margin-bottom: 1.2rem; }
.specs { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; margin-bottom: 1.2rem; }
.spec { background: rgba(134,232,255,0.05); border-radius: 8px; padding: 0.6rem 0.8rem; }
.spec-label { display: block; font-size: 0.6rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: #8a95a8; margin-bottom: 0.15rem; }
.spec-value { font-size: 0.85rem; font-weight: 600; color: #86e8ff; }
.controls { display: flex; gap: 0.5rem; }
.ctrl-btn {
flex: 1; padding: 0.55rem; border-radius: 8px;
border: 1px solid rgba(134,232,255,0.2); background: rgba(134,232,255,0.06);
color: #86e8ff; font: 600 0.75rem/1 'Inter', system-ui, sans-serif;
cursor: pointer; transition: background 0.2s, border-color 0.2s;
}
.ctrl-btn:hover { background: rgba(134,232,255,0.12); border-color: #86e8ff; }
.btn-back {
position: fixed; bottom: 2rem; right: 2rem; z-index: 10;
padding: 0.6rem 1.5rem; border-radius: 999px;
border: 1px solid rgba(134,232,255,0.2); color: #86e8ff; text-decoration: none;
font: 600 0.8rem/1 'Inter', system-ui, sans-serif;
background: rgba(5,5,8,0.7); backdrop-filter: blur(8px); transition: all 0.25s;
}
.btn-back:hover { background: rgba(134,232,255,0.08); border-color: #86e8ff; }
@media (max-width: 640px) {
.overlay { padding: 1.5rem; align-items: flex-end; }
.info-panel { max-width: 100%; }
}
if (!window.MotionPreference) {
const __mql = window.matchMedia("(prefers-reduced-motion: reduce)");
const __listeners = new Set();
const MotionPreference = {
prefersReducedMotion() {
return __mql.matches;
},
setOverride(value) {
const reduced = Boolean(value);
document.documentElement.classList.toggle("reduced-motion", reduced);
window.dispatchEvent(new CustomEvent("motion-preference", { detail: { reduced } }));
for (const listener of __listeners) {
try {
listener({ reduced, override: reduced, systemReduced: __mql.matches });
} catch {}
}
},
onChange(listener) {
__listeners.add(listener);
try {
listener({
reduced: __mql.matches,
override: null,
systemReduced: __mql.matches,
});
} catch {}
return () => __listeners.delete(listener);
},
getState() {
return { reduced: __mql.matches, override: null, systemReduced: __mql.matches };
},
};
window.MotionPreference = MotionPreference;
}
function prefersReducedMotion() {
return window.MotionPreference.prefersReducedMotion();
}
function initDemoShell() {
// No-op shim in imported standalone snippets.
}
import * as THREE from 'three';
initDemoShell({ title: '3D Product Showcase', category: '3d', tech: ['three.js', 'pbr-material', 'lighting'] });
let reduced = prefersReducedMotion();
if (reduced) document.documentElement.classList.add('reduced-motion');
window.addEventListener('motion-preference', (e) => { reduced = e.detail.reduced; });
// State
let orbiting = true;
let colorIndex = 0;
const colors = [
new THREE.Color('#86e8ff'),
new THREE.Color('#ae52ff'),
new THREE.Color('#ff40d6'),
new THREE.Color('#ffcc66'),
new THREE.Color('#50c878'),
];
// Scene
const container = document.getElementById('canvas-container');
const scene = new THREE.Scene();
scene.background = new THREE.Color('#050508');
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 1, 6);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
container.appendChild(renderer.domElement);
// Lights — three-point setup
const keyLight = new THREE.DirectionalLight(0xffffff, 2.5);
keyLight.position.set(5, 5, 5);
scene.add(keyLight);
const fillLight = new THREE.DirectionalLight(0x86e8ff, 1.0);
fillLight.position.set(-4, 2, -3);
scene.add(fillLight);
const rimLight = new THREE.DirectionalLight(0xae52ff, 1.5);
rimLight.position.set(0, -3, -5);
scene.add(rimLight);
scene.add(new THREE.AmbientLight(0x111122, 0.5));
// Object — Torus Knot
const geometry = new THREE.TorusKnotGeometry(1.2, 0.4, 200, 32);
const material = new THREE.MeshPhysicalMaterial({
color: colors[0],
metalness: 0.9,
roughness: 0.12,
clearcoat: 1.0,
clearcoatRoughness: 0.05,
reflectivity: 1.0,
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Ground reflection plane (subtle)
const planeGeo = new THREE.PlaneGeometry(20, 20);
const planeMat = new THREE.MeshStandardMaterial({
color: 0x050508, metalness: 0.8, roughness: 0.3,
});
const plane = new THREE.Mesh(planeGeo, planeMat);
plane.rotation.x = -Math.PI / 2;
plane.position.y = -2;
scene.add(plane);
// Mouse
const mouse = { x: 0, y: 0, sx: 0, sy: 0 };
document.addEventListener('mousemove', (e) => {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
});
// Controls
document.getElementById('btn-pause').addEventListener('click', () => {
orbiting = !orbiting;
document.getElementById('btn-pause').textContent = orbiting ? 'Pause Orbit' : 'Resume Orbit';
});
document.getElementById('btn-color').addEventListener('click', () => {
colorIndex = (colorIndex + 1) % colors.length;
material.color.copy(colors[colorIndex]);
});
// Animation
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
if (!reduced) {
// Smooth mouse
mouse.sx += (mouse.x - mouse.sx) * 0.03;
mouse.sy += (mouse.y - mouse.sy) * 0.03;
if (orbiting) {
mesh.rotation.y = t * 0.4;
mesh.rotation.x = Math.sin(t * 0.2) * 0.15;
}
// Mouse influence on camera
camera.position.x = mouse.sx * 1.5;
camera.position.y = 1 + mouse.sy * 0.8;
camera.lookAt(0, 0, 0);
// Floating bob
mesh.position.y = Math.sin(t * 0.8) * 0.1;
}
renderer.render(scene, camera);
}
animate();
// Resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
window.addEventListener('beforeunload', () => { geometry.dispose(); material.dispose(); renderer.dispose(); });
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Product Showcase — stealthisdesign</title>
<link rel="stylesheet" href="style.css">
<script type="importmap">{"imports":{"gsap":"/vendor/gsap/index.js","gsap/ScrollTrigger":"/vendor/gsap/ScrollTrigger.js","gsap/SplitText":"/vendor/gsap/SplitText.js","gsap/Flip":"/vendor/gsap/Flip.js","gsap/ScrambleTextPlugin":"/vendor/gsap/ScrambleTextPlugin.js","gsap/TextPlugin":"/vendor/gsap/TextPlugin.js","gsap/all":"/vendor/gsap/all.js","gsap/":"/vendor/gsap/","lenis":"/vendor/lenis/dist/lenis.mjs","three":"/vendor/three/build/three.module.js","three/addons/":"/vendor/three/examples/jsm/"}}</script>
</head>
<body>
<div id="canvas-container" aria-hidden="true"></div>
<div class="overlay">
<div class="info-panel">
<span class="eyebrow">Demo 08</span>
<h1>3D Product Showcase</h1>
<p class="subtitle">A procedural torus knot with MeshPhysicalMaterial — metalness, clearcoat, and three-point lighting. Click to pause/resume orbit.</p>
<div class="specs">
<div class="spec"><span class="spec-label">Material</span><span class="spec-value">Physical (PBR)</span></div>
<div class="spec"><span class="spec-label">Metalness</span><span class="spec-value">0.9</span></div>
<div class="spec"><span class="spec-label">Clearcoat</span><span class="spec-value">1.0</span></div>
<div class="spec"><span class="spec-label">Roughness</span><span class="spec-value">0.12</span></div>
</div>
<div class="controls">
<button id="btn-pause" class="ctrl-btn">Pause Orbit</button>
<button id="btn-color" class="ctrl-btn">Cycle Color</button>
</div>
</div>
</div>
<a href="/" class="btn-back">Back to Showcase</a>
<script type="module" src="script.js"></script>
</body>
</html>