"use client"; import { motion } from "framer-motion"; import { useEffect, useRef } from "react"; import * as THREE from "three"; import type { Messages } from "@/messages"; export default function GlassesModelSection({ t }: { t: Messages }) { const canvasRef = useRef(null); const wrapRef = useRef(null); const pointerRef = useRef({ x: 0, y: 0 }); useEffect(() => { const canvas = canvasRef.current; const wrap = wrapRef.current; if (!canvas || !wrap) return; let frame = 0; let disposed = false; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(34, 1, 0.1, 100); camera.position.set(0, 0.2, 7.2); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: "high-performance" }); renderer.setClearColor(0x000000, 0); renderer.outputColorSpace = THREE.SRGBColorSpace; renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.08; const group = createGlassesModel(); scene.add(group); scene.add(new THREE.HemisphereLight(0xffffff, 0xaeb9c6, 1.8)); const key = new THREE.DirectionalLight(0xffffff, 3.8); key.position.set(3.4, 4.6, 5.2); scene.add(key); const rim = new THREE.DirectionalLight(0x9ec7ff, 1.7); rim.position.set(-4, 1.8, -2); scene.add(rim); const resize = () => { const { width, height } = wrap.getBoundingClientRect(); const pixelRatio = Math.min(window.devicePixelRatio || 1, 1.7); renderer.setPixelRatio(pixelRatio); renderer.setSize(width, height, false); camera.aspect = width / Math.max(height, 1); camera.updateProjectionMatrix(); }; const observer = new ResizeObserver(resize); observer.observe(wrap); resize(); const handlePointerMove = (event: PointerEvent) => { const bounds = wrap.getBoundingClientRect(); pointerRef.current.x = ((event.clientX - bounds.left) / bounds.width - 0.5) * 2; pointerRef.current.y = ((event.clientY - bounds.top) / bounds.height - 0.5) * 2; }; const handlePointerLeave = () => { pointerRef.current.x = 0; pointerRef.current.y = 0; }; wrap.addEventListener("pointermove", handlePointerMove); wrap.addEventListener("pointerleave", handlePointerLeave); const animate = () => { if (disposed) return; const targetY = pointerRef.current.x * 0.28; const targetX = pointerRef.current.y * 0.16; group.rotation.y += (targetY + Math.sin(performance.now() * 0.00045) * 0.22 - group.rotation.y) * 0.055; group.rotation.x += (-0.08 - targetX - group.rotation.x) * 0.055; group.rotation.z = Math.sin(performance.now() * 0.00062) * 0.025; renderer.render(scene, camera); frame = window.requestAnimationFrame(animate); }; animate(); return () => { disposed = true; window.cancelAnimationFrame(frame); observer.disconnect(); wrap.removeEventListener("pointermove", handlePointerMove); wrap.removeEventListener("pointerleave", handlePointerLeave); scene.traverse((object) => { if (!(object instanceof THREE.Mesh)) return; object.geometry.dispose(); const material = object.material; if (Array.isArray(material)) material.forEach((item) => item.dispose()); else material.dispose(); }); renderer.dispose(); }; }, []); return (

{t.model.eyebrow}

{t.model.title}

{t.model.body}

); } function Annotation({ label, body, align, className }: { label: string; body: string; align: "left" | "right"; className: string }) { return (
{align === "right" ? : null} {align === "left" ? : null}

{label}

{body}

); } function createGlassesModel() { const group = new THREE.Group(); group.scale.setScalar(1.08); const frameMaterial = new THREE.MeshPhysicalMaterial({ color: 0x161a20, roughness: 0.28, metalness: 0.55, clearcoat: 0.85, clearcoatRoughness: 0.18 }); const lensMaterial = new THREE.MeshPhysicalMaterial({ color: 0xbfd7e3, roughness: 0.12, metalness: 0, transmission: 0.48, transparent: true, opacity: 0.28, thickness: 0.12, side: THREE.DoubleSide }); const padMaterial = new THREE.MeshPhysicalMaterial({ color: 0xf8fbff, roughness: 0.18, transparent: true, opacity: 0.52 }); const accentMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.42 }); const ringGeometry = new THREE.TorusGeometry(1.04, 0.065, 18, 112); const lensGeometry = new THREE.CircleGeometry(0.93, 72); const hingeGeometry = new THREE.BoxGeometry(0.22, 0.22, 0.22); const padGeometry = new THREE.SphereGeometry(0.16, 24, 16); [-1.18, 1.18].forEach((xPosition) => { const ring = new THREE.Mesh(ringGeometry, frameMaterial); ring.position.x = xPosition; ring.scale.y = 0.68; group.add(ring); const lens = new THREE.Mesh(lensGeometry, lensMaterial); lens.position.set(xPosition, 0, -0.03); lens.scale.set(1, 0.66, 1); group.add(lens); const highlight = new THREE.Mesh(new THREE.PlaneGeometry(0.58, 0.045), accentMaterial); highlight.position.set(xPosition - 0.2, 0.34, 0.02); highlight.rotation.z = -0.22; group.add(highlight); const hinge = new THREE.Mesh(hingeGeometry, frameMaterial); hinge.position.set(xPosition > 0 ? 2.18 : -2.18, 0.03, -0.02); hinge.scale.set(0.55, 0.8, 0.62); group.add(hinge); }); const bridgeCurve = new THREE.CatmullRomCurve3([ new THREE.Vector3(-0.3, 0.08, 0), new THREE.Vector3(-0.12, 0.25, 0.04), new THREE.Vector3(0, 0.3, 0.05), new THREE.Vector3(0.12, 0.25, 0.04), new THREE.Vector3(0.3, 0.08, 0) ]); group.add(new THREE.Mesh(new THREE.TubeGeometry(bridgeCurve, 38, 0.052, 14, false), frameMaterial)); [-0.28, 0.28].forEach((xPosition) => { const pad = new THREE.Mesh(padGeometry, padMaterial); pad.position.set(xPosition, -0.22, 0.14); pad.scale.set(0.55, 0.9, 0.28); pad.rotation.z = xPosition > 0 ? -0.22 : 0.22; group.add(pad); }); const leftTemple = new THREE.CatmullRomCurve3([ new THREE.Vector3(-2.18, 0.03, -0.03), new THREE.Vector3(-2.75, 0.03, -0.72), new THREE.Vector3(-3.1, -0.04, -1.75), new THREE.Vector3(-2.84, -0.28, -2.42) ]); const rightTemple = new THREE.CatmullRomCurve3([ new THREE.Vector3(2.18, 0.03, -0.03), new THREE.Vector3(2.75, 0.03, -0.72), new THREE.Vector3(3.1, -0.04, -1.75), new THREE.Vector3(2.84, -0.28, -2.42) ]); group.add(new THREE.Mesh(new THREE.TubeGeometry(leftTemple, 54, 0.055, 14, false), frameMaterial)); group.add(new THREE.Mesh(new THREE.TubeGeometry(rightTemple, 54, 0.055, 14, false), frameMaterial)); const shadow = new THREE.Mesh( new THREE.CircleGeometry(2.6, 96), new THREE.MeshBasicMaterial({ color: 0x315f8f, transparent: true, opacity: 0.08, depthWrite: false }) ); shadow.position.set(0, -1.06, -0.92); shadow.scale.y = 0.18; group.add(shadow); group.rotation.set(-0.08, -0.2, 0); return group; }