Nexus Inovação | Superfície 3D :root { –color-primary: #00f0ff; –color-secondary: #a855f7; –color-bg: #03030c; } * { box-sizing: border-box; margin: 0; padding: 0; cursor: none !important; /* Oculta o cursor nativo em toda a página */ } body { background-color: var(–color-bg); background-image: radial-gradient(circle at 50% 50%, rgba(15, 7, 34, 0.6) 0%, rgba(3, 3, 12, 1) 100%), url(‘innovation_bg.png’); background-size: cover; background-position: center; background-repeat: no-repeat; min-height: 100vh; overflow: hidden; position: relative; } /* Custom Cursor */ .custom-cursor { width: 30px; height: 30px; border: 2px solid var(–color-primary); border-radius: 50%; position: fixed; transform: translate(-50%, -50%); pointer-events: none; z-index: 9999; transition: width 0.2s, height 0.2s, background-color 0.2s, border-color 0.2s; mix-blend-mode: difference; } .custom-cursor-dot { width: 6px; height: 6px; background-color: var(–color-primary); border-radius: 50%; position: fixed; transform: translate(-50%, -50%); pointer-events: none; z-index: 9999; } #webgl-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } /* Flash de luz branca transição */ #flash-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #ffffff; opacity: 0; pointer-events: none; z-index: 10000; transition: opacity 0.8s cubic-bezier(0.16, 1, 0.3, 1); } #flash-overlay.active { opacity: 1; }
// — 1. CONFIGURAÇÃO DO CURSOR PERSONALIZADO — const cursor = document.getElementById(‘custom-cursor’); const cursorDot = document.getElementById(‘custom-cursor-dot’); let mouseX = window.innerWidth / 2; let mouseY = window.innerHeight / 2; let cursorX = mouseX; let cursorY = mouseY; let dotX = mouseX; let dotY = mouseY; window.addEventListener(‘mousemove’, (e) => { mouseX = e.clientX; mouseY = e.clientY; }); function animateCursor() { cursorX += (mouseX – cursorX) * 0.15; cursorY += (mouseY – cursorY) * 0.15; cursor.style.left = `${cursorX}px`; cursor.style.top = `${cursorY}px`; dotX += (mouseX – dotX) * 0.35; dotY += (mouseY – dotY) * 0.35; cursorDot.style.left = `${dotX}px`; cursorDot.style.top = `${dotY}px`; requestAnimationFrame(animateCursor); } animateCursor(); // — 2. CONFIGURAÇÃO DA CENA THREE.JS — const canvas = document.getElementById(‘webgl-canvas’); const clock = new THREE.Clock(); const mouse3D = new THREE.Vector2(0, 0); window.addEventListener(‘mousemove’, (e) => { mouse3D.x = (e.clientX / window.innerWidth) * 2 – 1; mouse3D.y = -(e.clientY / window.innerHeight) * 2 + 1; }); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100); camera.position.z = 8; const renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true, alpha: true, powerPreference: “high-performance” }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.2; const textureLoader = new THREE.TextureLoader(); let bgMesh = null; let glassMesh = null; let mouseLight = null; let textMeshes = []; let isTransitioning = false; let activeTextures = {}; const raycaster = new THREE.Raycaster(); // Fallback/Geração de textura de fundo em caso de erro ou file:// function createProceduralTexture() { const canvas = document.createElement(‘canvas’); canvas.width = 1024; canvas.height = 1024; const ctx = canvas.getContext(‘2d’); const grad = ctx.createRadialGradient(512, 512, 50, 512, 512, 700); grad.addColorStop(0, ‘#12042b’); grad.addColorStop(0.4, ‘#030514’); grad.addColorStop(1, ‘#010103’); ctx.fillStyle = grad; ctx.fillRect(0, 0, 1024, 1024); ctx.strokeStyle = ‘rgba(0, 240, 255, 0.12)’; ctx.lineWidth = 1.5; const points = []; for (let i = 0; i 0.6 }); } ctx.beginPath(); for (let i = 0; i < points.length; i++) { for (let j = i + 1; j < points.length; j++) { const dx = points[i].x – points[j].x; const dy = points[i].y – points[j].y; const dist = Math.sqrt(dx*dx + dy*dy); if (dist < 260) { ctx.moveTo(points[i].x, points[i].y); ctx.lineTo(points[j].x, points[j].y); } } } ctx.stroke(); for (let i = 0; i 930 && fontSize > 40) { fontSize -= 4; ctx.font = `bold ${fontSize}px “Saira Extra Condensed”, sans-serif`; } // 1. Degradê de preenchimento (Azul Glacial Cristalino de alto contraste) const fillGrad = ctx.createLinearGradient(0, 30, 0, 220); fillGrad.addColorStop(0, ‘#ffffff’); // Brilho superior reflexivo fillGrad.addColorStop(0.35, ‘#cbf3ff’); // Azul gelo claro fillGrad.addColorStop(0.7, ‘#00ddff’); // Ciano neon brilhante fillGrad.addColorStop(1, ‘#004c99’); // Tom azul escuro profundo na base ctx.fillStyle = fillGrad; // Sombra suave interna para dar volume às letras individuais ctx.shadowColor = ‘rgba(0, 240, 255, 0.45)’; ctx.shadowBlur = 14; ctx.shadowOffsetY = 4; ctx.fillText(text, 512, 128); // Reseta sombra para manter os contornos das bordas nítidos ctx.shadowColor = ‘transparent’; ctx.shadowBlur = 0; ctx.shadowOffsetY = 0; // 2. Contorno do vidro (Simula chanfragem reflexiva) // Contorno ciano externo médio ctx.strokeStyle = ‘rgba(0, 240, 255, 0.45)’; ctx.lineWidth = 5; ctx.strokeText(text, 512, 128); // Contorno branco interno muito fino ctx.strokeStyle = ‘rgba(255, 255, 255, 0.8)’; ctx.lineWidth = 1.5; ctx.strokeText(text, 512, 128); const texture = new THREE.CanvasTexture(canvas); texture.minFilter = THREE.LinearFilter; texture.magFilter = THREE.LinearFilter; return texture; } // Carrega assets necessários (somente o background) function loadAssets(callback) { let loadedCount = 0; const totalAssets = 1; const assets = {}; function checkLoaded() { loadedCount++; if (loadedCount === totalAssets) { callback(assets); } } // Background const isLocalFile = window.location.protocol === ‘file:’; if (isLocalFile) { assets.bg = createProceduralTexture(); checkLoaded(); } else { textureLoader.load(‘innovation_bg.png’, (tex) => { assets.bg = tex; checkLoaded(); }, undefined, () => { assets.bg = createProceduralTexture(); checkLoaded(); } ); } } // Garante carregamento da fonte antes de iniciar o Three.js document.fonts.ready.then(() => { loadAssets((loadedAssets) => { activeTextures = loadedAssets; initScene(); }); }); // — 4. INICIALIZAÇÃO DOS ELEMENTOS DA CENA — let dirLight1, dirLight2, ambientLight; function initScene() { activeTextures.bg.wrapS = THREE.ClampToEdgeWrapping; activeTextures.bg.wrapT = THREE.ClampToEdgeWrapping; activeTextures.bg.minFilter = THREE.LinearFilter; // Fundo const bgGeometry = new THREE.PlaneGeometry(60, 40); const bgMaterial = new THREE.MeshBasicMaterial({ map: activeTextures.bg }); bgMesh = new THREE.Mesh(bgGeometry, bgMaterial); bgMesh.position.z = -5; scene.add(bgMesh); // ILUMINAÇÃO ambientLight = new THREE.AmbientLight(0x0e0e22, 1.8); scene.add(ambientLight); dirLight1 = new THREE.DirectionalLight(0x00f0ff, 2.5); dirLight1.position.set(-8, 8, 6); scene.add(dirLight1); dirLight2 = new THREE.DirectionalLight(0xa855f7, 2.0); dirLight2.position.set(8, -8, 6); scene.add(dirLight2); mouseLight = new THREE.PointLight(0xffffff, 5.0, 18); mouseLight.position.set(0, 0, 2.5); scene.add(mouseLight); // Dados de posicionamento e links das palavras (Coordenadas ajustadas para evitar sobreposição) const wordsData = [ { key: ‘parcerias’, text: ‘Parcerias’, url: ‘parcerias.html’, x: -4.0, y: 2.0, z: -2, baseScale: 1.45 }, { key: ‘empreender’, text: ‘Empreendedorismo’, url: ‘empreender.html’, x: 3.7, y: 1.8, z: -2.2, baseScale: 1.3 }, { key: ‘spei’, text: ‘SPEI’, url: null, x: 0.0, y: 0.0, z: -2.1, baseScale: 1.4 }, // Sem link! { key: ‘inovacao’, text: ‘Inovação’, url: ‘inovacao.html’, x: -3.7, y: -2.0, z: -2.2, baseScale: 1.5 }, { key: ‘noticias’, text: ‘Notícias’, url: ‘noticias.html’, x: 4.0, y: -1.8, z: -2, baseScale: 1.45 } ]; wordsData.forEach(data => { // Desenha a textura dos caracteres da palavra const texture = createWordTexture(data.text); // Proporção baseada no canvas da palavra (1024/256 = 4.0) const aspect = 4.0; // Alturas significativamente aumentadas para maior tamanho na tela let textHeight = 1.05; if (data.key === ‘empreender’) { textHeight = 1.4; // Proporção maior para “Empreendedorismo” não ficar comprimida } else if (data.key === ‘spei’) { textHeight = 1.2; // Altura ideal de destaque central para o SPEI } const geometry = new THREE.PlaneGeometry(aspect * textHeight, textHeight); // Material Frontal (Crispado, Degradê de alto contraste) const frontMaterial = new THREE.MeshPhysicalMaterial({ map: texture, transparent: true, opacity: 1.0, transmission: 0.0, // Sem transparência para manter contraste roughness: 0.08, metalness: 0.05, ior: 1.5, clearcoat: 1.0, clearcoatRoughness: 0.05, emissive: new THREE.Color(0x000000), emissiveIntensity: 0.0, depthWrite: false }); // Material da Extrusão (Cria profundidade escura nas laterais) const extrusionMaterial = new THREE.MeshPhysicalMaterial({ map: texture, transparent: true, opacity: 0.85, transmission: 0.0, color: new THREE.Color(0x1a3a66), roughness: 0.25, metalness: 0.1, clearcoat: 0.5, clearcoatRoughness: 0.2, emissive: new THREE.Color(0x000000), emissiveIntensity: 0.0, depthWrite: false }); // Empilhamento Volumétrico (26 camadas) para criar o corpo 3D dos caracteres const wordGroup = new THREE.Group(); wordGroup.position.set(data.x, data.y, data.z); const layersCount = 26; const layerSpacing = 0.024; // Espessura do volume tridimensional let frontMesh = null; for (let i = 0; i textureAspect) { bgTexture.repeat.set(1, textureAspect / screenAspect); bgTexture.offset.set(0, (1 – textureAspect / screenAspect) / 2); } else { bgTexture.repeat.set(screenAspect / textureAspect, 1); bgTexture.offset.set((1 – screenAspect / textureAspect) / 2, 0); } } } window.addEventListener(‘resize’, resize); // — 6. EVENTO DE CLIQUE (TRANSIÇÃO) — window.addEventListener(‘click’, () => { if (isTransitioning) return; raycaster.setFromCamera(mouse3D, camera); const intersects = raycaster.intersectObjects(textMeshes); if (intersects.length > 0) { const clickedMesh = intersects[0].object; // Se a palavra não tiver URL associada (como o SPEI central), ignore o clique if (!clickedMesh.targetUrl) return; isTransitioning = true; // Ativa flash branco CSS const flashOverlay = document.getElementById(‘flash-overlay’); flashOverlay.classList.add(‘active’); // Anima luzes e câmera const startT = clock.getElapsedTime(); function flashLoop() { const elapsed = clock.getElapsedTime() – startT; if (elapsed { window.location.href = clickedMesh.targetUrl; }, 800); } }); // — 7. LOOP DE ANIMAÇÃO (RENDER) — function animate() { requestAnimationFrame(animate); const time = clock.getElapsedTime(); // Interatividade do mouse na luz de ponto if (mouseLight && camera && !isTransitioning) { const vector = new THREE.Vector3(mouse3D.x, mouse3D.y, 0.5); vector.unproject(camera); const dir = vector.sub(camera.position).normalize(); const distance = -camera.position.z / dir.z; const targetPos = camera.position.clone().add(dir.multiplyScalar(distance)); mouseLight.position.x += (targetPos.x – mouseLight.position.x) * 0.12; mouseLight.position.y += (targetPos.y – mouseLight.position.y) * 0.12; mouseLight.position.z = 2.0; } // Raycasting para Hover if (!isTransitioning) { raycaster.setFromCamera(mouse3D, camera); const intersects = raycaster.intersectObjects(textMeshes); let hoveredObj = null; if (intersects.length > 0) { hoveredObj = intersects[0].object; } // Efeito no cursor personalizado (funciona também no SPEI, pois é interativo e oscila) if (hoveredObj) { cursor.style.width = ’60px’; cursor.style.height = ’60px’; // Se não for clicável (SPEI), usa uma cor de cursor diferente (ex: ciano original) em vez de roxo if (!hoveredObj.targetUrl) { cursor.style.borderColor = ‘var(–color-primary)’; cursor.style.backgroundColor = ‘rgba(0, 240, 255, 0.08)’; } else { cursor.style.borderColor = ‘var(–color-secondary)’; cursor.style.backgroundColor = ‘rgba(168, 85, 247, 0.12)’; } } else { cursor.style.width = ’30px’; cursor.style.height = ’30px’; cursor.style.borderColor = ‘var(–color-primary)’; cursor.style.backgroundColor = ‘transparent’; } // Interpolação suave de foco e emissivo textMeshes.forEach(mesh => { if (mesh === hoveredObj) { mesh.isHovered = true; // Valores Alvo para Hover (Brilho ciano forte na frente) mesh.targetEmissiveIntensity = 0.9; mesh.targetEmissiveColor = new THREE.Color(0x00f0ff); mesh.targetScale = mesh.scaleFactor * 1.12; } else { mesh.isHovered = false; // Valores Alvo Padrão (Sem brilho para manter contraste) mesh.targetEmissiveIntensity = 0.0; mesh.targetEmissiveColor = new THREE.Color(0x000000); mesh.targetScale = mesh.scaleFactor; } // Interpolação aplicada unicamente ao material da frente (frontMaterial) mesh.frontMaterial.emissiveIntensity += (mesh.targetEmissiveIntensity – mesh.frontMaterial.emissiveIntensity) * 0.15; mesh.frontMaterial.emissive.lerp(mesh.targetEmissiveColor, 0.15); mesh.frontMaterial.needsUpdate = true; // Escala tridimensional do grupo const group = mesh.wordGroup; const s = group.scale.x + (mesh.targetScale – group.scale.x) * 0.15; group.scale.set(s, s, 1); }); } // Flutuação 3D e rotação reativa com oscilação no hover textMeshes.forEach(mesh => { const group = mesh.wordGroup; group.position.y = mesh.initialY + Math.sin(time * 0.8 + mesh.phase) * 0.15; group.position.x = mesh.initialX + Math.cos(time * 0.4 + mesh.phase) * 0.1; group.rotation.z = Math.sin(time * 0.3 + mesh.phase) * 0.04; if (!isTransitioning) { // Interpolação suave do progresso do hover if (mesh.isHovered) { mesh.hoverProgress += (1.0 – mesh.hoverProgress) * 0.10; } else { mesh.hoverProgress += (0.0 – mesh.hoverProgress) * 0.10; } // Rotação padrão (acompanha o mouse estaticamente) const standardRotY = Math.sin(time * 0.1 + mesh.phase) * 0.05 + mouse3D.x * 0.32; const standardRotX = mouse3D.y * 0.25; // Rotação oscilante de hover (oscila de um lado para o outro revelando o volume 3D) const swingRotY = Math.sin(time * 5.5) * 0.30 + mouse3D.x * 0.15; const swingRotX = Math.cos(time * 3.5) * 0.12 + mouse3D.y * 0.15; // Mistura as rotações de forma suave usando lerp group.rotation.y = THREE.MathUtils.lerp(standardRotY, swingRotY, mesh.hoverProgress); group.rotation.x = THREE.MathUtils.lerp(standardRotX, swingRotX, mesh.hoverProgress); } }); // Deforma a malha de vidro frontal para gerar refrações if (glassMesh) { const geometry = glassMesh.geometry; const position = geometry.attributes.position; for (let i = 0; i < position.count; i++) { const x = position.getX(i); const y = position.getY(i); const z1 = Math.sin(x * 0.2 + time * 0.7) * Math.cos(y * 0.2 + time * 0.7) * 0.45; const z2 = Math.sin(x * 0.45 – time * 0.5) * Math.cos(y * 0.35 + time * 0.4) * 0.2; const z3 = Math.cos(x * 0.1 + y * 0.1 + time * 0.3) * 0.3; const dx = x – mouseLight.position.x; const dy = y – mouseLight.position.y; const dist = Math.sqrt(dx * dx + dy * dy); const mouseGlowEffect = Math.sin(dist * 0.7 – time * 2.5) * Math.exp(-dist * 0.12) * 0.35; position.setZ(i, z1 + z2 + z3 + mouseGlowEffect); } geometry.computeVertexNormals(); position.needsUpdate = true; glassMesh.rotation.y = Math.sin(time * 0.05) * 0.04; glassMesh.rotation.x = Math.cos(time * 0.05) * 0.02; } renderer.render(scene, camera); }