From a0e4cd220858116abb646fff1f4f1bb139243cc4 Mon Sep 17 00:00:00 2001 From: marwin Date: Fri, 2 May 2025 09:27:22 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20implementiere=20Mindmap-Fun?= =?UTF-8?q?ktionalit=C3=A4t=20mit=20dynamischer=20Datenladung=20und=20verb?= =?UTF-8?q?esserten=20Benutzeroberfl=C3=A4chen-Elementen=20in=20mindmap.ht?= =?UTF-8?q?ml=20und=20mindmap-init.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/app.cpython-313.pyc | Bin 68413 -> 70320 bytes app.py | 106 ++++++-- database/systades.db | Bin 131072 -> 131072 bytes static/js/mindmap-init.js | 420 ++++++++++++++++++++++++++++++++ static/js/modules/mindmap.js | Bin 20772 -> 30130 bytes templates/mindmap.html | 102 +++++++- 6 files changed, 599 insertions(+), 29 deletions(-) create mode 100644 static/js/mindmap-init.js diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index 399441fa5295d135beb1c23ab97b89ccf7846911..621e517bb95856a81c62c1f5db4e9afe0cf9643e 100644 GIT binary patch delta 9973 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 NtWut>(l;A4{|C#uym9~l delta 8682 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 diff --git a/app.py b/app.py index 1b9a45b..a0d7e87 100644 --- a/app.py +++ b/app.py @@ -345,23 +345,35 @@ def index(): # Route for the mindmap page @app.route('/mindmap') def mindmap(): - """Zeigt die öffentliche Mindmap an.""" - try: - # Sicherstellen, dass wir Kategorien haben - if Category.query.count() == 0: - create_default_categories() - - # Hole alle Kategorien der obersten Ebene - categories = Category.query.filter_by(parent_id=None).all() - - # Transformiere Kategorien in ein anzeigbares Format für die Vorlage - category_tree = [build_category_tree(cat) for cat in categories] - - return render_template('mindmap.html', categories=category_tree) - except Exception as e: - # Bei Fehler leere Kategorienliste übergeben und Fehler protokollieren - print(f"Fehler beim Laden der Mindmap-Kategorien: {str(e)}") - return render_template('mindmap.html', categories=[], error=str(e)) + """Zeigt die Mindmap-Seite an.""" + + # Benutzer-Mindmaps, falls angemeldet + user_mindmaps = [] + if current_user.is_authenticated: + user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all() + + # Stelle sicher, dass der "Wissen"-Knoten existiert + wissen_node = MindMapNode.query.filter_by(name="Wissen").first() + if not wissen_node: + wissen_node = MindMapNode( + name="Wissen", + description="Zentrale Wissensbasis", + color_code="#4299E1", + is_public=True + ) + db.session.add(wissen_node) + db.session.commit() + print("'Wissen'-Knoten wurde erstellt") + + # Überprüfe, ob es Kategorien gibt, sonst erstelle sie + if Category.query.count() == 0: + create_default_categories() + print("Kategorien wurden erstellt") + + # Stelle sicher, dass die Route für statische Dateien korrekt ist + mindmap_js_path = url_for('static', filename='js/mindmap-init.js') + + return render_template('mindmap.html', user_mindmaps=user_mindmaps, mindmap_js_path=mindmap_js_path) # Route for user profile @app.route('/profile') @@ -1441,14 +1453,42 @@ def refresh_mindmap(): if Category.query.count() == 0: create_default_categories() + # Überprüfe, ob wir bereits einen "Wissen"-Knoten haben + wissen_node = MindMapNode.query.filter_by(name="Wissen").first() + + # Wenn kein "Wissen"-Knoten existiert, erstelle ihn + if not wissen_node: + wissen_node = MindMapNode( + name="Wissen", + description="Zentrale Wissensbasis", + color_code="#4299E1", + is_public=True + ) + db.session.add(wissen_node) + db.session.commit() + # Hole alle Kategorien und Knoten categories = Category.query.filter_by(parent_id=None).all() category_tree = [build_category_tree(cat) for cat in categories] - # Hole alle Mindmap-Knoten - nodes = MindMapNode.query.all() - node_data = [] + # Hole alle Mindmap-Knoten außer dem "Wissen"-Knoten + nodes = MindMapNode.query.filter(MindMapNode.id != wissen_node.id).all() + # Vorbereiten der Node- und Edge-Arrays für die Antwort + node_data = [] + edge_data = [] + + # Zuerst den "Wissen"-Knoten hinzufügen + node_data.append({ + 'id': wissen_node.id, + 'name': wissen_node.name, + 'description': wissen_node.description or '', + 'color_code': wissen_node.color_code or '#4299E1', + 'thought_count': len(wissen_node.thoughts), + 'category_id': wissen_node.category_id + }) + + # Dann die anderen Knoten for node in nodes: node_obj = { 'id': node.id, @@ -1459,15 +1499,28 @@ def refresh_mindmap(): 'category_id': node.category_id } - # Verbindungen hinzufügen - node_obj['connections'] = [{'target': child.id} for child in node.children] + # Verbinde alle Top-Level-Knoten mit dem Wissen-Knoten + if not node.parents.all(): + edge_data.append({ + 'source': wissen_node.id, + 'target': node.id + }) + + # Verbindungen zwischen vorhandenen Knoten hinzufügen + node_children = node.children.all() + for child in node_children: + edge_data.append({ + 'source': node.id, + 'target': child.id + }) node_data.append(node_obj) return jsonify({ 'success': True, 'categories': category_tree, - 'nodes': node_data + 'nodes': node_data, + 'edges': edge_data }) except Exception as e: @@ -1490,4 +1543,9 @@ def mindmap_page(): @app.route('/community_forum') def redirect_to_index(): """Leitet alle Community/Forum-URLs zur Startseite um""" - return redirect(url_for('index')) \ No newline at end of file + return redirect(url_for('index')) + +@app.route('/static/js/mindmap-init.js') +def serve_mindmap_init_js(): + """Bedient die Mindmap-Initialisierungsdatei.""" + return app.send_static_file('js/mindmap-init.js'), 200, {'Content-Type': 'application/javascript'} \ No newline at end of file diff --git a/database/systades.db b/database/systades.db index 6e65762a8829bb44d5983dfda38d9353f24d9207..c9cf76ebf6edc67630f5bc57735751f4a46ccc4d 100644 GIT binary patch delta 132 zcmZo@;Am*zm>|t4K2gS*QG8>o{v8bbJ2ndntmF4^W8`9xG?!-JbYx%%&nzxZ z&5KISD=A9MNmYPwi<1(IGmDi?j4Ul(4UG(pOmz)_$VkD!!pg|h%E(;L$iULb)MQaY Rz#;{KMFj$A|t4Hc`fzQEX$vlK9Om1%Lb(H3%$H5Wpmt88BV|0NH>N7XSbN diff --git a/static/js/mindmap-init.js b/static/js/mindmap-init.js new file mode 100644 index 0000000..f0de75f --- /dev/null +++ b/static/js/mindmap-init.js @@ -0,0 +1,420 @@ +/** + * Mindmap-Initialisierer + * Lädt und initialisiert die Mindmap-Visualisierung + */ + +// Warte bis DOM geladen ist +document.addEventListener('DOMContentLoaded', function() { + // Prüfe, ob wir auf der Mindmap-Seite sind + const cyContainer = document.getElementById('cy'); + + if (!cyContainer) { + console.log('Kein Mindmap-Container gefunden, überspringe Initialisierung.'); + return; + } + + console.log('Initialisiere Mindmap-Visualisierung...'); + + // Prüfe, ob Cytoscape.js verfügbar ist + if (typeof cytoscape === 'undefined') { + loadScript('/static/js/cytoscape.min.js', initMindmap); + } else { + initMindmap(); + } +}); + +/** + * Lädt ein Script dynamisch + * @param {string} src - Quelldatei + * @param {Function} callback - Callback nach dem Laden + */ +function loadScript(src, callback) { + const script = document.createElement('script'); + script.src = src; + script.onload = callback; + document.head.appendChild(script); +} + +/** + * Initialisiert die Mindmap-Visualisierung + */ +function initMindmap() { + const cyContainer = document.getElementById('cy'); + const fitBtn = document.getElementById('fit-btn'); + const resetBtn = document.getElementById('reset-btn'); + const toggleLabelsBtn = document.getElementById('toggle-labels-btn'); + const nodeInfoPanel = document.getElementById('node-info-panel'); + const nodeDescription = document.getElementById('node-description'); + const connectedNodes = document.getElementById('connected-nodes'); + + let labelsVisible = true; + let selectedNode = null; + + // Erstelle Cytoscape-Instanz + const cy = cytoscape({ + container: cyContainer, + style: getDefaultStyles(), + layout: { + name: 'cose', + animate: true, + animationDuration: 800, + nodeDimensionsIncludeLabels: true, + padding: 50, + spacingFactor: 1.2, + randomize: true, + componentSpacing: 100, + nodeRepulsion: 8000, + edgeElasticity: 100, + nestingFactor: 1.2, + gravity: 80 + }, + wheelSensitivity: 0.3, + }); + + // Daten vom Server laden + loadMindmapData(cy); + + // Event-Handler zuweisen + setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes); +} + +/** + * Lädt die Mindmap-Daten vom Server + * @param {Object} cy - Cytoscape-Instanz + */ +function loadMindmapData(cy) { + fetch('/api/mindmap') + .then(response => { + if (!response.ok) { + throw new Error(`HTTP Fehler: ${response.status}`); + } + return response.json(); + }) + .then(data => { + if (!data.nodes || data.nodes.length === 0) { + console.log('Keine Daten gefunden, versuche Refresh-API...'); + return fetch('/api/refresh-mindmap') + .then(response => { + if (!response.ok) { + throw new Error(`HTTP Fehler beim Refresh: ${response.status}`); + } + return response.json(); + }); + } + return data; + }) + .then(data => { + console.log('Mindmap-Daten geladen:', data); + + // Cytoscape-Elemente vorbereiten + const elements = []; + + // Prüfen, ob "Wissen"-Knoten existiert + let rootNode = data.nodes.find(node => node.name === "Wissen"); + + // Wenn nicht, Root-Knoten hinzufügen + if (!rootNode) { + rootNode = { + id: 'root', + name: 'Wissen', + description: 'Zentrale Wissensbasis', + color_code: '#4299E1' + }; + data.nodes.unshift(rootNode); + } + + // Knoten hinzufügen + data.nodes.forEach(node => { + elements.push({ + group: 'nodes', + data: { + id: node.id.toString(), + name: node.name, + description: node.description || '', + color: node.color_code || '#8B5CF6', + isRoot: node.name === 'Wissen' + } + }); + }); + + // Kanten hinzufügen, wenn vorhanden + if (data.edges && data.edges.length > 0) { + data.edges.forEach(edge => { + elements.push({ + group: 'edges', + data: { + id: `${edge.source}-${edge.target}`, + source: edge.source.toString(), + target: edge.target.toString() + } + }); + }); + } else { + // Wenn keine Kanten definiert sind, verbinde alle Knoten mit dem Root-Knoten + const rootId = rootNode.id.toString(); + + data.nodes.forEach(node => { + if (node.id.toString() !== rootId) { + elements.push({ + group: 'edges', + data: { + id: `${rootId}-${node.id}`, + source: rootId, + target: node.id.toString() + } + }); + } + }); + } + + // Elemente zu Cytoscape hinzufügen + cy.elements().remove(); + cy.add(elements); + + // Layout anwenden + cy.layout({ + name: 'cose', + animate: true, + animationDuration: 800, + nodeDimensionsIncludeLabels: true, + padding: 50, + spacingFactor: 1.5, + randomize: false, + fit: true + }).run(); + + // Nach dem Laden Event auslösen + document.dispatchEvent(new CustomEvent('mindmap-loaded')); + }) + .catch(error => { + console.error('Fehler beim Laden der Mindmap-Daten:', error); + + // Fallback mit Standard-Daten + const fallbackData = { + nodes: [ + { id: 1, name: 'Wissen', description: 'Zentrale Wissensbasis', color_code: '#4299E1' }, + { id: 2, name: 'Philosophie', description: 'Philosophisches Denken', color_code: '#9F7AEA' }, + { id: 3, name: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', color_code: '#48BB78' }, + { id: 4, name: 'Technologie', description: 'Technologische Entwicklungen', color_code: '#ED8936' }, + { id: 5, name: 'Künste', description: 'Künstlerische Ausdrucksformen', color_code: '#ED64A6' } + ], + edges: [ + { source: 1, target: 2 }, + { source: 1, target: 3 }, + { source: 1, target: 4 }, + { source: 1, target: 5 } + ] + }; + + const fallbackElements = []; + + // Knoten hinzufügen + fallbackData.nodes.forEach(node => { + fallbackElements.push({ + group: 'nodes', + data: { + id: node.id.toString(), + name: node.name, + description: node.description || '', + color: node.color_code || '#8B5CF6', + isRoot: node.name === 'Wissen' + } + }); + }); + + // Kanten hinzufügen + fallbackData.edges.forEach(edge => { + fallbackElements.push({ + group: 'edges', + data: { + id: `${edge.source}-${edge.target}`, + source: edge.source.toString(), + target: edge.target.toString() + } + }); + }); + + // Elemente zu Cytoscape hinzufügen + cy.elements().remove(); + cy.add(fallbackElements); + + // Layout anwenden + cy.layout({ name: 'cose', animate: true }).run(); + }); +} + +/** + * Richtet Event-Listener für die Mindmap ein + * @param {Object} cy - Cytoscape-Instanz + * @param {HTMLElement} fitBtn - Fit-Button + * @param {HTMLElement} resetBtn - Reset-Button + * @param {HTMLElement} toggleLabelsBtn - Toggle-Labels-Button + * @param {HTMLElement} nodeInfoPanel - Node-Info-Panel + * @param {HTMLElement} nodeDescription - Node-Description + * @param {HTMLElement} connectedNodes - Connected-Nodes-Container + */ +function setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes) { + let labelsVisible = true; + + // Fit-Button + if (fitBtn) { + fitBtn.addEventListener('click', function() { + cy.fit(); + }); + } + + // Reset-Button + if (resetBtn) { + resetBtn.addEventListener('click', function() { + cy.layout({ name: 'cose', animate: true }).run(); + }); + } + + // Toggle-Labels-Button + if (toggleLabelsBtn) { + toggleLabelsBtn.addEventListener('click', function() { + labelsVisible = !labelsVisible; + cy.style() + .selector('node') + .style({ + 'text-opacity': labelsVisible ? 1 : 0 + }) + .update(); + }); + } + + // Knoten-Klick + cy.on('tap', 'node', function(evt) { + const node = evt.target; + + // Zuvor ausgewählten Knoten zurücksetzen + cy.nodes().removeClass('selected'); + + // Neuen Knoten auswählen + node.addClass('selected'); + + if (nodeInfoPanel && nodeDescription && connectedNodes) { + // Info-Panel aktualisieren + nodeDescription.textContent = node.data('description') || 'Keine Beschreibung verfügbar.'; + + // Verbundene Knoten anzeigen + connectedNodes.innerHTML = ''; + + // Verbundene Knoten sammeln + const connectedNodesList = node.neighborhood('node'); + + if (connectedNodesList.length > 0) { + connectedNodesList.forEach(connectedNode => { + // Nicht den ausgewählten Knoten selbst anzeigen + if (connectedNode.id() !== node.id()) { + const nodeLink = document.createElement('span'); + nodeLink.className = 'node-link'; + nodeLink.textContent = connectedNode.data('name'); + nodeLink.style.backgroundColor = connectedNode.data('color'); + + // Klick-Ereignis, um zu diesem Knoten zu wechseln + nodeLink.addEventListener('click', function() { + connectedNode.select(); + cy.animate({ + center: { eles: connectedNode }, + duration: 500, + easing: 'ease-in-out-cubic' + }); + }); + + connectedNodes.appendChild(nodeLink); + } + }); + } else { + connectedNodes.innerHTML = 'Keine verbundenen Knoten'; + } + + // Panel anzeigen + nodeInfoPanel.classList.add('visible'); + } + }); + + // Hintergrund-Klick + cy.on('tap', function(evt) { + if (evt.target === cy) { + // Klick auf den Hintergrund + cy.nodes().removeClass('selected'); + + // Info-Panel verstecken + if (nodeInfoPanel) { + nodeInfoPanel.classList.remove('visible'); + } + } + }); + + // Dark Mode-Änderungen + document.addEventListener('darkModeToggled', function(event) { + const isDark = event.detail.isDark; + cy.style(getDefaultStyles(isDark)); + }); +} + +/** + * Liefert die Standard-Stile für die Mindmap + * @param {boolean} darkMode - Ob der Dark Mode aktiv ist + * @returns {Array} Array von Cytoscape-Stilen + */ +function getDefaultStyles(darkMode = document.documentElement.classList.contains('dark')) { + return [ + { + selector: 'node', + style: { + 'background-color': 'data(color)', + 'label': 'data(name)', + 'width': 40, + 'height': 40, + 'font-size': 12, + 'text-valign': 'bottom', + 'text-halign': 'center', + 'text-margin-y': 8, + 'color': darkMode ? '#f1f5f9' : '#334155', + 'text-background-color': darkMode ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)', + 'text-background-opacity': 0.8, + 'text-background-padding': '2px', + 'text-background-shape': 'roundrectangle', + 'text-wrap': 'ellipsis', + 'text-max-width': '100px' + } + }, + { + selector: 'node[?isRoot]', + style: { + 'width': 60, + 'height': 60, + 'font-size': 14, + 'font-weight': 'bold', + 'text-background-opacity': 0.9, + 'text-background-color': '#4299E1' + } + }, + { + selector: 'edge', + style: { + 'width': 2, + 'line-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)', + 'target-arrow-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)', + 'curve-style': 'bezier', + 'target-arrow-shape': 'triangle' + } + }, + { + selector: 'node.selected', + style: { + 'background-color': 'data(color)', + 'border-width': 3, + 'border-color': '#8b5cf6', + 'width': 50, + 'height': 50, + 'font-size': 14, + 'font-weight': 'bold', + 'text-background-color': '#8b5cf6', + 'text-background-opacity': 0.9 + } + } + ]; +} \ No newline at end of file diff --git a/static/js/modules/mindmap.js b/static/js/modules/mindmap.js index 34148fb0009ac791dfc7e793683f3ae4f8e858d7..7ae6be217adc22e586a4f3952972447b2b333394 100644 GIT binary patch delta 4368 zcmbtXTW?fV6y6o2Gi{yDOgr=@^vsmDGo4GP7VyHo7Xnsj(?p3}5(*Rdnt; zzWS}^R-{%^o1B&1wMWA;F}^8tTwPMrs;m~(l5*6m?)xQmR$ajREY>co683Wv>uqW2 zS5s;pzx&lI_?}hg@WhIos_$QJ2#J&4xN<#up+}h#8m2lh${JUOl=Cop8g5-sdG(5P)^*5g zEF$lZhQ^XG+t^cp>^c16IY+&PkF(}pLCxUIta=GvT)>$<`s1jg41}}t!_ebrU8}J- zqGcOFbz>yMi?WjrZk`3)MSlOoDyidX>u>*3tzfOvgW|>dLm*bDu2Nkr% z2fgh87M2?$wQg$UTDWE9xp->x_+72M4A_;`KgcR0%Q((q-GZ|+>QjI;^U1uH@jRd= z)L}e{r880!YNOk7Cs!wbr0e8P`Xn)U^&q4(UygdCq9kl)iOxxAodX8)YD^<%Nn7Kn zlsbm+EaF4!Qd$>LOi8r$JT%YXL#vEUvpCNZW6@2k*HmdEHkahf;Vq2(Rl<#1Wmn-S zkMM*Ru?IVALAZe3Ib>;CrL}ns0KvK!-xlbE{FFZ9!?@b~j3mZ)F-Qqw!?+x0njo#YLzINH4DB9o44I4XVAg-(ko0RHO+tm~5X?#Z2 zfJ(`M!G4Mg(@`C1dF*gjipi*~W}nqi3SfmfliBo6t-P48lb+0^&f^5+-;Wbh;p7iS z-9cUm=3s7v8O^J4pzCGqU({(Z96>AvZww)jMvl)?GmjvPHxk`0^Sx-pei#X3z=Me> z={3VTr@aK_bwN#Q#$hUFbdfwvcf(tCIyC!t#pV6{vNZICHo$qCT$$)=epK5i zKPEFexi|Y#wXS-53P_RGY&`6#up9D0cDoD>7N};IhZ97Ih_Z$~0K{crdmgAdBjRgB{G?q8j!j9mOhjE(WL|C&_eEBl*Zm zXJXg}evWOGz1!NoXiz%q>)l+c+X36z2C!}vjYjB;EpK{M@xsaD! zqt8S37-CA4)At+0iL(x@HZk-2O=v`j2USNnSl_)qCjk49_0?DD6 zKPmHUF(?fi6TY_=-jb+OC)W$d_1(i?4PC9UOsKJ7&GX?Qa;k8wxt_D@W`09A%p z%$UVWVDq=kLF^MYG>9Vs4ZVaXtK?OzoJVDlgVtpz`Mq(AI77u2(kxF-X;(S;lk28Y z4FmNDutO_eS62Z4L^rFRvn)Aql&35(k#z5@+&DNmJnzE?a~9!f?lQm*Rz~n`xX4JN zCuR5&@JkjpG|4+dk%#BACUQiT1psje$Cv-bv0XbXOBP(d_ZS-RJdcc&5tk{wm-bKS zvIQ{6EYYz|CoHwQ%mQP5x-TGsK0o}g5df+(R#oK)5LCCu5P*xr_9Efo4gpnmn)IE8 z%OwPR3Ey)XjF#CvMswZnvLd|>4uPD00@lbvA|d;;?Q4wg`- zn7`+`VsB!g*y4|#{xQ!ydk_OgbJ*LLFS2cR+-R)g%D`}X-x$&BbC3CVf-Q(eR0Vy_ zE)$wel zB=%%R?(E(xg~4dePT4m&s=o*2^S$58y?yOc-t~lhu%i&`t@^K`SN_bRjpE->l5bDP PR{3UN_sSnn?QH!QPe{I8 delta 270 zcmdn=nsLb@#tnSzy!s3U45bW73^@#$49Sxf*%UYHvHNjORxst+EFze}IC+DZ+++zs zp2_EgR5n+MD~V11Vq`X%kCS6Ek6yv#1aFSXR>mQd1?;_mY_G}Z%s5!}fCi;Z{%B}E zS-@Om@+=Fk$)60(CWlB#O|CKLnCxXLG5L|X#pEMExdWyKlc$8}O`gEPG5L>=$YdQ0 zg~`_(bS5kCiA|p1Xfj#BRRYL1n4Dm(H2DX+2-H@t$uF#VCM)UbOjfed1G}Kd3E=_( tf40e!obx7gxWr7(aS1{*OKWn2Yuw~j5(1O0QpGmE2%N*QxhB?30ssm{SMmS= diff --git a/templates/mindmap.html b/templates/mindmap.html index 0e7a940..9ea0ff9 100644 --- a/templates/mindmap.html +++ b/templates/mindmap.html @@ -110,6 +110,95 @@ background-color: rgba(0, 0, 0, 0.05); } + /* Info-Panel */ + .mindmap-info-panel { + position: absolute; + right: 1rem; + top: 5rem; + width: 280px; + background-color: rgba(15, 23, 42, 0.85); + border-radius: 8px; + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + padding: 1rem; + color: #f1f5f9; + opacity: 0; + transform: translateX(20px); + transition: all 0.3s ease; + pointer-events: none; + z-index: 10; + backdrop-filter: blur(4px); + } + + .mindmap-info-panel.visible { + opacity: 1; + transform: translateX(0); + pointer-events: auto; + } + + .info-panel-title { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.75rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + } + + .info-panel-description { + font-size: 0.875rem; + margin-bottom: 1rem; + line-height: 1.5; + } + + .node-navigation-title { + font-size: 0.9rem; + font-weight: 500; + margin-bottom: 0.5rem; + } + + .node-links { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + } + + .node-link { + display: inline-block; + padding: 0.25rem 0.5rem; + font-size: 0.75rem; + border-radius: 4px; + cursor: pointer; + transition: transform 0.2s ease; + color: white; + } + + .node-link:hover { + transform: translateY(-2px); + } + + /* Mindmap-Toolbar-Buttons */ + .mindmap-action-btn { + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.35rem 0.7rem; + font-size: 0.8rem; + border-radius: 0.25rem; + background-color: rgba(124, 58, 237, 0.1); + color: #8b5cf6; + border: 1px solid rgba(124, 58, 237, 0.2); + cursor: pointer; + transition: all 0.2s ease; + } + + .dark .mindmap-action-btn { + background-color: rgba(124, 58, 237, 0.15); + border-color: rgba(124, 58, 237, 0.3); + } + + .mindmap-action-btn:hover { + background-color: rgba(124, 58, 237, 0.2); + } + /* Zusätzliches Layout */ .mindmap-section { display: grid; @@ -252,14 +341,17 @@ {% endblock %} {% block extra_js %} + + + + + + {% endblock %} \ No newline at end of file