fix(architecture): inline SVG and complete i18n for interactive diagram
- Replaced <object> element with inline SVG to fix contentDocument NULL issues - Simplified interactive-diagram.js to work with inline SVG directly - Added diagram_services translations loading from window.i18nTranslations - Exposed window.i18nTranslations in i18n-simple.js for global access - Added event listeners for i18nInitialized and languageChanged - Diagram modals now fully translate across EN/DE/FR languages - Removed complex retry/race condition logic from SVG loading - Converted SVG style attributes to presentation attributes (CSP compliant) Fixes: Interactive diagram was broken due to contentDocument being NULL when accessing SVG via <object> element. Inline SVG approach is more reliable and works immediately without race conditions.
This commit is contained in:
parent
7e4fb44829
commit
b7a76017dc
3 changed files with 164 additions and 108 deletions
|
|
@ -335,15 +335,153 @@
|
|||
<div class="flex flex-col lg:flex-row lg:items-start gap-6">
|
||||
<!-- Interactive SVG (reduced to 25% surface area = 50% linear) -->
|
||||
<div class="flex-shrink-0 flex justify-center lg:justify-start">
|
||||
<object
|
||||
data="/images/architecture-diagram-interactive.svg"
|
||||
type="image/svg+xml"
|
||||
id="interactive-svg-object"
|
||||
class="w-64 sm:w-72 lg:w-80 h-auto"
|
||||
aria-label="Interactive Tractatus Architecture Diagram">
|
||||
<!-- Fallback for browsers that don't support object tag -->
|
||||
<img src="/images/architecture-diagram-interactive.svg" alt="Tractatus Architecture Diagram" class="w-64 sm:w-72 lg:w-80" />
|
||||
</object>
|
||||
<svg width="600" height="600" viewBox="0 0 600 600" xmlns="http://www.w3.org/2000/svg" id="interactive-arch-diagram">
|
||||
<defs>
|
||||
<!-- Central core gradient (shared with Passport - cyan to blue) -->
|
||||
<radialGradient id="tractatusCoreInteractive">
|
||||
<stop offset="0%" stop-color="#64ffda" stop-opacity="1" />
|
||||
<stop offset="70%" stop-color="#448aff" stop-opacity="1" />
|
||||
<stop offset="100%" stop-color="#0ea5e9" stop-opacity="1" />
|
||||
</radialGradient>
|
||||
|
||||
<!-- Service-specific gradients (6 governance services) -->
|
||||
<linearGradient id="serviceBoundaryInt" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#10b981" stop-opacity="1" />
|
||||
<stop offset="100%" stop-color="#059669" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="serviceInstructionInt" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#6366f1" stop-opacity="1" />
|
||||
<stop offset="100%" stop-color="#4f46e5" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="serviceValidatorInt" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#8b5cf6" stop-opacity="1" />
|
||||
<stop offset="100%" stop-color="#7c3aed" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="servicePressureInt" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#f59e0b" stop-opacity="1" />
|
||||
<stop offset="100%" stop-color="#d97706" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="serviceMetacognitiveInt" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ec4899" stop-opacity="1" />
|
||||
<stop offset="100%" stop-color="#db2777" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="serviceDeliberationInt" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#14b8a6" stop-opacity="1" />
|
||||
<stop offset="100%" stop-color="#0d9488" stop-opacity="1" />
|
||||
</linearGradient>
|
||||
|
||||
<filter id="dropShadowInt">
|
||||
<feDropShadow dx="0" dy="4" stdDeviation="6" flood-opacity="0.3"/>
|
||||
</filter>
|
||||
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="4" result="coloredBlur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="coloredBlur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="600" height="600" fill="#f9fafb"/>
|
||||
|
||||
<!-- Orbital rings (subtle, static in interactive version) -->
|
||||
<circle cx="300" cy="300" r="255" stroke="#64ffda" stroke-width="1" opacity="0.15" fill="none"/>
|
||||
<circle cx="300" cy="300" r="210" stroke="#64ffda" stroke-width="1" opacity="0.25" fill="none"/>
|
||||
<circle cx="300" cy="300" r="165" stroke="#64ffda" stroke-width="1" opacity="0.35" fill="none"/>
|
||||
|
||||
<!-- Connection lines (will be made interactive) -->
|
||||
<g id="connection-lines">
|
||||
<line id="conn-boundary" x1="300" y1="300" x2="300" y2="105" stroke="#10b981" stroke-width="3" opacity="0.3" class="connection-line"/>
|
||||
<line id="conn-instruction" x1="300" y1="300" x2="468" y2="202.5" stroke="#6366f1" stroke-width="3" opacity="0.3" class="connection-line"/>
|
||||
<line id="conn-validator" x1="300" y1="300" x2="468" y2="397.5" stroke="#8b5cf6" stroke-width="3" opacity="0.3" class="connection-line"/>
|
||||
<line id="conn-pressure" x1="300" y1="300" x2="300" y2="495" stroke="#f59e0b" stroke-width="3" opacity="0.3" class="connection-line"/>
|
||||
<line id="conn-metacognitive" x1="300" y1="300" x2="132" y2="397.5" stroke="#ec4899" stroke-width="3" opacity="0.3" class="connection-line"/>
|
||||
<line id="conn-deliberation" x1="300" y1="300" x2="132" y2="202.5" stroke="#14b8a6" stroke-width="3" opacity="0.3" class="connection-line"/>
|
||||
</g>
|
||||
|
||||
<!-- Service nodes (clickable) -->
|
||||
<g id="service-nodes">
|
||||
<!-- 1. BoundaryEnforcer (top) - Green -->
|
||||
<g id="node-boundary" class="service-node" data-service="boundary" style="cursor: pointer;">
|
||||
<circle cx="300" cy="105" r="45" fill="url(#serviceBoundaryInt)" filter="url(#dropShadowInt)" opacity="0.95"/>
|
||||
<text x="300" y="115" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white">B</text>
|
||||
<title>BoundaryEnforcer - Click for details</title>
|
||||
</g>
|
||||
|
||||
<!-- 2. InstructionPersistenceClassifier (top-right) - Indigo -->
|
||||
<g id="node-instruction" class="service-node" data-service="instruction" style="cursor: pointer;">
|
||||
<circle cx="468" cy="202.5" r="45" fill="url(#serviceInstructionInt)" filter="url(#dropShadowInt)" opacity="0.95"/>
|
||||
<text x="468" y="212.5" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white">I</text>
|
||||
<title>InstructionPersistenceClassifier - Click for details</title>
|
||||
</g>
|
||||
|
||||
<!-- 3. CrossReferenceValidator (bottom-right) - Purple -->
|
||||
<g id="node-validator" class="service-node" data-service="validator" style="cursor: pointer;">
|
||||
<circle cx="468" cy="397.5" r="45" fill="url(#serviceValidatorInt)" filter="url(#dropShadowInt)" opacity="0.95"/>
|
||||
<text x="468" y="407.5" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white">V</text>
|
||||
<title>CrossReferenceValidator - Click for details</title>
|
||||
</g>
|
||||
|
||||
<!-- 4. ContextPressureMonitor (bottom) - Amber -->
|
||||
<g id="node-pressure" class="service-node" data-service="pressure" style="cursor: pointer;">
|
||||
<circle cx="300" cy="495" r="45" fill="url(#servicePressureInt)" filter="url(#dropShadowInt)" opacity="0.95"/>
|
||||
<text x="300" y="505" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white">P</text>
|
||||
<title>ContextPressureMonitor - Click for details</title>
|
||||
</g>
|
||||
|
||||
<!-- 5. MetacognitiveVerifier (bottom-left) - Rose -->
|
||||
<g id="node-metacognitive" class="service-node" data-service="metacognitive" style="cursor: pointer;">
|
||||
<circle cx="132" cy="397.5" r="45" fill="url(#serviceMetacognitiveInt)" filter="url(#dropShadowInt)" opacity="0.95"/>
|
||||
<text x="132" y="407.5" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white">M</text>
|
||||
<title>MetacognitiveVerifier - Click for details</title>
|
||||
</g>
|
||||
|
||||
<!-- 6. PluralisticDeliberationOrchestrator (top-left) - Teal -->
|
||||
<g id="node-deliberation" class="service-node" data-service="deliberation" style="cursor: pointer;">
|
||||
<circle cx="132" cy="202.5" r="45" fill="url(#serviceDeliberationInt)" filter="url(#dropShadowInt)" opacity="0.95"/>
|
||||
<text x="132" y="212.5" text-anchor="middle" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white">D</text>
|
||||
<title>PluralisticDeliberationOrchestrator - Click for details</title>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- Central core (clickable) -->
|
||||
<g id="central-core" class="service-node" data-service="overview" style="cursor: pointer;">
|
||||
<circle cx="300" cy="300" r="85" fill="url(#tractatusCoreInteractive)" filter="url(#dropShadowInt)"/>
|
||||
<circle cx="300" cy="300" r="68" fill="rgba(0,0,0,0.25)"/>
|
||||
<text x="300" y="320" text-anchor="middle" font-family="Arial, sans-serif" font-size="64" font-weight="bold" fill="white" opacity="0.95">T</text>
|
||||
<text x="300" y="345" text-anchor="middle" font-family="Arial, sans-serif" font-size="14" fill="white" opacity="0.8">Tractatus</text>
|
||||
<title>Tractatus Core - Click to see how all services work together</title>
|
||||
</g>
|
||||
|
||||
<style>
|
||||
.service-node:hover circle {
|
||||
filter: url(#dropShadowInt) url(#glow);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.service-node.active circle {
|
||||
stroke: white;
|
||||
stroke-width: 4;
|
||||
filter: url(#dropShadowInt) url(#glow);
|
||||
}
|
||||
|
||||
.connection-line {
|
||||
transition: opacity 0.3s ease, stroke-width 0.3s ease;
|
||||
}
|
||||
|
||||
.connection-line.active {
|
||||
opacity: 0.8;
|
||||
stroke-width: 5;
|
||||
}
|
||||
</style>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Service detail panel (permanent, updates on click) -->
|
||||
|
|
@ -517,7 +655,7 @@
|
|||
|
||||
<!-- Footer -->
|
||||
<!-- Internationalization -->
|
||||
<script src="/js/i18n-simple.js?v=0.1.0.1761283486841" defer></script>
|
||||
<script src="/js/i18n-simple.js?v=0.1.4.1761439039276" defer></script>
|
||||
<script src="/js/components/language-selector.js?v=0.1.0.1761283486841" defer></script>
|
||||
|
||||
<!-- Scroll Animations (Phase 3) -->
|
||||
|
|
@ -527,7 +665,7 @@
|
|||
|
||||
|
||||
<!-- Interactive Architecture Diagram (Phase 3) -->
|
||||
<script src="/js/components/interactive-diagram.js?v=0.1.0.1761283486841" defer></script>
|
||||
<script src="/js/components/interactive-diagram.js?v=0.1.3.1761438894566" defer></script>
|
||||
|
||||
<!-- Data Visualizations (Phase 3) -->
|
||||
<script src="/js/components/pressure-chart.js?v=0.1.0.1761283486841"></script>
|
||||
|
|
|
|||
|
|
@ -196,55 +196,24 @@ class InteractiveDiagram {
|
|||
}
|
||||
|
||||
setup() {
|
||||
// SVG is loaded via <object> tag, need to access its contentDocument
|
||||
const objectElement = document.getElementById('interactive-svg-object');
|
||||
if (!objectElement) {
|
||||
console.warn('[InteractiveDiagram] SVG object element not found');
|
||||
// SVG is now inline in the HTML
|
||||
const svg = document.getElementById('interactive-arch-diagram');
|
||||
|
||||
if (!svg) {
|
||||
console.warn('[InteractiveDiagram] Inline SVG not found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for object to load with retry mechanism
|
||||
const initializeSVG = () => {
|
||||
const svgDoc = objectElement.contentDocument;
|
||||
if (!svgDoc) {
|
||||
console.warn('[InteractiveDiagram] Could not access SVG contentDocument, retrying...');
|
||||
setTimeout(initializeSVG, 100);
|
||||
return;
|
||||
}
|
||||
console.log('[InteractiveDiagram] Found inline SVG');
|
||||
this.svg = svg;
|
||||
|
||||
// The SVG is the document element itself, or we can query for it
|
||||
let svg = svgDoc.getElementById('interactive-arch-diagram');
|
||||
if (!svg) {
|
||||
// Try getting the root SVG element
|
||||
svg = svgDoc.documentElement;
|
||||
console.log('[InteractiveDiagram] Using documentElement as SVG');
|
||||
}
|
||||
const nodes = svg.querySelectorAll('.service-node');
|
||||
console.log(`[InteractiveDiagram] Found ${nodes.length} service nodes`);
|
||||
|
||||
if (!svg) {
|
||||
console.warn('[InteractiveDiagram] SVG diagram not found in contentDocument');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify it's actually an SVG element (case-insensitive check)
|
||||
const tagName = svg.tagName ? svg.tagName.toLowerCase() : '';
|
||||
if (tagName !== 'svg') {
|
||||
console.warn('[InteractiveDiagram] Element found but not SVG, tagName:', tagName, '- retrying...');
|
||||
// This is the race condition - contentDocument is HTML, not SVG yet
|
||||
setTimeout(initializeSVG, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store reference to SVG document for later use
|
||||
this.svgDoc = svgDoc;
|
||||
this.svg = svg;
|
||||
|
||||
const nodes = svg.querySelectorAll('.service-node');
|
||||
console.log(`[InteractiveDiagram] Found ${nodes.length} service nodes`);
|
||||
|
||||
if (nodes.length === 0) {
|
||||
console.warn('[InteractiveDiagram] No service nodes found in SVG');
|
||||
return;
|
||||
}
|
||||
if (nodes.length === 0) {
|
||||
console.warn('[InteractiveDiagram] No service nodes found in SVG');
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.forEach(node => {
|
||||
const serviceId = node.getAttribute('data-service');
|
||||
|
|
@ -281,61 +250,8 @@ class InteractiveDiagram {
|
|||
// Show initial state (overview)
|
||||
this.showServiceDetails('overview');
|
||||
console.log('[InteractiveDiagram] Setup complete, showing overview');
|
||||
};
|
||||
|
||||
// FIXED: Better load detection with SVG verification
|
||||
const checkAndInit = () => {
|
||||
const svgDoc = objectElement.contentDocument;
|
||||
|
||||
// Check if contentDocument exists and is actually SVG
|
||||
if (svgDoc && svgDoc.documentElement) {
|
||||
const rootTagName = svgDoc.documentElement.tagName ? svgDoc.documentElement.tagName.toLowerCase() : '';
|
||||
|
||||
if (rootTagName === 'svg') {
|
||||
// It's an SVG - safe to initialize
|
||||
console.log('[InteractiveDiagram] SVG detected in contentDocument, initializing');
|
||||
initializeSVG();
|
||||
return true;
|
||||
} else {
|
||||
console.log('[InteractiveDiagram] contentDocument exists but root is:', rootTagName, '- not ready yet');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// Try immediate initialization if already loaded
|
||||
if (!checkAndInit()) {
|
||||
// Not ready yet - wait for load event
|
||||
console.log('[InteractiveDiagram] Waiting for object to load...');
|
||||
|
||||
objectElement.addEventListener('load', () => {
|
||||
console.log('[InteractiveDiagram] Object load event fired');
|
||||
// Small delay to ensure contentDocument is fully parsed
|
||||
setTimeout(() => {
|
||||
if (!checkAndInit()) {
|
||||
// Still not ready - start retry mechanism
|
||||
initializeSVG();
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
|
||||
// Also try periodic checks as fallback
|
||||
let retryCount = 0;
|
||||
const maxRetries = 20;
|
||||
const retryInterval = setInterval(() => {
|
||||
retryCount++;
|
||||
if (checkAndInit() || retryCount >= maxRetries) {
|
||||
clearInterval(retryInterval);
|
||||
if (retryCount >= maxRetries) {
|
||||
console.error('[InteractiveDiagram] Failed to load SVG after', maxRetries, 'retries');
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
highlightService(serviceId) {
|
||||
if (!this.svg) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@ const I18n = {
|
|||
// Deep merge common and page-specific translations (page-specific takes precedence)
|
||||
// Uses deep merge to preserve nested objects like footer in common.json
|
||||
this.translations = this.deepMerge(commonTranslations, pageTranslations);
|
||||
// Expose translations globally for components like interactive-diagram
|
||||
window.i18nTranslations = this.translations;
|
||||
console.log(`[i18n] Loaded translations: common + ${pageName}`);
|
||||
console.log(`[i18n] Footer translations present:`, !!this.translations.footer);
|
||||
console.log(`[i18n] Footer.about_heading:`, this.translations.footer?.about_heading);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue