/** * Neural Network Background Animation * Modern, darker, mystical theme using WebGL * Subtle flowing network aesthetic */ class NeuralNetworkBackground { constructor(options = {}) { this.canvas = document.createElement('canvas'); 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 = '-1'; this.canvas.style.pointerEvents = 'none'; this.canvas.style.opacity = '0.6'; // Noch weiter reduzierte Opazität für subtileren Hintergrund // Standardkonfiguration mit subtileren Werten this.config = { nodeCount: 10, // Weniger Knoten nodeSize: 1.2, // Kleinere Knoten connectionDistance: 150, // Reduzierte Verbindungsdistanz connectionOpacity: 0.3, // Sanftere Verbindungslinien clusterCount: 7, // Weniger Cluster clusterRadius: 380, // Größerer Cluster-Radius für mehr Verteilung animationSpeed: 0.25, // Langsamere Animation flowDensity: 0.05, // Deutlich weniger Flussanimationen flowSpeed: 0.04, // Langsamerer Fluss flowLength: 0.04, // Kürzere Flussstreifen pulseSpeed: 0.0004, // Sehr sanftes Pulsieren activationFrequency: 0.4, // Seltenere Aktivierungen minConnectionsPerNode: 1, maxConnectionsPerNode: 3, // Weniger maximale Verbindungen lineUnderFlow: true, // Strichlinie unter Fluss anzeigen lineUnderFlowOpacity: 0.2, // Opazität der Strichlinie lineUnderFlowWidth: 1.0, // Breite der Strichlinie sequentialFlows: true, // Flüsse nacheinander anzeigen maxSimultaneousFlows: 3 // Maximale Anzahl gleichzeitiger Flüsse }; // Benutzerkonfiguration übernehmen (falls vorhanden) this.config = { ...this.config, ...options.config }; // Dunkler Modus mit subtileren Farben this.darkModeColors = { background: '#050a12', // Dunklerer Hintergrund nodeColor: '#5a79b7', // Sanfteres Blau connectionColor: '#2a3c68', // Gedämpfteres Blau flowColor: '#4d6ea8', // Sanfteres Flussblau lineUnderFlowColor: '#223a5c', // Dunkleres Blau für Strichlinie nodePulse: { r: 120, g: 150, b: 200 } // Sanftere Pulse-Farbe }; // Heller Modus mit subtileren Farben this.lightModeColors = { background: '#f9fbff', // Hellerer Hintergrund nodeColor: '#b9d0f8', // Helleres, sanfteres Blau connectionColor: '#e0f0ff', // Sehr helles Blau für subtilere Verbindungen flowColor: '#92b2e6', // Sanftes mittleres Blau lineUnderFlowColor: '#c7d8f2', // Helles Blau für Strichlinie nodePulse: { r: 110, g: 140, b: 200 } // Sanftere Pulse-Farbe }; // Erweiterte Konfiguration this.nodes = []; this.connections = []; this.flows = []; this.isDarkMode = options.isDarkMode !== undefined ? options.isDarkMode : true; this.useWebGL = options.useWebGL !== undefined ? options.useWebGL : true; this.frameRate = options.frameRate || 30; // Reduzierte Framerate für bessere Performance this.lastFrameTime = 0; this.frameInterval = 1000 / this.frameRate; this.lastFlowTime = 0; // Zeit des letzten Fluss-Starts this.flowInterval = 2000; // Mindestzeit zwischen neuen Flüssen (ms) // Ereignisbehandlung für Fenstergröße und Theme-Wechsel this.resizeTimeout = null; window.addEventListener('resize', this.onResize.bind(this)); document.addEventListener('theme-changed', (e) => { this.setDarkMode(e.detail.isDarkMode); }); // Initialisierung this.webglSetupComplete = false; if (this.useWebGL && this.setupWebGL()) { this.webglSetupComplete = true; } else { this.ctx = this.canvas.getContext('2d'); } // If canvas already exists, remove it first const existingCanvas = document.getElementById('neural-network-background'); if (existingCanvas) { existingCanvas.remove(); } // 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); } // Animation properties this.animationFrameId = null; // Initialize this.init(); // Event listeners document.addEventListener('darkModeToggled', (event) => { this.isDarkMode = event.detail.isDark; }); // Log that the background is initialized console.log('Neural Network Background initialized'); } init() { this.resizeCanvas(); if (this.useWebGL) { this.initWebGL(); } 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.useWebGL) { this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); } else if (this.ctx) { this.ctx.scale(pixelRatio, pixelRatio); } // Recalculate node positions after resize 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 const clusterCount = Math.floor(5 + Math.random() * 4); // 5-8 Cluster const clusters = []; for (let i = 0; i < clusterCount; i++) { clusters.push({ x: Math.random() * width, y: Math.random() * height, radius: 100 + Math.random() * 150 }); } // Create nodes with random positions and properties 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; 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 größer const nodeImportance = useCluster ? 1.2 : 0.8; const size = this.config.nodeSize * nodeImportance + Math.random() * this.config.nodeVariation; const node = { x: x, y: y, size: size, speed: { x: (Math.random() - 0.5) * this.config.animationSpeed, y: (Math.random() - 0.5) * this.config.animationSpeed }, 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 }; this.nodes.push(node); } } createConnections() { this.connections = []; this.flows = []; // Reset flows // Create connections between nearby nodes 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; const nodeB = this.nodes[j]; const dx = nodeB.x - nodeA.x; const dy = nodeB.y - nodeA.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < this.config.connectionDistance) { potentialConnections.push({ index: j, distance: distance }); } } // Sortiere nach Entfernung potentialConnections.sort((a, b) => a.distance - b.distance); // Wähle die nächsten N Verbindungen, maximal maxConnections const maxConn = Math.min( this.config.maxConnections, potentialConnections.length, 1 + Math.floor(Math.random() * this.config.maxConnections) ); for (let c = 0; c < maxConn; c++) { const connection = potentialConnections[c]; const j = connection.index; const nodeB = this.nodes[j]; const distance = connection.distance; // Create weighted connection (closer = stronger) const connectionStrength = Math.max(0, 1 - distance / this.config.connectionDistance); 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 startet mit progress=0 für animierten Aufbau this.connections.push({ from: i, to: j, distance: distance, opacity: connOpacity, strength: connectionStrength, hasFlow: false, lastActivated: 0, progress: 0 // Animationsfortschritt für Verbindungsaufbau }); nodeA.connections.push(j); nodeB.connections.push(i); } } } } startAnimation() { this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); } animate() { // Update nodes const width = this.canvas.width / (window.devicePixelRatio || 1); const height = this.canvas.height / (window.devicePixelRatio || 1); const now = Date.now(); // Simulate neural firing for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; // Check if node should fire based on its firing rate if (now - node.lastFired > node.firingRate) { node.isActive = true; node.lastFired = now; // Activate connected nodes with probability based on connection strength for (const connIndex of node.connections) { // Find the connection const conn = this.connections.find(c => (c.from === i && c.to === connIndex) || (c.from === connIndex && c.to === i) ); if (conn) { // Mark connection as recently activated conn.lastActivated = now; // Create a flow along this connection if (Math.random() < conn.strength * 0.8) { this.flows.push({ connection: conn, progress: 0, direction: conn.from === i, // Flow from activated node length: this.config.flowLength + Math.random() * 0.1, intensity: 0.7 + Math.random() * 0.3 // Random intensity for variation }); } // Probability for connected node to activate if (Math.random() < conn.strength * 0.5) { this.nodes[connIndex].isActive = true; this.nodes[connIndex].lastFired = now - Math.random() * 500; // Slight variation } } } } else if (now - node.lastFired > 300) { // Deactivate after short period node.isActive = false; } } // Animierter Verbindungsaufbau: progress inkrementieren for (const connection of this.connections) { if (connection.progress < 1) { // Langsamer Aufbau: Geschwindigkeit kann angepasst werden connection.progress += 0.012; // Sehr langsam, für subtilen Effekt if (connection.progress > 1) connection.progress = 1; } } // Update flows this.updateFlows(); // Occasionally create new flows along connections if (Math.random() < this.config.flowDensity) { this.createNewFlow(); } // Recalculate connections occasionally for a living network if (Math.random() < 0.01) { // Only recalculate 1% of the time for performance this.createConnections(); } // Render if (this.useWebGL) { this.renderWebGL(); } else { this.renderCanvas(); } // Continue animation this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); } // New method to update flow animations updateFlows() { // Update existing flows const now = Date.now(); for (let i = this.flows.length - 1; i >= 0; i--) { const flow = this.flows[i]; // Update flow progress flow.progress += this.config.flowSpeed / flow.connection.distance; // Update line progress (langsamer als der Blitz) flow.lineProgress = Math.min(flow.progress * 1.5, 1.0); // Remove completed flows if (flow.progress > 1.0) { this.flows.splice(i, 1); } } // Generate new flows selten und kontrolliert if (Math.random() < this.config.flowDensity && (!this.config.sequentialFlows || now - this.lastFlowTime >= this.flowInterval)) { this.createNewFlow(); } } // New method to create flow animations createNewFlow() { if (this.connections.length === 0) return; // Überprüfen, ob maximale Anzahl gleichzeitiger Flüsse erreicht ist if (this.config.sequentialFlows && this.flows.length >= this.config.maxSimultaneousFlows) { return; } // Überprüfen, ob genügend Zeit seit dem letzten Fluss vergangen ist const now = Date.now(); if (this.config.sequentialFlows && now - this.lastFlowTime < this.flowInterval) { return; } // Zeit des letzten Flusses aktualisieren this.lastFlowTime = now; // Select a random connection with preference for more connected nodes let connectionIdx = Math.floor(Math.random() * this.connections.length); let attempts = 0; // Try to find a connection with more connected nodes while (attempts < 5) { const testIdx = Math.floor(Math.random() * this.connections.length); const testConn = this.connections[testIdx]; const fromNode = this.nodes[testConn.from]; if (fromNode.connections.length > 2) { connectionIdx = testIdx; break; } attempts++; } const connection = this.connections[connectionIdx]; // 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 lineProgress: 0 // Fortschritt der unterliegenden Strichlinie (startet bei 0) }); } renderWebGL() { this.gl.clear(this.gl.COLOR_BUFFER_BIT); const width = this.canvas.width / (window.devicePixelRatio || 1); const height = this.canvas.height / (window.devicePixelRatio || 1); // 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(); // Draw flows on top of connections this.renderFlowsWebGL(); // Draw nodes this.renderNodesWebGL(); } renderNodesWebGL() { // Prepare node positions for WebGL const positions = new Float32Array(this.nodes.length * 2); const sizes = new Float32Array(this.nodes.length); const now = Date.now(); 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; // Maximales Pulsieren und sehr große Knoten let pulse = Math.sin(node.pulsePhase) * 0.38 + 1.35; const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 1.1; 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 - more visible with active highlighting 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 brighter color if (node.isActive) { r = (r + nodePulseColor.r / 255) / 2; g = (g + nodePulseColor.g / 255) / 2; b = (b + nodePulseColor.b / 255) / 2; } // Sehr sichtbare, maximal leuchtende Knoten this.gl.uniform4f( this.programInfo.uniformLocations.color, r, g, b, node.isActive ? 1.0 : 0.92 // Maximal sichtbar ); // Draw each node individually for better control this.gl.drawArrays(this.gl.POINTS, i, 1); } } renderConnectionsWebGL() { // Permanente Verbindungsstriche werden nicht mehr gezeichnet // Diese Methode bleibt leer, damit keine Linien mehr erscheinen } // New method to render the flowing animations renderFlowsWebGL() { // Für jeden Flow zuerst die unterliegende Strichlinie zeichnen, dann den Blitz for (const flow of this.flows) { const connection = flow.connection; const fromNode = this.nodes[connection.from]; const toNode = this.nodes[connection.to]; const direction = flow.direction ? 1 : -1; // 1. Die unterliegende Strichlinie zeichnen if (this.config.lineUnderFlow) { const lineStart = direction > 0 ? fromNode : toNode; const lineEnd = direction > 0 ? toNode : fromNode; // Strichlinie als gerade Linie mit voller Länge, aber mit Fortschritt const lineEndPoint = { x: lineStart.x + (lineEnd.x - lineStart.x) * flow.lineProgress, y: lineStart.y + (lineEnd.y - lineStart.y) * flow.lineProgress }; const positions = new Float32Array([ lineStart.x, lineStart.y, lineEndPoint.x, lineEndPoint.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); // Sanftere, transparentere Strichlinie const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; const lineColor = this.hexToRgb(colorObj.lineUnderFlowColor || colorObj.connectionColor); this.gl.uniform4f( this.programInfo.uniformLocations.color, lineColor.r / 255, lineColor.g / 255, lineColor.b / 255, this.config.lineUnderFlowOpacity // Sehr subtile Linie ); this.gl.enable(this.gl.BLEND); this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); // Normales Alpha-Blending this.gl.lineWidth(this.config.lineUnderFlowWidth); // Dünne Linie this.gl.drawArrays(this.gl.LINES, 0, 2); } // 2. Den Blitz selbst zeichnen const startProgress = flow.progress; const endProgress = Math.min(1, startProgress + flow.length); if (startProgress >= 1 || endProgress <= 0) continue; 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 (weniger ausgeprägt) const zigzag = this.generateZigZagPoints(p1, p2, 3, 5); // Weniger Segmente, kleinere 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, subtilerer Blitz const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; const flowColor = this.hexToRgb(colorObj.flowColor); this.gl.uniform4f( this.programInfo.uniformLocations.color, flowColor.r / 255, flowColor.g / 255, flowColor.b / 255, 0.45 // Geringere Sichtbarkeit ); this.gl.enable(this.gl.BLEND); this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); this.gl.lineWidth(1.0); // Dünnerer Blitz für subtileren Effekt this.gl.drawArrays(this.gl.LINES, 0, 2); } // Funken erzeugen - weniger und subtilere elektrische Funken const sparks = this.generateSparkPoints(zigzag, 2 + Math.floor(Math.random() * 2)); // Weniger Funken for (const spark of sparks) { // Sanfteres Weiß-Blau für subtilere elektrische Funken this.gl.uniform4f( this.programInfo.uniformLocations.color, 0.85, 0.9, 0.95, 0.4 * spark.intensity // Geringere Intensitä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); // Punktgröße setzen const sizes = new Float32Array([spark.size * 2.0]); // Kleinere Funken 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() { if (!this.ctx) return; const { background, nodeColor, connectionColor } = this.isDarkMode ? this.darkModeColors : this.lightModeColors; // Hintergrund sanft löschen mit leichter Transparenz für Nachleuchten this.ctx.fillStyle = background; this.ctx.globalAlpha = 0.22; // Sehr subtile Überblendung für sanfte Bewegung this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); this.ctx.globalAlpha = 1.0; // Verbindungen zeichnen mit distanzabhängiger Transparenz this.connections.forEach(connection => { const { source, target, strength } = connection; const dx = target.x - source.x; const dy = target.y - source.y; const distance = Math.sqrt(dx * dx + dy * dy); // Berechne Opazität basierend auf Distanz - je weiter entfernt, desto transparenter const maxOpacity = 0.02; // Sehr subtile maximale Opazität const opacityFactor = 1 - (distance / this.config.connectionDistance); let opacity = maxOpacity * opacityFactor * strength * this.config.connectionOpacity; // Subtilere Linien this.ctx.beginPath(); this.ctx.moveTo(source.x, source.y); this.ctx.lineTo(target.x, target.y); this.ctx.lineWidth = 0.4; // Sehr dünne Linie this.ctx.strokeStyle = connectionColor; this.ctx.globalAlpha = opacity; this.ctx.stroke(); this.ctx.globalAlpha = 1.0; }); // Knoten zeichnen mit subtilerem Glühen und Pulsieren this.nodes.forEach(node => { const nodeBaseSize = node.size * this.config.nodeSize; const pulse = Math.sin(this.time * this.config.pulseSpeed * node.pulseRate) * 0.1 + 1; // Reduzierte Pulsamplitude const glowSize = nodeBaseSize * 1.5; // Kleinerer Glowradius // Aktive Knoten speziell hervorheben, aber subtiler if (node.active) { // Subtiles Glühen für aktive Knoten const gradient = this.ctx.createRadialGradient( node.x, node.y, 0, node.x, node.y, glowSize * 2 ); this.ctx.globalAlpha = 0.09; // Sehr niedrige Opazität für subtiles Glühen gradient.addColorStop(0, 'rgba(255, 255, 255, 0.15)'); gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); this.ctx.fillStyle = gradient; this.ctx.beginPath(); this.ctx.arc(node.x, node.y, glowSize * 2, 0, Math.PI * 2); this.ctx.fill(); // Zeichne aktiven Knoten this.ctx.globalAlpha = 0.9; this.ctx.fillStyle = nodeColor; this.ctx.beginPath(); this.ctx.arc(node.x, node.y, nodeBaseSize * pulse, 0, Math.PI * 2); this.ctx.fill(); } else { // Inaktive Knoten sanfter darstellen this.ctx.globalAlpha = 0.8; this.ctx.fillStyle = nodeColor; this.ctx.beginPath(); this.ctx.arc(node.x, node.y, nodeBaseSize * pulse * 0.8, 0, Math.PI * 2); this.ctx.fill(); } this.ctx.globalAlpha = 1.0; }); } renderFlowsCanvas() { if (!this.ctx) return; const { flowColor } = this.isDarkMode ? this.darkModeColors : this.lightModeColors; this.flows.forEach(flow => { if (flow.progress > 1) return; const { source, target, progress } = flow; // Berechne aktuelle Position const dx = target.x - source.x; const dy = target.y - source.y; const currentX = source.x + dx * progress; const currentY = source.y + dy * progress; // Zick-Zack-Effekt reduzieren für sanftere Bewegung const segmentCount = 4; // Weniger Segmente const amplitude = 2; // Geringere Amplitude const waveLength = 0.2; let prevX = source.x; let prevY = source.y; this.ctx.beginPath(); this.ctx.moveTo(prevX, prevY); // Sanftere "Elektrizitäts"-Linie for (let i = 0; i <= segmentCount; i++) { const segmentProgress = i / segmentCount; if (segmentProgress > progress) break; const segX = source.x + dx * segmentProgress; const segY = source.y + dy * segmentProgress; // Sanftere Wellen const perpX = -dy * Math.sin(segmentProgress * Math.PI * 10) * amplitude; const perpY = dx * Math.sin(segmentProgress * Math.PI * 10) * amplitude; const pointX = segX + perpX * Math.sin(this.time * 0.01); const pointY = segY + perpY * Math.sin(this.time * 0.01); this.ctx.lineTo(pointX, pointY); prevX = pointX; prevY = pointY; } // Linienausrichtung und -stil this.ctx.lineCap = 'round'; this.ctx.lineWidth = 1.2; // Dünnere Linie // Subtiler Gloweffekt this.ctx.shadowColor = flowColor; this.ctx.shadowBlur = 6; // Reduzierter Unschärferadius this.ctx.strokeStyle = flowColor; this.ctx.globalAlpha = 0.7 - 0.5 * Math.abs(progress - 0.5); // Sanfteres Ein- und Ausblenden this.ctx.stroke(); this.ctx.globalAlpha = 1; this.ctx.shadowBlur = 0; // Funkeneffekte am Ende der Linie, aber weniger und kleiner if (progress > 0.9 && Math.random() < 0.3) { // Weniger Funken generieren const sparkCount = Math.floor(Math.random() * 2) + 1; // Maximal 3 Funken for (let i = 0; i < sparkCount; i++) { const sparkSize = Math.random() * 0.8 + 0.4; // Kleinere Funken const sparkAngle = Math.random() * Math.PI * 2; const sparkDistance = Math.random() * 5 + 2; const sparkX = currentX + Math.cos(sparkAngle) * sparkDistance; const sparkY = currentY + Math.sin(sparkAngle) * sparkDistance; // Weniger intensive Funken this.ctx.beginPath(); this.ctx.arc(sparkX, sparkY, sparkSize, 0, Math.PI * 2); this.ctx.fillStyle = flowColor; this.ctx.globalAlpha = 0.4; // Reduzierte Opazität this.ctx.fill(); this.ctx.globalAlpha = 1; } } }); } // 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 }; } // Cleanup method destroy() { if (this.animationFrameId) { cancelAnimationFrame(this.animationFrameId); } window.removeEventListener('resize', this.resizeCanvas.bind(this)); if (this.canvas && this.canvas.parentNode) { this.canvas.parentNode.removeChild(this.canvas); } if (this.gl) { // Clean up WebGL resources this.gl.deleteBuffer(this.positionBuffer); this.gl.deleteBuffer(this.sizeBuffer); this.gl.deleteProgram(this.shaderProgram); } } // Hilfsfunktion: Erzeuge Zickzack-Punkte für einen Blitz generateZigZagPoints(start, end, segments = 5, amplitude = 8) { const points = [start]; 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; // Versatz für Zickzack const angle = Math.atan2(end.y - start.y, end.x - start.x) + Math.PI / 2; const offset = (Math.random() - 0.5) * amplitude * (i % 2 === 0 ? 1 : -1); points.push({ x: x + Math.cos(angle) * offset, y: y + Math.sin(angle) * offset }); } points.push(end); return points; } // Hilfsfunktion: Erzeuge Funkenpunkte entlang eines Zickzack-Blitzes generateSparkPoints(zigzag, sparkCount = 4) { const sparks = []; // Moderatere Anzahl an Funken für subtileren Effekt const actualSparkCount = sparkCount + Math.floor(Math.random() * 2); for (let i = 0; i < actualSparkCount; i++) { // Entlang des gesamten Blitzes verteilen const seg = i * (zigzag.length - 1) / actualSparkCount; const segIndex = Math.floor(seg); const t = seg - segIndex; // Basis-Position const x = zigzag[segIndex].x + (zigzag[segIndex + 1].x - zigzag[segIndex].x) * t; const y = zigzag[segIndex].y + (zigzag[segIndex + 1].y - zigzag[segIndex].y) * t; // Sanfterer Versatz vom Blitz const angle = Math.random() * Math.PI * 2; const distance = 2 + Math.random() * 4; // Reduzierter Abstand für subtileres Sprühen sparks.push({ x: x + Math.cos(angle) * distance, y: y + Math.sin(angle) * distance, size: 0.5 + Math.random() * 1 // Kleinere Funken }); } return sparks; } onResize() { // Verhindere mehrfache Resize-Events in kurzer Zeit if (this.resizeTimeout) clearTimeout(this.resizeTimeout); this.resizeTimeout = setTimeout(() => { this.resizeCanvas(); // Knoten und Verbindungen neu erstellen, um sie an die neue Größe anzupassen this.createNodes(); this.createConnections(); }, 200); // Warte 200ms, bevor die Größenanpassung durchgeführt wird } setDarkMode(isDarkMode) { this.isDarkMode = isDarkMode; // WebGL-Hintergrundfarbe aktualisieren if (this.useWebGL && this.gl) { const bgColor = this.hexToRgb( this.isDarkMode ? this.darkModeColors.background : this.lightModeColors.background ); this.gl.clearColor(bgColor.r/255, bgColor.g/255, bgColor.b/255, 1.0); } } } // Initialize when DOM is loaded 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(); } }); // Clean up when window is closed window.addEventListener('beforeunload', () => { if (window.neuralNetworkBackground) { window.neuralNetworkBackground.destroy(); } }); function applyNeuralNetworkStyle(cy) { cy.style() .selector('node') .style({ 'label': 'data(label)', 'text-valign': 'center', 'text-halign': 'center', 'color': '#ffffff', 'text-outline-width': 2, 'text-outline-color': 'rgba(0,0,0,0.8)', 'text-outline-opacity': 0.9, 'font-size': 14, 'font-weight': '500', 'text-margin-y': 8, 'width': function(ele) { return ele.data('neuronSize') ? ele.data('neuronSize') * 8 : 60; }, 'height': function(ele) { return ele.data('neuronSize') ? ele.data('neuronSize') * 8 : 60; }, 'background-color': 'data(color)', 'background-opacity': 0.9, 'border-width': 2, 'border-color': '#ffffff', 'border-opacity': 0.8, 'shape': 'ellipse', 'transition-property': 'background-color, background-opacity, border-width', 'transition-duration': '0.3s', 'transition-timing-function': 'ease-in-out' }) .selector('edge') .style({ 'width': function(ele) { return ele.data('strength') ? ele.data('strength') * 3 : 1; }, 'curve-style': 'bezier', 'line-color': function(ele) { const sourceColor = ele.source().data('color'); return sourceColor || '#8a8aaa'; }, 'line-opacity': function(ele) { return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; }, 'line-style': function(ele) { const strength = ele.data('strength'); if (!strength) return 'solid'; if (strength <= 0.4) return 'dotted'; if (strength <= 0.6) return 'dashed'; return 'solid'; }, 'target-arrow-shape': 'none', 'source-endpoint': '0% 50%', 'target-endpoint': '100% 50%', 'transition-property': 'line-opacity, width', 'transition-duration': '0.3s', 'transition-timing-function': 'ease-in-out' }) .update(); }