diff --git a/internal/launchpad/index.html b/internal/launchpad/index.html
index c8dbdcc2..8c72bb43 100644
--- a/internal/launchpad/index.html
+++ b/internal/launchpad/index.html
@@ -149,14 +149,57 @@
/* Mermaid diagram styling */
.mermaid-diagram-wrapper {
+ position: relative;
display: flex;
justify-content: center;
align-items: center;
+ overflow: hidden;
+ cursor: grab;
}
+
+ .mermaid-diagram-wrapper:active {
+ cursor: grabbing;
+ }
+
.mermaid-diagram-wrapper svg {
- max-width: 100%;
+ max-width: none;
height: auto;
}
+
+ .mermaid-zoom-controls {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+ display: flex;
+ gap: 0.25rem;
+ background: white;
+ border-radius: 0.5rem;
+ padding: 0.25rem;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+ z-index: 10;
+ }
+
+ .mermaid-zoom-btn {
+ padding: 0.375rem;
+ background: white;
+ border: 1px solid #e5e7eb;
+ border-radius: 0.375rem;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+
+ .mermaid-zoom-btn:hover {
+ background-color: #f3f4f6;
+ }
+
+ .mermaid-zoom-btn svg {
+ width: 1rem;
+ height: 1rem;
+ color: #4b5563;
+ }
/* Loading Overlay */
#auth-loading {
@@ -494,6 +537,9 @@
let panzoomInstance = null;
let currentScale = 1;
+
+ // Track mermaid diagram panzoom instances
+ const mermaidPanzoomInstances = new Map();
// Initialize Mermaid
mermaid.initialize({
@@ -706,6 +752,63 @@
activeLink.classList.remove('text-gray-700');
activeLink.classList.add('bg-primary-50', 'border', 'border-primary-200', 'text-primary-700');
}
+
+ // Create zoom controls for mermaid diagrams
+ function createMermaidZoomControls(wrapper, svgElement, diagramId) {
+ const controls = document.createElement('div');
+ controls.className = 'mermaid-zoom-controls';
+ controls.innerHTML = `
+
+
+
+ `;
+
+ wrapper.appendChild(controls);
+
+ // Wait for next frame to ensure DOM is fully updated before initializing Panzoom
+ requestAnimationFrame(() => {
+ // Initialize panzoom for this mermaid diagram
+ const panzoom = Panzoom(svgElement, {
+ canvas: true,
+ maxScale: 10,
+ minScale: 0.3,
+ startScale: 1
+ });
+
+ mermaidPanzoomInstances.set(diagramId, panzoom);
+
+ // Add event listeners
+ controls.querySelector('[data-action="zoom-in"]').addEventListener('click', (e) => {
+ e.stopPropagation();
+ panzoom.zoomIn();
+ });
+
+ controls.querySelector('[data-action="zoom-out"]').addEventListener('click', (e) => {
+ e.stopPropagation();
+ panzoom.zoomOut();
+ });
+
+ controls.querySelector('[data-action="reset"]').addEventListener('click', (e) => {
+ e.stopPropagation();
+ panzoom.reset();
+ });
+
+ // Add wheel zoom
+ wrapper.addEventListener('wheel', panzoom.zoomWithWheel);
+ });
+ }
async function showView(viewName, navLink, filePath, title, type = 'svg') {
setActiveNav(navLink);
@@ -713,6 +816,11 @@
panzoomInstance.destroy();
panzoomInstance = null;
}
+
+ // Clean up mermaid panzoom instances when switching views
+ mermaidPanzoomInstances.forEach(instance => instance.destroy());
+ mermaidPanzoomInstances.clear();
+
diagramContainer.innerHTML = '';
documentContainer.innerHTML = '';
// Reset iframe
@@ -841,10 +949,18 @@
const pre = block.parentElement;
try {
- const { svg } = await mermaid.render(`mermaid-doc-${Date.now()}-${i}`, mermaidCode);
+ const diagramId = `mermaid-doc-${Date.now()}-${i}`;
+ const { svg } = await mermaid.render(diagramId, mermaidCode);
const wrapper = document.createElement('div');
- wrapper.className = 'mermaid-diagram-wrapper bg-white p-4 rounded-lg border border-gray-200 my-4 overflow-x-auto';
+ wrapper.className = 'mermaid-diagram-wrapper bg-white p-4 rounded-lg border border-gray-200 my-4';
wrapper.innerHTML = svg;
+
+ const svgElement = wrapper.querySelector('svg');
+ if (svgElement) {
+ // Add zoom controls for this mermaid diagram
+ createMermaidZoomControls(wrapper, svgElement, diagramId);
+ }
+
pre.replaceWith(wrapper);
} catch (err) {
console.error('Mermaid rendering error:', err);
diff --git a/makefiles/launchpad.mk b/makefiles/launchpad.mk
index 87050e19..4dff436f 100644
--- a/makefiles/launchpad.mk
+++ b/makefiles/launchpad.mk
@@ -2,7 +2,7 @@
.PHONY: launchpad-dev deploy-launchpad-hosting
-launchpad-dev: sync-prototypes
+launchpad-dev:
@echo "--> Starting local Launchpad server using Firebase Hosting emulator..."
@echo " - Generating secure email hashes..."
@node scripts/generate-allowed-hashes.js