From d7e6912e08f4f8eb8515a51ea71d2f66b44c8060 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Tue, 29 Apr 2025 14:58:54 +0200 Subject: [PATCH] =?UTF-8?q?Aktualisiere=20die=20Umgebungsvariablen=20in=20?= =?UTF-8?q?.env=20mit=20neuen=20Werten=20f=C3=BCr=20SECRET=5FKEY=20und=20O?= =?UTF-8?q?PENAI=5FAPI=5FKEY.=20Entferne=20die=20alte=20Instanz=20von=20sy?= =?UTF-8?q?stades.db=20und=20aktualisiere=20die=20neuronale=20Netzwerk-Hin?= =?UTF-8?q?tergrundanimation=20in=20neural-network-background.js=20mit=20v?= =?UTF-8?q?erbesserten=20visuellen=20Effekten,=20einschlie=C3=9Flich=20ein?= =?UTF-8?q?er=20neuen=20Zickzack-Bewegung=20f=C3=BCr=20Blitze=20und=20subt?= =?UTF-8?q?ileren=20Gl=C3=BCheffekten.=20Implementiere=20eine=20sanftere?= =?UTF-8?q?=20Ausblendanimation=20f=C3=BCr=20das=20Canvas-Element=20und=20?= =?UTF-8?q?verbessere=20die=20Funkenanimationen.=20Optimiere=20die=20Clust?= =?UTF-8?q?er-Generierung=20und=20-Verteilung=20f=C3=BCr=20eine=20fl=C3=BC?= =?UTF-8?q?ssigere=20Benutzererfahrung.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 15 +- __pycache__/app.cpython-313.pyc | Bin 69190 -> 69190 bytes __pycache__/models.cpython-313.pyc | Bin 22744 -> 22744 bytes database/systades.db | Bin 122880 -> 122880 bytes instance/systades.db | 0 static/neural-network-background.js | 1571 ++++++++++++++++++--------- 6 files changed, 1044 insertions(+), 542 deletions(-) delete mode 100644 instance/systades.db diff --git a/.env b/.env index 5c04806..07072cb 100644 --- a/.env +++ b/.env @@ -1,13 +1,2 @@ -# MindMap Umgebungsvariablen -# Kopiere diese Datei zu .env und passe die Werte an - -# Flask -SECRET_KEY=dein-geheimer-schluessel-hier - -# OpenAI API -OPENAI_API_KEY=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 - -# Datenbank -# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden -# Der Pfad wird relativ zum Projektverzeichnis angegeben -# SQLALCHEMY_DATABASE_URI=sqlite:////absoluter/pfad/zu/database/systades.db OPENAI_API_KEY=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 +SECRET_KEY=eed9298856dc9363cd32778265780d6904ba24e6a6b815a2cc382bcdd767ea7b +OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index b814309481afddfdbc5f633a8ffb0262aac4da30..413f36c7cdeb4036051b9f822e37b6d07d86574b 100644 GIT binary patch delta 28 icmX>$hvnED7QWBCyj%=G;Cx0PWB&C$hvnED7QWBCyj%=Gu%%ZZW9HpPzO8(W7JLASstCFO diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index d61fab9d33dac6663292ac07e5770028e5d4a4c4..aa2a1a9967a7784bd921f696ddd7d2c1876eae5d 100644 GIT binary patch delta 4533 zcmbtYYiv}<72dmG@55df`~sUAuX*@|&BMkt#19O9010*+Flj^;udnZ3yfCn4w67Fz)x!K{ac2Pz%<#3L z(b6ZNcAOyPB?yOEn9qi~9EEp6hc=GVbd#6CWU0rVx<`3q$v`bmYtrQEaMK1>4%G@k zC14$Yv1Du05Di6vIi*{1sVMyYx^0eHQzk3GQsInkB$Bgi7hfzn+&+ShUj=*(un|xO zkW7Rz+Oq^ZX^_%1{{HX=KKZ3=P^w@xlRmMg-mBf=XG=#Kx6nj3N-)UwVhDe-Z3Lpo zAVVgTY;L<1W!?O<(we$HIX`42qC@)ha58EN|B@0rKx>lFZ6es!=WGqLBQ$+k6RQ(C zc{EHyN+TdT3E2pLy{w~O8U`u+Wp66BN9s4$6 z7z0Q{{YK1+N12%r1qre>#Uy)foF%X)!Oxff$tR6Tcz#f_K7GF8xmCf_w3G$ocX)Z# z!L6W+ogx@y-54@J3INU+IA=D0p{io-w`nMR(K&M5GA?VZpI@wM?S^5jUoMTtk~5Pr zn|+6*%UGJTfj9Ckxpc>z!av*)mRCA)LgkEo1Izg_Lq=EtG+ zJm5vZOMsUNa-oh;sCUs!lV6eRqRcK}8Mb&hegqY_R6@S6)9H6>E@-OaFp)`* z)xPVcPP2Z~K+EzJk~xFyp(JRhe}q4on3;}J@Y!kp-AzsN=W*os037fNbc#Uh6pGL` zg>Om;vIb>87;dWh|H&n~JZy(&p3sF&5sK?6J2I7wS|=@&$_!=&5dda=1IFhHu~@~# zN#$)g_9xJ$vb3oz-Dtc7CBQjP`lp+>J6vKyHS~XP`9Px%_I`b2td%B5nPhb{c6kBd zbCUkjpTF;OC@FtvN~Gs4w3jvVGY!q{_qs3qdNfLvNont9`c>?^%5OEir4j(LGWts6 zHE-}u60;Wx1fSlvb6(nw6mS{}ykXLhW{hN4xq=ZR5WdZWgM%G=`yG$gKeR{VIKl}i z;^bsZI$i>rdj z-R#c@?(oNV9g@yxe?y{j-nygFRye9!>M4xwmInh3!q=Canj-6{!>Il#O7(cWB^)*5 zrfs$+*>p@#TwbL*X}{cI=qW>|yDH>#+@rc}B_^p@cxhW16=i)}XRr@HCDr-Y+nVWR z@M_x?!UOF`wNd`V_6B+%{GxrPIKRd6p54(SdF`z3ivFNn-Z*1cJx+hE=8_DV8%zO?T@i2J>Q3so9A$iE%NrBkCo?;mFKnlKGA|VNX-6&K=A+c zR>`nC5tkh)=ecZ4>fQG6t#zCZcJdvgWuC5dvd*J=j+-N}l-=N0_rIqcft*R7IqnJCtpl!GMbvW@N%{PM8mc=g9R?^{C1A$X@6*OEU2`d`g#V-!H}NGa0w?(g$7;hP*zZ<#V1iX@>Hi)p*VH#< zrN*m|C+B7CifEfC7}Y1}?jOSu5rB+J9Yzg+Z2)PTZ1~$r6a_={AsbiU_zA4b&y7nM z;hD+s)RG?vOkQt`5N`l_0RtXS1BDMmX&-ub?tc!b+0kQuCT{kyHZ8s5sW-f<@w)jR DVodJF delta 4536 zcmbtYYiv}<72dmG@55flHgW0bJenw>s!~<0l>Uf{u2d;)f3#bvRn^CjptR}v zX6|~|_9Z2%e|(xVkGpfe^PMv@zO|&>T2iY15eWEQ^!L5Rdt%?%vsCqZxuWv-%TG6D zJk*;ufBF+MYg(1_hNiRh95d9IndM>62TC;$c=wcJE%Wmu-k$PJviVhSt8$xP^qwaC z*gHsgzi$_z;cKBUzc*5!yXbq!t+et#S9F!%hc!ESW96eXR^2$LWck&~Hl>;Wx^kcl zA5?iwV5;^ArcDr}-2}lPYv%KTusz}R!06_2>JGUXwk+ncM^;SUTy?kwUkfQ>bRyKm zYB04HumMoVzh2cAdXSn{nVyImaWT>I9*Ua!l#$R`8HUO;rkYI6vmN|{sv~`4Sa=jL z31|XT0|XPzX!RCg7d6s~!rz%_;^K&k06Ydb2{;8{ zyrHiDKnt4F0I^Z88Z+V=)00+NlJ=Trf;}hBbga?&3w6JfEWuV^tf|kvQ2(`c{v=s> z{9uOHH9Xh`#@H;u2-}Nh3}66u0(t;v`7;gmHCbv}9&LeqH`FT%JIF6L^z6sp*g-Kg z6-#AiV09Lxdp0GC*naj+?4V8TfbC5*raRBzq7F@2{5@u%> z;O==o)Lhp-jq%L{mZGw)Fod3jlst1$(vg{F+xWTW#%)4K-@+6pH9;>dnR}!86-CMk z9cu2$mN(o~Y&LE=+`U>tLNibgi2!M9c~i;EoJPTCN&d|(p~Xdf@)^MMfaeHg9wv~7 zrfzu>l$2zMG!!LaBGmlfXAE7F0q`5?2o>$8vH58% z7X((ynAlS8L_a4$W}1|lPBgxZNr2~Fx!1Ptu(?Estm*&W_O3!j?49<=lU>v~#RRKC zYBH2;k&Q@G`up1fo07V_rbKuiqP47zpX=!ATPY^X8`U(*Op?9R)-Pe*C4Q^pniK$R zx#_E&Z@K*!$jo0R$nv@EyB5X1ApyIfz#A3>bO1zE`94~xG+EEqk&%)9y@za%Hr};I zBK7Tn67zmrF_Dv$0d4qvI{=AgdDK+WB)vNYon6GVQvgvtocxD_>_Tq7yU~ssI@H`N zJ(t({q22tC2{QblFOCZ5^FJq3I&b__p`~zCmQ(~#mege$EYEN%k)YjCg^~61c5O5s z?+$8uTsQTe6q}1hlULTsN_vn!K>kG2YLt$vfL(EqMNK0)OUc4(dpA&0w)YPBp;J+p zv;51wU358kx%VpJ1AWJoasJ)D4!R<|-Iu8xrD=sTj1TVC9^Qm?^7v}~e8B*TR+(v- zaa|U)SNUIeZyX1$Rz*6MVX7W67v^-Ue1=7nY7Es2=}53rjj@3-+ahoD>4$CJiW8GV?@Hd(aCprri{=1v0!08XC6HFll%4gN}c4qJNO za^OS7zeHyK?*v)?pMwn|>~_ROMJjqO%97On-IrETh{OT@Dz>r8)3r{vx@669as)eN zOZ>*ro6-^3YVO|8q1Nvt`Ug4R%g~5 zo)QCvt1NpT6H2Q9>@mLia9`=o##!xmywr$~7B4kxNuYu)Zv^7dDHeih{Q8X8f{X6d zj?o4aEX@CM_!p8S*lO;*kzXqPaNVe&nkz{vP}(55rDX^ z71UE(`dEVdY=}3HU6;(jR&&1{JLZ0Pn3~4D%gp3)@hGLID4(-5LeI98nvti{97~Tx|e?;pXg!Iw)?oy7N4|pezgv0+^NbL9c-J*id!<;6teDul^KD2(u zTM4|dVSa7mkCHOjvUI%p$dY>?Po~kaOdI13Va z*(43#cs#WzV&|t0t86MdLuda93_b=BQNbI8Z3A=y#NHl9Yde8eHcoHZxP0d8=oQZm zsFPg+TmXo3T^w(y>`YWiWIZE+cTo7y6#GN%F8=et@`mX$pNZ>(>{C~6=ZPP>*JI=V E1+Kc$7XSbN diff --git a/database/systades.db b/database/systades.db index 6a14d3c0c00fda3a88f550c47924a5e1cc8f0733..c8067b8502e4b715a1186004651e02033b7f6f5e 100644 GIT binary patch delta 140 zcmZoTz}|3xeS);02m=Fy91z2R@I)PBRuKlhHqFMAttpIi^*Q<48Tgp;7?|?5a~UwE zFmW|mFtUq_iZZqYPoHSVcuOj?I6g5YH#1Mc$=~0{)zMGE#nsI*)F(v2V7fpGqr~(^ ldq!0@e?B(xWZUVE8H^I!j~g={;9}wb!6?7I+m5kxApn7eB9;IE delta 91 zcmZoTz}|3xeS);0FaraF91z2R&_o?$R$&Icc 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [positions[i], positions[j]] = [positions[j], positions[i]]; - } - - // Erstelle die Cluster mit optimierten Parametern for (let i = 0; i < clusterCount; i++) { - const pos = positions[i % positions.length]; - - // Größere Cluster-Radien für bessere Sichtbarkeit und Trennung - const baseRadius = 130 + Math.random() * 170; + // 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: pos.x, - y: pos.y, - radius: baseRadius, - density: this.config.clusterDensity * (0.9 + Math.random() * 0.2), // Hohe Dichte mit leichter Variation - separation: this.config.clusterSeparation, // Verwende den Separationsparameter - type: Math.floor(Math.random() * 3) // 0: Standard, 1: Dicht, 2: Sternförmig + 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 }); } - // Stelle sicher, dass Cluster ausreichend voneinander getrennt sind - if (this.config.clusterSeparation > 0.5) { - this.ensureClusterSeparation(clusters, width, height); - } - - // Erstelle Knoten mit Berücksichtigung der Cluster und verbesserten Parametern + // Create nodes with random positions and properties for (let i = 0; i < this.config.nodeCount; i++) { - const inCluster = Math.random() < this.config.clusteringFactor; - let x, y, clusterType = -1; // -1 bedeutet "kein Cluster" - let assignedCluster = null; + // Entscheide, ob dieser Knoten zu einem Cluster gehört oder nicht + const useCluster = Math.random() < this.config.clusteringFactor; + let x, y; - if (inCluster && clusters.length > 0) { + if (useCluster && clusters.length > 0) { // Wähle ein zufälliges Cluster - assignedCluster = clusters[Math.floor(Math.random() * clusters.length)]; - clusterType = assignedCluster.type; - - // Verschiedene Verteilungsmuster je nach Cluster-Typ - let angle, distance; - - switch (assignedCluster.type) { - case 0: // Standard-Cluster mit gleichmäßiger Verteilung - angle = Math.random() * Math.PI * 2; - // Quadratische Verteilung für mehr Knoten in der Mitte - distance = assignedCluster.radius * Math.sqrt(Math.random()) * assignedCluster.density; - break; - - case 1: // Dichtes Cluster mit Konzentration in der Mitte - angle = Math.random() * Math.PI * 2; - // Kubische Verteilung für noch mehr Konzentration in der Mitte - distance = assignedCluster.radius * Math.pow(Math.random(), 2.0) * assignedCluster.density; - break; - - case 2: // Sternförmiges Cluster mit Strahlen - // Bevorzuge bestimmte Winkel für Strahleneffekt - const rayCount = 6 + Math.floor(Math.random() * 4); // 6-9 Strahlen für deutlichere Sterne - const baseAngle = Math.random() * Math.PI * 2; // Zufällige Basisrotation - const rayIndex = Math.floor(Math.random() * rayCount); - const rayAngleSpread = 0.2; // Streuung innerhalb des Strahls - - angle = baseAngle + (rayIndex / rayCount) * Math.PI * 2 + (Math.random() - 0.5) * rayAngleSpread; - distance = (0.3 + Math.random() * 0.7) * cluster.radius * cluster.density; // Längere Strahlen - break; - - default: - angle = Math.random() * Math.PI * 2; - distance = Math.random() * cluster.radius; - } + 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(20, Math.min(width - 20, x)); - y = Math.max(20, Math.min(height - 20, y)); + x = Math.max(0, Math.min(width, x)); + y = Math.max(0, Math.min(height, y)); } else { - // Zufällige Position außerhalb von Clustern, mit reduzierter Dichte in Clusternähe - let validPosition = false; - let attempts = 0; - - while (!validPosition && attempts < 10) { - x = Math.random() * width; - y = Math.random() * height; - - // Prüfe Abstand zu allen Clustern - let minDistanceRatio = 1.0; - - for (const cluster of clusters) { - const dx = x - cluster.x; - const dy = y - cluster.y; - const distance = Math.sqrt(dx * dx + dy * dy); - const distanceRatio = distance / (cluster.radius * 1.5); // Größerer Ausschlussbereich - - minDistanceRatio = Math.min(minDistanceRatio, distanceRatio); - } - - // Akzeptiere Position, wenn sie weit genug von allen Clustern entfernt ist - // oder nach mehreren Versuchen - if (minDistanceRatio > 1.0 || attempts > 5) { - validPosition = true; - } - - attempts++; - } + // 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 - let nodeImportance; - - if (clusterType === -1) { - // Nicht-Cluster-Knoten sind kleiner - nodeImportance = 0.5; - } else { - // Cluster-Knoten sind größer, mit Variation je nach Typ - switch (clusterType) { - case 0: nodeImportance = 1.5; break; // Standard - case 1: nodeImportance = 1.8; break; // Dichteres Cluster, größere Knoten - case 2: nodeImportance = 1.3 + Math.random() * 0.7; break; // Variable Größe für Strahleneffekt - default: nodeImportance = 1.5; - } - } - - const size = this.config.nodeSize * nodeImportance + Math.random() * this.config.nodeVariation * 1.2; + 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, - clusterType: clusterType, // Speichere den Cluster-Typ für spätere Verwendung speed: { - x: (Math.random() - 0.5) * this.config.animationSpeed * (clusterType === -1 ? 1.5 : 0.7), // Nicht-Cluster-Knoten bewegen sich mehr - y: (Math.random() - 0.5) * this.config.animationSpeed * (clusterType === -1 ? 1.5 : 0.7) + 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: clusterType !== -1 && Math.random() < 0.4, // Cluster-Knoten häufiger aktiv - lastFired: 0, - firingRate: clusterType === -1 ? 2000 + Math.random() * 5000 : 800 + Math.random() * 2000 // Schnellere Feuerrate für Cluster + 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); @@ -420,113 +301,92 @@ class NeuralNetworkBackground { } createConnections() { - // Connection probability matrix based on distance and cluster membership + 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 = []; - for (let j = i + 1; j < this.nodes.length; j++) { - const nodeB = this.nodes[j]; + // 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; - // Berechne Distanz zwischen den Knoten + 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); - // Basiswahrscheinlichkeit basierend auf Distanz - let connectionProbability = 0; - - // Verschiedene Verbindungsregeln basierend auf Cluster-Zugehörigkeit - const bothInCluster = nodeA.clusterType !== -1 && nodeB.clusterType !== -1; - const sameClusterType = nodeA.clusterType === nodeB.clusterType; - - // Maximale Verbindungsdistanz - dynamisch basierend auf Clusterzugehörigkeit - let maxDistance; - - if (bothInCluster && sameClusterType) { - // Innerhalb des gleichen Cluster-Typs: höhere Wahrscheinlichkeit für Verbindungen - maxDistance = 230; // Großzügige Verbindungsdistanz innerhalb von Clustern - - if (distance < maxDistance) { - // Höhere Wahrscheinlichkeit für nahe Knoten im selben Cluster - connectionProbability = Math.pow(1 - distance / maxDistance, 1.5) * 0.95; - - // Zusätzliche Regeln für spezifische Cluster-Typen - if (nodeA.clusterType === 1) { - // Dichte Cluster: noch stärkere Verbindungen im Zentrum - connectionProbability *= 1.2; - } else if (nodeA.clusterType === 2) { - // Sternförmige Cluster: bevorzuge Verbindungen entlang ähnlicher Winkel - // Berechne die Winkel der Knoten vom Clusterzentrum - const centerX = nodeA.x - dx / 2; // Grobe Schätzung des Zentrums - const centerY = nodeA.y - dy / 2; - - const angleA = Math.atan2(nodeA.y - centerY, nodeA.x - centerX); - const angleB = Math.atan2(nodeB.y - centerY, nodeB.x - centerX); - - // Berechne den Winkelunterschied und normalisiere ihn - let angleDiff = Math.abs(angleA - angleB); - if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; - - // Bevorzuge Verbindungen mit ähnlichem Winkel (entlang der Strahlen) - if (angleDiff < 0.3) { - connectionProbability *= 1.3; - } else { - connectionProbability *= 0.7; - } - } - } - } else if (bothInCluster && !sameClusterType) { - // Verschiedene Cluster-Typen: reduzierte Wahrscheinlichkeit, aber einige Cross-Cluster-Verbindungen - maxDistance = 180; - - if (distance < maxDistance) { - connectionProbability = Math.pow(1 - distance / maxDistance, 2) * 0.3; - } - } else if ((nodeA.clusterType !== -1) !== (nodeB.clusterType !== -1)) { - // Ein Knoten im Cluster, einer außerhalb: sehr geringe Wahrscheinlichkeit - maxDistance = 150; - - if (distance < maxDistance) { - connectionProbability = Math.pow(1 - distance / maxDistance, 2.5) * 0.15; - } - } else { - // Beide außerhalb von Clustern: mittlere Wahrscheinlichkeit für große Distanzen - maxDistance = 250; - - if (distance < maxDistance) { - connectionProbability = Math.pow(1 - distance / maxDistance, 1.2) * 0.4; - } + // 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); - // Zufällige Variation der Verbindungsdistanz - const connectionDistance = Math.random() * 275 + 25; + // Zusätzliche Faktoren für dynamischere Verbindungen + const randomFactorA = Math.random() * 0.2; // Leichte Zufallskomponente + const randomFactorB = Math.random() * 0.2; - // Überprüfe, ob wir eine Verbindung erstellen - if (Math.random() < connectionProbability) { - const connection = { - nodeA: i, - nodeB: j, - strength: 0.1 + Math.random() * 0.9, // Zufällige Verbindungsstärke - active: false, - signalPosition: 0, - signalSpeed: 0.02 + Math.random() * 0.08, - pulsePhase: Math.random() * Math.PI * 2 - }; - - // Verbindungen innerhalb des gleichen Clusters sind tendenziell aktiver - if (bothInCluster && sameClusterType) { - connection.active = Math.random() < 0.5; // 50% Chance für aktive Verbindungen - connection.strength *= 1.3; // Stärkere Verbindungen - } else if (bothInCluster && !sameClusterType) { - connection.active = Math.random() < 0.3; // 30% Chance für Cross-Cluster - } else { - connection.active = Math.random() < 0.15; // 15% Chance für andere - } - - // Speichere die Verbindung in beiden Knoten - nodeA.connections.push({index: j, connectionIndex: this.connections.length}); - nodeB.connections.push({index: i, connectionIndex: this.connections.length}); - this.connections.push(connection); + // 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); } } } @@ -542,15 +402,34 @@ class NeuralNetworkBackground { 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]; - node.pulsePhase += this.config.pulseSpeed * (1 + (node.connections.length * 0.04)); + + // 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); - node.y += node.speed.y * (Math.cos(now * 0.0002) * 0.4 + 0.4); + 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) { @@ -562,8 +441,12 @@ class NeuralNetworkBackground { node.y = Math.max(0, Math.min(height, node.y)); } - // Setze alle Knoten standardmäßig auf inaktiv + // 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 @@ -575,114 +458,426 @@ class NeuralNetworkBackground { } // Aktiviere den Zielknoten nur, wenn der Flow weit genug fortgeschritten ist - if (flow.targetNodeIdx !== undefined && flow.progress > 0.9) { + if (flow.targetNodeIdx !== undefined && flow.progress > 0.7) { this.nodes[flow.targetNodeIdx].isActive = true; this.nodes[flow.targetNodeIdx].lastFired = now; } } - // Zufällig neue Flows zwischen Knoten initiieren - if (Math.random() < 0.015) { // Reduzierte Chance in jedem Frame (1.5% statt 2%) - const randomNodeIdx = Math.floor(Math.random() * this.nodes.length); - const node = this.nodes[randomNodeIdx]; - - // Nur aktivieren, wenn Knoten Verbindungen hat - if (node.connections.length > 0) { - node.isActive = true; - node.lastFired = now; - - // Wähle eine zufällige Verbindung dieses Knotens - const randomConnIdx = Math.floor(Math.random() * node.connections.length); - const connectedNodeIdx = node.connections[randomConnIdx]; - - // Finde die entsprechende Verbindung - const conn = this.connections.find(c => - (c.from === randomNodeIdx && c.to === connectedNodeIdx) || - (c.from === connectedNodeIdx && c.to === randomNodeIdx) - ); - - if (conn) { - // Markiere die Verbindung als kürzlich aktiviert - conn.lastActivated = now; - - // Stelle sicher, dass die Verbindung sichtbar bleibt - if (conn.fadeState === 'out') { - conn.fadeState = 'visible'; - conn.fadeStartTime = now; - } - - // Verbindung soll schneller aufgebaut werden - if (conn.progress < 1) { - conn.buildSpeed = 0.015 + Math.random() * 0.01; - } - - // Erstelle einen neuen Flow, wenn nicht zu viele existieren - if (this.flows.length < this.config.maxFlowCount) { - // Bestimme die Richtung (vom aktivierten Knoten weg) - const direction = conn.from === randomNodeIdx; - - this.flows.push({ - connection: conn, - progress: 0, - direction: direction, - length: this.config.flowLength + Math.random() * 0.05, - creationTime: now, - totalDuration: 1000 + Math.random() * 600, - sourceNodeIdx: direction ? conn.from : conn.to, - targetNodeIdx: direction ? conn.to : conn.from - }); - } - } - } + // 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 = []; + + // 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) { // Noch früher auslösen für kontinuierlicheren Fluss + 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; + // Permanente Sichtbarkeit für Verbindungen + 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, unabhängig von der Generation + if (!flow.hasForwarded && targetNode.connections.length > 0) { + // Markiere, dass dieser Flow bereits weitergeleitet wurde + flow.hasForwarded = true; + + // Durch reduzierte Anzahl der Weiterleitungen schaffen wir einen endloseren Eindruck + // mit sichtbaren "Schlangen", die durch das Netzwerk ziehen + const connCount = Math.min(3, targetNode.connections.length); + const availableConnections = [...targetNode.connections]; + + // Keine Weiterleitung an die Quelle des Flows zurück, es sei denn, es ist die einzige Option + 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 => { + // Finde die Verbindung zwischen diesem und dem Zielknoten + const conn = this.connections.find(c => + (c.from === flow.targetNodeIdx && c.to === nodeIdx) || + (c.from === nodeIdx && c.to === flow.targetNodeIdx) + ); + + // Bewerte Verbindungen - bevorzuge solche, die länger nicht aktiviert wurden + const timeSinceLastActivation = conn ? now - (conn.lastActivated || 0) : 0; + const connectionCount = this.nodes[nodeIdx].connections.length; + + return { + nodeIdx, + connectionCount, + timeSinceLastActivation, + // Kombinierter Score für Bewertung + score: (timeSinceLastActivation * 0.00001) + // Bevorzuge länger inaktive Verbindungen + (connectionCount * 0.2) + // Bevorzuge gut vernetzte Knoten + (Math.random() * 0.5) // Zufallskomponente für Variation + }; + }).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; + + // Finde die Verbindung zwischen dem Target-Knoten und dem nächsten Knoten + const nextConn = this.connections.find(c => + (c.from === flow.targetNodeIdx && c.to === nextNodeIdx) || + (c.from === nextNodeIdx && c.to === flow.targetNodeIdx) + ); + + if (nextConn) { + // Verbindung aktivieren + nextConn.lastActivated = now; + + // Stelle sicher, dass die Verbindung dauerhaft sichtbar bleibt + if (nextConn.fadeState === 'out' || nextConn.fadeState === 'hidden') { + nextConn.fadeState = 'visible'; // Setze auf sichtbar zurück + nextConn.fadeStartTime = now; + // Verbindungen bleiben extrem lange sichtbar, um ein permanentes Netzwerk zu bilden + nextConn.visibleDuration = 600000 + Math.random() * 300000; // 10-15 Minuten sichtbar + } else if (nextConn.fadeState === 'visible') { + // Verlängere die Sichtbarkeit + nextConn.fadeStartTime = now; + } + + // Verbindung schneller aufbauen + if (nextConn.progress < 1) { + nextConn.buildSpeed = 0.04 + Math.random() * 0.02; // Noch schnellerer Aufbau + } + + // Berechne die nächste Generation - aber begrenze die Tiefe + const nextGeneration = (flow.generation || 1) + 1; + const maxGeneration = 200; // Extrem hohe Grenze für kontinuierliche Animation + + if (nextGeneration <= maxGeneration) { + // Richtung des Flows bestimmen + const direction = nextConn.from === flow.targetNodeIdx; + + // Für kontinuierliche Schlangenlinien: Berechne die zeitliche Verzögerung + // basierend auf Generation und Connection-Lä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) + ); + + // Adaptiere die Verzögerung basierend auf Verbindungslänge und Generation + // Kürzere Verbindungen und höhere Generationen haben weniger Verzögerung + const baseDelay = 80; // Basis-Verzögerung in ms + const lengthFactor = Math.min(1.2, connectionLength / 200); // 0.1-1.2 basierend auf Länge + const genFactor = Math.max(0.2, 1.0 - (nextGeneration * 0.005)); // Reduziert mit Generation + const delay = baseDelay * lengthFactor * genFactor; + + // Etwas kürzere Flows für schnellere Propagation durch das Netzwerk + setTimeout(() => { + // Prüfe, ob das Animation noch läuft, bevor ein neuer Flow hinzugefügt wird + if (!this.isDestroying && this.animationFrameId) { + // Für längere "Schlangen": Benutze dasselbe hasForwarded-Flag für alle Flows + // einer Generation, um endlosen Fluss zu ermöglichen + newFlows.push({ + connection: nextConn, + progress: 0, + direction: direction, + length: this.config.flowLength * (0.9 + Math.random() * 0.2), // Leichte Variation der Länge + creationTime: Date.now(), + // Längere Duration für höhere Generationen - schafft sichtbare "Schlangen" + 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, // Zurückgesetzt, damit auch dieser Flow weiterleiten kann + generation: nextGeneration, + hasForwarded: false, // Jeder Flow leitet genau einmal weiter + parentFlow: flow.id // Merke, von welchem Flow dieser stammt + }); + } + }, delay); + } + } + } + } + } + } + + // Stellen Sie sicher, dass die Verbindung aktiv bleibt + flow.connection.lastActivated = now; + + // Erhöhe die Sichtbarkeitsdauer jeder aktivierten Verbindung + if (flow.connection.fadeState === 'visible') { + // Stelle sicher, dass bereits sichtbare Verbindungen permanent sichtbar bleiben + flow.connection.visibleDuration = Math.max( + flow.connection.visibleDuration || 300000, + 600000 + Math.random() * 300000 // 10-15 Minuten Mindest-Sichtbarkeit + ); + } + + // Remove completed flows, but ensure parent-child relationships for visible "snakes" + // Bei "schlangenhaften" Flows: Entferne nur, wenn der Flow zu Ende ist UND ein Kind hat + // oder wenn er sein Maximum erreicht hat + const isComplete = flow.progress > 1.0 || flowProgress >= 1.0; + const hasMaxGeneration = flow.generation && flow.generation > 200; + + if (isComplete || hasMaxGeneration) { + this.flows.splice(i, 1); + } + } + + // Füge alle neuen Flows hinzu, die während der Iteration erzeugt wurden + 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 - connection.fadeProgress = Math.min(1.0, elapsedTime / connection.fadeTotalDuration); + // 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 - if (elapsedTime > connection.visibleDuration) { + // 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, aber nie komplett verschwinden - connection.fadeProgress = Math.max(0.1, 1.0 - (elapsedTime / connection.fadeTotalDuration)); + // 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 immer minimal sichtbar (nie komplett unsichtbar) - if (connection.fadeProgress <= 0.1) { - // Statt Verbindung komplett zu verstecken, setzen wir sie zurück auf "in" + // 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.1; // Minimal sichtbar bleiben - connection.visibleDuration = 15000 + Math.random() * 20000; // Längere Sichtbarkeit + connection.fadeProgress = 0.25; // Höhere Grundsichtbarkeit + connection.visibleDuration = 600000 + Math.random() * 300000; // 10-15 Minuten sichtbar } } else if (connection.fadeState === 'hidden') { - // Keine Verbindungen mehr verstecken, stattdessen immer wieder einblenden + // Keine Verbindungen verstecken, alle sichtbar halten connection.fadeState = 'in'; connection.fadeStartTime = now; - connection.fadeProgress = 0.1; + connection.fadeProgress = 0.25; // Höhere Grundsichtbarkeit } - // Verbindungen immer vollständig aufbauen und nicht zurücksetzen + // Verbindungen schneller vollständig aufbauen für permanentes Netzwerk if (connection.progress < 1) { - // Konstante Aufbaugeschwindigkeit, unabhängig vom Status - const baseBuildSpeed = 0.003; + // 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, schneller aufbauen + // Wenn kürzlich aktiviert, noch schneller aufbauen if (now - connection.lastActivated < 2000) { - buildSpeed = Math.max(buildSpeed, 0.006); + buildSpeed = Math.max(buildSpeed, 0.015); // Erhöht für schnelleren Aufbau } connection.progress += buildSpeed; @@ -694,93 +889,43 @@ class NeuralNetworkBackground { } } } - - // Update flows with proper fading - this.updateFlows(now); - - // Seltener neue Flows erstellen - if (Math.random() < this.config.flowDensity && this.flows.length < this.config.maxFlowCount) { - this.createNewFlow(now); - } - - // Recalculate connections occasionally for a living network - if (Math.random() < 0.005) { // Only recalculate 0.5% of the time for performance - this.createConnections(); - } - - // Render - if (this.useWebGL) { - this.renderWebGL(now); - } else { - this.renderCanvas(now); - } - - // Continue animation - this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); - } - - // Updated method to update flow animations with proper fading - updateFlows(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 - flow.progress += this.config.flowSpeed / 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 erst aktivieren, wenn der Flow ihn erreicht hat - if (flow.targetNodeIdx !== undefined && flow.progress > 0.9) { - this.nodes[flow.targetNodeIdx].isActive = true; - this.nodes[flow.targetNodeIdx].lastFired = now; - } - - // Stellen Sie sicher, dass die Verbindung aktiv bleibt - flow.connection.lastActivated = now; - - // Remove completed or expired flows - if (flow.progress > 1.0 || flowProgress >= 1.0) { - this.flows.splice(i, 1); - } - } } // Updated method to create flow animations with timing info createNewFlow(now) { - if (this.connections.length === 0 || this.flows.length >= 6) return; + if (this.connections.length === 0 || this.flows.length >= 8) return; - // Select a random connection with preference for more connected nodes - let connectionIdx = Math.floor(Math.random() * this.connections.length); - let attempts = 0; + // 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 + }; + }); - // 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++; - } + // 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.015 + Math.random() * 0.01; // Schnellerer Aufbau während eines Blitzes + 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 @@ -790,7 +935,12 @@ class NeuralNetworkBackground { direction: Math.random() > 0.5, // Randomly decide direction length: this.config.flowLength + Math.random() * 0.1, // Slightly vary lengths creationTime: now, - totalDuration: 800 + Math.random() * 500 // Zufällige Gesamtdauer (800-1300ms) + 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 }); } @@ -800,6 +950,9 @@ class NeuralNetworkBackground { 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); @@ -807,16 +960,16 @@ class NeuralNetworkBackground { this.gl.uniform2f(this.programInfo.uniformLocations.resolution, width, height); // Draw connections first (behind nodes) - this.renderConnectionsWebGL(now); + this.renderConnectionsWebGL(now, fadeoutFactor); // Draw flows on top of connections - this.renderFlowsWebGL(now); + this.renderFlowsWebGL(now, fadeoutFactor); // Draw nodes - this.renderNodesWebGL(now); + this.renderNodesWebGL(now, fadeoutFactor); } - renderNodesWebGL(now) { + 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); @@ -827,11 +980,11 @@ class NeuralNetworkBackground { positions[i * 2 + 1] = node.y; // Sanftere Pulsation mit moderaterem Aktivierungsboost - const activationBoost = node.isActive ? 1.3 : 1.0; - let pulse = (Math.sin(node.pulsePhase) * 0.25 + 1.2) * activationBoost; + 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) * 1.1; + const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 0.8; // Reduziert von 1.1 auf 0.8 sizes[i] = node.size * pulse * connectivityFactor; } @@ -879,18 +1032,20 @@ class NeuralNetworkBackground { let g = nodeColor.g / 255; let b = nodeColor.b / 255; - // Active nodes get slightly brighter color - subtiler + // Active nodes get slightly brighter color - noch subtiler if (node.isActive) { - r = (r * 0.7 + nodePulseColor.r / 255 * 0.3); - g = (g * 0.7 + nodePulseColor.g / 255 * 0.3); - b = (b * 0.7 + nodePulseColor.b / 255 * 0.3); + 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); } - // Subtilere Knoten mit reduzierter Opazität + // 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, - node.isActive ? 0.75 : 0.65 // Geringere Sichtbarkeit für subtileres Erscheinungsbild + nodeOpacity * fadeoutFactor // Reduzierte Opazität während des Ausblendprozesses ); // Draw each node individually for better control @@ -898,7 +1053,7 @@ class NeuralNetworkBackground { } } - renderConnectionsWebGL(now) { + 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; @@ -912,14 +1067,17 @@ class NeuralNetworkBackground { const y2 = fromNode.y + (toNode.y - fromNode.y) * progress; // Calculate opacity based on fade state - let opacity = connection.opacity * connection.fadeProgress * 0.85; // Reduzierte Gesamtopazität + 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.3); + 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 @@ -956,8 +1114,8 @@ class NeuralNetworkBackground { } } - // New method to render the flowing animations - renderFlowsWebGL(now) { + // 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; @@ -988,7 +1146,7 @@ class NeuralNetworkBackground { }; } // Zickzack-Blitz generieren - const zigzag = this.generateZigZagPoints(p1, p2, 8, 10); + 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, @@ -1010,26 +1168,27 @@ class NeuralNetworkBackground { const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; const flowColor = this.hexToRgb(colorObj.flowColor); // Definiere fadeFactor als 1.0, falls nicht von flow definiert - const fadeFactor = flow.fadeFactor || 1.0; + // 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.7 * fadeFactor // Reduced from higher values + 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.3); // Schmaler Blitz + this.gl.lineWidth(1.2); // Schmaler Blitz this.gl.drawArrays(this.gl.LINES, 0, 2); } - // Funken erzeugen - echte elektrische Funken - const sparks = this.generateSparkPoints(zigzag, 7 + Math.floor(Math.random() * 3)); + // 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 + // Helles Weiß-Blau für elektrische Funken, aber dezenter this.gl.uniform4f( this.programInfo.uniformLocations.color, - 0.88, 0.95, 1.0, 0.65 // Elektrisches Blau-Weiß + 0.85, 0.92, 0.98, 0.5 * fadeoutFactor // Reduzierte Opazität auf 50% ); // Position für den Funken setzen @@ -1037,8 +1196,8 @@ class NeuralNetworkBackground { 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 * 3.0]); + // 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); @@ -1072,6 +1231,9 @@ class NeuralNetworkBackground { 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 @@ -1121,12 +1283,15 @@ class NeuralNetworkBackground { 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)`; + this.ctx.shadowColor = `rgba(${rgbColor.r}, ${rgbColor.g}, ${rgbColor.b}, ${0.15 * fadeoutFactor})`; this.ctx.shadowBlur = 3; } else { this.ctx.shadowBlur = 0; @@ -1142,11 +1307,11 @@ class NeuralNetworkBackground { this.ctx.fill(); } - this.ctx.shadowBlur = 2; // Reset shadow for other elements + this.ctx.shadowBlur = 0; // Reset shadow for other elements } // Draw flows with fading effect - this.renderFlowsCanvas(now); + this.renderFlowsCanvas(now, fadeoutFactor); // Draw nodes with enhanced animations const nodeColor = this.isDarkMode @@ -1164,7 +1329,9 @@ class NeuralNetworkBackground { // Größe basierend auf Konnektivität und Wichtigkeit const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 1.4; - const nodeSize = node.size * pulse * connectivityFactor; + // 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); @@ -1189,21 +1356,24 @@ class NeuralNetworkBackground { ); // Intensiveres Zentrum und weicherer Übergang - glow.addColorStop(0, `rgba(${r}, ${g}, ${b}, ${node.isActive ? 0.95 : 0.8})`); - glow.addColorStop(0.4, `rgba(${r}, ${g}, ${b}, 0.45)`); + // 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; - this.ctx.globalAlpha = node.isActive ? 1.0 : 0.92; + // 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)`; + this.ctx.fillStyle = `rgba(${rgbPulseColor.r}, ${rgbPulseColor.g}, ${rgbPulseColor.b}, ${0.9 * fadeoutFactor})`; this.ctx.fill(); } @@ -1212,7 +1382,7 @@ class NeuralNetworkBackground { } // New method to render flows in Canvas mode with fading - renderFlowsCanvas(now) { + renderFlowsCanvas(now, fadeoutFactor = 1.0) { if (!this.flows.length) return; // Für jeden Flow in der Blitzanimation @@ -1240,11 +1410,16 @@ class NeuralNetworkBackground { // 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); @@ -1343,12 +1518,12 @@ class NeuralNetworkBackground { this.ctx.fillStyle = this.isDarkMode ? `rgba(245, 252, 255, ${0.85 * fadeFactor})` : `rgba(230, 245, 255, ${0.8 * fadeFactor})`; - this.ctx.shadowColor = this.isDarkMode + 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(); - + 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); @@ -1429,48 +1604,70 @@ class NeuralNetworkBackground { return { r, g, b, a: alpha }; } - // Cleanup method + // Cleanup method mit deutlich sanfterem Ausblenden destroy() { - // Sanftes Ausblenden der Animation vor dem Entfernen + // Sanfte Ausblendanimation starten + this.isDestroying = true; + + // Canvas-Element sanft ausblenden über einen längeren Zeitraum if (this.canvas) { - // Aktuelle Opazität abrufen und Animation starten - const currentOpacity = parseFloat(this.canvas.style.opacity) || 1; - this.canvas.style.transition = 'opacity 1500ms ease-out'; + this.canvas.style.transition = 'opacity 5s ease-in-out'; // Verlängert von 1.5s auf 5s + this.canvas.style.opacity = '0'; - // Animation starten + // Warte auf das Ende der Übergangsanimation, bevor die Ressourcen freigegeben werden setTimeout(() => { - this.canvas.style.opacity = '0'; - }, 10); - - // Erst nach dem vollständigen Ausblenden Ressourcen freigeben - setTimeout(() => { - // Animation beenden - if (this.animationFrameId) { - cancelAnimationFrame(this.animationFrameId); - } + // 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 + } + }; - // Event-Listener entfernen - window.removeEventListener('resize', this.resizeCanvas.bind(this)); + // 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'); + }; - // Canvas aus dem DOM entfernen - if (this.canvas && this.canvas.parentNode) { - this.canvas.parentNode.removeChild(this.canvas); - } + // Startzeit für die schrittweise Verlangsamung setzen + this._destroyStartTime = Date.now(); - // 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 sanft ausgeblendet und bereinigt'); - }, 1500); // Entspricht der Transitions-Dauer - } else { - // Fallback für den Fall, dass kein Canvas existiert - if (this.animationFrameId) { - cancelAnimationFrame(this.animationFrameId); - } + // Starte die schrittweise Verlangsamung + slowDownAnimation(); + }, 5500); // Warte länger als die CSS-Transition-Dauer } } @@ -1560,6 +1757,334 @@ class NeuralNetworkBackground { 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 / 100); + + // 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 @@ -1583,19 +2108,7 @@ window.addEventListener('load', () => { // Event listener to clean up when the window is closed window.addEventListener('beforeunload', function() { - if (window.neuralNetworkBackground) { - // Sanftes Ausblenden vor dem Schließen der Seite initiieren - window.neuralNetworkBackground.destroy(); - } -}); - -// Füge Handler für Navigationsänderungen hinzu (für SPA-Anwendungen) -document.addEventListener('visibilitychange', function() { - if (document.visibilityState === 'hidden' && window.neuralNetworkBackground) { - // Sanftes Ausblenden wenn der Tab in den Hintergrund wechselt - window.neuralNetworkBackground.destroy(); - } else if (document.visibilityState === 'visible' && !window.neuralNetworkBackground) { - // Neu initialisieren, wenn der Tab wieder sichtbar wird - window.neuralNetworkBackground = new NeuralNetworkBackground(); - } + if (window.neuralNetworkBackground) { + window.neuralNetworkBackground.destroy(); + } });