index

<!DOCTYPE html>
<html lang=”pt-BR”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>Nexus Inovação | Superfície 3D</title>
<meta name=”description” content=”Superfície 3D interativa de vidro fluido com blocos flutuantes ultra-volumétricos de vidro cristalino 3D.”>

<!– Google Fonts –>
<link rel=”preconnect” href=”https://fonts.googleapis.com”>
<link rel=”preconnect” href=”https://fonts.gstatic.com” crossorigin>
<link href=”https://fonts.googleapis.com/css2?family=Saira+Extra+Condensed:wght@700;800&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap” rel=”stylesheet”>

<style>
: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(‘https://jandaiadosul.ufpr.br/spei/wp-content/uploads/sites/77/2026/06/innovation_bg.jpg’);
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;
}
</style>
</head>
<body>

<!– Cursores Personalizados –>
<div class=”custom-cursor” id=”custom-cursor”></div>
<div class=”custom-cursor-dot” id=”custom-cursor-dot”></div>

<!– Flash de Transição –>
<div id=”flash-overlay”></div>

<!– Canvas para renderizar a superfície 3D –>
<canvas id=”webgl-canvas”></canvas>

<!– Biblioteca Three.js –>
<script src=”https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js”></script>

<script>
// — 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;

// — 3. CRIANDO A TEXTURA DE FUNDO E O FALLBACK —
const textureLoader = new THREE.TextureLoader();
let bgTexture = null;
let bgMesh = null;
let glassMesh = null;
let mouseLight = null;
let textMeshes = [];
let isTransitioning = false;

const raycaster = new THREE.Raycaster();

// Proporção de texto
function getWordAspect(text) {
const length = text.length;
if (length > 15) return 2.4; // Empreendedorismo
if (length > 10) return 1.9;
return 1.3; // Parcerias, Inovação, Notícias
}

// Desenha templates de textura com fonte Saira Extra Condensed
function createTextTexture(text, isHovered = false) {
const canvas = document.createElement(‘canvas’);
canvas.width = 1024;
canvas.height = 256;
const ctx = canvas.getContext(‘2d’);

ctx.clearRect(0, 0, 1024, 256);

// Fonte condensada, tamanho ligeiramente menor para maior nitidez/refinamento
ctx.font = ‘bold 115px “Saira Extra Condensed”, sans-serif’;
ctx.textAlign = ‘center’;
ctx.textBaseline = ‘middle’;

// Sombra interna refrativa
const shadowColor = isHovered ? ‘rgba(0, 240, 255, 0.25)’ : ‘rgba(255, 255, 255, 0.1)’;
ctx.fillStyle = shadowColor;
ctx.fillText(text, 516, 132);

// Borda do vidro
ctx.strokeStyle = isHovered ? ‘#00f0ff’ : ‘rgba(255, 255, 255, 0.35)’;
ctx.lineWidth = 3;
ctx.strokeText(text, 512, 128);

// Corpo do vidro
ctx.fillStyle = isHovered ? ‘rgba(0, 240, 255, 0.15)’ : ‘rgba(255, 255, 255, 0.08)’;
ctx.fillText(text, 512, 128);

const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.LinearFilter;
return texture;
}

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 < 45; i++) {
points.push({
x: Math.random() * 1024,
y: Math.random() * 1024,
r: Math.random() * 3 + 1,
glow: Math.random() > 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 < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, points[i].r, 0, Math.PI * 2);
ctx.fillStyle = ‘rgba(0, 240, 255, 0.7)’;
ctx.fill();

if (points[i].glow) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, points[i].r * 4, 0, Math.PI * 2);
const nodeGrad = ctx.createRadialGradient(points[i].x, points[i].y, points[i].r, points[i].x, points[i].y, points[i].r * 4);
nodeGrad.addColorStop(0, ‘rgba(0, 240, 255, 0.3)’);
nodeGrad.addColorStop(1, ‘rgba(0, 240, 255, 0)’);
ctx.fillStyle = nodeGrad;
ctx.fill();
}
}

const grad2 = ctx.createLinearGradient(0, 0, 1024, 1024);
grad2.addColorStop(0.2, ‘rgba(168, 85, 247, 0.0)’);
grad2.addColorStop(0.5, ‘rgba(168, 85, 247, 0.18)’);
grad2.addColorStop(0.8, ‘rgba(0, 240, 255, 0.12)’);
grad2.addColorStop(1, ‘rgba(0, 240, 255, 0.0)’);
ctx.fillStyle = grad2;
ctx.fillRect(0, 0, 1024, 1024);

