diff --git a/static/neural-network-background.js b/static/neural-network-background.js index fccd097..3f4d5f0 100644 --- a/static/neural-network-background.js +++ b/static/neural-network-background.js @@ -25,97 +25,68 @@ class NeuralNetworkBackground { } // An body anhängen als erstes Kind - // Append to body as first child to ensure it's behind everything if (document.body.firstChild) { document.body.insertBefore(this.canvas, document.body.firstChild); } else { document.body.appendChild(this.canvas); } - // WebGL context - this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl'); - if (!this.gl) { - console.warn('WebGL not supported, falling back to canvas rendering'); - this.gl = null; - this.ctx = this.canvas.getContext('2d'); - this.useWebGL = false; - } else { - this.useWebGL = true; - } + // 2D Context + this.ctx = this.canvas.getContext('2d'); - // Animation properties + // Eigenschaften this.nodes = []; this.connections = []; - this.flows = []; // Flow animations along connections + this.activeConnections = new Set(); this.animationFrameId = null; - this.isDarkMode = true; // Always use dark mode for the background - this.isDestroying = false; // Flag für den Ausblendeprozess + this.isDarkMode = true; + this.isDestroying = false; - // Colors - Lila Farbpalette mit intensiveren Effekten + // Farben - Lila Farbpalette this.darkModeColors = { - background: '#040215', // Dunklerer Hintergrund mit leichtem Violett-Anteil - nodeColor: '#6a5498', // Lila-basierte Knotenfarbe - nodePulse: '#9c7fe0', // Helleres Lila für Pulsieren - connectionColor: '#4a3870', // Dunklere Lila-Verbindungen - flowColor: '#b47fea' // Lebendiges Lila für Blitze + background: '#040215', + nodeColor: '#6a5498', + nodePulse: '#9c7fe0', + connectionColor: '#4a3870', + flowColor: '#b47fea' }; - // Farben für Light Mode auch mit Lila-Akzenten + // Farben für Light Mode mit Lila-Akzenten this.lightModeColors = { - background: '#f8f6fc', // Sehr heller Lila-Hintergrund - nodeColor: '#6a5498', // Gleiche Lila-Töne wie im Dark Mode - nodePulse: '#9c7fe0', // für konsistentes Erscheinungsbild - connectionColor: '#7e70a5', // Helleres Lila für Verbindungen - flowColor: '#a06cd5' // Sanfteres Lila für Blitze + background: '#f8f6fc', + nodeColor: '#6a5498', + nodePulse: '#9c7fe0', + connectionColor: '#7e70a5', + flowColor: '#a06cd5' }; - // Konfigurationsobjekt für schlangenartige, leuchtende Blitze und permanentes Netzwerk + // Konfiguration this.config = { - nodeCount: 100, // Weniger Knoten für klarere Strukturen - nodeSize: 3.2, // Etwas größere Knoten für bessere Sichtbarkeit - nodeVariation: 8, // Reduzierte Varianz für konsistenteres Erscheinungsbild - connectionDistance: 100, // Größere Verbindungsdistanz für weitläufigeres Netzwerk - connectionOpacity: 0.3, // Erhöhte Opazität für sichtbarere dauerhafte Verbindungen - animationSpeed: 0.1, // Noch langsamere Animation für sanftere Bewegung - pulseSpeed: 0, // Sehr langsames Pulsieren für ruhige Animation - flowSpeed: 0.15, // Deutlich langsamere Blitze für schlangenartige Bewegung - flowDensity: 2, // Weniger gleichzeitige Blitze für bessere Verteilung - flowLength: 0.05, // Längere einzelne Flows für sichtbare "Schlangen" - maxConnections: 12, // Mehr Verbindungen pro Neuron für dichtere Netzwerke - clusteringFactor: 0.005, // Stärkeres Clustering für deutlichere Strukturen - linesFadeDuration: 600, // Noch längere Dauer fürs Ein-/Ausblenden für permanentes Netzwerk (ms) - linesWidth: 1.0, // Etwas dickere Linien für bessere Sichtbarkeit - linesOpacity: 0.75, // Höhere Opazität für das permanente Netzwerk - maxFlowCount: 1, // Weniger gleichzeitige Flows für bessere Übersicht - glowIntensity: 1.0, // Verstärkter Glow-Effekt - sparkCount: 1, // Mehr Funken - sparkSize: 1, // Größere Funken - blurRadius: 10, // Blur-Radius für Glow-Effekte - minActiveFlows: 1, // Minimale Anzahl aktiver Flows - flowForwardingRate: 1.0, // 100% Weiterleitungsrate für endlose Flows - maxFlowGenerations: 10 // Sehr hohe Generationsgrenze für endlose Animation + 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 }; - // Initialize + // Initialisieren this.init(); - // Event listeners + // Event-Listener window.addEventListener('resize', this.resizeCanvas.bind(this)); document.addEventListener('darkModeToggled', (event) => { this.isDarkMode = event.detail.isDark; }); - // Log that the background is initialized - console.log('Neural Network Background initialized'); + console.log('Vereinfachter Neural Network Background initialized'); } init() { this.resizeCanvas(); - - if (this.useWebGL) { - this.initWebGL(); - } - this.createNodes(); this.createConnections(); this.startAnimation(); @@ -131,172 +102,55 @@ class NeuralNetworkBackground { this.canvas.width = width * pixelRatio; this.canvas.height = height * pixelRatio; - if (this.useWebGL) { - this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); - } else if (this.ctx) { + if (this.ctx) { this.ctx.scale(pixelRatio, pixelRatio); } - // Recalculate node positions after resize + // Neuberechnung der Knotenpositionen nach Größenänderung if (this.nodes.length) { this.createNodes(); this.createConnections(); } } - initWebGL() { - // Vertex shader - const vsSource = ` - attribute vec2 aVertexPosition; - attribute float aPointSize; - uniform vec2 uResolution; - - void main() { - // Convert from pixel to clip space - vec2 position = (aVertexPosition / uResolution) * 2.0 - 1.0; - // Flip Y coordinate - position.y = -position.y; - - gl_Position = vec4(position, 0, 1); - gl_PointSize = aPointSize; - } - `; - - // Fragment shader - Softer glow effect - const fsSource = ` - precision mediump float; - uniform vec4 uColor; - - void main() { - float distance = length(gl_PointCoord - vec2(0.5, 0.5)); - - // Softer glow with smoother falloff - float alpha = 1.0 - smoothstep(0.1, 0.5, distance); - alpha = pow(alpha, 1.5); // Make the glow even softer - - gl_FragColor = vec4(uColor.rgb, uColor.a * alpha); - } - `; - - // Initialize shaders - const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource); - const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource); - - // Create shader program - this.shaderProgram = this.gl.createProgram(); - this.gl.attachShader(this.shaderProgram, vertexShader); - this.gl.attachShader(this.shaderProgram, fragmentShader); - this.gl.linkProgram(this.shaderProgram); - - if (!this.gl.getProgramParameter(this.shaderProgram, this.gl.LINK_STATUS)) { - console.error('Unable to initialize the shader program: ' + - this.gl.getProgramInfoLog(this.shaderProgram)); - return; - } - - // Get attribute and uniform locations - this.programInfo = { - program: this.shaderProgram, - attribLocations: { - vertexPosition: this.gl.getAttribLocation(this.shaderProgram, 'aVertexPosition'), - pointSize: this.gl.getAttribLocation(this.shaderProgram, 'aPointSize') - }, - uniformLocations: { - resolution: this.gl.getUniformLocation(this.shaderProgram, 'uResolution'), - color: this.gl.getUniformLocation(this.shaderProgram, 'uColor') - } - }; - - // Create buffers - this.positionBuffer = this.gl.createBuffer(); - this.sizeBuffer = this.gl.createBuffer(); - - // Set clear color for WebGL context - const bgColor = this.hexToRgb(this.darkModeColors.background); - - this.gl.clearColor(bgColor.r/255, bgColor.g/255, bgColor.b/255, 1.0); - } - - loadShader(type, source) { - const shader = this.gl.createShader(type); - this.gl.shaderSource(shader, source); - this.gl.compileShader(shader); - - if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { - console.error('An error occurred compiling the shaders: ' + - this.gl.getShaderInfoLog(shader)); - this.gl.deleteShader(shader); - return null; - } - - return shader; - } - createNodes() { this.nodes = []; const width = this.canvas.width / (window.devicePixelRatio || 1); const height = this.canvas.height / (window.devicePixelRatio || 1); - // Erstelle Cluster-Zentren für neuronale Netzwerkmuster mit erhöhter Komplexität - const clusterCount = Math.floor(8 + Math.random() * 6); // 8-13 Cluster für mehr Dichte + // Cluster-Zentren für realistisches neuronales Netzwerk + const clusterCount = Math.floor(6 + Math.random() * 4); const clusters = []; for (let i = 0; i < clusterCount; i++) { - // Dynamischere Clustergrößen mit größerer Variationsbreite - const baseRadius = 120 + Math.random() * 200; // Größere Basisradien - const radiusVariation = 0.3 + Math.random() * 0.4; // 30-70% Variation - clusters.push({ x: Math.random() * width, y: Math.random() * height, - radius: baseRadius * (1 + radiusVariation), // Dynamischere Größen - density: 0.7 + Math.random() * 0.3, // Dichtefaktor für Knotenverteilung - influence: 0.5 + Math.random() * 0.5 // Einflussstärke auf umliegende Knoten + radius: 100 + Math.random() * 150 }); } - // Create nodes with random positions and properties + // Knoten erstellen for (let i = 0; i < this.config.nodeCount; i++) { - // Entscheide, ob dieser Knoten zu einem Cluster gehört oder nicht - const useCluster = Math.random() < this.config.clusteringFactor; - let x, y; + // Wähle zufällig ein Cluster + const cluster = clusters[Math.floor(Math.random() * clusters.length)]; - if (useCluster && clusters.length > 0) { - // Wähle ein zufälliges Cluster - const cluster = clusters[Math.floor(Math.random() * clusters.length)]; - const angle = Math.random() * Math.PI * 2; - const distance = Math.random() * cluster.radius; - - // Platziere in der Nähe des Clusters mit einiger Streuung - x = cluster.x + Math.cos(angle) * distance; - y = cluster.y + Math.sin(angle) * distance; - - // Stelle sicher, dass es innerhalb des Bildschirms bleibt - x = Math.max(0, Math.min(width, x)); - y = Math.max(0, Math.min(height, y)); - } else { - // Zufällige Position außerhalb von Clustern - x = Math.random() * width; - y = Math.random() * height; - } - - // Bestimme die Knotengröße - wichtigere Knoten (in Clustern) sind deutlich größer - const nodeImportance = useCluster ? 1.8 : 0.6; // Erhöhter Kontrast zwischen Cluster- und Nicht-Cluster-Knoten - const size = this.config.nodeSize * nodeImportance + Math.random() * this.config.nodeVariation * 1.5; // Größere Variationsbreite + // 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 = { - x: x, - y: y, - size: size, + 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) * this.config.animationSpeed, - y: (Math.random() - 0.5) * this.config.animationSpeed + x: (Math.random() - 0.5) * 0.2, + y: (Math.random() - 0.5) * 0.2 }, - pulsePhase: Math.random() * Math.PI * 2, // Random starting phase - connections: [], - isActive: Math.random() < 0.3, // Some nodes start active for neural firing effect - lastFired: 0, // For neural firing animation - firingRate: 1000 + Math.random() * 4000 // Random firing rate in ms + lastPulse: 0, + pulseInterval: 5000 + Math.random() * 10000, // Zufälliges Pulsieren + connections: [] }; this.nodes.push(node); @@ -305,1792 +159,243 @@ class NeuralNetworkBackground { createConnections() { this.connections = []; - this.flows = []; // Reset flows - // Create connections between nearby nodes + // Verbindungen zwischen Knoten erstellen for (let i = 0; i < this.nodes.length; i++) { const nodeA = this.nodes[i]; - nodeA.connections = []; - // Sortiere andere Knoten nach Entfernung für bevorzugte nahe Verbindungen - const potentialConnections = []; - - for (let j = 0; j < this.nodes.length; j++) { - if (i === j) continue; - + for (let j = i + 1; j < this.nodes.length; j++) { const nodeB = this.nodes[j]; - const dx = nodeB.x - nodeA.x; - const dy = nodeB.y - nodeA.y; + + const dx = nodeA.x - nodeB.x; + const dy = nodeA.y - nodeB.y; const distance = Math.sqrt(dx * dx + dy * dy); - // Erhöhe die Wahrscheinlichkeit für Verbindungen - if (distance < this.config.connectionDistance * 1.5) { // Erweiterter Verbindungsradius - potentialConnections.push({ - index: j, - distance: distance, - // Zufälliger Bonus für mehr Verbindungen - connectionBonus: Math.random() * 0.5 - }); - } - } - - // Sortiere nach Entfernung und Verbindungsbonus mit erweiterten Kriterien - potentialConnections.sort((a, b) => { - // Basis-Score aus Entfernung und Verbindungsbonus - const baseScoreA = a.distance * (1 - a.connectionBonus); - const baseScoreB = b.distance * (1 - b.connectionBonus); - - // Zusätzliche Faktoren für dynamischere Verbindungen - const randomFactorA = Math.random() * 0.2; // Leichte Zufallskomponente - const randomFactorB = Math.random() * 0.2; - - // Kombinierter Score mit Zufallskomponente - const scoreA = baseScoreA * (1 - randomFactorA); - const scoreB = baseScoreB * (1 - randomFactorB); - - return scoreA - scoreB; - }); - - // Erhöhe die maximale Anzahl der Verbindungen signifikant - const maxConn = Math.min( - this.config.maxConnections * 2.5, // Deutlich mehr Verbindungen erlaubt - potentialConnections.length, - 3 + Math.floor(Math.random() * this.config.maxConnections * 2) // Noch mehr zufällige Verbindungen - ); - for (let c = 0; c < maxConn; c++) { - const connection = potentialConnections[c]; - const j = connection.index; - const nodeB = this.nodes[j]; - const distance = connection.distance; - - // Stärkere Verbindungen durch den Bonus - const connectionStrength = Math.max(0, 1 - distance / (this.config.connectionDistance * 1.5)) + connection.connectionBonus; - const connOpacity = connectionStrength * this.config.connectionOpacity; - - // Check if connection already exists - if (!this.connections.some(conn => - (conn.from === i && conn.to === j) || (conn.from === j && conn.to === i) - )) { - // Neue Verbindung mit Ein-/Ausblend-Status - this.connections.push({ + if (distance < this.config.connectionDistance) { + const connection = { + id: `${i}-${j}`, from: i, to: j, distance: distance, - opacity: connOpacity, - strength: connectionStrength, - hasFlow: false, - lastActivated: 0, - progress: 0, // Verbindung beginnt unsichtbar und baut sich auf - fadeState: 'in', // Status: 'in' = einblenden, 'visible' = sichtbar, 'out' = ausblenden - fadeStartTime: Date.now(), // Wann der Fade-Vorgang gestartet wurde - fadeTotalDuration: this.config.linesFadeDuration, // Feste Dauer ohne Zufall - visibleDuration: 10000, // Feste Sichtbarkeitsdauer ohne Zufall - fadeProgress: 0, // Aktueller Fortschritt des Fade-Vorgangs (0-1) - buildSpeed: 0 // Geschwindigkeit, mit der die Verbindung aufgebaut wird - }); - nodeA.connections.push(j); - nodeB.connections.push(i); + 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.animationFrameId = requestAnimationFrame(this.animate.bind(this)); + this.animate(); } animate() { - // Update nodes + 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); - const now = Date.now(); - // Berechne den Fadeout-Faktor bei Deaktivierung (1.0 bis 0.0) - const fadeoutFactor = this.isDestroying ? parseFloat(this.canvas.style.opacity) || 0.98 : 1.0; - - // Für jeden Flow eine eindeutige ID vergeben, falls noch nicht vorhanden - for (const flow of this.flows) { - if (!flow.id) { - flow.id = now + '_' + Math.random().toString(36).substr(2, 9); - } - } - - // Setze zunächst alle Neuronen auf inaktiv + // Knoten bewegen for (let i = 0; i < this.nodes.length; i++) { - // Update pulse phase for smoother animation const node = this.nodes[i]; - // Bei Ausblendung langsamer pulsieren lassen - const pulseSpeed = this.isDestroying - ? this.config.pulseSpeed * 0.7 - : this.config.pulseSpeed * (1 + (node.connections.length * 0.04)); - - node.pulsePhase += pulseSpeed; + node.x += node.speed.x; + node.y += node.speed.y; - // Bei Ausblendung Bewegung reduzieren - const movementFactor = this.isDestroying ? 0.3 : 1.0; - - // Animate node position with gentler movement - node.x += node.speed.x * (Math.sin(now * 0.0003) * 0.4 + 0.4) * movementFactor; - node.y += node.speed.y * (Math.cos(now * 0.0002) * 0.4 + 0.4) * movementFactor; - - // Keep nodes within bounds + // Begrenzung am Rand if (node.x < 0 || node.x > width) { node.speed.x *= -1; - node.x = Math.max(0, Math.min(width, node.x)); } + if (node.y < 0 || node.y > height) { node.speed.y *= -1; - node.y = Math.max(0, Math.min(height, node.y)); } - // Knoten bleiben länger aktiv nach dem Auslösen (2000ms für langanhaltende Aktivierung) - if (node.lastFired && now - node.lastFired < 2000) { - node.isActive = true; + // 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 { - node.isActive = false; + connection.flowProgress = progress; } } - // Aktiviere Neuronen basierend auf aktiven Flows - for (const flow of this.flows) { - // Aktiviere den Quellknoten (der Flow geht von ihm aus) - if (flow.sourceNodeIdx !== undefined) { - this.nodes[flow.sourceNodeIdx].isActive = true; - this.nodes[flow.sourceNodeIdx].lastFired = now; - } + // 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); - // Aktiviere den Zielknoten nur, wenn der Flow weit genug fortgeschritten ist - if (flow.targetNodeIdx !== undefined && flow.progress > 0.7) { - this.nodes[flow.targetNodeIdx].isActive = true; - this.nodes[flow.targetNodeIdx].lastFired = now; - } - } - - // Minimale Anzahl aktiver Flows sicherstellen für endlose Animation - const minRequiredFlows = Math.min(15, Math.floor(this.nodes.length * 0.1)); // Mindestens 10% der Knoten haben aktive Flows - const shouldCreateNewFlows = this.flows.length < minRequiredFlows; - - // Chance auf neue Flows - const flowChance = this.isDestroying ? 0.005 : (shouldCreateNewFlows ? 0.1 : 0.04); - - // Neue Flows mit höherer Wahrscheinlichkeit erzeugen, wenn zu wenige aktiv sind - if (Math.random() < flowChance) { - this.initiateMajorFlowCascade(now, fadeoutFactor); - } - - // Aktualisiere die Ein-/Ausblendung von Verbindungen - this.updateConnectionsFading(now); - - // Update flows with proper fading - this.updateFlows(now); - - // Regelmäßig neue Flows erstellen, um das Netzwerk lebendig zu halten - if (Math.random() < 0.07) { // Hohe Chance für kontinuierliche Animation - this.createNewFlow(now); - } - - // Rekonfiguriere Verbindungen sehr selten für ein sich langsam entwickelndes Netzwerk - // Dies verhindert, dass das Netzwerk statisch wird - if (Math.random() < 0.0005) { // Sehr seltene Neuverbindungen (0.05% Chance pro Frame) - this.createConnections(); - } - - // Render - if (this.useWebGL) { - this.renderWebGL(now); - } else { - this.renderCanvas(now); - } - - // Continue animation - garantiere endlose Animation - this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); - } - - // Neue Methode: Starte eine größere Kaskade von Flows an strategischen Punkten - initiateMajorFlowCascade(now, fadeoutFactor = 1.0) { - // Finde gut vernetzte Knoten als Startpunkte - const topNodes = [...this.nodes] - .map((node, index) => ({ node, index, connections: node.connections.length })) - .filter(item => item.connections > 2) // Nur Knoten mit mindestens 3 Verbindungen - .sort((a, b) => b.connections - a.connections); // Sortiere nach Verbindungsanzahl - - if (topNodes.length === 0) return; - - // Wähle 1-3 Startknoten basierend auf Netzwerkgröße - const startNodeCount = Math.max(1, Math.min(3, Math.floor(topNodes.length / 20))); - - for (let i = 0; i < startNodeCount; i++) { - // Wähle einen der Top-Knoten, mit Präferenz für die am besten vernetzten - const nodeIndex = Math.floor(Math.random() * Math.min(10, topNodes.length)); - const startNodeData = topNodes[nodeIndex]; - if (!startNodeData) continue; - - const startNode = this.nodes[startNodeData.index]; - if (!startNode || startNode.connections.length === 0) continue; - - // Aktiviere den Startknoten - startNode.isActive = true; - startNode.lastFired = now; - startNode.lastCascade = now; // Markiere den Zeitpunkt der letzten Kaskade - - // Starte Flows an mehreren Verbindungen dieses Knotens - const connectionsToActivate = Math.min( - Math.ceil(startNode.connections.length / 2), // Bis zu 50% der Verbindungen - 5 // Aber maximal 5 - ); - - // Sorgfältige Auswahl der Verbindungen für eine optimale Weitergabe - const selectedConnections = []; - const allConnections = [...startNode.connections]; - - // Sortiere Verbindungen nach Aktivierungszeit - bevorzuge solche, die länger nicht aktiviert wurden - const sortedConnections = allConnections - .map(connIdx => { - const connectedNode = this.nodes[connIdx]; - const conn = this.connections.find(c => - (c.from === startNodeData.index && c.to === connIdx) || - (c.from === connIdx && c.to === startNodeData.index) - ); - - return { - connIdx, - nodeConnections: connectedNode ? connectedNode.connections.length : 0, - lastActivated: conn ? conn.lastActivated || 0 : 0 - }; - }) - .sort((a, b) => { - // Priorisiere Verbindungen, die länger nicht aktiviert wurden und zu gut vernetzten Knoten führen - const timeFactorA = (now - a.lastActivated) / 10000; // Zeit seit letzter Aktivierung in 10-Sekunden-Einheiten - const timeFactorB = (now - b.lastActivated) / 10000; - - return (timeFactorB + b.nodeConnections * 0.1) - (timeFactorA + a.nodeConnections * 0.1); - }); + if (availableConnections.length > 0) { + const randomIndex = Math.floor(Math.random() * availableConnections.length); + const connection = availableConnections[randomIndex]; - // Wähle die besten Verbindungen aus - for (let j = 0; j < connectionsToActivate && j < sortedConnections.length; j++) { - selectedConnections.push(sortedConnections[j].connIdx); - } - - // Startversatz für Flows eines Knotens - Verzögere die Flows leicht für Welleneffekt - const baseDelay = 100; // Basisverzögerung in ms - - // Erstelle Flows mit leichter Verzögerung zwischen ihnen - selectedConnections.forEach((connIdx, idx) => { - const conn = this.connections.find(c => - (c.from === startNodeData.index && c.to === connIdx) || - (c.from === connIdx && c.to === startNodeData.index) - ); + // 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]); - if (conn) { - // Verbindung aktivieren - conn.lastActivated = now; - - // Stelle sicher, dass die Verbindung dauerhaft sichtbar bleibt - if (conn.fadeState !== 'visible') { - conn.fadeState = 'visible'; - conn.fadeStartTime = now; - conn.visibleDuration = 600000 + Math.random() * 300000; // 10-15 Minuten sichtbar - } - - // Verbindung aufbauen - if (conn.progress < 1) { - conn.buildSpeed = 0.03 + Math.random() * 0.02; - } - - // Leichte Verzögerung zwischen den Flows für Welleneffekt - const delay = baseDelay + idx * 150; - - setTimeout(() => { - // Erstelle nur, wenn die Animation noch läuft - if (!this.isDestroying && this.animationFrameId) { - // Bestimme die Richtung des Flows - const direction = conn.from === startNodeData.index; - - // Erstelle den Flow - this.flows.push({ - id: now + '_cascade_' + idx + '_' + Math.random().toString(36).substr(2, 9), - connection: conn, - progress: 0, - direction: direction, - length: this.config.flowLength * (0.8 + Math.random() * 0.4), - creationTime: Date.now(), - totalDuration: 700 + Math.random() * 400, - sourceNodeIdx: direction ? conn.from : conn.to, - targetNodeIdx: direction ? conn.to : conn.from, - fadeFactor: fadeoutFactor, - isForwarded: false, - hasForwarded: false, - generation: 1, // Erste Generation - isCascadeRoot: true // Markiere als Wurzel einer Kaskade - }); - } - }, delay); - } - }); - } - } - - // Updated method to update flow animations with proper fading - updateFlows(now) { - // Track new flows to add after the loop to avoid modifying the array during iteration - const newFlows = []; - - // Zähle aktive Flows - const activeFlows = this.flows.length; - - // Wenn zu wenige Flows aktiv sind, starte neue Major Flow Cascades - if (activeFlows < this.config.minActiveFlows) { - this.initiateMajorFlowCascade(now); - } - - // Update existing flows - for (let i = this.flows.length - 1; i >= 0; i--) { - const flow = this.flows[i]; - - // Calculate flow age for fading effects - const flowAge = now - flow.creationTime; - const flowProgress = flowAge / flow.totalDuration; - - // Update flow progress with reduced speed für schlangenartige Bewegung - // Längere Generationen bewegen sich noch langsamer für sichtbare "Schlangen" - const generationSlowdown = Math.max(0.7, 1.0 - (flow.generation || 1) * 0.015); - flow.progress += (this.config.flowSpeed * generationSlowdown) / flow.connection.distance; - - // Aktiviere Quell- und Zielknoten basierend auf Flow-Fortschritt - if (flow.sourceNodeIdx !== undefined) { - // Quellknoten immer aktivieren, solange der Flow aktiv ist - this.nodes[flow.sourceNodeIdx].isActive = true; - this.nodes[flow.sourceNodeIdx].lastFired = now; - } - - // Zielknoten aktivieren und Weiterleitung starten, wenn der Flow ihn erreicht hat - if (flow.targetNodeIdx !== undefined && flow.progress > 0.80) { - const targetNode = this.nodes[flow.targetNodeIdx]; - targetNode.isActive = true; - targetNode.lastFired = now; - - // Stelle sicher, dass die Verbindung dauerhaft sichtbar bleibt - flow.connection.lastActivated = now; - if (flow.connection.fadeState !== 'visible') { - flow.connection.fadeState = 'visible'; - flow.connection.fadeStartTime = now; - flow.connection.visibleDuration = 600000 + Math.random() * 300000; // 10-15 Minuten sichtbar - } - - // Flow ist am Ziel angekommen, starte Weiterleitung zu anderen Knoten - // Jeder Flow leitet genau einmal weiter - if (!flow.hasForwarded && targetNode.connections.length > 0) { - flow.hasForwarded = true; - - // Durch reduzierte Anzahl der Weiterleitungen schaffen wir einen endloseren Eindruck - const connCount = Math.min(3, targetNode.connections.length); - const availableConnections = [...targetNode.connections]; - - // Keine Weiterleitung an die Quelle des Flows zurück - if (flow.sourceNodeIdx !== undefined && availableConnections.length > 1) { - const sourceIndex = availableConnections.indexOf(flow.sourceNodeIdx); - if (sourceIndex >= 0) { - availableConnections.splice(sourceIndex, 1); - } - } - - // Wenn noch Verbindungen übrig sind, leite weiter - if (availableConnections.length > 0) { - // Priorisiere ungenutzte Pfade für ein dynamischeres Netzwerk - const sortedConnections = availableConnections.map(nodeIdx => { - const conn = this.connections.find(c => - (c.from === flow.targetNodeIdx && c.to === nodeIdx) || - (c.from === nodeIdx && c.to === flow.targetNodeIdx) - ); - - const timeSinceLastActivation = conn ? now - (conn.lastActivated || 0) : 0; - const connectionCount = this.nodes[nodeIdx].connections.length; - - return { - nodeIdx, - connectionCount, - timeSinceLastActivation, - score: (timeSinceLastActivation * 0.00001) + - (connectionCount * 0.2) + - (Math.random() * 0.5) - }; - }).sort((a, b) => b.score - a.score); - - // Wähle priorisierte Verbindungen - for (let j = 0; j < connCount && j < sortedConnections.length; j++) { - const nextNodeIdx = sortedConnections[j].nodeIdx; - const nextConn = this.connections.find(c => - (c.from === flow.targetNodeIdx && c.to === nextNodeIdx) || - (c.from === nextNodeIdx && c.to === flow.targetNodeIdx) - ); - - if (nextConn) { - nextConn.lastActivated = now; - - if (nextConn.fadeState !== 'visible') { - nextConn.fadeState = 'visible'; - nextConn.fadeStartTime = now; - nextConn.visibleDuration = 600000 + Math.random() * 300000; - } - - if (nextConn.progress < 1) { - nextConn.buildSpeed = 0.04 + Math.random() * 0.02; - } - - const nextGeneration = (flow.generation || 1) + 1; - - if (nextGeneration <= this.config.maxFlowGenerations) { - const direction = nextConn.from === flow.targetNodeIdx; - - // Berechne Verzögerung basierend auf Verbindungslänge - const connectionLength = Math.sqrt( - Math.pow(this.nodes[nextConn.to].x - this.nodes[nextConn.from].x, 2) + - Math.pow(this.nodes[nextConn.to].y - this.nodes[nextConn.from].y, 2) - ); - - const baseDelay = 80; - const lengthFactor = Math.min(1.2, connectionLength / 200); - const genFactor = Math.max(0.2, 1.0 - (nextGeneration * 0.005)); - const delay = baseDelay * lengthFactor * genFactor; - - setTimeout(() => { - if (!this.isDestroying && this.animationFrameId) { - newFlows.push({ - id: now + '_' + Math.random().toString(36).substr(2, 9), - connection: nextConn, - progress: 0, - direction: direction, - length: this.config.flowLength * (0.9 + Math.random() * 0.2), - creationTime: Date.now(), - totalDuration: 600 + Math.random() * 300 + (nextGeneration * 5), - sourceNodeIdx: direction ? nextConn.from : nextConn.to, - targetNodeIdx: direction ? nextConn.to : nextConn.from, - fadeFactor: flow.fadeFactor || 1.0, - isForwarded: false, - generation: nextGeneration, - hasForwarded: false, - parentFlow: flow.id - }); - } - }, delay); - } - } - } - } - } - } - - // Stellen Sie sicher, dass die Verbindung aktiv bleibt - flow.connection.lastActivated = now; - - if (flow.connection.fadeState === 'visible') { - flow.connection.visibleDuration = Math.max( - flow.connection.visibleDuration || 300000, - 600000 + Math.random() * 300000 - ); - } - - // Remove completed flows, but ensure parent-child relationships for visible "snakes" - const isComplete = flow.progress > 1.0 || flowProgress >= 1.0; - const hasMaxGeneration = flow.generation && flow.generation > this.config.maxFlowGenerations; - - if (isComplete || hasMaxGeneration) { - this.flows.splice(i, 1); - } - } - - // Füge alle neuen Flows hinzu - this.flows.push(...newFlows); - } - - // Aktualisiert die Ein-/Ausblendung von Verbindungen mit deutlich langsameren Übergängen - updateConnectionsFading(now) { - for (const connection of this.connections) { - const elapsedTime = now - connection.fadeStartTime; - - // Update connection fade status - if (connection.fadeState === 'in') { - // Einblenden - langsamer für sanfteren Aufbau - connection.fadeProgress = Math.min(1.0, elapsedTime / (connection.fadeTotalDuration * 1.5)); - if (connection.fadeProgress >= 1.0) { - connection.fadeState = 'visible'; - connection.fadeStartTime = now; - } - } else if (connection.fadeState === 'visible') { - // Verbindung ist vollständig sichtbar - EXTREM lange Sichtbarkeitsdauer - // für permanente Netzwerkstrukturen - if (elapsedTime > connection.visibleDuration * 3.0) { // Erhöht von 2.5 auf 3.0 - // Prüfe, ob die Verbindung kürzlich aktiviert wurde - if (now - connection.lastActivated < 120000) { // 2 Minuten Aktivitätsschutz - // Wenn kürzlich aktiviert, verlängere die Sichtbarkeit - connection.fadeStartTime = now; - } else { - // Nur sehr alte, inaktive Verbindungen langsam ausblenden - connection.fadeState = 'out'; - connection.fadeStartTime = now; - connection.fadeProgress = 1.0; - } - } - } else if (connection.fadeState === 'out') { - // Ausblenden - EXTREM langsam für dauerhaft sichtbare Strukturen - connection.fadeProgress = Math.max(0.25, 1.0 - (elapsedTime / (connection.fadeTotalDuration * 10.0))); // Erhöht von 8.0 auf 10.0 - - // Verbindungen bleiben dauerhaft stark sichtbar - if (connection.fadeProgress <= 0.25) { // Minimum erhöht von 0.15 auf 0.25 - // Verbindungen niemals komplett verschwinden lassen - connection.fadeState = 'in'; - connection.fadeStartTime = now; - connection.fadeProgress = 0.25; // Höhere Grundsichtbarkeit - connection.visibleDuration = 600000 + Math.random() * 300000; // 10-15 Minuten sichtbar - } - } else if (connection.fadeState === 'hidden') { - // Keine Verbindungen verstecken, alle sichtbar halten - connection.fadeState = 'in'; - connection.fadeStartTime = now; - connection.fadeProgress = 0.25; // Höhere Grundsichtbarkeit - } - - // Verbindungen schneller vollständig aufbauen für permanentes Netzwerk - if (connection.progress < 1) { - // Erhöhte Basisgeschwindigkeit für schnelleren Aufbau - const baseBuildSpeed = 0.008; // Erhöht für schnelleren permanenten Aufbau - let buildSpeed = connection.buildSpeed || baseBuildSpeed; - - // Wenn kürzlich aktiviert, noch schneller aufbauen - if (now - connection.lastActivated < 2000) { - buildSpeed = Math.max(buildSpeed, 0.015); // Erhöht für schnelleren Aufbau - } - - connection.progress += buildSpeed; - - if (connection.progress > 1) { - connection.progress = 1; - // Zurücksetzen der Aufbaugeschwindigkeit - connection.buildSpeed = 0; - } + this.activeConnections.add(connection.id); } } } - // Updated method to create flow animations with timing info - createNewFlow(now) { - if (this.connections.length === 0 || this.flows.length >= 8) return; - - // Verbesserte Auswahl zufälliger Verbindungen mit Präferenz für hoch vernetzte Knoten - // Sammle alle Verbindungen und bewerte sie nach der Anzahl ihrer Verbindungen - const weightedConnections = this.connections.map((conn, index) => { - const fromNode = this.nodes[conn.from]; - const toNode = this.nodes[conn.to]; - const connectionCount = fromNode.connections.length + toNode.connections.length; - return { - index, - connectionCount, - weight: connectionCount + Math.random() * 5 // Füge Zufall hinzu für Variabilität - }; - }); - - // Sortiere nach Gewicht (Verbindungsanzahl + Zufallsfaktor) - weightedConnections.sort((a, b) => b.weight - a.weight); - - // Wähle eine der Top-Verbindungen - const connectionIdx = weightedConnections[Math.floor(Math.random() * Math.min(15, weightedConnections.length))].index; - const connection = this.connections[connectionIdx]; - - // Verbindung als "im Aufbau" markieren, wenn sie noch nicht vollständig ist - if (connection.progress < 1) { - connection.buildSpeed = 0.02 + Math.random() * 0.01; // Schnellerer Aufbau während eines Blitzes - } - - // Stelle sicher, dass die Verbindung dauerhaft sichtbar bleibt - connection.lastActivated = now; - if (connection.fadeState !== 'visible') { - connection.fadeState = 'visible'; - connection.fadeStartTime = now; - connection.visibleDuration = 150000 + Math.random() * 90000; // 2.5-4 Minuten sichtbar - } - - // Create a new flow along this connection - this.flows.push({ - connection: connection, - progress: 0, - direction: Math.random() > 0.5, // Randomly decide direction - length: this.config.flowLength + Math.random() * 0.1, // Slightly vary lengths - creationTime: now, - totalDuration: 900 + Math.random() * 500, // Längere Gesamtdauer (900-1400ms) - sourceNodeIdx: connection.direction ? connection.from : connection.to, - targetNodeIdx: connection.direction ? connection.to : connection.from, - isForwarded: false, // Dieses Flag ermöglicht Weiterleitung - hasForwarded: false, // Noch nicht weitergeleitet - generation: 1 // Erste Generation - }); - } - - renderWebGL(now) { - this.gl.clear(this.gl.COLOR_BUFFER_BIT); - + 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); - // Fade-Faktor für sanftes Ausblenden - const fadeoutFactor = this.isDestroying ? parseFloat(this.canvas.style.opacity) || 0.98 : 1.0; - - // Select shader program - this.gl.useProgram(this.programInfo.program); - - // Set resolution uniform - this.gl.uniform2f(this.programInfo.uniformLocations.resolution, width, height); - - // Draw connections first (behind nodes) - this.renderConnectionsWebGL(now, fadeoutFactor); - - // Draw flows on top of connections - this.renderFlowsWebGL(now, fadeoutFactor); - - // Draw nodes - this.renderNodesWebGL(now, fadeoutFactor); - } - - renderNodesWebGL(now, fadeoutFactor = 1.0) { - // Prepare node positions for WebGL - const positions = new Float32Array(this.nodes.length * 2); - const sizes = new Float32Array(this.nodes.length); - - for (let i = 0; i < this.nodes.length; i++) { - const node = this.nodes[i]; - positions[i * 2] = node.x; - positions[i * 2 + 1] = node.y; - - // Sanftere Pulsation mit moderaterem Aktivierungsboost - const activationBoost = node.isActive ? 1.2 : 1.0; // Reduziert von 1.3 auf 1.2 - let pulse = (Math.sin(node.pulsePhase) * 0.2 + 1.1) * activationBoost; // Reduzierte Pulsation - - // Größe basierend auf Konnektivität und Wichtigkeit, aber subtiler - const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 0.8; // Reduziert von 1.1 auf 0.8 - sizes[i] = node.size * pulse * connectivityFactor; - } - - // Bind position buffer - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, // components per vertex - this.gl.FLOAT, // data type - false, // normalize - 0, // stride - 0 // offset - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); - - // Bind size buffer - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, sizes, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.pointSize, - 1, // components per vertex - this.gl.FLOAT, // data type - false, // normalize - 0, // stride - 0 // offset - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.pointSize); - - // Enable blending for all nodes - this.gl.enable(this.gl.BLEND); - this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); // Additive blending for glow - - // Draw each node individually with its own color - for (let i = 0; i < this.nodes.length; i++) { - const node = this.nodes[i]; - - // Set node color - sanftere Hervorhebung von aktiven Knoten - const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; - const nodeColor = this.hexToRgb(colorObj.nodeColor); - const nodePulseColor = this.hexToRgb(colorObj.nodePulse); - - // Use pulse color for active nodes - let r = nodeColor.r / 255; - let g = nodeColor.g / 255; - let b = nodeColor.b / 255; - - // Active nodes get slightly brighter color - noch subtiler - if (node.isActive) { - r = (r * 0.8 + nodePulseColor.r / 255 * 0.2); // Reduziert von 0.7/0.3 auf 0.8/0.2 - g = (g * 0.8 + nodePulseColor.g / 255 * 0.2); - b = (b * 0.8 + nodePulseColor.b / 255 * 0.2); - } - - // Noch subtilere Knoten mit weiter reduzierter Opazität - // Berücksichtige fadeoutFactor für sanftes Ausblenden - const nodeOpacity = node.isActive ? 0.65 : 0.55; // Reduziert von 0.75/0.65 auf 0.65/0.55 - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - r, g, b, - nodeOpacity * fadeoutFactor // Reduzierte Opazität während des Ausblendprozesses - ); - - // Draw each node individually for better control - this.gl.drawArrays(this.gl.POINTS, i, 1); - } - } - - renderConnectionsWebGL(now, fadeoutFactor = 1.0) { - for (const connection of this.connections) { - // Überspringe Verbindungen, die komplett unsichtbar sind - if (connection.fadeState === 'hidden' || connection.fadeProgress <= 0) continue; - - const fromNode = this.nodes[connection.from]; - const toNode = this.nodes[connection.to]; - const progress = connection.progress || 1; - const x1 = fromNode.x; - const y1 = fromNode.y; - const x2 = fromNode.x + (toNode.x - fromNode.x) * progress; - const y2 = fromNode.y + (toNode.y - fromNode.y) * progress; - - // Calculate opacity based on fade state - let opacity = connection.opacity * connection.fadeProgress * 0.7; // Reduzierte Gesamtopazität auf 70% - - // Erhöhe kurzzeitig die Opazität bei kürzlich aktivierten Verbindungen - if (connection.lastActivated && now - connection.lastActivated < 800) { - const timeFactor = 1 - ((now - connection.lastActivated) / 800); - opacity = Math.max(opacity, timeFactor * 0.25); // Reduzierte Highlight-Opazität auf 25% - } - - // Berücksichtige fadeoutFactor für sanftes Ausblenden - opacity *= fadeoutFactor; - - const positions = new Float32Array([ - x1, y1, - x2, y2 - ]); - - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0 - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); - this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize); - - // Set color with calculated opacity - const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; - const connColor = this.hexToRgb(colorObj.connectionColor); - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - connColor.r / 255, - connColor.g / 255, - connColor.b / 255, - opacity - ); - - this.gl.enable(this.gl.BLEND); - this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); - this.gl.lineWidth(this.config.linesWidth); - this.gl.drawArrays(this.gl.LINES, 0, 2); - } - } - - // New method to render the flowing animations with subtilerer Appearance - renderFlowsWebGL(now, fadeoutFactor = 1.0) { - // Für jeden Flow einen dezenten, echten Blitz als Zickzack zeichnen und Funken erzeugen - for (const flow of this.flows) { - const connection = flow.connection; - const fromNode = this.nodes[connection.from]; - const toNode = this.nodes[connection.to]; - const startProgress = flow.progress; - const endProgress = Math.min(1, startProgress + flow.length); - if (startProgress >= 1 || endProgress <= 0) continue; - const direction = flow.direction ? 1 : -1; - let p1, p2; - if (direction > 0) { - p1 = { - x: fromNode.x + (toNode.x - fromNode.x) * startProgress, - y: fromNode.y + (toNode.y - fromNode.y) * startProgress - }; - p2 = { - x: fromNode.x + (toNode.x - fromNode.x) * endProgress, - y: fromNode.y + (toNode.y - fromNode.y) * endProgress - }; - } else { - p1 = { - x: toNode.x + (fromNode.x - toNode.x) * startProgress, - y: toNode.y + (fromNode.y - toNode.y) * startProgress - }; - p2 = { - x: toNode.x + (fromNode.x - toNode.x) * endProgress, - y: toNode.y + (fromNode.y - toNode.y) * endProgress - }; - } - // Zickzack-Blitz generieren - const zigzag = this.generateZigZagPoints(p1, p2, 7, 8); // Reduzierte Zickzack-Amplitude - for (let i = 0; i < zigzag.length - 1; i++) { - const positions = new Float32Array([ - zigzag[i].x, zigzag[i].y, - zigzag[i + 1].x, zigzag[i + 1].y - ]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0 - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); - this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize); - // Dezenter, leuchtender Blitz - const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; - const flowColor = this.hexToRgb(colorObj.flowColor); - // Definiere fadeFactor als 1.0, falls nicht von flow definiert - // Berücksichtige auch den fadeoutFactor für das Ausblenden der gesamten Animation - const fadeFactor = (flow.fadeFactor || 1.0) * fadeoutFactor; - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - flowColor.r / 255, - flowColor.g / 255, - flowColor.b / 255, - 0.55 * fadeFactor // Reduziert von 0.7 auf 0.55 für subtilere Blitze - ); - this.gl.enable(this.gl.BLEND); - this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); - this.gl.lineWidth(1.2); // Schmaler Blitz - this.gl.drawArrays(this.gl.LINES, 0, 2); - } - // Funken erzeugen - subtilere elektrische Funken - const sparks = this.generateSparkPoints(zigzag, 5 + Math.floor(Math.random() * 3)); // Weniger Funken - for (const spark of sparks) { - // Helles Weiß-Blau für elektrische Funken, aber dezenter - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - 0.85, 0.92, 0.98, 0.5 * fadeoutFactor // Reduzierte Opazität auf 50% - ); - - // Position für den Funken setzen - const sparkPos = new Float32Array([spark.x, spark.y]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, sparkPos, this.gl.STATIC_DRAW); - - // Punktgröße setzen - kleinere Funken - const sizes = new Float32Array([spark.size * 2.5]); // Reduziert von 3.0 auf 2.5 - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, sizes, this.gl.STATIC_DRAW); - - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0 - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); - - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.pointSize, - 1, - this.gl.FLOAT, - false, - 0, - 0 - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.pointSize); - - this.gl.drawArrays(this.gl.POINTS, 0, 1); - } - } - } - - renderCanvas(now) { - // Clear canvas - const width = this.canvas.width / (window.devicePixelRatio || 1); - const height = this.canvas.height / (window.devicePixelRatio || 1); - - // Fade-Faktor für sanftes Ausblenden im Canvas-Modus - const fadeoutFactor = this.isDestroying ? parseFloat(this.canvas.style.opacity) || 0.98 : 1.0; - - this.ctx.clearRect(0, 0, width, height); - - // Set background - const backgroundColor = this.isDarkMode - ? this.darkModeColors.background - : this.lightModeColors.background; - - this.ctx.fillStyle = backgroundColor; + // Hintergrund löschen + this.ctx.fillStyle = colors.background; this.ctx.fillRect(0, 0, width, height); - // Draw connections with fade effects - const connectionColor = this.isDarkMode - ? this.darkModeColors.connectionColor - : this.lightModeColors.connectionColor; - + // Verbindungen zeichnen (statisch) + this.ctx.strokeStyle = colors.connectionColor; + this.ctx.lineWidth = 1.2; + for (const connection of this.connections) { - // Überspringe Verbindungen, die komplett unsichtbar sind - if (connection.fadeState === 'hidden' || connection.fadeProgress <= 0) continue; + 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]; - // Zeichne nur den Teil der Verbindung, der schon aufgebaut wurde - const progress = connection.progress || 0; - if (progress <= 0) continue; // Skip if no progress yet + // Glühen-Effekt + this.ctx.globalAlpha = Math.sin(connection.flowProgress * Math.PI) * 0.8; - const x1 = fromNode.x; - const y1 = fromNode.y; - - // Endpunkt basiert auf dem aktuellen Fortschritt - const x2 = x1 + (toNode.x - x1) * progress; - const y2 = y1 + (toNode.y - y1) * progress; - - // Zeichne die unterliegende Linie mit Ein-/Ausblendung + // Linie zeichnen this.ctx.beginPath(); - this.ctx.moveTo(x1, y1); - this.ctx.lineTo(x2, y2); - - const rgbColor = this.hexToRgb(connectionColor); - - // Calculate opacity based on fade state - let opacity = connection.opacity * connection.fadeProgress; - - // Erhöhe kurzzeitig die Opazität bei kürzlich aktivierten Verbindungen - if (connection.lastActivated && now - connection.lastActivated < 800) { - const timeFactor = 1 - ((now - connection.lastActivated) / 800); - opacity = Math.max(opacity, timeFactor * this.config.linesOpacity); - } - - // Berücksichtige fadeoutFactor für sanftes Ausblenden - opacity *= fadeoutFactor; - - this.ctx.strokeStyle = `rgba(${rgbColor.r}, ${rgbColor.g}, ${rgbColor.b}, ${opacity})`; - this.ctx.lineWidth = this.config.linesWidth; - - // Leichter Glow-Effekt für die Linien - if (opacity > 0.1) { - this.ctx.shadowColor = `rgba(${rgbColor.r}, ${rgbColor.g}, ${rgbColor.b}, ${0.15 * fadeoutFactor})`; - this.ctx.shadowBlur = 3; - } else { - this.ctx.shadowBlur = 0; - } - + this.ctx.moveTo(fromNode.x, fromNode.y); + this.ctx.lineTo(toNode.x, toNode.y); this.ctx.stroke(); - // Zeichne einen Fortschrittspunkt am Ende der sich aufbauenden Verbindung - if (progress < 0.95 && connection.fadeProgress > 0.5) { - this.ctx.beginPath(); - this.ctx.arc(x2, y2, 1.5, 0, Math.PI * 2); - this.ctx.fillStyle = `rgba(${rgbColor.r}, ${rgbColor.g}, ${rgbColor.b}, ${opacity * 1.3})`; - this.ctx.fill(); - } + // 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.shadowBlur = 0; // Reset shadow for other elements - } - - // Draw flows with fading effect - this.renderFlowsCanvas(now, fadeoutFactor); - - // Draw nodes with enhanced animations - const nodeColor = this.isDarkMode - ? this.darkModeColors.nodeColor - : this.lightModeColors.nodeColor; - - const nodePulse = this.isDarkMode - ? this.darkModeColors.nodePulse - : this.lightModeColors.nodePulse; - - for (const node of this.nodes) { - // Verbesserte Pulsation mit Aktivierungsboost - const activationBoost = node.isActive ? 1.7 : 1.0; - const pulse = (Math.sin(node.pulsePhase) * 0.45 + 1.4) * activationBoost; - - // Größe basierend auf Konnektivität und Wichtigkeit - const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 1.4; - // Während des Ausblendprozesses die Knotengröße leicht reduzieren - const sizeReduction = this.isDestroying ? 0.85 : 1.0; - const nodeSize = node.size * pulse * connectivityFactor * sizeReduction; - - // Verbesserte Leuchtkraft und Glow-Effekt - const rgbNodeColor = this.hexToRgb(nodeColor); - const rgbPulseColor = this.hexToRgb(nodePulse); - - // Mische Farben basierend auf Aktivierung - let r, g, b; - if (node.isActive) { - r = (rgbNodeColor.r * 0.3 + rgbPulseColor.r * 0.7); - g = (rgbNodeColor.g * 0.3 + rgbPulseColor.g * 0.7); - b = (rgbNodeColor.b * 0.3 + rgbPulseColor.b * 0.7); - } else { - r = rgbNodeColor.r; - g = rgbNodeColor.g; - b = rgbNodeColor.b; - } - - // Äußerer Glow - const glow = this.ctx.createRadialGradient( - node.x, node.y, 0, - node.x, node.y, nodeSize * 4.5 - ); - - // Intensiveres Zentrum und weicherer Übergang - // Berücksichtige fadeoutFactor für sanftes Ausblenden - const activeOpacity = node.isActive ? 0.95 : 0.8; - glow.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${activeOpacity * fadeoutFactor})`); - glow.addColorStop(0.4, `rgba(${r}, ${g}, ${b}, ${0.45 * fadeoutFactor})`); - glow.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`); + this.ctx.globalAlpha = 0.9; + this.ctx.fillStyle = colors.flowColor; this.ctx.beginPath(); - this.ctx.arc(node.x, node.y, nodeSize, 0, Math.PI * 2); - this.ctx.fillStyle = glow; - // Globale Transparenz reduzieren beim Ausblenden - this.ctx.globalAlpha = (node.isActive ? 1.0 : 0.92) * fadeoutFactor; + 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(); - // Innerer Kern für stärkeren Leuchteffekt - if (node.isActive) { + // 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, nodeSize * 0.4, 0, Math.PI * 2); - this.ctx.fillStyle = `rgba(${rgbPulseColor.r}, ${rgbPulseColor.g}, ${rgbPulseColor.b}, ${0.9 * fadeoutFactor})`; + this.ctx.arc(node.x, node.y, node.size + 5 * pulseProgress, 0, Math.PI * 2); this.ctx.fill(); } - - this.ctx.globalAlpha = 1.0; - } - } - - // New method to render flows in Canvas mode with fading - renderFlowsCanvas(now, fadeoutFactor = 1.0) { - if (!this.flows.length) return; - - // Für jeden Flow in der Blitzanimation - for (const flow of this.flows) { - const connection = flow.connection; - const fromNode = this.nodes[connection.from]; - const toNode = this.nodes[connection.to]; - - // Berechne den Fortschritt der Connection und des Flows - const connProgress = connection.progress || 1; - - // Flussrichtung bestimmen - const [startNode, endNode] = flow.direction ? [fromNode, toNode] : [toNode, fromNode]; - - // Berücksichtige den Verbindungs-Fortschritt - const maxDistance = Math.min(connProgress, 1) * Math.sqrt( - Math.pow(endNode.x - startNode.x, 2) + - Math.pow(endNode.y - startNode.y, 2) - ); - - // Berechne den aktuellen Fortschritt mit Ein- und Ausblendung - const flowAge = now - flow.creationTime; - const flowLifetime = flow.totalDuration; - let fadeFactor = 1.0; - - // Sanftere Ein- und Ausblendung für Blitzeffekte - if (flowAge < flowLifetime * 0.3) { - // Einblenden - sanfter und länger - fadeFactor = flowAge / (flowLifetime * 0.3); - } else if (flowAge > flowLifetime * 0.7) { - // Ausblenden - sanfter und länger - fadeFactor = 1.0 - ((flowAge - flowLifetime * 0.7) / (flowLifetime * 0.3)); - } - - // Beim Ausblenden der gesamten Animation auch den Flow-Faktor anpassen - fadeFactor *= fadeoutFactor; - - // Flow-Fortschritt - const startProgress = Math.max(0.0, flow.progress - flow.length); - const endProgress = Math.min(flow.progress, connProgress); - - // Start- und Endpunkte basierend auf dem Fortschritt - const p1 = { - x: startNode.x + (endNode.x - startNode.x) * startProgress, - y: startNode.y + (endNode.y - startNode.y) * startProgress - }; - - const p2 = { - x: startNode.x + (endNode.x - startNode.x) * endProgress, - y: startNode.y + (endNode.y - startNode.y) * endProgress - }; - - if (endProgress > connProgress) continue; - - // Lila Gradient für den Blitz - const gradient = this.ctx.createLinearGradient(p1.x, p1.y, p2.x, p2.y); - gradient.addColorStop(0, 'rgba(255, 0, 255, 0.8)'); - gradient.addColorStop(0.5, 'rgba(200, 0, 255, 0.9)'); - gradient.addColorStop(1, 'rgba(255, 0, 255, 0.8)'); - - this.ctx.save(); - - // Untergrundspur mit stärkerem Glühen - this.ctx.beginPath(); - this.ctx.moveTo(p1.x, p1.y); - this.ctx.lineTo(p2.x, p2.y); - this.ctx.strokeStyle = gradient; - this.ctx.lineWidth = 20.0; - this.ctx.shadowColor = 'rgba(255, 0, 255, 0.4)'; - this.ctx.shadowBlur = 25; - this.ctx.stroke(); - - // Abgerundeter Zickzack-Blitz mit weicheren Kurven - const zigzag = this.generateZigZagPoints(p1, p2, 4, 6, true); - - // Hauptblitz mit Gradient - this.ctx.strokeStyle = gradient; - this.ctx.lineWidth = 1.5; - this.ctx.shadowColor = 'rgba(255, 0, 255, 0.5)'; - this.ctx.shadowBlur = 30; - this.ctx.beginPath(); - this.ctx.moveTo(zigzag[0].x, zigzag[0].y); - for (let i = 1; i < zigzag.length; i++) { - const cp1x = zigzag[i-1].x + (zigzag[i].x - zigzag[i-1].x) * 0.4; - const cp1y = zigzag[i-1].y + (zigzag[i].y - zigzag[i-1].y) * 0.4; - const cp2x = zigzag[i].x - (zigzag[i].x - zigzag[i-1].x) * 0.4; - const cp2y = zigzag[i].y - (zigzag[i].y - zigzag[i-1].y) * 0.4; - this.ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, zigzag[i].x, zigzag[i].y); - } - this.ctx.stroke(); - - // Funken mit lila Glühen - const sparks = this.generateSparkPoints(zigzag, 10 + Math.floor(Math.random() * 6)); - - const sparkGradient = this.ctx.createRadialGradient(0, 0, 0, 0, 0, 10); - sparkGradient.addColorStop(0, 'rgba(255, 0, 255, 0.95)'); - sparkGradient.addColorStop(0.5, 'rgba(200, 0, 255, 0.8)'); - sparkGradient.addColorStop(1, 'rgba(255, 0, 255, 0.6)'); - - for (const spark of sparks) { - this.ctx.beginPath(); - - // Weichere Sternform - const points = 4 + Math.floor(Math.random() * 3); - const outerRadius = spark.size * 2.0; - const innerRadius = spark.size * 0.5; - - for (let i = 0; i < points * 2; i++) { - const radius = i % 2 === 0 ? outerRadius : innerRadius; - const angle = (i * Math.PI) / points; - const x = spark.x + Math.cos(angle) * radius; - const y = spark.y + Math.sin(angle) * radius; - - if (i === 0) { - this.ctx.moveTo(x, y); - } else { - this.ctx.lineTo(x, y); - } - } - - this.ctx.closePath(); - - // Intensives lila Glühen - this.ctx.shadowColor = 'rgba(255, 0, 255, 0.8)'; - this.ctx.shadowBlur = 25; - this.ctx.fillStyle = sparkGradient; - this.ctx.fill(); - // Intensiverer innerer Glüheffekt für ausgewählte Funken mit mehrfacher Schichtung - if (spark.size > 3 && Math.random() > 0.3) { - // Erste Glühschicht - größer und weicher - this.ctx.beginPath(); - this.ctx.arc(spark.x, spark.y, spark.size * 0.8, 0, Math.PI * 2); - this.ctx.fillStyle = this.isDarkMode - ? `rgba(245, 252, 255, ${0.85 * fadeFactor})` - : `rgba(230, 245, 255, ${0.8 * fadeFactor})`; - this.ctx.shadowColor = this.isDarkMode - ? `rgba(200, 225, 255, ${0.7 * fadeFactor})` - : `rgba(180, 220, 255, ${0.6 * fadeFactor})`; - this.ctx.shadowBlur = 15; - this.ctx.fill(); - - // Zweite Glühschicht - kleiner und intensiver - this.ctx.beginPath(); - this.ctx.arc(spark.x, spark.y, spark.size * 0.5, 0, Math.PI * 2); - this.ctx.fillStyle = this.isDarkMode - ? `rgba(255, 255, 255, ${0.95 * fadeFactor})` - : `rgba(240, 250, 255, ${0.9 * fadeFactor})`; - this.ctx.shadowColor = this.isDarkMode - ? `rgba(220, 235, 255, ${0.8 * fadeFactor})` - : `rgba(200, 230, 255, ${0.7 * fadeFactor})`; - this.ctx.shadowBlur = 20; - this.ctx.fill(); - - // Dritte Glühschicht - noch intensiverer Kern - this.ctx.beginPath(); - this.ctx.arc(spark.x, spark.y, spark.size * 0.3, 0, Math.PI * 2); - this.ctx.fillStyle = this.isDarkMode - ? `rgba(255, 255, 255, ${0.98 * fadeFactor})` - : `rgba(245, 252, 255, ${0.95 * fadeFactor})`; - this.ctx.shadowColor = this.isDarkMode - ? `rgba(230, 240, 255, ${0.9 * fadeFactor})` - : `rgba(210, 235, 255, ${0.8 * fadeFactor})`; - this.ctx.shadowBlur = 25; - this.ctx.fill(); - - // Vierte Glühschicht - pulsierender Effekt - const pulseSize = spark.size * (0.2 + Math.sin(Date.now() * 0.01) * 0.1); - this.ctx.beginPath(); - this.ctx.arc(spark.x, spark.y, pulseSize, 0, Math.PI * 2); - this.ctx.fillStyle = this.isDarkMode - ? `rgba(255, 255, 255, ${0.99 * fadeFactor})` - : `rgba(250, 255, 255, ${0.97 * fadeFactor})`; - this.ctx.shadowColor = this.isDarkMode - ? `rgba(240, 245, 255, ${0.95 * fadeFactor})` - : `rgba(220, 240, 255, ${0.85 * fadeFactor})`; - this.ctx.shadowBlur = 30; - this.ctx.fill(); - } - } - - // Deutlicherer und länger anhaltender Fortschrittseffekt an der Spitze des Blitzes - if (endProgress >= connProgress - 0.1 && connProgress < 0.98) { - const tipGlow = this.ctx.createRadialGradient( - p2.x, p2.y, 0, - p2.x, p2.y, 10 - ); - tipGlow.addColorStop(0, `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.85 * fadeFactor})`); - tipGlow.addColorStop(0.5, `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.4 * fadeFactor})`); - tipGlow.addColorStop(1, `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, 0)`); - - this.ctx.fillStyle = tipGlow; - this.ctx.beginPath(); - this.ctx.arc(p2.x, p2.y, 10, 0, Math.PI * 2); - this.ctx.fill(); - } - - this.ctx.restore(); - } - } - - // Helper method to convert hex to RGB - hexToRgb(hex) { - // Remove # if present - hex = hex.replace(/^#/, ''); - - // Handle rgba hex format - let alpha = 1; - if (hex.length === 8) { - alpha = parseInt(hex.slice(6, 8), 16) / 255; - hex = hex.slice(0, 6); } - // Parse hex values - const bigint = parseInt(hex, 16); - const r = (bigint >> 16) & 255; - const g = (bigint >> 8) & 255; - const b = bigint & 255; - - return { r, g, b, a: alpha }; + this.ctx.globalAlpha = 1; } - // Cleanup method mit deutlich sanfterem Ausblenden destroy() { - // Sanfte Ausblendanimation starten + if (this.isDestroying) return; this.isDestroying = true; - // Canvas-Element sanft ausblenden über einen längeren Zeitraum - if (this.canvas) { - this.canvas.style.transition = 'opacity 5s ease-in-out'; // Verlängert von 1.5s auf 5s - this.canvas.style.opacity = '0'; - - // Warte auf das Ende der Übergangsanimation, bevor die Ressourcen freigegeben werden - setTimeout(() => { - // Animation schrittweise verlangsamen statt sofort stoppen - const slowDownAnimation = () => { - // Schrittweise die Animation verlangsamen durch Reduzierung der FPS + // Animation stoppen if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); - - // Animiere mit abnehmender Framerate - setTimeout(() => { - this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); - - // Nach 10 weiteren Sekunden komplett beenden - if (this._destroyStartTime && (Date.now() - this._destroyStartTime > 10000)) { - finalCleanup(); - } else { - slowDownAnimation(); - } - }, 500); // Zunehmende Verzögerung zwischen Frames - } - }; - - // Endgültige Bereinigung nach dem vollständigen Ausblenden - const finalCleanup = () => { - // Animation stoppen - if (this.animationFrameId) { - cancelAnimationFrame(this.animationFrameId); - this.animationFrameId = null; - } - - // Event-Listener entfernen - window.removeEventListener('resize', this.resizeCanvas.bind(this)); - - // Canvas-Element aus dem DOM entfernen, nachdem es ausgeblendet wurde - if (this.canvas && this.canvas.parentNode) { - this.canvas.parentNode.removeChild(this.canvas); } - // WebGL-Ressourcen bereinigen - if (this.gl) { - this.gl.deleteBuffer(this.positionBuffer); - this.gl.deleteBuffer(this.sizeBuffer); - this.gl.deleteProgram(this.shaderProgram); - } - - console.log('Neural Network Background erfolgreich deaktiviert'); - }; - - // Startzeit für die schrittweise Verlangsamung setzen - this._destroyStartTime = Date.now(); - - // Starte die schrittweise Verlangsamung - slowDownAnimation(); - }, 5500); // Warte länger als die CSS-Transition-Dauer - } + // Canvas ausblenden + this.canvas.style.opacity = '0'; + + // Nach Übergang entfernen + setTimeout(() => { + if (this.canvas && this.canvas.parentNode) { + this.canvas.parentNode.removeChild(this.canvas); + } + }, 3500); } - // Hilfsfunktion: Erzeuge Zickzack-Punkte für einen Blitz mit geringerer Vibration - generateZigZagPoints(start, end, segments = 5, amplitude = 8) { - const points = [start]; - const mainAngle = Math.atan2(end.y - start.y, end.x - start.x); - - // Berechne die Gesamtlänge des Blitzes - const totalDistance = Math.sqrt( - Math.pow(end.x - start.x, 2) + - Math.pow(end.y - start.y, 2) - ); - - // Geringere Amplitude für subtilere Zickzack-Muster - const baseAmplitude = totalDistance * 0.12; // Reduziert für sanfteres Erscheinungsbild - - for (let i = 1; i < segments; i++) { - const t = i / segments; - const x = start.x + (end.x - start.x) * t; - const y = start.y + (end.y - start.y) * t; - - // Geringere Vibration durch kleinere Zufallsvariationen - const perpendicularAngle = mainAngle + Math.PI/2; - const variation = (Math.random() * 0.8 - 0.4); // Kleinere Variation für sanftere Muster - - // Mal Links, mal Rechts für sanften Blitz - const directionFactor = (i % 2 === 0) ? 1 : -1; - const offset = baseAmplitude * (Math.sin(i * Math.PI) + variation) * directionFactor; - - points.push({ - x: x + Math.cos(perpendicularAngle) * offset, - y: y + Math.sin(perpendicularAngle) * offset - }); - } - points.push(end); - return points; - } - - // Hilfsfunktion: Erzeuge intensivere Funkenpunkte mit dynamischer Verteilung - generateSparkPoints(zigzag, sparkCount = 15) { - const sparks = []; - // Mehr Funken für intensiveren Effekt - const actualSparkCount = Math.min(sparkCount, zigzag.length * 2); - - // Funken an zufälligen Stellen entlang des Blitzes - for (let i = 0; i < actualSparkCount; i++) { - // Zufälliges Segment des Zickzacks auswählen - const segIndex = Math.floor(Math.random() * (zigzag.length - 1)); - - // Bestimme Richtung des Segments - const dx = zigzag[segIndex + 1].x - zigzag[segIndex].x; - const dy = zigzag[segIndex + 1].y - zigzag[segIndex].y; - const segmentAngle = Math.atan2(dy, dx); - - // Zufällige Position entlang des Segments - const t = Math.random(); - const x = zigzag[segIndex].x + dx * t; - const y = zigzag[segIndex].y + dy * t; - - // Dynamischer Versatz für intensivere Funken - const offsetAngle = segmentAngle + (Math.random() * Math.PI - Math.PI/2); - const offsetDistance = Math.random() * 8 - 4; // Größerer Offset für dramatischere Funken - - // Zufällige Größe für variierende Intensität - const baseSize = 3.5 + Math.random() * 3.5; - const sizeVariation = Math.random() * 2.5; - - sparks.push({ - x: x + Math.cos(offsetAngle) * offsetDistance, - y: y + Math.sin(offsetAngle) * offsetDistance, - size: baseSize + sizeVariation // Größere und variablere Funkengröße - }); - - // Zusätzliche kleinere Funken in der Nähe für einen intensiveren Effekt - if (Math.random() < 0.4) { // 40% Chance für zusätzliche Funken - const subSparkAngle = offsetAngle + (Math.random() * Math.PI/2 - Math.PI/4); - const subDistance = offsetDistance * (0.4 + Math.random() * 0.6); - - sparks.push({ - x: x + Math.cos(subSparkAngle) * subDistance, - y: y + Math.sin(subSparkAngle) * subDistance, - size: (baseSize + sizeVariation) * 0.6 // Kleinere Größe für sekundäre Funken - }); - } - } - - return sparks; - } - - // New method to render the flowing animations with intensiveren Glow und Lila-Farbverlauf - renderFlowsWebGL(now, fadeoutFactor = 1.0) { - // Für jeden Flow einen leuchtenden Lila-Blitz als Schlange zeichnen und Funken erzeugen - for (const flow of this.flows) { - const connection = flow.connection; - const fromNode = this.nodes[connection.from]; - const toNode = this.nodes[connection.to]; - const startProgress = flow.progress; - const endProgress = Math.min(1, startProgress + flow.length); - if (startProgress >= 1 || endProgress <= 0) continue; - const direction = flow.direction ? 1 : -1; - let p1, p2; - if (direction > 0) { - p1 = { - x: fromNode.x + (toNode.x - fromNode.x) * startProgress, - y: fromNode.y + (toNode.y - fromNode.y) * startProgress - }; - p2 = { - x: fromNode.x + (toNode.x - fromNode.x) * endProgress, - y: fromNode.y + (toNode.y - fromNode.y) * endProgress - }; - } else { - p1 = { - x: toNode.x + (fromNode.x - toNode.x) * startProgress, - y: toNode.y + (fromNode.y - toNode.y) * startProgress - }; - p2 = { - x: toNode.x + (fromNode.x - toNode.x) * endProgress, - y: toNode.y + (fromNode.y - toNode.y) * endProgress - }; - } - - // Erzeuge Schlangenbewegung durch intensivere Zickzack-Muster - const snakeSegments = 12; // Mehr Segmente für flüssigere Schlangenbewegung - const snakeAmplitude = 12; // Größere Amplitude für deutlichere Schlangenbewegung - // Einflussfaktor, der von der Generation abhängt - höhere Generationen schlängeln mehr - const generationFactor = Math.min(1.5, (flow.generation || 1) / 10 + 0.8); - const zigzag = this.generateZigZagPoints( - p1, p2, - snakeSegments, - snakeAmplitude * generationFactor, - true // Sanftere Kurven aktivieren - ); - - // Lila Gradient Effekt für Blitze - Multi-Pass Rendering für intensives Glühen - - // 1. Render outer glow - very wide and subtle - const outerGlowWidth = 6.0 * this.config.glowIntensity; - for (let i = 0; i < zigzag.length - 1; i++) { - const positions = new Float32Array([ - zigzag[i].x, zigzag[i].y, - zigzag[i + 1].x, zigzag[i + 1].y - ]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0 - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); - this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize); - - // Äußerer Glow in dunklerem Lila mit niedriger Opazität - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - 0.45, 0.2, 0.7, 0.15 * fadeoutFactor // Dunkler lila Außenglow - ); - this.gl.enable(this.gl.BLEND); - this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); - this.gl.lineWidth(outerGlowWidth); - this.gl.drawArrays(this.gl.LINES, 0, 2); - } - - // 2. Middle glow layer - more opaque and focused - const middleGlowWidth = 3.5 * this.config.glowIntensity; - for (let i = 0; i < zigzag.length - 1; i++) { - const positions = new Float32Array([ - zigzag[i].x, zigzag[i].y, - zigzag[i + 1].x, zigzag[i + 1].y - ]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0 - ); - - // Mittlerer Glow in hellerem Lila mit mittlerer Opazität - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - 0.65, 0.3, 0.85, 0.35 * fadeoutFactor // Mittleres Lila - ); - this.gl.lineWidth(middleGlowWidth); - this.gl.drawArrays(this.gl.LINES, 0, 2); - } - - // 3. Inner core - bright and vibrant - for (let i = 0; i < zigzag.length - 1; i++) { - const positions = new Float32Array([ - zigzag[i].x, zigzag[i].y, - zigzag[i + 1].x, zigzag[i + 1].y - ]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0 - ); - - // Innerer Kern in strahlendem Lila-Weiß - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - 0.9, 0.7, 1.0, 0.8 * fadeoutFactor // Weißlich-Lila für intensives Zentrum - ); - this.gl.lineWidth(1.6); // Schmaler Kern - this.gl.drawArrays(this.gl.LINES, 0, 2); - } - - // Viele intensivere Funken erzeugen - const sparkCount = this.config.sparkCount + Math.floor((flow.generation || 1) / 2); // Mehr Funken bei höheren Generationen - const sparks = this.generateSparkPoints(zigzag, sparkCount); - - // Render die Funken mit mehreren Ebenen für intensiveres Glühen - for (const spark of sparks) { - // 1. Äußerer Glow der Funken - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - 0.5, 0.2, 0.8, 0.3 * fadeoutFactor // Lila Glow mit niedriger Opazität - ); - - // Position für den Funken setzen - const sparkPos = new Float32Array([spark.x, spark.y]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, sparkPos, this.gl.STATIC_DRAW); - - // Größerer Glow um jeden Funken - const sizes = new Float32Array([spark.size * this.config.sparkSize * 2.0]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, sizes, this.gl.STATIC_DRAW); - - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.vertexPosition, - 2, - this.gl.FLOAT, - false, - 0, - 0 - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); - - this.gl.vertexAttribPointer( - this.programInfo.attribLocations.pointSize, - 1, - this.gl.FLOAT, - false, - 0, - 0 - ); - this.gl.enableVertexAttribArray(this.programInfo.attribLocations.pointSize); - - this.gl.drawArrays(this.gl.POINTS, 0, 1); - - // 2. Mittlere Schicht der Funken - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - 0.7, 0.4, 0.95, 0.6 * fadeoutFactor // Mittleres Lila mit mittlerer Opazität - ); - - const middleSizes = new Float32Array([spark.size * this.config.sparkSize * 1.2]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, middleSizes, this.gl.STATIC_DRAW); - - this.gl.drawArrays(this.gl.POINTS, 0, 1); - - // 3. Innerer Kern der Funken - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - 0.95, 0.85, 1.0, 0.9 * fadeoutFactor // Fast weißes Zentrum für intensives Leuchten - ); - - const innerSizes = new Float32Array([spark.size * this.config.sparkSize * 0.6]); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer); - this.gl.bufferData(this.gl.ARRAY_BUFFER, innerSizes, this.gl.STATIC_DRAW); - - this.gl.drawArrays(this.gl.POINTS, 0, 1); - } - } - } - - // Hilfsfunktion: Erzeuge schlangenartige Zickzack-Punkte mit Glätte-Option - generateZigZagPoints(start, end, segments = 5, amplitude = 8, smooth = false) { - const points = [start]; - const mainAngle = Math.atan2(end.y - start.y, end.x - start.x); - - // Berechne die Gesamtlänge des Blitzes - const totalDistance = Math.sqrt( - Math.pow(end.x - start.x, 2) + - Math.pow(end.y - start.y, 2) - ); - - // Dynamische Amplitude basierend auf Distanz - const baseAmplitude = amplitude * (totalDistance / 150); - - // Welle für schlangenartigen Effekt - const waveFrequency = 2 + Math.random() * 1.5; // Zusätzlicher Welleneffekt - - // Vorherige Punktrichtung für glattere Übergänge - let prevOffsetX = 0; - let prevOffsetY = 0; - - for (let i = 1; i < segments; i++) { - const t = i / segments; - // Kubische Easing-Funktion für natürlichere Bewegung - const easedT = smooth ? t*t*(3-2*t) : t; - - const x = start.x + (end.x - start.x) * easedT; - const y = start.y + (end.y - start.y) * easedT; - - // Schlangenbewegung durch Sinuswelle mit zusätzlicher Zeit- und Positionsabhängigkeit - const wavePhase = waveFrequency * Math.PI * t + (Date.now() % 1000) / 1000 * Math.PI; - const waveAmplitude = Math.sin(wavePhase) * baseAmplitude; - - // Perpendikuläre Richtung zur Hauptrichtung + etwas Zufall - const perpendicularAngle = mainAngle + Math.PI/2 + (Math.random() * 0.3 - 0.15); - - // Für glattere Übergänge, interpoliere zwischen vorherigem und neuem Offset - let offsetX, offsetY; - if (smooth && i > 1) { - const newOffsetX = Math.cos(perpendicularAngle) * waveAmplitude; - const newOffsetY = Math.sin(perpendicularAngle) * waveAmplitude; - - // Interpoliere zwischen vorherigem und neuem Offset (30% vorheriger, 70% neuer) - offsetX = prevOffsetX * 0.3 + newOffsetX * 0.7; - offsetY = prevOffsetY * 0.3 + newOffsetY * 0.7; - - // Speichere für nächsten Punkt - prevOffsetX = offsetX; - prevOffsetY = offsetY; - } else { - offsetX = Math.cos(perpendicularAngle) * waveAmplitude; - offsetY = Math.sin(perpendicularAngle) * waveAmplitude; - - prevOffsetX = offsetX; - prevOffsetY = offsetY; - } - - points.push({ - x: x + offsetX, - y: y + offsetY - }); - } - - points.push(end); - return points; - } - - // Hilfsfunktion: Erzeuge leuchtende Funkenpunkte - generateSparkPoints(zigzag, sparkCount = 15) { - const sparks = []; - // Mehr Funken für intensiveren Effekt - const actualSparkCount = Math.min(sparkCount, zigzag.length * 3); - - // Funken an zufälligen Stellen entlang des Blitzes mit mehr Variabilität - for (let i = 0; i < actualSparkCount; i++) { - // Zufälliges Segment des Zickzacks auswählen, mit Präferenz für Ecken - let segIndex; - if (Math.random() < 0.6 && zigzag.length > 3) { - // 60% der Funken an Ecken platzieren (mehr Funken an Wendepunkten) - segIndex = 1 + Math.floor(Math.random() * (zigzag.length - 3)); - } else { - // Rest gleichmäßig verteilen - segIndex = Math.floor(Math.random() * (zigzag.length - 1)); - } - - // Bestimme Richtung des Segments - const dx = zigzag[segIndex + 1].x - zigzag[segIndex].x; - const dy = zigzag[segIndex + 1].y - zigzag[segIndex].y; - const segmentAngle = Math.atan2(dy, dx); - - // Zufällige Position entlang des Segments mit Präferenz für die Mitte - let t; - if (Math.random() < 0.4) { - // 40% der Funken näher zur Mitte des Segments - t = 0.3 + Math.random() * 0.4; - } else { - // Rest gleichmäßig verteilen - t = Math.random(); - } - - const x = zigzag[segIndex].x + dx * t; - const y = zigzag[segIndex].y + dy * t; - - // Dynamischer Versatz für intensivere Funken - // Versatz tendenziell senkrecht zur Segmentrichtung - const offsetAngle = segmentAngle + (Math.random() * Math.PI - Math.PI/2); - // Größerer Versatz für Funken, die weiter vom Pfad wegfliegen - const offsetDistance = (0.8 + Math.random() * 2.0) * 6; - - // Größere und variablere Funken mit Zeit-basiertem Pulsieren - const baseSize = 0.8 + Math.random() * 1.0; - // Pulsierender Effekt - const pulsePhase = (Date.now() % 1000) / 1000 * Math.PI * 2; - const pulseFactor = 0.8 + Math.sin(pulsePhase) * 0.2; - - sparks.push({ - x: x + Math.cos(offsetAngle) * offsetDistance, - y: y + Math.sin(offsetAngle) * offsetDistance, - size: baseSize * pulseFactor, - // Zufälliger Winkel für Funken-Rotation - angle: Math.random() * Math.PI * 2 - }); - } - - return sparks; + 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; } } -// Initialize when DOM is loaded +// Initialisiert den Hintergrund, sobald die Seite geladen ist document.addEventListener('DOMContentLoaded', () => { - // Short delay to ensure DOM is fully loaded - setTimeout(() => { - if (!window.neuralNetworkBackground) { - console.log('Creating Neural Network Background'); - window.neuralNetworkBackground = new NeuralNetworkBackground(); - } - }, 100); -}); - -// Re-initialize when page is fully loaded (for safety) -window.addEventListener('load', () => { - if (!window.neuralNetworkBackground) { - console.log('Re-initializing Neural Network Background on full load'); - window.neuralNetworkBackground = new NeuralNetworkBackground(); - } -}); - -// Event listener to clean up when the window is closed -window.addEventListener('beforeunload', function() { - if (window.neuralNetworkBackground) { - window.neuralNetworkBackground.destroy(); - } -}); + window.neuralBackground = new NeuralNetworkBackground(); +}); \ No newline at end of file