diff --git a/apps/web/src/components/ArchitectureDiagram.tsx b/apps/web/src/components/ArchitectureDiagram.tsx index ccdae59..9eb599e 100644 --- a/apps/web/src/components/ArchitectureDiagram.tsx +++ b/apps/web/src/components/ArchitectureDiagram.tsx @@ -73,6 +73,19 @@ const nodeDescriptions: Record = }, }; +function getThemeColors(element: HTMLElement) { + const styles = getComputedStyle(element); + return { + background: styles.getPropertyValue('--color-background').trim() || '#ffffff', + foreground: styles.getPropertyValue('--color-foreground').trim() || '#0a0a0a', + foregroundSecondary: + styles.getPropertyValue('--color-foreground-secondary').trim() || '#525252', + foregroundTertiary: styles.getPropertyValue('--color-foreground-tertiary').trim() || '#737373', + border: styles.getPropertyValue('--color-border').trim() || '#e5e5e5', + primary: styles.getPropertyValue('--color-primary').trim() || '#2563eb', + }; +} + export function ArchitectureDiagram(): React.ReactElement { const svgRef = useRef(null); const containerRef = useRef(null); @@ -93,7 +106,10 @@ export function ArchitectureDiagram(): React.ReactElement { }, []); useEffect(() => { - if (!svgRef.current) return; + if (!svgRef.current || !containerRef.current) return; + + // Get computed theme colors + const colors = getThemeColors(containerRef.current); const svg = d3.select(svgRef.current); svg.selectAll('*').remove(); @@ -256,8 +272,8 @@ export function ArchitectureDiagram(): React.ReactElement { .attr('dx', '0') .attr('dy', '2') .attr('stdDeviation', '4') - .attr('flood-color', 'currentColor') - .attr('flood-opacity', '0.15'); + .attr('flood-color', colors.foreground) + .attr('flood-opacity', '0.1'); // Arrow marker defs @@ -271,8 +287,7 @@ export function ArchitectureDiagram(): React.ReactElement { .attr('orient', 'auto') .append('path') .attr('d', 'M0,-5L10,0L0,5') - .attr('fill', 'currentColor') - .attr('class', 'text-foreground-tertiary'); + .attr('fill', colors.foregroundTertiary); const mainGroup = svg.append('g'); @@ -296,18 +311,16 @@ export function ArchitectureDiagram(): React.ReactElement { .append('path') .attr('d', pathData) .attr('fill', 'none') - .attr('stroke', 'currentColor') - .attr('class', 'text-border') + .attr('stroke', colors.border) .attr('stroke-width', 1.5) - .attr('opacity', 0.3); + .attr('opacity', 0.5); // Animated path const animatedPath = mainGroup .append('path') .attr('d', pathData) .attr('fill', 'none') - .attr('stroke', 'currentColor') - .attr('class', 'text-foreground-tertiary') + .attr('stroke', colors.foregroundTertiary) .attr('stroke-width', 1.5) .attr('stroke-dasharray', '6,10') .attr('stroke-linecap', 'round') @@ -331,8 +344,7 @@ export function ArchitectureDiagram(): React.ReactElement { const particle = mainGroup .append('circle') .attr('r', 3) - .attr('fill', 'currentColor') - .attr('class', 'text-primary') + .attr('fill', colors.primary) .attr('opacity', 0) .attr('filter', 'url(#arch-glow)'); @@ -386,25 +398,29 @@ export function ArchitectureDiagram(): React.ReactElement { .attr('width', node.width + 8) .attr('height', node.height + 8) .attr('rx', 10) - .attr('fill', 'currentColor') - .attr('class', 'node-glow text-primary') + .attr('fill', colors.primary) .attr('opacity', 0) .attr('filter', 'url(#arch-glow)'); // Determine colors based on type - let strokeColor = 'var(--color-border)'; - let fillColor = 'var(--color-background)'; + let strokeColor = colors.border; + let fillColor = colors.background; + let textColor = colors.foreground; + let sublabelColor = colors.foregroundTertiary; + if (node.type === 'source') { - strokeColor = 'var(--color-foreground)'; + strokeColor = colors.foreground; } else if (node.type === 'process') { - strokeColor = 'var(--color-primary)'; + strokeColor = colors.primary; } else if (node.type === 'storage') { - strokeColor = 'var(--color-foreground)'; + strokeColor = colors.foreground; } else if (node.type === 'api') { - strokeColor = 'var(--color-foreground-secondary)'; + strokeColor = colors.foregroundSecondary; } else if (node.type === 'output') { - strokeColor = 'var(--color-foreground)'; - fillColor = 'var(--color-foreground)'; + strokeColor = colors.foreground; + fillColor = colors.foreground; + textColor = colors.background; + sublabelColor = colors.background; } // Main rectangle @@ -427,10 +443,7 @@ export function ArchitectureDiagram(): React.ReactElement { .attr('x', 0) .attr('y', node.sublabel ? -4 : 4) .attr('text-anchor', 'middle') - .attr( - 'fill', - node.type === 'output' ? 'var(--color-background)' : 'var(--color-foreground)' - ) + .attr('fill', textColor) .attr('font-size', '13px') .attr('font-weight', '600') .attr('font-family', 'ui-monospace, monospace') @@ -443,13 +456,10 @@ export function ArchitectureDiagram(): React.ReactElement { .attr('x', 0) .attr('y', 14) .attr('text-anchor', 'middle') - .attr( - 'fill', - node.type === 'output' ? 'var(--color-background)' : 'var(--color-foreground-tertiary)' - ) + .attr('fill', sublabelColor) .attr('font-size', '10px') .attr('font-family', 'ui-monospace, monospace') - .attr('opacity', 0.8) + .attr('opacity', node.type === 'output' ? 0.7 : 0.8) .text(node.sublabel); } @@ -493,17 +503,17 @@ export function ArchitectureDiagram(): React.ReactElement { role="img" aria-label="TPMJS System Architecture diagram" /> - - {/* Tooltip */} - {hoveredInfo && ( -
-
- {hoveredInfo.title} —{' '} - {hoveredInfo.description} -
-
- )} + + {/* Tooltip - positioned outside diagram container */} + {hoveredInfo && ( +
+
+ {hoveredInfo.title} —{' '} + {hoveredInfo.description} +
+
+ )} ); }