/* =======================================================
AION — 3D WebGL Chronographic Experience Engine
======================================================= */
let scrollWatchScene, scrollWatchCamera, scrollWatchRenderer;
let watchGroup, caseMesh, bezelMesh, glassMesh, dialMesh, gearsGroup, powerCoil, backMesh, strapGroup;
let gear1, gear2, gear3, hourHand, minuteHand, secondHand;
let mouseX = 0;
let mouseY = 0;
document.addEventListener("DOMContentLoaded", () => {
initEntranceScreen();
initLenisScroll();
initWatch3DScene();
initGearsThree(); // Caliber specs section canvas
initHeroParallax();
initStackExplodedView();
initTechnicalHotspots();
initWatchConfigurator();
initContactFormSubmit();
// Handle cursor tracking for watch tilt
window.addEventListener("mousemove", (e) => {
mouseX = (e.clientX / window.innerWidth) - 0.5;
mouseY = (e.clientY / window.innerHeight) - 0.5;
});
});
/* =======================================================
1. Entrance Screen Dismissal
======================================================= */
function initEntranceScreen() {
const entryScreen = document.getElementById("entry-screen");
const btnEnter = document.getElementById("btn-enter-exp");
if (!entryScreen) return;
if (btnEnter) {
btnEnter.addEventListener("click", () => {
if (typeof gsap !== "undefined") {
gsap.to(entryScreen, {
y: "-100%",
duration: 1.4,
ease: "power4.inOut",
onComplete: () => {
entryScreen.style.display = "none";
// Wake up hero text
gsap.from("#hero .hero-title, #hero .hero-subheadline, #hero .hero-actions, #hero .hero-specs-row", {
y: 45,
opacity: 0,
stagger: 0.15,
duration: 1.2,
ease: "power3.out"
});
}
});
} else {
entryScreen.style.transition = "transform 0.8s ease-in-out";
entryScreen.style.transform = "translateY(-100%)";
setTimeout(() => {
entryScreen.style.display = "none";
}, 800);
}
});
}
}
/* =======================================================
2. Lenis Smooth Scroll
======================================================= */
function initLenisScroll() {
if (typeof Lenis === "undefined" || typeof gsap === "undefined") return;
const lenis = new Lenis({
duration: 1.4,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
smoothWheel: true,
});
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => {
lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
}
/* =======================================================
3. True 3D Watch WebGL Scene (Central Pinned Scroll)
======================================================= */
function initWatch3DScene() {
const canvas = document.getElementById("watch-3d-canvas");
if (!canvas) return;
// Fallback if Three.js failed to load
if (typeof THREE === "undefined") {
const frame = document.querySelector(".watch-3d-canvas-frame");
if (frame) {
frame.innerHTML = `
`;
}
return;
}
// Init Scene
scrollWatchScene = new THREE.Scene();
// Camera
scrollWatchCamera = new THREE.PerspectiveCamera(40, canvas.clientWidth / canvas.clientHeight, 0.1, 100);
scrollWatchCamera.position.set(0, 1.5, 12);
// Renderer
scrollWatchRenderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
scrollWatchRenderer.setSize(canvas.clientWidth, canvas.clientHeight);
scrollWatchRenderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
scrollWatchScene.add(ambientLight);
const dirLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
dirLight1.position.set(5, 10, 8);
scrollWatchScene.add(dirLight1);
const spotlight = new THREE.SpotLight(0xc9a86a, 2.5, 20, Math.PI / 6, 0.5, 1);
spotlight.position.set(-6, 8, 6);
scrollWatchScene.add(spotlight);
const fillLight = new THREE.PointLight(0x1b2a38, 1.5, 15);
fillLight.position.set(-4, -2, 4);
scrollWatchScene.add(fillLight);
// Group container
watchGroup = new THREE.Group();
scrollWatchScene.add(watchGroup);
// --- MODELING WATCH PARTS ---
// Materials
const steelMat = new THREE.MeshStandardMaterial({ color: 0xc9cdd2, roughness: 0.12, metalness: 0.95 });
const darkSteelMat = new THREE.MeshStandardMaterial({ color: 0x1a1c1e, roughness: 0.25, metalness: 0.92 });
const goldMat = new THREE.MeshStandardMaterial({ color: 0xc9a86a, roughness: 0.15, metalness: 0.88 });
const rubyMat = new THREE.MeshStandardMaterial({ color: 0xb33a31, roughness: 0.1, metalness: 0.3 });
const glassMat = new THREE.MeshPhysicalMaterial({
color: 0x44e5e7,
transparent: true,
opacity: 0.16,
roughness: 0.05,
transmission: 0.92,
ior: 1.5,
thickness: 0.15
});
// 1. Case Chassis
const caseGeo = new THREE.CylinderGeometry(3.6, 3.6, 1.1, 64);
caseMesh = new THREE.Mesh(caseGeo, darkSteelMat);
watchGroup.add(caseMesh);
// Crown
const crownGeo = new THREE.CylinderGeometry(0.4, 0.4, 0.6, 16);
watchCrown = new THREE.Mesh(crownGeo, steelMat);
watchCrown.rotation.z = Math.PI / 2;
watchCrown.position.set(3.8, 0, 0);
caseMesh.add(watchCrown);
// Lugs / Strap connectors
const lugGeo = new THREE.BoxGeometry(0.7, 0.4, 1.2);
const lugTopL = new THREE.Mesh(lugGeo, darkSteelMat); lugTopL.position.set(-1.8, 0, -3.8); caseMesh.add(lugTopL);
const lugTopR = new THREE.Mesh(lugGeo, darkSteelMat); lugTopR.position.set(1.8, 0, -3.8); caseMesh.add(lugTopR);
const lugBotL = new THREE.Mesh(lugGeo, darkSteelMat); lugBotL.position.set(-1.8, 0, 3.8); caseMesh.add(lugBotL);
const lugBotR = new THREE.Mesh(lugGeo, darkSteelMat); lugBotR.position.set(1.8, 0, 3.8); caseMesh.add(lugBotR);
// 2. Bezel Ring
const bezelGeo = new THREE.TorusGeometry(3.5, 0.16, 16, 64);
bezelMesh = new THREE.Mesh(bezelGeo, steelMat);
bezelMesh.rotation.x = Math.PI / 2;
bezelMesh.position.y = 0.58;
watchGroup.add(bezelMesh);
// 3. Sapphire Crystal (Glass disc)
const glassGeo = new THREE.CylinderGeometry(3.4, 3.4, 0.04, 32);
glassMesh = new THREE.Mesh(glassGeo, glassMat);
glassMesh.position.y = 0.68;
watchGroup.add(glassMesh);
// 4. Skeleton Dial
dialMesh = new THREE.Mesh(new THREE.CylinderGeometry(3.3, 3.3, 0.05, 32), new THREE.MeshStandardMaterial({ color: 0x050505, roughness: 0.5 }));
dialMesh.position.y = 0.38;
watchGroup.add(dialMesh);
// Dial golden index ticks
const tickGeo = new THREE.BoxGeometry(0.08, 0.06, 0.32);
for (let i = 0; i < 12; i++) {
const tick = new THREE.Mesh(tickGeo, goldMat);
const angle = (i / 12) * Math.PI * 2;
tick.position.set(Math.cos(angle) * 2.9, 0.04, Math.sin(angle) * 2.9);
tick.rotation.y = -angle + Math.PI / 2;
dialMesh.add(tick);
}
// 5. Caliber Gears (Mechanical Caliber Cal. 01)
gearsGroup = new THREE.Group();
gearsGroup.position.y = 0.08;
watchGroup.add(gearsGroup);
// Gear 1: Large Gold Wheel
const g1Group = new THREE.Group();
g1Group.position.set(-0.9, 0, 0.8);
gearsGroup.add(g1Group);
gear1 = new THREE.Mesh(new THREE.CylinderGeometry(1.2, 1.2, 0.12, 32), goldMat);
g1Group.add(gear1);
for (let i = 0; i < 16; i++) {
const t = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.15, 0.1), goldMat);
const angle = (i / 16) * Math.PI * 2;
t.position.set(Math.cos(angle) * 1.2, 0, Math.sin(angle) * 1.2);
t.rotation.y = -angle;
gear1.add(t);
}
// Gear 2: Steel Wheel
const g2Group = new THREE.Group();
g2Group.position.set(0.9, 0, -0.6);
gearsGroup.add(g2Group);
gear2 = new THREE.Mesh(new THREE.CylinderGeometry(0.85, 0.85, 0.12, 32), steelMat);
g2Group.add(gear2);
for (let i = 0; i < 12; i++) {
const t = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.15, 0.08), steelMat);
const angle = (i / 12) * Math.PI * 2;
t.position.set(Math.cos(angle) * 0.85, 0, Math.sin(angle) * 0.85);
t.rotation.y = -angle;
gear2.add(t);
}
// Gear 3: Red Escapement Wheel
const g3Group = new THREE.Group();
g3Group.position.set(0.1, 0, -1.3);
gearsGroup.add(g3Group);
gear3 = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.5, 0.1, 16), rubyMat);
g3Group.add(gear3);
// 6. Power Spiral Mainspring
powerCoil = new THREE.Mesh(new THREE.TorusKnotGeometry(0.7, 0.04, 100, 4, 3, 4), rubyMat);
powerCoil.rotation.x = Math.PI / 2;
powerCoil.position.y = -0.2;
watchGroup.add(powerCoil);
// 7. Case Back Plate
const backGeo = new THREE.CylinderGeometry(3.55, 3.55, 0.12, 64);
backMesh = new THREE.Mesh(backGeo, darkSteelMat);
backMesh.position.y = -0.62;
watchGroup.add(backMesh);
// 8. Strap Link Extensions
strapGroup = new THREE.Group();
watchGroup.add(strapGroup);
const strapGeo = new THREE.BoxGeometry(3.0, 0.15, 3.5);
const strapTop = new THREE.Mesh(strapGeo, steelMat); strapTop.position.set(0, 0, -5.2); strapGroup.add(strapTop);
const strapBot = new THREE.Mesh(strapGeo, steelMat); strapBot.position.set(0, 0, 5.2); strapGroup.add(strapBot);
// Hands Group (ticking on dial)
handsGroup = new THREE.Group();
handsGroup.position.y = 0.42;
watchGroup.add(handsGroup);
// Hour Hand (pivot at center)
hourHand = new THREE.Mesh(new THREE.BoxGeometry(0.14, 0.04, 1.6), steelMat);
hourHand.position.z = 0.8;
handsGroup.add(hourHand);
// Minute Hand
minuteHand = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.04, 2.4), goldMat);
minuteHand.position.z = 1.2;
handsGroup.add(minuteHand);
// Second Hand
secondHand = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.02, 2.7), rubyMat);
secondHand.position.z = 1.35;
handsGroup.add(secondHand);
// --- SCROLL ACTION TRIGGER ---
const steps = document.querySelectorAll(".story-step");
if (typeof ScrollTrigger !== "undefined") {
const tl3d = gsap.timeline({
scrollTrigger: {
trigger: "#scroll-story",
start: "top top",
end: `+=${(steps.length - 1) * 115}%`,
pin: true,
scrub: 1.2,
onUpdate: (self) => {
const p = self.progress;
// Target values
let glassY = 0.68;
let bezelY = 0.58;
let dialY = 0.38;
let handsY = 0.42;
let gearsY = 0.08;
let powerY = -0.2;
let backY = -0.62;
let strapZOffset = 0;
let globalRotY = 0;
let globalRotX = 0;
if (p < 0.15) {
// Step 1: Case rotation profile
const ratio = p / 0.15;
globalRotY = ratio * 1.3;
globalRotX = ratio * 0.4;
}
else if (p < 0.82) {
// Pushed states (Exploded watch)
globalRotY = 1.3;
globalRotX = 0.4;
const sep = Math.min(1.0, (p - 0.15) / 0.5); // 0 to 1
glassY = 0.68 + sep * 5.2;
bezelY = 0.58 + sep * 3.8;
dialY = 0.38 + sep * 2.2;
handsY = 0.42 + sep * 2.5;
gearsY = 0.08 - sep * 1.4;
powerY = -0.2 - sep * 3.0;
backY = -0.62 - sep * 4.6;
strapZOffset = sep * 1.8;
}
else {
// Step 6: Collapse / Reassemble Watch
const ratio = (1.0 - p) / 0.18; // 1 down to 0
globalRotY = ratio * 1.3;
globalRotX = ratio * 0.4;
glassY = 0.68 + ratio * 5.2;
bezelY = 0.58 + ratio * 3.8;
dialY = 0.38 + ratio * 2.2;
handsY = 0.42 + ratio * 2.5;
gearsY = 0.08 - ratio * 1.4;
powerY = -0.2 - ratio * 3.0;
backY = -0.62 - ratio * 4.6;
strapZOffset = ratio * 1.8;
}
// Apply values with smooth GSAP mapping
gsap.to(glassMesh.position, { y: glassY, duration: 0.1, overwrite: "auto" });
gsap.to(bezelMesh.position, { y: bezelY, duration: 0.1, overwrite: "auto" });
gsap.to(dialMesh.position, { y: dialY, duration: 0.1, overwrite: "auto" });
gsap.to(handsGroup.position, { y: handsY, duration: 0.1, overwrite: "auto" });
gsap.to(gearsGroup.position, { y: gearsY, duration: 0.1, overwrite: "auto" });
gsap.to(powerCoil.position, { y: powerY, duration: 0.1, overwrite: "auto" });
gsap.to(backMesh.position, { y: backY, duration: 0.1, overwrite: "auto" });
// Strap separation
gsap.to(strapTop.position, { z: -5.2 - strapZOffset, duration: 0.1, overwrite: "auto" });
gsap.to(strapBot.position, { z: 5.2 + strapZOffset, duration: 0.1, overwrite: "auto" });
// Watch base rotation mapping
gsap.to(watchGroup.rotation, {
y: globalRotY,
x: globalRotX,
duration: 0.15,
overwrite: "auto"
});
}
}
});
// Update active text steps
steps.forEach((step, i) => {
ScrollTrigger.create({
trigger: "#scroll-story",
start: () => `top+=${(i / steps.length) * window.innerHeight * 5 * 1.15} top`,
end: () => `top+=${((i + 0.8) / steps.length) * window.innerHeight * 5 * 1.15} top`,
onEnter: () => toggleStepActive(i),
onEnterBack: () => toggleStepActive(i)
});
});
}
function toggleStepActive(idx) {
steps.forEach((s, i) => s.classList.toggle("active", i === idx));
}
// Animation ticks loop
function tick() {
requestAnimationFrame(tick);
// Watch ticking (ticking gears)
if (gear1) gear1.rotation.y += 0.005;
if (gear2) gear2.rotation.y -= 0.007;
if (gear3) gear3.rotation.y += 0.015;
// Time ticking hands
if (hourHand) hourHand.rotation.y -= 0.00015;
if (minuteHand) minuteHand.rotation.y -= 0.0018;
if (secondHand) secondHand.rotation.y -= 0.01;
// Apply mouse parallax rotation
if (watchGroup && (typeof ScrollTrigger !== "undefined" && !ScrollTrigger.isScrolling())) {
watchGroup.rotation.y += (mouseX * 0.4 - watchGroup.rotation.y) * 0.05;
watchGroup.rotation.x += (mouseY * 0.2 - watchGroup.rotation.x) * 0.05;
}
scrollWatchRenderer.render(scrollWatchScene, scrollWatchCamera);
}
tick();
window.addEventListener("resize", () => {
if (!canvas.parentElement) return;
scrollWatchCamera.aspect = canvas.clientWidth / canvas.clientHeight;
scrollWatchCamera.updateProjectionMatrix();
scrollWatchRenderer.setSize(canvas.clientWidth, canvas.clientHeight);
});
}
/* =======================================================
4. Three.js Caliber Specs Gear Section (Right-Side Visual)
======================================================= */
function initGearsThree() {
if (typeof THREE === "undefined") return;
const canvas = document.getElementById("gears-three-canvas");
if (!canvas) return;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100);
camera.position.z = 18;
const renderer = new THREE.WebGLRenderer({ canvas: canvas, alpha: true, antialias: true });
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
// Lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.25);
scene.add(ambientLight);
const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.85);
dirLight1.position.set(5, 5, 8);
scene.add(dirLight1);
const dirLight2 = new THREE.DirectionalLight(0xc9a86a, 0.55);
dirLight2.position.set(-5, -5, 5);
scene.add(dirLight2);
const steelMat = new THREE.MeshStandardMaterial({ color: 0x8e9399, roughness: 0.2, metalness: 0.95 });
const goldMat = new THREE.MeshStandardMaterial({ color: 0xc9a86a, roughness: 0.15, metalness: 0.9 });
const rubyMat = new THREE.MeshStandardMaterial({ color: 0xb33a31, roughness: 0.1, metalness: 0.2, transparent: true, opacity: 0.85 });
const gearsGroup = new THREE.Group();
scene.add(gearsGroup);
function createCogWheel(radius, thickness, teethCount, material) {
const cogGroup = new THREE.Group();
const coreGeo = new THREE.CylinderGeometry(radius * 0.9, radius * 0.9, thickness, 32);
const coreMesh = new THREE.Mesh(coreGeo, material);
coreMesh.rotation.x = Math.PI / 2;
cogGroup.add(coreMesh);
const toothGeo = new THREE.BoxGeometry(radius * 0.18, thickness * 1.05, radius * 0.15);
for (let i = 0; i < teethCount; i++) {
const angle = (i / teethCount) * Math.PI * 2;
const tooth = new THREE.Mesh(toothGeo, material);
tooth.position.set(Math.cos(angle) * radius, Math.sin(angle) * radius, 0);
tooth.rotation.z = angle;
cogGroup.add(tooth);
}
const rubyGeo = new THREE.CylinderGeometry(radius * 0.18, radius * 0.18, thickness * 1.15, 16);
const ruby = new THREE.Mesh(rubyGeo, rubyMat);
ruby.rotation.x = Math.PI / 2;
cogGroup.add(ruby);
return cogGroup;
}
const gearGold = createCogWheel(3.2, 0.4, 20, goldMat);
gearGold.position.set(-2, 1, 0);
gearsGroup.add(gearGold);
const gearSteel = createCogWheel(2.2, 0.45, 14, steelMat);
gearSteel.position.set(2.8, -0.2, 0);
gearsGroup.add(gearSteel);
const gearRuby = createCogWheel(1.4, 0.35, 10, goldMat);
gearRuby.position.set(0.6, -2.6, 0);
gearsGroup.add(gearRuby);
function animate() {
requestAnimationFrame(animate);
gearGold.rotation.z += 0.006;
gearSteel.rotation.z -= (20 / 14) * 0.006;
gearRuby.rotation.z -= (20 / 10) * 0.006;
gearsGroup.rotation.y = Math.sin(Date.now() * 0.0005) * 0.15;
renderer.render(scene, camera);
}
animate();
window.addEventListener("resize", () => {
if (!canvas.parentElement) return;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
});
}
/* =======================================================
5. Hero Watch Mouse Parallax
======================================================= */
function initHeroParallax() {
const heroSec = document.getElementById("hero");
const watchFrame = document.getElementById("hero-watch-frame");
if (!heroSec || !watchFrame) return;
heroSec.addEventListener("mousemove", (e) => {
const rect = heroSec.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
if (typeof gsap !== "undefined") {
gsap.to(watchFrame, {
rotateY: x * 22,
rotateX: -y * 22,
translateX: x * 15,
translateY: y * 15,
duration: 0.6,
ease: "power2.out"
});
} else {
watchFrame.style.transform = `rotateY(${x * 22}deg) rotateX(${-y * 22}deg) translate(${x * 15}px, ${y * 15}px)`;
}
});
heroSec.addEventListener("mouseleave", () => {
if (typeof gsap !== "undefined") {
gsap.to(watchFrame, {
rotateX: 0,
rotateY: 0,
translateX: 0,
translateY: 0,
duration: 1,
ease: "power3.out"
});
} else {
watchFrame.style.transform = "none";
}
});
}
/* =======================================================
6. Exploded View Vertical Assembly Stack
======================================================= */
function initStackExplodedView() {
const layers = document.querySelectorAll(".exp-layer");
const specsBox = document.getElementById("exploded-specs-box");
const titleEl = document.getElementById("exp-spec-title");
const descEl = document.getElementById("exp-spec-desc");
const btnReassemble = document.getElementById("btn-reassemble-stack");
if (!layers.length) return;
layers.forEach(layer => {
layer.addEventListener("mouseenter", () => {
layers.forEach(l => l.classList.remove("active"));
layer.classList.add("active");
const title = layer.dataset.title;
const desc = layer.dataset.desc;
if (typeof gsap !== "undefined" && specsBox) {
gsap.to(specsBox, {
opacity: 0,
y: 8,
duration: 0.2,
onComplete: () => {
titleEl.textContent = title;
descEl.textContent = desc;
gsap.to(specsBox, { opacity: 1, y: 0, duration: 0.3 });
}
});
} else {
titleEl.textContent = title;
descEl.textContent = desc;
}
explodeStack(layer);
});
});
function explodeStack(activeLayer) {
const hoverIdx = Array.from(layers).indexOf(activeLayer);
layers.forEach((layer, idx) => {
let offset = 0;
if (idx < hoverIdx) {
offset = (idx - hoverIdx) * 35 - 30;
} else if (idx > hoverIdx) {
offset = (idx - hoverIdx) * 35 + 30;
} else {
offset = 0;
}
if (typeof gsap !== "undefined") {
gsap.to(layer, {
y: offset,
scale: idx === hoverIdx ? 1.08 : 0.95,
duration: 0.4,
ease: "power2.out",
overwrite: "auto"
});
} else {
layer.style.transform = `translateY(${offset}px) scale(${idx === hoverIdx ? 1.08 : 0.95})`;
}
});
}
if (btnReassemble) {
btnReassemble.addEventListener("click", () => {
layers.forEach((layer, idx) => {
const defaultOffset = idx * -10;
layers.forEach(l => l.classList.remove("active"));
if (typeof gsap !== "undefined") {
gsap.to(layer, {
y: defaultOffset,
scale: 1,
duration: 0.8,
ease: "power3.out"
});
} else {
layer.style.transform = `translateY(${defaultOffset}px) scale(1)`;
}
});
titleEl.textContent = "Select Component...";
descEl.textContent = "Hover over any layer in the assembly stack to examine its metallurgical and mechanical properties.";
});
}
}
/* =======================================================
7. Luminescent Hotspots
======================================================= */
function initTechnicalHotspots() {
const dots = document.querySelectorAll(".hotspot-dot");
const descCard = document.getElementById("hotspot-desc-card");
const titleEl = document.getElementById("hotspot-title");
const descEl = document.getElementById("hotspot-desc");
dots.forEach(dot => {
dot.addEventListener("mouseenter", () => {
const title = dot.dataset.title;
const desc = dot.dataset.desc;
if (typeof gsap !== "undefined" && descCard) {
gsap.to(descCard, {
opacity: 0,
y: 8,
duration: 0.25,
onComplete: () => {
titleEl.textContent = title;
descEl.textContent = desc;
gsap.to(descCard, { opacity: 1, y: 0, duration: 0.35 });
}
});
} else {
titleEl.textContent = title;
descEl.textContent = desc;
}
});
});
}
/* =======================================================
8. Material Explorer & Configurator
======================================================= */
function initWatchConfigurator() {
const buttons = document.querySelectorAll(".opt-btn");
const summaryTxt = document.getElementById("summary-text");
const saveBtn = document.getElementById("btn-save-config");
const watchStrapTop = document.getElementById("watch-strap-top");
const watchStrapBottom = document.getElementById("watch-strap-bottom");
const watchCase = document.getElementById("watch-case");
const watchCrown = document.getElementById("watch-crown");
const watchBezel = document.getElementById("watch-bezel");
const watchDial = document.getElementById("watch-dial");
const configState = {
case: "Brushed Steel",
dial: "Obsidian Black",
strap: "Steel Link"
};
buttons.forEach(btn => {
btn.addEventListener("click", () => {
const type = btn.dataset.type;
const value = btn.dataset.value;
btn.parentElement.querySelectorAll(".opt-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
if (type === "case") {
if (value === "brushed-steel") {
configState.case = "Brushed Steel";
setSVGAttribute([watchCase, watchCrown, watchBezel], "fill", "url(#metal-steel)");
} else if (value === "black-titanium") {
configState.case = "Black Titanium";
setSVGAttribute([watchCase, watchCrown, watchBezel], "fill", "url(#metal-titanium)");
} else if (value === "warm-bronze") {
configState.case = "Warm Bronze";
setSVGAttribute([watchCase, watchCrown, watchBezel], "fill", "url(#metal-bronze)");
}
}
if (type === "dial") {
if (value === "obsidian") {
configState.dial = "Obsidian Black";
setSVGAttribute(watchDial, "fill", "url(#dial-obsidian)");
} else if (value === "royal-blue") {
configState.dial = "Royal Blue";
setSVGAttribute(watchDial, "fill", "url(#dial-blue)");
} else if (value === "forest-green") {
configState.dial = "Forest Green";
setSVGAttribute(watchDial, "fill", "url(#dial-green)");
}
}
if (type === "strap") {
if (value === "steel-link") {
configState.strap = "Steel Link";
setSVGAttribute([watchStrapTop, watchStrapBottom], "fill", "url(#strap-steel)");
} else if (value === "black-leather") {
configState.strap = "Black Leather";
setSVGAttribute([watchStrapTop, watchStrapBottom], "fill", "url(#strap-black)");
} else if (value === "brown-leather") {
configState.strap = "Brown Leather";
setSVGAttribute([watchStrapTop, watchStrapBottom], "fill", "url(#strap-brown)");
}
}
if (summaryTxt) {
summaryTxt.textContent = `${configState.case} / ${configState.dial} / ${configState.strap}`;
}
});
});
function setSVGAttribute(elements, attr, value) {
if (!elements) return;
if (Array.isArray(elements)) {
elements.forEach(el => {
if (el) el.setAttribute(attr, value);
});
} else {
if (elements) elements.setAttribute(attr, value);
}
}
if (saveBtn) {
saveBtn.addEventListener("click", () => {
saveBtn.disabled = true;
saveBtn.textContent = "Saving Watch Configuration...";
setTimeout(() => {
saveBtn.disabled = false;
saveBtn.textContent = "✓ Configuration Saved";
const modal = document.createElement("div");
modal.className = "config-modal-overlay";
modal.innerHTML = `
Caja: ${configState.case}
Esfera: ${configState.dial}
Correa: ${configState.strap}
Recibirás los detalles de fabricación numerada en ${email}.