'use client'; import { useEffect, useRef } from 'react'; import * as THREE from 'three'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; interface ThreeLogoProps { className?: string; } export default function ThreeLogo({ className = "" }: ThreeLogoProps) { const containerRef = useRef(null); useEffect(() => { if (!containerRef.current) return; const container = containerRef.current; // Scene / Renderer / Camera const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100); camera.position.set(0, 0, 3.0); // Move camera back to prevent clipping camera.lookAt(0, 0, 0); // Ensure camera looks at exact center const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setClearColor(0x000000, 0); // transparent background container.appendChild(renderer.domElement); // Resize handling with proper aspect ratio const resize = () => { const { clientWidth, clientHeight } = container; // Ensure square aspect ratio for the logo const size = Math.min(clientWidth, clientHeight); renderer.setSize(size, size, false); camera.aspect = 1; // Always square camera.updateProjectionMatrix(); }; resize(); window.addEventListener('resize', resize); // Exact lighting setup from your reference logo.html const light = new THREE.PointLight(0xffffff, 1.4); light.position.set(0, 4, 1); scene.add(light); const bottomLight = new THREE.PointLight(0x800080, 1.2, 12); bottomLight.position.set(0, -4, 1); scene.add(bottomLight); const leftLight = new THREE.PointLight(0x808000, 1.45, 5); leftLight.position.set(-5, 0, 4); scene.add(leftLight); scene.add(new THREE.AmbientLight(0xffffff, 0.45)); // Load environment map from your horizon gradient image const expandGradient = (img: HTMLImageElement) => { const canvas = document.createElement('canvas'); const w = 512, h = 256; // safe HDRI-like size canvas.width = w; canvas.height = h; const ctx = canvas.getContext('2d')!; // Stretch the narrow gradient strip to fill the canvas ctx.drawImage(img, 0, 0, w, h); return new THREE.CanvasTexture(canvas); }; const envLoader = new THREE.ImageLoader(); envLoader.load('/logos/horizon-gradient.png', (image) => { const tex = expandGradient(image); tex.mapping = THREE.EquirectangularReflectionMapping; // Generate proper environment map using PMREM const pmrem = new THREE.PMREMGenerator(renderer); const envMap = pmrem.fromEquirectangular(tex).texture; scene.environment = envMap; pmrem.dispose(); }); scene.background = null; // keep transparent // Theme-aware material colors const getThemeMaterial = (theme: string = 'default') => { const colorMap = { 'default': 0x333333, 'protanopia': 0x1e40af, // Blue-800 for red-blind 'deuteranopia': 0x6b21a8, // Purple-800 for green-blind 'tritanopia': 0x991b1b, // Red-800 for blue-blind 'achromatopsia': 0x374151, // Gray-700 for color-blind }; return new THREE.MeshPhysicalMaterial({ color: colorMap[theme as keyof typeof colorMap] || colorMap.default, roughness: 0.24, metalness: 1.0, clearcoat: 0.48, clearcoatRoughness: 0.15, reflectivity: 1.2, sheen: 0.35, sheenColor: new THREE.Color(0x212121), sheenRoughness: 0.168, envMapIntensity: 1, }); }; // Initialize with default material let currentMaterial = getThemeMaterial('default'); // Load GLB with DRACO support const loader = new GLTFLoader(); const draco = new DRACOLoader(); draco.setDecoderPath('/draco/'); loader.setDRACOLoader(draco); let model: THREE.Object3D | null = null; console.log('Loading your mobius-ring.glb...'); loader.load( '/logos/mobius-ring.glb', (gltf) => { console.log('🎉 Your GLB loaded successfully!', gltf); model = gltf.scene; // Apply the theme-aware material model.traverse((child) => { if ((child as THREE.Mesh).isMesh) { (child as THREE.Mesh).material = currentMaterial; } }); // Center the model exactly at origin const box = new THREE.Box3().setFromObject(model); const center = box.getCenter(new THREE.Vector3()); model.position.sub(center); // Move model so its center is at (0,0,0) scene.add(model); console.log('🎯 Your Möbius GLB model added to scene and centered at origin'); }, (progress) => { if (progress.total > 0) { const percent = Math.round(progress.loaded / progress.total * 100); console.log(`GLB loading progress: ${percent}% (${progress.loaded}/${progress.total} bytes)`); } }, (err) => { console.error('❌ GLB load error:', err); // Fallback: create a torus geometry with the theme-aware material console.log('Creating fallback torus geometry...'); const fallbackGeometry = new THREE.TorusGeometry(0.6, 0.2, 16, 100); const fallbackMesh = new THREE.Mesh(fallbackGeometry, currentMaterial); fallbackMesh.position.set(0, 0, 0); // Ensure fallback is also centered scene.add(fallbackMesh); model = fallbackMesh; console.log('⚠️ Fallback torus geometry created (placeholder for your GLB)'); } ); // Listen for accessibility theme changes const handleThemeChange = (event: any) => { const newTheme = event.detail.theme; const newMaterial = getThemeMaterial(newTheme); if (model) { // Update material on all meshes model.traverse((child: any) => { if (child.isMesh) { child.material.dispose(); // Clean up old material child.material = newMaterial; } }); } // Update current material reference currentMaterial = newMaterial; console.log(`🎨 Logo theme changed to: ${newTheme}`); }; window.addEventListener('accessibilityThemeChanged', handleThemeChange); let raf = 0; const tick = () => { raf = requestAnimationFrame(tick); if (model) { // Use exact rotation parameters from your reference logo.html model.rotation.x += 0.010; // spinSpeedX from params model.rotation.y += -0.010; // spinSpeedY from params model.rotation.z += -0.1; // spinSpeedZ from params } renderer.render(scene, camera); }; tick(); // Cleanup return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', resize); window.removeEventListener('accessibilityThemeChanged', handleThemeChange); renderer.dispose(); draco.dispose(); if (container.contains(renderer.domElement)) { container.removeChild(renderer.domElement); } scene.traverse((obj: any) => { if (obj.geometry) obj.geometry.dispose?.(); if (obj.material) { const mats = Array.isArray(obj.material) ? obj.material : [obj.material]; mats.forEach((m: any) => m.dispose?.()); } }); }; }, []); return (
); }