feat: enhance bridge diagram with animations, gradients, and visual polish
- Add gradient fills for machine, bridge, and cloud sections - Add glow effect on bridge component - Add drop shadows for depth - Add animated arrows that draw on load with stagger - Add pulse animation on connection status indicator - Add animated data flow dots between bridge and cloud - Add hover effects on MCP server boxes - Add traffic light dots and icons for visual appeal - Improve typography and spacing
This commit is contained in:
parent
b4be05e004
commit
b663ca3e05
1 changed files with 411 additions and 158 deletions
|
|
@ -16,136 +16,77 @@ export function BridgeArchitectureDiagram({ className }: BridgeArchitectureDiagr
|
|||
const svg = d3.select(svgRef.current);
|
||||
svg.selectAll('*').remove();
|
||||
|
||||
const width = 700;
|
||||
const height = 500;
|
||||
const width = 720;
|
||||
const height = 520;
|
||||
|
||||
svg.attr('viewBox', `0 0 ${width} ${height}`);
|
||||
|
||||
// Colors
|
||||
const colors = {
|
||||
machineBox: '#1e293b',
|
||||
machineStroke: '#475569',
|
||||
serverBox: '#334155',
|
||||
serverStroke: '#64748b',
|
||||
bridgeBox: '#0f766e',
|
||||
bridgeStroke: '#14b8a6',
|
||||
cloudBox: '#1e40af',
|
||||
cloudStroke: '#3b82f6',
|
||||
text: '#e2e8f0',
|
||||
textMuted: '#94a3b8',
|
||||
arrow: '#64748b',
|
||||
};
|
||||
// Define gradients
|
||||
const defs = svg.append('defs');
|
||||
|
||||
// User's Machine container
|
||||
const machineGroup = svg.append('g');
|
||||
// Machine gradient
|
||||
const machineGrad = defs
|
||||
.append('linearGradient')
|
||||
.attr('id', 'machineGradient')
|
||||
.attr('x1', '0%')
|
||||
.attr('y1', '0%')
|
||||
.attr('x2', '0%')
|
||||
.attr('y2', '100%');
|
||||
machineGrad.append('stop').attr('offset', '0%').attr('stop-color', '#1e293b');
|
||||
machineGrad.append('stop').attr('offset', '100%').attr('stop-color', '#0f172a');
|
||||
|
||||
// Machine box
|
||||
machineGroup
|
||||
.append('rect')
|
||||
.attr('x', 20)
|
||||
.attr('y', 20)
|
||||
.attr('width', width - 40)
|
||||
.attr('height', 280)
|
||||
.attr('rx', 12)
|
||||
.attr('fill', colors.machineBox)
|
||||
.attr('stroke', colors.machineStroke)
|
||||
.attr('stroke-width', 2);
|
||||
// Bridge gradient
|
||||
const bridgeGrad = defs
|
||||
.append('linearGradient')
|
||||
.attr('id', 'bridgeGradient')
|
||||
.attr('x1', '0%')
|
||||
.attr('y1', '0%')
|
||||
.attr('x2', '100%')
|
||||
.attr('y2', '100%');
|
||||
bridgeGrad.append('stop').attr('offset', '0%').attr('stop-color', '#0d9488');
|
||||
bridgeGrad.append('stop').attr('offset', '100%').attr('stop-color', '#0f766e');
|
||||
|
||||
// Machine label
|
||||
machineGroup
|
||||
.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', 50)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', colors.text)
|
||||
.attr('font-size', '16px')
|
||||
.attr('font-weight', '600')
|
||||
.text('Your Machine');
|
||||
// Cloud gradient
|
||||
const cloudGrad = defs
|
||||
.append('linearGradient')
|
||||
.attr('id', 'cloudGradient')
|
||||
.attr('x1', '0%')
|
||||
.attr('y1', '0%')
|
||||
.attr('x2', '100%')
|
||||
.attr('y2', '100%');
|
||||
cloudGrad.append('stop').attr('offset', '0%').attr('stop-color', '#2563eb');
|
||||
cloudGrad.append('stop').attr('offset', '100%').attr('stop-color', '#1d4ed8');
|
||||
|
||||
// MCP Servers
|
||||
const servers = [
|
||||
{ name: 'Chrome MCP', y: 90 },
|
||||
{ name: 'Filesystem', y: 160 },
|
||||
{ name: 'Custom Server', y: 230 },
|
||||
];
|
||||
// Glow filter for bridge
|
||||
const glowFilter = defs
|
||||
.append('filter')
|
||||
.attr('id', 'glow')
|
||||
.attr('x', '-50%')
|
||||
.attr('y', '-50%')
|
||||
.attr('width', '200%')
|
||||
.attr('height', '200%');
|
||||
glowFilter.append('feGaussianBlur').attr('stdDeviation', '3').attr('result', 'coloredBlur');
|
||||
const feMerge = glowFilter.append('feMerge');
|
||||
feMerge.append('feMergeNode').attr('in', 'coloredBlur');
|
||||
feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
|
||||
|
||||
servers.forEach((server) => {
|
||||
// Server box
|
||||
machineGroup
|
||||
.append('rect')
|
||||
.attr('x', 50)
|
||||
.attr('y', server.y)
|
||||
.attr('width', 140)
|
||||
.attr('height', 50)
|
||||
.attr('rx', 8)
|
||||
.attr('fill', colors.serverBox)
|
||||
.attr('stroke', colors.serverStroke)
|
||||
.attr('stroke-width', 1);
|
||||
// Drop shadow filter
|
||||
const shadowFilter = defs
|
||||
.append('filter')
|
||||
.attr('id', 'shadow')
|
||||
.attr('x', '-20%')
|
||||
.attr('y', '-20%')
|
||||
.attr('width', '140%')
|
||||
.attr('height', '140%');
|
||||
shadowFilter
|
||||
.append('feDropShadow')
|
||||
.attr('dx', '0')
|
||||
.attr('dy', '4')
|
||||
.attr('stdDeviation', '8')
|
||||
.attr('flood-color', 'rgba(0,0,0,0.3)');
|
||||
|
||||
// Server label
|
||||
machineGroup
|
||||
.append('text')
|
||||
.attr('x', 120)
|
||||
.attr('y', server.y + 30)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', colors.text)
|
||||
.attr('font-size', '13px')
|
||||
.text(server.name);
|
||||
|
||||
// Arrow from server to bridge
|
||||
machineGroup
|
||||
.append('path')
|
||||
.attr('d', `M 190 ${server.y + 25} L 270 ${server.y + 25}`)
|
||||
.attr('stroke', colors.arrow)
|
||||
.attr('stroke-width', 2)
|
||||
.attr('fill', 'none')
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
});
|
||||
|
||||
// Bridge CLI box
|
||||
machineGroup
|
||||
.append('rect')
|
||||
.attr('x', 280)
|
||||
.attr('y', 80)
|
||||
.attr('width', 360)
|
||||
.attr('height', 210)
|
||||
.attr('rx', 10)
|
||||
.attr('fill', colors.bridgeBox)
|
||||
.attr('stroke', colors.bridgeStroke)
|
||||
.attr('stroke-width', 2);
|
||||
|
||||
// Bridge label
|
||||
machineGroup
|
||||
.append('text')
|
||||
.attr('x', 460)
|
||||
.attr('y', 115)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', colors.text)
|
||||
.attr('font-size', '15px')
|
||||
.attr('font-weight', '600')
|
||||
.text('@tpmjs/bridge CLI');
|
||||
|
||||
// Bridge features
|
||||
const features = [
|
||||
'Connects to MCP servers',
|
||||
'Registers tools with TPMJS',
|
||||
'Polls for tool calls',
|
||||
'Returns results',
|
||||
];
|
||||
|
||||
features.forEach((feature, i) => {
|
||||
machineGroup
|
||||
.append('text')
|
||||
.attr('x', 320)
|
||||
.attr('y', 155 + i * 28)
|
||||
.attr('fill', colors.textMuted)
|
||||
.attr('font-size', '13px')
|
||||
.text(`• ${feature}`);
|
||||
});
|
||||
|
||||
// Arrow marker definition
|
||||
svg
|
||||
.append('defs')
|
||||
// Arrow marker with gradient
|
||||
defs
|
||||
.append('marker')
|
||||
.attr('id', 'arrowhead')
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
|
|
@ -156,69 +97,381 @@ export function BridgeArchitectureDiagram({ className }: BridgeArchitectureDiagr
|
|||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
||||
.attr('fill', colors.arrow);
|
||||
.attr('fill', '#14b8a6');
|
||||
|
||||
// HTTPS Arrow from machine to cloud
|
||||
svg
|
||||
// Animated arrow marker
|
||||
defs
|
||||
.append('marker')
|
||||
.attr('id', 'arrowheadAnimated')
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 8)
|
||||
.attr('refY', 5)
|
||||
.attr('markerWidth', 6)
|
||||
.attr('markerHeight', 6)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', `M ${width / 2} 300 L ${width / 2} 340`)
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z')
|
||||
.attr('fill', '#3b82f6');
|
||||
|
||||
// Colors
|
||||
const colors = {
|
||||
text: '#f1f5f9',
|
||||
textMuted: '#94a3b8',
|
||||
textDark: '#cbd5e1',
|
||||
serverBox: '#334155',
|
||||
serverStroke: '#475569',
|
||||
arrow: '#14b8a6',
|
||||
arrowPulse: '#5eead4',
|
||||
};
|
||||
|
||||
// User's Machine container
|
||||
const machineGroup = svg.append('g').attr('class', 'machine-group');
|
||||
|
||||
// Machine box with shadow
|
||||
machineGroup
|
||||
.append('rect')
|
||||
.attr('x', 20)
|
||||
.attr('y', 20)
|
||||
.attr('width', width - 40)
|
||||
.attr('height', 290)
|
||||
.attr('rx', 16)
|
||||
.attr('fill', 'url(#machineGradient)')
|
||||
.attr('stroke', '#334155')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('filter', 'url(#shadow)');
|
||||
|
||||
// Machine label with icon
|
||||
machineGroup
|
||||
.append('text')
|
||||
.attr('x', 50)
|
||||
.attr('y', 52)
|
||||
.attr('fill', colors.text)
|
||||
.attr('font-size', '14px')
|
||||
.attr('font-weight', '600')
|
||||
.attr('letter-spacing', '0.05em')
|
||||
.text('YOUR MACHINE');
|
||||
|
||||
// Decorative dots
|
||||
[0, 1, 2].forEach((i) => {
|
||||
machineGroup
|
||||
.append('circle')
|
||||
.attr('cx', width - 50 - i * 16)
|
||||
.attr('cy', 48)
|
||||
.attr('r', 5)
|
||||
.attr('fill', i === 0 ? '#ef4444' : i === 1 ? '#eab308' : '#22c55e')
|
||||
.attr('opacity', 0.8);
|
||||
});
|
||||
|
||||
// MCP Servers
|
||||
const servers = [
|
||||
{ name: 'Chrome MCP', icon: '🌐', y: 85 },
|
||||
{ name: 'Filesystem', icon: '📁', y: 155 },
|
||||
{ name: 'Custom Server', icon: '⚡', y: 225 },
|
||||
];
|
||||
|
||||
servers.forEach((server, idx) => {
|
||||
const serverGroup = machineGroup.append('g').attr('class', `server-${idx}`);
|
||||
|
||||
// Server box with hover effect
|
||||
serverGroup
|
||||
.append('rect')
|
||||
.attr('x', 50)
|
||||
.attr('y', server.y)
|
||||
.attr('width', 150)
|
||||
.attr('height', 55)
|
||||
.attr('rx', 10)
|
||||
.attr('fill', colors.serverBox)
|
||||
.attr('stroke', colors.serverStroke)
|
||||
.attr('stroke-width', 1.5)
|
||||
.style('cursor', 'pointer')
|
||||
.on('mouseenter', function () {
|
||||
d3.select(this)
|
||||
.transition()
|
||||
.duration(200)
|
||||
.attr('stroke', '#14b8a6')
|
||||
.attr('stroke-width', 2);
|
||||
})
|
||||
.on('mouseleave', function () {
|
||||
d3.select(this)
|
||||
.transition()
|
||||
.duration(200)
|
||||
.attr('stroke', colors.serverStroke)
|
||||
.attr('stroke-width', 1.5);
|
||||
});
|
||||
|
||||
// Server icon
|
||||
serverGroup
|
||||
.append('text')
|
||||
.attr('x', 70)
|
||||
.attr('y', server.y + 35)
|
||||
.attr('font-size', '20px')
|
||||
.text(server.icon);
|
||||
|
||||
// Server label
|
||||
serverGroup
|
||||
.append('text')
|
||||
.attr('x', 100)
|
||||
.attr('y', server.y + 35)
|
||||
.attr('fill', colors.text)
|
||||
.attr('font-size', '14px')
|
||||
.attr('font-weight', '500')
|
||||
.text(server.name);
|
||||
|
||||
// Animated arrow from server to bridge
|
||||
const arrowPath = serverGroup
|
||||
.append('path')
|
||||
.attr('d', `M 200 ${server.y + 27} L 280 ${server.y + 27}`)
|
||||
.attr('stroke', colors.arrow)
|
||||
.attr('stroke-width', 2)
|
||||
.attr('fill', 'none')
|
||||
.attr('marker-end', 'url(#arrowhead)')
|
||||
.attr('stroke-dasharray', '80')
|
||||
.attr('stroke-dashoffset', '80');
|
||||
|
||||
// Animate arrow on load with stagger
|
||||
arrowPath
|
||||
.transition()
|
||||
.delay(300 + idx * 150)
|
||||
.duration(600)
|
||||
.ease(d3.easeQuadOut)
|
||||
.attr('stroke-dashoffset', '0');
|
||||
});
|
||||
|
||||
// Bridge CLI box with glow
|
||||
const bridgeGroup = machineGroup.append('g').attr('class', 'bridge-group');
|
||||
|
||||
bridgeGroup
|
||||
.append('rect')
|
||||
.attr('x', 290)
|
||||
.attr('y', 75)
|
||||
.attr('width', 370)
|
||||
.attr('height', 215)
|
||||
.attr('rx', 12)
|
||||
.attr('fill', 'url(#bridgeGradient)')
|
||||
.attr('stroke', '#5eead4')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('filter', 'url(#glow)');
|
||||
|
||||
// Bridge header bar
|
||||
bridgeGroup
|
||||
.append('rect')
|
||||
.attr('x', 290)
|
||||
.attr('y', 75)
|
||||
.attr('width', 370)
|
||||
.attr('height', 40)
|
||||
.attr('rx', 12)
|
||||
.attr('fill', 'rgba(0,0,0,0.2)');
|
||||
|
||||
// Fix bottom corners of header
|
||||
bridgeGroup
|
||||
.append('rect')
|
||||
.attr('x', 290)
|
||||
.attr('y', 103)
|
||||
.attr('width', 370)
|
||||
.attr('height', 12)
|
||||
.attr('fill', 'rgba(0,0,0,0.2)');
|
||||
|
||||
// Bridge label
|
||||
bridgeGroup
|
||||
.append('text')
|
||||
.attr('x', 475)
|
||||
.attr('y', 102)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', colors.text)
|
||||
.attr('font-size', '15px')
|
||||
.attr('font-weight', '700')
|
||||
.attr('letter-spacing', '0.02em')
|
||||
.text('@tpmjs/bridge');
|
||||
|
||||
// Bridge features with icons
|
||||
const features = [
|
||||
{ icon: '🔗', text: 'Connects to MCP servers' },
|
||||
{ icon: '📝', text: 'Registers tools with TPMJS' },
|
||||
{ icon: '📡', text: 'Polls for tool calls' },
|
||||
{ icon: '✨', text: 'Returns results' },
|
||||
];
|
||||
|
||||
features.forEach((feature, i) => {
|
||||
const featureGroup = bridgeGroup.append('g').attr('class', `feature-${i}`);
|
||||
|
||||
featureGroup
|
||||
.append('text')
|
||||
.attr('x', 320)
|
||||
.attr('y', 150 + i * 32)
|
||||
.attr('font-size', '16px')
|
||||
.text(feature.icon);
|
||||
|
||||
featureGroup
|
||||
.append('text')
|
||||
.attr('x', 350)
|
||||
.attr('y', 150 + i * 32)
|
||||
.attr('fill', colors.textDark)
|
||||
.attr('font-size', '14px')
|
||||
.text(feature.text);
|
||||
});
|
||||
|
||||
// Animated pulse indicator
|
||||
const pulseGroup = bridgeGroup.append('g');
|
||||
|
||||
pulseGroup.append('circle').attr('cx', 630).attr('cy', 95).attr('r', 6).attr('fill', '#22c55e');
|
||||
|
||||
// Pulse animation
|
||||
const pulse = pulseGroup
|
||||
.append('circle')
|
||||
.attr('cx', 630)
|
||||
.attr('cy', 95)
|
||||
.attr('r', 6)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke', '#22c55e')
|
||||
.attr('stroke-width', 2);
|
||||
|
||||
function animatePulse() {
|
||||
pulse
|
||||
.attr('r', 6)
|
||||
.attr('opacity', 1)
|
||||
.transition()
|
||||
.duration(1500)
|
||||
.ease(d3.easeQuadOut)
|
||||
.attr('r', 18)
|
||||
.attr('opacity', 0)
|
||||
.on('end', animatePulse);
|
||||
}
|
||||
animatePulse();
|
||||
|
||||
// HTTPS Arrow from machine to cloud with animation
|
||||
const httpsArrowGroup = svg.append('g').attr('class', 'https-arrow');
|
||||
|
||||
// Arrow line
|
||||
const httpsLine = httpsArrowGroup
|
||||
.append('path')
|
||||
.attr('d', `M ${width / 2} 310 L ${width / 2} 365`)
|
||||
.attr('stroke', '#3b82f6')
|
||||
.attr('stroke-width', 3)
|
||||
.attr('fill', 'none')
|
||||
.attr('marker-end', 'url(#arrowhead)');
|
||||
.attr('marker-end', 'url(#arrowheadAnimated)')
|
||||
.attr('stroke-dasharray', '55')
|
||||
.attr('stroke-dashoffset', '55');
|
||||
|
||||
// HTTPS label
|
||||
svg
|
||||
httpsLine
|
||||
.transition()
|
||||
.delay(900)
|
||||
.duration(500)
|
||||
.ease(d3.easeQuadOut)
|
||||
.attr('stroke-dashoffset', '0');
|
||||
|
||||
// HTTPS badge
|
||||
const httpsBadge = httpsArrowGroup
|
||||
.append('g')
|
||||
.attr('transform', `translate(${width / 2 + 20}, 335)`);
|
||||
|
||||
httpsBadge
|
||||
.append('rect')
|
||||
.attr('x', -5)
|
||||
.attr('y', -12)
|
||||
.attr('width', 55)
|
||||
.attr('height', 20)
|
||||
.attr('rx', 4)
|
||||
.attr('fill', '#1e40af');
|
||||
|
||||
httpsBadge
|
||||
.append('text')
|
||||
.attr('x', width / 2 + 15)
|
||||
.attr('y', 328)
|
||||
.attr('fill', colors.textMuted)
|
||||
.attr('font-size', '12px')
|
||||
.attr('x', 22)
|
||||
.attr('y', 3)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', '#93c5fd')
|
||||
.attr('font-size', '11px')
|
||||
.attr('font-weight', '600')
|
||||
.text('HTTPS');
|
||||
|
||||
// TPMJS Cloud box
|
||||
const cloudGroup = svg.append('g');
|
||||
const cloudGroup = svg.append('g').attr('class', 'cloud-group');
|
||||
|
||||
cloudGroup
|
||||
.append('rect')
|
||||
.attr('x', width / 2 - 140)
|
||||
.attr('y', 350)
|
||||
.attr('width', 280)
|
||||
.attr('height', 120)
|
||||
.attr('rx', 12)
|
||||
.attr('fill', colors.cloudBox)
|
||||
.attr('stroke', colors.cloudStroke)
|
||||
.attr('stroke-width', 2);
|
||||
.attr('x', width / 2 - 150)
|
||||
.attr('y', 375)
|
||||
.attr('width', 300)
|
||||
.attr('height', 125)
|
||||
.attr('rx', 16)
|
||||
.attr('fill', 'url(#cloudGradient)')
|
||||
.attr('stroke', '#60a5fa')
|
||||
.attr('stroke-width', 2)
|
||||
.attr('filter', 'url(#shadow)');
|
||||
|
||||
// Cloud icon
|
||||
cloudGroup
|
||||
.append('text')
|
||||
.attr('x', width / 2 - 120)
|
||||
.attr('y', 415)
|
||||
.attr('font-size', '28px')
|
||||
.text('☁️');
|
||||
|
||||
// Cloud label
|
||||
cloudGroup
|
||||
.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', 385)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('x', width / 2 - 80)
|
||||
.attr('y', 415)
|
||||
.attr('fill', colors.text)
|
||||
.attr('font-size', '16px')
|
||||
.attr('font-weight', '600')
|
||||
.attr('font-size', '18px')
|
||||
.attr('font-weight', '700')
|
||||
.text('TPMJS Cloud');
|
||||
|
||||
// Cloud description
|
||||
cloudGroup
|
||||
.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', 420)
|
||||
.attr('y', 455)
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', colors.textMuted)
|
||||
.attr('font-size', '14px')
|
||||
.text('Your AI uses');
|
||||
.text('Your AI agents use bridge tools');
|
||||
|
||||
// AI icons
|
||||
const aiIcons = ['🤖', '💬', '🧠'];
|
||||
aiIcons.forEach((icon, i) => {
|
||||
cloudGroup
|
||||
.append('text')
|
||||
.attr('x', width / 2)
|
||||
.attr('y', 445)
|
||||
.attr('x', width / 2 - 40 + i * 40)
|
||||
.attr('y', 485)
|
||||
.attr('font-size', '20px')
|
||||
.attr('text-anchor', 'middle')
|
||||
.attr('fill', colors.textMuted)
|
||||
.attr('font-size', '14px')
|
||||
.text('bridge tools');
|
||||
.text(icon);
|
||||
});
|
||||
|
||||
// Data flow animation dots
|
||||
function createFlowDot(startY: number, delay: number) {
|
||||
const dot = svg
|
||||
.append('circle')
|
||||
.attr('cx', width / 2)
|
||||
.attr('cy', startY)
|
||||
.attr('r', 4)
|
||||
.attr('fill', '#60a5fa')
|
||||
.attr('opacity', 0);
|
||||
|
||||
function animate() {
|
||||
dot
|
||||
.attr('cy', startY)
|
||||
.attr('opacity', 0)
|
||||
.transition()
|
||||
.delay(delay)
|
||||
.duration(200)
|
||||
.attr('opacity', 1)
|
||||
.transition()
|
||||
.duration(800)
|
||||
.ease(d3.easeQuadIn)
|
||||
.attr('cy', 365)
|
||||
.transition()
|
||||
.duration(200)
|
||||
.attr('opacity', 0)
|
||||
.on('end', animate);
|
||||
}
|
||||
animate();
|
||||
}
|
||||
|
||||
// Create multiple flow dots with different delays
|
||||
createFlowDot(315, 0);
|
||||
createFlowDot(315, 1200);
|
||||
createFlowDot(315, 2400);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
|
@ -226,7 +479,7 @@ export function BridgeArchitectureDiagram({ className }: BridgeArchitectureDiagr
|
|||
<svg
|
||||
ref={svgRef}
|
||||
className="w-full h-auto max-w-3xl mx-auto"
|
||||
style={{ minHeight: '400px' }}
|
||||
style={{ minHeight: '420px' }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue