Enhance footer layout and mindmap functionality: Revamp footer structure with improved grid layout, add social media icons, and implement a newsletter subscription form. Update mindmap template to use SVG background, streamline script loading, and enhance visualization initialization with new event handlers for user interactions.
This commit is contained in:
230
website/models.py
Normal file
230
website/models.py
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from datetime import datetime
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
# Beziehungstypen für Gedankenverknüpfungen
|
||||||
|
class RelationType(Enum):
|
||||||
|
SUPPORTS = "stützt"
|
||||||
|
CONTRADICTS = "widerspricht"
|
||||||
|
BUILDS_UPON = "baut auf auf"
|
||||||
|
GENERALIZES = "verallgemeinert"
|
||||||
|
SPECIFIES = "spezifiziert"
|
||||||
|
INSPIRES = "inspiriert"
|
||||||
|
|
||||||
|
# Beziehungstabelle für viele-zu-viele Beziehung zwischen MindMapNodes
|
||||||
|
node_relationship = db.Table('node_relationship',
|
||||||
|
db.Column('parent_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
|
||||||
|
db.Column('child_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Beziehungstabelle für öffentliche Knoten und Gedanken
|
||||||
|
node_thought_association = db.Table('node_thought_association',
|
||||||
|
db.Column('node_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
|
||||||
|
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Beziehungstabelle für Benutzer-spezifische Mindmap-Knoten und Gedanken
|
||||||
|
user_mindmap_thought_association = db.Table('user_mindmap_thought_association',
|
||||||
|
db.Column('user_mindmap_id', db.Integer, db.ForeignKey('user_mindmap.id'), primary_key=True),
|
||||||
|
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Beziehungstabelle für Benutzer-Bookmarks von Gedanken
|
||||||
|
user_thought_bookmark = db.Table('user_thought_bookmark',
|
||||||
|
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
|
||||||
|
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True),
|
||||||
|
db.Column('created_at', db.DateTime, default=datetime.utcnow)
|
||||||
|
)
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||||
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
|
password_hash = db.Column(db.String(128))
|
||||||
|
is_admin = db.Column(db.Boolean, default=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
last_login = db.Column(db.DateTime)
|
||||||
|
avatar = db.Column(db.String(200))
|
||||||
|
bio = db.Column(db.Text)
|
||||||
|
|
||||||
|
# Beziehungen
|
||||||
|
thoughts = db.relationship('Thought', backref='author', lazy=True)
|
||||||
|
comments = db.relationship('Comment', backref='author', lazy=True)
|
||||||
|
user_mindmaps = db.relationship('UserMindmap', backref='user', lazy=True)
|
||||||
|
mindmap_notes = db.relationship('MindmapNote', backref='user', lazy=True)
|
||||||
|
bookmarked_thoughts = db.relationship('Thought', secondary=user_thought_bookmark,
|
||||||
|
backref=db.backref('bookmarked_by', lazy='dynamic'))
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
self.password_hash = generate_password_hash(password)
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return check_password_hash(self.password_hash, password)
|
||||||
|
|
||||||
|
class Category(db.Model):
|
||||||
|
"""Wissenschaftliche Kategorien für die Gliederung der öffentlichen Mindmap"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
color_code = db.Column(db.String(7)) # Hex color
|
||||||
|
icon = db.Column(db.String(50))
|
||||||
|
parent_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True)
|
||||||
|
|
||||||
|
# Beziehungen
|
||||||
|
children = db.relationship('Category', backref=db.backref('parent', remote_side=[id]))
|
||||||
|
nodes = db.relationship('MindMapNode', backref='category', lazy=True)
|
||||||
|
|
||||||
|
class MindMapNode(db.Model):
|
||||||
|
"""Öffentliche Mindmap-Knoten, die für alle Benutzer sichtbar sind"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(100), nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
color_code = db.Column(db.String(7))
|
||||||
|
icon = db.Column(db.String(50))
|
||||||
|
is_public = db.Column(db.Boolean, default=True)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
|
||||||
|
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True)
|
||||||
|
|
||||||
|
# Beziehungen für Baumstruktur (mehrere Eltern möglich)
|
||||||
|
parents = db.relationship(
|
||||||
|
'MindMapNode',
|
||||||
|
secondary=node_relationship,
|
||||||
|
primaryjoin=(node_relationship.c.child_id == id),
|
||||||
|
secondaryjoin=(node_relationship.c.parent_id == id),
|
||||||
|
backref=db.backref('children', lazy='dynamic'),
|
||||||
|
lazy='dynamic'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Beziehungen zu Gedanken
|
||||||
|
thoughts = db.relationship('Thought',
|
||||||
|
secondary=node_thought_association,
|
||||||
|
backref=db.backref('nodes', lazy='dynamic'))
|
||||||
|
|
||||||
|
# Beziehung zum Ersteller
|
||||||
|
created_by = db.relationship('User', backref='created_nodes')
|
||||||
|
|
||||||
|
class UserMindmap(db.Model):
|
||||||
|
"""Benutzerspezifische Mindmap, die vom Benutzer personalisierbar ist"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(100), nullable=False)
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
is_private = db.Column(db.Boolean, default=True)
|
||||||
|
|
||||||
|
# Beziehungen zu öffentlichen Knoten
|
||||||
|
public_nodes = db.relationship('MindMapNode',
|
||||||
|
secondary='user_mindmap_node',
|
||||||
|
backref=db.backref('in_user_mindmaps', lazy='dynamic'))
|
||||||
|
|
||||||
|
# Beziehungen zu Gedanken
|
||||||
|
thoughts = db.relationship('Thought',
|
||||||
|
secondary=user_mindmap_thought_association,
|
||||||
|
backref=db.backref('in_user_mindmaps', lazy='dynamic'))
|
||||||
|
|
||||||
|
# Notizen zu dieser Mindmap
|
||||||
|
notes = db.relationship('MindmapNote', backref='mindmap', lazy=True)
|
||||||
|
|
||||||
|
# Beziehungstabelle für benutzerorientierte Mindmaps und öffentliche Knoten
|
||||||
|
class UserMindmapNode(db.Model):
|
||||||
|
"""Speichert die Beziehung zwischen Benutzer-Mindmaps und öffentlichen Knoten inkl. Position"""
|
||||||
|
user_mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), primary_key=True)
|
||||||
|
node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True)
|
||||||
|
x_position = db.Column(db.Float, default=0) # Position X auf der Mindmap
|
||||||
|
y_position = db.Column(db.Float, default=0) # Position Y auf der Mindmap
|
||||||
|
scale = db.Column(db.Float, default=1.0) # Größe des Knotens
|
||||||
|
added_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
class MindmapNote(db.Model):
|
||||||
|
"""Private Notizen der Benutzer zu ihrer Mindmap"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), nullable=False)
|
||||||
|
node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
|
||||||
|
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
|
||||||
|
content = db.Column(db.Text, nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
color_code = db.Column(db.String(7), default="#FFF59D") # Farbe der Notiz
|
||||||
|
|
||||||
|
class Thought(db.Model):
|
||||||
|
"""Gedanken und Inhalte, die in der Mindmap verknüpft werden können"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
title = db.Column(db.String(200), nullable=False)
|
||||||
|
content = db.Column(db.Text, nullable=False)
|
||||||
|
abstract = db.Column(db.Text)
|
||||||
|
keywords = db.Column(db.String(500))
|
||||||
|
color_code = db.Column(db.String(7)) # Hex color code
|
||||||
|
source_type = db.Column(db.String(50)) # PDF, Markdown, Text etc.
|
||||||
|
branch = db.Column(db.String(100), nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
|
||||||
|
# Beziehungen
|
||||||
|
comments = db.relationship('Comment', backref='thought', lazy=True, cascade="all, delete-orphan")
|
||||||
|
ratings = db.relationship('ThoughtRating', backref='thought', lazy=True)
|
||||||
|
|
||||||
|
outgoing_relations = db.relationship(
|
||||||
|
'ThoughtRelation',
|
||||||
|
foreign_keys='ThoughtRelation.source_id',
|
||||||
|
backref='source_thought',
|
||||||
|
lazy=True
|
||||||
|
)
|
||||||
|
incoming_relations = db.relationship(
|
||||||
|
'ThoughtRelation',
|
||||||
|
foreign_keys='ThoughtRelation.target_id',
|
||||||
|
backref='target_thought',
|
||||||
|
lazy=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def average_rating(self):
|
||||||
|
if not self.ratings:
|
||||||
|
return 0
|
||||||
|
return sum(r.relevance_score for r in self.ratings) / len(self.ratings)
|
||||||
|
|
||||||
|
class ThoughtRelation(db.Model):
|
||||||
|
"""Beziehungen zwischen Gedanken"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
source_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||||
|
target_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||||
|
relation_type = db.Column(db.Enum(RelationType), nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
|
||||||
|
# Beziehung zum Ersteller
|
||||||
|
created_by = db.relationship('User', backref='created_relations')
|
||||||
|
|
||||||
|
class ThoughtRating(db.Model):
|
||||||
|
"""Bewertungen von Gedanken durch Benutzer"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
relevance_score = db.Column(db.Integer, nullable=False) # 1-5
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
__table_args__ = (
|
||||||
|
db.UniqueConstraint('thought_id', 'user_id', name='unique_thought_rating'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Beziehung zum Benutzer
|
||||||
|
user = db.relationship('User', backref='ratings')
|
||||||
|
|
||||||
|
class Comment(db.Model):
|
||||||
|
"""Kommentare zu Gedanken"""
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
content = db.Column(db.Text, nullable=False)
|
||||||
|
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
@@ -734,56 +734,119 @@
|
|||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<footer class="mt-12 py-10 transition-colors duration-300 rounded-t-3xl mx-4 sm:mx-6 md:mx-8"
|
<footer class="mt-12 py-10 transition-colors duration-300 rounded-t-3xl mx-4 sm:mx-6 md:mx-8"
|
||||||
:class="darkMode ? 'bg-gray-900/40 backdrop-blur-md border border-white/10' : 'bg-white/40 backdrop-blur-md border border-gray-200/50'">
|
:class="darkMode ? 'bg-gray-900/60 backdrop-blur-xl border-t border-white/10' : 'bg-white/60 backdrop-blur-xl border-t border-gray-200/50'">
|
||||||
<div class="container mx-auto px-4">
|
<div class="container mx-auto px-4">
|
||||||
<div class="flex flex-col md:flex-row justify-between items-center md:items-start">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||||
<div class="mb-8 md:mb-0 text-center md:text-left">
|
<!-- Logo und Beschreibung -->
|
||||||
<a href="{{ url_for('index') }}" class="text-2xl font-bold gradient-text inline-block transform transition-transform hover:scale-105">Systades</a>
|
<div class="text-center md:text-left flex flex-col">
|
||||||
<p class="mt-3 text-sm max-w-sm"
|
<a href="{{ url_for('index') }}" class="text-2xl font-bold mb-4 gradient-text inline-block transform transition-transform hover:scale-105">Systades</a>
|
||||||
:class="darkMode ? 'text-gray-400' : 'text-gray-600'">Eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen.</p>
|
<p class="mt-2 text-sm max-w-md"
|
||||||
|
:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||||
|
Eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen und Gedanken in einem strukturierten Format.
|
||||||
|
</p>
|
||||||
|
<!-- Social Media Icons -->
|
||||||
|
<div class="flex items-center space-x-4 mt-6 justify-center md:justify-start">
|
||||||
|
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
|
||||||
|
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
|
||||||
|
<i class="fab fa-twitter text-xl"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
|
||||||
|
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
|
||||||
|
<i class="fab fa-linkedin text-xl"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
|
||||||
|
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
|
||||||
|
<i class="fab fa-github text-xl"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
|
||||||
|
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
|
||||||
|
<i class="fab fa-discord text-xl"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap justify-center md:justify-start space-x-4 sm:space-x-6 mb-8 md:mb-0">
|
<!-- Links -->
|
||||||
<a href="{{ url_for('impressum') }}"
|
<div class="grid grid-cols-2 gap-8">
|
||||||
class="transition-all duration-200 text-sm px-4 py-2.5 rounded-xl"
|
<div class="flex flex-col space-y-3">
|
||||||
:class="darkMode ? 'text-gray-400 hover:text-white hover:bg-purple-600/20 hover:shadow-md' : 'text-gray-700 hover:text-gray-900 hover:bg-purple-500/10 hover:shadow-sm'">
|
<h3 class="font-semibold text-lg mb-2"
|
||||||
<span class="flex items-center">
|
:class="darkMode ? 'text-white' : 'text-gray-800'">Navigation</h3>
|
||||||
<i class="fa-solid fa-info-circle mr-2 opacity-70"></i>
|
<a href="{{ url_for('index') }}" class="text-sm transition-all duration-200"
|
||||||
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
|
Startseite
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('mindmap') }}" class="text-sm transition-all duration-200"
|
||||||
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
|
Mindmap
|
||||||
|
</a>
|
||||||
|
{% if current_user.is_authenticated %}
|
||||||
|
<a href="{{ url_for('profile') }}" class="text-sm transition-all duration-200"
|
||||||
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
|
Profil
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('my_account') }}" class="text-sm transition-all duration-200"
|
||||||
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
|
Meine Merkliste
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ url_for('login') }}" class="text-sm transition-all duration-200"
|
||||||
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
|
Anmelden
|
||||||
|
</a>
|
||||||
|
<a href="{{ url_for('register') }}" class="text-sm transition-all duration-200"
|
||||||
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
|
Registrieren
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col space-y-3">
|
||||||
|
<h3 class="font-semibold text-lg mb-2"
|
||||||
|
:class="darkMode ? 'text-white' : 'text-gray-800'">Rechtliches</h3>
|
||||||
|
<a href="{{ url_for('impressum') }}" class="text-sm transition-all duration-200"
|
||||||
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
Impressum
|
Impressum
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('datenschutz') }}"
|
<a href="{{ url_for('datenschutz') }}" class="text-sm transition-all duration-200"
|
||||||
class="transition-all duration-200 text-sm px-4 py-2.5 rounded-xl"
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
:class="darkMode ? 'text-gray-400 hover:text-white hover:bg-purple-600/20 hover:shadow-md' : 'text-gray-700 hover:text-gray-900 hover:bg-purple-500/10 hover:shadow-sm'">
|
|
||||||
<span class="flex items-center">
|
|
||||||
<i class="fa-solid fa-shield-alt mr-2 opacity-70"></i>
|
|
||||||
Datenschutz
|
Datenschutz
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ url_for('agb') }}"
|
<a href="{{ url_for('agb') }}" class="text-sm transition-all duration-200"
|
||||||
class="transition-all duration-200 text-sm px-4 py-2.5 rounded-xl"
|
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
|
||||||
:class="darkMode ? 'text-gray-400 hover:text-white hover:bg-purple-600/20 hover:shadow-md' : 'text-gray-700 hover:text-gray-900 hover:bg-purple-500/10 hover:shadow-sm'">
|
|
||||||
<span class="flex items-center">
|
|
||||||
<i class="fa-solid fa-file-contract mr-2 opacity-70"></i>
|
|
||||||
AGB
|
AGB
|
||||||
</span>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Optional: Social Media Links oder andere Elemente -->
|
|
||||||
{#
|
|
||||||
<div class="flex space-x-4">
|
|
||||||
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-twitter"></i></a>
|
|
||||||
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-linkedin"></i></a>
|
|
||||||
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-github"></i></a>
|
|
||||||
</div>
|
|
||||||
#}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 pt-6 text-center text-xs"
|
<!-- Newsletter Anmeldung -->
|
||||||
:class="darkMode ? 'border-t border-gray-800/50 text-gray-500' : 'border-t border-gray-300/50 text-gray-600'">
|
<div class="flex flex-col">
|
||||||
|
<h3 class="font-semibold text-lg mb-4"
|
||||||
|
:class="darkMode ? 'text-white' : 'text-gray-800'">Newsletter</h3>
|
||||||
|
<p class="text-sm mb-4"
|
||||||
|
:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||||
|
Bleibe auf dem Laufenden mit unseren neuesten Funktionen und Updates.
|
||||||
|
</p>
|
||||||
|
<form class="flex flex-col space-y-3">
|
||||||
|
<input type="email" placeholder="Deine E-Mail Adresse"
|
||||||
|
class="px-4 py-2.5 rounded-xl transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
:class="darkMode ? 'bg-gray-800/80 text-white border border-gray-700 focus:bg-gray-800' : 'bg-white/80 text-gray-800 border border-gray-300 focus:bg-white'" />
|
||||||
|
<button type="submit"
|
||||||
|
class="px-4 py-2.5 rounded-xl font-medium transition-all duration-300 bg-gradient-to-r from-purple-600 to-indigo-600 text-white shadow-md hover:shadow-lg hover:-translate-y-0.5">
|
||||||
|
Abonnieren
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Untere Linie -->
|
||||||
|
<div class="mt-10 pt-6 border-t flex flex-col md:flex-row justify-between items-center"
|
||||||
|
:class="darkMode ? 'border-gray-800/50 text-gray-400' : 'border-gray-300/50 text-gray-600'">
|
||||||
|
<div class="text-xs md:text-sm mb-3 md:mb-0">
|
||||||
© {{ current_year }} Systades. Alle Rechte vorbehalten.
|
© {{ current_year }} Systades. Alle Rechte vorbehalten.
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-xs md:text-sm">
|
||||||
|
Designed with <i class="fas fa-heart text-pink-500"></i> in Deutschland
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -516,7 +516,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-image: url('/static/network-bg.jpg');
|
background-image: url('/static/network-bg.svg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
@@ -651,237 +651,101 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block scripts %}
|
||||||
<!-- D3.js für die Mindmap-Visualisierung -->
|
<!-- D3.js Library -->
|
||||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||||
<!-- Tippy.js für verbesserte Tooltips -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
|
<!-- Mindmap scripts -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/tippy.js@6.3.7/dist/tippy.umd.min.js"></script>
|
|
||||||
<!-- D3-Erweiterungen für spezifische Effekte -->
|
|
||||||
<script src="{{ url_for('static', filename='d3-extensions.js') }}"></script>
|
<script src="{{ url_for('static', filename='d3-extensions.js') }}"></script>
|
||||||
<!-- Mindmap JS -->
|
|
||||||
<script src="{{ url_for('static', filename='mindmap.js') }}"></script>
|
<script src="{{ url_for('static', filename='mindmap.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='network-animation.js') }}"></script>
|
|
||||||
|
|
||||||
|
<!-- Initialization Script -->
|
||||||
<script>
|
<script>
|
||||||
// Dynamische Neuronen-Netz-Animation im Hintergrund
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
// Inizialize the mindmap visualization
|
||||||
// Animation des neuronalen Netzwerks hinzufügen
|
|
||||||
const neuralBg = document.querySelector('.neural-universe-bg');
|
|
||||||
|
|
||||||
// Neuronenpunkte erstellen
|
|
||||||
const neuronCount = 100;
|
|
||||||
for (let i = 0; i < neuronCount; i++) {
|
|
||||||
const neuron = document.createElement('div');
|
|
||||||
neuron.className = 'neuron-point';
|
|
||||||
|
|
||||||
// Zufällige Position
|
|
||||||
const posX = Math.random() * 100;
|
|
||||||
const posY = Math.random() * 100;
|
|
||||||
const size = Math.random() * 3 + 1;
|
|
||||||
const animDuration = Math.random() * 50 + 20;
|
|
||||||
|
|
||||||
// Styling mit Glasmorphismus
|
|
||||||
neuron.style.cssText = `
|
|
||||||
position: absolute;
|
|
||||||
left: ${posX}%;
|
|
||||||
top: ${posY}%;
|
|
||||||
width: ${size}px;
|
|
||||||
height: ${size}px;
|
|
||||||
background: rgba(255, 255, 255, ${Math.random() * 0.3 + 0.1});
|
|
||||||
border-radius: 50%;
|
|
||||||
filter: blur(${Math.random() * 1}px);
|
|
||||||
box-shadow: 0 0 ${Math.random() * 10 + 5}px rgba(179, 143, 255, 0.5);
|
|
||||||
animation: pulse ${animDuration}s infinite alternate ease-in-out;
|
|
||||||
`;
|
|
||||||
|
|
||||||
neuralBg.appendChild(neuron);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verbindungen zwischen Neuronen erstellen
|
|
||||||
const connectionCount = 40;
|
|
||||||
for (let i = 0; i < connectionCount; i++) {
|
|
||||||
const connection = document.createElement('div');
|
|
||||||
connection.className = 'neuron-connection';
|
|
||||||
|
|
||||||
// Zufällige Position und Rotation für Verbindungen
|
|
||||||
const posX = Math.random() * 100;
|
|
||||||
const posY = Math.random() * 100;
|
|
||||||
const width = Math.random() * 150 + 50;
|
|
||||||
const height = Math.random() * 1 + 0.5;
|
|
||||||
const rotation = Math.random() * 360;
|
|
||||||
const opacity = Math.random() * 0.2 + 0.05;
|
|
||||||
const animDuration = Math.random() * 20 + 10;
|
|
||||||
|
|
||||||
connection.style.cssText = `
|
|
||||||
position: absolute;
|
|
||||||
left: ${posX}%;
|
|
||||||
top: ${posY}%;
|
|
||||||
width: ${width}px;
|
|
||||||
height: ${height}px;
|
|
||||||
background: linear-gradient(90deg, transparent, rgba(179, 143, 255, ${opacity}), transparent);
|
|
||||||
transform: rotate(${rotation}deg);
|
|
||||||
animation: flash ${animDuration}s infinite alternate ease-in-out;
|
|
||||||
opacity: ${opacity};
|
|
||||||
`;
|
|
||||||
|
|
||||||
neuralBg.appendChild(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialisiere die Mindmap-Visualisierung
|
|
||||||
const mindmapContainer = document.getElementById('mindmap-container');
|
const mindmapContainer = document.getElementById('mindmap-container');
|
||||||
const containerWidth = mindmapContainer.clientWidth;
|
|
||||||
const containerHeight = mindmapContainer.clientHeight;
|
|
||||||
|
|
||||||
const mindmap = new MindMapVisualization('#mindmap-container', {
|
// Options for the visualization
|
||||||
width: containerWidth,
|
const options = {
|
||||||
height: containerHeight,
|
width: mindmapContainer.clientWidth,
|
||||||
|
height: mindmapContainer.clientHeight,
|
||||||
nodeRadius: 22,
|
nodeRadius: 22,
|
||||||
selectedNodeRadius: 28,
|
selectedNodeRadius: 28,
|
||||||
linkDistance: 160,
|
linkDistance: 150,
|
||||||
chargeStrength: -1200,
|
chargeStrength: -1000,
|
||||||
centerForce: 0.1,
|
centerForce: 0.15,
|
||||||
tooltipEnabled: true,
|
tooltipEnabled: true,
|
||||||
onNodeClick: function(node) {
|
onNodeClick: function(node) {
|
||||||
console.log('Node clicked:', node);
|
console.log('Node clicked:', node);
|
||||||
// Hier können spezifische Aktionen für Knotenklicks definiert werden
|
// Handle node click - could be expanded to display node content
|
||||||
|
// or fetch more information from the server
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
// Event-Listener für Steuerungsbuttons
|
// Create and initialize the visualization
|
||||||
|
window.mindmap = new MindMapVisualization('#mindmap-container', options);
|
||||||
|
|
||||||
|
// Set up UI event handlers
|
||||||
document.getElementById('zoom-in-btn').addEventListener('click', function() {
|
document.getElementById('zoom-in-btn').addEventListener('click', function() {
|
||||||
// Zoom-In-Funktionalität
|
if (window.mindmap) {
|
||||||
const svg = d3.select('#mindmap-container svg');
|
const transform = d3.zoomTransform(window.mindmap.svg.node());
|
||||||
const currentZoom = d3.zoomTransform(svg.node());
|
window.mindmap.svg.call(
|
||||||
const newScale = currentZoom.k * 1.3;
|
|
||||||
svg.transition().duration(300).call(
|
|
||||||
d3.zoom().transform,
|
d3.zoom().transform,
|
||||||
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
|
d3.zoomIdentity.scale(transform.k * 1.3)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('zoom-out-btn').addEventListener('click', function() {
|
document.getElementById('zoom-out-btn').addEventListener('click', function() {
|
||||||
// Zoom-Out-Funktionalität
|
if (window.mindmap) {
|
||||||
const svg = d3.select('#mindmap-container svg');
|
const transform = d3.zoomTransform(window.mindmap.svg.node());
|
||||||
const currentZoom = d3.zoomTransform(svg.node());
|
window.mindmap.svg.call(
|
||||||
const newScale = currentZoom.k / 1.3;
|
|
||||||
svg.transition().duration(300).call(
|
|
||||||
d3.zoom().transform,
|
d3.zoom().transform,
|
||||||
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
|
d3.zoomIdentity.scale(transform.k / 1.3)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('center-btn').addEventListener('click', function() {
|
document.getElementById('center-btn').addEventListener('click', function() {
|
||||||
// Zentrieren-Funktionalität
|
if (window.mindmap) {
|
||||||
const svg = d3.select('#mindmap-container svg');
|
window.mindmap.svg.call(
|
||||||
svg.transition().duration(500).call(
|
|
||||||
d3.zoom().transform,
|
d3.zoom().transform,
|
||||||
d3.zoomIdentity.scale(1)
|
d3.zoomIdentity
|
||||||
);
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Thought Button
|
|
||||||
document.getElementById('add-thought-btn').addEventListener('click', function() {
|
document.getElementById('add-thought-btn').addEventListener('click', function() {
|
||||||
// Implementierung für das Hinzufügen eines neuen Gedankens
|
alert('Diese Funktion steht demnächst zur Verfügung.');
|
||||||
if (mindmap.selectedNode) {
|
|
||||||
const newNodeName = prompt('Gedanke eingeben:');
|
|
||||||
if (newNodeName && newNodeName.trim() !== '') {
|
|
||||||
const newNodeId = 'node_' + Date.now();
|
|
||||||
const newNode = {
|
|
||||||
id: newNodeId,
|
|
||||||
name: newNodeName,
|
|
||||||
description: 'Neuer Gedanke',
|
|
||||||
thought_count: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// Node zur Mindmap hinzufügen
|
|
||||||
mindmap.nodes.push(newNode);
|
|
||||||
|
|
||||||
// Link zum ausgewählten Knoten erstellen
|
|
||||||
mindmap.links.push({
|
|
||||||
source: mindmap.selectedNode.id,
|
|
||||||
target: newNodeId
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Mindmap aktualisieren
|
|
||||||
mindmap.updateVisualization();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert('Bitte zuerst einen Knoten auswählen, um einen Gedanken hinzuzufügen.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect Button
|
|
||||||
document.getElementById('connect-btn').addEventListener('click', function() {
|
document.getElementById('connect-btn').addEventListener('click', function() {
|
||||||
// Implementierung für das Verbinden von Knoten
|
alert('Diese Funktion steht demnächst zur Verfügung.');
|
||||||
if (mindmap.selectedNode && mindmap.mouseoverNode && mindmap.selectedNode !== mindmap.mouseoverNode) {
|
});
|
||||||
// Prüfen, ob Verbindung bereits existiert
|
|
||||||
const existingLink = mindmap.links.find(link =>
|
// Handle window resize
|
||||||
(link.source.id === mindmap.selectedNode.id && link.target.id === mindmap.mouseoverNode.id) ||
|
let resizeTimeout;
|
||||||
(link.source.id === mindmap.mouseoverNode.id && link.target.id === mindmap.selectedNode.id)
|
window.addEventListener('resize', function() {
|
||||||
|
clearTimeout(resizeTimeout);
|
||||||
|
resizeTimeout = setTimeout(function() {
|
||||||
|
if (window.mindmap) {
|
||||||
|
window.mindmap.width = mindmapContainer.clientWidth;
|
||||||
|
window.mindmap.height = mindmapContainer.clientHeight;
|
||||||
|
window.mindmap.svg
|
||||||
|
.attr('width', window.mindmap.width)
|
||||||
|
.attr('height', window.mindmap.height)
|
||||||
|
.attr('viewBox', `0 0 ${window.mindmap.width} ${window.mindmap.height}`);
|
||||||
|
|
||||||
|
// Update the center force
|
||||||
|
window.mindmap.simulation.force('center',
|
||||||
|
d3.forceCenter(window.mindmap.width / 2, window.mindmap.height / 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!existingLink) {
|
// Restart the simulation
|
||||||
// Link erstellen
|
window.mindmap.simulation.alpha(0.3).restart();
|
||||||
mindmap.links.push({
|
}
|
||||||
source: mindmap.selectedNode.id,
|
}, 250);
|
||||||
target: mindmap.mouseoverNode.id
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
// Mindmap aktualisieren
|
|
||||||
mindmap.updateVisualization();
|
|
||||||
} else {
|
|
||||||
alert('Diese Verbindung existiert bereits.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
alert('Bitte wähle zwei verschiedene Knoten aus, um sie zu verbinden.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Responsive Anpassung bei Fenstergrößenänderung
|
|
||||||
window.addEventListener('resize', function() {
|
|
||||||
const newWidth = mindmapContainer.clientWidth;
|
|
||||||
const newHeight = mindmapContainer.clientHeight;
|
|
||||||
|
|
||||||
if (mindmap.svg) {
|
|
||||||
mindmap.svg
|
|
||||||
.attr('width', newWidth)
|
|
||||||
.attr('height', newHeight);
|
|
||||||
|
|
||||||
mindmap.width = newWidth;
|
|
||||||
mindmap.height = newHeight;
|
|
||||||
|
|
||||||
// Force-Simulation aktualisieren
|
|
||||||
if (mindmap.simulation) {
|
|
||||||
mindmap.simulation
|
|
||||||
.force('center', d3.forceCenter(newWidth / 2, newHeight / 2))
|
|
||||||
.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Aktualisiere das Aussehen von Bookmarks, sobald die Mindmap vollständig geladen ist
|
|
||||||
setTimeout(() => {
|
|
||||||
if (mindmap && typeof mindmap.updateAllBookmarkedNodes === 'function') {
|
|
||||||
mindmap.updateAllBookmarkedNodes();
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Animationen für die Hintergrundeffekte
|
|
||||||
document.head.insertAdjacentHTML('beforeend', `
|
|
||||||
<style>
|
|
||||||
@keyframes pulse {
|
|
||||||
0% { transform: scale(1); opacity: 0.5; }
|
|
||||||
100% { transform: scale(1.5); opacity: 0.2; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes flash {
|
|
||||||
0% { opacity: 0.02; }
|
|
||||||
50% { opacity: 0.2; }
|
|
||||||
100% { opacity: 0.08; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
`);
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user