// Rotating 3D crystal gem — Three.js, brand-colored, with orbiting particles. const Gem = ({ height = 560, className = "" }) => { const mountRef = React.useRef(null); React.useEffect(() => { const mount = mountRef.current; if (!mount || typeof THREE === "undefined") return; const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches; let w = mount.clientWidth, h = mount.clientHeight; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, w / h, 0.1, 100); camera.position.set(0, 0, 7.2); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.setSize(w, h); mount.appendChild(renderer.domElement); const group = new THREE.Group(); scene.add(group); // --- core crystal: faceted icosahedron, flat-shaded glassy look --- const geo = new THREE.IcosahedronGeometry(1.85, 0); const mat = new THREE.MeshPhysicalMaterial({ color: new THREE.Color("#FF6B35"), metalness: 0.35, roughness: 0.18, flatShading: true, clearcoat: 1, clearcoatRoughness: 0.25, emissive: new THREE.Color("#E2521F"), emissiveIntensity: 0.12, }); const crystal = new THREE.Mesh(geo, mat); group.add(crystal); // --- wireframe overlay in accent --- const wireGeo = new THREE.IcosahedronGeometry(1.92, 0); const wire = new THREE.LineSegments( new THREE.EdgesGeometry(wireGeo), new THREE.LineBasicMaterial({ color: new THREE.Color("#00CEC9"), transparent: true, opacity: 0.55 }) ); group.add(wire); // --- inner glowing core --- const core = new THREE.Mesh( new THREE.IcosahedronGeometry(0.7, 1), new THREE.MeshBasicMaterial({ color: new THREE.Color("#5BE9E4"), transparent: true, opacity: 0.9 }) ); group.add(core); // --- orbiting ring of particles --- const N = 140; const pGeo = new THREE.BufferGeometry(); const pos = new Float32Array(N * 3); const radii = []; const speeds = []; const angles = []; for (let i = 0; i < N; i++) { const r = 2.7 + Math.random() * 1.9; const a = Math.random() * Math.PI * 2; const yj = (Math.random() - 0.5) * 1.6; radii.push(r); angles.push(a); speeds.push(0.1 + Math.random() * 0.25); pos[i*3] = Math.cos(a) * r; pos[i*3+1] = yj; pos[i*3+2] = Math.sin(a) * r; } pGeo.setAttribute("position", new THREE.BufferAttribute(pos, 3)); const particles = new THREE.Points(pGeo, new THREE.PointsMaterial({ color: new THREE.Color("#FF8757"), size: 0.05, transparent: true, opacity: 0.85, })); scene.add(particles); // --- lights --- scene.add(new THREE.AmbientLight(0xffffff, 0.5)); const key = new THREE.PointLight(0xff8757, 90, 60); key.position.set(6, 6, 8); scene.add(key); const fill = new THREE.PointLight(0x00cec9, 70, 60); fill.position.set(-7, -3, 5); scene.add(fill); const rim = new THREE.PointLight(0xffffff, 30, 40); rim.position.set(0, 8, -6); scene.add(rim); // --- pointer parallax --- let mx = 0, my = 0, tx = 0, ty = 0; const onMove = (e) => { const rect = mount.getBoundingClientRect(); tx = ((e.clientX - rect.left) / rect.width - 0.5) * 2; ty = ((e.clientY - rect.top) / rect.height - 0.5) * 2; }; window.addEventListener("pointermove", onMove); const clock = new THREE.Clock(); let frame; const animate = () => { const t = clock.getElapsedTime(); mx += (tx - mx) * 0.05; my += (ty - my) * 0.05; if (!reduced) { crystal.rotation.y = t * 0.35; crystal.rotation.x = Math.sin(t * 0.4) * 0.25; wire.rotation.copy(crystal.rotation); core.rotation.y = -t * 0.6; core.scale.setScalar(1 + Math.sin(t * 2) * 0.06); // particles orbit const p = pGeo.attributes.position.array; for (let i = 0; i < N; i++) { const a = angles[i] + t * speeds[i] * 0.4; p[i*3] = Math.cos(a) * radii[i]; p[i*3+2] = Math.sin(a) * radii[i]; } pGeo.attributes.position.needsUpdate = true; particles.rotation.x = 0.4; } group.rotation.y = mx * 0.4; group.rotation.x = my * 0.3; renderer.render(scene, camera); frame = requestAnimationFrame(animate); }; animate(); const onResize = () => { w = mount.clientWidth; h = mount.clientHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); }; window.addEventListener("resize", onResize); return () => { cancelAnimationFrame(frame); window.removeEventListener("pointermove", onMove); window.removeEventListener("resize", onResize); renderer.dispose(); geo.dispose(); mat.dispose(); if (renderer.domElement.parentNode) renderer.domElement.parentNode.removeChild(renderer.domElement); }; }, []); return (