392 lines
12 KiB
JavaScript
392 lines
12 KiB
JavaScript
/**
|
|
* Vereinfachter Neuronales Netzwerk Hintergrund
|
|
* Verwendet Canvas 2D anstelle von WebGL für bessere Leistung
|
|
*/
|
|
|
|
class NeuralNetworkBackground {
|
|
constructor() {
|
|
// Canvas einrichten
|
|
this.canvas = document.createElement('canvas');
|
|
this.canvas.id = 'neural-network-background';
|
|
this.canvas.style.position = 'fixed';
|
|
this.canvas.style.top = '0';
|
|
this.canvas.style.left = '0';
|
|
this.canvas.style.width = '100%';
|
|
this.canvas.style.height = '100%';
|
|
this.canvas.style.zIndex = '-10';
|
|
this.canvas.style.pointerEvents = 'none';
|
|
this.canvas.style.opacity = '1';
|
|
this.canvas.style.transition = 'opacity 3.5s ease-in-out';
|
|
|
|
// Falls Canvas bereits existiert, entfernen
|
|
const existingCanvas = document.getElementById('neural-network-background');
|
|
if (existingCanvas) {
|
|
existingCanvas.remove();
|
|
}
|
|
|
|
// An body anhängen als erstes Kind
|
|
if (document.body.firstChild) {
|
|
document.body.insertBefore(this.canvas, document.body.firstChild);
|
|
} else {
|
|
document.body.appendChild(this.canvas);
|
|
}
|
|
|
|
// 2D Context
|
|
this.ctx = this.canvas.getContext('2d');
|
|
|
|
// Eigenschaften
|
|
this.nodes = [];
|
|
this.connections = [];
|
|
this.activeConnections = new Set();
|
|
this.animationFrameId = null;
|
|
this.isDestroying = false;
|
|
|
|
// Farben - Lila Farbpalette
|
|
this.colors = {
|
|
background: '#040215',
|
|
nodeColor: '#6a5498',
|
|
nodePulse: '#9c7fe0',
|
|
connectionColor: '#4a3870',
|
|
flowColor: '#b47fea'
|
|
};
|
|
|
|
// Konfiguration
|
|
this.config = {
|
|
nodeCount: 80, // Anzahl der Knoten
|
|
nodeSize: 2.5, // Größe der Knoten
|
|
connectionDistance: 150, // Maximale Verbindungsdistanz
|
|
connectionOpacity: 0.5, // Erhöht von 0.3 auf 0.5 - Deckkraft der ständigen Verbindungen
|
|
animationSpeed: 0.15, // Geschwindigkeit der Animation
|
|
flowDensity: 2, // Anzahl aktiver Verbindungen
|
|
maxFlowsPerNode: 2, // Maximale Anzahl aktiver Verbindungen pro Knoten
|
|
flowDuration: [2000, 5000], // Min/Max Dauer des Flows in ms
|
|
nodePulseFrequency: 0.01 // Wie oft Knoten pulsieren
|
|
};
|
|
|
|
// Initialisieren
|
|
this.init();
|
|
|
|
// Event-Listener
|
|
window.addEventListener('resize', this.resizeCanvas.bind(this));
|
|
|
|
console.log('Vereinfachter Neural Network Background initialized');
|
|
}
|
|
|
|
init() {
|
|
this.resizeCanvas();
|
|
this.createNodes();
|
|
this.createConnections();
|
|
this.startAnimation();
|
|
}
|
|
|
|
resizeCanvas() {
|
|
const pixelRatio = window.devicePixelRatio || 1;
|
|
const width = window.innerWidth;
|
|
const height = window.innerHeight;
|
|
|
|
this.canvas.style.width = width + 'px';
|
|
this.canvas.style.height = height + 'px';
|
|
this.canvas.width = width * pixelRatio;
|
|
this.canvas.height = height * pixelRatio;
|
|
|
|
if (this.ctx) {
|
|
this.ctx.scale(pixelRatio, pixelRatio);
|
|
}
|
|
|
|
// Neuberechnung der Knotenpositionen nach Größenänderung
|
|
if (this.nodes.length) {
|
|
this.createNodes();
|
|
this.createConnections();
|
|
}
|
|
}
|
|
|
|
createNodes() {
|
|
this.nodes = [];
|
|
const width = this.canvas.width / (window.devicePixelRatio || 1);
|
|
const height = this.canvas.height / (window.devicePixelRatio || 1);
|
|
|
|
// Cluster-Zentren für realistisches neuronales Netzwerk
|
|
const clusterCount = Math.floor(6 + Math.random() * 4);
|
|
const clusters = [];
|
|
|
|
for (let i = 0; i < clusterCount; i++) {
|
|
clusters.push({
|
|
x: Math.random() * width,
|
|
y: Math.random() * height,
|
|
radius: 100 + Math.random() * 150
|
|
});
|
|
}
|
|
|
|
// Knoten erstellen
|
|
for (let i = 0; i < this.config.nodeCount; i++) {
|
|
// Wähle zufällig ein Cluster
|
|
const cluster = clusters[Math.floor(Math.random() * clusters.length)];
|
|
|
|
// Erstelle einen Knoten innerhalb des Clusters mit zufälligem Offset
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const distance = Math.random() * cluster.radius;
|
|
|
|
const node = {
|
|
id: i,
|
|
x: cluster.x + Math.cos(angle) * distance,
|
|
y: cluster.y + Math.sin(angle) * distance,
|
|
size: this.config.nodeSize * (0.8 + Math.random() * 0.4),
|
|
speed: {
|
|
x: (Math.random() - 0.5) * 0.2,
|
|
y: (Math.random() - 0.5) * 0.2
|
|
},
|
|
lastPulse: 0,
|
|
pulseInterval: 5000 + Math.random() * 10000, // Zufälliges Pulsieren
|
|
connections: []
|
|
};
|
|
|
|
this.nodes.push(node);
|
|
}
|
|
}
|
|
|
|
createConnections() {
|
|
this.connections = [];
|
|
|
|
// Verbindungen zwischen Knoten erstellen
|
|
for (let i = 0; i < this.nodes.length; i++) {
|
|
const nodeA = this.nodes[i];
|
|
|
|
for (let j = i + 1; j < this.nodes.length; j++) {
|
|
const nodeB = this.nodes[j];
|
|
|
|
const dx = nodeA.x - nodeB.x;
|
|
const dy = nodeA.y - nodeB.y;
|
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
|
|
if (distance < this.config.connectionDistance) {
|
|
const connection = {
|
|
id: `${i}-${j}`,
|
|
from: i,
|
|
to: j,
|
|
distance: distance,
|
|
opacity: Math.max(0.05, 1 - (distance / this.config.connectionDistance)),
|
|
active: false,
|
|
flowProgress: 0,
|
|
flowDuration: 0,
|
|
flowStart: 0
|
|
};
|
|
|
|
this.connections.push(connection);
|
|
nodeA.connections.push(connection);
|
|
nodeB.connections.push(connection);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
startAnimation() {
|
|
this.animate();
|
|
}
|
|
|
|
animate() {
|
|
this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
|
|
|
|
const now = Date.now();
|
|
this.updateNodes(now);
|
|
this.updateConnections(now);
|
|
this.render(now);
|
|
}
|
|
|
|
updateNodes(now) {
|
|
const width = this.canvas.width / (window.devicePixelRatio || 1);
|
|
const height = this.canvas.height / (window.devicePixelRatio || 1);
|
|
|
|
// Knoten bewegen
|
|
for (let i = 0; i < this.nodes.length; i++) {
|
|
const node = this.nodes[i];
|
|
|
|
node.x += node.speed.x;
|
|
node.y += node.speed.y;
|
|
|
|
// Begrenzung am Rand
|
|
if (node.x < 0 || node.x > width) {
|
|
node.speed.x *= -1;
|
|
}
|
|
|
|
if (node.y < 0 || node.y > height) {
|
|
node.speed.y *= -1;
|
|
}
|
|
|
|
// Zufällig Richtung ändern
|
|
if (Math.random() < 0.01) {
|
|
node.speed.x = (Math.random() - 0.5) * 0.2;
|
|
node.speed.y = (Math.random() - 0.5) * 0.2;
|
|
}
|
|
|
|
// Zufälliges Pulsieren
|
|
if (Math.random() < this.config.nodePulseFrequency && now - node.lastPulse > node.pulseInterval) {
|
|
node.lastPulse = now;
|
|
}
|
|
}
|
|
}
|
|
|
|
updateConnections(now) {
|
|
// Update aktive Verbindungen
|
|
for (const connectionId of this.activeConnections) {
|
|
const connection = this.connections.find(c => c.id === connectionId);
|
|
if (!connection) continue;
|
|
|
|
// Aktualisiere den Flow-Fortschritt
|
|
const elapsed = now - connection.flowStart;
|
|
const progress = elapsed / connection.flowDuration;
|
|
|
|
if (progress >= 1) {
|
|
// Flow beenden
|
|
connection.active = false;
|
|
this.activeConnections.delete(connectionId);
|
|
} else {
|
|
connection.flowProgress = progress;
|
|
}
|
|
}
|
|
|
|
// Neue Flows starten, wenn unter dem Limit
|
|
if (this.activeConnections.size < this.config.flowDensity) {
|
|
// Wähle eine zufällige Verbindung
|
|
const availableConnections = this.connections.filter(c => !c.active);
|
|
|
|
if (availableConnections.length > 0) {
|
|
const randomIndex = Math.floor(Math.random() * availableConnections.length);
|
|
const connection = availableConnections[randomIndex];
|
|
|
|
// Aktiviere die Verbindung
|
|
connection.active = true;
|
|
connection.flowProgress = 0;
|
|
connection.flowStart = now;
|
|
connection.flowDuration = this.config.flowDuration[0] +
|
|
Math.random() * (this.config.flowDuration[1] - this.config.flowDuration[0]);
|
|
|
|
this.activeConnections.add(connection.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
render(now) {
|
|
const colors = this.colors;
|
|
const width = this.canvas.width / (window.devicePixelRatio || 1);
|
|
const height = this.canvas.height / (window.devicePixelRatio || 1);
|
|
|
|
// Hintergrund löschen
|
|
this.ctx.fillStyle = colors.background;
|
|
this.ctx.fillRect(0, 0, width, height);
|
|
|
|
// Verbindungen zeichnen (statisch)
|
|
this.ctx.strokeStyle = colors.connectionColor;
|
|
this.ctx.lineWidth = 1.2;
|
|
|
|
for (const connection of this.connections) {
|
|
const fromNode = this.nodes[connection.from];
|
|
const toNode = this.nodes[connection.to];
|
|
|
|
this.ctx.globalAlpha = connection.opacity * 0.5;
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(fromNode.x, fromNode.y);
|
|
this.ctx.lineTo(toNode.x, toNode.y);
|
|
this.ctx.stroke();
|
|
}
|
|
|
|
// Aktive Verbindungen zeichnen (Flows)
|
|
this.ctx.strokeStyle = colors.flowColor;
|
|
this.ctx.lineWidth = 2.5;
|
|
|
|
for (const connectionId of this.activeConnections) {
|
|
const connection = this.connections.find(c => c.id === connectionId);
|
|
if (!connection) continue;
|
|
|
|
const fromNode = this.nodes[connection.from];
|
|
const toNode = this.nodes[connection.to];
|
|
|
|
// Glühen-Effekt
|
|
this.ctx.globalAlpha = Math.sin(connection.flowProgress * Math.PI) * 0.8;
|
|
|
|
// Linie zeichnen
|
|
this.ctx.beginPath();
|
|
this.ctx.moveTo(fromNode.x, fromNode.y);
|
|
this.ctx.lineTo(toNode.x, toNode.y);
|
|
this.ctx.stroke();
|
|
|
|
// Fließendes Partikel
|
|
const progress = connection.flowProgress;
|
|
const x = fromNode.x + (toNode.x - fromNode.x) * progress;
|
|
const y = fromNode.y + (toNode.y - fromNode.y) * progress;
|
|
|
|
this.ctx.globalAlpha = 0.9;
|
|
this.ctx.fillStyle = colors.flowColor;
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(x, y, 2, 0, Math.PI * 2);
|
|
this.ctx.fill();
|
|
}
|
|
|
|
// Knoten zeichnen
|
|
for (const node of this.nodes) {
|
|
// Pulsierende Knoten
|
|
const timeSinceLastPulse = now - node.lastPulse;
|
|
const isPulsing = timeSinceLastPulse < 800;
|
|
const pulseProgress = isPulsing ? timeSinceLastPulse / 800 : 0;
|
|
|
|
// Knoten selbst
|
|
this.ctx.globalAlpha = 1;
|
|
this.ctx.fillStyle = isPulsing
|
|
? colors.nodePulse
|
|
: colors.nodeColor;
|
|
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(node.x, node.y, node.size + (isPulsing ? 1 * Math.sin(pulseProgress * Math.PI) : 0), 0, Math.PI * 2);
|
|
this.ctx.fill();
|
|
|
|
// Wenn pulsierend, füge einen Glow-Effekt hinzu
|
|
if (isPulsing) {
|
|
this.ctx.globalAlpha = 0.5 * (1 - pulseProgress);
|
|
this.ctx.beginPath();
|
|
this.ctx.arc(node.x, node.y, node.size + 5 * pulseProgress, 0, Math.PI * 2);
|
|
this.ctx.fill();
|
|
}
|
|
}
|
|
|
|
this.ctx.globalAlpha = 1;
|
|
}
|
|
|
|
destroy() {
|
|
if (this.isDestroying) return;
|
|
this.isDestroying = true;
|
|
|
|
// Animation stoppen
|
|
if (this.animationFrameId) {
|
|
cancelAnimationFrame(this.animationFrameId);
|
|
}
|
|
|
|
// Canvas ausblenden
|
|
this.canvas.style.opacity = '0';
|
|
|
|
// Nach Übergang entfernen
|
|
setTimeout(() => {
|
|
if (this.canvas && this.canvas.parentNode) {
|
|
this.canvas.parentNode.removeChild(this.canvas);
|
|
}
|
|
}, 3500);
|
|
}
|
|
|
|
hexToRgb(hex) {
|
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
return result ? {
|
|
r: parseInt(result[1], 16),
|
|
g: parseInt(result[2], 16),
|
|
b: parseInt(result[3], 16)
|
|
} : null;
|
|
}
|
|
}
|
|
|
|
// Initialisiert den Hintergrund, sobald die Seite geladen ist
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.neuralBackground = new NeuralNetworkBackground();
|
|
|
|
// Sicherstellen, dass die Seite immer im Dark Mode ist
|
|
document.documentElement.classList.add('dark');
|
|
document.body.classList.add('dark');
|
|
});
|