/** * 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 für Dark/Light Mode this.colors = { dark: { background: '#040215', nodeColor: '#6a5498', nodePulse: '#9c7fe0', connectionColor: '#4a3870', flowColor: '#b47fea' }, light: { background: '#f9fafb', nodeColor: '#8b5cf6', nodePulse: '#7c3aed', connectionColor: '#c4b5fd', flowColor: '#6d28d9' } }; // Aktuelle Farbpalette basierend auf Theme this.currentColors = document.documentElement.classList.contains('dark') ? this.colors.dark : this.colors.light; // 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; connection.flowProgress = 0; this.activeConnections.delete(connectionId); } else { connection.flowProgress = progress; } } // Neue aktive Verbindungen starten if (this.activeConnections.size < this.config.flowDensity && Math.random() < 0.05) { // Zufälligen Knoten auswählen const nodeIndex = Math.floor(Math.random() * this.nodes.length); const node = this.nodes[nodeIndex]; // Anzahl der aktiven Verbindungen für diesen Knoten zählen const activeConnectionsCount = Array.from(this.activeConnections) .filter(id => { const [from, to] = id.split('-').map(Number); return from === nodeIndex || to === nodeIndex; }).length; // Nur neue Verbindung aktivieren, wenn Knoten noch nicht zu viele aktive hat if (activeConnectionsCount < this.config.maxFlowsPerNode) { // Verfügbare Verbindungen für diesen Knoten finden const availableConnections = node.connections.filter(conn => !conn.active); if (availableConnections.length > 0) { // Zufällige Verbindung auswählen const connection = availableConnections[Math.floor(Math.random() * availableConnections.length)]; // Verbindung aktivieren 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); } } } // Verbindungsdistanzen neu berechnen for (let i = 0; i < this.connections.length; i++) { const connection = this.connections[i]; const nodeA = this.nodes[connection.from]; const nodeB = this.nodes[connection.to]; const dx = nodeA.x - nodeB.x; const dy = nodeA.y - nodeB.y; const distance = Math.sqrt(dx * dx + dy * dy); connection.distance = distance; // Bei zu großer Distanz Verbindung deaktivieren if (distance > this.config.connectionDistance) { connection.opacity = 0; if (connection.active) { connection.active = false; connection.flowProgress = 0; this.activeConnections.delete(connection.id); } } else { // Verbesserte Berechnung der Opazität für einen natürlicheren Look const opacityFactor = document.documentElement.classList.contains('dark') ? 1.0 : 0.85; connection.opacity = Math.max(0.08, (1 - (distance / this.config.connectionDistance)) * this.config.connectionOpacity * opacityFactor); } } } render(now) { // Canvas löschen this.ctx.clearRect(0, 0, this.canvas.width / (window.devicePixelRatio || 1), this.canvas.height / (window.devicePixelRatio || 1)); // Aktualisiere Farbpalette basierend auf aktuellem Theme this.currentColors = document.documentElement.classList.contains('dark') ? this.colors.dark : this.colors.light; // Light Mode mit zusätzlichem Blur-Effekt für weicheres Erscheinungsbild if (!document.documentElement.classList.contains('dark')) { this.ctx.filter = 'blur(0.5px)'; } else { this.ctx.filter = 'none'; } // Verbindungen zeichnen for (let i = 0; i < this.connections.length; i++) { const connection = this.connections[i]; if (connection.opacity <= 0) continue; const nodeA = this.nodes[connection.from]; const nodeB = this.nodes[connection.to]; this.ctx.strokeStyle = this.currentColors.connectionColor; this.ctx.globalAlpha = connection.opacity; this.ctx.beginPath(); this.ctx.moveTo(nodeA.x, nodeA.y); this.ctx.lineTo(nodeB.x, nodeB.y); this.ctx.stroke(); // Aktive Verbindungen mit Fluss darstellen if (connection.active) { const fromX = nodeA.x; const fromY = nodeA.y; const toX = nodeB.x; const toY = nodeB.y; // Position des Flusspunkts const x = fromX + (toX - fromX) * connection.flowProgress; const y = fromY + (toY - fromY) * connection.flowProgress; // Fluss-Effekt zeichnen const pulseSize = document.documentElement.classList.contains('dark') ? 3 : 4; const pulseOpacity = document.documentElement.classList.contains('dark') ? 0.8 : 0.85; // Pulse-Effekt this.ctx.fillStyle = this.currentColors.flowColor; this.ctx.globalAlpha = pulseOpacity; this.ctx.beginPath(); this.ctx.arc(x, y, pulseSize, 0, Math.PI * 2); this.ctx.fill(); // Glow-Effekt const gradient = this.ctx.createRadialGradient(x, y, 0, x, y, pulseSize * 2); gradient.addColorStop(0, this.hexToRgba(this.currentColors.flowColor, 0.4)); gradient.addColorStop(1, this.hexToRgba(this.currentColors.flowColor, 0)); this.ctx.fillStyle = gradient; this.ctx.globalAlpha = 0.8; this.ctx.beginPath(); this.ctx.arc(x, y, pulseSize * 2, 0, Math.PI * 2); this.ctx.fill(); } } // Knoten zeichnen for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; const isPulsing = now - node.lastPulse < 300; // Erhöhte Helligkeit für pulsierende Knoten const nodeColor = isPulsing ? this.currentColors.nodePulse : this.currentColors.nodeColor; const glowSize = isPulsing ? node.size * 2.5 : node.size * 1.5; // Glow-Effekt const gradient = this.ctx.createRadialGradient( node.x, node.y, 0, node.x, node.y, glowSize ); gradient.addColorStop(0, this.hexToRgba(nodeColor, isPulsing ? 0.6 : 0.3)); gradient.addColorStop(1, this.hexToRgba(nodeColor, 0)); this.ctx.fillStyle = gradient; this.ctx.globalAlpha = document.documentElement.classList.contains('dark') ? 0.7 : 0.5; this.ctx.beginPath(); this.ctx.arc(node.x, node.y, glowSize, 0, Math.PI * 2); this.ctx.fill(); // Knoten selbst zeichnen this.ctx.fillStyle = nodeColor; this.ctx.globalAlpha = 0.8; this.ctx.beginPath(); this.ctx.arc(node.x, node.y, node.size, 0, Math.PI * 2); this.ctx.fill(); } // Zurücksetzen der Globalwerte this.ctx.globalAlpha = 1; this.ctx.filter = 'none'; } 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 shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b); 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; } hexToRgba(hex, alpha) { const rgb = this.hexToRgb(hex); return rgb ? `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha})` : `rgba(0, 0, 0, ${alpha})`; } } // Initialisiert den Hintergrund, sobald die Seite geladen ist document.addEventListener('DOMContentLoaded', () => { window.neuralBackground = new NeuralNetworkBackground(); // Theme-Wechsel-Event-Listener document.addEventListener('theme-changed', () => { if (window.neuralBackground) { window.neuralBackground.currentColors = document.documentElement.classList.contains('dark') ? window.neuralBackground.colors.dark : window.neuralBackground.colors.light; } }); });