return new THREE.CanvasTexture(canvas);
}

// Garante carregamento da fonte antes de iniciar o Three.js
document.fonts.ready.then(() => {
const isLocalFile = window.location.protocol === ‘file:’;
if (isLocalFile) {
console.warn(“Servindo via file://. Usando fallback de textura procedural para evitar erros de CORS.”);
const pTexture = createProceduralTexture();
initScene(pTexture);
} else {
textureLoader.load(
‘innovation_bg.png’,
(loadedTexture) => {
initScene(loadedTexture);
},
undefined,
(err) => {
console.error(“Erro ao carregar imagem, usando fallback de textura procedural.”, err);
const pTexture = createProceduralTexture();
initScene(pTexture);
}
);
}
});

// — 4. INICIALIZAÇÃO DOS ELEMENTOS DA CENA —
let dirLight1, dirLight2, ambientLight;

function initScene(texture) {
bgTexture = texture;
bgTexture.wrapS = THREE.ClampToEdgeWrapping;
bgTexture.wrapT = THREE.ClampToEdgeWrapping;
bgTexture.minFilter = THREE.LinearFilter;

// Fundo
const bgGeometry = new THREE.PlaneGeometry(60, 40);
const bgMaterial = new THREE.MeshBasicMaterial({ map: bgTexture });
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 das palavras flutuantes
// baseScale reduzido levemente para fonte menor conforme solicitado
const wordsData = [
{ text: “Parcerias”, url: “parcerias.html”, x: -3.8, y: 1.8, z: -2, baseScale: 1.45 },
{ text: “Empreendedorismo”, url: “empreender.html”, x: 3.5, y: 1.6, z: -2.2, baseScale: 1.3 },
{ text: “Inovação”, url: “inovacao.html”, x: -3.4, y: -1.8, z: -2.2, baseScale: 1.5 },
{ text: “Notícias”, url: “noticias.html”, x: 3.8, y: -1.6, z: -2, baseScale: 1.45 }
];

wordsData.forEach(data => {
const normTex = createTextTexture(data.text, false);
const hovTex = createTextTexture(data.text, true);

const aspect = getWordAspect(data.text);
const geometry = new THREE.PlaneGeometry(aspect * 2.2, 2.2);

// Material físico de vidro compartilhado pelas camadas da palavra
const wordMaterial = new THREE.MeshPhysicalMaterial({
map: normTex,
transparent: true,
opacity: 1.0,
transmission: 0.92, // Vidro puro cristalino
roughness: 0.05, // Superfície polida
metalness: 0.1,
ior: 1.52, // IOR de vidro crown
thickness: 2.5, // Refração física
clearcoat: 1.0,
clearcoatRoughness: 0.05,
emissive: new THREE.Color(0xa855f7),
emissiveIntensity: 0.4,
depthWrite: false
});

// Criamos um Grupo para empilhar camadas (Volumetric Stacked Layers)
const wordGroup = new THREE.Group();
wordGroup.position.set(data.x, data.y, data.z);

// AUMENTADO O VOLUME 3D:
// Aumentamos o número de camadas para 26 e mantivemos o espaçamento curto (0.022)
// Isso cria uma extrusão massiva de 0.572 unidades de espessura sem vãos visíveis!
const layersCount = 26;
const layerSpacing = 0.022;

let frontMesh = null;
for (let i = 0; i < layersCount; i++) {
const layerMesh = new THREE.Mesh(geometry, wordMaterial);
layerMesh.position.z = -i * layerSpacing;
wordGroup.add(layerMesh);

if (i === 0) {
frontMesh = layerMesh;
}
}

// Metadados anexados ao frontMesh
frontMesh.wordGroup = wordGroup;
frontMesh.wordMaterial = wordMaterial;
frontMesh.targetUrl = data.url;
frontMesh.normalTexture = normTex;
frontMesh.hoverTexture = hovTex;
frontMesh.isHovered = false;
frontMesh.scaleFactor = data.baseScale;

frontMesh.initialX = data.x;
frontMesh.initialY = data.y;
frontMesh.phase = Math.random() * Math.PI * 2;

scene.add(wordGroup);
textMeshes.push(frontMesh);
});

// Criar a superfície de vidro 3D ondulada por cima dos textos
const glassGeometry = new THREE.PlaneGeometry(32, 20, 95, 95);
const glassMaterial = new THREE.MeshPhysicalMaterial({
color: 0xffffff,
transparent: true,
transmission: 0.95,
opacity: 1.0,
roughness: 0.12,
metalness: 0.05,
ior: 1.5,
thickness: 1.5,
specularIntensity: 1.2,
specularColor: 0xffffff,
clearcoat: 1.0,
clearcoatRoughness: 0.08,
side: THREE.DoubleSide
});

glassMesh = new THREE.Mesh(glassGeometry, glassMaterial);
glassMesh.position.z = 0;
scene.add(glassMesh);

resize();
animate();
}

// — 5. REDIMENSIONAMENTO —
function resize() {
if (!bgMesh) return;

const width = window.innerWidth;
const height = window.innerHeight;

camera.aspect = width / height;
camera.updateProjectionMatrix();
renderer.setSize(width, height);

const distance = camera.position.z – bgMesh.position.z;
const vFov = (camera.fov * Math.PI) / 180;
const planeHeight = 2 * Math.tan(vFov / 2) * distance;
const planeWidth = planeHeight * camera.aspect;

bgMesh.scale.set(planeWidth / 60, planeHeight / 40, 1);

if (bgTexture && bgTexture.image) {
const textureAspect = bgTexture.image.width / bgTexture.image.height;
const screenAspect = camera.aspect;

if (screenAspect > 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;
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 < 0.8) {
// Explode fontes WebGL
if (mouseLight) mouseLight.intensity += 12.0;
if (dirLight1) dirLight1.intensity += 6.0;
if (dirLight2) dirLight2.intensity += 6.0;

// Explode o brilho emissivo do material da palavra clicada
clickedMesh.wordMaterial.emissiveIntensity += 1.5;
clickedMesh.wordMaterial.emissive.lerp(new THREE.Color(0xffffff), 0.25);

// Zoom na palavra
camera.position.x += (clickedMesh.wordGroup.position.x – camera.position.x) * 0.15;
camera.position.y += (clickedMesh.wordGroup.position.y – camera.position.y) * 0.15;
camera.position.z -= 0.12;

requestAnimationFrame(flashLoop);
}
}
flashLoop();

setTimeout(() => {
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
if (hoveredObj) {
cursor.style.width = ’60px’;
cursor.style.height = ’60px’;
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 para o material físico de vidro dos textos (Lerp)
textMeshes.forEach(mesh => {
if (mesh === hoveredObj) {
mesh.isHovered = true;
mesh.wordMaterial.map = mesh.hoverTexture;

// Valores Alvo para Hover
mesh.targetEmissiveIntensity = 1.4;
mesh.targetEmissiveColor = new THREE.Color(0x00f0ff); // Vidro ciano elétrico
mesh.targetScale = mesh.scaleFactor * 1.12;
mesh.wordMaterial.roughness = 0.02;
} else {
mesh.isHovered = false;
mesh.wordMaterial.map = mesh.normalTexture;

// Valores Alvo Padrão (Vidro cristalino lilás discreto)
mesh.targetEmissiveIntensity = 0.4;
mesh.targetEmissiveColor = new THREE.Color(0xa855f7);
mesh.targetScale = mesh.scaleFactor;
mesh.wordMaterial.roughness = 0.05;
}

// Aplica interpolação linear suave no material da palavra
mesh.wordMaterial.emissiveIntensity += (mesh.targetEmissiveIntensity – mesh.wordMaterial.emissiveIntensity) * 0.15;
mesh.wordMaterial.emissive.lerp(mesh.targetEmissiveColor, 0.15);
mesh.wordMaterial.needsUpdate = true;

// Escala 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 direcionada ao cursor (Acentua a profundidade das laterais do vidro)
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) {
// Limites de rotação aumentados ligeiramente para enfatizar a espessura de 26 camadas
group.rotation.y = Math.sin(time * 0.1 + mesh.phase) * 0.05 + mouse3D.x * 0.32;
group.rotation.x = mouse3D.y * 0.25;
}
});

// 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);
}
</script>
</body>
</html>