Compare commits

2 Commits

89 changed files with 3136 additions and 3498 deletions

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -1,10 +0,0 @@
FROM python:3.9-slim-buster
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY website .
CMD ["python", "app.py"]

180
README.md
View File

@@ -1,94 +1,134 @@
# MindMap Wissensnetzwerk
# MindMapProjekt - Roadmap
Eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen mit integriertem ChatGPT-Assistenten.
## Projektübersicht
Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen. Das Projekt wird umfassend überarbeitet, um ein modernes, benutzerfreundliches Design und erweiterte Funktionalitäten zu bieten.
## Features
## Technischer Stack
- **Backend**: Python/Flask
- **Frontend**:
- Tailwind CSS für moderne UI
- SVG-Bibliotheken für Visualisierungen (D3.js)
- JavaScript/Alpine.js für interaktive Komponenten
- **Datenbank**: SQLite mit SQLAlchemy
- **KI-Integration**: OpenAI API für intelligente Assistenz
- Interaktive Mindmap zur Visualisierung von Wissensverbindungen
- Gedanken mit verschiedenen Beziehungstypen verknüpfen
- Suchfunktion für Gedanken und Verbindungen
- Bewertungssystem für Gedanken
- Dark/Light Mode
- **Integrierter KI-Assistent** mit OpenAI GPT-Integration
## Installation und Verwendung
## Installation
### Installation
1. Repository klonen
2. Virtuelle Umgebung erstellen: `python -m venv venv`
3. Virtuelle Umgebung aktivieren:
- Windows: `venv\Scripts\activate`
- Unix/MacOS: `source venv/bin/activate`
4. Abhängigkeiten installieren: `pip install -r requirements.txt`
5. Datenbank initialisieren: `python TOOLS.py db:rebuild`
6. Admin-Benutzer erstellen: `python TOOLS.py user:admin`
7. Server starten: `python TOOLS.py server:run`
### Einfache Installation
### Standardbenutzer
- **Admin-Benutzer**: Username: `admin` / Passwort: `admin`
- **Testbenutzer**: Username: `user` / Passwort: `user`
Führe im übergeordneten Verzeichnis folgendes aus:
### Verwaltungswerkzeuge mit TOOLS.py
Das Projekt enthält ein zentrales Verwaltungsskript `TOOLS.py`, das verschiedene Hilfsfunktionen bietet:
```
python setup.py
```
#### Datenbankverwaltung
- `python TOOLS.py db:fix` - Reparieren der Datenbankstruktur
- `python TOOLS.py db:rebuild` - Datenbank neu aufbauen (löscht alle Daten!)
- `python TOOLS.py db:test` - Datenbankverbindung und Modelle testen
- `python TOOLS.py db:stats` - Datenbankstatistiken anzeigen
Dies erstellt eine virtuelle Umgebung, installiert alle Abhängigkeiten und erstellt die CSS-Dateien mit Tailwind.
#### Benutzerverwaltung
- `python TOOLS.py user:list` - Alle Benutzer anzeigen
- `python TOOLS.py user:create -u USERNAME -e EMAIL -p PASSWORD [-a]` - Neuen Benutzer erstellen
- `python TOOLS.py user:admin` - Admin-Benutzer erstellen (admin/admin)
- `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD` - Benutzerpasswort zurücksetzen
- `python TOOLS.py user:delete -u USERNAME` - Benutzer löschen
### Manuelle Installation
#### Serververwaltung
- `python TOOLS.py server:run [--host HOST] [--port PORT] [--no-debug]` - Entwicklungsserver starten
1. Repository klonen:
```
git clone <repository-url>
```
Für detaillierte Hilfe: `python TOOLS.py -h`
2. Python-Abhängigkeiten installieren:
```
cd website
pip install -r requirements.txt
```
## Roadmap der Überarbeitung
3. Environment-Variablen konfigurieren:
```
cp example.env .env
```
Bearbeite die `.env`-Datei und füge deinen OpenAI API-Schlüssel ein.
### Phase 1: Grundlegende Infrastruktur ✅
- [x] Bestandsaufnahme des aktuellen Projekts
- [x] Erstellung der Roadmap
- [x] Aktualisierung der Abhängigkeiten
- [x] Integration von Tailwind CSS
- [x] Einrichtung der SVG-Bibliotheken (D3.js)
- [x] Favicon erstellen
- [x] Setup-Skript für einfache Installation
4. CSS mit Tailwind erstellen:
```
python build_css.py
```
### Phase 2: Design-Überarbeitung 🔄
- [x] Implementierung des Dark Mode
- [x] Erstellung eines modernen, minimalistischen UI mit Tech-Ästhetik
- [x] Responsive Design für alle Geräte
- [ ] Gestaltung der Landing Page mit großer Typografie
5. Datenbank initialisieren:
```
python init_db.py
```
### Phase 3: Mindmap-Funktionalitäten 🔄
- [x] Verbesserte Visualisierung mit SVG und D3.js
- [x] Implementierung der Mouseover-Funktion
- [x] Entwicklung der Suchfunktion für Knoten
- [ ] Tagging-System für Inhalte
- [ ] Quellenmanagement und -verlinkung
- [ ] Upload-Funktionalität an Knotenpunkten
6. Anwendung starten:
```
python run.py
```
### Phase 4: Kernseitenentwicklung
- [ ] Überarbeitung der Startseite mit neuen Features
- [ ] Entwicklung der "Wer sind wir?"-Seite
- [ ] Implementierung von Impressum und Datenschutzerklärung
- [ ] Erstellung der Kontaktseite mit FAQs
- [ ] Überarbeitung des Benutzerprofilbereichs
## Entwicklung
### Phase 5: Community-Features
- [ ] Entwicklung des Autorenbereichs
- [ ] Implementierung von Community-Bereichen für Themenbereiche
- [ ] Verbesserter Kommentarbereich
- [ ] Benutzerrechtemanagement
Für die Entwicklung mit automatischem CSS-Reload:
### Phase 6: KI-Integration
- [ ] Implementierung des Frage-Antwort-Systems
- [ ] KI-generierte Themeneinleitungen
- [ ] Intelligente Suchunterstützung
- [ ] Geführte Pfade durch Themenbereiche
- [ ] Vorgeschlagene Chat-Möglichkeiten
```
python dev.py
```
### Phase 7: Benutzerprofilfunktionen
- [ ] Speichern von Thematiken
- [ ] Persönliche Mindmap/Pinboard
- [ ] Beitragsmanagement
- [ ] Benutzerstatistiken und -aktivitäten
Dieser Befehl startet sowohl den Flask-Server als auch den Tailwind CSS-Watcher, der CSS bei Änderungen automatisch neu generiert.
### Phase 8: Testing und Optimierung
- [ ] Umfassende Tests aller Funktionen
- [ ] Performance-Optimierung
- [ ] SEO-Implementierung
- [ ] Barrierefreiheit prüfen und verbessern
## Verwendung des KI-Assistenten
### Phase 9: Dokumentation und Einführung
- [ ] Erstellung von Benutzeranleitungen
- [ ] Entwicklerdokumentation
- [ ] Administratorenhandbuch
- [ ] Guided Tour für neue Benutzer
Der KI-Assistent ist über folgende Wege zugänglich:
## Aktueller Status
- **Phase 1**: ✅ Abgeschlossen
- **Phase 2**: 🔄 In Bearbeitung (75% abgeschlossen)
- **Phase 3**: 🔄 In Bearbeitung (50% abgeschlossen)
1. **Schwebende Schaltfläche**: In der unteren rechten Ecke der Webseite ist eine Roboter-Schaltfläche, die den Assistenten öffnet.
2. **Navigation**: In der Hauptnavigation gibt es ebenfalls eine Schaltfläche mit Roboter-Symbol.
3. **Startseite**: Im "KI-Assistent"-Abschnitt auf der Startseite gibt es einen "KI-Chat starten"-Button.
## Aktuelle Fortschritte
- Grundlegende UI modernisiert mit Tailwind CSS und Dark Mode
- Neues Favicon für bessere visuelle Identität erstellt
- Setup-Prozess vereinfacht mit einem Shell-Skript
- Mindmap-Visualisierung komplett überarbeitet mit D3.js für eine interaktivere Erfahrung
- Responsive Design für optimale Darstellung auf allen Geräten
Der Assistent kann bei folgenden Aufgaben helfen:
## Nächste Schritte
- Fertigstellung der Landing Page
- Erstellung der "Wer sind wir?"-Seite
- Implementierung des Tagging-Systems für Gedanken
- Verbesserung der Gedankenansicht im Mindmap-Bereich
- Erklärung von Themen und Konzepten
- Suche nach Verbindungen zwischen Gedanken
- Beantwortung von Fragen zur Plattform
- Vorschläge für neue Gedankenverbindungen
## Technologie-Stack
- **Backend**: Flask, SQLAlchemy
- **Frontend**: HTML, CSS, JavaScript, Tailwind CSS (ohne npm), Alpine.js
- **KI**: OpenAI GPT API
- **Datenbank**: SQLite (Standard), kann auf andere Datenbanken umgestellt werden
## Konfiguration
Die Anwendung kann über Umgebungsvariablen konfiguriert werden. Siehe `example.env` für verfügbare Optionen.
*Zuletzt aktualisiert: 01.06.2024*

View File

@@ -41,7 +41,7 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=365) # Langlebige Session für Dark Mode-Einstellung
# OpenAI API-Konfiguration
client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
client = OpenAI(api_key="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")
# Context processor für globale Template-Variablen
@app.context_processor
@@ -202,7 +202,7 @@ def settings():
current_user.set_password(new_password)
db.session.commit()
flash('Passwort erfolgreich aktualisiert!', 'success')
return redirect(url_for('settings'))
return render_template('settings.html')
@@ -993,7 +993,7 @@ def chat_with_assistant():
return jsonify({
'response': answer
})
except Exception as e:
return jsonify({
'error': f'Fehler bei der OpenAI-Anfrage: {str(e)}'
@@ -1108,8 +1108,8 @@ def create_default_categories():
for child_data in children_data:
child = Category(**child_data, parent_id=category.id)
db.session.add(child)
db.session.commit()
db.session.commit()
print("Standard-Kategorien wurden erstellt!")
def initialize_database():
@@ -1145,10 +1145,24 @@ def my_account():
@app.route('/static/network-bg.jpg')
@app.route('/static/network-bg.svg')
def dummy_network_bg():
"""Leere Antwort für die nicht mehr verwendeten Netzwerk-Hintergrundbilder."""
return '', 200
"""Stellt einen Fallback für alte Netzwerk-Hintergrund-Anfragen bereit."""
return redirect(url_for('static', filename='img/backgrounds/network-bg.jpg'))
@app.route('/static/css/src/cybernetwork-bg.css')
def serve_cybernetwork_css():
"""Stellt das CSS für den cybertechnischen Netzwerk-Hintergrund bereit."""
return app.send_static_file('css/src/cybernetwork-bg.css')
@app.route('/static/js/modules/cyber-network.js')
def serve_cybernetwork_js():
"""Stellt das JavaScript-Modul für den cybertechnischen Netzwerk-Hintergrund bereit."""
return app.send_static_file('js/modules/cyber-network.js')
@app.route('/static/js/modules/cyber-network-init.js')
def serve_cybernetwork_init_js():
"""Stellt das Initialisierungs-JavaScript für den cybertechnischen Netzwerk-Hintergrund bereit."""
return app.send_static_file('js/modules/cyber-network-init.js')
# Route zum expliziten Neu-Laden der Umgebungsvariablen
@app.route('/admin/reload-env', methods=['POST'])
@admin_required
def reload_env():

View File

@@ -1,33 +0,0 @@
@echo off
echo Copying network image to website/static/network-bg.jpg...
if not exist "website\static" (
echo Error: website/static directory does not exist.
echo Make sure you are running this script from the main project directory.
pause
exit /b 1
)
if "%~1"=="" (
echo Usage: copy-network-image.bat [path_to_image]
echo Example: copy-network-image.bat d2efd014-1325-471f-b9a7-90d025eb81d6.png
pause
exit /b 1
)
if not exist "%~1" (
echo Error: The specified image file "%~1" does not exist.
pause
exit /b 1
)
copy /Y "%~1" "website\static\network-bg.jpg" > nul
if %errorlevel% equ 0 (
echo Success! The image has been copied to website/static/network-bg.jpg
echo Please restart the Flask server to see the changes.
) else (
echo Error: Failed to copy the image.
)
pause

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
import shutil
from pathlib import Path
def main():
"""Deploy the website on a server"""
print("Deploying the website on the server...")
# Get the directory where deploy.py is located (project root)
project_root = Path(__file__).resolve().parent
website_dir = project_root / "website"
# Check if virtual environment exists, create if not
venv_dir = project_root / "venv"
if not venv_dir.exists():
print("Creating virtual environment...")
subprocess.run([sys.executable, "-m", "venv", str(venv_dir)], check=True)
# Determine Python and pip paths based on OS
if os.name == 'nt': # Windows
python = venv_dir / "Scripts" / "python"
pip = venv_dir / "Scripts" / "pip"
else: # Unix-like
python = venv_dir / "bin" / "python"
pip = venv_dir / "bin" / "pip"
# Install dependencies
print("Installing dependencies...")
subprocess.run([str(pip), "install", "-r", str(website_dir / "requirements.txt")], check=True)
subprocess.run([str(pip), "install", "gunicorn"], check=True)
# Build CSS
print("Building CSS with Tailwind...")
subprocess.run([str(python), str(website_dir / "build_css.py")], check=True)
# Create a systemd service file
service_file = """[Unit]
Description=MindMap Wissensnetzwerk
After=network.target
[Service]
User=www-data
WorkingDirectory={website_dir}
Environment="PATH={venv_bin}"
ExecStart={gunicorn} --workers 3 --bind 0.0.0.0:5000 --log-level info 'run:app'
Restart=always
[Install]
WantedBy=multi-user.target
""".format(
website_dir=website_dir,
venv_bin=venv_dir / "bin",
gunicorn=venv_dir / "bin" / "gunicorn"
)
service_path = project_root / "mindmap.service"
with open(service_path, 'w') as f:
f.write(service_file)
print(f"""
Deployment files created!
To install the service on a Linux server:
1. Copy the systemd service file:
sudo cp {service_path} /etc/systemd/system/
2. Reload systemd:
sudo systemctl daemon-reload
3. Enable and start the service:
sudo systemctl enable mindmap.service
sudo systemctl start mindmap.service
4. Check service status:
sudo systemctl status mindmap.service
Alternatively, you can run the application with gunicorn manually:
cd {website_dir}
{venv_dir}/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 'run:app'
""")
if __name__ == "__main__":
main()

View File

@@ -1,60 +0,0 @@
#!/bin/bash
# Farben für Ausgaben
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${GREEN}==== MindMap Projekt Server Deployment ====${NC}"
# Python-Umgebung erstellen
echo -e "${BLUE}Erstelle Python-Umgebung...${NC}"
python3 -m venv venv
source venv/bin/activate
# Python-Abhängigkeiten installieren
echo -e "${BLUE}Installiere Python-Abhängigkeiten...${NC}"
pip install -r website/requirements.txt
pip install gunicorn
# Tailwind CSS kompilieren
echo -e "${BLUE}Kompiliere Tailwind CSS...${NC}"
cd website
python build_css.py
cd ..
# Datenbank initialisieren, falls noch nicht vorhanden
echo -e "${BLUE}Initialisiere die Datenbank, falls nötig...${NC}"
cd website
python init_db.py
cd ..
# Systemd Service erstellen
echo -e "${BLUE}Erstelle Systemd Service...${NC}"
SERVICE_FILE="[Unit]
Description=MindMap Wissensnetzwerk
After=network.target
[Service]
User=$(whoami)
WorkingDirectory=$(pwd)/website
Environment=\"PATH=$(pwd)/venv/bin\"
ExecStart=$(pwd)/venv/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 --log-level info 'run:app'
Restart=always
[Install]
WantedBy=multi-user.target"
echo "$SERVICE_FILE" > mindmap.service
echo -e "${GREEN}==== Deployment abgeschlossen ====${NC}"
echo -e "${BLUE}Um den Service zu installieren, führe folgende Befehle aus:${NC}"
echo -e "sudo cp mindmap.service /etc/systemd/system/"
echo -e "sudo systemctl daemon-reload"
echo -e "sudo systemctl enable mindmap.service"
echo -e "sudo systemctl start mindmap.service"
echo -e ""
echo -e "${BLUE}Alternativ kannst du den Server manuell starten:${NC}"
echo -e "cd website"
echo -e "../venv/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 'run:app'"

View File

@@ -1,7 +0,0 @@
version: "3.9"
services:
web:
build: .
ports:
- "5000:5000"
restart: always

View File

@@ -1,53 +0,0 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
from pathlib import Path
def main():
"""Set up the project from the parent directory"""
print("Setting up the project...")
# Get the directory where setup.py is located (project root)
project_root = Path(__file__).resolve().parent
website_dir = project_root / "website"
# Check if virtual environment exists, create if not
venv_dir = project_root / "venv"
if not venv_dir.exists():
print("Creating virtual environment...")
subprocess.run([sys.executable, "-m", "venv", str(venv_dir)], check=True)
# Determine pip path based on OS
if os.name == 'nt': # Windows
pip = venv_dir / "Scripts" / "pip"
else: # Unix-like
pip = venv_dir / "bin" / "pip"
# Install dependencies
print("Installing dependencies...")
subprocess.run([str(pip), "install", "-r", str(website_dir / "requirements.txt")], check=True)
# Build CSS
print("Building CSS with Tailwind...")
if os.name == 'nt': # Windows
python = venv_dir / "Scripts" / "python"
else: # Unix-like
python = venv_dir / "bin" / "python"
subprocess.run([str(python), str(website_dir / "build_css.py")], check=True)
print("""
Setup completed successfully!
To run the development server:
1. Activate the virtual environment:
- Windows: .\\venv\\Scripts\\activate
- Unix/MacOS: source venv/bin/activate
2. Run the Flask application:
- cd website
- python run.py
""")
if __name__ == "__main__":
main()

108
static/background.js Normal file
View File

@@ -0,0 +1,108 @@
// Background animation with Three.js
let scene, camera, renderer, stars = [];
function initBackground() {
// Setup scene
scene = new THREE.Scene();
// Setup camera
camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 100;
// Setup renderer
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000, 0); // Transparent background
// Append renderer to DOM
const backgroundContainer = document.getElementById('background-container');
if (backgroundContainer) {
backgroundContainer.appendChild(renderer.domElement);
}
// Add stars
for (let i = 0; i < 1000; i++) {
const geometry = new THREE.SphereGeometry(0.2, 8, 8);
const material = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: Math.random() * 0.5 + 0.1 });
const star = new THREE.Mesh(geometry, material);
// Random position
star.position.x = Math.random() * 600 - 300;
star.position.y = Math.random() * 600 - 300;
star.position.z = Math.random() * 600 - 300;
// Store reference to move in animation
star.velocity = Math.random() * 0.02 + 0.005;
stars.push(star);
scene.add(star);
}
// Add large glowing particles
for (let i = 0; i < 15; i++) {
const size = Math.random() * 5 + 2;
const geometry = new THREE.SphereGeometry(size, 16, 16);
// Create a glowing material
const color = new THREE.Color();
color.setHSL(Math.random(), 0.7, 0.5); // Random hue
const material = new THREE.MeshBasicMaterial({
color: color,
transparent: true,
opacity: 0.2
});
const particle = new THREE.Mesh(geometry, material);
// Random position but further away
particle.position.x = Math.random() * 1000 - 500;
particle.position.y = Math.random() * 1000 - 500;
particle.position.z = Math.random() * 200 - 400;
// Store reference to move in animation
particle.velocity = Math.random() * 0.01 + 0.002;
stars.push(particle);
scene.add(particle);
}
// Handle window resize
window.addEventListener('resize', onWindowResize);
// Start animation
animate();
}
function animate() {
requestAnimationFrame(animate);
// Move stars
stars.forEach(star => {
star.position.z += star.velocity;
// Reset position if star moves too close
if (star.position.z > 100) {
star.position.z = -300;
}
});
// Rotate the entire scene slightly for a dreamy effect
scene.rotation.y += 0.0003;
scene.rotation.x += 0.0001;
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Initialize background when the DOM is loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initBackground);
} else {
initBackground();
}

1
static/background.mp4 Normal file
View File

@@ -0,0 +1 @@
C:\Users\firem\Downloads\background.mp4

426
static/css/base-styles.css Normal file
View File

@@ -0,0 +1,426 @@
/* Globale Variablen */
:root {
--dark-bg: #0e1220;
--dark-card-bg: rgba(24, 28, 45, 0.8);
--dark-element-bg: rgba(24, 28, 45, 0.8);
--light-bg: #f0f4f8;
--light-card-bg: rgba(255, 255, 255, 0.85);
--accent-color: #b38fff;
--accent-gradient: linear-gradient(135deg, #b38fff, #58a9ff);
--accent-gradient-hover: linear-gradient(135deg, #c7a8ff, #70b5ff);
--blur-amount: 20px;
--border-radius: 28px;
--card-border-radius: 24px;
--button-radius: 18px;
--nav-item-radius: 14px;
}
/* Dark Mode Einstellungen */
html.dark {
color-scheme: dark;
}
/* Base Styles */
html, body {
background-color: var(--dark-bg) !important;
min-height: 100vh;
width: 100%;
color: #ffffff;
margin: 0;
padding: 0;
font-family: 'Inter', system-ui, -apple-system, sans-serif;
overflow-x: hidden;
transition: background-color 0.5s ease, color 0.5s ease;
}
/* Sicherstellen, dass der dunkle Hintergrund die gesamte Seite abdeckt */
#app-container, .container, main, .mx-auto, .py-12, #content-wrapper {
background-color: transparent !important;
width: 100%;
}
/* Light Mode Einstellungen */
html.light, html.light body {
background-color: var(--light-bg) !important;
color: #1a202c;
}
/* Große Headings mit verbesserten Stilen */
h1.hero-heading {
font-size: clamp(2.5rem, 8vw, 5rem);
line-height: 1.1;
font-weight: 800;
letter-spacing: -0.03em;
margin-bottom: 1.5rem;
}
h2.section-heading {
font-size: clamp(1.75rem, 5vw, 3rem);
line-height: 1.2;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 1.25rem;
}
/* Verbesserte Glasmorphismus-Stile */
.glass-morphism {
background: var(--dark-card-bg);
backdrop-filter: blur(var(--blur-amount));
-webkit-backdrop-filter: blur(var(--blur-amount));
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: var(--card-border-radius);
box-shadow: 0 12px 36px rgba(0, 0, 0, 0.35);
transition: all 0.3s ease;
}
.glass-morphism:hover {
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.45);
transform: translateY(-2px);
}
.glass-morphism-light {
background: var(--light-card-bg);
backdrop-filter: blur(var(--blur-amount));
-webkit-backdrop-filter: blur(var(--blur-amount));
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: var(--card-border-radius);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
transition: all 0.3s ease;
}
.glass-morphism-light:hover {
border: 1px solid rgba(0, 0, 0, 0.15);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.18);
transform: translateY(-2px);
}
/* Verbesserte Navbar-Styles */
.glass-navbar-dark {
background: rgba(14, 18, 32, 0.85);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
border-radius: 0 0 20px 20px;
}
.glass-navbar-light {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
border-color: rgba(0, 0, 0, 0.05);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
border-radius: 0 0 20px 20px;
}
/* Verbesserte Button-Stile mit besserer Lesbarkeit und stärkeren Farbverläufen */
.btn, button, .button, [type="button"], [type="submit"] {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: var(--button-radius);
padding: 0.75rem 1.5rem;
font-weight: 600;
letter-spacing: 0.4px;
color: rgba(255, 255, 255, 1);
background: linear-gradient(135deg, rgba(99, 102, 241, 0.8), rgba(168, 85, 247, 0.8));
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
position: relative;
overflow: hidden;
outline: none;
}
.btn:hover, button:hover, .button:hover, [type="button"]:hover, [type="submit"]:hover {
background: linear-gradient(135deg, rgba(129, 140, 248, 0.9), rgba(192, 132, 252, 0.9));
transform: translateY(-3px);
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.25), 0 0 12px rgba(179, 143, 255, 0.35);
color: white;
}
.btn:active, button:active, .button:active, [type="button"]:active, [type="submit"]:active {
transform: translateY(1px);
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
}
/* Navigation Stile mit verbesserten Farbverläufen */
.nav-link {
transition: all 0.25s ease;
border-radius: var(--nav-item-radius);
padding: 0.625rem 1rem;
font-weight: 500;
letter-spacing: 0.3px;
color: rgba(255, 255, 255, 0.9);
position: relative;
overflow: visible;
}
.nav-link:hover {
background: rgba(179, 143, 255, 0.2);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.nav-link-active {
background: linear-gradient(135deg, rgba(124, 58, 237, 0.3), rgba(139, 92, 246, 0.3));
color: white;
font-weight: 600;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
}
.nav-link-active::after {
content: '';
position: absolute;
bottom: 0;
left: 10%;
width: 80%;
height: 2px;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.7), transparent);
}
/* Light-Mode Navigation Stile */
.nav-link-light {
color: rgba(26, 32, 44, 0.85);
}
.nav-link-light:hover {
background: rgba(179, 143, 255, 0.15);
color: rgba(26, 32, 44, 1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.nav-link-light-active {
background: linear-gradient(135deg, rgba(124, 58, 237, 0.2), rgba(139, 92, 246, 0.2));
color: rgba(26, 32, 44, 1);
font-weight: 600;
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1);
}
.nav-link-light-active::after {
background: linear-gradient(90deg, transparent, rgba(26, 32, 44, 0.5), transparent);
}
/* Entfernung von Gradient-Hintergrund überall */
.gradient-bg, .purple-gradient, .gradient-purple-bg {
background: var(--dark-bg) !important;
background-image: none !important;
}
/* Verbesserte Light-Mode-Stile für Buttons */
html.light .btn, html.light button, html.light .button,
html.light [type="button"], html.light [type="submit"] {
background: linear-gradient(135deg, rgba(124, 58, 237, 0.7), rgba(139, 92, 246, 0.7));
color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
text-shadow: none;
}
html.light .btn:hover, html.light button:hover, html.light .button:hover,
html.light [type="button"]:hover, html.light [type="submit"]:hover {
background: linear-gradient(135deg, rgba(139, 92, 246, 0.85), rgba(168, 85, 247, 0.85));
color: #ffffff;
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 0 8px 22px rgba(0, 0, 0, 0.12), 0 0 12px rgba(179, 143, 255, 0.2);
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
/* Verbesserte Buttons mit Glasmorphismus */
.btn-primary {
background: linear-gradient(135deg, rgba(179, 143, 255, 0.8), rgba(88, 169, 255, 0.8));
backdrop-filter: blur(var(--blur-amount));
-webkit-backdrop-filter: blur(var(--blur-amount));
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: var(--button-radius);
color: white !important;
font-weight: 600;
padding: 0.75rem 1.5rem;
transition: all 0.3s ease;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
background: linear-gradient(135deg, rgba(190, 160, 255, 0.9), rgba(100, 180, 255, 0.9));
}
.btn-secondary {
background: rgba(32, 36, 55, 0.8);
backdrop-filter: blur(var(--blur-amount));
-webkit-backdrop-filter: blur(var(--blur-amount));
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: var(--button-radius);
color: white;
font-weight: 500;
padding: 0.75rem 1.5rem;
transition: all 0.3s ease;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
}
.btn-secondary:hover {
transform: translateY(-3px);
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.3);
background: rgba(38, 42, 65, 0.9);
border: 1px solid rgba(255, 255, 255, 0.15);
}
/* Steuerungsbutton-Stil */
.control-btn {
padding: 0.5rem 1rem;
background: rgba(32, 36, 55, 0.8);
color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 14px;
font-size: 0.875rem;
font-weight: 500;
transition: all 0.25s ease;
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
}
.control-btn:hover {
background: rgba(38, 42, 65, 0.9);
border: 1px solid rgba(255, 255, 255, 0.2);
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.25);
}
/* Verbesserter Farbverlauf-Text */
.gradient-text {
background: linear-gradient(135deg, rgba(200, 170, 255, 1), rgba(100, 180, 255, 1));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 700;
letter-spacing: -0.02em;
text-shadow: 0 2px 10px rgba(179, 143, 255, 0.3);
filter: drop-shadow(0 2px 6px rgba(179, 143, 255, 0.3));
}
/* Globaler Hintergrund */
.full-page-bg {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: var(--dark-bg);
z-index: -10;
}
html.light .full-page-bg {
background-color: var(--light-bg);
}
/* Animationen für Hintergrundeffekte */
@keyframes float {
0% { transform: translateY(0); }
50% { transform: translateY(-12px); }
100% { transform: translateY(0); }
}
@keyframes pulse {
0% { opacity: 0.7; transform: scale(1); }
50% { opacity: 1; transform: scale(1.05); }
100% { opacity: 0.7; transform: scale(1); }
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
.animate-pulse {
animation: pulse 3s ease-in-out infinite;
}
/* Verbesserter Container für konsistente Layouts */
.page-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
}
/* Dark Mode Toggle Stile */
.dot {
transform: translateX(0);
transition: transform 0.3s ease-in-out, background-color 0.3s ease;
}
input:checked ~ .dot {
transform: translateX(100%);
background-color: #58a9ff;
}
input:checked ~ .block {
background-color: rgba(88, 169, 255, 0.4);
}
/* Feature Cards mit Glasmorphismus und Farbverlauf */
.feature-card {
border-radius: var(--card-border-radius);
padding: 2rem;
transition: all 0.3s ease;
background: linear-gradient(145deg, rgba(32, 36, 55, 0.7), rgba(24, 28, 45, 0.9));
backdrop-filter: blur(var(--blur-amount));
-webkit-backdrop-filter: blur(var(--blur-amount));
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
.feature-card:hover {
transform: translateY(-5px);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.15);
background: linear-gradient(145deg, rgba(40, 44, 65, 0.8), rgba(28, 32, 50, 0.95));
}
html.light .feature-card {
background: linear-gradient(145deg, rgba(255, 255, 255, 0.8), rgba(240, 240, 250, 0.9));
border: 1px solid rgba(0, 0, 0, 0.05);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
html.light .feature-card:hover {
background: linear-gradient(145deg, rgba(255, 255, 255, 0.9), rgba(245, 245, 255, 0.95));
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.12);
}
.feature-card .icon {
width: 60px;
height: 60px;
border-radius: 18px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
margin-bottom: 1.25rem;
background: linear-gradient(135deg, rgba(124, 58, 237, 0.8), rgba(139, 92, 246, 0.6));
color: white;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
.feature-card h3 {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.75rem;
color: rgba(255, 255, 255, 0.95);
}
html.light .feature-card h3 {
color: rgba(26, 32, 44, 0.95);
}
.feature-card p {
color: rgba(255, 255, 255, 0.75);
line-height: 1.6;
}
html.light .feature-card p {
color: rgba(26, 32, 44, 0.75);
}

View File

@@ -0,0 +1,125 @@
/* Cybertechnisches Netzwerk Hintergrund-Overlay */
.cyber-network-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
pointer-events: none;
overflow: hidden;
}
.cyber-network-bg::before {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: linear-gradient(125deg,
rgba(14, 14, 22, 0.95) 0%,
rgba(30, 30, 46, 0.98) 100%);
}
.network-grid {
position: absolute;
width: 200%;
height: 200%;
top: -50%;
left: -50%;
background-size: 40px 40px;
background-image:
linear-gradient(to right, rgba(108, 93, 211, 0.05) 1px, transparent 1px),
linear-gradient(to bottom, rgba(108, 93, 211, 0.05) 1px, transparent 1px);
transform: perspective(500px) rotateX(60deg);
animation: grid-move 20s linear infinite;
}
.node {
position: absolute;
width: 4px;
height: 4px;
background: rgba(76, 223, 255, 0.8);
border-radius: 50%;
box-shadow: 0 0 10px rgba(76, 223, 255, 0.6);
filter: blur(1px);
}
.connection {
position: absolute;
height: 1px;
background: linear-gradient(90deg,
rgba(76, 223, 255, 0.2) 0%,
rgba(108, 93, 211, 0.3) 50%,
rgba(76, 223, 255, 0.2) 100%);
transform-origin: left center;
animation: pulse 4s infinite;
}
.data-packet {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
background: rgba(118, 69, 217, 0.8);
filter: blur(1px);
animation: travel var(--travel-time, 6s) linear infinite;
}
.glow-overlay {
position: absolute;
width: 100%;
height: 100%;
background: radial-gradient(
circle at 50% 40%,
rgba(76, 223, 255, 0.03) 0%,
rgba(108, 93, 211, 0.03) 45%,
transparent 70%
);
opacity: 0.8;
animation: pulse-glow 8s infinite;
}
/* Animations */
@keyframes grid-move {
0% {
transform: perspective(500px) rotateX(60deg) translateY(0);
}
100% {
transform: perspective(500px) rotateX(60deg) translateY(40px);
}
}
@keyframes pulse {
0%, 100% {
opacity: 0.2;
}
50% {
opacity: 0.4;
}
}
@keyframes travel {
0% {
transform: translateX(0) translateY(0);
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
transform: translateX(var(--travel-x, 100px)) translateY(var(--travel-y, 100px));
opacity: 0;
}
}
@keyframes pulse-glow {
0%, 100% {
opacity: 0.4;
}
50% {
opacity: 0.8;
}
}

View File

@@ -1434,11 +1434,16 @@ html, body {
overflow-x: hidden;
background: linear-gradient(135deg, var(--background-start), var(--background-end));
background-attachment: fixed;
scroll-behavior: smooth;
height: 100%;
}
/* Sticky navbar */
.navbar.sticky-top {
position: sticky;
top: 0;
z-index: 1000;
}
z-index: 50;
}
/* Importiere das Cyber-Network CSS */
@import url('/static/css/src/cybernetwork-bg.css');

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,95 @@
/**
* Initialisierungsmodul für den CyberNetwork-Hintergrund
* Importiert und startet die Animation
*/
import CyberNetwork from './cyber-network.js';
// Beim Laden des Dokuments starten
document.addEventListener('DOMContentLoaded', () => {
console.log('CyberNetwork: Initialisierung gestartet');
// Prüfen ob das CSS bereits geladen ist, wenn nicht, dann laden
if (!document.querySelector('link[href*="cybernetwork-bg.css"]')) {
console.log('CyberNetwork: CSS wird geladen');
const cyberNetworkCss = document.createElement('link');
cyberNetworkCss.rel = 'stylesheet';
cyberNetworkCss.href = '/static/css/src/cybernetwork-bg.css';
document.head.appendChild(cyberNetworkCss);
}
// Container-Element für das Netzwerk finden
const container = document.getElementById('cyber-background-container');
if (!container) {
console.error('CyberNetwork: Container #cyber-background-container nicht gefunden!');
return;
}
console.log('CyberNetwork: Container gefunden', container);
// Konfiguration für den Netzwerk-Hintergrund
const networkConfig = {
container: container,
nodeCount: window.innerWidth < 768 ? 15 : 30, // Weniger Nodes auf mobilen Geräten
connectionCount: window.innerWidth < 768 ? 25 : 50,
packetCount: window.innerWidth < 768 ? 8 : 15,
animationSpeed: 1.0
};
// Netzwerk erstellen und initialisieren
const cyberNetwork = new CyberNetwork(networkConfig);
cyberNetwork.init();
console.log('CyberNetwork: Netzwerk initialisiert');
// Globale Referenz für Debug-Zwecke
window.cyberNetwork = cyberNetwork;
});
// Funktion zum manuellen Initialisieren, falls notwendig
export function initCyberNetwork(config = {}) {
console.log('CyberNetwork: Manuelle Initialisierung gestartet');
// CSS laden, falls nicht vorhanden
if (!document.querySelector('link[href*="cybernetwork-bg.css"]')) {
console.log('CyberNetwork: CSS wird geladen (manuell)');
const cyberNetworkCss = document.createElement('link');
cyberNetworkCss.rel = 'stylesheet';
cyberNetworkCss.href = '/static/css/src/cybernetwork-bg.css';
document.head.appendChild(cyberNetworkCss);
}
// Container-Element für das Netzwerk finden
const container = document.getElementById('cyber-background-container');
if (!container) {
console.error('CyberNetwork: Container #cyber-background-container nicht gefunden!');
return null;
}
// Bestehende Instanz zurücksetzen, falls vorhanden
if (window.cyberNetwork) {
console.log('CyberNetwork: Bestehende Instanz wird zurückgesetzt');
window.cyberNetwork.reset();
}
// Netzwerk mit benutzerdefinierten Optionen erstellen
const networkConfig = {
container: container,
nodeCount: window.innerWidth < 768 ? 15 : 30,
connectionCount: window.innerWidth < 768 ? 25 : 50,
packetCount: window.innerWidth < 768 ? 8 : 15,
animationSpeed: 1.0,
...config
};
// Neue Instanz erstellen und initialisieren
const cyberNetwork = new CyberNetwork(networkConfig);
cyberNetwork.init();
console.log('CyberNetwork: Netzwerk manuell initialisiert');
// Globale Referenz aktualisieren
window.cyberNetwork = cyberNetwork;
return cyberNetwork;
}

View File

