diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc
index 04d4971..87792dd 100644
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
diff --git a/app.py b/app.py
index a2546d3..3a71f7a 100644
--- a/app.py
+++ b/app.py
@@ -1212,8 +1212,17 @@ def chat_with_assistant():
# Extrahiere Systemnachricht falls vorhanden, sonst Standard-Systemnachricht
system_message = next((msg['content'] for msg in messages if msg['role'] == 'system'),
- "Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. "
- "Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. "
+ "Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
+ "Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
+ "Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
+ "Wichtige Funktionen sind:\n"
+ "- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
+ "- Kategorisierung und thematische Organisation\n"
+ "- Interaktive Mindmaps zur Wissensvisualisierung\n"
+ "- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
+ "- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
+ "Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
+ "Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
"Antworte informativ, sachlich und gut strukturiert auf Deutsch.")
# Formatiere Nachrichten für OpenAI API
@@ -1227,6 +1236,7 @@ def chat_with_assistant():
# Alte Implementierung für direktes Prompt
prompt = data.get('prompt', '')
context = data.get('context', '')
+ selected_items = data.get('selected_items', []) # Ausgewählte Elemente aus der Datenbank
if not prompt:
return jsonify({
@@ -1235,13 +1245,39 @@ def chat_with_assistant():
# Zusammenfassen mehrerer Gedanken oder Analyse anfordern
system_message = (
- "Du bist ein hilfreicher Assistent, der Zugriff auf die Wissensdatenbank hat. Du antwortest nur auf Fragen bezüglich Systades und der Wissensdatenbank. "
- "Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern. "
+ "Du bist ein spezialisierter Assistent für Systades, eine innovative Wissensmanagement-Plattform. "
+ "Systades ist ein intelligentes System zur Verwaltung, Verknüpfung und Visualisierung von Wissen. "
+ "Die Plattform ermöglicht es Nutzern, Gedanken zu erfassen, in Kategorien zu organisieren und durch Mindmaps zu visualisieren. "
+ "Wichtige Funktionen sind:\n"
+ "- Gedankenverwaltung mit Titeln, Zusammenfassungen und Keywords\n"
+ "- Kategorisierung und thematische Organisation\n"
+ "- Interaktive Mindmaps zur Wissensvisualisierung\n"
+ "- KI-gestützte Analyse und Zusammenfassung von Inhalten\n"
+ "- Kollaborative Wissensarbeit und Teilen von Inhalten\n\n"
+ "Du antwortest AUSSCHLIESSLICH auf Fragen bezüglich der Systades-Wissensdatenbank und Website. "
+ "Du kannst Informationen zu Gedanken, Kategorien und Mindmaps liefern und durch Themen führen. "
"Antworte informativ, sachlich und gut strukturiert auf Deutsch."
)
if context:
system_message += f"\n\nKontext: {context}"
+
+ if selected_items:
+ system_message += "\n\nAusgewählte Elemente aus der Datenbank:\n"
+ for item in selected_items:
+ if 'type' in item and 'data' in item:
+ if item['type'] == 'thought':
+ system_message += f"- Gedanke: {item['data'].get('title', 'Unbekannter Titel')}\n"
+ system_message += f" Zusammenfassung: {item['data'].get('abstract', 'Keine Zusammenfassung')}\n"
+ system_message += f" Keywords: {item['data'].get('keywords', 'Keine Keywords')}\n"
+ elif item['type'] == 'category':
+ system_message += f"- Kategorie: {item['data'].get('name', 'Unbekannte Kategorie')}\n"
+ system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
+ system_message += f" Unterkategorien: {item['data'].get('subcategories', 'Keine Unterkategorien')}\n"
+ elif item['type'] == 'mindmap':
+ system_message += f"- Mindmap: {item['data'].get('name', 'Unbekannte Mindmap')}\n"
+ system_message += f" Beschreibung: {item['data'].get('description', 'Keine Beschreibung')}\n"
+ system_message += f" Knoten: {item['data'].get('nodes', 'Keine Knoten')}\n"
api_messages = [
{"role": "system", "content": system_message},
@@ -1276,7 +1312,7 @@ def chat_with_assistant():
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=api_messages,
- max_tokens=600, # Erhöht für längere, detailliertere Antworten
+ max_tokens=1000, # Erhöht für ausführlichere Antworten und detaillierte Führungen
temperature=0.7,
timeout=20 # 20 Sekunden Timeout
)
diff --git a/static/css/base-styles.css b/static/css/base-styles.css
index e1c593b..989c7fd 100644
--- a/static/css/base-styles.css
+++ b/static/css/base-styles.css
@@ -35,6 +35,21 @@
--transition-fast: 150ms ease-in-out;
--transition-normal: 300ms ease-in-out;
--transition-slow: 500ms ease-in-out;
+
+ /* Light mode optimierte Farben */
+ --light-bg: #f9fafb;
+ --light-text: #1e293b;
+ --light-heading: #0f172a;
+ --light-primary: #3b82f6;
+ --light-primary-hover: #4f46e5;
+ --light-secondary: #6b7280;
+ --light-border: #e5e7eb;
+ --light-card-bg: rgba(255, 255, 255, 0.92);
+ --light-navbar-bg: rgba(255, 255, 255, 0.92);
+ --light-input-bg: #ffffff;
+ --light-input-border: #d1d5db;
+ --light-input-focus: #3b82f6;
+ --light-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
/* Base Styles */
@@ -60,9 +75,9 @@ html.dark body {
}
/* Light Mode */
-body {
- background-color: var(--bg-primary-light);
- color: var(--text-primary-light);
+body:not(.dark) {
+ background-color: var(--light-bg);
+ color: var(--light-text);
}
/* Typography */
@@ -418,4 +433,94 @@ html.dark .mystical-dot {
html.dark :focus-visible {
outline-color: var(--accent-primary-dark);
+}
+
+/* Light Mode Überschriften */
+body:not(.dark) h1,
+body:not(.dark) h2,
+body:not(.dark) h3,
+body:not(.dark) h4,
+body:not(.dark) h5,
+body:not(.dark) h6 {
+ color: var(--light-heading);
+}
+
+/* Light Mode Links */
+body:not(.dark) a {
+ color: var(--light-primary);
+}
+
+body:not(.dark) a:hover {
+ color: var(--light-primary-hover);
+}
+
+/* Light Mode Buttons */
+body:not(.dark) .btn,
+body:not(.dark) button:not(.toggle) {
+ background-color: var(--light-primary);
+ color: white;
+ border: none;
+ box-shadow: var(--light-shadow);
+ border-radius: 0.375rem;
+ padding: 0.5rem 1rem;
+ transition: all 0.3s ease;
+}
+
+body:not(.dark) .btn:hover,
+body:not(.dark) button:not(.toggle):hover {
+ background-color: var(--light-primary-hover);
+ transform: translateY(-2px);
+ box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1);
+}
+
+/* Light Mode Cards und Panels */
+body:not(.dark) .card,
+body:not(.dark) .panel {
+ background-color: var(--light-card-bg);
+ border: 1px solid var(--light-border);
+ border-radius: 0.5rem;
+ box-shadow: var(--light-shadow);
+}
+
+/* Light Mode Tabelle */
+body:not(.dark) table {
+ background-color: var(--light-card-bg);
+ border-collapse: collapse;
+}
+
+body:not(.dark) th {
+ background-color: var(--light-bg);
+ color: var(--light-heading);
+ border-bottom: 1px solid var(--light-border);
+}
+
+body:not(.dark) td {
+ border-bottom: 1px solid var(--light-border);
+}
+
+/* Light Mode Inputs */
+body:not(.dark) input,
+body:not(.dark) textarea,
+body:not(.dark) select {
+ background-color: var(--light-input-bg);
+ border: 1px solid var(--light-input-border);
+ color: var(--light-text);
+ border-radius: 0.375rem;
+ padding: 0.5rem;
+}
+
+body:not(.dark) input:focus,
+body:not(.dark) textarea:focus,
+body:not(.dark) select:focus {
+ border-color: var(--light-input-focus);
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+ outline: none;
+}
+
+/* Navbar im Light Mode verbessern */
+body:not(.dark) nav,
+body:not(.dark) .navbar {
+ background-color: var(--light-navbar-bg);
+ box-shadow: var(--light-shadow);
+ border-bottom: 1px solid var(--light-border);
}
\ No newline at end of file
diff --git a/static/css/neural-network-background.css b/static/css/neural-network-background.css
index 593add5..f5c6b76 100644
--- a/static/css/neural-network-background.css
+++ b/static/css/neural-network-background.css
@@ -33,15 +33,74 @@ html.dark, html {
backdrop-filter: blur(5px) !important;
}
+/* Dark Mode - Navbar */
body.dark .glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.7) !important;
}
+/* Light Mode - Verbesserter Navbar */
body .glass-navbar-light {
- background-color: rgba(255, 255, 255, 0.7) !important;
+ background-color: rgba(255, 255, 255, 0.92) !important;
+ backdrop-filter: blur(10px) !important;
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
+ border-bottom: 1px solid rgba(220, 220, 220, 0.5) !important;
}
-/* Make sure footer has proper transparency */
-footer {
+/* Light Mode - Verbesserte Lesbarkeit für Navbar-Elemente */
+body:not(.dark) .navbar-link,
+body:not(.dark) .navbar-item {
+ color: #1e3a8a !important; /* Dunkles Blau für bessere Lesbarkeit */
+}
+
+body:not(.dark) .navbar-link:hover,
+body:not(.dark) .navbar-item:hover {
+ color: #4f46e5 !important; /* Helles Lila beim Hover */
+ background-color: rgba(240, 245, 255, 0.9) !important;
+}
+
+/* Light Mode - Buttons verbessert */
+body:not(.dark) .btn,
+body:not(.dark) button {
+ background-color: #3b82f6 !important; /* Klares Blau statt Grau */
+ color: white !important;
+ border: none !important;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
+}
+
+body:not(.dark) .btn:hover,
+body:not(.dark) button:hover {
+ background-color: #4f46e5 !important; /* Lila beim Hover */
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.12) !important;
+}
+
+/* Verbesserte Karten im Light Mode */
+body:not(.dark) .card,
+body:not(.dark) .panel {
+ background-color: rgba(255, 255, 255, 0.92) !important;
+ border: 1px solid rgba(220, 220, 220, 0.8) !important;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05) !important;
+}
+
+/* Verbesserte Lesbarkeit für Text im Light Mode */
+body:not(.dark) {
+ color: #1e293b !important; /* Dunkles Blau-Grau statt Schwarz */
+}
+
+body:not(.dark) h1,
+body:not(.dark) h2,
+body:not(.dark) h3,
+body:not(.dark) h4,
+body:not(.dark) h5,
+body:not(.dark) h6 {
+ color: #0f172a !important; /* Fast schwarz für Überschriften */
+}
+
+/* Make sure footer has proper transparency and styling */
+body.dark footer {
background-color: rgba(10, 14, 25, 0.7) !important;
+}
+
+body:not(.dark) footer {
+ background-color: rgba(249, 250, 251, 0.92) !important;
+ border-top: 1px solid rgba(220, 220, 220, 0.8) !important;
}
\ No newline at end of file
diff --git a/static/css/style.css b/static/css/style.css
index 7bf65df..51ecb7e 100644
--- a/static/css/style.css
+++ b/static/css/style.css
@@ -1441,4 +1441,204 @@ html, body {
position: sticky;
top: 0;
z-index: 1000;
+}
+
+/* Light Mode Optimierungen für wichtige UI-Komponenten */
+
+/* Buttons im Light Mode */
+.btn-primary:not(.dark-mode .btn-primary) {
+ background-color: var(--light-primary, #3b82f6);
+ color: white;
+ border: none;
+ font-weight: 500;
+}
+
+.btn-primary:not(.dark-mode .btn-primary):hover {
+ background-color: var(--light-primary-hover, #4f46e5);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.btn-secondary:not(.dark-mode .btn-secondary) {
+ background-color: #f3f4f6;
+ color: #374151;
+ border: 1px solid #d1d5db;
+ font-weight: 500;
+}
+
+.btn-secondary:not(.dark-mode .btn-secondary):hover {
+ background-color: #e5e7eb;
+}
+
+/* Navbar im Light Mode */
+.navbar:not(.dark-mode .navbar),
+.nav:not(.dark-mode .nav) {
+ background-color: rgba(255, 255, 255, 0.95);
+ border-bottom: 1px solid #e5e7eb;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
+}
+
+.navbar:not(.dark-mode .navbar) .nav-link,
+.nav:not(.dark-mode .nav) .nav-link {
+ color: #1e3a8a;
+ font-weight: 500;
+}
+
+.navbar:not(.dark-mode .navbar) .nav-link:hover,
+.nav:not(.dark-mode .nav) .nav-link:hover {
+ color: #4f46e5;
+}
+
+.navbar:not(.dark-mode .navbar) .navbar-brand,
+.nav:not(.dark-mode .nav) .navbar-brand {
+ color: #0f172a;
+ font-weight: 700;
+}
+
+/* Dropdown Menüs im Light Mode */
+.dropdown-menu:not(.dark-mode .dropdown-menu) {
+ background-color: white;
+ border: 1px solid #e5e7eb;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05), 0 10px 15px rgba(0, 0, 0, 0.1);
+ border-radius: 0.5rem;
+ padding: 0.5rem 0;
+}
+
+.dropdown-item:not(.dark-mode .dropdown-item) {
+ color: #1e293b;
+ padding: 0.5rem 1rem;
+}
+
+.dropdown-item:not(.dark-mode .dropdown-item):hover {
+ background-color: #f1f5f9;
+ color: #4f46e5;
+}
+
+/* Karten im Light Mode */
+.card:not(.dark-mode .card) {
+ background-color: white;
+ border: 1px solid #e5e7eb;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.03), 0 1px 3px rgba(0, 0, 0, 0.05);
+ border-radius: 0.5rem;
+ overflow: hidden;
+}
+
+.card-header:not(.dark-mode .card-header) {
+ background-color: #f8fafc;
+ border-bottom: 1px solid #e5e7eb;
+ padding: 1rem 1.5rem;
+}
+
+.card-footer:not(.dark-mode .card-footer) {
+ background-color: #f8fafc;
+ border-top: 1px solid #e5e7eb;
+}
+
+/* Formulare im Light Mode */
+.form-control:not(.dark-mode .form-control) {
+ background-color: white;
+ border: 1px solid #d1d5db;
+ border-radius: 0.375rem;
+ padding: 0.5rem 0.75rem;
+ color: #1e293b;
+}
+
+.form-control:not(.dark-mode .form-control):focus {
+ border-color: #3b82f6;
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.25);
+}
+
+/* Tabs im Light Mode */
+.nav-tabs:not(.dark-mode .nav-tabs) {
+ border-bottom-color: #e5e7eb;
+}
+
+.nav-tabs:not(.dark-mode .nav-tabs) .nav-link {
+ color: #64748b;
+ border: 1px solid transparent;
+}
+
+.nav-tabs:not(.dark-mode .nav-tabs) .nav-link:hover {
+ border-color: #e5e7eb #e5e7eb #e5e7eb;
+ color: #3b82f6;
+}
+
+.nav-tabs:not(.dark-mode .nav-tabs) .nav-link.active {
+ color: #0f172a;
+ background-color: white;
+ border-color: #e5e7eb #e5e7eb white;
+ font-weight: 500;
+}
+
+/* Alerts im Light Mode */
+.alert:not(.dark-mode .alert) {
+ border-radius: 0.5rem;
+ border: 1px solid transparent;
+}
+
+.alert-primary:not(.dark-mode .alert-primary) {
+ background-color: #eff6ff;
+ border-color: #bfdbfe;
+ color: #1e40af;
+}
+
+.alert-success:not(.dark-mode .alert-success) {
+ background-color: #f0fdf4;
+ border-color: #bbf7d0;
+ color: #166534;
+}
+
+.alert-warning:not(.dark-mode .alert-warning) {
+ background-color: #fffbeb;
+ border-color: #fef3c7;
+ color: #92400e;
+}
+
+.alert-danger:not(.dark-mode .alert-danger) {
+ background-color: #fef2f2;
+ border-color: #fecaca;
+ color: #b91c1c;
+}
+
+/* Badges im Light Mode */
+.badge:not(.dark-mode .badge) {
+ font-weight: 500;
+ padding: 0.25em 0.6em;
+ border-radius: 0.375rem;
+}
+
+.badge-primary:not(.dark-mode .badge-primary) {
+ background-color: #3b82f6;
+ color: white;
+}
+
+.badge-secondary:not(.dark-mode .badge-secondary) {
+ background-color: #f3f4f6;
+ color: #1f2937;
+}
+
+/* Tabellen im Light Mode */
+table:not(.dark-mode table) {
+ background-color: white;
+ border-collapse: collapse;
+ width: 100%;
+}
+
+table:not(.dark-mode table) th {
+ background-color: #f8fafc;
+ border-bottom: 1px solid #e5e7eb;
+ color: #0f172a;
+ font-weight: 600;
+ padding: 0.75rem;
+ text-align: left;
+}
+
+table:not(.dark-mode table) td {
+ border-bottom: 1px solid #e5e7eb;
+ padding: 0.75rem;
+ color: #1e293b;
+}
+
+table:not(.dark-mode table) tr:hover {
+ background-color: #f8fafc;
}
\ No newline at end of file
diff --git a/static/neural-network-background-full.js b/static/neural-network-background-full.js
new file mode 100644
index 0000000..266732f
--- /dev/null
+++ b/static/neural-network-background-full.js
@@ -0,0 +1,1109 @@
+/**
+ * Neural Network Background Animation
+ * Modern, darker, mystical theme using WebGL
+ * Subtle flowing network aesthetic
+ */
+
+class NeuralNetworkBackground {
+ constructor(options = {}) {
+ this.canvas = document.createElement('canvas');
+ this.canvas.style.position = 'fixed';
+ this.canvas.style.top = '0';
+ this.canvas.style.left = '0';
+ this.canvas.style.width = '100%';
+ this.canvas.style.height = '100%';
+ this.canvas.style.zIndex = '-1';
+ this.canvas.style.pointerEvents = 'none';
+ this.canvas.style.opacity = '0.6'; // Noch weiter reduzierte Opazität für subtileren Hintergrund
+
+ // Standardkonfiguration mit subtileren Werten
+ this.config = {
+ nodeCount: 50, // Weniger Knoten
+ nodeSize: 1.2, // Kleinere Knoten
+ connectionDistance: 150, // Reduzierte Verbindungsdistanz
+ connectionOpacity: 0.3, // Sanftere Verbindungslinien
+ clusterCount: 2, // Weniger Cluster
+ clusterRadius: 380, // Größerer Cluster-Radius für mehr Verteilung
+ animationSpeed: 0.25, // Langsamere Animation
+ flowDensity: 0.05, // Deutlich weniger Flussanimationen
+ flowSpeed: 0.04, // Langsamerer Fluss
+ flowLength: 0.04, // Kürzere Flussstreifen
+ pulseSpeed: 0.0004, // Sehr sanftes Pulsieren
+ activationFrequency: 0.4, // Seltenere Aktivierungen
+ minConnectionsPerNode: 1,
+ maxConnectionsPerNode: 3, // Weniger maximale Verbindungen
+ lineUnderFlow: true, // Strichlinie unter Fluss anzeigen
+ lineUnderFlowOpacity: 0.2, // Opazität der Strichlinie
+ lineUnderFlowWidth: 1.0, // Breite der Strichlinie
+ sequentialFlows: true, // Flüsse nacheinander anzeigen
+ maxSimultaneousFlows: 3 // Maximale Anzahl gleichzeitiger Flüsse
+ };
+
+ // Benutzerkonfiguration übernehmen (falls vorhanden)
+ this.config = { ...this.config, ...options.config };
+
+ // Dunkler Modus mit subtileren Farben
+ this.darkModeColors = {
+ background: '#050a12', // Dunklerer Hintergrund
+ nodeColor: '#5a79b7', // Sanfteres Blau
+ connectionColor: '#2a3c68', // Gedämpfteres Blau
+ flowColor: '#4d6ea8', // Sanfteres Flussblau
+ lineUnderFlowColor: '#223a5c', // Dunkleres Blau für Strichlinie
+ nodePulse: { r: 120, g: 150, b: 200 } // Sanftere Pulse-Farbe
+ };
+
+ // Heller Modus mit subtileren Farben
+ this.lightModeColors = {
+ background: '#f9fbff', // Hellerer Hintergrund
+ nodeColor: '#b9d0f8', // Helleres, sanfteres Blau
+ connectionColor: '#e0f0ff', // Sehr helles Blau für subtilere Verbindungen
+ flowColor: '#92b2e6', // Sanftes mittleres Blau
+ lineUnderFlowColor: '#c7d8f2', // Helles Blau für Strichlinie
+ nodePulse: { r: 110, g: 140, b: 200 } // Sanftere Pulse-Farbe
+ };
+
+ // Erweiterte Konfiguration
+ this.nodes = [];
+ this.connections = [];
+ this.flows = [];
+ this.isDarkMode = options.isDarkMode !== undefined ? options.isDarkMode : true;
+ this.useWebGL = options.useWebGL !== undefined ? options.useWebGL : true;
+ this.frameRate = options.frameRate || 30; // Reduzierte Framerate für bessere Performance
+ this.lastFrameTime = 0;
+ this.frameInterval = 1000 / this.frameRate;
+ this.lastFlowTime = 0; // Zeit des letzten Fluss-Starts
+ this.flowInterval = 2000; // Mindestzeit zwischen neuen Flüssen (ms)
+
+ // Ereignisbehandlung für Fenstergröße und Theme-Wechsel
+ this.resizeTimeout = null;
+ window.addEventListener('resize', this.onResize.bind(this));
+ document.addEventListener('theme-changed', (e) => {
+ this.setDarkMode(e.detail.isDarkMode);
+ });
+
+ // Initialisierung
+ this.webglSetupComplete = false;
+ if (this.useWebGL && this.setupWebGL()) {
+ this.webglSetupComplete = true;
+ } else {
+ this.ctx = this.canvas.getContext('2d');
+ }
+
+ // If canvas already exists, remove it first
+ const existingCanvas = document.getElementById('neural-network-background');
+ if (existingCanvas) {
+ existingCanvas.remove();
+ }
+
+ // Append to body as first child to ensure it's behind everything
+ if (document.body.firstChild) {
+ document.body.insertBefore(this.canvas, document.body.firstChild);
+ } else {
+ document.body.appendChild(this.canvas);
+ }
+
+ // Animation properties
+ this.animationFrameId = null;
+
+ // Initialize
+ this.init();
+
+ // Event listeners
+ document.addEventListener('darkModeToggled', (event) => {
+ this.isDarkMode = event.detail.isDark;
+ });
+
+ // Log that the background is initialized
+ console.log('Neural Network Background initialized');
+ }
+
+ init() {
+ this.resizeCanvas();
+
+ if (this.useWebGL) {
+ this.initWebGL();
+ }
+
+ this.createNodes();
+ this.createConnections();
+ this.startAnimation();
+ }
+
+ resizeCanvas() {
+ const pixelRatio = window.devicePixelRatio || 1;
+ const width = window.innerWidth;
+ const height = window.innerHeight;
+
+ this.canvas.style.width = width + 'px';
+ this.canvas.style.height = height + 'px';
+ this.canvas.width = width * pixelRatio;
+ this.canvas.height = height * pixelRatio;
+
+ if (this.useWebGL) {
+ this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
+ } else if (this.ctx) {
+ this.ctx.scale(pixelRatio, pixelRatio);
+ }
+
+ // Recalculate node positions after resize
+ if (this.nodes.length) {
+ this.createNodes();
+ this.createConnections();
+ }
+ }
+
+ initWebGL() {
+ // Vertex shader
+ const vsSource = `
+ attribute vec2 aVertexPosition;
+ attribute float aPointSize;
+ uniform vec2 uResolution;
+
+ void main() {
+ // Convert from pixel to clip space
+ vec2 position = (aVertexPosition / uResolution) * 2.0 - 1.0;
+ // Flip Y coordinate
+ position.y = -position.y;
+
+ gl_Position = vec4(position, 0, 1);
+ gl_PointSize = aPointSize;
+ }
+ `;
+
+ // Fragment shader - Softer glow effect
+ const fsSource = `
+ precision mediump float;
+ uniform vec4 uColor;
+
+ void main() {
+ float distance = length(gl_PointCoord - vec2(0.5, 0.5));
+
+ // Softer glow with smoother falloff
+ float alpha = 1.0 - smoothstep(0.1, 0.5, distance);
+ alpha = pow(alpha, 1.5); // Make the glow even softer
+
+ gl_FragColor = vec4(uColor.rgb, uColor.a * alpha);
+ }
+ `;
+
+ // Initialize shaders
+ const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
+ const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource);
+
+ // Create shader program
+ this.shaderProgram = this.gl.createProgram();
+ this.gl.attachShader(this.shaderProgram, vertexShader);
+ this.gl.attachShader(this.shaderProgram, fragmentShader);
+ this.gl.linkProgram(this.shaderProgram);
+
+ if (!this.gl.getProgramParameter(this.shaderProgram, this.gl.LINK_STATUS)) {
+ console.error('Unable to initialize the shader program: ' +
+ this.gl.getProgramInfoLog(this.shaderProgram));
+ return;
+ }
+
+ // Get attribute and uniform locations
+ this.programInfo = {
+ program: this.shaderProgram,
+ attribLocations: {
+ vertexPosition: this.gl.getAttribLocation(this.shaderProgram, 'aVertexPosition'),
+ pointSize: this.gl.getAttribLocation(this.shaderProgram, 'aPointSize')
+ },
+ uniformLocations: {
+ resolution: this.gl.getUniformLocation(this.shaderProgram, 'uResolution'),
+ color: this.gl.getUniformLocation(this.shaderProgram, 'uColor')
+ }
+ };
+
+ // Create buffers
+ this.positionBuffer = this.gl.createBuffer();
+ this.sizeBuffer = this.gl.createBuffer();
+
+ // Set clear color for WebGL context
+ const bgColor = this.hexToRgb(this.darkModeColors.background);
+
+ this.gl.clearColor(bgColor.r/255, bgColor.g/255, bgColor.b/255, 1.0);
+ }
+
+ loadShader(type, source) {
+ const shader = this.gl.createShader(type);
+ this.gl.shaderSource(shader, source);
+ this.gl.compileShader(shader);
+
+ if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
+ console.error('An error occurred compiling the shaders: ' +
+ this.gl.getShaderInfoLog(shader));
+ this.gl.deleteShader(shader);
+ return null;
+ }
+
+ return shader;
+ }
+
+ createNodes() {
+ this.nodes = [];
+ const width = this.canvas.width / (window.devicePixelRatio || 1);
+ const height = this.canvas.height / (window.devicePixelRatio || 1);
+
+ // Erstelle Cluster-Zentren für neuronale Netzwerkmuster
+ const clusterCount = Math.floor(5 + Math.random() * 4); // 5-8 Cluster
+ const clusters = [];
+
+ for (let i = 0; i < clusterCount; i++) {
+ clusters.push({
+ x: Math.random() * width,
+ y: Math.random() * height,
+ radius: 100 + Math.random() * 150
+ });
+ }
+
+ // Create nodes with random positions and properties
+ for (let i = 0; i < this.config.nodeCount; i++) {
+ // Entscheide, ob dieser Knoten zu einem Cluster gehört oder nicht
+ const useCluster = Math.random() < this.config.clusteringFactor;
+ let x, y;
+
+ if (useCluster && clusters.length > 0) {
+ // Wähle ein zufälliges Cluster
+ const cluster = clusters[Math.floor(Math.random() * clusters.length)];
+ const angle = Math.random() * Math.PI * 2;
+ const distance = Math.random() * cluster.radius;
+
+ // Platziere in der Nähe des Clusters mit einiger Streuung
+ x = cluster.x + Math.cos(angle) * distance;
+ y = cluster.y + Math.sin(angle) * distance;
+
+ // Stelle sicher, dass es innerhalb des Bildschirms bleibt
+ x = Math.max(0, Math.min(width, x));
+ y = Math.max(0, Math.min(height, y));
+ } else {
+ // Zufällige Position außerhalb von Clustern
+ x = Math.random() * width;
+ y = Math.random() * height;
+ }
+
+ // Bestimme die Knotengröße - wichtigere Knoten (in Clustern) sind größer
+ const nodeImportance = useCluster ? 1.2 : 0.8;
+ const size = this.config.nodeSize * nodeImportance + Math.random() * this.config.nodeVariation;
+
+ const node = {
+ x: x,
+ y: y,
+ size: size,
+ speed: {
+ x: (Math.random() - 0.5) * this.config.animationSpeed,
+ y: (Math.random() - 0.5) * this.config.animationSpeed
+ },
+ pulsePhase: Math.random() * Math.PI * 2, // Random starting phase
+ connections: [],
+ isActive: Math.random() < 0.3, // Some nodes start active for neural firing effect
+ lastFired: 0, // For neural firing animation
+ firingRate: 1000 + Math.random() * 4000 // Random firing rate in ms
+ };
+
+ this.nodes.push(node);
+ }
+ }
+
+ createConnections() {
+ this.connections = [];
+ this.flows = []; // Reset flows
+
+ // Create connections between nearby nodes
+ for (let i = 0; i < this.nodes.length; i++) {
+ const nodeA = this.nodes[i];
+ nodeA.connections = [];
+
+ // Sortiere andere Knoten nach Entfernung für bevorzugte nahe Verbindungen
+ const potentialConnections = [];
+
+ for (let j = 0; j < this.nodes.length; j++) {
+ if (i === j) continue;
+
+ const nodeB = this.nodes[j];
+ const dx = nodeB.x - nodeA.x;
+ const dy = nodeB.y - nodeA.y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < this.config.connectionDistance) {
+ potentialConnections.push({
+ index: j,
+ distance: distance
+ });
+ }
+ }
+
+ // Sortiere nach Entfernung
+ potentialConnections.sort((a, b) => a.distance - b.distance);
+
+ // Wähle die nächsten N Verbindungen, maximal maxConnections
+ const maxConn = Math.min(
+ this.config.maxConnections,
+ potentialConnections.length,
+ 1 + Math.floor(Math.random() * this.config.maxConnections)
+ );
+
+ for (let c = 0; c < maxConn; c++) {
+ const connection = potentialConnections[c];
+ const j = connection.index;
+ const nodeB = this.nodes[j];
+ const distance = connection.distance;
+
+ // Create weighted connection (closer = stronger)
+ const connectionStrength = Math.max(0, 1 - distance / this.config.connectionDistance);
+ const connOpacity = connectionStrength * this.config.connectionOpacity;
+
+ // Check if connection already exists
+ if (!this.connections.some(conn =>
+ (conn.from === i && conn.to === j) || (conn.from === j && conn.to === i)
+ )) {
+ // Neue Verbindung startet mit progress=0 für animierten Aufbau
+ this.connections.push({
+ from: i,
+ to: j,
+ distance: distance,
+ opacity: connOpacity,
+ strength: connectionStrength,
+ hasFlow: false,
+ lastActivated: 0,
+ progress: 0 // Animationsfortschritt für Verbindungsaufbau
+ });
+ nodeA.connections.push(j);
+ nodeB.connections.push(i);
+ }
+ }
+ }
+ }
+
+ startAnimation() {
+ this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
+ }
+
+ animate() {
+ // Update nodes
+ const width = this.canvas.width / (window.devicePixelRatio || 1);
+ const height = this.canvas.height / (window.devicePixelRatio || 1);
+ const now = Date.now();
+
+ // Simulate neural firing
+ for (let i = 0; i < this.nodes.length; i++) {
+ const node = this.nodes[i];
+
+ // Check if node should fire based on its firing rate
+ if (now - node.lastFired > node.firingRate) {
+ node.isActive = true;
+ node.lastFired = now;
+
+ // Activate connected nodes with probability based on connection strength
+ for (const connIndex of node.connections) {
+ // Find the connection
+ const conn = this.connections.find(c =>
+ (c.from === i && c.to === connIndex) || (c.from === connIndex && c.to === i)
+ );
+
+ if (conn) {
+ // Mark connection as recently activated
+ conn.lastActivated = now;
+
+ // Create a flow along this connection
+ if (Math.random() < conn.strength * 0.8) {
+ this.flows.push({
+ connection: conn,
+ progress: 0,
+ direction: conn.from === i, // Flow from activated node
+ length: this.config.flowLength + Math.random() * 0.1,
+ intensity: 0.7 + Math.random() * 0.3 // Random intensity for variation
+ });
+ }
+
+ // Probability for connected node to activate
+ if (Math.random() < conn.strength * 0.5) {
+ this.nodes[connIndex].isActive = true;
+ this.nodes[connIndex].lastFired = now - Math.random() * 500; // Slight variation
+ }
+ }
+ }
+ } else if (now - node.lastFired > 300) { // Deactivate after short period
+ node.isActive = false;
+ }
+ }
+
+ // Animierter Verbindungsaufbau: progress inkrementieren
+ for (const connection of this.connections) {
+ if (connection.progress < 1) {
+ // Langsamer Aufbau: Geschwindigkeit kann angepasst werden
+ connection.progress += 0.012; // Sehr langsam, für subtilen Effekt
+ if (connection.progress > 1) connection.progress = 1;
+ }
+ }
+
+ // Update flows
+ this.updateFlows();
+
+ // Occasionally create new flows along connections
+ if (Math.random() < this.config.flowDensity) {
+ this.createNewFlow();
+ }
+
+ // Recalculate connections occasionally for a living network
+ if (Math.random() < 0.01) { // Only recalculate 1% of the time for performance
+ this.createConnections();
+ }
+
+ // Render
+ if (this.useWebGL) {
+ this.renderWebGL();
+ } else {
+ this.renderCanvas();
+ }
+
+ // Continue animation
+ this.animationFrameId = requestAnimationFrame(this.animate.bind(this));
+ }
+
+ // New method to update flow animations
+ updateFlows() {
+ // Update existing flows
+ const now = Date.now();
+
+ for (let i = this.flows.length - 1; i >= 0; i--) {
+ const flow = this.flows[i];
+
+ // Update flow progress
+ flow.progress += this.config.flowSpeed / flow.connection.distance;
+
+ // Update line progress (langsamer als der Blitz)
+ flow.lineProgress = Math.min(flow.progress * 1.5, 1.0);
+
+ // Remove completed flows
+ if (flow.progress > 1.0) {
+ this.flows.splice(i, 1);
+ }
+ }
+
+ // Generate new flows selten und kontrolliert
+ if (Math.random() < this.config.flowDensity &&
+ (!this.config.sequentialFlows ||
+ now - this.lastFlowTime >= this.flowInterval)) {
+ this.createNewFlow();
+ }
+ }
+
+ // New method to create flow animations
+ createNewFlow() {
+ if (this.connections.length === 0) return;
+
+ // Überprüfen, ob maximale Anzahl gleichzeitiger Flüsse erreicht ist
+ if (this.config.sequentialFlows &&
+ this.flows.length >= this.config.maxSimultaneousFlows) {
+ return;
+ }
+
+ // Überprüfen, ob genügend Zeit seit dem letzten Fluss vergangen ist
+ const now = Date.now();
+ if (this.config.sequentialFlows &&
+ now - this.lastFlowTime < this.flowInterval) {
+ return;
+ }
+
+ // Zeit des letzten Flusses aktualisieren
+ this.lastFlowTime = now;
+
+ // Select a random connection with preference for more connected nodes
+ let connectionIdx = Math.floor(Math.random() * this.connections.length);
+ let attempts = 0;
+
+ // Try to find a connection with more connected nodes
+ while (attempts < 5) {
+ const testIdx = Math.floor(Math.random() * this.connections.length);
+ const testConn = this.connections[testIdx];
+ const fromNode = this.nodes[testConn.from];
+
+ if (fromNode.connections.length > 2) {
+ connectionIdx = testIdx;
+ break;
+ }
+ attempts++;
+ }
+
+ const connection = this.connections[connectionIdx];
+
+ // Create a new flow along this connection
+ this.flows.push({
+ connection: connection,
+ progress: 0,
+ direction: Math.random() > 0.5, // Randomly decide direction
+ length: this.config.flowLength + Math.random() * 0.1, // Slightly vary lengths
+ lineProgress: 0 // Fortschritt der unterliegenden Strichlinie (startet bei 0)
+ });
+ }
+
+ renderWebGL() {
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT);
+
+ const width = this.canvas.width / (window.devicePixelRatio || 1);
+ const height = this.canvas.height / (window.devicePixelRatio || 1);
+
+ // Select shader program
+ this.gl.useProgram(this.programInfo.program);
+
+ // Set resolution uniform
+ this.gl.uniform2f(this.programInfo.uniformLocations.resolution, width, height);
+
+ // Draw connections first (behind nodes)
+ this.renderConnectionsWebGL();
+
+ // Draw flows on top of connections
+ this.renderFlowsWebGL();
+
+ // Draw nodes
+ this.renderNodesWebGL();
+ }
+
+ renderNodesWebGL() {
+ // Prepare node positions for WebGL
+ const positions = new Float32Array(this.nodes.length * 2);
+ const sizes = new Float32Array(this.nodes.length);
+
+ const now = Date.now();
+
+ for (let i = 0; i < this.nodes.length; i++) {
+ const node = this.nodes[i];
+ positions[i * 2] = node.x;
+ positions[i * 2 + 1] = node.y;
+
+ // Maximales Pulsieren und sehr große Knoten
+ let pulse = Math.sin(node.pulsePhase) * 0.38 + 1.35;
+ const connectivityFactor = 1 + (node.connections.length / this.config.maxConnections) * 1.1;
+ sizes[i] = node.size * pulse * connectivityFactor;
+ }
+
+ // Bind position buffer
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW);
+ this.gl.vertexAttribPointer(
+ this.programInfo.attribLocations.vertexPosition,
+ 2, // components per vertex
+ this.gl.FLOAT, // data type
+ false, // normalize
+ 0, // stride
+ 0 // offset
+ );
+ this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition);
+
+ // Bind size buffer
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, sizes, this.gl.STATIC_DRAW);
+ this.gl.vertexAttribPointer(
+ this.programInfo.attribLocations.pointSize,
+ 1, // components per vertex
+ this.gl.FLOAT, // data type
+ false, // normalize
+ 0, // stride
+ 0 // offset
+ );
+ this.gl.enableVertexAttribArray(this.programInfo.attribLocations.pointSize);
+
+ // Enable blending for all nodes
+ this.gl.enable(this.gl.BLEND);
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); // Additive blending for glow
+
+ // Draw each node individually with its own color
+ for (let i = 0; i < this.nodes.length; i++) {
+ const node = this.nodes[i];
+
+ // Set node color - more visible with active highlighting
+ const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors;
+ const nodeColor = this.hexToRgb(colorObj.nodeColor);
+ const nodePulseColor = this.hexToRgb(colorObj.nodePulse);
+
+ // Use pulse color for active nodes
+ let r = nodeColor.r / 255;
+ let g = nodeColor.g / 255;
+ let b = nodeColor.b / 255;
+
+ // Active nodes get brighter color
+ if (node.isActive) {
+ r = (r + nodePulseColor.r / 255) / 2;
+ g = (g + nodePulseColor.g / 255) / 2;
+ b = (b + nodePulseColor.b / 255) / 2;
+ }
+
+ // Sehr sichtbare, maximal leuchtende Knoten
+ this.gl.uniform4f(
+ this.programInfo.uniformLocations.color,
+ r, g, b,
+ node.isActive ? 1.0 : 0.92 // Maximal sichtbar
+ );
+
+ // Draw each node individually for better control
+ this.gl.drawArrays(this.gl.POINTS, i, 1);
+ }
+ }
+
+ renderConnectionsWebGL() {
+ // Permanente Verbindungsstriche werden nicht mehr gezeichnet
+ // Diese Methode bleibt leer, damit keine Linien mehr erscheinen
+ }
+
+ // New method to render the flowing animations
+ renderFlowsWebGL() {
+ // Für jeden Flow zuerst die unterliegende Strichlinie zeichnen, dann den Blitz
+ for (const flow of this.flows) {
+ const connection = flow.connection;
+ const fromNode = this.nodes[connection.from];
+ const toNode = this.nodes[connection.to];
+ const direction = flow.direction ? 1 : -1;
+
+ // 1. Die unterliegende Strichlinie zeichnen
+ if (this.config.lineUnderFlow) {
+ const lineStart = direction > 0 ? fromNode : toNode;
+ const lineEnd = direction > 0 ? toNode : fromNode;
+
+ // Strichlinie als gerade Linie mit voller Länge, aber mit Fortschritt
+ const lineEndPoint = {
+ x: lineStart.x + (lineEnd.x - lineStart.x) * flow.lineProgress,
+ y: lineStart.y + (lineEnd.y - lineStart.y) * flow.lineProgress
+ };
+
+ const positions = new Float32Array([
+ lineStart.x, lineStart.y,
+ lineEndPoint.x, lineEndPoint.y
+ ]);
+
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW);
+ this.gl.vertexAttribPointer(
+ this.programInfo.attribLocations.vertexPosition,
+ 2,
+ this.gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition);
+ this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize);
+
+ // Sanftere, transparentere Strichlinie
+ const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors;
+ const lineColor = this.hexToRgb(colorObj.lineUnderFlowColor || colorObj.connectionColor);
+
+ this.gl.uniform4f(
+ this.programInfo.uniformLocations.color,
+ lineColor.r / 255,
+ lineColor.g / 255,
+ lineColor.b / 255,
+ this.config.lineUnderFlowOpacity // Sehr subtile Linie
+ );
+
+ this.gl.enable(this.gl.BLEND);
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA); // Normales Alpha-Blending
+ this.gl.lineWidth(this.config.lineUnderFlowWidth); // Dünne Linie
+ this.gl.drawArrays(this.gl.LINES, 0, 2);
+ }
+
+ // 2. Den Blitz selbst zeichnen
+ const startProgress = flow.progress;
+ const endProgress = Math.min(1, startProgress + flow.length);
+ if (startProgress >= 1 || endProgress <= 0) continue;
+
+ let p1, p2;
+ if (direction > 0) {
+ p1 = {
+ x: fromNode.x + (toNode.x - fromNode.x) * startProgress,
+ y: fromNode.y + (toNode.y - fromNode.y) * startProgress
+ };
+ p2 = {
+ x: fromNode.x + (toNode.x - fromNode.x) * endProgress,
+ y: fromNode.y + (toNode.y - fromNode.y) * endProgress
+ };
+ } else {
+ p1 = {
+ x: toNode.x + (fromNode.x - toNode.x) * startProgress,
+ y: toNode.y + (fromNode.y - toNode.y) * startProgress
+ };
+ p2 = {
+ x: toNode.x + (fromNode.x - toNode.x) * endProgress,
+ y: toNode.y + (fromNode.y - toNode.y) * endProgress
+ };
+ }
+
+ // Zickzack-Blitz generieren (weniger ausgeprägt)
+ const zigzag = this.generateZigZagPoints(p1, p2, 3, 5); // Weniger Segmente, kleinere Amplitude
+ for (let i = 0; i < zigzag.length - 1; i++) {
+ const positions = new Float32Array([
+ zigzag[i].x, zigzag[i].y,
+ zigzag[i + 1].x, zigzag[i + 1].y
+ ]);
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, positions, this.gl.STATIC_DRAW);
+ this.gl.vertexAttribPointer(
+ this.programInfo.attribLocations.vertexPosition,
+ 2,
+ this.gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition);
+ this.gl.disableVertexAttribArray(this.programInfo.attribLocations.pointSize);
+
+ // Dezenter, subtilerer Blitz
+ const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors;
+ const flowColor = this.hexToRgb(colorObj.flowColor);
+ this.gl.uniform4f(
+ this.programInfo.uniformLocations.color,
+ flowColor.r / 255,
+ flowColor.g / 255,
+ flowColor.b / 255,
+ 0.45 // Geringere Sichtbarkeit
+ );
+ this.gl.enable(this.gl.BLEND);
+ this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
+ this.gl.lineWidth(1.0); // Dünnerer Blitz für subtileren Effekt
+ this.gl.drawArrays(this.gl.LINES, 0, 2);
+ }
+
+ // Funken erzeugen - weniger und subtilere elektrische Funken
+ const sparks = this.generateSparkPoints(zigzag, 2 + Math.floor(Math.random() * 2)); // Weniger Funken
+ for (const spark of sparks) {
+ // Sanfteres Weiß-Blau für subtilere elektrische Funken
+ this.gl.uniform4f(
+ this.programInfo.uniformLocations.color,
+ 0.85, 0.9, 0.95, 0.4 * spark.intensity // Geringere Intensität
+ );
+
+ // Position für den Funken setzen
+ const sparkPos = new Float32Array([spark.x, spark.y]);
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, sparkPos, this.gl.STATIC_DRAW);
+
+ // Punktgröße setzen
+ const sizes = new Float32Array([spark.size * 2.0]); // Kleinere Funken
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.sizeBuffer);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, sizes, this.gl.STATIC_DRAW);
+
+ this.gl.vertexAttribPointer(
+ this.programInfo.attribLocations.vertexPosition,
+ 2,
+ this.gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition);
+
+ this.gl.vertexAttribPointer(
+ this.programInfo.attribLocations.pointSize,
+ 1,
+ this.gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ this.gl.enableVertexAttribArray(this.programInfo.attribLocations.pointSize);
+
+ this.gl.drawArrays(this.gl.POINTS, 0, 1);
+ }
+ }
+ }
+
+ renderCanvas() {
+ if (!this.ctx) return;
+
+ const { background, nodeColor, connectionColor } = this.isDarkMode ? this.darkModeColors : this.lightModeColors;
+
+ // Hintergrund sanft löschen mit leichter Transparenz für Nachleuchten
+ this.ctx.fillStyle = background;
+ this.ctx.globalAlpha = 0.22; // Sehr subtile Überblendung für sanfte Bewegung
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+ this.ctx.globalAlpha = 1.0;
+
+ // Verbindungen zeichnen mit distanzabhängiger Transparenz
+ this.connections.forEach(connection => {
+ const { source, target, strength } = connection;
+ const dx = target.x - source.x;
+ const dy = target.y - source.y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ // Berechne Opazität basierend auf Distanz - je weiter entfernt, desto transparenter
+ const maxOpacity = 0.02; // Sehr subtile maximale Opazität
+ const opacityFactor = 1 - (distance / this.config.connectionDistance);
+ let opacity = maxOpacity * opacityFactor * strength * this.config.connectionOpacity;
+
+ // Subtilere Linien
+ this.ctx.beginPath();
+ this.ctx.moveTo(source.x, source.y);
+ this.ctx.lineTo(target.x, target.y);
+ this.ctx.lineWidth = 0.4; // Sehr dünne Linie
+ this.ctx.strokeStyle = connectionColor;
+ this.ctx.globalAlpha = opacity;
+ this.ctx.stroke();
+ this.ctx.globalAlpha = 1.0;
+ });
+
+ // Knoten zeichnen mit subtilerem Glühen und Pulsieren
+ this.nodes.forEach(node => {
+ const nodeBaseSize = node.size * this.config.nodeSize;
+ const pulse = Math.sin(this.time * this.config.pulseSpeed * node.pulseRate) * 0.1 + 1; // Reduzierte Pulsamplitude
+ const glowSize = nodeBaseSize * 1.5; // Kleinerer Glowradius
+
+ // Aktive Knoten speziell hervorheben, aber subtiler
+ if (node.active) {
+ // Subtiles Glühen für aktive Knoten
+ const gradient = this.ctx.createRadialGradient(
+ node.x, node.y, 0,
+ node.x, node.y, glowSize * 2
+ );
+ this.ctx.globalAlpha = 0.09; // Sehr niedrige Opazität für subtiles Glühen
+ gradient.addColorStop(0, 'rgba(255, 255, 255, 0.15)');
+ gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
+
+ this.ctx.fillStyle = gradient;
+ this.ctx.beginPath();
+ this.ctx.arc(node.x, node.y, glowSize * 2, 0, Math.PI * 2);
+ this.ctx.fill();
+
+ // Zeichne aktiven Knoten
+ this.ctx.globalAlpha = 0.9;
+ this.ctx.fillStyle = nodeColor;
+ this.ctx.beginPath();
+ this.ctx.arc(node.x, node.y, nodeBaseSize * pulse, 0, Math.PI * 2);
+ this.ctx.fill();
+ } else {
+ // Inaktive Knoten sanfter darstellen
+ this.ctx.globalAlpha = 0.8;
+ this.ctx.fillStyle = nodeColor;
+ this.ctx.beginPath();
+ this.ctx.arc(node.x, node.y, nodeBaseSize * pulse * 0.8, 0, Math.PI * 2);
+ this.ctx.fill();
+ }
+ this.ctx.globalAlpha = 1.0;
+ });
+ }
+
+ renderFlowsCanvas() {
+ if (!this.ctx) return;
+
+ const { flowColor } = this.isDarkMode ? this.darkModeColors : this.lightModeColors;
+
+ this.flows.forEach(flow => {
+ if (flow.progress > 1) return;
+
+ const { source, target, progress } = flow;
+
+ // Berechne aktuelle Position
+ const dx = target.x - source.x;
+ const dy = target.y - source.y;
+ const currentX = source.x + dx * progress;
+ const currentY = source.y + dy * progress;
+
+ // Zick-Zack-Effekt reduzieren für sanftere Bewegung
+ const segmentCount = 4; // Weniger Segmente
+ const amplitude = 2; // Geringere Amplitude
+ const waveLength = 0.2;
+
+ let prevX = source.x;
+ let prevY = source.y;
+
+ this.ctx.beginPath();
+ this.ctx.moveTo(prevX, prevY);
+
+ // Sanftere "Elektrizitäts"-Linie
+ for (let i = 0; i <= segmentCount; i++) {
+ const segmentProgress = i / segmentCount;
+ if (segmentProgress > progress) break;
+
+ const segX = source.x + dx * segmentProgress;
+ const segY = source.y + dy * segmentProgress;
+
+ // Sanftere Wellen
+ const perpX = -dy * Math.sin(segmentProgress * Math.PI * 10) * amplitude;
+ const perpY = dx * Math.sin(segmentProgress * Math.PI * 10) * amplitude;
+
+ const pointX = segX + perpX * Math.sin(this.time * 0.01);
+ const pointY = segY + perpY * Math.sin(this.time * 0.01);
+
+ this.ctx.lineTo(pointX, pointY);
+ prevX = pointX;
+ prevY = pointY;
+ }
+
+ // Linienausrichtung und -stil
+ this.ctx.lineCap = 'round';
+ this.ctx.lineWidth = 1.2; // Dünnere Linie
+
+ // Subtiler Gloweffekt
+ this.ctx.shadowColor = flowColor;
+ this.ctx.shadowBlur = 6; // Reduzierter Unschärferadius
+
+ this.ctx.strokeStyle = flowColor;
+ this.ctx.globalAlpha = 0.7 - 0.5 * Math.abs(progress - 0.5); // Sanfteres Ein- und Ausblenden
+ this.ctx.stroke();
+ this.ctx.globalAlpha = 1;
+ this.ctx.shadowBlur = 0;
+
+ // Funkeneffekte am Ende der Linie, aber weniger und kleiner
+ if (progress > 0.9 && Math.random() < 0.3) { // Weniger Funken generieren
+ const sparkCount = Math.floor(Math.random() * 2) + 1; // Maximal 3 Funken
+
+ for (let i = 0; i < sparkCount; i++) {
+ const sparkSize = Math.random() * 0.8 + 0.4; // Kleinere Funken
+ const sparkAngle = Math.random() * Math.PI * 2;
+ const sparkDistance = Math.random() * 5 + 2;
+
+ const sparkX = currentX + Math.cos(sparkAngle) * sparkDistance;
+ const sparkY = currentY + Math.sin(sparkAngle) * sparkDistance;
+
+ // Weniger intensive Funken
+ this.ctx.beginPath();
+ this.ctx.arc(sparkX, sparkY, sparkSize, 0, Math.PI * 2);
+ this.ctx.fillStyle = flowColor;
+ this.ctx.globalAlpha = 0.4; // Reduzierte Opazität
+ this.ctx.fill();
+ this.ctx.globalAlpha = 1;
+ }
+ }
+ });
+ }
+
+ // Helper method to convert hex to RGB
+ hexToRgb(hex) {
+ // Remove # if present
+ hex = hex.replace(/^#/, '');
+
+ // Handle rgba hex format
+ let alpha = 1;
+ if (hex.length === 8) {
+ alpha = parseInt(hex.slice(6, 8), 16) / 255;
+ hex = hex.slice(0, 6);
+ }
+
+ // Parse hex values
+ const bigint = parseInt(hex, 16);
+ const r = (bigint >> 16) & 255;
+ const g = (bigint >> 8) & 255;
+ const b = bigint & 255;
+
+ return { r, g, b, a: alpha };
+ }
+
+ // Cleanup method
+ destroy() {
+ if (this.animationFrameId) {
+ cancelAnimationFrame(this.animationFrameId);
+ }
+
+ window.removeEventListener('resize', this.resizeCanvas.bind(this));
+
+ if (this.canvas && this.canvas.parentNode) {
+ this.canvas.parentNode.removeChild(this.canvas);
+ }
+
+ if (this.gl) {
+ // Clean up WebGL resources
+ this.gl.deleteBuffer(this.positionBuffer);
+ this.gl.deleteBuffer(this.sizeBuffer);
+ this.gl.deleteProgram(this.shaderProgram);
+ }
+ }
+
+ // Hilfsfunktion: Erzeuge Zickzack-Punkte für einen Blitz
+ generateZigZagPoints(start, end, segments = 5, amplitude = 8) {
+ const points = [start];
+ for (let i = 1; i < segments; i++) {
+ const t = i / segments;
+ const x = start.x + (end.x - start.x) * t;
+ const y = start.y + (end.y - start.y) * t;
+ // Versatz für Zickzack
+ const angle = Math.atan2(end.y - start.y, end.x - start.x) + Math.PI / 2;
+ const offset = (Math.random() - 0.5) * amplitude * (i % 2 === 0 ? 1 : -1);
+ points.push({
+ x: x + Math.cos(angle) * offset,
+ y: y + Math.sin(angle) * offset
+ });
+ }
+ points.push(end);
+ return points;
+ }
+
+ // Hilfsfunktion: Erzeuge Funkenpunkte entlang eines Zickzack-Blitzes
+ generateSparkPoints(zigzag, sparkCount = 4) {
+ const sparks = [];
+ // Moderatere Anzahl an Funken für subtileren Effekt
+ const actualSparkCount = sparkCount + Math.floor(Math.random() * 2);
+
+ for (let i = 0; i < actualSparkCount; i++) {
+ // Entlang des gesamten Blitzes verteilen
+ const seg = i * (zigzag.length - 1) / actualSparkCount;
+ const segIndex = Math.floor(seg);
+ const t = seg - segIndex;
+
+ // Basis-Position
+ const x = zigzag[segIndex].x + (zigzag[segIndex + 1].x - zigzag[segIndex].x) * t;
+ const y = zigzag[segIndex].y + (zigzag[segIndex + 1].y - zigzag[segIndex].y) * t;
+
+ // Sanfterer Versatz vom Blitz
+ const angle = Math.random() * Math.PI * 2;
+ const distance = 2 + Math.random() * 4; // Reduzierter Abstand für subtileres Sprühen
+
+ sparks.push({
+ x: x + Math.cos(angle) * distance,
+ y: y + Math.sin(angle) * distance,
+ size: 0.5 + Math.random() * 1 // Kleinere Funken
+ });
+ }
+ return sparks;
+ }
+
+ onResize() {
+ // Verhindere mehrfache Resize-Events in kurzer Zeit
+ if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
+
+ this.resizeTimeout = setTimeout(() => {
+ this.resizeCanvas();
+
+ // Knoten und Verbindungen neu erstellen, um sie an die neue Größe anzupassen
+ this.createNodes();
+ this.createConnections();
+ }, 200); // Warte 200ms, bevor die Größenanpassung durchgeführt wird
+ }
+
+ setDarkMode(isDarkMode) {
+ this.isDarkMode = isDarkMode;
+
+ // WebGL-Hintergrundfarbe aktualisieren
+ if (this.useWebGL && this.gl) {
+ const bgColor = this.hexToRgb(
+ this.isDarkMode ? this.darkModeColors.background : this.lightModeColors.background
+ );
+ this.gl.clearColor(bgColor.r/255, bgColor.g/255, bgColor.b/255, 1.0);
+ }
+ }
+}
+
+// Initialize when DOM is loaded
+document.addEventListener('DOMContentLoaded', () => {
+ // Short delay to ensure DOM is fully loaded
+ setTimeout(() => {
+ if (!window.neuralNetworkBackground) {
+ console.log('Creating Neural Network Background');
+ window.neuralNetworkBackground = new NeuralNetworkBackground();
+ }
+ }, 100);
+});
+
+// Re-initialize when page is fully loaded (for safety)
+window.addEventListener('load', () => {
+ if (!window.neuralNetworkBackground) {
+ console.log('Re-initializing Neural Network Background on full load');
+ window.neuralNetworkBackground = new NeuralNetworkBackground();
+ }
+});
+
+// Clean up when window is closed
+window.addEventListener('beforeunload', () => {
+ if (window.neuralNetworkBackground) {
+ window.neuralNetworkBackground.destroy();
+ }
+});
\ 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 @@
-