add animated background
Some checks failed
Frontend Build and Deploy / build (push) Failing after 12s
Some checks failed
Frontend Build and Deploy / build (push) Failing after 12s
This commit is contained in:
parent
557a157226
commit
88afb7b453
18
package-lock.json
generated
18
package-lock.json
generated
@ -18,6 +18,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.25.0",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
|
"@types/node": "^22.15.29",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
@ -1675,6 +1676,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.15.29",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz",
|
||||||
|
"integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.6",
|
"version": "19.1.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.6.tgz",
|
||||||
@ -3952,6 +3963,13 @@
|
|||||||
"typescript": ">=4.8.4 <5.9.0"
|
"typescript": ">=4.8.4 <5.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.25.0",
|
"@eslint/js": "^9.25.0",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
|
"@types/node": "^22.15.29",
|
||||||
"@types/react": "^19.1.2",
|
"@types/react": "^19.1.2",
|
||||||
"@types/react-dom": "^19.1.2",
|
"@types/react-dom": "^19.1.2",
|
||||||
"@types/react-router-dom": "^5.3.3",
|
"@types/react-router-dom": "^5.3.3",
|
||||||
|
14
src/App.css
14
src/App.css
@ -1,5 +1,17 @@
|
|||||||
#root {
|
#root {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure body and html have no margins/padding for full screen background */
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
18
src/App.tsx
18
src/App.tsx
@ -6,6 +6,7 @@ import StatisticsSection from './components/StatisticsSection';
|
|||||||
import FeaturesSection from './components/FeaturesSection';
|
import FeaturesSection from './components/FeaturesSection';
|
||||||
import Footer from './components/Footer';
|
import Footer from './components/Footer';
|
||||||
import CandidatePage from './components/CandidatePage/CandidatePage';
|
import CandidatePage from './components/CandidatePage/CandidatePage';
|
||||||
|
import MatrixBackground from './components/MatrixBackground';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
// HomePage component
|
// HomePage component
|
||||||
@ -19,13 +20,16 @@ const HomePage: React.FC = () => (
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-900 min-h-screen w-full flex flex-col">
|
<div className="min-h-screen w-full flex flex-col relative" style={{ backgroundColor: 'transparent' }}>
|
||||||
<Navbar />
|
<MatrixBackground />
|
||||||
<Routes>
|
<div className="relative z-10">
|
||||||
<Route path="/" element={<HomePage />} />
|
<Navbar />
|
||||||
<Route path="/candidato/:id" element={<CandidatePage />} />
|
<Routes>
|
||||||
</Routes>
|
<Route path="/" element={<HomePage />} />
|
||||||
<Footer />
|
<Route path="/candidato/:id" element={<CandidatePage />} />
|
||||||
|
</Routes>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
|
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-gray-900 text-gray-400 py-8 text-center">
|
<footer className="bg-gray-800/30 text-gray-400 py-8 text-center">
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
<p className="mb-2">
|
<p className="mb-2">
|
||||||
© {new Date().getFullYear()} OpenCand. Todos os direitos reservados.
|
© {new Date().getFullYear()} OpenCand. Todos os direitos reservados.
|
||||||
|
334
src/components/MatrixBackground.tsx
Normal file
334
src/components/MatrixBackground.tsx
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
interface Dot {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
vx: number;
|
||||||
|
vy: number;
|
||||||
|
connections: number[];
|
||||||
|
brightness: number;
|
||||||
|
targetBrightness: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Connection {
|
||||||
|
from: number;
|
||||||
|
to: number;
|
||||||
|
brightness: number;
|
||||||
|
targetBrightness: number;
|
||||||
|
thickness: number;
|
||||||
|
targetThickness: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MatrixBackground: React.FC = () => {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const animationRef = useRef<number>(0);
|
||||||
|
const dotsRef = useRef<Dot[]>([]);
|
||||||
|
const connectionsRef = useRef<Connection[]>([]);
|
||||||
|
const mouseRef = useRef({ x: 0, y: 0 });
|
||||||
|
const [, setDimensions] = useState({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const config = {
|
||||||
|
dotCount: 300,
|
||||||
|
maxConnections: 3,
|
||||||
|
connectionDistance: 150,
|
||||||
|
dotSpeed: 0.3,
|
||||||
|
hoverRadius: 120,
|
||||||
|
baseBrightness: 0.25, // Reduced to 30% opacity (0.3 * 0.3)
|
||||||
|
hoverBrightness: 0.6, // Reduced hover brightness
|
||||||
|
baseThickness: 0.5,
|
||||||
|
hoverThickness: 1.5,
|
||||||
|
fadeSpeed: 0.08, // Slightly faster fade for better responsiveness
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize dots
|
||||||
|
const initializeDots = (width: number, height: number) => {
|
||||||
|
const dots: Dot[] = [];
|
||||||
|
for (let i = 0; i < config.dotCount; i++) {
|
||||||
|
dots.push({
|
||||||
|
x: Math.random() * width,
|
||||||
|
y: Math.random() * height,
|
||||||
|
vx: (Math.random() - 0.5) * config.dotSpeed,
|
||||||
|
vy: (Math.random() - 0.5) * config.dotSpeed,
|
||||||
|
connections: [],
|
||||||
|
brightness: config.baseBrightness,
|
||||||
|
targetBrightness: config.baseBrightness,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return dots;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate connections between dots
|
||||||
|
const calculateConnections = (dots: Dot[]) => {
|
||||||
|
const connections: Connection[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < dots.length; i++) {
|
||||||
|
const dot = dots[i];
|
||||||
|
dot.connections = [];
|
||||||
|
|
||||||
|
const distances: { index: number; distance: number }[] = [];
|
||||||
|
|
||||||
|
for (let j = 0; j < dots.length; j++) {
|
||||||
|
if (i === j) continue;
|
||||||
|
|
||||||
|
const dx = dot.x - dots[j].x;
|
||||||
|
const dy = dot.y - dots[j].y;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance < config.connectionDistance) {
|
||||||
|
distances.push({ index: j, distance });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by distance and take closest connections
|
||||||
|
distances.sort((a, b) => a.distance - b.distance);
|
||||||
|
const maxConnections = Math.min(config.maxConnections, distances.length);
|
||||||
|
|
||||||
|
for (let k = 0; k < maxConnections; k++) {
|
||||||
|
const targetIndex = distances[k].index;
|
||||||
|
|
||||||
|
// Avoid duplicate connections
|
||||||
|
const existing = connections.find(
|
||||||
|
conn => (conn.from === i && conn.to === targetIndex) ||
|
||||||
|
(conn.from === targetIndex && conn.to === i)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
dot.connections.push(targetIndex);
|
||||||
|
connections.push({
|
||||||
|
from: i,
|
||||||
|
to: targetIndex,
|
||||||
|
brightness: config.baseBrightness,
|
||||||
|
targetBrightness: config.baseBrightness,
|
||||||
|
thickness: config.baseThickness,
|
||||||
|
targetThickness: config.baseThickness,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return connections;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update hover effects
|
||||||
|
const updateHoverEffects = (dots: Dot[], connections: Connection[], mouseX: number, mouseY: number) => {
|
||||||
|
// Reset all targets to base values
|
||||||
|
dots.forEach(dot => {
|
||||||
|
dot.targetBrightness = config.baseBrightness;
|
||||||
|
});
|
||||||
|
|
||||||
|
connections.forEach(conn => {
|
||||||
|
conn.targetBrightness = config.baseBrightness;
|
||||||
|
conn.targetThickness = config.baseThickness;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check which dots are near the mouse
|
||||||
|
let hoveredCount = 0;
|
||||||
|
dots.forEach((dot, index) => {
|
||||||
|
const dx = dot.x - mouseX;
|
||||||
|
const dy = dot.y - mouseY;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
|
if (distance < config.hoverRadius) {
|
||||||
|
hoveredCount++;
|
||||||
|
const intensity = 1 - (distance / config.hoverRadius);
|
||||||
|
dot.targetBrightness = config.baseBrightness +
|
||||||
|
(config.hoverBrightness - config.baseBrightness) * intensity;
|
||||||
|
|
||||||
|
// Brighten connected lines
|
||||||
|
connections.forEach(conn => {
|
||||||
|
if (conn.from === index || conn.to === index) {
|
||||||
|
conn.targetBrightness = config.baseBrightness +
|
||||||
|
(config.hoverBrightness - config.baseBrightness) * intensity * 0.7;
|
||||||
|
conn.targetThickness = config.baseThickness +
|
||||||
|
(config.hoverThickness - config.baseThickness) * intensity;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Smooth transitions
|
||||||
|
dots.forEach(dot => {
|
||||||
|
dot.brightness += (dot.targetBrightness - dot.brightness) * config.fadeSpeed;
|
||||||
|
});
|
||||||
|
|
||||||
|
connections.forEach(conn => {
|
||||||
|
conn.brightness += (conn.targetBrightness - conn.brightness) * config.fadeSpeed;
|
||||||
|
conn.thickness += (conn.targetThickness - conn.thickness) * config.fadeSpeed;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Animation loop
|
||||||
|
const animate = () => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
const { width, height } = canvas;
|
||||||
|
|
||||||
|
// Clear canvas with dark blue background
|
||||||
|
ctx.fillStyle = '#0a0f1a';
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
const dots = dotsRef.current;
|
||||||
|
const connections = connectionsRef.current;
|
||||||
|
|
||||||
|
// Update dot positions
|
||||||
|
dots.forEach(dot => {
|
||||||
|
dot.x += dot.vx;
|
||||||
|
dot.y += dot.vy;
|
||||||
|
|
||||||
|
// Bounce off edges
|
||||||
|
if (dot.x <= 0 || dot.x >= width) {
|
||||||
|
dot.vx *= -1;
|
||||||
|
dot.x = Math.max(0, Math.min(width, dot.x));
|
||||||
|
}
|
||||||
|
if (dot.y <= 0 || dot.y >= height) {
|
||||||
|
dot.vy *= -1;
|
||||||
|
dot.y = Math.max(0, Math.min(height, dot.y));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recalculate connections periodically for smooth movement
|
||||||
|
if (Math.random() < 0.02) {
|
||||||
|
connectionsRef.current = calculateConnections(dots);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update hover effects
|
||||||
|
updateHoverEffects(dots, connections, mouseRef.current.x, mouseRef.current.y);
|
||||||
|
|
||||||
|
// Draw connections
|
||||||
|
connections.forEach(conn => {
|
||||||
|
const fromDot = dots[conn.from];
|
||||||
|
const toDot = dots[conn.to];
|
||||||
|
|
||||||
|
if (!fromDot || !toDot) return;
|
||||||
|
|
||||||
|
ctx.strokeStyle = `rgba(100, 200, 255, ${conn.brightness * 0.3})`; // Changed to blue with 30% opacity
|
||||||
|
ctx.lineWidth = conn.thickness;
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(fromDot.x, fromDot.y);
|
||||||
|
ctx.lineTo(toDot.x, toDot.y);
|
||||||
|
ctx.stroke();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Draw dots
|
||||||
|
dots.forEach(dot => {
|
||||||
|
ctx.fillStyle = `rgba(100, 200, 255, ${dot.brightness * 0.3})`; // Changed to blue with 30% opacity
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(dot.x, dot.y, 2, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
|
||||||
|
// Add subtle glow
|
||||||
|
ctx.shadowColor = `rgba(100, 200, 255, ${dot.brightness * 0.15})`; // Reduced glow opacity
|
||||||
|
ctx.shadowBlur = 4;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(dot.x, dot.y, 1.5, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
ctx.shadowBlur = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
animationRef.current = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle resize
|
||||||
|
const handleResize = () => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
// Set canvas size to window size
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
|
||||||
|
canvas.style.width = `${window.innerWidth}px`;
|
||||||
|
canvas.style.height = `${window.innerHeight}px`;
|
||||||
|
|
||||||
|
setDimensions({ width: window.innerWidth, height: window.innerHeight });
|
||||||
|
|
||||||
|
// Reinitialize dots for new dimensions
|
||||||
|
dotsRef.current = initializeDots(window.innerWidth, window.innerHeight);
|
||||||
|
connectionsRef.current = calculateConnections(dotsRef.current);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log(`MatrixBackground: Canvas resized to ${window.innerWidth}x${window.innerHeight}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle mouse movement
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
mouseRef.current = {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('MatrixBackground: Canvas not found');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('MatrixBackground: Canvas found, initializing...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
handleResize();
|
||||||
|
|
||||||
|
// Start animation
|
||||||
|
const startAnimation = () => {
|
||||||
|
if (animationRef.current) {
|
||||||
|
cancelAnimationFrame(animationRef.current);
|
||||||
|
}
|
||||||
|
animationRef.current = requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
startAnimation();
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('MatrixBackground: Animation started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (animationRef.current) {
|
||||||
|
cancelAnimationFrame(animationRef.current);
|
||||||
|
}
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('MatrixBackground: Cleanup completed');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className="fixed inset-0"
|
||||||
|
style={{
|
||||||
|
zIndex: 0,
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: '100vw',
|
||||||
|
height: '100vh',
|
||||||
|
backgroundColor: '#0a0f1a', // Dark blue background
|
||||||
|
pointerEvents: 'none'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MatrixBackground;
|
@ -105,18 +105,11 @@ const StatisticsSection: React.FC = () => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="stats" className="py-20 bg-gray-900">
|
<section id="stats" className="py-20 bg-gray-800/30">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<h2 className="text-3xl font-bold text-center text-white mb-12">
|
<h2 className="text-3xl font-bold text-center text-white mb-12">
|
||||||
Dados em Números
|
Dados em Números
|
||||||
</h2>
|
</h2>
|
||||||
{error && !isLoading && (
|
|
||||||
<div className="text-center mb-8">
|
|
||||||
<p className="text-yellow-400 text-sm">
|
|
||||||
⚠️ Usando dados de demonstração - {error}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex flex-wrap justify-center gap-8 mx-auto">
|
<div className="flex flex-wrap justify-center gap-8 mx-auto">
|
||||||
{statisticsData.slice(0, 3).map((stat, index) => (
|
{statisticsData.slice(0, 3).map((stat, index) => (
|
||||||
<div key={index} className="w-full md:w-80 lg:w-96">
|
<div key={index} className="w-full md:w-80 lg:w-96">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user