@@ -0,0 +1,240 @@
/**
* Cyber Network Background Animation
* Generiert dynamisch ein animiertes Netzwerk für den Hintergrund
*/
class CyberNetwork {
constructor(options = {}) {
this.options = {
container: options.container || document.body,
nodeCount: options.nodeCount || 30,
connectionCount: options.connectionCount || 50,
packetCount: options.packetCount || 15,
animationSpeed: options.animationSpeed || 1.0,
...options
};
this.nodes = [];
this.connections = [];
this.packets = [];
this.initialized = false;
this.containerElement = null;
this.networkGridElement = null;
this.glowOverlayElement = null;
}
init() {
if (this.initialized) return;
// Container erstellen
this.containerElement = document.createElement('div');
this.containerElement.className = 'cyber-network-bg';
// Grid erstellen
this.networkGridElement = document.createElement('div');
this.networkGridElement.className = 'network-grid';
this.containerElement.appendChild(this.networkGridElement);
// Glow Overlay erstellen
this.glowOverlayElement = document.createElement('div');
this.glowOverlayElement.className = 'glow-overlay';
this.containerElement.appendChild(this.glowOverlayElement);
// Nodes generieren
this.generateNodes();
// Connections generieren
this.generateConnections();
// Data packets generieren
this.generateDataPackets();
// Container zum DOM hinzufügen
if (typeof this.options.container === 'string') {
const container = document.querySelector(this.options.container);
if (container) {
container.appendChild(this.containerElement);
} else {
document.body.appendChild(this.containerElement);
}
} else {
this.options.container.appendChild(this.containerElement);
}
this.initialized = true;
// Animation starten
window.addEventListener('resize', this.handleResize.bind(this));
this.startAnimationCycle();
}
generateNodes() {
const containerWidth = window.innerWidth;
const containerHeight = window.innerHeight;
for (let i = 0; i < this.options.nodeCount; i++) {
const x = Math.random() * containerWidth;
const y = Math.random() * containerHeight;
const node = document.createElement('div');
node.className = 'node';
node.style.left = `${x}px`;
node.style.top = `${y}px`;
// Größen-Variation für visuelle Tiefe
const size = 2 + Math.random() * 4;
node.style.width = `${size}px`;
node.style.height = `${size}px`;
// Speichern der Position für spätere Referenz
node._data = { x, y, size };
this.containerElement.appendChild(node);
this.nodes.push(node);
}
}
generateConnections() {
for (let i = 0; i < this.options.connectionCount; i++) {
// Zufällige Nodes auswählen
const startNodeIndex = Math.floor(Math.random() * this.nodes.length);
let endNodeIndex;
do {
endNodeIndex = Math.floor(Math.random() * this.nodes.length);
} while (endNodeIndex === startNodeIndex);
const startNode = this.nodes[startNodeIndex];
const endNode = this.nodes[endNodeIndex];
const startData = startNode._data;
const endData = endNode._data;
// Verbindung erstellen
const connection = document.createElement('div');
connection.className = 'connection';
// Position und Rotation berechnen
const dx = endData.x - startData.x;
const dy = endData.y - startData.y;
const length = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
connection.style.width = `${length}px`;
connection.style.left = `${startData.x}px`;
connection.style.top = `${startData.y}px`;
connection.style.transform = `rotate(${angle}deg)`;
// Variation in der Animations-Geschwindigkeit
connection.style.animationDuration = `${3 + Math.random() * 4}s`;
// Speichern der verbundenen Nodes
connection._data = {
startNode: startNodeIndex,
endNode: endNodeIndex,
length
};
this.containerElement.appendChild(connection);
this.connections.push(connection);
}
}
generateDataPackets() {
for (let i = 0; i < this.options.packetCount; i++) {
this.createNewDataPacket();
}
}
createNewDataPacket() {
if (this.connections.length === 0) return;
// Zufällige Verbindung auswählen
const connectionIndex = Math.floor(Math.random() * this.connections.length);
const connection = this.connections[connectionIndex];
const connectionData = connection._data;
const startNode = this.nodes[connectionData.startNode];
const startData = startNode._data;
// Data Packet erstellen
const packet = document.createElement('div');
packet.className = 'data-packet';
// Position auf dem Startknoten
packet.style.left = `${startData.x}px`;
packet.style.top = `${startData.y}px`;
// Zufällige Geschwindigkeit
const travelTime = (4 + Math.random() * 4) / this.options.animationSpeed;
packet.style.setProperty('--travel-time', `${travelTime}s`);
// Ziel-Koordinaten berechnen
const endNode = this.nodes[connectionData.endNode];
const endData = endNode._data;
const travelX = endData.x - startData.x;
const travelY = endData.y - startData.y;
packet.style.setProperty('--travel-x', `${travelX}px`);
packet.style.setProperty('--travel-y', `${travelY}px`);
// Farb-Variation
if (Math.random() > 0.5) {
packet.style.background = 'rgba(76, 223, 255, 0.8)'; // Akzentfarbe
}
this.containerElement.appendChild(packet);
this.packets.push(packet);
// Nach Ende der Animation neues Paket erstellen
setTimeout(() => {
if (this.containerElement.contains(packet)) {
this.containerElement.removeChild(packet);
}
const index = this.packets.indexOf(packet);
if (index > -1) {
this.packets.splice(index, 1);
this.createNewDataPacket();
}
}, travelTime * 1000);
}
handleResize() {
if (!this.initialized) return;
// Bei Größenänderung alles neu generieren
this.reset();
this.init();
}
reset() {
if (!this.initialized) return;
// Alle Elemente entfernen
this.nodes.forEach(node => node.remove());
this.connections.forEach(connection => connection.remove());
this.packets.forEach(packet => packet.remove());
this.nodes = [];
this.connections = [];
this.packets = [];
if (this.containerElement) {
this.containerElement.remove();
}
this.initialized = false;
}
startAnimationCycle() {
// Regelmäßig neue Pakete erstellen für mehr Dynamik
setInterval(() => {
if (this.packets.length < this.options.packetCount * 1.5) {
this.createNewDataPacket();
}
}, 1000 / this.options.animationSpeed);
}
}
// Exportieren als Modul
export default CyberNetwork;

View File

@@ -0,0 +1,88 @@
// Network Animation Effect
document.addEventListener('DOMContentLoaded', function() {
// Check if we're on the mindmap page
const mindmapContainer = document.getElementById('mindmap-container');
if (!mindmapContainer) return;
// Add enhanced animations for links and nodes
setTimeout(function() {
// Get all SVG links (connections between nodes)
const links = document.querySelectorAll('.link');
const nodes = document.querySelectorAll('.node');
// Add animation to links
links.forEach(link => {
// Create random animation duration between 15 and 30 seconds
const duration = 15 + Math.random() * 15;
link.style.animation = `dash ${duration}s linear infinite`;
link.style.strokeDasharray = '5, 5';
// Add pulse effect on hover
link.addEventListener('mouseover', function() {
this.classList.add('highlighted');
this.style.animation = 'dash 5s linear infinite';
});
link.addEventListener('mouseout', function() {
this.classList.remove('highlighted');
this.style.animation = `dash ${duration}s linear infinite`;
});
});
// Add effects to nodes
nodes.forEach(node => {
node.addEventListener('mouseover', function() {
this.querySelector('circle').style.filter = 'drop-shadow(0 0 15px rgba(179, 143, 255, 0.8))';
// Highlight connected links
const nodeId = this.getAttribute('data-id') || this.id;
links.forEach(link => {
const source = link.getAttribute('data-source');
const target = link.getAttribute('data-target');
if (source === nodeId || target === nodeId) {
link.classList.add('highlighted');
link.style.animation = 'dash 5s linear infinite';
}
});
});
node.addEventListener('mouseout', function() {
this.querySelector('circle').style.filter = 'drop-shadow(0 0 8px rgba(179, 143, 255, 0.5))';
// Remove highlight from connected links
const nodeId = this.getAttribute('data-id') || this.id;
links.forEach(link => {
const source = link.getAttribute('data-source');
const target = link.getAttribute('data-target');
if (source === nodeId || target === nodeId) {
link.classList.remove('highlighted');
const duration = 15 + Math.random() * 15;
link.style.animation = `dash ${duration}s linear infinite`;
}
});
});
});
}, 1000); // Wait for the mindmap to be fully loaded
// Add network background effect
const networkBackground = document.createElement('div');
networkBackground.className = 'network-background';
networkBackground.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(179, 143, 255, 0.05);
background-size: cover;
background-position: center;
opacity: 0.15;
z-index: -1;
pointer-events: none;
animation: pulse 10s ease-in-out infinite alternate;
`;
mindmapContainer.appendChild(networkBackground);
});

View File

@@ -0,0 +1,232 @@
// Animated Network Background
let canvas, ctx, networkImage;
let isImageLoaded = false;
let animationSpeed = 0.0003; // Reduzierte Geschwindigkeit für sanftere Rotation
let scaleSpeed = 0.0001; // Reduzierte Geschwindigkeit für sanftere Skalierung
let opacitySpeed = 0.0002; // Reduzierte Geschwindigkeit für sanftere Opazitätsänderung
let rotation = 0;
let scale = 1;
let opacity = 0.7; // Höhere Basisopazität für bessere Sichtbarkeit
let scaleDirection = 1;
let opacityDirection = 1;
let animationFrameId = null;
let isDarkMode = document.documentElement.classList.contains('dark');
let loadAttempts = 0;
const MAX_LOAD_ATTEMPTS = 2;
// Initialize the canvas and load the image
function initNetworkBackground() {
// Create canvas element if it doesn't exist
if (!document.getElementById('network-background')) {
canvas = document.createElement('canvas');
canvas.id = 'network-background';
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.zIndex = '-5'; // Höher als -10 für den full-page-bg
canvas.style.pointerEvents = 'none'; // Stellt sicher, dass der Canvas keine Mausinteraktionen blockiert
document.body.appendChild(canvas);
} else {
canvas = document.getElementById('network-background');
}
// Set canvas size to window size with pixel ratio consideration
resizeCanvas();
// Get context with alpha enabled
ctx = canvas.getContext('2d', { alpha: true });
// Load the network image - versuche zuerst die SVG-Version
networkImage = new Image();
networkImage.crossOrigin = "anonymous"; // Vermeidet CORS-Probleme
// Keine Bilder laden, direkt Fallback-Hintergrund verwenden
console.log("Verwende einfachen Hintergrund ohne Bilddateien");
isImageLoaded = true; // Animation ohne Hintergrundbild starten
startAnimation();
// Handle window resize
window.addEventListener('resize', debounce(resizeCanvas, 250));
// Überwache Dark Mode-Änderungen
document.addEventListener('darkModeToggled', function(event) {
isDarkMode = event.detail.isDark;
});
}
// Hilfsfunktion zur Reduzierung der Resize-Event-Aufrufe
function debounce(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function() {
func.apply(context, args);
}, wait);
};
}
// Resize canvas to match window size with proper pixel ratio
function resizeCanvas() {
if (!canvas) return;
const pixelRatio = window.devicePixelRatio || 1;
const width = window.innerWidth;
const height = window.innerHeight;
// Set display size (css pixels)
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
// Set actual size in memory (scaled for pixel ratio)
canvas.width = width * pixelRatio;
canvas.height = height * pixelRatio;
// Scale context to match pixel ratio
if (ctx) {
ctx.scale(pixelRatio, pixelRatio);
}
// Wenn Animation läuft und Bild geladen, zeichne erneut
if (isImageLoaded && animationFrameId) {
drawNetworkImage();
}
}
// Start animation
function startAnimation() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
// Start animation loop
animate();
}
// Draw network image
function drawNetworkImage() {
if (!ctx) return;
// Clear canvas with proper clear method
ctx.clearRect(0, 0, canvas.width / (window.devicePixelRatio || 1), canvas.height / (window.devicePixelRatio || 1));
// Save context state
ctx.save();
// Move to center of canvas
ctx.translate(canvas.width / (2 * (window.devicePixelRatio || 1)), canvas.height / (2 * (window.devicePixelRatio || 1)));
// Rotate
ctx.rotate(rotation);
// Scale
ctx.scale(scale, scale);
// Set global opacity, angepasst für Dark Mode
ctx.globalAlpha = isDarkMode ? opacity : opacity * 0.8;
if (isImageLoaded && networkImage.complete) {
// Bildgröße berechnen, um den Bildschirm abzudecken
const imgAspect = networkImage.width / networkImage.height;
const canvasAspect = canvas.width / canvas.height;
let drawWidth, drawHeight;
if (canvasAspect > imgAspect) {
drawWidth = canvas.width / (window.devicePixelRatio || 1);
drawHeight = drawWidth / imgAspect;
} else {
drawHeight = canvas.height / (window.devicePixelRatio || 1);
drawWidth = drawHeight * imgAspect;
}
// Draw image centered
ctx.drawImage(
networkImage,
-drawWidth / 2,
-drawHeight / 2,
drawWidth,
drawHeight
);
} else {
// Fallback: Zeichne einen einfachen Hintergrund mit Punkten
drawFallbackBackground();
}
// Restore context state
ctx.restore();
}
// Fallback-Hintergrund mit Punkten und Linien
function drawFallbackBackground() {
const width = canvas.width / (window.devicePixelRatio || 1);
const height = canvas.height / (window.devicePixelRatio || 1);
// Zeichne einige zufällige Punkte
ctx.fillStyle = isDarkMode ? 'rgba(139, 92, 246, 0.2)' : 'rgba(139, 92, 246, 0.1)';
for (let i = 0; i < 50; i++) {
const x = Math.random() * width;
const y = Math.random() * height;
const radius = Math.random() * 3 + 1;
ctx.beginPath();
ctx.arc(x - width/2, y - height/2, radius, 0, Math.PI * 2);
ctx.fill();
}
}
// Animation loop
function animate() {
// Update animation parameters
rotation += animationSpeed;
// Update scale with oscillation
scale += scaleSpeed * scaleDirection;
if (scale > 1.05) { // Kleinerer Skalierungsbereich für weniger starke Größenänderung
scaleDirection = -1;
} else if (scale < 0.95) {
scaleDirection = 1;
}
// Update opacity with oscillation
opacity += opacitySpeed * opacityDirection;
if (opacity > 0.75) { // Kleinerer Opazitätsbereich für subtilere Änderungen
opacityDirection = -1;
} else if (opacity < 0.65) {
opacityDirection = 1;
}
// Draw the image
drawNetworkImage();
// Request next frame
animationFrameId = requestAnimationFrame(animate);
}
// Cleanup Funktion für Speicherbereinigung
function cleanupNetworkBackground() {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
animationFrameId = null;
}
if (canvas && canvas.parentNode) {
canvas.parentNode.removeChild(canvas);
}
window.removeEventListener('resize', resizeCanvas);
}
// Führe Initialisierung aus, wenn DOM geladen ist
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initNetworkBackground);
} else {
initNetworkBackground();
}
// Führe Cleanup durch, wenn das Fenster geschlossen wird
window.addEventListener('beforeunload', cleanupNetworkBackground);

View File

@@ -73,6 +73,7 @@
<!-- Assistent CSS -->
<link href="{{ url_for('static', filename='css/assistant.css') }}" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/src/cybernetwork-bg.css') }}">
<!-- Basis-Stylesheet -->
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
@@ -83,11 +84,8 @@
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.12.3/dist/cdn.min.js"></script>
<!-- Neural Network Background CSS -->
<link href="{{ url_for('static', filename='css/neural-network-background.css') }}" rel="stylesheet">
<!-- Neural Network Background Script -->
<script src="{{ url_for('static', filename='neural-network-background.js') }}"></script>
<!-- Network Background Script -->
<script src="{{ url_for('static', filename='network-background.js') }}"></script>
<!-- Hauptmodul laden (als ES6 Modul) -->
<script type="module">
@@ -95,7 +93,7 @@
// Alpine.js-Integration
document.addEventListener('alpine:init', () => {
Alpine.data('layout', () => ({
darkMode: true, // Default to dark mode
darkMode: false,
mobileMenuOpen: false,
userMenuOpen: false,
showSettingsModal: false,
@@ -106,7 +104,7 @@
fetchDarkModeFromSession() {
// Lade den Dark Mode-Status vom Server
fetch('/api/get_dark_mode')
fetch('/get_dark_mode')
.then(response => response.json())
.then(data => {
if (data.success) {
@@ -124,7 +122,7 @@
document.querySelector('html').classList.toggle('dark', this.darkMode);
// Speichere den Dark Mode-Status auf dem Server
fetch('/api/set_dark_mode', {
fetch('/set_dark_mode', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -158,65 +156,18 @@
<!-- Seitenspezifische Styles -->
{% block extra_css %}{% endblock %}
<!-- Custom dark mode styles -->
<style>
/* Dark mystical theme */
.dark {
--bg-primary: #0a0e19;
--bg-secondary: #111827;
--text-primary: #f9fafb;
--text-secondary: #e5e7eb;
--accent-primary: #6d28d9;
--accent-secondary: #8b5cf6;
--glow-effect: 0 0 15px rgba(124, 58, 237, 0.5);
}
/* Light theme with mystical tones */
:root {
--bg-primary: #f8fafc;
--bg-secondary: #f1f5f9;
--text-primary: #1e293b;
--text-secondary: #475569;
--accent-primary: #7c3aed;
--accent-secondary: #8b5cf6;
--glow-effect: 0 0 15px rgba(139, 92, 246, 0.3);
}
body.dark {
background-color: var(--bg-primary);
color: var(--text-primary);
}
/* Mystical glowing effects */
.mystical-glow {
text-shadow: var(--glow-effect);
}
.gradient-text {
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: none;
}
/* Glass morphism effects */
.glass-morphism {
backdrop-filter: blur(10px);
}
.dark .glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.8);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
.glass-navbar-light {
background-color: rgba(255, 255, 255, 0.8);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
}
</style>
<!-- Cybertechnisches Netzwerk-Hintergrund -->
<script type="module" src="{{ url_for('static', filename='js/modules/cyber-network-init.js') }}"></script>
</head>
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark">
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden">
<!-- Cybertechnisches Netzwerk-Hintergrund Container (wird via JavaScript befüllt) -->
<div id="cyber-background-container" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: -1; pointer-events: none; overflow: hidden;"></div>
<!-- Globaler Hintergrund -->
<div class="full-page-bg"></div>
<!-- Statischer Fallback-Hintergrund (wird nur angezeigt, wenn JavaScript deaktiviert ist) -->
<div class="fixed inset-0 z-[-9] bg-cover bg-center opacity-50"></div>
<!-- App-Container -->
<div id="app-container" class="flex flex-col min-h-screen" x-data="layout">
<!-- Hauptnavigation -->
@@ -255,8 +206,8 @@
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.toggleAssistant(true)"
class="nav-link flex items-center"
x-bind:class="darkMode
? 'bg-gradient-to-r from-purple-900/90 to-indigo-800/90 text-white font-medium px-4 py-2 rounded-xl hover:shadow-lg hover:shadow-purple-800/30 transition-all duration-300'
: 'bg-gradient-to-r from-purple-600/30 to-indigo-500/30 text-gray-800 font-medium px-4 py-2 rounded-xl hover:shadow-md transition-all duration-300'">
? 'bg-gradient-to-r from-purple-600/80 to-blue-500/80 text-white font-medium px-4 py-2 rounded-xl hover:shadow-lg transition-all duration-300 hover:-translate-y-0.5'
: 'bg-gradient-to-r from-purple-500/20 to-blue-400/20 text-gray-800 font-medium px-4 py-2 rounded-xl hover:shadow-md transition-all duration-300 hover:-translate-y-0.5'">
<i class="fa-solid fa-robot mr-2"></i>KI-Chat
</button>
{% if current_user.is_authenticated %}
@@ -277,9 +228,9 @@
<div class="relative w-12 h-6">
<input type="checkbox" id="darkModeToggle" class="sr-only" x-model="darkMode">
<div class="block w-12 h-6 rounded-full transition-colors duration-300"
x-bind:class="darkMode ? 'bg-purple-800/50' : 'bg-gray-400/50'"></div>
x-bind:class="darkMode ? 'bg-blue-400/50' : 'bg-gray-400/50'"></div>
<div class="dot absolute left-1 top-1 w-4 h-4 rounded-full transition-transform duration-300 shadow-md"
x-bind:class="darkMode ? 'bg-purple-600 transform translate-x-6' : 'bg-white'"></div>
x-bind:class="darkMode ? 'bg-blue-500 transform translate-x-6' : 'bg-white'"></div>
</div>
<div class="ml-3 hidden sm:block"
x-bind:class="darkMode ? 'text-white/90' : 'text-gray-700'">
@@ -357,22 +308,13 @@
</div>
</div>
{% else %}
<div class="flex items-center space-x-2">
<a href="{{ url_for('login') }}"
class="py-2 px-4 rounded-lg transition-all duration-300"
x-bind:class="darkMode
? 'text-white/90 hover:bg-dark-700/80'
: 'text-gray-700 hover:bg-gray-100/80'">
<i class="fa-solid fa-sign-in-alt mr-2"></i>Login
</a>
<a href="{{ url_for('register') }}"
class="py-2 px-4 rounded-lg transition-all duration-300 font-medium"
x-bind:class="darkMode
? 'bg-purple-800/80 text-white hover:bg-purple-700/80'
: 'bg-purple-600/20 text-gray-700 hover:bg-purple-600/30'">
Registrieren
</a>
</div>
<a href="{{ url_for('login') }}"
class="flex items-center px-4 py-2.5 rounded-xl font-medium transition-all duration-300"
x-bind:class="darkMode
? 'bg-gray-800/80 text-white hover:bg-gray-700/80 shadow-md hover:shadow-lg hover:-translate-y-0.5'
: 'bg-gray-200/80 text-gray-800 hover:bg-gray-300/80 shadow-sm hover:shadow-md hover:-translate-y-0.5'">
<i class="fa-solid fa-user mr-2"></i>Mein Konto
</a>
{% endif %}
<!-- Mobilmenü-Button -->

603
templates/index.html Normal file
View File

@@ -0,0 +1,603 @@
{% extends "base.html" %}
{% block title %}Wissensnetzwerk{% endblock %}
{% block extra_css %}
<style>
/* Hintergrund über die gesamte Seite erstrecken */
html, body {
min-height: 100vh;
width: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
/* Entferne den Gradient-Hintergrund vollständig */
.hero-gradient, .bg-fade {
background: none !important;
clip-path: none !important;
}
.tech-line {
height: 1px;
background: linear-gradient(to right, transparent, rgba(100, 100, 100, 0.1), transparent);
}
.tech-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: rgba(100, 100, 100, 0.2);
position: absolute;
}
.dark .tech-line {
background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.1), transparent);
}
.dark .tech-dot {
background-color: rgba(255, 255, 255, 0.3);
}
@keyframes pulse {
0% { r: 10; opacity: 0.7; }
50% { r: 12; opacity: 1; }
100% { r: 10; opacity: 0.7; }
}
.animate-pulse {
animation: pulse 3s ease-in-out infinite;
}
@keyframes iconPulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.icon-pulse {
animation: iconPulse 3s ease-in-out infinite;
display: inline-block;
}
/* Volle Seitenbreite für Container */
#app-container, .container, main, .mx-auto, .py-12 {
width: 100%;
}
/* Sicherstellen dass der Hintergrund die ganze Seite abdeckt */
.full-page-bg {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: -1;
}
/* Chat-Animationen */
.typing-dots span {
animation-duration: 1.2s;
animation-iteration-count: infinite;
}
/* Chat-Nachrichten-Animation */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translate3d(0, 10px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
#embedded-chat-messages > div {
animation: fadeInUp 0.3s ease-out forwards;
}
/* Sanftes Scrollen im Chat */
#embedded-chat-messages {
scroll-behavior: smooth;
}
/* Benutzerdefinierter Scrollbar für den Chat */
#embedded-chat-messages::-webkit-scrollbar {
width: 6px;
}
#embedded-chat-messages::-webkit-scrollbar-track {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
#embedded-chat-messages::-webkit-scrollbar-thumb {
background-color: rgba(139, 92, 246, 0.3);
border-radius: 10px;
}
.dark #embedded-chat-messages::-webkit-scrollbar-thumb {
background-color: rgba(139, 92, 246, 0.5);
}
/* Hover-Effekt für Quick-Query-Buttons */
.quick-query-btn:hover {
cursor: pointer;
background: linear-gradient(to right, rgba(139, 92, 246, 0.1), rgba(96, 165, 250, 0.1));
}
.dark .quick-query-btn:hover {
background: linear-gradient(to right, rgba(139, 92, 246, 0.2), rgba(96, 165, 250, 0.2));
}
</style>
{% endblock %}
{% block content %}
<!-- Hintergrund für die gesamte Seite -->
<div class="full-page-bg gradient-background"></div>
<!-- Hero Section -->
<section class="relative pt-20 pb-32">
<!-- Hero Content -->
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="text-center mb-16">
<h1 class="hero-heading mb-8 text-gray-900 dark:text-white">
<span class="gradient-text inline-block transform transition-all duration-700 hover:scale-105">Wissen</span> neu
<div class="mt-2 relative">
<span class="relative inline-block">vernetzen
<div class="absolute -bottom-2 left-0 right-0 h-1 bg-gradient-to-r from-purple-500/0 via-purple-500/70 to-purple-500/0 rounded-full"></div>
</span>
</div>
</h1>
<p class="text-xl md:text-2xl text-gray-700 dark:text-gray-300 max-w-3xl mx-auto mb-12">
Erkunde komplexe Ideen visuell, schaffe Verbindungen und teile deine Gedanken
in einem interaktiven Wissensnetzwerk.
</p>
<div class="flex flex-col sm:flex-row gap-5 justify-center">
<a href="{{ url_for('mindmap') }}" class="group transition-all duration-300 bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 text-white font-medium text-lg px-8 py-4 rounded-2xl shadow-lg hover:shadow-xl hover:shadow-purple-500/20 transform hover:-translate-y-1">
<span class="flex items-center justify-center">
<i class="fa-solid fa-diagram-project mr-3 text-purple-200 group-hover:text-white transition-all duration-300 animate-pulse"></i>
<span class="relative">
Mindmap erkunden
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-white group-hover:w-full transition-all duration-300"></span>
</span>
</span>
</a>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="group transition-all duration-300 bg-gradient-to-r from-blue-500 to-cyan-500 hover:from-blue-600 hover:to-cyan-600 text-white font-medium text-lg px-8 py-4 rounded-2xl shadow-lg hover:shadow-xl hover:shadow-blue-500/20 transform hover:-translate-y-1">
<span class="flex items-center justify-center">
<i class="fa-solid fa-user-plus mr-3 text-blue-200 group-hover:text-white transition-all duration-300 icon-pulse"></i>
<span class="relative">
Konto erstellen
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-white group-hover:w-full transition-all duration-300"></span>
</span>
</span>
</a>
{% endif %}
</div>
</div>
<!-- Tech illustration -->
<div class="relative w-full max-w-4xl mx-auto h-80 sm:h-96">
<div class="absolute inset-0 flex items-center justify-center">
<div class="hidden md:block text-center">
<div class="text-3xl font-bold gradient-text mb-2 animate-float">Systades</div>
<div class="text-lg text-gray-700 dark:text-gray-300">WISSEN VERNETZEN</div>
</div>
<!-- Network Visualization with SVG -->
<svg class="absolute inset-0 w-full h-full" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet">
<!-- Glossy Nodes and Lines -->
<defs>
<radialGradient id="nodeGradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
<stop offset="0%" stop-color="rgba(139, 92, 246, 0.9)" />
<stop offset="100%" stop-color="rgba(79, 70, 229, 0.5)" />
</radialGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<!-- Network Lines -->
<g class="lines">
<!-- Connection network -->
<line x1="200" y1="250" x2="400" y2="150" stroke="rgba(0,0,0,0.1)" stroke-width="1" class="dark:hidden" />
<line x1="400" y1="150" x2="600" y2="250" stroke="rgba(0,0,0,0.1)" stroke-width="1" class="dark:hidden" />
<line x1="600" y1="250" x2="400" y2="350" stroke="rgba(0,0,0,0.1)" stroke-width="1" class="dark:hidden" />
<line x1="400" y1="350" x2="200" y2="250" stroke="rgba(0,0,0,0.1)" stroke-width="1" class="dark:hidden" />
<line x1="400" y1="150" x2="400" y2="350" stroke="rgba(0,0,0,0.1)" stroke-width="1" class="dark:hidden" />
<line x1="200" y1="250" x2="600" y2="250" stroke="rgba(0,0,0,0.1)" stroke-width="1" class="dark:hidden" />
<!-- Dark mode connections -->
<line x1="200" y1="250" x2="400" y2="150" stroke="rgba(255,255,255,0.1)" stroke-width="1" class="hidden dark:inline" />
<line x1="400" y1="150" x2="600" y2="250" stroke="rgba(255,255,255,0.1)" stroke-width="1" class="hidden dark:inline" />
<line x1="600" y1="250" x2="400" y2="350" stroke="rgba(255,255,255,0.1)" stroke-width="1" class="hidden dark:inline" />
<line x1="400" y1="350" x2="200" y2="250" stroke="rgba(255,255,255,0.1)" stroke-width="1" class="hidden dark:inline" />
<line x1="400" y1="150" x2="400" y2="350" stroke="rgba(255,255,255,0.1)" stroke-width="1" class="hidden dark:inline" />
<line x1="200" y1="250" x2="600" y2="250" stroke="rgba(255,255,255,0.1)" stroke-width="1" class="hidden dark:inline" />
<!-- Pulse animation for some lines -->
<line class="animate-pulse" x1="400" y1="150" x2="300" y2="200" stroke="rgba(139, 92, 246, 0.5)" stroke-width="2" />
<line class="animate-pulse" x1="400" y1="350" x2="500" y2="300" stroke="rgba(168, 85, 247, 0.5)" stroke-width="2" />
</g>
<!-- Network Nodes -->
<g class="nodes">
<circle cx="400" cy="150" r="15" fill="url(#nodeGradient)" filter="url(#glow)" class="animate-pulse float-animation" />
<circle cx="200" cy="250" r="10" fill="url(#nodeGradient)" class="float-animation" />
<circle cx="600" cy="250" r="10" fill="url(#nodeGradient)" class="float-animation" />
<circle cx="400" cy="350" r="15" fill="url(#nodeGradient)" filter="url(#glow)" class="animate-pulse float-animation" />
<circle cx="300" cy="200" r="8" fill="url(#nodeGradient)" class="float-animation" />
<circle cx="500" cy="300" r="8" fill="url(#nodeGradient)" class="float-animation" />
</g>
</svg>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section class="py-20 relative">
<div class="tech-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-16">
<h2 class="section-heading mb-4 text-gray-900 dark:text-white">Was ist <span class="gradient-text">Systades?</span></h2>
<p class="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
Ein modernes Werkzeug zum Visualisieren, Erforschen und Teilen von Wissen
in einer intuitiven, interaktiven Umgebung.
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
<!-- Feature Card 1 -->
<div class="feature-card p-8 rounded-3xl hover:-translate-y-3 transform transition-all duration-300">
<div class="icon mb-6 rounded-2xl shadow-lg">
<i class="fa-solid fa-brain"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Visualisiere Wissen</h3>
<p>
Sieh Wissen als vernetztes System, entdecke Zusammenhänge und erkenne überraschende
Verbindungen zwischen verschiedenen Themengebieten.
</p>
</div>
<!-- Feature Card 2 -->
<div class="feature-card p-8 rounded-3xl hover:-translate-y-3 transform transition-all duration-300">
<div class="icon mb-6 rounded-2xl shadow-lg">
<i class="fa-solid fa-lightbulb"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Teile Gedanken</h3>
<p>
Füge deine eigenen Ideen und Perspektiven hinzu. Erstelle Verbindungen zu
vorhandenen Gedanken und bereichere die wachsende Wissensbasis.
</p>
</div>
<!-- Feature Card 3 -->
<div class="feature-card p-8 rounded-3xl hover:-translate-y-3 transform transition-all duration-300">
<div class="icon mb-6 rounded-2xl shadow-lg">
<i class="fa-solid fa-users"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Community</h3>
<p>
Sei Teil einer Gemeinschaft, die gemeinsam ein verteiltes Wissensarchiv aufbaut
und sich in thematisch fokussierten Bereichen austauscht.
</p>
</div>
<!-- Feature Card 4 -->
<div class="feature-card p-8 rounded-3xl hover:-translate-y-3 transform transition-all duration-300">
<div class="icon mb-6 rounded-2xl shadow-lg">
<i class="fa-solid fa-robot"></i>
</div>
<h3 class="text-xl font-semibold mb-3">KI-Assistenz</h3>
<p>
Lass dir von künstlicher Intelligenz helfen, neue Zusammenhänge zu entdecken,
Inhalte zusammenzufassen und Fragen zu beantworten.
</p>
</div>
<!-- Feature Card 5 -->
<div class="feature-card p-8 rounded-3xl hover:-translate-y-3 transform transition-all duration-300">
<div class="icon mb-6 rounded-2xl shadow-lg">
<i class="fa-solid fa-search"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Intelligente Suche</h3>
<p>
Finde genau die Informationen, die du suchst, mit fortschrittlichen Such- und
Filterfunktionen für eine präzise Navigation durch das Wissen.
</p>
</div>
<!-- Feature Card 6 -->
<div class="feature-card p-8 rounded-3xl hover:-translate-y-3 transform transition-all duration-300">
<div class="icon mb-6 rounded-2xl shadow-lg">
<i class="fa-solid fa-route"></i>
</div>
<h3 class="text-xl font-semibold mb-3">Geführte Pfade</h3>
<p>
Folge kuratierten Lernpfaden durch komplexe Themen oder erschaffe selbst
Routen für andere, die deinen Gedankengängen folgen möchten.
</p>
</div>
</div>
</div>
</section>
<!-- Call to Action Section -->
<section class="py-16 sm:py-20 md:py-24 relative overflow-hidden">
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="glass-effect p-6 sm:p-8 md:p-12 rounded-3xl transform transition-all duration-500 hover:-translate-y-2 hover:shadow-2xl bg-gradient-to-br from-purple-500/15 to-blue-500/15 backdrop-blur-xl border border-white/10 shadow-lg">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div class="md:w-2/3">
<h2 class="text-2xl sm:text-3xl lg:text-4xl font-bold mb-3 text-gray-900 dark:text-white leading-tight">
Bereit, <span class="gradient-text bg-clip-text text-transparent bg-gradient-to-r from-purple-500 to-blue-500">Wissen</span> neu zu entdecken?
</h2>
<p class="text-gray-700 dark:text-gray-300 text-base sm:text-lg mb-6 md:mb-0 max-w-2xl">
Starte jetzt deine Reise durch das Wissensnetzwerk und erschließe neue Perspektiven.
</p>
</div>
<div class="md:w-1/3 text-center md:text-right">
<a href="{{ url_for('mindmap') }}" class="inline-flex items-center justify-center w-full md:w-auto btn-primary font-bold py-3 sm:py-3.5 px-6 sm:px-8 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 hover:scale-105 bg-gradient-to-r from-purple-600 to-blue-600 text-white">
<span class="flex items-center justify-center">
<i class="fa-solid fa-arrow-right mr-2"></i>
<span>Zur Mindmap</span>
</span>
</a>
</div>
</div>
</div>
</div>
</section>
<!-- Quick Access Section -->
<section class="py-16 sm:py-20">
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 lg:gap-8">
<!-- Themen-Übersicht -->
<div class="glass-morphism p-6 sm:p-8 rounded-3xl transition-all duration-500 hover:-translate-y-3 hover:shadow-xl border border-white/10 backdrop-blur-md">
<h3 class="text-xl font-bold mb-4 flex items-center text-gray-800 dark:text-white">
<div class="w-10 h-10 sm:w-12 sm:h-12 rounded-2xl bg-gradient-to-r from-violet-500 to-fuchsia-500 flex items-center justify-center mr-3 sm:mr-4 shadow-md transform transition-transform duration-300 hover:scale-110">
<i class="fa-solid fa-fire text-white text-base sm:text-lg"></i>
</div>
<span class="text-lg sm:text-xl md:text-2xl">Themen-Übersicht</span>
</h3>
<div class="space-y-3 sm:space-y-4 mb-6">
<a href="{{ url_for('mindmap') }}" class="flex items-center p-3 sm:p-3.5 rounded-xl hover:bg-gray-100/50 dark:hover:bg-white/5 transition-all duration-200 group">
<div class="w-3 h-3 rounded-full bg-purple-400 mr-3 group-hover:scale-125 transition-transform"></div>
<div class="flex-grow">
<p class="font-medium text-gray-800 dark:text-gray-200">Wissensbereiche <span class="text-xs text-gray-500">(12)</span></p>
<p class="text-xs sm:text-sm text-gray-500 dark:text-gray-400">Überblick über Themenbereiche</p>
</div>
<i class="fa-solid fa-chevron-right text-gray-500 group-hover:translate-x-1 transition-transform"></i>
</a>
<a href="{{ url_for('search_thoughts_page') }}" class="flex items-center p-3 sm:p-3.5 rounded-xl hover:bg-gray-100/50 dark:hover:bg-white/5 transition-all duration-200 group">
<div class="w-3 h-3 rounded-full bg-blue-400 mr-3 group-hover:scale-125 transition-transform"></div>
<div class="flex-grow">
<p class="font-medium text-gray-800 dark:text-gray-200">Gedanken <span class="text-xs text-gray-500">(87)</span></p>
<p class="text-xs sm:text-sm text-gray-500 dark:text-gray-400">Konkrete Einträge durchsuchen</p>
</div>
<i class="fa-solid fa-chevron-right text-gray-500 group-hover:translate-x-1 transition-transform"></i>
</a>
<a href="#" class="flex items-center p-3 sm:p-3.5 rounded-xl hover:bg-gray-100/50 dark:hover:bg-white/5 transition-all duration-200 group">
<div class="w-3 h-3 rounded-full bg-green-400 mr-3 group-hover:scale-125 transition-transform"></div>
<div class="flex-grow">
<p class="font-medium text-gray-800 dark:text-gray-200">Verbindungen <span class="text-xs text-gray-500">(34)</span></p>
<p class="text-xs sm:text-sm text-gray-500 dark:text-gray-400">Beziehungen zwischen Gedanken</p>
</div>
<i class="fa-solid fa-chevron-right text-gray-500 group-hover:translate-x-1 transition-transform"></i>
</a>
</div>
<a href="{{ url_for('search_thoughts_page') }}" class="btn-primary w-full text-center rounded-xl py-3 sm:py-3.5 transform transition-all duration-300 hover:-translate-y-1 hover:shadow-lg flex items-center justify-center">
<span>Alle Themen entdecken</span>
<i class="fa-solid fa-arrow-right ml-2"></i>
</a>
</div>
<!-- KI-Assistent mit eingebettetem Chat -->
<div class="glass-morphism p-6 sm:p-8 rounded-3xl transition-all duration-500 hover:-translate-y-1 hover:shadow-xl backdrop-blur-md border border-white/10">
<h3 class="text-xl md:text-2xl font-bold mb-4 flex flex-wrap sm:flex-nowrap items-center text-gray-800 dark:text-white">
<div class="w-10 h-10 sm:w-12 sm:h-12 rounded-2xl bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center mr-3 sm:mr-4 shadow-lg transform transition-transform duration-300 hover:scale-110">
<i class="fa-solid fa-robot text-white text-base sm:text-lg"></i>
</div>
<span class="mt-1 sm:mt-0">KI-Assistent</span>
</h3>
<!-- Eingebettetes Chat-Interface -->
<div id="embedded-assistant" class="rounded-xl border border-gray-200/50 dark:border-gray-700/50 overflow-hidden flex flex-col h-[300px]">
<!-- Chat Verlauf -->
<div id="embedded-chat-messages" class="flex-grow p-4 overflow-y-auto space-y-3 bg-white/70 dark:bg-gray-800/70">
<!-- Begrüßungsnachricht -->
<div class="flex items-start space-x-2">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-robot text-white text-xs"></i>
</div>
<div class="max-w-[85%] bg-purple-100 dark:bg-gray-700 p-3 rounded-xl rounded-tl-none shadow-sm">
<p class="text-sm text-gray-700 dark:text-gray-200">Hallo! Ich bin dein KI-Assistent. Wie kann ich dir helfen?</p>
</div>
</div>
</div>
<!-- Chat Eingabe -->
<div class="p-3 border-t border-gray-200/70 dark:border-gray-700/70 bg-gray-50/90 dark:bg-gray-800/90">
<form id="embedded-chat-form" class="flex items-center space-x-2">
<input type="text" id="embedded-chat-input"
placeholder="Stelle eine Frage..."
class="flex-grow px-4 py-2 rounded-xl border bg-white/90 dark:bg-gray-700/90 border-gray-300 dark:border-gray-600 shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200 placeholder-gray-400 dark:placeholder-gray-500 text-gray-700 dark:text-gray-200">
<button type="submit"
class="p-2 rounded-xl bg-gradient-to-r from-purple-600 to-blue-600 text-white shadow-md hover:shadow-lg transition-all duration-200 hover:-translate-y-0.5">
<i class="fa-solid fa-paper-plane"></i>
</button>
</form>
</div>
</div>
<!-- Schnelllinks unter dem Chat -->
<div class="mt-4 flex flex-wrap gap-2">
<button class="quick-query-btn px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
Was ist Systades?
</button>
<button class="quick-query-btn px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
Wie erstelle ich eine Mindmap?
</button>
<button class="quick-query-btn px-2 sm:px-3 py-1 sm:py-1.5 bg-gray-100 hover:bg-gray-200 dark:bg-gray-800/70 dark:hover:bg-gray-700/80 rounded-lg sm:rounded-xl text-xs text-gray-700 dark:text-gray-300 transition-all duration-200 hover:-translate-y-0.5 shadow-sm hover:shadow">
Zeige neueste Gedanken
</button>
</div>
<!-- Vollständigen KI-Chat öffnen -->
<button onclick="window.MindMap.assistant.toggleAssistant(true)" class="mt-4 btn-primary w-full text-center rounded-xl py-2 sm:py-2.5 shadow-md hover:shadow-lg transition-all duration-300 hover:-translate-y-1 flex items-center justify-center">
<i class="fa-solid fa-expand mr-2"></i>
<span>Chat in Vollansicht öffnen</span>
</button>
</div>
</div>
</div>
</section>
{% endblock %}
<!-- JavaScript für eingebetteten Chat -->
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Warten bis MindMap und der Assistent initialisiert sind
const waitForAssistant = setInterval(() => {
if (window.MindMap && window.MindMap.assistant) {
clearInterval(waitForAssistant);
initEmbeddedChat();
}
}, 200);
function initEmbeddedChat() {
const chatForm = document.getElementById('embedded-chat-form');
const chatInput = document.getElementById('embedded-chat-input');
const messagesContainer = document.getElementById('embedded-chat-messages');
const quickQueryBtns = document.querySelectorAll('.quick-query-btn');
// Event-Listener für das Chat-Formular
chatForm.addEventListener('submit', function(e) {
e.preventDefault();
const userMessage = chatInput.value.trim();
if (!userMessage) return;
// Nachricht des Benutzers anzeigen
appendMessage('user', userMessage);
chatInput.value = '';
// Anzeigen, dass der Assistent antwortet
const typingIndicator = appendTypingIndicator();
// API-Anfrage an den Assistenten senden
sendToAssistant(userMessage)
.then(response => {
// Entferne Tipp-Indikator
typingIndicator.remove();
// Zeige Antwort des Assistenten an
appendMessage('assistant', response);
})
.catch(error => {
typingIndicator.remove();
appendMessage('assistant', 'Es tut mir leid, ich konnte deine Nachricht nicht verarbeiten. Bitte versuche es später noch einmal.');
console.error('Fehler bei der Kommunikation mit dem Assistenten:', error);
});
});
// Schnellabfragen-Buttons
quickQueryBtns.forEach(btn => {
btn.addEventListener('click', function() {
const query = this.textContent.trim();
chatInput.value = query;
chatForm.dispatchEvent(new Event('submit'));
});
});
// Funktion zum Hinzufügen einer Nachricht zum Chat
function appendMessage(sender, message) {
const messageElement = document.createElement('div');
messageElement.className = 'flex items-start space-x-2';
if (sender === 'user') {
messageElement.innerHTML = `
<div class="flex-grow"></div>
<div class="max-w-[85%] bg-blue-100 dark:bg-blue-900/40 p-3 rounded-xl rounded-tr-none shadow-sm">
<p class="text-sm text-gray-700 dark:text-gray-200">${message}</p>
</div>
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-indigo-500 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-user text-white text-xs"></i>
</div>
`;
} else {
messageElement.innerHTML = `
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-robot text-white text-xs"></i>
</div>
<div class="max-w-[85%] bg-purple-100 dark:bg-gray-700 p-3 rounded-xl rounded-tl-none shadow-sm">
<p class="text-sm text-gray-700 dark:text-gray-200">${message}</p>
</div>
`;
}
messagesContainer.appendChild(messageElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Tipp-Indikator für "Assistent schreibt..."
function appendTypingIndicator() {
const indicatorElement = document.createElement('div');
indicatorElement.className = 'flex items-start space-x-2 typing-indicator';
indicatorElement.innerHTML = `
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-blue-600 flex items-center justify-center flex-shrink-0">
<i class="fa-solid fa-robot text-white text-xs"></i>
</div>
<div class="max-w-[85%] bg-purple-100 dark:bg-gray-700 p-3 rounded-xl rounded-tl-none shadow-sm">
<p class="text-sm text-gray-500 dark:text-gray-400 flex items-center">
<span class="mr-1">Tipp</span>
<span class="typing-dots flex space-x-1">
<span class="w-1.5 h-1.5 bg-gray-500 dark:bg-gray-400 rounded-full animate-bounce" style="animation-delay: 0ms;"></span>
<span class="w-1.5 h-1.5 bg-gray-500 dark:bg-gray-400 rounded-full animate-bounce" style="animation-delay: 150ms;"></span>
<span class="w-1.5 h-1.5 bg-gray-500 dark:bg-gray-400 rounded-full animate-bounce" style="animation-delay: 300ms;"></span>
</span>
</p>
</div>
`;
messagesContainer.appendChild(indicatorElement);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
return indicatorElement;
}
// Sende Nachricht an den Assistenten und erhalte Antwort
async function sendToAssistant(message) {
try {
const response = await fetch('/api/assistant', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: [
{ role: "system", content: "Du bist ein hilfreicher Assistent für das Wissensnetzwerk Systades." },
{ role: "user", content: message }
]
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Unbekannter Fehler');
}
return data.response || data.answer || 'Ich habe keine Antwort erhalten.';
} catch (error) {
console.error('Fehler bei der API-Anfrage:', error);
throw error;
}
}
}
});
</script>
{% endblock %}

1051
templates/mindmap.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,134 +0,0 @@
# MindMapProjekt - Roadmap
## Projektübersicht
Das MindMapProjekt ist eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen. Das Projekt wird umfassend überarbeitet, um ein modernes, benutzerfreundliches Design und erweiterte Funktionalitäten zu bieten.
## Technischer Stack
- **Backend**: Python/Flask
- **Frontend**:
- Tailwind CSS für moderne UI
- SVG-Bibliotheken für Visualisierungen (D3.js)
- JavaScript/Alpine.js für interaktive Komponenten
- **Datenbank**: SQLite mit SQLAlchemy
- **KI-Integration**: OpenAI API für intelligente Assistenz
## Installation und Verwendung
### Installation
1. Repository klonen
2. Virtuelle Umgebung erstellen: `python -m venv venv`
3. Virtuelle Umgebung aktivieren:
- Windows: `venv\Scripts\activate`
- Unix/MacOS: `source venv/bin/activate`
4. Abhängigkeiten installieren: `pip install -r requirements.txt`
5. Datenbank initialisieren: `python TOOLS.py db:rebuild`
6. Admin-Benutzer erstellen: `python TOOLS.py user:admin`
7. Server starten: `python TOOLS.py server:run`
### Standardbenutzer
- **Admin-Benutzer**: Username: `admin` / Passwort: `admin`
- **Testbenutzer**: Username: `user` / Passwort: `user`
### Verwaltungswerkzeuge mit TOOLS.py
Das Projekt enthält ein zentrales Verwaltungsskript `TOOLS.py`, das verschiedene Hilfsfunktionen bietet:
#### Datenbankverwaltung
- `python TOOLS.py db:fix` - Reparieren der Datenbankstruktur
- `python TOOLS.py db:rebuild` - Datenbank neu aufbauen (löscht alle Daten!)
- `python TOOLS.py db:test` - Datenbankverbindung und Modelle testen
- `python TOOLS.py db:stats` - Datenbankstatistiken anzeigen
#### Benutzerverwaltung
- `python TOOLS.py user:list` - Alle Benutzer anzeigen
- `python TOOLS.py user:create -u USERNAME -e EMAIL -p PASSWORD [-a]` - Neuen Benutzer erstellen
- `python TOOLS.py user:admin` - Admin-Benutzer erstellen (admin/admin)
- `python TOOLS.py user:reset-pw -u USERNAME -p NEWPASSWORD` - Benutzerpasswort zurücksetzen
- `python TOOLS.py user:delete -u USERNAME` - Benutzer löschen
#### Serververwaltung
- `python TOOLS.py server:run [--host HOST] [--port PORT] [--no-debug]` - Entwicklungsserver starten
Für detaillierte Hilfe: `python TOOLS.py -h`
## Roadmap der Überarbeitung
### Phase 1: Grundlegende Infrastruktur ✅
- [x] Bestandsaufnahme des aktuellen Projekts
- [x] Erstellung der Roadmap
- [x] Aktualisierung der Abhängigkeiten
- [x] Integration von Tailwind CSS
- [x] Einrichtung der SVG-Bibliotheken (D3.js)
- [x] Favicon erstellen
- [x] Setup-Skript für einfache Installation
### Phase 2: Design-Überarbeitung 🔄
- [x] Implementierung des Dark Mode
- [x] Erstellung eines modernen, minimalistischen UI mit Tech-Ästhetik
- [x] Responsive Design für alle Geräte
- [ ] Gestaltung der Landing Page mit großer Typografie
### Phase 3: Mindmap-Funktionalitäten 🔄
- [x] Verbesserte Visualisierung mit SVG und D3.js
- [x] Implementierung der Mouseover-Funktion
- [x] Entwicklung der Suchfunktion für Knoten
- [ ] Tagging-System für Inhalte
- [ ] Quellenmanagement und -verlinkung
- [ ] Upload-Funktionalität an Knotenpunkten
### Phase 4: Kernseitenentwicklung
- [ ] Überarbeitung der Startseite mit neuen Features
- [ ] Entwicklung der "Wer sind wir?"-Seite
- [ ] Implementierung von Impressum und Datenschutzerklärung
- [ ] Erstellung der Kontaktseite mit FAQs
- [ ] Überarbeitung des Benutzerprofilbereichs
### Phase 5: Community-Features
- [ ] Entwicklung des Autorenbereichs
- [ ] Implementierung von Community-Bereichen für Themenbereiche
- [ ] Verbesserter Kommentarbereich
- [ ] Benutzerrechtemanagement
### Phase 6: KI-Integration
- [ ] Implementierung des Frage-Antwort-Systems
- [ ] KI-generierte Themeneinleitungen
- [ ] Intelligente Suchunterstützung
- [ ] Geführte Pfade durch Themenbereiche
- [ ] Vorgeschlagene Chat-Möglichkeiten
### Phase 7: Benutzerprofilfunktionen
- [ ] Speichern von Thematiken
- [ ] Persönliche Mindmap/Pinboard
- [ ] Beitragsmanagement
- [ ] Benutzerstatistiken und -aktivitäten
### Phase 8: Testing und Optimierung
- [ ] Umfassende Tests aller Funktionen
- [ ] Performance-Optimierung
- [ ] SEO-Implementierung
- [ ] Barrierefreiheit prüfen und verbessern
### Phase 9: Dokumentation und Einführung
- [ ] Erstellung von Benutzeranleitungen
- [ ] Entwicklerdokumentation
- [ ] Administratorenhandbuch
- [ ] Guided Tour für neue Benutzer
## Aktueller Status
- **Phase 1**: ✅ Abgeschlossen
- **Phase 2**: 🔄 In Bearbeitung (75% abgeschlossen)
- **Phase 3**: 🔄 In Bearbeitung (50% abgeschlossen)
## Aktuelle Fortschritte
- Grundlegende UI modernisiert mit Tailwind CSS und Dark Mode
- Neues Favicon für bessere visuelle Identität erstellt
- Setup-Prozess vereinfacht mit einem Shell-Skript
- Mindmap-Visualisierung komplett überarbeitet mit D3.js für eine interaktivere Erfahrung
- Responsive Design für optimale Darstellung auf allen Geräten
## Nächste Schritte
- Fertigstellung der Landing Page
- Erstellung der "Wer sind wir?"-Seite
- Implementierung des Tagging-Systems für Gedanken
- Verbesserung der Gedankenansicht im Mindmap-Bereich
*Zuletzt aktualisiert: 01.06.2024*

Binary file not shown.

View File

@@ -1,14 +0,0 @@
flask==2.2.5
flask-login==0.6.2
flask-wtf
email-validator
python-dotenv
werkzeug==2.2.3
flask-sqlalchemy==3.0.5
openai==1.3.0
requests==2.31.0
flask-cors==4.0.0
gunicorn==21.2.0
#pillow==10.0.1
pytest==7.4.0
pytest-flask==1.2.0

View File

@@ -1,33 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from pathlib import Path
from dotenv import load_dotenv
from init_db import init_database
from app import app
if __name__ == "__main__":
# Lade .env-Datei explizit
env_path = Path(__file__).parent / ".env"
if env_path.exists():
print(f"Lade Umgebungsvariablen aus {env_path}")
load_dotenv(dotenv_path=env_path, override=True, force=True)
else:
print("Warnung: .env-Datei nicht gefunden!")
# Check if CSS file exists, build it if it doesn't
css_file = Path(__file__).parent / "static" / "css" / "main.css"
if not css_file.exists():
print("CSS file not found. Building with Tailwind...")
try:
from build_css import build_css
build_css()
except Exception as e:
print(f"Warning: Failed to build CSS: {e}")
print("You may need to run 'python build_css.py' manually.")
# Initialize the database first
init_database()
# Run the Flask application
app.run(debug=True, host='0.0.0.0', port=5000)

View File

@@ -1,52 +0,0 @@
#!/bin/bash
# Farben für Ausgaben
GREEN='\033[0;32m'
BLUE='\033[0;34m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}==== MindMap Projekt Setup ====${NC}"
# Verzeichnis erstellen, wenn nicht vorhanden
echo -e "${BLUE}Überprüfe Verzeichnisstruktur...${NC}"
mkdir -p static/css/src
mkdir -p static/img
mkdir -p static/js
mkdir -p bin
# Überprüfe, ob .env-Datei existiert und erstelle sie, wenn nicht
echo -e "${BLUE}Überprüfe .env-Datei...${NC}"
if [ ! -f ".env" ]; then
echo -e "${YELLOW}Keine .env-Datei gefunden. Erstelle neue .env-Datei aus example.env...${NC}"
cp example.env .env
echo -e "${YELLOW}Bitte bearbeiten Sie die .env-Datei und setzen Sie die erforderlichen Werte.${NC}"
echo -e "${YELLOW}Insbesondere müssen Sie einen gültigen API-Schlüssel für OpenAI setzen.${NC}"
else
echo -e "${GREEN}.env-Datei existiert bereits.${NC}"
fi
# Python-Abhängigkeiten installieren
echo -e "${BLUE}Installiere Python-Abhängigkeiten...${NC}"
pip install -r requirements.txt
# Zusätzliche Abhängigkeiten für Favicon-Erstellung
echo -e "${BLUE}Installiere Abhängigkeiten für Favicon-Erstellung...${NC}"
pip install pillow cairosvg
# Tailwind CSS mit Python-Skript kompilieren
echo -e "${BLUE}Kompiliere Tailwind CSS...${NC}"
python build_css.py
# Favicon generieren
echo -e "${BLUE}Generiere Favicon...${NC}"
python static/img/favicon-gen.py
# Erstelle die Datenbank
echo -e "${BLUE}Initialisiere die Datenbank...${NC}"
python init_db.py
echo -e "${GREEN}==== Setup abgeschlossen ====${NC}"
echo -e "${GREEN}Starte die Anwendung mit: python run.py${NC}"
echo -e "${GREEN}Für Entwicklung mit CSS-Autoreload: python dev.py${NC}"

View File

@@ -1,421 +0,0 @@
/* Base Styles - Dark, Mystical Theme */
/* Global Variables */
:root {
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-mono: 'JetBrains Mono', SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* Light Theme */
--bg-primary-light: #f8fafc;
--bg-secondary-light: #f1f5f9;
--bg-tertiary-light: #e2e8f0;
--text-primary-light: #1e293b;
--text-secondary-light: #475569;
--accent-primary-light: #7c3aed;
--accent-secondary-light: #8b5cf6;
--accent-tertiary-light: #a78bfa;
--border-light: #e2e8f0;
--shadow-light: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--glow-light: 0 0 15px rgba(139, 92, 246, 0.3);
/* Dark Theme */
--bg-primary-dark: #0a0e19;
--bg-secondary-dark: #111827;
--bg-tertiary-dark: #1f2937;
--text-primary-dark: #f9fafb;
--text-secondary-dark: #e5e7eb;
--accent-primary-dark: #6d28d9;
--accent-secondary-dark: #8b5cf6;
--accent-tertiary-dark: #a78bfa;
--border-dark: #1f2937;
--shadow-dark: 0 4px 6px -1px rgba(0, 0, 0, 0.5), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
--glow-dark: 0 0 15px rgba(124, 58, 237, 0.5);
/* Transitions */
--transition-fast: 150ms ease-in-out;
--transition-normal: 300ms ease-in-out;
--transition-slow: 500ms ease-in-out;
}
/* Base Styles */
html {
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
scroll-behavior: smooth;
}
body {
transition: background-color var(--transition-normal), color var(--transition-normal);
overflow-x: hidden;
min-height: 100vh;
font-family: var(--font-sans);
line-height: 1.5;
}
/* Dark Mode */
html.dark body {
background-color: var(--bg-primary-dark);
color: var(--text-primary-dark);
}
/* Light Mode */
body {
background-color: var(--bg-primary-light);
color: var(--text-primary-light);
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
.hero-heading {
font-size: 2.75rem;
font-weight: 700;
letter-spacing: -0.02em;
line-height: 1.1;
}
.section-heading {
font-size: 2rem;
font-weight: 700;
letter-spacing: -0.01em;
}
.gradient-text {
background-clip: text;
-webkit-background-clip: text;
color: transparent;
display: inline-block;
}
html.dark .gradient-text {
background-image: linear-gradient(135deg, var(--accent-primary-dark), var(--accent-secondary-dark));
text-shadow: var(--glow-dark);
}
.gradient-text {
background-image: linear-gradient(135deg, var(--accent-primary-light), var(--accent-secondary-light));
text-shadow: var(--glow-light);
}
/* Mystical elements */
.mystical-border {
position: relative;
}
.mystical-border::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid;
border-radius: inherit;
pointer-events: none;
opacity: 0.3;
}
html.dark .mystical-border::before {
border-color: var(--accent-primary-dark);
box-shadow: var(--glow-dark);
}
.mystical-border::before {
border-color: var(--accent-primary-light);
box-shadow: var(--glow-light);
}
/* Navigation Links */
.nav-link {
position: relative;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: var(--transition-normal);
}
html.dark .nav-link {
color: var(--text-secondary-dark);
}
.nav-link {
color: var(--text-secondary-light);
}
html.dark .nav-link:hover {
color: var(--text-primary-dark);
background-color: rgba(31, 41, 55, 0.5);
}
.nav-link:hover {
color: var(--text-primary-light);
background-color: rgba(241, 245, 249, 0.5);
}
html.dark .nav-link-active {
color: var(--accent-tertiary-dark);
background-color: rgba(109, 40, 217, 0.15);
}
.nav-link-light-active {
color: var(--accent-primary-light);
background-color: rgba(139, 92, 246, 0.1);
}
/* Glass Morphism Effects */
.glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
.glass-navbar-light {
background-color: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-color: rgba(226, 232, 240, 0.5);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
}
.glass-morphism {
transition: background-color var(--transition-normal), backdrop-filter var(--transition-normal);
}
/* Cards */
.mystical-card {
border-radius: 0.75rem;
overflow: hidden;
transition: var(--transition-normal);
}
html.dark .mystical-card {
background-color: var(--bg-secondary-dark);
border: 1px solid var(--border-dark);
box-shadow: var(--shadow-dark);
}
.mystical-card {
background-color: var(--bg-secondary-light);
border: 1px solid var(--border-light);
box-shadow: var(--shadow-light);
}
html.dark .mystical-card:hover {
box-shadow: var(--glow-dark), var(--shadow-dark);
border-color: var(--accent-primary-dark);
}
.mystical-card:hover {
box-shadow: var(--glow-light), var(--shadow-light);
border-color: var(--accent-primary-light);
}
/* Buttons */
.mystical-button {
padding: 0.625rem 1.25rem;
border-radius: 0.5rem;
font-weight: 500;
transition: var(--transition-normal);
position: relative;
overflow: hidden;
}
.mystical-button::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, transparent, rgba(255, 255, 255, 0.05), transparent);
transform: translateX(-100%);
transition: transform 0.8s ease-in-out;
}
.mystical-button:hover::before {
transform: translateX(100%);
}
html.dark .mystical-button-primary {
background-color: var(--accent-primary-dark);
color: white;
}
.mystical-button-primary {
background-color: var(--accent-primary-light);
color: white;
}
html.dark .mystical-button-primary:hover {
background-color: var(--accent-secondary-dark);
box-shadow: var(--glow-dark);
}
.mystical-button-primary:hover {
background-color: var(--accent-secondary-light);
box-shadow: var(--glow-light);
}
html.dark .mystical-button-secondary {
background-color: var(--bg-tertiary-dark);
color: var(--text-primary-dark);
border: 1px solid var(--border-dark);
}
.mystical-button-secondary {
background-color: var(--bg-tertiary-light);
color: var(--text-primary-light);
border: 1px solid var(--border-light);
}
html.dark .mystical-button-secondary:hover {
background-color: var(--bg-secondary-dark);
border-color: var(--accent-tertiary-dark);
}
.mystical-button-secondary:hover {
background-color: var(--bg-secondary-light);
border-color: var(--accent-tertiary-light);
}
/* Inputs */
.mystical-input {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: var(--transition-normal);
width: 100%;
outline: none;
}
html.dark .mystical-input {
background-color: var(--bg-tertiary-dark);
border: 1px solid var(--border-dark);
color: var(--text-primary-dark);
}
.mystical-input {
background-color: var(--bg-tertiary-light);
border: 1px solid var(--border-light);
color: var(--text-primary-light);
}
html.dark .mystical-input:focus {
border-color: var(--accent-tertiary-dark);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.25);
}
.mystical-input:focus {
border-color: var(--accent-tertiary-light);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
}
/* Animations */
@keyframes floatAnimation {
0% { transform: translateY(0); }
50% { transform: translateY(-5px); }
100% { transform: translateY(0); }
}
.animate-float {
animation: floatAnimation 3s ease-in-out infinite;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
/* Scroll Bars */
::-webkit-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
html.dark ::-webkit-scrollbar-track {
background: var(--bg-secondary-dark);
}
::-webkit-scrollbar-track {
background: var(--bg-secondary-light);
}
html.dark ::-webkit-scrollbar-thumb {
background: var(--accent-primary-dark);
border-radius: 0.25rem;
}
::-webkit-scrollbar-thumb {
background: var(--accent-primary-light);
border-radius: 0.25rem;
}
html.dark ::-webkit-scrollbar-thumb:hover {
background: var(--accent-secondary-dark);
}
::-webkit-scrollbar-thumb:hover {
background: var(--accent-secondary-light);
}
/* Responsive Utilities */
@media (max-width: 640px) {
.hero-heading {
font-size: 2rem;
}
.section-heading {
font-size: 1.5rem;
}
}
/* Additional background elements */
.mystical-dot {
position: absolute;
border-radius: 50%;
opacity: 0.15;
filter: blur(3px);
z-index: -1;
transition: opacity var(--transition-normal);
}
html.dark .mystical-dot {
background-color: var(--accent-primary-dark);
box-shadow: 0 0 15px var(--accent-primary-dark);
}
.mystical-dot {
background-color: var(--accent-primary-light);
box-shadow: 0 0 15px var(--accent-primary-light);
}
/* Accessibility */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
/* Focus styles for keyboard navigation */
:focus-visible {
outline: 2px solid var(--accent-primary-light);
outline-offset: 2px;
}
html.dark :focus-visible {
outline-color: var(--accent-primary-dark);
}

View File

@@ -1,47 +0,0 @@
/* Neural Network Background CSS */
/* Make sure the neural network background is always visible */
#neural-network-background {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100% !important;
z-index: -10 !important; /* Below content but above regular background */
pointer-events: none !important;
opacity: 1 !important;
}
/* Override any solid background colors for the body */
body, body.dark {
background-color: transparent !important;
}
/* Make sure any background color is removed */
html.dark, html {
background-color: transparent !important;
}
/* Make sure any fixed backgrounds are removed */
#app-container {
background-color: transparent !important;
}
/* Ensure content is properly visible over the background */
.glass-morphism {
background-color: rgba(17, 24, 39, 0.6) !important;
backdrop-filter: blur(5px) !important;
}
body.dark .glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.7) !important;
}
body .glass-navbar-light {
background-color: rgba(255, 255, 255, 0.7) !important;
}
/* Make sure footer has proper transparency */
footer {
background-color: rgba(10, 14, 25, 0.7) !important;
}

View File

@@ -1,813 +0,0 @@
/**
* Neural Network Background Animation
* Modern, darker, mystical theme using WebGL
* Subtle flowing network aesthetic
*/
class NeuralNetworkBackground {
constructor() {
// Canvas setup
this.canvas = document.createElement('canvas');
this.canvas.id = 'neural-network-background';
this.canvas.style.position = 'fixed';
this.canvas.style.top = '0';
this.canvas.style.left = '0';
this.canvas.style.width = '100%';
this.canvas.style.height = '100%';
this.canvas.style.zIndex = '-10'; // Ensure it's behind content but visible
this.canvas.style.pointerEvents = 'none';
this.canvas.style.opacity = '1'; // Force visibility
// If canvas already exists, remove it first
const existingCanvas = document.getElementById('neural-network-background');
if (existingCanvas) {
existingCanvas.remove();
}
// Append to body as first child to ensure it's behind everything
if (document.body.firstChild) {
document.body.insertBefore(this.canvas, document.body.firstChild);
} else {
document.body.appendChild(this.canvas);
}
// WebGL context
this.gl = this.canvas.getContext('webgl') || this.canvas.getContext('experimental-webgl');
if (!this.gl) {
console.warn('WebGL not supported, falling back to canvas rendering');
this.gl = null;
this.ctx = this.canvas.getContext('2d');
this.useWebGL = false;
} else {
this.useWebGL = true;
}
// Animation properties
this.nodes = [];
this.connections = [];
this.flows = []; // Flow animations along connections
this.animationFrameId = null;
this.isDarkMode = true; // Always use dark mode for the background
// Colors - Updated to be more subtle
this.darkModeColors = {
background: '#050a14', // Darker blue-black background
nodeColor: '#4a5568', // Darker nodes for subtlety
nodePulse: '#718096', // Subtle blue pulse
connectionColor: '#2d3748', // Darker connections
flowColor: '#4a88ff80' // Semi-transparent flow highlight
};
this.lightModeColors = {
background: '#f9fafb',
nodeColor: '#7c3aed',
nodePulse: '#8b5cf6',
connectionColor: '#a78bfa',
flowColor: '#c4b5fd'
};
// Config - Updated to be more flowing and subtle
this.config = {
nodeCount: 100, // Slightly fewer nodes for cleaner look
nodeSize: 0.8, // Smaller nodes
nodeVariation: 0.5, // Less variation for uniformity
connectionDistance: 200, // Longer connections for better flow
connectionOpacity: 0.2, // More subtle connections
animationSpeed: 0.08, // Much slower movement
pulseSpeed: 0.004, // Slower pulse
flowSpeed: 0.6, // Speed of flow animations
flowDensity: 0.001, // How often new flows start (lower = less frequent)
flowLength: 0.2 // Length of the flow (percentage of the connection)
};
// Initialize
this.init();
// Event listeners
window.addEventListener('resize', this.resizeCanvas.bind(this));
document.addEventListener('darkModeToggled', (event) => {
this.isDarkMode = event.detail.isDark;
});
// Log that the background is initialized
console.log('Neural Network Background initialized');
}
init() {
this.resizeCanvas();
if (this.useWebGL) {
this.initWebGL();
}
this.createNodes();
this.createConnections();
this.startAnimation();
}
resizeCanvas() {
const pixelRatio = window.devicePixelRatio || 1;
const width = window.innerWidth;
const height = window.innerHeight;
this.canvas.style.width = width + 'px';
this.canvas.style.height = height + 'px';
this.canvas.width = width * pixelRatio;
this.canvas.height = height * pixelRatio;
if (this.useWebGL) {
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
} else if (this.ctx) {
this.ctx.scale(pixelRatio, pixelRatio);
}
// Recalculate node positions after resize
if (this.nodes.length) {
this.createNodes();
this.createConnections();
}
}
initWebGL() {
// Vertex shader
const vsSource = `
attribute vec2 aVertexPosition;
attribute float aPointSize;
uniform vec2 uResolution;
void main() {
// Convert from pixel to clip space
vec2 position = (aVertexPosition / uResolution) * 2.0 - 1.0;
// Flip Y coordinate
position.y = -position.y;
gl_Position = vec4(position, 0, 1);
gl_PointSize = aPointSize;
}
`;
// Fragment shader - Softer glow effect
const fsSource = `
precision mediump float;
uniform vec4 uColor;
void main() {
float distance = length(gl_PointCoord - vec2(0.5, 0.5));
// Softer glow with smoother falloff
float alpha = 1.0 - smoothstep(0.1, 0.5, distance);
alpha = pow(alpha, 1.5); // Make the glow even softer
gl_FragColor = vec4(uColor.rgb, uColor.a * alpha);
}
`;
// Initialize shaders
const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource);
// Create shader program
this.shaderProgram = this.gl.createProgram();
this.gl.attachShader(this.shaderProgram, vertexShader);
this.gl.attachShader(this.shaderProgram, fragmentShader);
this.gl.linkProgram(this.shaderProgram);
if (!this.gl.getProgramParameter(this.shaderProgram, this.gl.LINK_STATUS)) {
console.error('Unable to initialize the shader program: ' +
this.gl.getProgramInfoLog(this.shaderProgram));
return;
}
// Get attribute and uniform locations
this.programInfo = {
program: this.shaderProgram,
attribLocations: {
vertexPosition: this.gl.getAttribLocation(this.shaderProgram, 'aVertexPosition'),
pointSize: this.gl.getAttribLocation(this.shaderProgram, 'aPointSize')
},
uniformLocations: {
resolution: this.gl.getUniformLocation(this.shaderProgram, 'uResolution'),
color: this.gl.getUniformLocation(this.shaderProgram, 'uColor')
}
};
// Create buffers
this.positionBuffer = this.gl.createBuffer();
this.sizeBuffer = this.gl.createBuffer();
// Set clear color for WebGL context
const bgColor = this.hexToRgb(this.darkModeColors.background);
this.gl.clearColor(bgColor.r/255, bgColor.g/255, bgColor.b/255, 1.0);
}
loadShader(type, source) {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
console.error('An error occurred compiling the shaders: ' +
this.gl.getShaderInfoLog(shader));
this.gl.deleteShader(shader);
return null;
}
return shader;
}
createNodes() {
this.nodes = [];
const width = this.canvas.width / (window.devicePixelRatio || 1);
const height = this.canvas.height / (window.devicePixelRatio || 1);
// Create nodes with random positions and properties
for (let i = 0; i < this.config.nodeCount; i++) {
const node = {
x: Math.random() * width,
y: Math.random() * height,
size: this.config.nodeSize + Math.random() * this.config.nodeVariation,
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: []
};
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 = [];
for (let j = i + 1; j < this.nodes.length; j++) {
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) {
// Create connection
const connection = {
from: i,
to: j,
distance: distance,
opacity: Math.max(0, 1 - distance / this.config.connectionDistance) * this.config.connectionOpacity,
hasFlow: false // Each connection can have a flow
};
this.connections.push(connection);
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);
// Update node positions - slower, more flowing movement
for (let i = 0; i < this.nodes.length; i++) {
const node = this.nodes[i];
// Move node with slight randomness for organic feel
node.speed.x += (Math.random() - 0.5) * 0.001;
node.speed.y += (Math.random() - 0.5) * 0.001;
// Dampen speeds for stability
node.speed.x *= 0.99;
node.speed.y *= 0.99;
// Apply speed limits
node.speed.x = Math.max(-this.config.animationSpeed, Math.min(this.config.animationSpeed, node.speed.x));
node.speed.y = Math.max(-this.config.animationSpeed, Math.min(this.config.animationSpeed, node.speed.y));
// Move node
node.x += node.speed.x;
node.y += node.speed.y;
// Boundary check with smooth bounce
if (node.x < 0 || node.x > width) {
node.speed.x *= -0.8; // Softer bounce
node.x = Math.max(0, Math.min(node.x, width));
}
if (node.y < 0 || node.y > height) {
node.speed.y *= -0.8; // Softer bounce
node.y = Math.max(0, Math.min(node.y, height));
}
// Update pulse phase
node.pulsePhase += this.config.pulseSpeed;
if (node.pulsePhase > Math.PI * 2) {
node.pulsePhase -= Math.PI * 2;
}
}
// 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
for (let i = this.flows.length - 1; i >= 0; i--) {
const flow = this.flows[i];
flow.progress += this.config.flowSpeed / flow.connection.distance;
// Remove completed flows
if (flow.progress > 1.0) {
this.flows.splice(i, 1);
}
}
}
// New method to create flow animations
createNewFlow() {
if (this.connections.length === 0) return;
// 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
});
}
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);
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;
// Size with subtle pulse effect
const pulse = Math.sin(node.pulsePhase) * 0.2 + 1;
sizes[i] = node.size * pulse * (node.connections.length > 3 ? 1.3 : 1);
}
// 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);
// Set node color - more subtle
const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors;
const nodeColor = this.hexToRgb(colorObj.nodeColor);
this.gl.uniform4f(
this.programInfo.uniformLocations.color,
nodeColor.r / 255,
nodeColor.g / 255,
nodeColor.b / 255,
0.7 // Lower opacity for subtlety
);
// Draw nodes
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE); // Additive blending for glow
this.gl.drawArrays(this.gl.POINTS, 0, this.nodes.length);
}
renderConnectionsWebGL() {
// For each connection, draw a line
for (const connection of this.connections) {
const fromNode = this.nodes[connection.from];
const toNode = this.nodes[connection.to];
// Line positions
const positions = new Float32Array([
fromNode.x, fromNode.y,
toNode.x, toNode.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);
// Set line color with connection opacity - darker, more subtle
const colorObj = this.isDarkMode ? this.darkModeColors : this.lightModeColors;
const lineColor = this.hexToRgb(colorObj.connectionColor);
this.gl.uniform4f(
this.programInfo.uniformLocations.color,
lineColor.r / 255,
lineColor.g / 255,
lineColor.b / 255,
connection.opacity * 0.8 // Reduced for subtlety
);
// Draw the line
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
this.gl.lineWidth(1);
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
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,
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
};
}
// 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 - subtle glow
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.4 * fadeOpacity // Subtle flow opacity
);
// Draw the flow line
this.gl.enable(this.gl.BLEND);
this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE);
this.gl.lineWidth(1.5); // Slightly thicker for visibility
this.gl.drawArrays(this.gl.LINES, 0, 2);
}
}
renderCanvas() {
// Clear canvas
const width = this.canvas.width / (window.devicePixelRatio || 1);
const height = this.canvas.height / (window.devicePixelRatio || 1);
this.ctx.clearRect(0, 0, width, height);
// Set background
const backgroundColor = this.isDarkMode
? this.darkModeColors.background
: this.lightModeColors.background;
this.ctx.fillStyle = backgroundColor;
this.ctx.fillRect(0, 0, width, height);
// Draw connections
const connectionColor = this.isDarkMode
? this.darkModeColors.connectionColor
: this.lightModeColors.connectionColor;
for (const connection of this.connections) {
const fromNode = this.nodes[connection.from];
const toNode = this.nodes[connection.to];
this.ctx.beginPath();
this.ctx.moveTo(fromNode.x, fromNode.y);
this.ctx.lineTo(toNode.x, toNode.y);
const rgbColor = this.hexToRgb(connectionColor);
this.ctx.strokeStyle = `rgba(${rgbColor.r}, ${rgbColor.g}, ${rgbColor.b}, ${connection.opacity})`;
this.ctx.stroke();
}
// Draw flows
this.renderFlowsCanvas();
// Draw nodes
const nodeColor = this.isDarkMode
? this.darkModeColors.nodeColor
: this.lightModeColors.nodeColor;
const nodePulse = this.isDarkMode
? this.darkModeColors.nodePulse
: this.lightModeColors.nodePulse;
for (const node of this.nodes) {
// Node with subtle glow effect
const pulse = Math.sin(node.pulsePhase) * 0.2 + 1;
const nodeSize = node.size * pulse * (node.connections.length > 3 ? 1.3 : 1);
// Glow effect
const glow = this.ctx.createRadialGradient(
node.x, node.y, 0,
node.x, node.y, nodeSize * 2
);
const rgbNodeColor = this.hexToRgb(nodeColor);
const rgbPulseColor = this.hexToRgb(nodePulse);
glow.addColorStop(0, `rgba(${rgbPulseColor.r}, ${rgbPulseColor.g}, ${rgbPulseColor.b}, 0.6)`);
glow.addColorStop(0.5, `rgba(${rgbNodeColor.r}, ${rgbNodeColor.g}, ${rgbNodeColor.b}, 0.2)`);
glow.addColorStop(1, `rgba(${rgbNodeColor.r}, ${rgbNodeColor.g}, ${rgbNodeColor.b}, 0)`);
this.ctx.beginPath();
this.ctx.arc(node.x, node.y, nodeSize * 2, 0, Math.PI * 2);
this.ctx.fillStyle = glow;
this.ctx.fill();
// Main node
this.ctx.beginPath();
this.ctx.arc(node.x, node.y, nodeSize, 0, Math.PI * 2);
this.ctx.fillStyle = nodeColor;
this.ctx.fill();
}
}
// 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);
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,
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
};
}
// Fade the flow at the beginning and end
const fadeEdge = 0.2;
const fadeOpacity = Math.min(
startProgress / fadeEdge,
(1 - endProgress) / fadeEdge,
1
);
// Draw flow
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.4 * fadeOpacity})`;
this.ctx.lineWidth = 1.5;
this.ctx.stroke();
}
}
// 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);
}
}
}
// 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();
}
});

