Skip to content

Commit 9c65ec0

Browse files
committed
fix: make Hero3D resilient with WebGL fallback and relative asset base
1 parent 3fa7d6d commit 9c65ec0

2 files changed

Lines changed: 164 additions & 165 deletions

File tree

src/components/Hero3D.jsx

Lines changed: 163 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -8,175 +8,174 @@ export default function Hero3D() {
88
const container = containerRef.current;
99
if (!container) return;
1010

11-
// Scene setup
12-
const scene = new THREE.Scene();
13-
const camera = new THREE.PerspectiveCamera(
14-
60,
15-
container.clientWidth / container.clientHeight,
16-
0.1,
17-
1000
18-
);
19-
camera.position.z = 35;
20-
21-
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
22-
renderer.setSize(container.clientWidth, container.clientHeight);
23-
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
24-
container.appendChild(renderer.domElement);
25-
26-
// Group for entire interactive network
27-
const networkGroup = new THREE.Group();
28-
scene.add(networkGroup);
29-
30-
// Create central security node (Icosahedron wireframe + core)
31-
const coreGeometry = new THREE.IcosahedronGeometry(6.5, 1);
32-
const coreMaterial = new THREE.MeshBasicMaterial({
33-
color: 0x0ea5e9,
34-
wireframe: true,
35-
transparent: true,
36-
opacity: 0.35,
37-
});
38-
const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
39-
networkGroup.add(coreMesh);
40-
41-
// Inner glowing sphere
42-
const innerGeo = new THREE.SphereGeometry(3.5, 16, 16);
43-
const innerMat = new THREE.MeshBasicMaterial({
44-
color: 0x38bdf8,
45-
wireframe: false,
46-
transparent: true,
47-
opacity: 0.25,
48-
});
49-
const innerMesh = new THREE.Mesh(innerGeo, innerMat);
50-
networkGroup.add(innerMesh);
51-
52-
// Outer orbiting nodes / particles
53-
const particleCount = 75;
54-
const nodePositions = [];
55-
const nodeGeometry = new THREE.BufferGeometry();
56-
const positions = new Float32Array(particleCount * 3);
57-
const colors = new Float32Array(particleCount * 3);
58-
59-
const color1 = new THREE.Color(0x38bdf8); // Sky blue
60-
const color2 = new THREE.Color(0x818cf8); // Indigo
61-
const color3 = new THREE.Color(0x2dd4bf); // Teal
62-
63-
for (let i = 0; i < particleCount; i++) {
64-
const radius = 12 + Math.random() * 18;
65-
const theta = Math.random() * Math.PI * 2;
66-
const phi = Math.acos(Math.random() * 2 - 1);
67-
68-
const x = radius * Math.sin(phi) * Math.cos(theta);
69-
const y = radius * Math.sin(phi) * Math.sin(theta);
70-
const z = radius * Math.cos(phi);
71-
72-
positions[i * 3] = x;
73-
positions[i * 3 + 1] = y;
74-
positions[i * 3 + 2] = z;
75-
76-
nodePositions.push(new THREE.Vector3(x, y, z));
77-
78-
const mixedColor = i % 3 === 0 ? color1 : i % 3 === 1 ? color2 : color3;
79-
colors[i * 3] = mixedColor.r;
80-
colors[i * 3 + 1] = mixedColor.g;
81-
colors[i * 3 + 2] = mixedColor.b;
82-
}
11+
let renderer, scene, camera, animationFrameId;
12+
let clock = new THREE.Clock();
8313

84-
nodeGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
85-
nodeGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
86-
87-
const particleMaterial = new THREE.PointsMaterial({
88-
size: 0.9,
89-
vertexColors: true,
90-
transparent: true,
91-
opacity: 0.85,
92-
});
93-
94-
const particles = new THREE.Points(nodeGeometry, particleMaterial);
95-
networkGroup.add(particles);
96-
97-
// Connecting lines between nearby nodes
98-
const lineMaterial = new THREE.LineBasicMaterial({
99-
color: 0x38bdf8,
100-
transparent: true,
101-
opacity: 0.15,
102-
});
103-
104-
const linePositions = [];
105-
for (let i = 0; i < nodePositions.length; i++) {
106-
for (let j = i + 1; j < nodePositions.length; j++) {
107-
const dist = nodePositions[i].distanceTo(nodePositions[j]);
108-
if (dist < 10) {
109-
linePositions.push(
110-
nodePositions[i].x, nodePositions[i].y, nodePositions[i].z,
111-
nodePositions[j].x, nodePositions[j].y, nodePositions[j].z
112-
);
14+
try {
15+
const width = container.clientWidth || window.innerWidth || 800;
16+
const height = container.clientHeight || window.innerHeight || 600;
17+
18+
scene = new THREE.Scene();
19+
camera = new THREE.PerspectiveCamera(60, width / height, 0.1, 1000);
20+
camera.position.z = 35;
21+
22+
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
23+
renderer.setSize(width, height);
24+
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
25+
container.appendChild(renderer.domElement);
26+
27+
const networkGroup = new THREE.Group();
28+
scene.add(networkGroup);
29+
30+
// Core wireframe
31+
const coreGeometry = new THREE.IcosahedronGeometry(6.5, 1);
32+
const coreMaterial = new THREE.MeshBasicMaterial({
33+
color: 0x0ea5e9,
34+
wireframe: true,
35+
transparent: true,
36+
opacity: 0.35,
37+
});
38+
const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
39+
networkGroup.add(coreMesh);
40+
41+
// Inner glowing sphere
42+
const innerGeo = new THREE.SphereGeometry(3.5, 16, 16);
43+
const innerMat = new THREE.MeshBasicMaterial({
44+
color: 0x38bdf8,
45+
wireframe: false,
46+
transparent: true,
47+
opacity: 0.25,
48+
});
49+
const innerMesh = new THREE.Mesh(innerGeo, innerMat);
50+
networkGroup.add(innerMesh);
51+
52+
// Particles
53+
const particleCount = 65;
54+
const nodePositions = [];
55+
const nodeGeometry = new THREE.BufferGeometry();
56+
const positions = new Float32Array(particleCount * 3);
57+
const colors = new Float32Array(particleCount * 3);
58+
59+
const color1 = new THREE.Color(0x38bdf8);
60+
const color2 = new THREE.Color(0x818cf8);
61+
const color3 = new THREE.Color(0x2dd4bf);
62+
63+
for (let i = 0; i < particleCount; i++) {
64+
const radius = 12 + Math.random() * 16;
65+
const theta = Math.random() * Math.PI * 2;
66+
const phi = Math.acos(Math.random() * 2 - 1);
67+
68+
const x = radius * Math.sin(phi) * Math.cos(theta);
69+
const y = radius * Math.sin(phi) * Math.sin(theta);
70+
const z = radius * Math.cos(phi);
71+
72+
positions[i * 3] = x;
73+
positions[i * 3 + 1] = y;
74+
positions[i * 3 + 2] = z;
75+
76+
nodePositions.push(new THREE.Vector3(x, y, z));
77+
78+
const mixedColor = i % 3 === 0 ? color1 : i % 3 === 1 ? color2 : color3;
79+
colors[i * 3] = mixedColor.r;
80+
colors[i * 3 + 1] = mixedColor.g;
81+
colors[i * 3 + 2] = mixedColor.b;
82+
}
83+
84+
nodeGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
85+
nodeGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
86+
87+
const particleMaterial = new THREE.PointsMaterial({
88+
size: 0.9,
89+
vertexColors: true,
90+
transparent: true,
91+
opacity: 0.85,
92+
});
93+
94+
const particles = new THREE.Points(nodeGeometry, particleMaterial);
95+
networkGroup.add(particles);
96+
97+
// Connecting lines
98+
const linePositions = [];
99+
for (let i = 0; i < nodePositions.length; i++) {
100+
for (let j = i + 1; j < nodePositions.length; j++) {
101+
const dist = nodePositions[i].distanceTo(nodePositions[j]);
102+
if (dist < 10) {
103+
linePositions.push(
104+
nodePositions[i].x, nodePositions[i].y, nodePositions[i].z,
105+
nodePositions[j].x, nodePositions[j].y, nodePositions[j].z
106+
);
107+
}
113108
}
114109
}
115-
}
116110

117-
const lineGeometry = new THREE.BufferGeometry();
118-
lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(linePositions, 3));
119-
const lines = new THREE.LineSegments(lineGeometry, lineMaterial);
120-
networkGroup.add(lines);
121-
122-
// Mouse parallax
123-
let mouseX = 0;
124-
let mouseY = 0;
125-
let targetX = 0;
126-
let targetY = 0;
127-
128-
const handleMouseMove = (e) => {
129-
const rect = container.getBoundingClientRect();
130-
mouseX = ((e.clientX - rect.left) / rect.width) * 2 - 1;
131-
mouseY = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
132-
};
133-
134-
window.addEventListener('mousemove', handleMouseMove);
135-
136-
// Responsive resize handler
137-
const handleResize = () => {
138-
if (!container) return;
139-
camera.aspect = container.clientWidth / container.clientHeight;
140-
camera.updateProjectionMatrix();
141-
renderer.setSize(container.clientWidth, container.clientHeight);
142-
};
143-
144-
window.addEventListener('resize', handleResize);
145-
146-
// Animation loop
147-
let animationFrameId;
148-
let clock = new THREE.Clock();
111+
const lineGeometry = new THREE.BufferGeometry();
112+
lineGeometry.setAttribute('position', new THREE.Float32BufferAttribute(linePositions, 3));
113+
const lineMaterial = new THREE.LineBasicMaterial({
114+
color: 0x38bdf8,
115+
transparent: true,
116+
opacity: 0.15,
117+
});
118+
const lines = new THREE.LineSegments(lineGeometry, lineMaterial);
119+
networkGroup.add(lines);
120+
121+
// Mouse follow
122+
let mouseX = 0, mouseY = 0, targetX = 0, targetY = 0;
123+
const handleMouseMove = (e) => {
124+
const rect = container.getBoundingClientRect();
125+
if (rect.width > 0 && rect.height > 0) {
126+
mouseX = ((e.clientX - rect.left) / rect.width) * 2 - 1;
127+
mouseY = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
128+
}
129+
};
130+
131+
window.addEventListener('mousemove', handleMouseMove);
132+
133+
const handleResize = () => {
134+
if (!container || !renderer || !camera) return;
135+
const w = container.clientWidth || window.innerWidth;
136+
const h = container.clientHeight || window.innerHeight;
137+
if (w > 0 && h > 0) {
138+
camera.aspect = w / h;
139+
camera.updateProjectionMatrix();
140+
renderer.setSize(w, h);
141+
}
142+
};
149143

150-
const animate = () => {
151-
animationFrameId = requestAnimationFrame(animate);
152-
const elapsedTime = clock.getElapsedTime();
153-
154-
// Gentle continuous rotation
155-
networkGroup.rotation.y = elapsedTime * 0.08;
156-
networkGroup.rotation.x = Math.sin(elapsedTime * 0.05) * 0.15;
157-
coreMesh.rotation.y = -elapsedTime * 0.15;
158-
coreMesh.rotation.z = elapsedTime * 0.05;
159-
160-
// Smooth mouse follow
161-
targetX += (mouseX * 0.5 - targetX) * 0.05;
162-
targetY += (mouseY * 0.5 - targetY) * 0.05;
163-
networkGroup.rotation.y += targetX * 0.2;
164-
networkGroup.rotation.x -= targetY * 0.2;
165-
166-
renderer.render(scene, camera);
167-
};
168-
169-
animate();
170-
171-
return () => {
172-
window.removeEventListener('mousemove', handleMouseMove);
173-
window.removeEventListener('resize', handleResize);
174-
cancelAnimationFrame(animationFrameId);
175-
if (container && renderer.domElement) {
176-
container.removeChild(renderer.domElement);
177-
}
178-
renderer.dispose();
179-
};
144+
window.addEventListener('resize', handleResize);
145+
146+
const animate = () => {
147+
animationFrameId = requestAnimationFrame(animate);
148+
const elapsedTime = clock.getElapsedTime();
149+
150+
networkGroup.rotation.y = elapsedTime * 0.08;
151+
networkGroup.rotation.x = Math.sin(elapsedTime * 0.05) * 0.15;
152+
coreMesh.rotation.y = -elapsedTime * 0.15;
153+
coreMesh.rotation.z = elapsedTime * 0.05;
154+
155+
targetX += (mouseX * 0.5 - targetX) * 0.05;
156+
targetY += (mouseY * 0.5 - targetY) * 0.05;
157+
networkGroup.rotation.y += targetX * 0.2;
158+
networkGroup.rotation.x -= targetY * 0.2;
159+
160+
renderer.render(scene, camera);
161+
};
162+
163+
animate();
164+
165+
return () => {
166+
window.removeEventListener('mousemove', handleMouseMove);
167+
window.removeEventListener('resize', handleResize);
168+
if (animationFrameId) cancelAnimationFrame(animationFrameId);
169+
if (container && renderer?.domElement) {
170+
try {
171+
container.removeChild(renderer.domElement);
172+
} catch (e) {}
173+
}
174+
renderer?.dispose();
175+
};
176+
} catch (err) {
177+
console.warn('Hero3D WebGL initialization skipped:', err);
178+
}
180179
}, []);
181180

182181
return (

vite.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import react from '@vitejs/plugin-react';
44
// https://vitejs.dev/config/
55
export default defineConfig({
66
plugins: [react()],
7-
base: '/',
7+
base: './',
88
build: {
99
rollupOptions: {
1010
output: {

0 commit comments

Comments
 (0)