webapp
Create a high-end, professional 3D interactive landing page
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#08080b" />
<title>Darksin Rogue — Harken Parfums</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;600;700;800&family=DM+Mono:wght@400;500&family=Montserrat:wght@400;500;600;700&family=Unbounded:wght@500;700;800&display=swap" rel="stylesheet" />
<style>
:root {
--ink: #08080b;
--soft-ink: #101015;
--paper: #e9e4e7;
--muted: #9f9aa3;
--violet: #b788ec;
--pink: #ef9dbb;
--ember: #e88a54;
--line: rgba(255,255,255,.13);
--serif: "Cinzel", Georgia, serif;
--sans: "Montserrat", Arial, sans-serif;
--mono: "DM Mono", monospace;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; background: var(--ink); }
body {
margin: 0; color: var(--paper); background: var(--ink); font-family: var(--sans);
overflow-x: hidden; cursor: none;
}
body::before {
content: ""; position: fixed; inset: 0; z-index: 20; pointer-events: none; opacity: .055;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.7'/%3E%3C/svg%3E");
}
::selection { background: var(--pink); color: #120d15; }
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: none; }
#webgl { position: fixed; inset: 0; z-index: 0; pointer-events: none; }
.cursor, .cursor-ring {
position: fixed; top: 0; left: 0; border-radius: 50%; pointer-events: none; z-index: 100;
transform: translate(-50%, -50%);
}
.cursor { width: 8px; height: 8px; background: #fff; mix-blend-mode: difference; }
.cursor-ring { width: 34px; height: 34px; border: 1px solid rgba(243,200,255,.75); transition: width .25s, height .25s, background .25s; }
body.hovering .cursor-ring { width: 56px; height: 56px; background: rgba(183,136,236,.1); }
nav {
position: fixed; z-index: 10; top: 0; width: 100%; padding: 25px clamp(22px, 4vw, 68px);
display: flex; justify-content: space-between; align-items: center;
transition: background .4s, padding .4s, border-color .4s; border-bottom: 1px solid transparent;
}
nav.scrolled { background: rgba(8,8,11,.7); border-color: var(--line); backdrop-filter: blur(18px); padding-top: 16px; padding-bottom: 16px; }
.wordmark { font-family: var(--serif); font-size: 15px; letter-spacing: .13em; font-weight: 700; }
.wordmark b { color: var(--pink); font-weight: 600; }
.nav-links { display: flex; align-items: center; gap: clamp(20px, 3vw, 48px); font-size: 10px; letter-spacing: .17em; text-transform: uppercase; }
.nav-links a { color: #c8c2ca; transition: color .2s; }
.nav-links a:hover { color: white; }
.nav-shop { border: 1px solid rgba(238,157,187,.7); padding: 11px 17px; color: #ffdce8 !important; }
.nav-shop:hover { background: #ef9dbb; color: #160c13 !important; }
main { position: relative; z-index: 1; }
.hero { min-height: 100svh; padding: 130px clamp(22px, 7vw, 120px) 52px; position: relative; display: flex; flex-direction: column; justify-content: space-between; }
.hero-top { max-width: 560px; position: relative; z-index: 2; }
.eyebrow { display: flex; align-items: center; gap: 11px; font: 10px var(--mono); text-transform: uppercase; letter-spacing: .18em; color: var(--pink); }
.eyebrow::before { content:""; display:inline-block; height: 1px; width: 30px; background: currentColor; }
h1 { font-family: var(--serif); font-size: clamp(54px, 8.7vw, 142px); line-height: .82; letter-spacing: -.075em; margin: 28px 0 27px; font-weight: 700; }
h1 em { display: block; font-style: normal; color: transparent; -webkit-text-stroke: 1px rgba(255,255,255,.68); margin-left: clamp(22px, 4vw, 68px); }
.hero-copy { color: #b9b3bc; font-size: 13px; line-height: 1.85; max-width: 310px; }
.hero-meta { position: relative; z-index: 2; display: flex; justify-content: space-between; align-items: flex-end; gap: 20px; }
.discover { display: inline-flex; align-items: center; gap: 16px; color: #f5edf1; text-transform: uppercase; font: 10px var(--mono); letter-spacing: .16em; }
.discover .circle { width: 47px; height: 47px; border-radius: 50%; border: 1px solid rgba(255,255,255,.48); display: grid; place-items: center; transition: .3s; }
.discover:hover .circle { background: var(--paper); color: var(--ink); transform: translateY(5px); }
.edition { text-align: right; text-transform: uppercase; font: 10px var(--mono); letter-spacing: .14em; color: #77717a; line-height: 1.8; }
.edition strong { color: #d5cbd1; font-weight: 400; }
.side-stamp { position: absolute; right: 25px; top: 50%; transform: rotate(90deg) translateX(50%); transform-origin: right top; font: 9px var(--mono); letter-spacing: .35em; color: #77717a; text-transform: uppercase; }
.section { position: relative; padding: 145px clamp(22px, 7vw, 120px); }
.manifesto { min-height: 87vh; background: linear-gradient(180deg, transparent, #101015 30%, #101015); display: grid; grid-template-columns: .72fr 1.28fr; gap: 10vw; align-items: center; }
.section-index { font: 10px var(--mono); letter-spacing: .18em; color: var(--pink); }
.manifesto h2, .collection-title h2 { font: 500 clamp(35px, 5.5vw, 83px)/1.04 var(--serif); letter-spacing: -.055em; margin: 0; }
.manifesto h2 span { color: var(--violet); }
.manifesto p { color: #aaa4ad; max-width: 560px; margin: 28px 0 0; line-height: 1.9; font-size: 14px; }
.notes { display: flex; gap: 0; margin-top: 55px; border-top: 1px solid var(--line); }
.note { flex: 1; padding: 18px 12px 0 0; }
.note + .note { padding-left: 18px; border-left: 1px solid var(--line); }
.note label { color: #706b72; display: block; font: 9px var(--mono); letter-spacing: .15em; text-transform: uppercase; margin-bottom: 9px; }
.note strong { font: 500 13px var(--sans); color: #e6dde2; }
.collection { background: #151318; padding-top: 118px; overflow: hidden; }
.collection-title { display: flex; justify-content: space-between; gap: 30px; align-items: end; margin-bottom: 52px; }
.collection-title p { max-width: 270px; color: #948e98; font-size: 12px; line-height: 1.8; margin: 0; }
.product-grid { display: grid; grid-template-columns: 1.25fr .75fr .75fr; gap: 13px; }
.product { min-height: 495px; position: relative; overflow: hidden; padding: 25px; border: 1px solid rgba(255,255,255,.09); background: #1a181d; display: flex; flex-direction: column; justify-content: flex-end; isolation: isolate; transition: border-color .35s, transform .35s; }
.product:hover { transform: translateY(-7px); border-color: rgba(239,157,187,.57); }
.product::before { content:""; z-index: -1; position: absolute; width: 230px; height: 230px; border-radius: 50%; filter: blur(20px); opacity:.75; top: 55px; left: 50%; transform: translateX(-50%); background: radial-gradient(circle, var(--product-glow), transparent 67%); transition: transform .6s; }
.product:hover::before { transform: translateX(-50%) scale(1.22); }
.product-art { position: absolute; top: 45px; left: 50%; transform: translateX(-50%); width: 108px; height: 230px; border-radius: 16px 16px 10px 10px; background: linear-gradient(105deg,#15151a,#2a282d 28%,#0a0a0d 50%,#242127 78%,#09090b); box-shadow: 20px 25px 35px rgba(0,0,0,.55), inset 3px 0 5px rgba(255,255,255,.12), inset -7px 0 12px rgba(0,0,0,.7); }
.product-art::before { content:""; position:absolute; width: 73px; height: 52px; left: 18px; top: -45px; border-radius: 12px 12px 4px 4px; background: repeating-linear-gradient(90deg,#0d0d10 0 4px,#303036 5px 7px,#0a0a0c 8px 10px); box-shadow: inset 0 5px 7px rgba(255,255,255,.14); }
.product-art::after { content: attr(data-name); position:absolute; text-align:center; top: 92px; left: 9px; right:9px; color:#f0d8e5; font: 700 12px/1 var(--serif); letter-spacing: -.05em; text-shadow: 0 0 8px var(--product-glow); }
.product small { text-transform: uppercase; letter-spacing: .17em; color: var(--pink); font: 9px var(--mono); }
.product h3 { margin: 10px 0 5px; font: 500 25px var(--serif); letter-spacing: -.04em; }
.product p { margin: 0; color: #9d979f; font-size: 11px; }
.product .price { margin-top: 19px; font: 11px var(--mono); color:#ece4e8; }
.ritual { min-height: 82vh; display: grid; grid-template-columns: 1fr 1fr; gap: 10vw; align-items: center; background: radial-gradient(ellipse at 85% 30%, rgba(123,55,98,.17), transparent 42%), #09090c; }
.ritual-copy h2 { margin: 19px 0; font: 500 clamp(38px,5vw,76px)/1 var(--serif); letter-spacing: -.06em; }
.ritual-copy p { max-width: 390px; color:#aaa5ad; font-size: 13px; line-height:1.9; }
.outlined-btn { margin-top: 29px; background: transparent; color: #f8ebf1; border: 1px solid rgba(239,157,187,.66); padding: 16px 24px; font: 10px var(--mono); text-transform: uppercase; letter-spacing:.16em; transition: .3s; }
.outlined-btn:hover { background: var(--pink); color: #180d14; box-shadow: 0 0 36px rgba(239,157,187,.3); }
.ritual-list { border-top: 1px solid var(--line); }
.ritual-item { padding: 23px 0; display:flex; align-items:center; border-bottom:1px solid var(--line); gap: 23px; }
.ritual-item span { color: var(--pink); font: 10px var(--mono); }
.ritual-item strong { font: 500 18px var(--serif); letter-spacing: -.03em; }
footer { position:relative; z-index:1; padding: 48px clamp(22px,7vw,120px) 28px; background:#e6dfe2; color:#111015; }
.footer-main { display:flex; justify-content:space-between; align-items:flex-end; gap: 30px; padding-bottom:65px; }
.footer-main h2 { margin:0; max-width:780px; font: 600 clamp(35px,6vw,93px)/.9 var(--serif); letter-spacing:-.075em; }
.footer-main a { border-bottom:1px solid #201a20; padding-bottom: 8px; font: 10px var(--mono); text-transform:uppercase; letter-spacing:.15em; white-space:nowrap; }
.footer-bottom { display:flex; justify-content:space-between; padding-top:18px; border-top:1px solid rgba(0,0,0,.22); color:#514b52; text-transform:uppercase; letter-spacing:.12em; font:9px var(--mono); }
.reveal { opacity: 0; transform: translateY(28px); }
@media(max-width: 720px) {
body { cursor: auto; } .cursor,.cursor-ring { display:none; } button { cursor:pointer; }
.nav-links a:not(.nav-shop) { display:none; } .nav-links { gap:0; }
.hero { padding-top: 115px; } h1 { margin-top:22px; } .side-stamp { display:none; }
.manifesto,.ritual { grid-template-columns:1fr; min-height:auto; gap:42px; }
.section { padding-top:95px; padding-bottom:95px; }
.product-grid { grid-template-columns:1fr; } .product { min-height:390px; }
.collection-title,.footer-main { align-items:flex-start; flex-direction:column; }
.notes { margin-top:40px; } .footer-bottom { gap:14px; flex-wrap:wrap; }
}
@media(prefers-reduced-motion: reduce) { *,*::before,*::after { animation-duration:.01ms!important; transition-duration:.01ms!important; scroll-behavior:auto!important; } }
</style>
</head>
<body>
<div id="webgl" aria-hidden="true"></div>
<div class="cursor"></div><div class="cursor-ring"></div>
<nav id="nav">
<a href="#" class="wordmark">HARKEN <b>—</b> PARFUMS</a>
<div class="nav-links">
<a href="#collection">Collection</a>
<a href="#ritual">The Ritual</a>
<a class="nav-shop" href="#collection">Shop Rogue</a>
</div>
</nav>
<main>
<section class="hero">
<div class="hero-top">
<div class="eyebrow">Extrait de Parfum · 2026</div>
<h1>DARKSIN <em>ROGUE</em></h1>
<p class="hero-copy">A beautiful transgression. Shadowed florals, burning amber and the confidence to leave a trace.</p>
</div>
<div class="side-stamp">Harken Parfums · Made for after dark</div>
<div class="hero-meta">
<a class="discover magnetic" href="#story"><span class="circle">↓</span> Reveal the scent</a>
<div class="edition"><strong>01 / 03</strong><br />50 ml · Extrait</div>
</div>
</section>
<section class="section manifesto" id="story">
<div class="section-index reveal">01 — The Darkside</div>
<div>
<h2 class="reveal">Not made to <span>behave.</span><br />Made to be remembered.</h2>
<p class="reveal">Darksin Rogue is an encounter between polished danger and unapologetic desire. It opens with a blade of pink pepper, softens into midnight rose, then stays close to skin in a trail of smoked vanilla and black resin.</p>
<div class="notes reveal">
<div class="note"><label>Opening</label><strong>Pink Pepper<br />Bergamot</strong></div>
<div class="note"><label>Heart</label><strong>Midnight Rose<br />Dark Suede</strong></div>
<div class="note"><label>Dry Down</label><strong>Smoked Vanilla<br />Black Amber</strong></div>
</div>
</div>
</section>
<section class="section collection" id="collection">
<div class="collection-title">
<div>
<div class="section-index reveal">02 — The Collection</div>
<h2 class="reveal">Choose your<br />after dark.</h2>
</div>
<p class="reveal">Three limited expressions of the Rogue signature, each composed to shift with the night.</p>
</div>
<div class="product-grid">
<article class="product reveal" style="--product-glow:#a878e5">
<div class="product-art" data-name="DARKSIN ROGUE"></div>
<small>Extrait de Parfum</small><h3>Original Rogue</h3><p>Rose · Suede · Black Amber</p><div class="price">50 ML — $185</div>
</article>
<article class="product reveal" style="--product-glow:#df7e55">
<div class="product-art" data-name="EMBER ROGUE"></div>
<small>Extrait de Parfum</small><h3>Ember Rogue</h3><p>Saffron · Leather · Tobacco</p><div class="price">50 ML — $185</div>
</article>
<article class="product reveal" style="--product-glow:#80a8df">
<div class="product-art" data-name="FROST ROGUE"></div>
<small>Extrait de Parfum</small><h3>Frost Rogue</h3><p>Iris · Ink · Mineral Musk</p><div class="price">50 ML — $185</div>
</article>
</div>
</section>
<section class="section ritual" id="ritual">
<div class="ritual-copy">
<div class="section-index reveal">03 — Wear the Night</div>
<h2 class="reveal">Your skin,<br />your alibi.</h2>
<p class="reveal">Apply to pulse points. Do not rub. Let the heat of your body unlock every precise, dangerous layer.</p>
<button class="outlined-btn magnetic">Discover the full ritual</button>
</div>
<div class="ritual-list reveal">
<div class="ritual-item"><span>01</span><strong>Spray once at the throat</strong></div>
<div class="ritual-item"><span>02</span><strong>Let silence do the work</strong></div>
<div class="ritual-item"><span>03</span><strong>Leave nothing ordinary behind</strong></div>
</div>
</section>
</main>
<footer>
<div class="footer-main">
<h2>Enter the<br /><i>beautiful</i> dark.</h2>
<a href="mailto:private@harkenparfums.com">Private access ↗</a>
</div>
<div class="footer-bottom"><span>© 2026 Harken Parfums</span><span>Paris · London · New York</span><span>Privacy · Terms</span></div>
</footer>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
<script>
(() => {
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
const container = document.getElementById("webgl");
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x08080b, .065);
const camera = new THREE.PerspectiveCamera(34, innerWidth / innerHeight, .1, 100);
camera.position.set(0, .35, 10);
const renderer = new THREE.WebGLRenderer({ antialias:true, alpha:true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.outputEncoding = THREE.sRGBEncoding;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
container.appendChild(renderer.domElement);
const root = new THREE.Group();
root.position.set(1.72, -.25, 0);
root.rotation.set(-.05, -.31, .045);
scene.add(root);
const bottle = new THREE.Group();
root.add(bottle);
const bodyShape = new THREE.Shape();
const w = 1.45, h = 3.22, r = .19;
bodyShape.moveTo(-w/2+r,-h/2); bodyShape.lineTo(w/2-r,-h/2);
bodyShape.quadraticCurveTo(w/2,-h/2,w/2,-h/2+r);
bodyShape.lineTo(w/2,h/2-r); bodyShape.quadraticCurveTo(w/2,h/2,w/2-r,h/2);
bodyShape.lineTo(-w/2+r,h/2); bodyShape.quadraticCurveTo(-w/2,h/2,-w/2,h/2-r);
bodyShape.lineTo(-w/2,-h/2+r); bodyShape.quadraticCurveTo(-w/2,-h/2,-w/2+r,-h/2);
const bodyGeo = new THREE.ExtrudeGeometry(bodyShape,{depth:.47,bevelEnabled:true,bevelSegments:4,bevelSize:.065,bevelThickness:.07,curveSegments:22});
bodyGeo.center();
const bodyMat = new THREE.MeshPhysicalMaterial({color:0x101014, roughness:.34, metalness:.65, clearcoat:.42, clearcoatRoughness:.25});
const body = new THREE.Mesh(bodyGeo,bodyMat); bottle.add(body);
const neckMat = new THREE.MeshPhysicalMaterial({color:0x111116,roughness:.2,metalness:.85});
const neck = new THREE.Mesh(new THREE.CylinderGeometry(.34,.36,.38,48),neckMat); neck.position.y=1.78; bottle.add(neck);
const cap = new THREE.Group(); cap.position.y=2.12; bottle.add(cap);
const capBase = new THREE.Mesh(new THREE.CylinderGeometry(.55,.57,.23,48),neckMat); cap.add(capBase);
const capMain = new THREE.Mesh(new THREE.CylinderGeometry(.49,.53,.62,48),new THREE.MeshPhysicalMaterial({color:0x0a0a0e,roughness:.14,metalness:.83,clearcoat:1})); capMain.position.y=.36; cap.add(capMain);
for(let i=0;i<8;i++){ const ring = new THREE.Mesh(new THREE.TorusGeometry(.5,.018,7,48),new THREE.MeshStandardMaterial({color:0x34313a,metalness:.8,roughness:.25})); ring.rotation.x=Math.PI/2; ring.position.y=.09+i*.072; cap.add(ring); }
const capTop = new THREE.Mesh(new THREE.CylinderGeometry(.42,.49,.12,48),neckMat); capTop.position.y=.73; cap.add(capTop);
function labelTexture(){
const c=document.createElement("canvas"); c.width=800; c.height=1100; const x=c.getContext("2d");
x.clearRect(0,0,800,1100); x.strokeStyle="rgba(230,202,225,.6)"; x.lineWidth=4; x.setLineDash([17,10]);
x.beginPath(); x.arc(400,490,265,0,Math.PI*2); x.stroke(); x.setLineDash([]);
x.fillStyle="#edcddd"; for(let i=0;i<16;i++){let a=i*Math.PI*2/16, px=400+285*Math.cos(a),py=490+285*Math.sin(a);x.fillRect(px-3,py-3,6,6);}
const g=x.createLinearGradient(170,320,620,680);g.addColorStop(0,"#f6f0f5");g.addColorStop(1,"#d58cb5");x.fillStyle=g;
x.textAlign="center";x.font="800 87px Cinzel, serif"; x.fillText("DARKSIN",400,470);x.font="800 102px Cinzel, serif";x.fillText("ROGUE",400,565);
x.fillStyle="#ed9cc0";x.font="500 25px Montserrat, sans-serif";x.letterSpacing="8px";x.fillText("HARKEN",400,675);
x.fillStyle="rgba(255,255,255,.55)";x.font="16px DM Mono";x.fillText("EXTRAIT DE PARFUM · 50 ML",400,898);
return new THREE.CanvasTexture(c);
}
const label = new THREE.Mesh(new THREE.PlaneGeometry(1.18,1.75),new THREE.MeshBasicMaterial({map:labelTexture(),transparent:true,opacity:.97}));
label.position.set(0,.0,.274); bottle.add(label);
const floor = new THREE.Mesh(new THREE.PlaneGeometry(50,50),new THREE.MeshStandardMaterial({color:0x08080b,roughness:.87,metalness:.18}));
floor.rotation.x=-Math.PI/2;floor.position.y=-2.0;scene.add(floor);
const reflector = new THREE.Mesh(new THREE.CircleGeometry(2.9,64),new THREE.MeshBasicMaterial({color:0x9f5079,transparent:true,opacity:.065}));
reflector.rotation.x=-Math.PI/2;reflector.position.y=-1.985;reflector.scale.y=.48;scene.add(reflector);
scene.add(new THREE.HemisphereLight(0x8fa8ff,0x0b080a,.45));
const leftLight=new THREE.SpotLight(0xbacbff,4.7,15,.58,.55,1);leftLight.position.set(-5,5,4);leftLight.target=bottle;scene.add(leftLight,leftLight.target);
const warm=new THREE.PointLight(0xf08150,4.2,9);warm.position.set(4,4,2);scene.add(warm);
const pink=new THREE.PointLight(0xb664c8,2.3,8);pink.position.set(0,-1,3);scene.add(pink);
const smokeGroup=new THREE.Group();scene.add(smokeGroup);
const smokeCanvas=document.createElement("canvas");smokeCanvas.width=128;smokeCanvas.height=128;let sc=smokeCanvas.getContext("2d");
let grad=sc.createRadialGradient(64,64,2,64,64,64);grad.addColorStop(0,"rgba(205,223,255,.5)");grad.addColorStop(.3,"rgba(183,184,229,.18)");grad.addColorStop(1,"rgba(130,100,170,0)");sc.fillStyle=grad;sc.fillRect(0,0,128,128);
const smokeTex=new THREE.CanvasTexture(smokeCanvas);
for(let i=0;i<25;i++){let m=new THREE.Mesh(new THREE.PlaneGeometry(2.8,2.8),new THREE.MeshBasicMaterial({map:smokeTex,transparent:true,depthWrite:false,opacity:.14,blending:THREE.AdditiveBlending}));m.position.set((Math.random()-.5)*6,Math.random()*5-1.6,-1-Math.random()*2);m.scale.setScalar(.5+Math.random()*1.3);m.userData={speed:.001+Math.random()*.002,phase:Math.random()*6.28};smokeGroup.add(m)}
let mouse={x:0,y:0}, target={x:0,y:0};
addEventListener("pointermove",e=>{mouse.x=(e.clientX/innerWidth-.5);mouse.y=(e.clientY/innerHeight-.5)});
function resize(){camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setSize(innerWidth,innerHeight)} addEventListener("resize",resize);
const clock=new THREE.Clock();
function animate(){
requestAnimationFrame(animate); let t=clock.getElapsedTime();
target.x+=(mouse.x-target.x)*.04;target.y+=(mouse.y-target.y)*.04;
root.rotation.y=-.31+target.x*.35;root.rotation.x=-.05-target.y*.16;
root.position.y=-.25+Math.sin(t*.7)*.045;
smokeGroup.children.forEach((m,i)=>{m.position.x+=Math.sin(t*m.userData.speed*1000+m.userData.phase)*.0015;m.position.y+=m.userData.speed;m.material.opacity=.09+Math.sin(t+i)*.035;if(m.position.y>4)m.position.y=-2;});
renderer.render(scene,camera);
} animate();
gsap.registerPlugin(ScrollTrigger);
if(!reduce){
gsap.to(root.position,{x:-1.8,y:.2,ease:"none",scrollTrigger:{trigger:"#story",start:"top bottom",end:"bottom top",scrub:1.2}});
gsap.to(root.rotation,{y:.45,x:.12,ease:"none",scrollTrigger:{trigger:"#story",start:"top bottom",end:"bottom top",scrub:1.2}});
gsap.utils.toArray(".reveal").forEach(el=>gsap.to(el,{opacity:1,y:0,duration:1.05,ease:"power3.out",scrollTrigger:{trigger:el,start:"top 88%"}}));
} else document.querySelectorAll(".reveal").forEach(e=>{e.style.opacity=1;e.style.transform="none"});
addEventListener("scroll",()=>document.getElementById("nav").classList.toggle("scrolled",scrollY>30),{passive:true});
const dot=document.querySelector(".cursor"), ring=document.querySelector(".cursor-ring");
addEventListener("pointermove",e=>{dot.style.left=ring.style.left=e.clientX+"px";dot.style.top=ring.style.top=e.clientY+"px"});
document.querySelectorAll("a,button,.product").forEach(e=>{e.addEventListener("mouseenter",()=>document.body.classList.add("hovering"));e.addEventListener("mouseleave",()=>document.body.classList.remove("hovering"))});
document.querySelectorAll(".magnetic").forEach(el=>{el.addEventListener("mousemove",e=>{let r=el.getBoundingClientRect(),x=e.clientX-r.left-r.width/2,y=e.clientY-r.top-r.height/2;el.style.transform=`translate(${x*.13}px,${y*.13}px)`});el.addEventListener("mouseleave",()=>el.style.transform="")});
})();
</script>
</body>
</html>