/** * 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.isDarkMode = true; this.isDestroying = false; // Farben - Lila Farbpalette this.darkModeColors = { background: '#040215', nodeColor: '#6a5498', nodePulse: '#9c7fe0', connectionColor: '#4a3870', flowColor: '#b47fea' }; // Farben für Light Mode mit Lila-Akzenten this.lightModeColors = { background: '#f8f6fc', nodeColor: '#6a5498', nodePulse: '#9c7fe0', connectionColor: '#7e70a5', flowColor: '#a06cd5' }; // 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)); document.addEventListener('darkModeToggled', (event) => { this.isDarkMode = event.detail.isDark; }); 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.isDarkMode ? this.darkModeColors : this.lightModeColors; 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(); });