From 89476d5353fc04c93236f3e16b82d0df29114857 Mon Sep 17 00:00:00 2001 From: marwin Date: Tue, 29 Apr 2025 20:51:49 +0100 Subject: [PATCH] w --- database/systades.db | Bin 0 -> 122880 bytes static/neural-network-background.js | 2096 +++++++++++++++++++++++++++ 2 files changed, 2096 insertions(+) create mode 100644 database/systades.db create mode 100644 static/neural-network-background.js diff --git a/database/systades.db b/database/systades.db new file mode 100644 index 0000000000000000000000000000000000000000..c8067b8502e4b715a1186004651e02033b7f6f5e GIT binary patch literal 122880 zcmeI*?Qh%09S3kSicMR#M8}D%rkWcDftzTJmnsi+Jh#}CSV;{(CAO2Uzy^V$&K4_* zR7om{Q(zBH(!JaMgT5Mu4SPAD7}jnW5MX;Vpct^XeYF8YvAx;5Vtuo_6Dg9iC_7EE zCBipAB8qoB-F<#{lt|qv>(`fbmnfT-T~}S@oG>PcqVR1+5d`4~{rfWg>;E}SHxBww z=%X0gj@vvUeB*=M0Lv_Y$?{}c4{_PC9|91700bZa0SG_<0uX=z1Rwx`Coga$onnQK zJ^7MRA_O1+0SG_<0uX=z1Rwwb2tWV=&yGNnRR!1o&rU#469_;60uX=z1Rwwb2tWV= z5P-l_5Ww~SQ$R(L5P$##AOHafKmY;|fB*y_0D)&rfX)A(Nd8@re=Gk$F3M8+H|h7& z#q@CM_o-THYV=>DzaFiQUKmY{{CVVqk=2poG%Nl<00Izz00bZa0SG|gnHHFvkc3xf zvsv*Pv7Lt>nTD=aYeaW1S5;fBxx^;=7BL;Is&2Z(JaPW)>})Z=sZKW?VvnCbA_>Q) zvXf%5Zn?T;67wqAeE3P#aBdUB4Ki1WYP;hX4%6H-H20#lW$G;dJTYs;RGMZ*d0Va8 zCaKoLuTi#D-5j4EmxL3`*%QJwXIraUhP9=WWnLv#SK=r)iCt9mLIO_$eJd7IcZ z5*$;utXg^@EeTmQdt6w2_z5kM@S3&i!kbiU8nhT*%|g?u*iEfQfpsTZRfD)LnMp~) z@tN!~v1pK*Yg?vXYj4pY5@mgxzCfMT;Nt8#*2X%iTlRKxW|aDzWBtIkwrsUt*NNRU zx7Mvq_qJ*iN_bn>Y6jgE9I9CrlAIrrgt=K3RbKg4D;G?cR+sghyTK;?DYa#lRzj!! zBq+qT%9fkFo}@u0*(qUzXjQWnYqmvSq_HqeDX!Cq zFPN=gaMNpV>kdVurP0o;REVw2tF+tPqG7Vjw9m7~oxgbL(#%EbVyF$*YD}{>4t#Tn z7Ih(eQe0NuDxp6Hr`HN)$K?Y=S$FMbjm3wK4*y_HHMFLox|Tg~UXp~#x$IHe@mkI5 zsDz^|kut4RaB#&`2j&JP;pid@M|GQax2wbJ`zLvSp8$@;+Z zfFw+5*-0V%QsM1ZkmhyguHG>;wY`r?*zv*Z0lX3 zxziP0WnFOg06lUxJ3U-e?tnHA^Z&!|Xna9O28QX|t2wI}l;?M~}d z;96BTEXQh8brNi{Lj0HKEnl?EyQJZUOJk=V_Wl1v`ePyerTn3MHvOsmC;1=p57VE? zugDwni}GjkPvvy_WBK3mujE4dpK?R~=$Ss%qjC^{00bZa0SG_<0uX=z1R&6V0*4c? zcHYsONG0fPA^%3*5%#QoBkr)AI3b4auZ@o&5P$## zAOHafKmY;|fB*y_0D(XN-~R^|*n$8AAOHafKmY;|fB*y_009W}s{p?L@7KtpT@Zi( z1Rwwb2tWV=5P$##AOL|t0N?)y7TAIS1Rwwb2tWV=5P$##AOHaf^s4~A|L@nxqFoSx z00bZa0SG_<0uX=z1Rwx`KmgzW2Nu|Z00bZa0SG_<0uX=z1Rwwb2=uD}zW?vn$f8{k zfB*y_009U<00Izz00bZafj}UY{;`lgUqNO(SiAgIVeQn# z*K&~zq^{~lJ2ii{Be|hE&TY%Cv~yg@pN$rv*+g|orKGybJgwHo!g7(luk5rUrBqiN9I1^SJ}OjT_&{$rKO~tej1FdQ zoaq?=n;2Z(sp<_+KD;Zn^YVz4Idew5R}09)8F=l(J4SaShm)CZL$!(NmSO<4s%})` z5+l0pa!xD$LA!L@c^=WXU0_&ucUY3SowcNt$!5j-?{lDrZM{o0*LxU=V0b%woG4t~ z4UNtGg^u$;g*ck6H`pjqHj3ZbU?VYR(3hJUyxdPPQj z*}ksbHuM^i1N{A7$K6Q`OPQlb#d|78b*uFBR%!j4jF7hW?OXKhYyVTxNN@GdocpFr z=I)IS@vim2@)Y>F(i`H#9S%J0t3zm(>C!>TyH(_^Ni93BP2WV?9j->U*?W-_yctnc z-V-guvP&90t+$hAc!Fa!ZH=Dmw;MqU?b_^^vaOn0wVglT8K0j0wflRXp*n7*qfXL?Yqu{r?4_{qF65 ztd8rJ{^wCPj`sN(AZQ&uGbeLr1_m>iC*wXV`I$?3e``?wLOjMV46^Oh0gFywT^KyZ zhyRm_!OYx5oF_kfon`+XJb7TxjCqzZL9Z%@*p(&b|M%1NHQE9J2tWV=5P$##AOHaf zKmY;|=q`Zy|J_}%7XlD~00bZa0SG_<0uX=z1R&5)0;!?z3n_6yNNyzuhyF73!QdZ; z<_`WYaY$S^@YnPwsUMBXthF=~{`3~OHISI(AoJ) z)70N<22;;l4OILYL_w8L^J)Eb>ej9aMM0j(3*m)m+?c z8UOWKRW0SC&$IV(FG!i`Y4L-BXdJz%-R<1o^P}Bk>819FrZCSa~=Z;{wp-ToZ# z=q%8DeqYNN{5VJE*F}0lw~&Wt+P1462|%xawVSi=BO#KaPo_Y30}v72R6;l?>4~^Y;Wv5VZZeqV=;aJ+&RpbR6ID=o;c`Ng|GME?epu+>M;@N47-w9kBICH zA{H4V9Wjoi-6=IJN9PL*t}Lw<_{xIqF8dr!r3)0=S=35p_oAan{5mRjTJd2!1J#l7 zX+(=Ih&j@0K}@6yyqXmoyWC1M@*;i^T#B|jOyJX)+;?1Iq~Y#chc}`8n&hT}r_UchVT{={}1#4Y8VNdjA4)g!_d1gd5 z2tWV=5P$##AOHafKmY;|fWUJsfb0L~IC!W!1Rwwb2tWV=5P$##AOHafKwzH&xc=WK z6tY180uX=z1Rwwb2tWV=5P$##o?`+0{@-&PJX9S55P$##AOHafKmY;|fB*y_uulPe z|G!TtWP<<%AOHafKmY;|fB*y_009U*#{#(ie~yEPszU$*5P$##AOHafKmY;|fB*#c hDS+$$eL^7{1Rwwb2tWV=5P$##AOHafK;StR_%AJ9$PoYl literal 0 HcmV?d00001 diff --git a/static/neural-network-background.js b/static/neural-network-background.js new file mode 100644 index 0000000..2cc56c2 --- /dev/null +++ b/static/neural-network-background.js @@ -0,0 +1,2096 @@ +/** + * Neural Network Background Animation + * Modern, darker, mystical theme using WebGL + * Subtle flowing network aesthetic + */ + +class NeuralNetworkBackground { + constructor() { + // Canvas setup + this.canvas = document.createElement('canvas'); + this.canvas.id = 'neural-network-background'; + this.canvas.style.position = 'fixed'; + this.canvas.style.top = '0'; + this.canvas.style.left = '0'; + this.canvas.style.width = '100%'; + this.canvas.style.height = '100%'; + this.canvas.style.zIndex = '-10'; // Ensure it's behind content but visible + this.canvas.style.pointerEvents = 'none'; + this.canvas.style.opacity = '1'; // Force visibility + this.canvas.style.transition = 'opacity 3.5s ease-in-out'; // Längere Übergangsanimation (von 1.5s auf 3.5s) + + // 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); + } + + // 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; + } + + // Animation properties + this.nodes = []; + this.connections = []; + this.flows = []; // Flow animations along connections + this.animationFrameId = null; + this.isDarkMode = true; // Always use dark mode for the background + this.isDestroying = false; // Flag für den Ausblendeprozess + + // Colors - Lila Farbpalette mit intensiveren Effekten + 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 + }; + + // Farben für Light Mode auch 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 + }; + + // Konfigurationsobjekt für schlangenartige, leuchtende Blitze und permanentes Netzwerk + 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 + }; + + // Initialize + this.init(); + + // Event listeners + 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'); + } + + 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 mit erhöhter Komplexität + const clusterCount = Math.floor(8 + Math.random() * 6); // 8-13 Cluster für mehr Dichte + 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 + }); + } + + // 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 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 + + 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); + + // 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({ + 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); + } + } + } + } + + 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(); + + // 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 + 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; + + // 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 + 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; + } else { + node.isActive = false; + } + } + + // 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; + } + + // 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); + }); + + // 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) + ); + + 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; + } + } + } + } + + // 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); + + 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; + this.ctx.fillRect(0, 0, width, height); + + // Draw connections with fade effects + const connectionColor = this.isDarkMode + ? this.darkModeColors.connectionColor + : this.lightModeColors.connectionColor; + + 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]; + + // Zeichne nur den Teil der Verbindung, der schon aufgebaut wurde + const progress = connection.progress || 0; + if (progress <= 0) continue; // Skip if no progress yet + + 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 + 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.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(); + } + + 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.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.fill(); + + // Innerer Kern für stärkeren Leuchteffekt + if (node.isActive) { + 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.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 }; + } + + // Cleanup method mit deutlich sanfterem Ausblenden + destroy() { + // Sanfte Ausblendanimation starten + 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 + 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 + } + } + + // 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; + } +} + +// 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(); + } +}); + +// Event listener to clean up when the window is closed +window.addEventListener('beforeunload', function() { + if (window.neuralNetworkBackground) { + window.neuralNetworkBackground.destroy(); + } +});