diff --git a/website/.env b/website/.env index a44a6ea..a0ab5ba 100644 --- a/website/.env +++ b/website/.env @@ -1 +1,13 @@ -OPENAI_API_KEY=sk-placeholder +# MindMap Umgebungsvariablen +# Kopiere diese Datei zu .env und passe die Werte an + +# Flask +SECRET_KEY=dein-geheimer-schluessel-hier + +# OpenAI API +OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier + +# Datenbank +# Bei Bedarf kann hier eine andere Datenbank-URL angegeben werden +# Der Pfad wird relativ zum Projektverzeichnis angegeben +# SQLALCHEMY_DATABASE_URI=sqlite:////absoluter/pfad/zu/database/systades.db OPENAI_API_KEY=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 diff --git a/website/app.py b/website/app.py index 8cd9d24..5ee97eb 100755 --- a/website/app.py +++ b/website/app.py @@ -26,7 +26,7 @@ from models import ( ) # Lade .env-Datei -load_dotenv() +load_dotenv(force=True) # force=True erzwingt die Synchronisierung # Bestimme den absoluten Pfad zur Datenbank basedir = os.path.abspath(os.path.dirname(__file__)) @@ -1126,6 +1126,31 @@ def dummy_network_bg(): """Leere Antwort für die nicht mehr verwendeten Netzwerk-Hintergrundbilder.""" return '', 200 +# Route zum expliziten Neu-Laden der Umgebungsvariablen +@app.route('/admin/reload-env', methods=['POST']) +@admin_required +def reload_env(): + """Lädt die Umgebungsvariablen aus der .env-Datei neu.""" + try: + # Erzwinge das Neuladen der .env-Datei + load_dotenv(override=True, force=True) + + # Aktualisiere OpenAI API-Key + openai.api_key = os.environ.get('OPENAI_API_KEY') + + # Weitere Umgebungsvariablen hier aktualisieren, falls nötig + app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', app.config['SECRET_KEY']) + + return jsonify({ + 'success': True, + 'message': 'Umgebungsvariablen wurden erfolgreich neu geladen.' + }) + except Exception as e: + return jsonify({ + 'success': False, + 'message': f'Fehler beim Neuladen der Umgebungsvariablen: {str(e)}' + }), 500 + # Flask starten if __name__ == '__main__': with app.app_context(): diff --git a/website/build_css.py b/website/build_css.py deleted file mode 100755 index 96fae31..0000000 --- a/website/build_css.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python3 -import os -import subprocess -import shutil -import platform -import tempfile -import zipfile -import requests -from pathlib import Path - -def download_tailwind_cli(): - """Download the standalone Tailwind CLI for the current platform""" - # Get current system info - system = platform.system().lower() - arch = platform.machine().lower() - - # Map to tailwind downloadable platform - if system == "linux": - platform_name = "linux" - elif system == "darwin": - platform_name = "macos" - elif system == "windows": - platform_name = "windows" - else: - raise Exception(f"Unsupported platform: {system}") - - # Map architecture - if "arm" in arch or "aarch" in arch: - arch_name = "arm64" - elif "x86_64" in arch or "amd64" in arch or "x64" in arch: - arch_name = "x64" - else: - # Default to x64 for other architectures - arch_name = "x64" - - # URL for the binary - url = f"https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-{platform_name}-{arch_name}" - if system == "windows": - url += ".exe" - - # Download location - bin_dir = Path("bin") - bin_dir.mkdir(exist_ok=True) - tailwind_bin = bin_dir / f"tailwindcss{'.exe' if system == 'windows' else ''}" - - # Download if not exists - if not tailwind_bin.exists(): - print(f"Downloading Tailwind CLI from {url}...") - response = requests.get(url, stream=True) - response.raise_for_status() - - with open(tailwind_bin, 'wb') as f: - for chunk in response.iter_content(chunk_size=8192): - f.write(chunk) - - # Make executable on Unix - if system != "windows": - os.chmod(tailwind_bin, 0o755) - - return str(tailwind_bin) - -def build_css(watch=False): - """Build CSS using Tailwind CLI""" - base_dir = Path(__file__).parent - input_file = base_dir / "static" / "css" / "src" / "input.css" - output_file = base_dir / "static" / "css" / "main.css" - - tailwind_bin = download_tailwind_cli() - - config_file = base_dir / "tailwind.config.js" - - # Prepare command - cmd = [ - tailwind_bin, - "-i", str(input_file), - "-o", str(output_file), - "-c", str(config_file) - ] - - if watch: - cmd.append("--watch") - - print(f"Running: {' '.join(cmd)}") - - # Run the command - if watch: - process = subprocess.Popen(cmd) - try: - process.wait() - except KeyboardInterrupt: - process.terminate() - print("\nBuild process terminated") - else: - subprocess.run(cmd, check=True) - print(f"CSS built successfully: {output_file}") - -if __name__ == "__main__": - import argparse - - parser = argparse.ArgumentParser(description="Build Tailwind CSS") - parser.add_argument("--watch", action="store_true", help="Watch for changes") - args = parser.parse_args() - - build_css(watch=args.watch) \ No newline at end of file diff --git a/website/dev.py b/website/dev.py deleted file mode 100755 index 058c558..0000000 --- a/website/dev.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import subprocess -import threading -import time -from pathlib import Path - -def run_flask(): - """Run the Flask development server""" - env = os.environ.copy() - env["FLASK_ENV"] = "development" - env["FLASK_DEBUG"] = "1" - - # Import and run the Flask app - from run import app - app.run(debug=True, use_reloader=False) - -def run_tailwind_watch(): - """Run the Tailwind CSS watcher""" - # Import the build_css function from build_css.py - from build_css import build_css - build_css(watch=True) - -def main(): - """Run both the Flask server and Tailwind CSS watcher""" - print("Starting development environment...") - - # Start Tailwind watcher in a separate thread - tailwind_thread = threading.Thread(target=run_tailwind_watch, daemon=True) - tailwind_thread.start() - - # Give Tailwind a moment to start - time.sleep(1) - - # Run Flask in the main thread - print("Starting Flask development server...") - run_flask() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/website/run.py b/website/run.py index 174d8db..7657b40 100755 --- a/website/run.py +++ b/website/run.py @@ -2,10 +2,19 @@ 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(): diff --git a/website/setup.sh b/website/setup.sh index 10d32ea..32dcdf1 100755 --- a/website/setup.sh +++ b/website/setup.sh @@ -4,6 +4,7 @@ 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}" @@ -15,6 +16,17 @@ 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 diff --git a/website/static/css/base-styles.css b/website/static/css/base-styles.css new file mode 100644 index 0000000..f7c70e9 --- /dev/null +++ b/website/static/css/base-styles.css @@ -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); +} \ No newline at end of file diff --git a/website/templates/base.html b/website/templates/base.html index 445af5f..84afdac 100644 --- a/website/templates/base.html +++ b/website/templates/base.html @@ -14,6 +14,55 @@ + + + + @@ -28,8 +77,8 @@ - - + + @@ -102,437 +151,7 @@ // MindMap global verfügbar machen (für Alpine.js und andere nicht-Module Skripte) window.MindMap = MindMap; - - - - {% block extra_css %}{% endblock %}