View File

@@ -1,492 +0,0 @@
/* Main Systades Styles - Dark Mystical Theme */
/* Import Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap');
/* Root Variables */
:root {
/* Light Theme Colors */
--light-bg-primary: #f8fafc;
--light-bg-secondary: #f1f5f9;
--light-text-primary: #1e293b;
--light-text-secondary: #475569;
--light-accent-primary: #7c3aed;
--light-accent-secondary: #8b5cf6;
--light-border: #e2e8f0;
/* Dark Theme Colors */
--dark-bg-primary: #0a0e19;
--dark-bg-secondary: #111827;
--dark-text-primary: #f9fafb;
--dark-text-secondary: #e5e7eb;
--dark-accent-primary: #6d28d9;
--dark-accent-secondary: #8b5cf6;
--dark-border: #1f2937;
/* Common */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--shadow-md: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
/* Transitions */
--transition-fast: 150ms ease-in-out;
--transition-normal: 300ms ease-in-out;
--transition-slow: 500ms ease-in-out;
}
/* Base Elements */
body {
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
transition: background-color var(--transition-normal), color var(--transition-normal);
background-color: transparent !important; /* Ensure background is transparent */
}
/* HTML root element should also be transparent */
html {
background-color: transparent !important;
}
html.dark {
background-color: transparent !important;
}
/* Theme Specific - keep the color but remove background */
body {
color: var(--light-text-primary);
}
body.dark {
color: var(--dark-text-primary);
}
/* Typography */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.2;
}
p {
line-height: 1.6;
}
.gradient-text {
background-clip: text;
-webkit-background-clip: text;
color: transparent;
position: relative;
}
body .gradient-text {
background-image: linear-gradient(135deg, var(--light-accent-primary), var(--light-accent-secondary));
}
body.dark .gradient-text {
background-image: linear-gradient(135deg, var(--dark-accent-primary), var(--dark-accent-secondary));
}
/* Subtle glow for dark mode gradient text */
body.dark .gradient-text::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
filter: blur(8px);
opacity: 0.3;
background-image: inherit;
z-index: -1;
pointer-events: none;
}
/* Containers and Layout */
.container {
width: 100%;
margin-left: auto;
margin-right: auto;
padding-left: 1rem;
padding-right: 1rem;
}
@media (min-width: 640px) {
.container {
max-width: 640px;
}
}
@media (min-width: 768px) {
.container {
max-width: 768px;
}
}
@media (min-width: 1024px) {
.container {
max-width: 1024px;
}
}
@media (min-width: 1280px) {
.container {
max-width: 1280px;
}
}
/* Glass Morphism */
.glass-morphism {
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
body .glass-navbar-light {
background-color: rgba(255, 255, 255, 0.8);
border-color: rgba(226, 232, 240, 0.5);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
}
body.dark .glass-navbar-dark {
background-color: rgba(10, 14, 25, 0.8);
border-color: rgba(31, 41, 55, 0.5);
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.3);
}
/* Navigation */
.nav-link {
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: all var(--transition-normal);
}
body .nav-link {
color: var(--light-text-secondary);
}
body.dark .nav-link {
color: var(--dark-text-secondary);
}
body .nav-link:hover {
color: var(--light-text-primary);
background-color: rgba(241, 245, 249, 0.5);
}
body.dark .nav-link:hover {
color: var(--dark-text-primary);
background-color: rgba(31, 41, 55, 0.5);
}
body .nav-link-light-active {
color: var(--light-accent-primary);
background-color: rgba(139, 92, 246, 0.1);
}
body.dark .nav-link-active {
color: var(--dark-accent-secondary);
background-color: rgba(109, 40, 217, 0.15);
}
/* Buttons */
.btn {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-weight: 500;
transition: all var(--transition-normal);
display: inline-flex;
align-items: center;
justify-content: center;
}
body .btn-primary {
background-color: var(--light-accent-primary);
color: white;
}
body.dark .btn-primary {
background-color: var(--dark-accent-primary);
color: white;
}
body .btn-primary:hover {
background-color: var(--light-accent-secondary);
box-shadow: 0 0 15px rgba(124, 58, 237, 0.3);
}
body.dark .btn-primary:hover {
background-color: var(--dark-accent-secondary);
box-shadow: 0 0 15px rgba(109, 40, 217, 0.5);
}
body .btn-secondary {
background-color: transparent;
border: 1px solid var(--light-border);
color: var(--light-text-primary);
}
body.dark .btn-secondary {
background-color: transparent;
border: 1px solid var(--dark-border);
color: var(--dark-text-primary);
}
body .btn-secondary:hover {
background-color: var(--light-bg-secondary);
border-color: var(--light-accent-secondary);
}
body.dark .btn-secondary:hover {
background-color: var(--dark-bg-secondary);
border-color: var(--dark-accent-secondary);
}
/* Cards */
.card {
border-radius: 0.75rem;
overflow: hidden;
transition: all var(--transition-normal);
}
body .card {
background-color: white;
border: 1px solid var(--light-border);
box-shadow: var(--shadow);
}
body.dark .card {
background-color: var(--dark-bg-secondary);
border: 1px solid var(--dark-border);
box-shadow: var(--shadow);
}
body .card:hover {
transform: translateY(-5px);
box-shadow: var(--shadow-md);
}
body.dark .card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2);
}
/* Form Elements */
.form-input {
width: 100%;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
transition: all var(--transition-normal);
}
body .form-input {
background-color: white;
border: 1px solid var(--light-border);
color: var(--light-text-primary);
}
body.dark .form-input {
background-color: var(--dark-bg-secondary);
border: 1px solid var(--dark-border);
color: var(--dark-text-primary);
}
body .form-input:focus {
outline: none;
border-color: var(--light-accent-secondary);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.15);
}
body.dark .form-input:focus {
outline: none;
border-color: var(--dark-accent-secondary);
box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.25);
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.animate-float {
animation: float 5s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.7; }
50% { opacity: 1; }
}
.animate-pulse {
animation: pulse 3s ease-in-out infinite;
}
/* Utilities */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.shadow-elevation {
transition: box-shadow var(--transition-normal), transform var(--transition-normal);
}
body .shadow-elevation {
box-shadow: var(--shadow);
}
body.dark .shadow-elevation {
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
}
body .shadow-elevation:hover {
box-shadow: var(--shadow-md);
transform: translateY(-2px);
}
body.dark .shadow-elevation:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
transform: translateY(-2px);
}
/* Tooltips */
.tooltip {
position: relative;
}
.tooltip:hover::before {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
white-space: nowrap;
z-index: 10;
opacity: 0;
animation: fadeIn 0.3s ease-out forwards;
}
body .tooltip:hover::before {
background-color: var(--light-text-primary);
color: white;
}
body.dark .tooltip:hover::before {
background-color: var(--dark-text-primary);
color: var(--dark-bg-primary);
}
/* Mystical elements */
.mystical-border {
position: relative;
overflow: hidden;
}
.mystical-border::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid;
border-radius: inherit;
pointer-events: none;
opacity: 0.3;
transition: opacity var(--transition-normal);
}
body .mystical-border::after {
border-color: var(--light-accent-primary);
}
body.dark .mystical-border::after {
border-color: var(--dark-accent-primary);
}
.mystical-border:hover::after {
opacity: 0.6;
}
/* Responsive Design Helpers */
@media (max-width: 640px) {
.container {
padding-left: 1rem;
padding-right: 1rem;
}
.hero-heading {
font-size: 2rem;
}
.section-heading {
font-size: 1.5rem;
}
}
/* Accessibility */
:focus-visible {
outline: 2px solid;
outline-offset: 2px;
}
body :focus-visible {
outline-color: var(--light-accent-primary);
}
body.dark :focus-visible {
outline-color: var(--dark-accent-primary);
}
/* Scrollbar styling */
::-webkit-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
body ::-webkit-scrollbar-track {
background: var(--light-bg-secondary);
}
body.dark ::-webkit-scrollbar-track {
background: var(--dark-bg-secondary);
}
body ::-webkit-scrollbar-thumb {
background: var(--light-accent-primary);
border-radius: 0.25rem;
}
body.dark ::-webkit-scrollbar-thumb {
background: var(--dark-accent-primary);
border-radius: 0.25rem;
}
body ::-webkit-scrollbar-thumb:hover {
background: var(--light-accent-secondary);
}
body.dark ::-webkit-scrollbar-thumb:hover {
background: var(--dark-accent-secondary);
}

