From 7a0533ac09048c7d69b0af0385dc67cf716ce3b2 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Mon, 28 Apr 2025 14:49:02 +0200 Subject: [PATCH] =?UTF-8?q?Verbessere=20die=20Funktionalit=C3=A4t=20des=20?= =?UTF-8?q?Chat-Assistenten=20in=20app.py:=20Aktualisiere=20die=20Systemna?= =?UTF-8?q?chricht=20mit=20spezifischen=20Informationen=20zur=20Systades-W?= =?UTF-8?q?issensdatenbank=20und=20erweitere=20die=20API-Nachrichtenformat?= =?UTF-8?q?ierung.=20F=C3=BCge=20Unterst=C3=BCtzung=20f=C3=BCr=20ausgew?= =?UTF-8?q?=C3=A4hlte=20Elemente=20aus=20der=20Datenbank=20hinzu=20und=20e?= =?UTF-8?q?rh=C3=B6he=20die=20maximale=20Tokenanzahl=20f=C3=BCr=20detailli?= =?UTF-8?q?ertere=20Antworten.=20Implementiere=20eine=20neue=20JavaScript-?= =?UTF-8?q?Datei=20f=C3=BCr=20eine=20neuronale=20Netzwerk-Hintergrundanima?= =?UTF-8?q?tion=20und=20verbessere=20die=20CSS-Stile=20f=C3=BCr=20den=20Li?= =?UTF-8?q?ght=20Mode.=20Optimiere=20die=20Benutzeroberfl=C3=A4che=20und?= =?UTF-8?q?=20die=20Lesbarkeit=20in=20beiden=20Modi.=20Aktualisiere=20die?= =?UTF-8?q?=20Grundstile=20f=C3=BCr=20eine=20konsistente=20Darstellung.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/app.cpython-313.pyc | Bin 65846 -> 67747 bytes app.py | 46 +- static/css/base-styles.css | 111 ++- static/css/neural-network-background.css | 65 +- static/css/style.css | 200 ++++ static/neural-network-background-full.js | 1109 ++++++++++++++++++++++ static/neural-network-background.js | 830 +++++++++++----- static/style.css | 16 + templates/base.html | 100 +- 9 files changed, 2175 insertions(+), 302 deletions(-) create mode 100644 static/neural-network-background-full.js diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index 04d49718edef87daa74461b6014305a96de9455d..87792dddd43113ec06daa3c64dd941f17b5f4f48 100644 GIT binary patch delta 3263 zcma)8eQZ!v^g#tA6_oHzuM@R5X&1b8@XODVj>*Y+d!Grad4 zi0SHWnmVLSYrES@bp2zf9crjn3Fa0BjY)KAN?7=K#%sgU@iC@tFm)O<8)#^gcCKR^ z!$4d9xIX9H-#O=ZKCYcp-_gDMy3RC{oo!&??{jaC_nqmJrpsfRS2BJyywJpOtdnVk z8-72-ILXPE3(4Y7CiHD=&(CHx{dgUrumLyFmuBc9%$f4mDq@jwrl4qbw zQz$!E#+5$})g`C#KxsTGQoa@3bKJtG`9jr;(ol=J%4eXeQm9653AglVsIDt%Jgk&Q zyldCuJz19Wr(A4L^Z&oORHtIuxEgNx(_*!;T`L(Ti$M&R0dX^HG+Zrtb-a*$o*Wxr zrk&fDL#}^NM(&N5YF9W7B=6Jx6?MrqN$v%|09MJITBVg_*MzxvRoXhOj<1uQtM4?5 zSD*GEoIyugNo zd$kO+A&B|~SwUD3k-QIw1l}je0+tjkp%z(&D=aF=J$6Py+_0?hE-c$97$ZRx14Fzb z3}LiWkYy~&eqQ7~*bffXTYbEuxC4^ECgp~bX#`QhKA#Zw01(UZd+bM{poDs{bb$9M zLD6HQ@}M|&W}h206ck;kSCE4Va?~^w5EFT8&;|jcR7Qj)|JW&yPZ;n*Su*MlDj_V1 zHq?e)yf}zONCg;on5U#|PzUPd73>K}!t7l@^6+B3382%=uAnsFMO}jE^7H#->OS;{ z3l?jD2jmhw7;OrQgNhIk!BGad!%&^dI+Usn`2_{-5W>&}NdJwX%=@8dZlD8oB~o_c z;R6B5B}0HDIXabOFBC8KL+j-MFGfAHbU_V>0PldNN<6TDxuj8+l0!;S568pN1Rd2L zEGuJYl#qf^i^%(iWgIX332yP8cZgmX5vHj+13n+$ACMkf0$%FJP#^8v4lMYf9y+O% zmjM=z7ZoV30EVxi7AME8Z{O0fk>j>>tZyeDOq!RvVE2;CR-M=_7mP^khxx@L@5KGG zpkVUH$xW8D5$%AsKtkA7uY`&9LMyrR>-D+QCK>zifP!7S!N)HX+hkcdlw~k<2B235 z6$c8^H)b%j1Z59CF!oa~)Q2|u;+u>SACwVgv?0mBVVKq_!~3w*2tP>@h9i`fnzI8* zyQn44DfZ(*UZm52VZ*GaS;ND8l$duoHE?ivkvbH1#$VMFEq`c4D7g!O+)a_DS!B{I zy#eGy*^d?lQy{e!JmQerBK$BHp;jD*)se8!4+{n8Jrq4}Hf$??bQJ==PFlfW>VW_n zQ*?sv6Svam^bWd_*gOXc%}XPZu!an%9CeBTm?(sMm%11eHN}n6I79M&l}}#1a7dG> zj~FjDvzpu5NcW`q*LuPWo37>Gg{+<9`>;5ZW;MdUBk|hdunCg z)uPg$Sl+Uz&#gGo8Y{9NHW71lahSsIYMHs_`(=zV|Eh7p(d-e=$k){}d(>z@-2LaQ zBCx4t-BDxr;qKpADqquG%`ZB#F99GTzl10Inp_*8|^)9 zoT_h+*0)cX)$KS|?^f*|)g!3Zy-~~FBPM{UwsmK9XL~<3PKBGdMw_>)+xpa9N~}4k zt{YN^4yxASsAV{g9N98jFzP;CGS$!#ZRnV=s=e-5gGa6Rs@}b-buelf{ILlNJK}z; zWMo0CsE%^z8eKFxblNuM*c^3io>;ANLd>yOZ5UJs{i;=rTEqm0HKRMuhAU6MFxAu* zZR(ovtGo8cnk03Ntja;vIux}GJwZ0Xb&uLV9BU4#>kg@hzNuPYj#^%RM55@kq80z$ zi23Tar#E7|jk5UPBt|x$P)0YMd}+$w7PYrcWT@MA$LxF5)x63Ns8(0h;(FV3ub3&S zx{JuJ=x^C#V!slrJnv|k*Hy|)X1C_R$Hh`Z52L+U)ne*y)LvS}f^w-*-@`;|V>>h1 zv*hp>YuVPwxi9Kj{Uj{aMxwhG(n}*C5^`;^UOG;tPp^G{&N&dI^W=v&=f{B@k*+Uc zFOavdM@yn`wNxinP(&?VSNedZ-%#~4a_>T6+@TjDSCR8K5IaTwa$}KRdLQlyy6Jr~ z@&Gx0lSaOB^W0;RZO@AAC6|BS0a1%?_2{MdXw<=5GL&}X)_OyPlAwnpV*A5Et^N*( zp~&$+y}`0ylC`&s^?wCJIP&7{g{=N>ApRio%AG?R{R0q5WX_i#>DhmgGxtJz2LAj0 zd8F~HU$X2>S#D@c%LZ delta 1402 zcmb_aU2GIp6rMY?({8sryG?i7rQ5%|Y=5D)C}JxLWsx9jl^Q1_=&CsF&UQDpQ<>Qf zba~K_Mo36RIQY;91JMT?i3T&lp9B)IZCKh!f3!{MYbpInY`1JeLJh%t7upmO-@Fg! zo^$SZ&iCDO?>A>mKOHyOQi5P%$g2C$9`3pMqiwj4oiLs4@(PTIB<3Y@B7e*0B{TFS$~h<5uO$w9@>Wl(9)o2!!vtKbm0~^!L#{&CI^qp!;sVj#WL2}I zEO_&<6TYa-;w*t2*tN45A`O)#Vxh!|MPjj7B9^|$tJ&)s;OhWY78FAxQ zZdD0pr0lma$X`3y0v>$FHbDEJ({2r9u8HzWRyaLa>JdaL!>QWV^)Cx-Ud2fQe7eb% zy>=PN2Frt6g(nfu4!Jz!N;+H0h;CKz31q72J%mEc4z835q3UuYGV~sLBR;k#hFcGFO^LLI6%G4zoum~tT7!cmd^gsnMM5Dg$3i$5QSgC?t}Ch@lnJCoRzJdF zISS{7_d;s88%~Urd=e`r&-y<$uf?R9tVWM?YEgygRbyIO%wA1yQ&ild#QUzawG-`F z?9!uhQBcuwObw>RKP~pLP@fRbwpf}v)u?hbx*fyLu`}##lQBBB3$X`T!||6NAv?T3 zUJvKT3u`Ws`%GC#Yb|;x8tG89i!_lTtNIZ|)4nAyq+KQ%oqkc68{d?Dl|+|JB=ZXk z#S;M=E9^%pj&nDoleVN4KAU(M-G#o13Rsya_t3*6$7H$gSsANi((1k-^tScBGg4AN zYOO!HKbZlGledr?61V=W=-=qe-N!Qn+jiT@mf$V^DlTF7+5BGaMlmA64KMGnGOkS> zFr!~#cqR{Or>jwkad5f;@%==V2i&ujaOJj`7k6Otw7T5Up{OBQ*JElM8H97D+!nWD z+@y4oaR+reo0(G?zmZbychWFF-K7o|W~$HtIPQ!V4U(g3ljfm_Ep!xzsQH72H_y)I zrSZ=Z)>qL8w9J;}#&a;~>`^4(enl~9v!^ei0Z_-W$4ZR9VWL+B1%Tq@*eNg`kjEqM_?Pu#GP zN*-hRW#V=kdg?Nd?m_kaIKM&?pBugRuOjrf5&Gu=DqSKOZIPONR8^9CjnwO=<_F{W zgA=HZeuXr>Mu{~_rcoM=5@_F1 { + this.setDarkMode(e.detail.isDarkMode); + }); + + // Initialisierung + this.webglSetupComplete = false; + if (this.useWebGL && this.setupWebGL()) { + this.webglSetupComplete = true; + } else { + this.ctx = this.canvas.getContext('2d'); + } + + // If canvas already exists, remove it first + const existingCanvas = document.getElementById('neural-network-background'); + if (existingCanvas) { + existingCanvas.remove(); + } + + // Append to body as first child to ensure it's behind everything + if (document.body.firstChild) { + document.body.insertBefore(this.canvas, document.body.firstChild); + } else { + document.body.appendChild(this.canvas); + } + + // Animation properties + this.animationFrameId = null; + + // Initialize + this.init(); + + // Event listeners + document.addEventListener('darkModeToggled', (event) => { + this.isDarkMode = event.detail.isDark; + }); + + // Log that the background is initialized + console.log('Neural Network Background initialized'); + } + + init() { + this.resizeCanvas(); + + if (this.useWebGL) { + this.initWebGL(); + } + + this.createNodes(); + this.createConnections(); + this.startAnimation(); + } + + resizeCanvas() { + const pixelRatio = window.devicePixelRatio || 1; + const width = window.innerWidth; + const height = window.innerHeight; + + this.canvas.style.width = width + 'px'; + this.canvas.style.height = height + 'px'; + this.canvas.width = width * pixelRatio; + this.canvas.height = height * pixelRatio; + + if (this.useWebGL) { + this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); + } else if (this.ctx) { + this.ctx.scale(pixelRatio, pixelRatio); + } + + // Recalculate node positions after resize + if (this.nodes.length) { + this.createNodes(); + this.createConnections(); + } + } + + initWebGL() { + // Vertex shader + const vsSource = ` + attribute vec2 aVertexPosition; + attribute float aPointSize; + uniform vec2 uResolution; + + void main() { + // Convert from pixel to clip space + vec2 position = (aVertexPosition / uResolution) * 2.0 - 1.0; + // Flip Y coordinate + position.y = -position.y; + + gl_Position = vec4(position, 0, 1); + gl_PointSize = aPointSize; + } + `; + + // Fragment shader - Softer glow effect + const fsSource = ` + precision mediump float; + uniform vec4 uColor; + + void main() { + float distance = length(gl_PointCoord - vec2(0.5, 0.5)); + + // Softer glow with smoother falloff + float alpha = 1.0 - smoothstep(0.1, 0.5, distance); + alpha = pow(alpha, 1.5); // Make the glow even softer + + gl_FragColor = vec4(uColor.rgb, uColor.a * alpha); + } + `; + + // Initialize shaders + const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource); + const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource); + + // Create shader program + this.shaderProgram = this.gl.createProgram(); + this.gl.attachShader(this.shaderProgram, vertexShader); + this.gl.attachShader(this.shaderProgram, fragmentShader); + this.gl.linkProgram(this.shaderProgram); + + if (!this.gl.getProgramParameter(this.shaderProgram, this.gl.LINK_STATUS)) { + console.error('Unable to initialize the shader program: ' + + this.gl.getProgramInfoLog(this.shaderProgram)); + return; + } + + // Get attribute and uniform locations + this.programInfo = { + program: this.shaderProgram, + attribLocations: { + vertexPosition: this.gl.getAttribLocation(this.shaderProgram, 'aVertexPosition'), + pointSize: this.gl.getAttribLocation(this.shaderProgram, 'aPointSize') + }, + uniformLocations: { + resolution: this.gl.getUniformLocation(this.shaderProgram, 'uResolution'), + color: this.gl.getUniformLocation(this.shaderProgram, 'uColor') + } + }; + + // Create buffers + this.positionBuffer = this.gl.createBuffer(); + this.sizeBuffer = this.gl.createBuffer(); + + // Set clear color for WebGL context + const bgColor = this.hexToRgb(this.darkModeColors.background); + + this.gl.clearColor(bgColor.r/255, bgColor.g/255, bgColor.b/255, 1.0); + } + + loadShader(type, source) { + const shader = this.gl.createShader(type); + this.gl.shaderSource(shader, source); + this.gl.compileShader(shader); + + if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { + console.error('An error occurred compiling the shaders: ' + + this.gl.getShaderInfoLog(shader)); + this.gl.deleteShader(shader); + return null; + } + + return shader; + } + + createNodes() { + this.nodes = []; + const width = this.canvas.width / (window.devicePixelRatio || 1); + const height = this.canvas.height / (window.devicePixelRatio || 1); + + // Erstelle Cluster-Zentren für neuronale Netzwerkmuster + const clusterCount = Math.floor(5 + Math.random() * 4); // 5-8 Cluster + const clusters = []; + + for (let i = 0; i < clusterCount; i++) { + clusters.push({ + x: Math.random() * width, + y: Math.random() * height, + radius: 100 + Math.random() * 150 + }); + } + + // Create nodes with random positions and properties + for (let i = 0; i < this.config.nodeCount; i++) { + // Entscheide, ob dieser Knoten zu einem Cluster gehört oder nicht + const useCluster = Math.random() < this.config.clusteringFactor; + let x, y; + + if (useCluster && clusters.length > 0) { + // Wähle ein zufälliges Cluster + const cluster = clusters[Math.floor(Math.random() * clusters.length)]; + const angle = Math.random() * Math.PI * 2; + const distance = Math.random() * cluster.radius; + + // Platziere in der Nähe des Clusters mit einiger Streuung + x = cluster.x + Math.cos(angle) * distance; + y = cluster.y + Math.sin(angle) * distance; + + // Stelle sicher, dass es innerhalb des Bildschirms bleibt + x = Math.max(0, Math.min(width, x)); + y = Math.max(0, Math.min(height, y)); + } else { + // Zufällige Position außerhalb von Clustern + x = Math.random() * width; + y = Math.random() * height; + } + + // Bestimme die Knotengröße - wichtigere Knoten (in Clustern) sind größer + const nodeImportance = useCluster ? 1.2 : 0.8; + const size = this.config.nodeSize * nodeImportance + Math.random() * this.config.nodeVariation; + + const node = { + x: x, + y: y, + size: size, + speed: { + x: (Math.random() - 0.5) * this.config.animationSpeed, + y: (Math.random() - 0.5) * this.config.animationSpeed + }, + pulsePhase: Math.random() * Math.PI * 2, // Random starting phase + connections: [], + isActive: Math.random() < 0.3, // Some nodes start active for neural firing effect + lastFired: 0, // For neural firing animation + firingRate: 1000 + Math.random() * 4000 // Random firing rate in ms + }; + + this.nodes.push(node); + } + } + + createConnections() { + this.connections = []; + this.flows = []; // Reset flows + + // Create connections between nearby nodes + for (let i = 0; i < this.nodes.length; i++) { + const nodeA = this.nodes[i]; + nodeA.connections = []; + + // Sortiere andere Knoten nach Entfernung für bevorzugte nahe Verbindungen + const potentialConnections = []; + + for (let j = 0; j < this.nodes.length; j++) { + if (i === j) continue; + + const nodeB = this.nodes[j]; + const dx = nodeB.x - nodeA.x; + const dy = nodeB.y - nodeA.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + if (distance < this.config.connectionDistance) { + potentialConnections.push({ + index: j, + distance: distance + }); + } + } + + // Sortiere nach Entfernung + potentialConnections.sort((a, b) => a.distance - b.distance); + + // Wähle die nächsten N Verbindungen, maximal maxConnections + const maxConn = Math.min( + this.config.maxConnections, + potentialConnections.length, + 1 + Math.floor(Math.random() * this.config.maxConnections) + ); + + for (let c = 0; c < maxConn; c++) { + const connection = potentialConnections[c]; + const j = connection.index; + const nodeB = this.nodes[j]; + const distance = connection.distance; + + // Create weighted connection (closer = stronger) + const connectionStrength = Math.max(0, 1 - distance / this.config.connectionDistance); + const connOpacity = connectionStrength * this.config.connectionOpacity; + + // Check if connection already exists + if (!this.connections.some(conn => + (conn.from === i && conn.to === j) || (conn.from === j && conn.to === i) + )) { + // Neue Verbindung startet mit progress=0 für animierten Aufbau + this.connections.push({ + from: i, + to: j, + distance: distance, + opacity: connOpacity, + strength: connectionStrength, + hasFlow: false, + lastActivated: 0, + progress: 0 // Animationsfortschritt für Verbindungsaufbau + }); + nodeA.connections.push(j); + nodeB.connections.push(i); + } + } + } + } + + startAnimation() { + this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); + } + + animate() { + // Update nodes + const width = this.canvas.width / (window.devicePixelRatio || 1); + const height = this.canvas.height / (window.devicePixelRatio || 1); + const now = Date.now(); + + // Simulate neural firing + for (let i = 0; i < this.nodes.length; i++) { + const node = this.nodes[i]; + + // Check if node should fire based on its firing rate + if (now - node.lastFired > node.firingRate) { + node.isActive = true; + node.lastFired = now; + + // Activate connected nodes with probability based on connection strength + for (const connIndex of node.connections) { + // Find the connection + const conn = this.connections.find(c => + (c.from === i && c.to === connIndex) || (c.from === connIndex && c.to === i) + ); + + if (conn) { + // Mark connection as recently activated + conn.lastActivated = now; + + // Create a flow along this connection + if (Math.random() < conn.strength * 0.8) { + this.flows.push({ + connection: conn, + progress: 0, + direction: conn.from === i, // Flow from activated node + length: this.config.flowLength + Math.random() * 0.1, + intensity: 0.7 + Math.random() * 0.3 // Random intensity for variation + }); + } + + // Probability for connected node to activate + if (Math.random() < conn.strength * 0.5) { + this.nodes[connIndex].isActive = true; + this.nodes[connIndex].lastFired = now - Math.random() * 500; // Slight variation + } + } + } + } else if (now - node.lastFired > 300) { // Deactivate after short period + node.isActive = false; + } + } + + // Animierter Verbindungsaufbau: progress inkrementieren + for (const connection of this.connections) { + if (connection.progress < 1) { + // Langsamer Aufbau: Geschwindigkeit kann angepasst werden + connection.progress += 0.012; // Sehr langsam, für subtilen Effekt + if (connection.progress > 1) connection.progress = 1; + } + } + + // Update flows + this.updateFlows(); + + // Occasionally create new flows along connections + if (Math.random() < this.config.flowDensity) { + this.createNewFlow(); + } + + // Recalculate connections occasionally for a living network + if (Math.random() < 0.01) { // Only recalculate 1% of the time for performance + this.createConnections(); + } + + // Render + if (this.useWebGL) { + this.renderWebGL(); + } else { + this.renderCanvas(); + } + + // Continue animation + this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); + } + + // New method to update flow animations + updateFlows() { + // Update existing flows + const now = Date.now(); + + for (let i = this.flows.length - 1; i >= 0; i--) { + const flow = this.flows[i]; + + // Update flow progress + flow.progress += this.config.flowSpeed / flow.connection.distance; + + // Update line progress (langsamer als der Blitz) + flow.lineProgress = Math.min(flow.progress * 1.5, 1.0); + + // Remove completed flows + if (flow.progress > 1.0) { + this.flows.splice(i, 1); + } + } + + // Generate new flows selten und kontrolliert + if (Math.random() < this.config.flowDensity && + (!this.config.sequentialFlows || + now - this.lastFlowTime >= this.flowInterval)) { + this.createNewFlow(); + } + } + + // New method to create flow animations + createNewFlow() { + if (this.connections.length === 0) return; + + // Überprüfen, ob maximale Anzahl gleichzeitiger Flüsse erreicht ist + if (this.config.sequentialFlows && + this.flows.length >= this.config.maxSimultaneousFlows) { + return; + } + + // Überprüfen, ob genügend Zeit seit dem letzten Fluss vergangen ist + const now = Date.now(); + if (this.config.sequentialFlows && + now - this.lastFlowTime < this.flowInterval) { + return; + } + + // Zeit des letzten Flusses aktualisieren + this.lastFlowTime = now; + + // Select a random connection with preference for more connected nodes + let connectionIdx = Math.floor(Math.random() * this.connections.length); + let attempts = 0; + + // Try to find a connection with more connected nodes + while (attempts < 5) { + const testIdx = Math.floor(Math.random() * this.connections.length); + const testConn = this.connections[testIdx]; + const fromNode = this.nodes[testConn.from]; + + if (fromNode.connections.length > 2) { + connectionIdx = testIdx; + break; + } + attempts++; + } + + const connection = this.connections[connectionIdx]; + + // Create a new flow along this connection + this.flows.push({ + connection: connection, + progress: 0, + direction: Math.random() > 0.5, // Randomly decide direction + length: this.config.flowLength + Math.random() * 0.1, // Slightly vary lengths + lineProgress: 0 // Fortschritt der unterliegenden Strichlinie (startet bei 0) + }); + } + + renderWebGL() { + this.gl.clear(this.gl.COLOR_BUFFER_BIT); + + const width = this.canvas.width / (window.devicePixelRatio || 1); + const height = this.canvas.height / (window.devicePixelRatio || 1); + + // Select shader program + this.gl.useProgram(this.programInfo.program); + + // Set resolution uniform + this.gl.uniform2f(this.programInfo.uniformLocations.resolution, width, height); + + // Draw connections first (behind nodes) + this.renderConnectionsWebGL(); + + // Draw flows on top of connections + this.renderFlowsWebGL(); + + // Draw nodes + this.renderNodesWebGL(); + } + + renderNodesWebGL() { + // Prepare node positions for WebGL + const positions = new Float32Array(this.nodes.length * 2); + const sizes = new Float32Array(this.nodes.length); + + const now = Date.now(); + + for (let i = 0; i < this.nodes.length; i++) { + const node = this.nodes[i]; + positions[i * 2] = node.x; + positions[i * 2 + 1] = node.y; + + // Maximales Pulsieren und sehr große Knoten + let pulse = Math.sin(node.pulsePhase) * 0.38 + 1.35; + const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 1.1; + sizes[i] = node.size * pulse * connectivityFactor; + } + + // Bind position buffer + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer( + this.programInfo.attribLocations.vertexPosition, + 2, // components per vertex + this.gl.FLOAT, // data type + false, // normalize + 0, // stride + 0 // offset + ); + this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); + + // Bind size buffer + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, sizes, this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer( + this.programInfo.attribLocations.pointSize, + 1, // components per vertex + this.gl.FLOAT, // data type + false, // normalize + 0, // stride + 0 // offset + ); + this.gl.enableVertexAttribArray(this.programInfo.attribLocations.pointSize); + + // Enable blending for all nodes + this.gl.enable(this.gl.BLEND); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); // Additive blending for glow + + // Draw each node individually with its own color + for (let i = 0; i < this.nodes.length; i++) { + const node = this.nodes[i]; + + // Set node color - more visible with active highlighting + const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; + const nodeColor = this.hexToRgb(colorObj.nodeColor); + const nodePulseColor = this.hexToRgb(colorObj.nodePulse); + + // Use pulse color for active nodes + let r = nodeColor.r / 255; + let g = nodeColor.g / 255; + let b = nodeColor.b / 255; + + // Active nodes get brighter color + if (node.isActive) { + r = (r + nodePulseColor.r / 255) / 2; + g = (g + nodePulseColor.g / 255) / 2; + b = (b + nodePulseColor.b / 255) / 2; + } + + // Sehr sichtbare, maximal leuchtende Knoten + this.gl.uniform4f( + this.programInfo.uniformLocations.color, + r, g, b, + node.isActive ? 1.0 : 0.92 // Maximal sichtbar + ); + + // Draw each node individually for better control + this.gl.drawArrays(this.gl.POINTS, i, 1); + } + } + + renderConnectionsWebGL() { + // Permanente Verbindungsstriche werden nicht mehr gezeichnet + // Diese Methode bleibt leer, damit keine Linien mehr erscheinen + } + + // New method to render the flowing animations + renderFlowsWebGL() { + // Für jeden Flow zuerst die unterliegende Strichlinie zeichnen, dann den Blitz + for (const flow of this.flows) { + const connection = flow.connection; + const fromNode = this.nodes[connection.from]; + const toNode = this.nodes[connection.to]; + const direction = flow.direction ? 1 : -1; + + // 1. Die unterliegende Strichlinie zeichnen + if (this.config.lineUnderFlow) { + const lineStart = direction > 0 ? fromNode : toNode; + const lineEnd = direction > 0 ? toNode : fromNode; + + // Strichlinie als gerade Linie mit voller Länge, aber mit Fortschritt + const lineEndPoint = { + x: lineStart.x + (lineEnd.x - lineStart.x) * flow.lineProgress, + y: lineStart.y + (lineEnd.y - lineStart.y) * flow.lineProgress + }; + + const positions = new Float32Array([ + lineStart.x, lineStart.y, + lineEndPoint.x, lineEndPoint.y + ]); + + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer( + this.programInfo.attribLocations.vertexPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); + this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize); + + // Sanftere, transparentere Strichlinie + const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; + const lineColor = this.hexToRgb(colorObj.lineUnderFlowColor || colorObj.connectionColor); + + this.gl.uniform4f( + this.programInfo.uniformLocations.color, + lineColor.r / 255, + lineColor.g / 255, + lineColor.b / 255, + this.config.lineUnderFlowOpacity // Sehr subtile Linie + ); + + this.gl.enable(this.gl.BLEND); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); // Normales Alpha-Blending + this.gl.lineWidth(this.config.lineUnderFlowWidth); // Dünne Linie + this.gl.drawArrays(this.gl.LINES, 0, 2); + } + + // 2. Den Blitz selbst zeichnen + const startProgress = flow.progress; + const endProgress = Math.min(1, startProgress + flow.length); + if (startProgress >= 1 || endProgress <= 0) continue; + + let p1, p2; + if (direction > 0) { + p1 = { + x: fromNode.x + (toNode.x - fromNode.x) * startProgress, + y: fromNode.y + (toNode.y - fromNode.y) * startProgress + }; + p2 = { + x: fromNode.x + (toNode.x - fromNode.x) * endProgress, + y: fromNode.y + (toNode.y - fromNode.y) * endProgress + }; + } else { + p1 = { + x: toNode.x + (fromNode.x - toNode.x) * startProgress, + y: toNode.y + (fromNode.y - toNode.y) * startProgress + }; + p2 = { + x: toNode.x + (fromNode.x - toNode.x) * endProgress, + y: toNode.y + (fromNode.y - toNode.y) * endProgress + }; + } + + // Zickzack-Blitz generieren (weniger ausgeprägt) + const zigzag = this.generateZigZagPoints(p1, p2, 3, 5); // Weniger Segmente, kleinere Amplitude + for (let i = 0; i < zigzag.length - 1; i++) { + const positions = new Float32Array([ + zigzag[i].x, zigzag[i].y, + zigzag[i + 1].x, zigzag[i + 1].y + ]); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW); + this.gl.vertexAttribPointer( + this.programInfo.attribLocations.vertexPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); + this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize); + + // Dezenter, subtilerer Blitz + const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; + const flowColor = this.hexToRgb(colorObj.flowColor); + this.gl.uniform4f( + this.programInfo.uniformLocations.color, + flowColor.r / 255, + flowColor.g / 255, + flowColor.b / 255, + 0.45 // Geringere Sichtbarkeit + ); + this.gl.enable(this.gl.BLEND); + this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); + this.gl.lineWidth(1.0); // Dünnerer Blitz für subtileren Effekt + this.gl.drawArrays(this.gl.LINES, 0, 2); + } + + // Funken erzeugen - weniger und subtilere elektrische Funken + const sparks = this.generateSparkPoints(zigzag, 2 + Math.floor(Math.random() * 2)); // Weniger Funken + for (const spark of sparks) { + // Sanfteres Weiß-Blau für subtilere elektrische Funken + this.gl.uniform4f( + this.programInfo.uniformLocations.color, + 0.85, 0.9, 0.95, 0.4 * spark.intensity // Geringere Intensität + ); + + // Position für den Funken setzen + const sparkPos = new Float32Array([spark.x, spark.y]); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, sparkPos, this.gl.STATIC_DRAW); + + // Punktgröße setzen + const sizes = new Float32Array([spark.size * 2.0]); // Kleinere Funken + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, sizes, this.gl.STATIC_DRAW); + + this.gl.vertexAttribPointer( + this.programInfo.attribLocations.vertexPosition, + 2, + this.gl.FLOAT, + false, + 0, + 0 + ); + this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); + + this.gl.vertexAttribPointer( + this.programInfo.attribLocations.pointSize, + 1, + this.gl.FLOAT, + false, + 0, + 0 + ); + this.gl.enableVertexAttribArray(this.programInfo.attribLocations.pointSize); + + this.gl.drawArrays(this.gl.POINTS, 0, 1); + } + } + } + + renderCanvas() { + if (!this.ctx) return; + + const { background, nodeColor, connectionColor } = this.isDarkMode ? this.darkModeColors : this.lightModeColors; + + // Hintergrund sanft löschen mit leichter Transparenz für Nachleuchten + this.ctx.fillStyle = background; + this.ctx.globalAlpha = 0.22; // Sehr subtile Überblendung für sanfte Bewegung + this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); + this.ctx.globalAlpha = 1.0; + + // Verbindungen zeichnen mit distanzabhängiger Transparenz + this.connections.forEach(connection => { + const { source, target, strength } = connection; + const dx = target.x - source.x; + const dy = target.y - source.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // Berechne Opazität basierend auf Distanz - je weiter entfernt, desto transparenter + const maxOpacity = 0.02; // Sehr subtile maximale Opazität + const opacityFactor = 1 - (distance / this.config.connectionDistance); + let opacity = maxOpacity * opacityFactor * strength * this.config.connectionOpacity; + + // Subtilere Linien + this.ctx.beginPath(); + this.ctx.moveTo(source.x, source.y); + this.ctx.lineTo(target.x, target.y); + this.ctx.lineWidth = 0.4; // Sehr dünne Linie + this.ctx.strokeStyle = connectionColor; + this.ctx.globalAlpha = opacity; + this.ctx.stroke(); + this.ctx.globalAlpha = 1.0; + }); + + // Knoten zeichnen mit subtilerem Glühen und Pulsieren + this.nodes.forEach(node => { + const nodeBaseSize = node.size * this.config.nodeSize; + const pulse = Math.sin(this.time * this.config.pulseSpeed * node.pulseRate) * 0.1 + 1; // Reduzierte Pulsamplitude + const glowSize = nodeBaseSize * 1.5; // Kleinerer Glowradius + + // Aktive Knoten speziell hervorheben, aber subtiler + if (node.active) { + // Subtiles Glühen für aktive Knoten + const gradient = this.ctx.createRadialGradient( + node.x, node.y, 0, + node.x, node.y, glowSize * 2 + ); + this.ctx.globalAlpha = 0.09; // Sehr niedrige Opazität für subtiles Glühen + gradient.addColorStop(0, 'rgba(255, 255, 255, 0.15)'); + gradient.addColorStop(1, 'rgba(255, 255, 255, 0)'); + + this.ctx.fillStyle = gradient; + this.ctx.beginPath(); + this.ctx.arc(node.x, node.y, glowSize * 2, 0, Math.PI * 2); + this.ctx.fill(); + + // Zeichne aktiven Knoten + this.ctx.globalAlpha = 0.9; + this.ctx.fillStyle = nodeColor; + this.ctx.beginPath(); + this.ctx.arc(node.x, node.y, nodeBaseSize * pulse, 0, Math.PI * 2); + this.ctx.fill(); + } else { + // Inaktive Knoten sanfter darstellen + this.ctx.globalAlpha = 0.8; + this.ctx.fillStyle = nodeColor; + this.ctx.beginPath(); + this.ctx.arc(node.x, node.y, nodeBaseSize * pulse * 0.8, 0, Math.PI * 2); + this.ctx.fill(); + } + this.ctx.globalAlpha = 1.0; + }); + } + + renderFlowsCanvas() { + if (!this.ctx) return; + + const { flowColor } = this.isDarkMode ? this.darkModeColors : this.lightModeColors; + + this.flows.forEach(flow => { + if (flow.progress > 1) return; + + const { source, target, progress } = flow; + + // Berechne aktuelle Position + const dx = target.x - source.x; + const dy = target.y - source.y; + const currentX = source.x + dx * progress; + const currentY = source.y + dy * progress; + + // Zick-Zack-Effekt reduzieren für sanftere Bewegung + const segmentCount = 4; // Weniger Segmente + const amplitude = 2; // Geringere Amplitude + const waveLength = 0.2; + + let prevX = source.x; + let prevY = source.y; + + this.ctx.beginPath(); + this.ctx.moveTo(prevX, prevY); + + // Sanftere "Elektrizitäts"-Linie + for (let i = 0; i <= segmentCount; i++) { + const segmentProgress = i / segmentCount; + if (segmentProgress > progress) break; + + const segX = source.x + dx * segmentProgress; + const segY = source.y + dy * segmentProgress; + + // Sanftere Wellen + const perpX = -dy * Math.sin(segmentProgress * Math.PI * 10) * amplitude; + const perpY = dx * Math.sin(segmentProgress * Math.PI * 10) * amplitude; + + const pointX = segX + perpX * Math.sin(this.time * 0.01); + const pointY = segY + perpY * Math.sin(this.time * 0.01); + + this.ctx.lineTo(pointX, pointY); + prevX = pointX; + prevY = pointY; + } + + // Linienausrichtung und -stil + this.ctx.lineCap = 'round'; + this.ctx.lineWidth = 1.2; // Dünnere Linie + + // Subtiler Gloweffekt + this.ctx.shadowColor = flowColor; + this.ctx.shadowBlur = 6; // Reduzierter Unschärferadius + + this.ctx.strokeStyle = flowColor; + this.ctx.globalAlpha = 0.7 - 0.5 * Math.abs(progress - 0.5); // Sanfteres Ein- und Ausblenden + this.ctx.stroke(); + this.ctx.globalAlpha = 1; + this.ctx.shadowBlur = 0; + + // Funkeneffekte am Ende der Linie, aber weniger und kleiner + if (progress > 0.9 && Math.random() < 0.3) { // Weniger Funken generieren + const sparkCount = Math.floor(Math.random() * 2) + 1; // Maximal 3 Funken + + for (let i = 0; i < sparkCount; i++) { + const sparkSize = Math.random() * 0.8 + 0.4; // Kleinere Funken + const sparkAngle = Math.random() * Math.PI * 2; + const sparkDistance = Math.random() * 5 + 2; + + const sparkX = currentX + Math.cos(sparkAngle) * sparkDistance; + const sparkY = currentY + Math.sin(sparkAngle) * sparkDistance; + + // Weniger intensive Funken + this.ctx.beginPath(); + this.ctx.arc(sparkX, sparkY, sparkSize, 0, Math.PI * 2); + this.ctx.fillStyle = flowColor; + this.ctx.globalAlpha = 0.4; // Reduzierte Opazität + this.ctx.fill(); + this.ctx.globalAlpha = 1; + } + } + }); + } + + // Helper method to convert hex to RGB + hexToRgb(hex) { + // Remove # if present + hex = hex.replace(/^#/, ''); + + // Handle rgba hex format + let alpha = 1; + if (hex.length === 8) { + alpha = parseInt(hex.slice(6, 8), 16) / 255; + hex = hex.slice(0, 6); + } + + // Parse hex values + const bigint = parseInt(hex, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + + return { r, g, b, a: alpha }; + } + + // Cleanup method + destroy() { + if (this.animationFrameId) { + cancelAnimationFrame(this.animationFrameId); + } + + window.removeEventListener('resize', this.resizeCanvas.bind(this)); + + if (this.canvas && this.canvas.parentNode) { + this.canvas.parentNode.removeChild(this.canvas); + } + + if (this.gl) { + // Clean up WebGL resources + this.gl.deleteBuffer(this.positionBuffer); + this.gl.deleteBuffer(this.sizeBuffer); + this.gl.deleteProgram(this.shaderProgram); + } + } + + // Hilfsfunktion: Erzeuge Zickzack-Punkte für einen Blitz + generateZigZagPoints(start, end, segments = 5, amplitude = 8) { + const points = [start]; + for (let i = 1; i < segments; i++) { + const t = i / segments; + const x = start.x + (end.x - start.x) * t; + const y = start.y + (end.y - start.y) * t; + // Versatz für Zickzack + const angle = Math.atan2(end.y - start.y, end.x - start.x) + Math.PI / 2; + const offset = (Math.random() - 0.5) * amplitude * (i % 2 === 0 ? 1 : -1); + points.push({ + x: x + Math.cos(angle) * offset, + y: y + Math.sin(angle) * offset + }); + } + points.push(end); + return points; + } + + // Hilfsfunktion: Erzeuge Funkenpunkte entlang eines Zickzack-Blitzes + generateSparkPoints(zigzag, sparkCount = 4) { + const sparks = []; + // Moderatere Anzahl an Funken für subtileren Effekt + const actualSparkCount = sparkCount + Math.floor(Math.random() * 2); + + for (let i = 0; i < actualSparkCount; i++) { + // Entlang des gesamten Blitzes verteilen + const seg = i * (zigzag.length - 1) / actualSparkCount; + const segIndex = Math.floor(seg); + const t = seg - segIndex; + + // Basis-Position + const x = zigzag[segIndex].x + (zigzag[segIndex + 1].x - zigzag[segIndex].x) * t; + const y = zigzag[segIndex].y + (zigzag[segIndex + 1].y - zigzag[segIndex].y) * t; + + // Sanfterer Versatz vom Blitz + const angle = Math.random() * Math.PI * 2; + const distance = 2 + Math.random() * 4; // Reduzierter Abstand für subtileres Sprühen + + sparks.push({ + x: x + Math.cos(angle) * distance, + y: y + Math.sin(angle) * distance, + size: 0.5 + Math.random() * 1 // Kleinere Funken + }); + } + return sparks; + } + + onResize() { + // Verhindere mehrfache Resize-Events in kurzer Zeit + if (this.resizeTimeout) clearTimeout(this.resizeTimeout); + + this.resizeTimeout = setTimeout(() => { + this.resizeCanvas(); + + // Knoten und Verbindungen neu erstellen, um sie an die neue Größe anzupassen + this.createNodes(); + this.createConnections(); + }, 200); // Warte 200ms, bevor die Größenanpassung durchgeführt wird + } + + setDarkMode(isDarkMode) { + this.isDarkMode = isDarkMode; + + // WebGL-Hintergrundfarbe aktualisieren + if (this.useWebGL && this.gl) { + const bgColor = this.hexToRgb( + this.isDarkMode ? this.darkModeColors.background : this.lightModeColors.background + ); + this.gl.clearColor(bgColor.r/255, bgColor.g/255, bgColor.b/255, 1.0); + } + } +} + +// Initialize when DOM is loaded +document.addEventListener('DOMContentLoaded', () => { + // Short delay to ensure DOM is fully loaded + setTimeout(() => { + if (!window.neuralNetworkBackground) { + console.log('Creating Neural Network Background'); + window.neuralNetworkBackground = new NeuralNetworkBackground(); + } + }, 100); +}); + +// Re-initialize when page is fully loaded (for safety) +window.addEventListener('load', () => { + if (!window.neuralNetworkBackground) { + console.log('Re-initializing Neural Network Background on full load'); + window.neuralNetworkBackground = new NeuralNetworkBackground(); + } +}); + +// Clean up when window is closed +window.addEventListener('beforeunload', () => { + if (window.neuralNetworkBackground) { + window.neuralNetworkBackground.destroy(); + } +}); \ No newline at end of file diff --git a/static/neural-network-background.js b/static/neural-network-background.js index 3cb5eb3..06afebd 100644 --- a/static/neural-network-background.js +++ b/static/neural-network-background.js @@ -49,37 +49,41 @@ class NeuralNetworkBackground { this.animationFrameId = null; this.isDarkMode = true; // Always use dark mode for the background - // Colors - Updated for intense visibility and neural effect + // Colors - Subtilere Farben mit weniger Intensität this.darkModeColors = { - background: '#030610', // Noch dunklerer Hintergrund für besseren Kontrast - nodeColor: '#88a5ff', // Hellere, leuchtende Knoten - nodePulse: '#c0d5ff', // Strahlend helles Pulsieren - connectionColor: '#5a6ca8', // Hellere, sichtbarere Verbindungen - flowColor: '#90c8ffee' // Sehr leuchtende, fast undurchsichtige Flüsse + background: '#030610', // Dunkler Hintergrund beibehalten + nodeColor: '#6685cc', // Gedämpftere Knotenfarbe + nodePulse: '#a0b5e0', // Weniger intensives Pulsieren + connectionColor: '#485880', // Subtilere Verbindungen + flowColor: '#c0d7f0' // Sanfteres Blitz-Blau }; + // Farben für Light Mode dezenter und harmonischer gestalten this.lightModeColors = { - background: '#f9fafb', - nodeColor: '#8c4aff', - nodePulse: '#ab7cff', - connectionColor: '#b798ff', - flowColor: '#d4c5ff' + background: '#f5f7fa', // Hellerer Hintergrund für subtileren Kontrast + nodeColor: '#5a77c2', // Gedämpfteres Blau + nodePulse: '#80b9e0', // Sanfteres Türkis für Glow + connectionColor: '#8a8fc0', // Dezenteres Lila + flowColor: '#6d97d0' // Sanfteres Blau für Blitze }; - // Config - Drastisch verstärkt für strahlende Animationen und neuronale Vernetzung + // Konfigurationsobjekt für subtilere, sanftere Neuronen this.config = { - nodeCount: 120, // Anzahl der Knoten bleibt hoch für Netzwerkstruktur - nodeSize: 1.1, // Dezenter: kleinere Knoten - nodeVariation: 0.4, // Weniger Variation für ruhigeres Bild - connectionDistance: 220, // Unverändert: gute Vernetzung - connectionOpacity: 0.18, // Deutlich dezentere Verbindungen - animationSpeed: 0.05, // Ruhigere Bewegung - pulseSpeed: 0.004, // Ruhigeres Pulsieren - flowSpeed: 1.2, // Flows schneller für flüssigere Aktivität - flowDensity: 0.012, // Mehr Flows für sichtbare Aktivität - flowLength: 0.32, // Flows länger sichtbar - maxConnections: 5, // Weniger Überlagerung - clusteringFactor: 0.35 // Mehr Cluster für neuronalen Effekt + nodeCount: 60, // Weniger Knoten für bessere Leistung und subtileres Aussehen + nodeSize: 2.8, // Kleinere Knoten für dezenteres Erscheinungsbild + nodeVariation: 0.6, // Weniger Varianz für gleichmäßigeres Erscheinungsbild + connectionDistance: 220, // Etwas geringere Verbindungsdistanz + connectionOpacity: 0.15, // Transparentere Verbindungen + animationSpeed: 0.02, // Langsamere Animation für sanftere Bewegung + pulseSpeed: 0.002, // Langsameres Pulsieren für subtilere Animation + flowSpeed: 0.6, // Langsamer für sanftere Animation + flowDensity: 0.002, // Deutlich weniger Blitze für subtileres Erscheinungsbild + flowLength: 0.12, // Kürzere Blitze für dezentere Effekte + maxConnections: 3, // Weniger Verbindungen für aufgeräumteres Erscheinungsbild + clusteringFactor: 0.4, // Moderate Clustering-Stärke + linesFadeDuration: 3500, // Längere Dauer für sanfteres Ein-/Ausblenden von Linien (ms) + linesWidth: 0.6, // Dünnere unterliegende Linien + linesOpacity: 0.25 // Geringere Opazität für Linien }; // Initialize @@ -335,7 +339,7 @@ class NeuralNetworkBackground { if (!this.connections.some(conn => (conn.from === i && conn.to === j) || (conn.from === j && conn.to === i) )) { - // Neue Verbindung startet mit progress=0 für animierten Aufbau + // Neue Verbindung mit Ein-/Ausblend-Status this.connections.push({ from: i, to: j, @@ -344,7 +348,13 @@ class NeuralNetworkBackground { strength: connectionStrength, hasFlow: false, lastActivated: 0, - progress: 0 // Animationsfortschritt für Verbindungsaufbau + 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 + Math.random() * 1000, // Zufällige Dauer + visibleDuration: 10000 + Math.random() * 15000, // Wie lange die Linie sichtbar bleibt + 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); @@ -363,14 +373,32 @@ class NeuralNetworkBackground { const height = this.canvas.height / (window.devicePixelRatio || 1); const now = Date.now(); - // Simulate neural firing + // Simulate neural firing with reduced activity for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; - // Check if node should fire based on its firing rate - if (now - node.lastFired > node.firingRate) { + // Update pulse phase for smoother animation + node.pulsePhase += this.config.pulseSpeed * (1 + (node.connections.length * 0.04)); + + // 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); + + // 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)); + } + + // Check if node should fire based on reduced firing rate + if (now - node.lastFired > node.firingRate * 1.3) { // 30% langsamere Feuerrate node.isActive = true; node.lastFired = now; + node.activationTime = now; // Track when activation started // Activate connected nodes with probability based on connection strength for (const connIndex of node.connections) { @@ -383,84 +411,165 @@ class NeuralNetworkBackground { // Mark connection as recently activated conn.lastActivated = now; - // Create a flow along this connection - if (Math.random() < conn.strength * 0.8) { + // Wenn eine Verbindung aktiviert wird, verlängere ggf. ihre Sichtbarkeit + if (conn.fadeState === 'out') { + conn.fadeState = 'visible'; + conn.fadeStartTime = now; + } + + // Verbindung soll schneller aufgebaut werden, wenn ein Neuron feuert + if (conn.progress < 1) { + conn.buildSpeed = 0.015 + Math.random() * 0.01; // Schnellerer Aufbau während der Aktivierung + } + + // Reduzierte Wahrscheinlichkeit für neue Flows + if (this.flows.length < 4 && Math.random() < conn.strength * 0.5) { // Reduzierte Wahrscheinlichkeit this.flows.push({ connection: conn, progress: 0, direction: conn.from === i, // Flow from activated node - length: this.config.flowLength + Math.random() * 0.1, - intensity: 0.7 + Math.random() * 0.3 // Random intensity for variation + length: this.config.flowLength + Math.random() * 0.05, // Geringere Variation + intensity: 0.5 + Math.random() * 0.3, // Geringere Intensität für subtilere Darstellung + creationTime: now, + totalDuration: 1000 + Math.random() * 600 // Längere Dauer für sanftere Animation }); } // Probability for connected node to activate if (Math.random() < conn.strength * 0.5) { this.nodes[connIndex].isActive = true; + this.nodes[connIndex].activationTime = now; this.nodes[connIndex].lastFired = now - Math.random() * 500; // Slight variation } } } - } else if (now - node.lastFired > 300) { // Deactivate after short period + } else if (now - node.lastFired > 400) { // Deactivate after longer period node.isActive = false; } } - // Animierter Verbindungsaufbau: progress inkrementieren + // Aktualisiere die Ein-/Ausblendung von Verbindungen 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); + 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) { + connection.fadeState = 'out'; + connection.fadeStartTime = now; + connection.fadeProgress = 1.0; + + // Setze den Fortschritt zurück, damit die Verbindung neu aufgebaut werden kann + if (Math.random() < 0.7) { + connection.progress = 0; + } + } + } else if (connection.fadeState === 'out') { + // Ausblenden + connection.fadeProgress = Math.max(0.0, 1.0 - (elapsedTime / connection.fadeTotalDuration)); + if (connection.fadeProgress <= 0.0) { + // Setze Verbindung zurück, damit sie wieder eingeblendet werden kann + if (Math.random() < 0.4) { // 40% Chance, direkt wieder einzublenden + connection.fadeState = 'in'; + connection.fadeStartTime = now; + connection.fadeProgress = 0.0; + connection.visibleDuration = 10000 + Math.random() * 15000; // Neue Dauer generieren + + // Setze den Fortschritt zurück, damit die Verbindung neu aufgebaut werden kann + connection.progress = 0; + } else { + // Kurze Pause, bevor die Verbindung wieder erscheint + connection.fadeState = 'hidden'; + connection.fadeStartTime = now; + connection.hiddenDuration = 3000 + Math.random() * 7000; + + // Setze den Fortschritt zurück, damit die Verbindung neu aufgebaut werden kann + connection.progress = 0; + } + } + } else if (connection.fadeState === 'hidden') { + // Verbindung ist unsichtbar, warte auf Wiedereinblendung + if (elapsedTime > connection.hiddenDuration) { + connection.fadeState = 'in'; + connection.fadeStartTime = now; + connection.fadeProgress = 0.0; + + // Verbindung wird komplett neu aufgebaut + connection.progress = 0; + } + } + + // Animierter Verbindungsaufbau: progress inkrementieren, aber nur wenn aktiv if (connection.progress < 1) { - // Langsamer Aufbau: Geschwindigkeit kann angepasst werden - connection.progress += 0.012; // Sehr langsam, für subtilen Effekt - if (connection.progress > 1) connection.progress = 1; + // Verbindung wird nur aufgebaut, wenn sie gerade aktiv ist oder ein Blitz sie aufbaut + const buildingSpeed = connection.buildSpeed || 0.002; // Langsamer Standard-Aufbau + + // Bau die Verbindung auf, wenn sie kürzlich aktiviert wurde + if (now - connection.lastActivated < 2000) { + connection.progress += buildingSpeed; + if (connection.progress > 1) connection.progress = 1; + } + + // Zurücksetzen der Aufbaugeschwindigkeit + connection.buildSpeed = 0; } } - // Update flows - this.updateFlows(); + // Update flows with proper fading + this.updateFlows(now); - // Occasionally create new flows along connections - if (Math.random() < this.config.flowDensity) { - this.createNewFlow(); + // Seltener neue Flows erstellen + if (Math.random() < this.config.flowDensity * 0.8 && this.flows.length < 4) { // Reduzierte Kapazität und Rate + this.createNewFlow(now); } // Recalculate connections occasionally for a living network - if (Math.random() < 0.01) { // Only recalculate 1% of the time for performance + if (Math.random() < 0.005) { // Only recalculate 0.5% of the time for performance this.createConnections(); } // Render if (this.useWebGL) { - this.renderWebGL(); + this.renderWebGL(now); } else { - this.renderCanvas(); + this.renderCanvas(now); } // Continue animation this.animationFrameId = requestAnimationFrame(this.animate.bind(this)); } - // New method to update flow animations - updateFlows() { + // 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; - // Remove completed flows - if (flow.progress > 1.0) { + // Remove completed or expired flows + if (flow.progress > 1.0 || flowProgress >= 1.0) { this.flows.splice(i, 1); } } - - // Generate more flows for enhanced visibility - if (Math.random() < this.config.flowDensity * 2) { - this.createNewFlow(); - } } - // New method to create flow animations - createNewFlow() { - if (this.connections.length === 0) return; + // Updated method to create flow animations with timing info + createNewFlow(now) { + if (this.connections.length === 0 || this.flows.length >= 6) return; // Select a random connection with preference for more connected nodes let connectionIdx = Math.floor(Math.random() * this.connections.length); @@ -481,16 +590,23 @@ class NeuralNetworkBackground { 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 + } + // 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 + length: this.config.flowLength + Math.random() * 0.1, // Slightly vary lengths + creationTime: now, + totalDuration: 800 + Math.random() * 500 // Zufällige Gesamtdauer (800-1300ms) }); } - renderWebGL() { + renderWebGL(now) { this.gl.clear(this.gl.COLOR_BUFFER_BIT); const width = this.canvas.width / (window.devicePixelRatio || 1); @@ -503,30 +619,31 @@ class NeuralNetworkBackground { this.gl.uniform2f(this.programInfo.uniformLocations.resolution, width, height); // Draw connections first (behind nodes) - this.renderConnectionsWebGL(); + this.renderConnectionsWebGL(now); // Draw flows on top of connections - this.renderFlowsWebGL(); + this.renderFlowsWebGL(now); // Draw nodes - this.renderNodesWebGL(); + this.renderNodesWebGL(now); } - renderNodesWebGL() { + renderNodesWebGL(now) { // Prepare node positions for WebGL const positions = new Float32Array(this.nodes.length * 2); const sizes = new Float32Array(this.nodes.length); - const now = Date.now(); - for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; positions[i * 2] = node.x; positions[i * 2 + 1] = node.y; - // Sichtbarkeit der Neuronen erhöhen - let pulse = Math.sin(node.pulsePhase) * 0.22 + 1.08; - const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 0.8; + // Sanftere Pulsation mit moderaterem Aktivierungsboost + const activationBoost = node.isActive ? 1.4 : 1.0; + let pulse = (Math.sin(node.pulsePhase) * 0.35 + 1.3) * activationBoost; + + // Größe basierend auf Konnektivität und Wichtigkeit, aber subtiler + const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 1.1; sizes[i] = node.size * pulse * connectivityFactor; } @@ -564,7 +681,7 @@ class NeuralNetworkBackground { for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; - // Set node color - more visible with active highlighting + // 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); @@ -574,18 +691,18 @@ class NeuralNetworkBackground { let g = nodeColor.g / 255; let b = nodeColor.b / 255; - // Active nodes get brighter color + // Active nodes get slightly brighter color - subtiler if (node.isActive) { - r = (r + nodePulseColor.r / 255) / 2; - g = (g + nodePulseColor.g / 255) / 2; - b = (b + nodePulseColor.b / 255) / 2; + 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); } - // Sehr sichtbare Knoten + // Subtilere Knoten mit reduzierter Opazität this.gl.uniform4f( this.programInfo.uniformLocations.color, r, g, b, - node.isActive ? 0.98 : 0.8 // Sehr sichtbar + node.isActive ? 0.85 : 0.75 // Geringere Sichtbarkeit für subtileres Erscheinungsbild ); // Draw each node individually for better control @@ -593,21 +710,33 @@ class NeuralNetworkBackground { } } - renderConnectionsWebGL() { - const now = Date.now(); + renderConnectionsWebGL(now) { 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]; - // Animierter Verbindungsaufbau: nur Teil der Linie zeichnen const progress = connection.progress || 1; const x1 = fromNode.x; const y1 = fromNode.y; - const x2 = x1 + (toNode.x - x1) * progress; - const y2 = y1 + (toNode.y - y1) * progress; + 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; + + // 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); + } + 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( @@ -620,47 +749,37 @@ class NeuralNetworkBackground { ); 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; - let lineColor = this.hexToRgb(colorObj.connectionColor); - // Sehr dezente Grundopazität - let opacity = connection.opacity * 0.7; - if (now - connection.lastActivated < 800) { - lineColor = this.hexToRgb(colorObj.flowColor); - const timeFactor = 1 - ((now - connection.lastActivated) / 800); - opacity = Math.max(opacity, timeFactor * 0.32); - } + const connColor = this.hexToRgb(colorObj.connectionColor); this.gl.uniform4f( this.programInfo.uniformLocations.color, - lineColor.r / 255, - lineColor.g / 255, - lineColor.b / 255, + connColor.r / 255, + connColor.g / 255, + connColor.b / 255, opacity ); - // Sehr dünne Linien - this.gl.lineWidth(0.5); + + 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 - renderFlowsWebGL() { - // For each flow, draw a segment along its connection + renderFlowsWebGL(now) { + // 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]; - - // Calculate flow position const startProgress = flow.progress; const endProgress = Math.min(1, startProgress + flow.length); - - // If flow hasn't started yet or has finished if (startProgress >= 1 || endProgress <= 0) continue; - - // Calculate actual positions const direction = flow.direction ? 1 : -1; let p1, p2; - if (direction > 0) { p1 = { x: fromNode.x + (toNode.x - fromNode.x) * startProgress, @@ -680,57 +799,85 @@ class NeuralNetworkBackground { y: toNode.y + (fromNode.y - toNode.y) * endProgress }; } - - // Line positions for the flow - const positions = new Float32Array([ - p1.x, p1.y, - p2.x, p2.y - ]); - - // 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); - - // Disable point size attribute for lines - this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize); - - // Fade the flow at the beginning and end - const fadeEdge = 0.2; - const fadeOpacity = Math.min( - startProgress / fadeEdge, - (1 - endProgress) / fadeEdge, - 1 - ); - - // Flow color - much stronger glow - const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors; - const flowColor = this.hexToRgb(colorObj.flowColor); - - // Flows mit sanftem, aber sichtbarem Glow und höherer Opazität - this.gl.uniform4f( - this.programInfo.uniformLocations.color, - flowColor.r / 255, - flowColor.g / 255, - flowColor.b / 255, - 0.55 * fadeOpacity * (flow.intensity || 1) // Dezenter, aber sichtbar - ); - - // Dünnere Flows für subtilen Effekt - this.gl.lineWidth(1.2); - this.gl.drawArrays(this.gl.LINES, 0, 2); + // Zickzack-Blitz generieren + const zigzag = this.generateZigZagPoints(p1, p2, 8, 10); + 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); + this.gl.uniform4f( + this.programInfo.uniformLocations.color, + flowColor.r / 255, + flowColor.g / 255, + flowColor.b / 255, + 0.55 // Dezent, aber sichtbar + ); + 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.drawArrays(this.gl.LINES, 0, 2); + } + // Funken erzeugen - echte elektrische Funken + const sparks = this.generateSparkPoints(zigzag, 7 + Math.floor(Math.random() * 3)); + for (const spark of sparks) { + // Helles Weiß-Blau für elektrische Funken + this.gl.uniform4f( + this.programInfo.uniformLocations.color, + 0.88, 0.95, 1.0, 0.65 // Elektrisches Blau-Weiß + ); + + // Position für den Funken setzen + const sparkPos = new Float32Array([spark.x, spark.y]); + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer); + this.gl.bufferData(this.gl.ARRAY_BUFFER, sparkPos, this.gl.STATIC_DRAW); + + // Punktgröße setzen + const sizes = new Float32Array([spark.size * 3.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); + } } } - renderCanvas() { + renderCanvas(now) { // Clear canvas const width = this.canvas.width / (window.devicePixelRatio || 1); const height = this.canvas.height / (window.devicePixelRatio || 1); @@ -745,34 +892,73 @@ class NeuralNetworkBackground { this.ctx.fillStyle = backgroundColor; this.ctx.fillRect(0, 0, width, height); - // Draw connections + // 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]; - // Animierter Verbindungsaufbau: nur Teil der Linie zeichnen - const progress = connection.progress || 1; + + // 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); - // Sehr dezente Opazität - this.ctx.strokeStyle = `rgba(${rgbColor.r}, ${rgbColor.g}, ${rgbColor.b}, ${connection.opacity * 0.7})`; - this.ctx.lineWidth = 0.5; + + // 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); + } + + 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.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 - this.renderFlowsCanvas(); + // Draw flows with fading effect + this.renderFlowsCanvas(now); - // Draw nodes + // Draw nodes with enhanced animations const nodeColor = this.isDarkMode ? this.darkModeColors.nodeColor : this.lightModeColors.nodeColor; @@ -782,86 +968,217 @@ class NeuralNetworkBackground { : this.lightModeColors.nodePulse; for (const node of this.nodes) { - // Sichtbarkeit der Neuronen erhöhen - const pulse = Math.sin(node.pulsePhase) * 0.18 + 1.08; // Leicht erhöhte Amplitude - const nodeSize = node.size * pulse * (node.connections.length > 3 ? 1.22 : 1); + // 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; + const nodeSize = node.size * pulse * connectivityFactor; + + // 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 * 2.2 + node.x, node.y, nodeSize * 4.5 ); - glow.addColorStop(0, `rgba(${nodePulse.r}, ${nodePulse.g}, ${nodePulse.b}, 0.52)`); - glow.addColorStop(0.5, `rgba(${nodeColor.r}, ${nodeColor.g}, ${nodeColor.b}, 0.22)`); - glow.addColorStop(1, `rgba(${nodeColor.r}, ${nodeColor.g}, ${nodeColor.b}, 0)`); + + // 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)`); + 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 ? 0.95 : 0.7; // Sehr sichtbar + this.ctx.globalAlpha = node.isActive ? 1.0 : 0.92; 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.fill(); + } + this.ctx.globalAlpha = 1.0; } } - // New method to render flows in Canvas mode - renderFlowsCanvas() { - if (!this.ctx) return; - - const flowColor = this.isDarkMode - ? this.darkModeColors.flowColor - : this.lightModeColors.flowColor; - - const rgbFlowColor = this.hexToRgb(flowColor); + // New method to render flows in Canvas mode with fading + renderFlowsCanvas(now) { + 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]; - // Calculate flow position - const startProgress = flow.progress; - const endProgress = Math.min(1, startProgress + flow.length); + // Berechne den Fortschritt der Connection und des Flows + const connProgress = connection.progress || 1; - // If flow hasn't started yet or has finished - if (startProgress >= 1 || endProgress <= 0) continue; + // Flussrichtung bestimmen + const [startNode, endNode] = flow.direction ? [fromNode, toNode] : [toNode, fromNode]; - // Calculate actual positions - 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) * startProgress - }; - } 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 - }; - } - - // Fade the flow at the beginning and end - const fadeEdge = 0.2; - const fadeOpacity = Math.min( - startProgress / fadeEdge, - (1 - endProgress) / fadeEdge, - 1 + // 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) ); - // Dezente Flows mit sanftem Fade-Out + // 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)); + } + + // 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 + }; + + // Prüfe, ob der Fluss den aktuellen Verbindungsfortschritt überschritten hat + if (endProgress > connProgress) continue; + + // Farbe des Flusses basierend auf dem aktuellen Modus + const flowColor = this.isDarkMode ? this.darkModeColors.flowColor : this.lightModeColors.flowColor; + const rgbFlowColor = this.hexToRgb(flowColor); + + const baseAngle = Math.atan2(p2.y - p1.y, p2.x - p1.x); + + this.ctx.save(); + + // Subtilere Untergrundspur für den Blitz this.ctx.beginPath(); this.ctx.moveTo(p1.x, p1.y); this.ctx.lineTo(p2.x, p2.y); - this.ctx.strokeStyle = `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.28 * fadeOpacity})`; - this.ctx.lineWidth = 1.1; + this.ctx.strokeStyle = `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.15 * fadeFactor})`; + this.ctx.lineWidth = 3; + this.ctx.shadowColor = `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.1 * fadeFactor})`; + this.ctx.shadowBlur = 7; this.ctx.stroke(); + + // Zickzack-Blitz mit geringerer Vibration generieren + const zigzag = this.generateZigZagPoints(p1, p2, 6, 7); + + // Hauptblitz mit dezenterem Ein-/Ausblendeffekt + this.ctx.strokeStyle = `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.5 * fadeFactor})`; + this.ctx.lineWidth = 1.2; + this.ctx.shadowColor = `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.25 * fadeFactor})`; + this.ctx.shadowBlur = 6; + this.ctx.beginPath(); + this.ctx.moveTo(zigzag[0].x, zigzag[0].y); + for (let i = 1; i < zigzag.length; i++) { + this.ctx.lineTo(zigzag[i].x, zigzag[i].y); + } + this.ctx.stroke(); + + // Weniger Funken mit geringerer Vibration + const sparks = this.generateSparkPoints(zigzag, 4 + Math.floor(Math.random() * 2)); + + // Dezenteres Funkenlicht mit Ein-/Ausblendeffekt + const sparkBaseOpacity = this.isDarkMode ? 0.65 : 0.55; + const sparkBaseColor = this.isDarkMode + ? `rgba(220, 235, 245, ${sparkBaseOpacity * fadeFactor})` + : `rgba(180, 220, 245, ${sparkBaseOpacity * fadeFactor})`; + + for (const spark of sparks) { + this.ctx.beginPath(); + + // Subtilere Stern/Funken-Form + const points = 4 + Math.floor(Math.random() * 3); // 4-6 Spitzen + const outerRadius = spark.size * 1.8; + const innerRadius = spark.size * 0.4; + + 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(); + this.ctx.fillStyle = sparkBaseColor; + this.ctx.shadowColor = this.isDarkMode + ? `rgba(170, 215, 245, ${0.4 * fadeFactor})` + : `rgba(140, 200, 245, ${0.3 * fadeFactor})`; + this.ctx.shadowBlur = 7; + this.ctx.fill(); + } + + // Dezenterer Fortschrittseffekt an der Spitze des Blitzes + if (endProgress >= connProgress - 0.05 && connProgress < 0.95) { + const tipGlow = this.ctx.createRadialGradient( + p2.x, p2.y, 0, + p2.x, p2.y, 6 + ); + tipGlow.addColorStop(0, `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.7 * 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, 6, 0, Math.PI * 2); + this.ctx.fill(); + } + + // Sanftere Start- und Endblitz-Fades + if (startProgress < 0.1) { + const startFade = startProgress / 0.1; // 0 bis 1 + const startGlow = this.ctx.createRadialGradient( + p1.x, p1.y, 0, + p1.x, p1.y, 8 * startFade + ); + startGlow.addColorStop(0, `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, ${0.4 * fadeFactor * startFade})`); + startGlow.addColorStop(1, `rgba(${rgbFlowColor.r}, ${rgbFlowColor.g}, ${rgbFlowColor.b}, 0)`); + + this.ctx.fillStyle = startGlow; + this.ctx.beginPath(); + this.ctx.arc(p1.x, p1.y, 8 * startFade, 0, Math.PI * 2); + this.ctx.fill(); + } + + this.ctx.restore(); } } @@ -905,6 +1222,77 @@ class NeuralNetworkBackground { this.gl.deleteProgram(this.shaderProgram); } } + + // 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 dezentere Funkenpunkte mit gemäßigter Verteilung + generateSparkPoints(zigzag, sparkCount = 4) { + const sparks = []; + // Weniger Funken + const actualSparkCount = Math.min(sparkCount, zigzag.length); + + // 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; + + // Rechtwinkliger Versatz vom Segment (sanftere Verteilung) + const offsetAngle = segmentAngle + Math.PI/2; + const offsetDistance = Math.random() * 4 - 2; // Geringerer Offset für dezentere Funken + + sparks.push({ + x: x + Math.cos(offsetAngle) * offsetDistance, + y: y + Math.sin(offsetAngle) * offsetDistance, + size: 1 + Math.random() * 1.5 // Kleinere Funkengröße für subtilere Effekte + }); + } + + return sparks; + } } // Initialize when DOM is loaded @@ -926,9 +1314,9 @@ window.addEventListener('load', () => { } }); -// Clean up when window is closed -window.addEventListener('beforeunload', () => { - if (window.neuralNetworkBackground) { - window.neuralNetworkBackground.destroy(); - } -}); \ No newline at end of file +// Event listener to clean up when the window is closed +window.addEventListener('beforeunload', function() { + if (window.neuralNetworkBackground) { + window.neuralNetworkBackground.destroy(); + } +}); diff --git a/static/style.css b/static/style.css index 9e05d85..e7c6d52 100644 --- a/static/style.css +++ b/static/style.css @@ -62,6 +62,22 @@ body { body.dark { color: var(--dark-text-primary); + background-color: transparent; +} + +/* Ensure proper contrast in both modes */ +body:not(.dark) { + --text-primary: var(--light-text-primary); + --text-secondary: var(--light-text-secondary); + --bg-primary: var(--light-bg-primary); + --bg-secondary: var(--light-bg-secondary); +} + +body.dark { + --text-primary: var(--dark-text-primary); + --text-secondary: var(--dark-text-secondary); + --bg-primary: var(--dark-bg-primary); + --bg-secondary: var(--dark-bg-secondary); } /* Typography */ diff --git a/templates/base.html b/templates/base.html index 14a8411..7c2da0f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -17,7 +17,6 @@ -