View File

@@ -1,464 +0,0 @@
{% extends "base.html" %}
{% block title %}Wissensnetzwerk{% endblock %}
{% block extra_css %}
<style>
/* Full height and width for the page */
html, body {
min-height: 100vh;
width: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
/* Remove gradient backgrounds */
.hero-gradient, .bg-fade {
background: none !important;
clip-path: none !important;
}
/* Style elements */
.mystical-line {
height: 1px;
background: linear-gradient(to right, transparent, rgba(109, 40, 217, 0.2), transparent);
}
.dark .mystical-line {
background: linear-gradient(to right, transparent, rgba(139, 92, 246, 0.2), transparent);
}
/* Text reveal animation */
@keyframes textReveal {
0% { clip-path: polygon(0 0, 0 0, 0 100%, 0 100%); }
100% { clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%); }
}
.text-reveal {
animation: textReveal 1s cubic-bezier(0.77, 0, 0.18, 1) forwards;
}
.delay-1 { animation-delay: 0.2s; }
.delay-2 { animation-delay: 0.4s; }
.delay-3 { animation-delay: 0.6s; }
/* Home page specific styles */
.featured-card {
transition: transform 0.5s ease, box-shadow 0.5s ease;
border: 1px solid;
border-color: rgba(139, 92, 246, 0.1);
}
.dark .featured-card {
border-color: rgba(109, 40, 217, 0.2);
background-color: rgba(17, 24, 39, 0.7);
}
.featured-card:hover {
transform: translateY(-5px);
}
.dark .featured-card:hover {
box-shadow: 0 5px 15px rgba(109, 40, 217, 0.2);
border-color: rgba(109, 40, 217, 0.3);
}
.featured-card:hover {
box-shadow: 0 5px 15px rgba(139, 92, 246, 0.1);
border-color: rgba(139, 92, 246, 0.2);
}
/* Chat section styles */
.embedded-chat {
height: 350px;
border-radius: 1rem;
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid;
}
.dark .embedded-chat {
background-color: rgba(17, 24, 39, 0.7);
border-color: rgba(109, 40, 217, 0.2);
}
.embedded-chat {
background-color: rgba(255, 255, 255, 0.7);
border-color: rgba(139, 92, 246, 0.1);
}
#embedded-chat-messages {
height: 250px;
overflow-y: auto;
padding: 1rem;
}
/* Chat typing indicator */
.typing-dots span {
display: inline-block;
width: 5px;
height: 5px;
border-radius: 50%;
margin-right: 3px;
background-color: currentColor;
opacity: 0.5;
}
.typing-dots span:nth-child(1) {
animation: dot-pulse 1.2s infinite ease-in-out;
}
.typing-dots span:nth-child(2) {
animation: dot-pulse 1.2s infinite ease-in-out 0.2s;
}
.typing-dots span:nth-child(3) {
animation: dot-pulse 1.2s infinite ease-in-out 0.4s;
}
@keyframes dot-pulse {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.3); opacity: 1; }
}
</style>
{% endblock %}
{% block content %}
<!-- Hero Section -->
<section class="relative pt-20 pb-24">
<!-- Hero Content -->
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
<div class="text-center mb-16">
<h1 class="hero-heading mb-8 text-gray-900 dark:text-white">
<div class="overflow-hidden">
<span class="gradient-text inline-block text-reveal">Wissen</span>
</div>
<div class="overflow-hidden mt-2">
<span class="inline-block text-reveal delay-1">neu</span>
</div>
<div class="mt-2 relative overflow-hidden">
<span class="relative inline-block text-reveal delay-2">vernetzen
<div class="absolute -bottom-2 left-0 right-0 h-1 bg-gradient-to-r from-purple-500/0 via-purple-500/70 to-purple-500/0 rounded-full"></div>
</span>
</div>
</h1>
<div class="overflow-hidden">
<p class="text-xl md:text-2xl text-gray-700 dark:text-gray-300 max-w-3xl mx-auto mb-12 text-reveal delay-3">
Erkunde komplexe Ideen visuell, schaffe Verbindungen und teile deine Gedanken
in einem interaktiven Wissensnetzwerk.
</p>
</div>
<div class="flex flex-col sm:flex-row gap-5 justify-center">
<a href="{{ url_for('mindmap') }}" class="mystical-button mystical-button-primary group transition-all duration-300">
<span class="flex items-center justify-center">
<i class="fa-solid fa-diagram-project mr-3 text-purple-200 group-hover:text-white transition-all duration-300"></i>
<span class="relative">
Mindmap erkunden
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-white group-hover:w-full transition-all duration-300"></span>
</span>
</span>
</a>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="mystical-button mystical-button-secondary group transition-all duration-300">
<span class="flex items-center justify-center">
<i class="fa-solid fa-user-plus mr-3 group-hover:text-accent-secondary-dark dark:group-hover:text-accent-secondary-light transition-all duration-300"></i>
<span class="relative">
Konto erstellen
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-accent-primary-light dark:bg-accent-primary-dark group-hover:w-full transition-all duration-300"></span>
</span>
</span>
</a>
{% endif %}
</div>
</div>
<!-- Central logo and name -->
<div class="relative w-full max-w-4xl mx-auto h-40 sm:h-60 mb-16">
<div class="absolute inset-0 flex items-center justify-center">
<div class="text-center">
<div class="text-3xl font-bold gradient-text mb-2 animate-float">Systades</div>
<div class="text-lg text-gray-700 dark:text-gray-300">WISSEN VERNETZEN</div>
</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-16">
<h2 class="section-heading mb-4 text-gray-900 dark:text-white">Was ist <span class="gradient-text">Systades?</span></h2>
<p class="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
Ein modernes Werkzeug zum Visualisieren, Erforschen und Teilen von Wissen in einem interaktiven
Netzwerk, das dir hilft, Verbindungen zu entdecken und dein Wissen zu organisieren.
</p>
</div>
<!-- Feature Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
<!-- Feature 1: Visualize -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm">
<div class="mb-4 text-purple-600 dark:text-purple-400">
<i class="fa-solid fa-diagram-project text-3xl"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Visualisiere Wissen</h3>
<p class="text-gray-700 dark:text-gray-300">
Organisiere Gedanken und Ideen in einem interaktiven Netzwerk, das komplexe Beziehungen
visuell darstellt und neue Verbindungen offenbart.
</p>
</div>
<!-- Feature 2: Connect -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm">
<div class="mb-4 text-indigo-600 dark:text-indigo-400">
<i class="fa-solid fa-network-wired text-3xl"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Verknüpfe Gedanken</h3>
<p class="text-gray-700 dark:text-gray-300">
Entdecke Zusammenhänge zwischen scheinbar unzusammenhängenden Ideen und schaffe dein
persönliches Wissensnetzwerk über verschiedene Bereiche hinweg.
</p>
</div>
<!-- Feature 3: Share -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm">
<div class="mb-4 text-purple-600 dark:text-purple-400">
<i class="fa-solid fa-share-nodes text-3xl"></i>
</div>
<h3 class="text-xl font-semibold mb-2 text-gray-900 dark:text-white">Teile und Entdecke</h3>
<p class="text-gray-700 dark:text-gray-300">
Tausche Erkenntnisse mit anderen aus und erweitere dein Wissen durch die
Perspektiven und Gedanken der Community.
</p>
</div>
</div>
</div>
</section>
<!-- AI Assistant Preview -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="section-heading mb-4 text-gray-900 dark:text-white">Dein <span class="gradient-text">KI-Assistent</span></h2>
<p class="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
Unser integrierter KI-Assistent hilft dir, Wissen zu organisieren, Verbindungen zu finden und
Einsichten zu gewinnen.
</p>
</div>
<!-- Chat Interface Preview -->
<div class="max-w-3xl mx-auto embedded-chat">
<!-- Chat Header -->
<div class="p-4 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-3">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<span class="font-medium text-gray-800 dark:text-gray-200">Systades Assistent</span>
</div>
<div>
<button class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
<i class="fa-solid fa-expand"></i>
</button>
</div>
</div>
<!-- Chat Messages -->
<div id="embedded-chat-messages" class="border-b border-gray-200 dark:border-gray-700">
<!-- Assistant Message -->
<div class="mb-4 flex">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-700 dark:text-gray-300">
Hallo! Ich bin dein Systades-Assistent. Wie kann ich dir heute helfen? Du kannst mir Fragen zu deinen Gedanken stellen,
Verbindungen zwischen Konzepten finden oder Informationen zusammenfassen lassen.
</p>
</div>
</div>
<!-- User Message -->
<div class="mb-4 flex justify-end">
<div class="bg-purple-100 dark:bg-purple-900/30 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-800 dark:text-gray-200">
Kann ich mit deiner Hilfe eine Mindmap zum Thema Künstliche Intelligenz erstellen?
</p>
</div>
<div class="w-8 h-8 rounded-full bg-gray-300 dark:bg-gray-600 flex items-center justify-center text-gray-700 dark:text-gray-300 ml-2 flex-shrink-0">
<i class="fa-solid fa-user text-sm"></i>
</div>
</div>
<!-- Assistant Typing -->
<div class="flex items-center">
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3">
<div class="typing-dots">
<span></span>
<span></span>
<span></span>
</div>
</div>
</div>
</div>
<!-- Chat Input -->
<div class="p-4">
<div class="flex">
<input type="text" placeholder="Stelle eine Frage..." class="mystical-input flex-grow" disabled>
<button class="ml-2 bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-2 rounded-lg disabled:opacity-50" disabled>
<i class="fa-solid fa-paper-plane"></i>
</button>
</div>
<!-- Quick Queries -->
<div class="mt-3 flex flex-wrap gap-2">
<span class="text-xs text-gray-500 dark:text-gray-400 mr-1">Beispiele:</span>
<button class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300" disabled>Verbindungen finden</button>
<button class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300" disabled>Zusammenfassen</button>
<button class="quick-query-btn text-xs px-2 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300" disabled>Mindmap erstellen</button>
</div>
</div>
</div>
<!-- Try it Button -->
<div class="text-center mt-10">
<button onclick="window.MindMap && window.MindMap.assistant && window.MindMap.assistant.toggleAssistant(true)"
class="mystical-button mystical-button-primary inline-flex items-center">
<i class="fa-solid fa-robot mr-2"></i>
KI-Assistenten ausprobieren
</button>
</div>
</div>
</section>
<!-- Getting Started Section -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="text-center mb-12">
<h2 class="section-heading mb-4 text-gray-900 dark:text-white">So <span class="gradient-text">funktioniert's</span></h2>
<p class="text-lg text-gray-700 dark:text-gray-300 max-w-3xl mx-auto">
In wenigen einfachen Schritten kannst du mit Systades beginnen, dein Wissen zu organisieren.
</p>
</div>
<!-- Step Cards -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Step 1 -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm relative overflow-hidden">
<div class="absolute top-4 right-4 w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center text-purple-600 dark:text-purple-400 font-bold">
1
</div>
<h3 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Konto erstellen</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Registriere dich für ein kostenloses Konto, um deine persönliche Wissenslandschaft zu erstellen.
</p>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Jetzt registrieren <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
{% endif %}
</div>
<!-- Step 2 -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm relative overflow-hidden">
<div class="absolute top-4 right-4 w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center text-purple-600 dark:text-purple-400 font-bold">
2
</div>
<h3 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Mindmap erkunden</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Entdecke die öffentliche Wissensmindmap und füge Knoten zu deiner persönlichen Landschaft hinzu.
</p>
<a href="{{ url_for('mindmap') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Zur Mindmap <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
</div>
<!-- Step 3 -->
<div class="featured-card rounded-2xl p-6 bg-white/80 dark:bg-gray-800/30 backdrop-blur-sm relative overflow-hidden">
<div class="absolute top-4 right-4 w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/30 flex items-center justify-center text-purple-600 dark:text-purple-400 font-bold">
3
</div>
<h3 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Gedanken teilen</h3>
<p class="text-gray-700 dark:text-gray-300 mb-4">
Teile deine eigenen Gedanken, verbinde sie mit vorhandenen Knoten und baue das kollektive Wissen aus.
</p>
{% if current_user.is_authenticated %}
<a href="{{ url_for('profile') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Zum Profil <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
{% else %}
<a href="{{ url_for('login') }}" class="text-purple-600 dark:text-purple-400 hover:underline">
Jetzt anmelden <i class="fa-solid fa-arrow-right ml-1"></i>
</a>
{% endif %}
</div>
</div>
</div>
</section>
<!-- Call to Action -->
<section class="py-20 relative">
<div class="mystical-line absolute top-0 left-1/2 transform -translate-x-1/2 w-1/3"></div>
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<h2 class="section-heading mb-6 text-gray-900 dark:text-white">Bereit, dein <span class="gradient-text">Wissen zu vernetzen</span>?</h2>
<p class="text-lg text-gray-700 dark:text-gray-300 mb-8">
Tritt unserer wachsenden Community bei und entdecke eine neue Art, Wissen zu organisieren und zu teilen.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<a href="{{ url_for('mindmap') }}" class="mystical-button mystical-button-primary">
<i class="fa-solid fa-diagram-project mr-2"></i> Mindmap erkunden
</a>
{% if not current_user.is_authenticated %}
<a href="{{ url_for('register') }}" class="mystical-button mystical-button-secondary">
<i class="fa-solid fa-user-plus mr-2"></i> Konto erstellen
</a>
{% endif %}
</div>
</div>
</section>
{% endblock %}
{% block extra_js %}
<script>
// Simulate assistant typing and response
setTimeout(() => {
const chatMessages = document.getElementById('embedded-chat-messages');
const typingIndicator = chatMessages.querySelector('.flex:last-child');
if (typingIndicator) {
// Create assistant response
const response = document.createElement('div');
response.className = 'mb-4 flex';
response.innerHTML = `
<div class="w-8 h-8 rounded-full bg-gradient-to-r from-purple-600 to-indigo-600 flex items-center justify-center text-white mr-2 flex-shrink-0">
<i class="fa-solid fa-robot text-sm"></i>
</div>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 max-w-[80%]">
<p class="text-gray-700 dark:text-gray-300">
Natürlich! Ich kann dir dabei helfen, eine Mindmap zum Thema KI zu erstellen. Beginnen wir mit zentralen Konzepten wie Machine Learning, Neural Networks und Natural Language Processing. Möchtest du einen bestimmten Aspekt der KI genauer betrachten?
</p>
</div>
`;
// Remove typing indicator and add response
typingIndicator.remove();
chatMessages.appendChild(response);
// Scroll to bottom
chatMessages.scrollTop = chatMessages.scrollHeight;
}
}, 3000);
</script>
{% endblock %}

View File

@@ -1,613 +0,0 @@
{% extends "base.html" %}
{% block title %}Mindmap{% endblock %}
{% block extra_css %}
<style>
/* Full page background */
html, body {
min-height: 100vh;
width: 100%;
margin: 0;
padding: 0;
overflow-x: hidden;
}
/* Mindmap Container */
#mindmap-container {
position: relative;
min-height: calc(100vh - 160px);
z-index: 1;
}
/* Control Panel */
.control-panel {
position: fixed;
top: 100px;
left: 20px;
z-index: 10;
transition: all 0.3s ease;
border-radius: 1rem;
overflow: hidden;
border: 1px solid;
}
.dark .control-panel {
background-color: rgba(17, 24, 39, 0.8);
border-color: rgba(109, 40, 217, 0.3);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.control-panel {
background-color: rgba(255, 255, 255, 0.85);
border-color: rgba(139, 92, 246, 0.2);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
/* Control Panel Toggle */
.panel-toggle {
position: absolute;
top: 8px;
right: 8px;
z-index: 2;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
}
.dark .panel-toggle {
background-color: rgba(109, 40, 217, 0.2);
color: rgba(255, 255, 255, 0.8);
}
.panel-toggle {
background-color: rgba(139, 92, 246, 0.1);
color: rgba(30, 41, 59, 0.8);
}
.panel-toggle:hover {
transform: rotate(180deg);
}
/* Category Tree */
.category-tree {
max-height: 70vh;
overflow-y: auto;
}
.category-item {
transition: all 0.3s ease;
border-left: 2px solid transparent;
cursor: pointer;
}
.dark .category-item:hover {
background-color: rgba(109, 40, 217, 0.1);
border-left-color: rgba(139, 92, 246, 0.5);
}
.category-item:hover {
background-color: rgba(139, 92, 246, 0.05);
border-left-color: rgba(139, 92, 246, 0.5);
}
/* Node List */
.node-list {
max-height: 300px;
overflow-y: auto;
}
.node-item {
transition: all 0.3s ease;
border-radius: 0.5rem;
cursor: pointer;
}
.dark .node-item {
background-color: rgba(31, 41, 55, 0.5);
border: 1px solid rgba(55, 65, 81, 0.5);
}
.node-item {
background-color: rgba(255, 255, 255, 0.5);
border: 1px solid rgba(226, 232, 240, 0.5);
}
.dark .node-item:hover {
background-color: rgba(55, 65, 81, 0.7);
border-color: rgba(139, 92, 246, 0.5);
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}
.node-item:hover {
background-color: rgba(255, 255, 255, 0.8);
border-color: rgba(139, 92, 246, 0.3);
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05);
}
/* Node Counter Badge */
.node-counter {
min-width: 20px;
height: 20px;
border-radius: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 500;
padding: 0 6px;
}
.dark .node-counter {
background-color: rgba(109, 40, 217, 0.3);
color: rgba(255, 255, 255, 0.9);
}
.node-counter {
background-color: rgba(139, 92, 246, 0.1);
color: rgba(109, 40, 217, 0.9);
}
/* Canvas area */
#mindmap-canvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: 1;
}
/* Tooltip */
.tooltip-container {
position: absolute;
pointer-events: none;
z-index: 1000;
max-width: 300px;
opacity: 0;
transition: opacity 0.3s ease;
}
.dark .tooltip-container {
background-color: rgba(17, 24, 39, 0.9);
border: 1px solid rgba(109, 40, 217, 0.3);
color: rgba(255, 255, 255, 0.9);
}
.tooltip-container {
background-color: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(139, 92, 246, 0.2);
color: rgba(30, 41, 59, 0.9);
}
/* Search input */
.search-input {
transition: all 0.3s ease;
width: 100%;
border-radius: 0.5rem;
padding: 0.5rem 0.75rem;
outline: none;
}
.dark .search-input {
background-color: rgba(31, 41, 55, 0.7);
border: 1px solid rgba(55, 65, 81, 0.5);
color: rgba(255, 255, 255, 0.9);
}
.search-input {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(226, 232, 240, 0.8);
color: rgba(30, 41, 59, 0.9);
}
.dark .search-input:focus {
border-color: rgba(139, 92, 246, 0.5);
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2);
}
.search-input:focus {
border-color: rgba(139, 92, 246, 0.3);
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.1);
}
/* Mode toggle */
.mode-toggle {
padding: 0.5rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
}
.dark .mode-toggle {
background-color: rgba(31, 41, 55, 0.5);
border: 1px solid rgba(55, 65, 81, 0.5);
color: rgba(255, 255, 255, 0.9);
}
.mode-toggle {
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(226, 232, 240, 0.8);
color: rgba(30, 41, 59, 0.9);
}
.dark .mode-toggle.active {
background-color: rgba(109, 40, 217, 0.2);
border-color: rgba(139, 92, 246, 0.4);
color: rgba(255, 255, 255, 1);
}
.mode-toggle.active {
background-color: rgba(139, 92, 246, 0.1);
border-color: rgba(139, 92, 246, 0.3);
color: rgba(109, 40, 217, 1);
}
/* User Mindmaps */
.user-mindmap-section {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 10;
transition: all 0.3s ease;
border-radius: 1rem;
overflow: hidden;
max-width: 350px;
}
.dark .user-mindmap-section {
background-color: rgba(17, 24, 39, 0.85);
border: 1px solid rgba(109, 40, 217, 0.3);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
}
.user-mindmap-section {
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(139, 92, 246, 0.2);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
/* User Mindmap List */
.user-mindmap-item {
transition: all 0.3s ease;
border-radius: 0.5rem;
cursor: pointer;
}
.dark .user-mindmap-item {
background-color: rgba(31, 41, 55, 0.5);
border: 1px solid rgba(55, 65, 81, 0.5);
}
.user-mindmap-item {
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(226, 232, 240, 0.7);
}
.dark .user-mindmap-item:hover {
background-color: rgba(55, 65, 81, 0.7);
border-color: rgba(139, 92, 246, 0.4);
transform: translateY(-2px);
}
.user-mindmap-item:hover {
background-color: rgba(255, 255, 255, 0.9);
border-color: rgba(139, 92, 246, 0.3);
transform: translateY(-2px);
}
/* Zoom Controls */
.zoom-controls {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 10;
display: flex;
gap: 0.5rem;
border-radius: 2rem;
padding: 0.5rem;
transition: all 0.3s ease;
}
.dark .zoom-controls {
background-color: rgba(17, 24, 39, 0.7);
border: 1px solid rgba(55, 65, 81, 0.5);
}
.zoom-controls {
background-color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(226, 232, 240, 0.7);
}
.zoom-btn {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
cursor: pointer;
transition: all 0.3s ease;
}
.dark .zoom-btn {
background-color: rgba(31, 41, 55, 0.7);
color: rgba(255, 255, 255, 0.9);
}
.zoom-btn {
background-color: rgba(255, 255, 255, 0.9);
color: rgba(30, 41, 59, 0.9);
}
.dark .zoom-btn:hover {
background-color: rgba(139, 92, 246, 0.3);
color: rgba(255, 255, 255, 1);
}
.zoom-btn:hover {
background-color: rgba(139, 92, 246, 0.1);
color: rgba(109, 40, 217, 1);
}
/* Loading spinner */
.spinner {
border: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
border-top: 3px solid;
width: 24px;
height: 24px;
animation: spin 1s linear infinite;
}
.dark .spinner {
border-top-color: rgba(139, 92, 246, 0.7);
}
.spinner {
border-top-color: rgba(109, 40, 217, 0.7);
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
{% endblock %}
{% block content %}
<!-- Mindmap Container -->
<div id="mindmap-container">
<!-- Main Canvas -->
<div id="mindmap-canvas"></div>
<!-- Control Panel -->
<div class="control-panel p-4 w-64" id="control-panel" x-data="{ isExpanded: true }">
<div class="panel-toggle" @click="isExpanded = !isExpanded">
<i class="fa-solid" :class="isExpanded ? 'fa-chevron-left' : 'fa-chevron-right'"></i>
</div>
<div x-show="isExpanded">
<h2 class="text-xl font-semibold mb-4 text-gray-900 dark:text-white">Wissensbereiche</h2>
<!-- Search Box -->
<div class="mb-4">
<input type="text" id="category-search" class="search-input" placeholder="Bereich suchen...">
</div>
<!-- Category Tree -->
<div class="category-tree">
<!-- Recursive template for categories -->
<script type="text/x-template" id="category-template">
<div class="category-item pl-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-700"
:class="[level > 0 ? 'ml-' + (level * 2) : '']"
@click="toggleCategory(category.id)">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fa-solid" :class="[isExpanded ? 'fa-chevron-down' : 'fa-chevron-right', 'mr-2 text-sm transition-transform']"></i>
<span :class="{'font-medium': isActive}">
<i class="fa-solid mr-2" :class="category.icon || 'fa-folder'"></i>
${category.name}
</span>
</div>
<span class="node-counter">${category.nodes.length}</span>
</div>
<!-- Nodes for this category -->
<div x-show="isExpanded && isActive" class="mt-2 node-list pl-4">
<div v-for="node in category.nodes" class="node-item p-2 mb-2"
:style="{ borderLeftColor: node.color_code }"
@click.stop="addNodeToCanvas(node)">
<div class="flex items-center justify-between">
<div>${node.name}</div>
<span class="node-counter">${node.thought_count}</span>
</div>
</div>
</div>
<!-- Subcategories recursive -->
<div x-show="isExpanded" class="mt-2">
<template v-for="child in category.children">
<category-item :category="child" :level="level + 1"></category-item>
</template>
</div>
</div>
</script>
<!-- Root categories rendered here -->
<div id="categories-container"></div>
</div>
<!-- View Mode Toggle -->
<div class="mt-4">
<h3 class="text-sm font-medium mb-2 text-gray-700 dark:text-gray-300">Ansicht</h3>
<div class="flex justify-between gap-2">
<button id="view-all" class="mode-toggle text-sm flex-1 active">
<i class="fa-solid fa-diagram-project mr-1"></i> Alles
</button>
<button id="view-focused" class="mode-toggle text-sm flex-1">
<i class="fa-solid fa-bullseye mr-1"></i> Fokus
</button>
</div>
</div>
</div>
</div>
<!-- User Mindmaps Section -->
{% if current_user.is_authenticated %}
<div class="user-mindmap-section p-4 w-64" x-data="{ isExpanded: true }">
<div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Meine Mindmaps</h2>
<button @click="isExpanded = !isExpanded" class="text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">
<i class="fa-solid" :class="isExpanded ? 'fa-chevron-down' : 'fa-chevron-up'"></i>
</button>
</div>
<div x-show="isExpanded">
<!-- User Mindmap List -->
<div class="space-y-2 max-h-60 overflow-y-auto mb-3">
<!-- Will be populated by JS -->
<div id="user-mindmaps-list" class="space-y-2"></div>
</div>
<!-- Add New Mindmap Button -->
<a href="{{ url_for('create_mindmap') }}" class="mystical-button mystical-button-primary w-full text-center text-sm">
<i class="fa-solid fa-plus mr-1"></i> Neue Mindmap erstellen
</a>
</div>
</div>
{% endif %}
<!-- Zoom Controls -->
<div class="zoom-controls">
<button id="zoom-in" class="zoom-btn">
<i class="fa-solid fa-plus"></i>
</button>
<button id="zoom-out" class="zoom-btn">
<i class="fa-solid fa-minus"></i>
</button>
<button id="reset-view" class="zoom-btn">
<i class="fa-solid fa-home"></i>
</button>
</div>
<!-- Node Tooltip -->
<div id="node-tooltip" class="tooltip-container rounded-lg p-4 shadow-lg"></div>
</div>
{% endblock %}
{% block extra_js %}
<!-- D3.js for Mindmap Visualization -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Custom D3 Extensions -->
<script src="{{ url_for('static', filename='d3-extensions.js') }}"></script>
<!-- Mindmap Script -->
<script src="{{ url_for('static', filename='mindmap.js') }}"></script>
<script>
// Initialize the public mindmap
document.addEventListener('DOMContentLoaded', function() {
// Set up for dark/light mode changes
const isDarkMode = document.documentElement.classList.contains('dark');
// Initialize the node tooltip
const tooltip = document.getElementById('node-tooltip');
// Initialize mindmap
const mindmap = new MindMap({
container: '#mindmap-canvas',
apiEndpoint: '/api/mindmap/public',
isDarkMode: isDarkMode,
tooltip: tooltip
});
// Load public mindmap data
mindmap.loadData().then(() => {
console.log('Mindmap data loaded');
});
// View mode toggle
document.getElementById('view-all').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('view-focused').classList.remove('active');
mindmap.setViewMode('all');
});
document.getElementById('view-focused').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('view-all').classList.remove('active');
mindmap.setViewMode('focus');
});
// Zoom controls
document.getElementById('zoom-in').addEventListener('click', function() {
mindmap.zoomIn();
});
document.getElementById('zoom-out').addEventListener('click', function() {
mindmap.zoomOut();
});
document.getElementById('reset-view').addEventListener('click', function() {
mindmap.resetView();
});
// Handle dark mode toggle
document.addEventListener('darkModeToggled', function(event) {
const isDark = event.detail.isDark;
mindmap.updateTheme(isDark);
});
// Search functionality
const searchInput = document.getElementById('category-search');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
mindmap.searchCategories(searchTerm);
});
{% if current_user.is_authenticated %}
// Load user mindmaps
fetch('/api/mindmap/user')
.then(response => response.json())
.then(data => {
const mindmapsList = document.getElementById('user-mindmaps-list');
mindmapsList.innerHTML = '';
if (data.length === 0) {
mindmapsList.innerHTML = `
<div class="text-center text-gray-500 dark:text-gray-400 py-2">
Keine Mindmaps gefunden
</div>
`;
return;
}
data.forEach(mindmap => {
const item = document.createElement('div');
item.className = 'user-mindmap-item p-3';
item.innerHTML = `
<div class="font-medium text-gray-800 dark:text-gray-200">${mindmap.name}</div>
<div class="text-xs text-gray-500 dark:text-gray-400 mb-2">${mindmap.description}</div>
<a href="/my-mindmap/${mindmap.id}" class="text-purple-600 dark:text-purple-400 text-xs hover:underline">
<i class="fa-solid fa-arrow-right mr-1"></i> Öffnen
</a>
`;
mindmapsList.appendChild(item);
});
})
.catch(error => {
console.error('Error loading user mindmaps:', error);
});
{% endif %}
});
</script>
{% endblock %}