Files
BrainWind/app/src/components/FallbackDiagram.tsx
T

541 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function pressureColor(cpe: number, cpi: number): string {
const p = cpe - cpi;
const intensity = Math.min(1, Math.abs(p) / 1.2);
if (p > 0) return `hsl(${215 - intensity * 10}, ${70 + intensity * 25}%, ${Math.max(35, 65 - intensity * 25)}%)`;
return `hsl(0, ${70 + intensity * 25}%, ${Math.max(40, 65 - intensity * 20)}%)`;
}
function cpeColor(cpe: number): string {
const clamped = Math.max(-2.5, Math.min(1.5, cpe));
const t = (clamped + 2.5) / 4.0;
const h = 240 - t * 240;
return `hsl(${h}, 70%, 50%)`;
}
function forceLen(kN: number): number {
return Math.min(Math.max(Math.abs(kN) * 8, 15), 80);
}
interface WarehouseProps {
width: number;
length: number;
height: number;
roofPitch: number;
wallCpe: { A: number; B: number; C: number; D: number };
roofCpe: { E: number; F: number; G: number; H: number };
windAngle: 0 | 90;
cpi: number;
}
function WarehouseDiagram({ width, length, height, roofPitch, wallCpe, roofCpe, cpi }: WarehouseProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 80) / length, (vh - 100) / (height + (width / 2) * Math.tan((roofPitch * Math.PI) / 180)));
const ox = vw / 2, oy = vh - 40;
const wS = width * s, lS = length * s, hS = height * s;
const roofH = (width / 2) * Math.tan((roofPitch * Math.PI) / 180) * s;
const hx = lS / 2, hy = hS;
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
{/* Chão */}
<line x1={ox - hx - 20} y1={oy} x2={ox + hx + 20} y2={oy} stroke="#94a3b8" strokeWidth={1} />
{/* Parede frontal */}
<rect x={ox - hx} y={oy - hy} width={lS} height={hy} fill={pressureColor(wallCpe.C, cpi)} opacity={0.8} stroke="#334155" strokeWidth={1.5} />
<text x={ox} y={oy - hy / 2} textAnchor="middle" fontSize={10} fill="#1e293b" fontWeight="bold">C</text>
{/* Parede lateral esquerda (projeção) */}
<polygon points={`${ox - hx},${oy - hy} ${ox - hx - wS * 0.4},${oy - hy - wS * 0.2} ${ox - hx - wS * 0.4},${oy - wS * 0.2} ${ox - hx},${oy}`}
fill={pressureColor(wallCpe.D, cpi)} opacity={0.6} stroke="#334155" strokeWidth={1} />
<text x={ox - hx - wS * 0.2 - 5} y={oy - hy / 2 - wS * 0.1} fontSize={9} fill="#1e293b" fontWeight="bold">D</text>
{/* Telhado */}
<polygon points={`${ox - hx},${oy - hy} ${ox},${oy - hy - roofH} ${ox + hx},${oy - hy} ${ox + hx - wS * 0.4},${oy - hy - wS * 0.2} ${ox},${oy - hy - roofH - wS * 0.2} ${ox - hx - wS * 0.4},${oy - hy - wS * 0.2}`}
fill={roofCpe.G ? cpeColor(roofCpe.G) : '#94a3b8'} opacity={0.7} stroke="#334155" strokeWidth={1} />
<text x={ox + 15} y={oy - hy - roofH / 2} fontSize={9} fill="#7c3aed" fontWeight="bold">G/H</text>
{/* Rótulos de dimensão */}
<text x={ox} y={oy + 18} textAnchor="middle" fontSize={9} fill="#475569">L = {length}m</text>
<text x={ox - hx - 15} y={oy - hy / 2} textAnchor="middle" fontSize={9} fill="#475569" transform={`rotate(-90, ${ox - hx - 15}, ${oy - hy / 2})`}>h = {height}m</text>
{/* Seta de vento */}
<defs><marker id="warrow" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#22c55e" /></marker></defs>
<line x1={30} y1={oy - hS / 2} x2={70} y2={oy - hS / 2} stroke="#22c55e" strokeWidth={2} markerEnd="url(#warrow)" />
<text x={50} y={oy - hS / 2 - 8} textAnchor="middle" fontSize={8} fill="#22c55e">Vento</text>
</svg>
);
}
interface CylinderProps {
diameter: number;
height: number;
cpi: number;
cpeProfile: { angle: number; cpe: number }[];
}
function CylinderDiagram({ diameter, height, cpeProfile }: CylinderProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 100) / diameter, (vh - 80) / height);
const cx = vw / 2, cy = vh - 40;
const r = (diameter / 2) * s;
const h = height * s;
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={cx - r - 30} y1={cy} x2={cx + r + 30} y2={cy} stroke="#94a3b8" strokeWidth={1} />
{/* Cilindro — perfil lateral com cores */}
{cpeProfile.slice(0, -1).map((p, i) => {
const next = cpeProfile[i + 1];
const x0 = cx - r + (i / (cpeProfile.length - 1)) * r * 2;
const x1 = cx - r + ((i + 1) / (cpeProfile.length - 1)) * r * 2;
const mid = (p.cpe + next.cpe) / 2;
return <rect key={i} x={x0} y={cy - h} width={x1 - x0} height={h} fill={cpeColor(mid)} opacity={0.85} />;
})}
{/* Outline */}
<rect x={cx - r} y={cy - h} width={r * 2} height={h} fill="none" stroke="#334155" strokeWidth={1.5} rx={2} />
{/* Tampa superior */}
<ellipse cx={cx} cy={cy - h} rx={r} ry={6} fill={cpeColor(cpeProfile[cpeProfile.length - 1]?.cpe ?? -1)} opacity={0.7} stroke="#334155" strokeWidth={1} />
<text x={cx} y={cy - h - 10} textAnchor="middle" fontSize={9} fill="#475569">d = {diameter}m</text>
<text x={cx - r - 15} y={cy - h / 2} textAnchor="middle" fontSize={9} fill="#475569" transform={`rotate(-90, ${cx - r - 15}, ${cy - h / 2})`}>h = {height}m</text>
<text x={cx} y={cy + 18} textAnchor="middle" fontSize={9} fill="#475569">Cpe: {cpeProfile[0]?.cpe.toFixed(1)} (0°) {cpeProfile[Math.floor(cpeProfile.length / 2)]?.cpe.toFixed(1)} (90°)</text>
</svg>
);
}
interface VaultProps {
span: number;
length: number;
rise: number;
cpi: number;
cpeProfile: Record<string, number>;
}
function VaultDiagram({ span, rise, cpeProfile }: VaultProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 80) / span, (vh - 80) / rise);
const ox = vw / 2, oy = vh - 40;
const spanS = span * s, riseS = rise * s;
const archPoints: string[] = [];
const segments = 32;
for (let i = 0; i <= segments; i++) {
const t = i / segments;
const x = ox - spanS / 2 + t * spanS;
const y = oy - riseS * Math.sin(t * Math.PI);
archPoints.push(`${x},${y}`);
}
const zones = [
{ idx: 0, label: '1', key: 'zone1' },
{ idx: 5, label: '2', key: 'zone2' },
{ idx: 11, label: '3', key: 'zone3' },
{ idx: 16, label: '4', key: 'zone4' },
{ idx: 21, label: '5', key: 'zone5' },
{ idx: 27, label: '6', key: 'zone6' },
];
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={ox - spanS / 2 - 20} y1={oy} x2={ox + spanS / 2 + 20} y2={oy} stroke="#94a3b8" strokeWidth={1} />
{/* Arco */}
<polygon points={`${ox - spanS / 2},${oy} ${archPoints.join(' ')} ${ox + spanS / 2},${oy}`}
fill="none" stroke="#334155" strokeWidth={1.5} />
{/* Zonas coloridas */}
{zones.map((z, i) => {
const nextIdx = i < zones.length - 1 ? zones[i + 1].idx : segments;
const pts: string[] = [];
for (let j = z.idx; j <= nextIdx; j++) {
const t = j / segments;
pts.push(`${ox - spanS / 2 + t * spanS},${oy - riseS * Math.sin(t * Math.PI)}`);
}
const lastT = nextIdx / segments;
pts.push(`${ox - spanS / 2 + lastT * spanS},${oy}`);
const firstT = z.idx / segments;
pts.push(`${ox - spanS / 2 + firstT * spanS},${oy}`);
const cpeVal = cpeProfile[z.key] ?? -0.5;
const midX = ox - spanS / 2 + ((z.idx + nextIdx) / 2 / segments) * spanS;
const midY = oy - riseS * 0.6;
return (
<g key={z.key}>
<polygon points={pts.join(' ')} fill={cpeColor(cpeVal)} opacity={0.7} />
<text x={midX} y={midY} textAnchor="middle" fontSize={10} fill="#1e293b" fontWeight="bold">{z.label}</text>
</g>
);
})}
<text x={ox} y={oy + 18} textAnchor="middle" fontSize={9} fill="#475569">vão = {span}m</text>
<text x={ox - spanS / 2 - 15} y={oy - riseS / 2} textAnchor="middle" fontSize={9} fill="#475569" transform={`rotate(-90, ${ox - spanS / 2 - 15}, ${oy - riseS / 2})`}>flecha = {rise}m</text>
</svg>
);
}
interface DomeProps {
diameter: number;
rise: number;
wallHeight: number;
cpi: number;
cpeBarlavento: number;
cpeTopo: number;
cpeLateral: number;
}
function DomeDiagram({ diameter, rise, wallHeight, cpeBarlavento, cpeTopo }: DomeProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 80) / diameter, (vh - 80) / (wallHeight + rise));
const cx = vw / 2, cy = vh - 40;
const r = (diameter / 2) * s;
const wh = wallHeight * s;
const rh = rise * s;
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={cx - r - 30} y1={cy} x2={cx + r + 30} y2={cy} stroke="#94a3b8" strokeWidth={1} />
{/* Parede cilíndrica */}
<rect x={cx - r} y={cy - wh} width={r * 2} height={wh} fill="#94a3b8" opacity={0.5} stroke="#334155" strokeWidth={1.5} />
{/* Cúpula — 3 zonas */}
<path d={`M ${cx - r} ${cy - wh} Q ${cx - r} ${cy - wh - rh * 0.6} ${cx} ${cy - wh - rh} Q ${cx + r} ${cy - wh - rh * 0.6} ${cx + r} ${cy - wh}`}
fill={cpeColor(cpeBarlavento)} opacity={0.7} stroke="#334155" strokeWidth={1.5} />
<path d={`M ${cx - r * 0.5} ${cy - wh - rh * 0.9} Q ${cx} ${cy - wh - rh} ${cx + r * 0.5} ${cy - wh - rh * 0.9}`}
fill={cpeColor(cpeTopo)} opacity={0.7} stroke="#334155" strokeWidth={1} />
{/* Labels */}
<text x={cx - r * 0.6} y={cy - wh - rh * 0.3} textAnchor="middle" fontSize={9} fill="#1e293b" fontWeight="bold">Barlavento</text>
<text x={cx} y={cy - wh - rh - 5} textAnchor="middle" fontSize={9} fill="#1e293b" fontWeight="bold">Topo</text>
<text x={cx + r * 0.6} y={cy - wh - rh * 0.3} textAnchor="middle" fontSize={9} fill="#1e293b" fontWeight="bold">Lateral</text>
<text x={cx} y={cy + 18} textAnchor="middle" fontSize={9} fill="#475569">d = {diameter}m | h = {wallHeight}m | flecha = {rise}m</text>
</svg>
);
}
interface SignProps {
length: number;
height: number;
groundClearance: number;
alpha: number;
cf: number;
forceKN: number;
applicationPoint: number;
}
function SignDiagram({ length, height, groundClearance, cf, forceKN }: SignProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 100) / length, (vh - 80) / (height + groundClearance));
const cx = vw / 2, ground = vh - 40;
const gc = groundClearance * s;
const h = height * s;
const w = length * s;
const plateY = ground - gc - h;
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={cx - w - 30} y1={ground} x2={cx + w + 30} y2={ground} stroke="#94a3b8" strokeWidth={1} />
{/* Placa */}
<rect x={cx - w / 2} y={plateY} width={w} height={h} fill={cpeColor(cf)} opacity={0.7} stroke="#334155" strokeWidth={1.5} />
{/* Suporte */}
<line x1={cx} y1={plateY + h} x2={cx} y2={ground} stroke="#475569" strokeWidth={3} />
<text x={cx} y={plateY + h / 2 + 4} textAnchor="middle" fontSize={10} fill="#1e293b" fontWeight="bold">Cf = {cf.toFixed(2)}</text>
{/* Seta de força */}
{forceKN > 0 && (
<g>
<defs><marker id="sarrow" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#ef4444" /></marker></defs>
<line x1={cx + w / 2 + 10} y1={plateY + h / 2} x2={cx + w / 2 + 10 + forceLen(forceKN)} y2={plateY + h / 2}
stroke="#ef4444" strokeWidth={2} markerEnd="url(#sarrow)" />
<text x={cx + w / 2 + 10 + forceLen(forceKN) / 2} y={plateY + h / 2 - 6} textAnchor="middle" fontSize={8} fill="#ef4444">{forceKN.toFixed(1)} kN</text>
</g>
)}
{/* Dimensões */}
<text x={cx} y={ground + 18} textAnchor="middle" fontSize={9} fill="#475569"> = {length}m | h = {height}m | e = {groundClearance}m</text>
</svg>
);
}
interface BarProps {
barType: 'flat' | 'circular';
section?: string;
diameter?: number;
width?: number;
length: number;
alpha: number;
fxKN: number;
fyKN: number;
cx: number;
}
function BarDiagram({ barType, length, alpha, fxKN, fyKN, cx: cxVal }: BarProps) {
const vw = 400, vh = 300;
const cx = vw / 2, cy = vh / 2;
const barLen = Math.min(length * 8, vw - 100);
const forceMag = Math.sqrt(fxKN * fxKN + fyKN * fyKN);
const forceAngle = Math.atan2(fyKN, fxKN);
void barType;
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<g transform={`rotate(${-alpha * 180 / Math.PI}, ${cx}, ${cy})`}>
{/* Barra */}
<line x1={cx - barLen / 2} y1={cy} x2={cx + barLen / 2} y2={cy} stroke={cpeColor(cxVal)} strokeWidth={barType === 'circular' ? 6 : 10} strokeLinecap="round" />
<text x={cx} y={cy - 12} textAnchor="middle" fontSize={9} fill="#475569">{barType === 'circular' ? `d=${length}m` : `=${length}m`}</text>
</g>
{/* Eixo */}
<line x1={cx - barLen / 2 - 15} y1={cy} x2={cx + barLen / 2 + 15} y2={cy} stroke="#94a3b8" strokeWidth={0.5} strokeDasharray="4" />
{/* Seta de força */}
{forceMag > 0.01 && (
<g>
<defs><marker id="barrow" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#ef4444" /></marker></defs>
<line x1={cx} y1={cy} x2={cx + Math.cos(forceAngle) * forceLen(forceMag)} y2={cy + Math.sin(forceAngle) * forceLen(forceMag)}
stroke="#ef4444" strokeWidth={2} markerEnd="url(#barrow)" />
<text x={cx + Math.cos(forceAngle) * forceLen(forceMag) / 2} y={cy + Math.sin(forceAngle) * forceLen(forceMag) / 2 - 6}
textAnchor="middle" fontSize={8} fill="#ef4444">{forceMag.toFixed(1)} kN</text>
</g>
)}
<text x={cx} y={vh - 15} textAnchor="middle" fontSize={9} fill="#475569">α = {alpha}° | Cx = {cxVal.toFixed(2)}</text>
</svg>
);
}
interface BridgeProps {
lp: number;
width: number;
deckHeight: number;
heg: number;
cx: number;
cz: number;
fxPerLength: number;
fzPerLength: number;
}
function BridgeDiagram({ lp, width, deckHeight, heg, cx: cxVal, fxPerLength, fzPerLength }: BridgeProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 80) / lp, (vh - 80) / (deckHeight + heg));
const ox = vw / 2, ground = vh - 40;
const lpS = lp * s;
const dh = deckHeight * s;
const deckT = Math.max(heg, 0.8) * s;
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={ox - lpS / 2 - 30} y1={ground} x2={ox + lpS / 2 + 30} y2={ground} stroke="#94a3b8" strokeWidth={1} />
{/* Água/solo */}
<rect x={ox - lpS / 2 - 20} y={ground - 5} width={lpS + 40} height={10} fill="#60a5fa" opacity={0.3} rx={2} />
{/* Pilares */}
{[-0.35, 0, 0.35].map((frac, i) => (
<rect key={i} x={ox + frac * lpS - 5} y={ground - dh} width={10} height={dh} fill="#64748b" opacity={0.7} />
))}
{/* Tabuleiro */}
<rect x={ox - lpS / 2} y={ground - dh - deckT} width={lpS} height={deckT} fill={cpeColor(cxVal)} opacity={0.8} stroke="#334155" strokeWidth={1.5} />
<text x={ox} y={ground - dh - deckT / 2 + 4} textAnchor="middle" fontSize={9} fill="#1e293b" fontWeight="bold">Cx = {cxVal.toFixed(2)}</text>
{/* Guarda-rodas */}
<line x1={ox - lpS / 2} y1={ground - dh - deckT - 3} x2={ox + lpS / 2} y2={ground - dh - deckT - 3} stroke="#94a3b8" strokeWidth={2} />
{/* Setas de força */}
<defs>
<marker id="bga" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#ef4444" /></marker>
<marker id="bgb" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#3b82f6" /></marker>
</defs>
{fxPerLength !== 0 && (
<line x1={ox - lpS / 2 - 5} y1={ground - dh - deckT / 2} x2={ox - lpS / 2 - 5 + forceLen(fxPerLength)} y2={ground - dh - deckT / 2}
stroke="#ef4444" strokeWidth={2} markerEnd="url(#bga)" />
)}
{fzPerLength !== 0 && (
<line x1={ox + lpS / 2 + 5} y1={ground - dh - deckT} x2={ox + lpS / 2 + 5} y2={ground - dh - deckT - forceLen(fzPerLength)}
stroke="#3b82f6" strokeWidth={2} markerEnd="url(#bgb)" />
)}
<text x={ox} y={ground + 18} textAnchor="middle" fontSize={9} fill="#475569">Lp = {lp}m | B = {width}m | z = {deckHeight}m</text>
</svg>
);
}
interface TowerProps {
section: 'square' | 'triangular';
baseWidth: number;
height: number;
panels: number;
phi: number;
alphaWind: number;
forceKN: number;
}
function TowerDiagram({ baseWidth, height, panels, phi, forceKN }: TowerProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 80) / baseWidth, (vh - 80) / height);
const cx = vw / 2, ground = vh - 40;
const bw = baseWidth * s;
const h = height * s;
const lines: React.ReactElement[] = [];
for (let p = 0; p < panels; p++) {
const y0 = ground - (p / panels) * h;
const y1 = ground - ((p + 1) / panels) * h;
const shrink = p / panels;
const nextShrink = (p + 1) / panels;
const w0 = bw * (1 - shrink * 0.6);
const w1 = bw * (1 - nextShrink * 0.6);
// Montantes
lines.push(<line key={`l${p}`} x1={cx - w0 / 2} y1={y0} x2={cx - w1 / 2} y2={y1} stroke="#1e293b" strokeWidth={2} />);
lines.push(<line key={`r${p}`} x1={cx + w0 / 2} y1={y0} x2={cx + w1 / 2} y2={y1} stroke="#1e293b" strokeWidth={2} />);
// Diagonais
lines.push(<line key={`d1${p}`} x1={cx - w0 / 2} y1={y0} x2={cx + w1 / 2} y2={y1} stroke={cpeColor(phi)} strokeWidth={1} opacity={0.7} />);
lines.push(<line key={`d2${p}`} x1={cx + w0 / 2} y1={y0} x2={cx - w1 / 2} y2={y1} stroke={cpeColor(phi)} strokeWidth={1} opacity={0.7} />);
// Travessa
lines.push(<line key={`h${p}`} x1={cx - w1 / 2} y1={y1} x2={cx + w1 / 2} y2={y1} stroke="#64748b" strokeWidth={1} />);
}
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={cx - bw - 20} y1={ground} x2={cx + bw + 20} y2={ground} stroke="#94a3b8" strokeWidth={1} />
{lines}
{/* Seta de força */}
{forceKN > 0 && (
<g>
<defs><marker id="tarrow" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#ef4444" /></marker></defs>
<line x1={cx} y1={ground - h - 5} x2={cx + forceLen(forceKN)} y2={ground - h - 5} stroke="#ef4444" strokeWidth={2} markerEnd="url(#tarrow)" />
<text x={cx + forceLen(forceKN) / 2} y={ground - h - 12} textAnchor="middle" fontSize={8} fill="#ef4444">{forceKN.toFixed(1)} kN</text>
</g>
)}
<text x={cx} y={ground + 18} textAnchor="middle" fontSize={9} fill="#475569">h = {height}m | base = {baseWidth}m | φ = {phi.toFixed(2)}</text>
</svg>
);
}
interface IsolatedRoofProps {
type: 'shed' | 'gable';
theta: number;
height: number;
depth: number;
cpeWindward: number;
cpeLeeward: number;
cpeTop: number;
forceKN: number;
}
function IsolatedRoofDiagram({ type, theta, height, depth, cpeWindward, cpeLeeward, cpeTop, forceKN }: IsolatedRoofProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 100) / depth, (vh - 80) / (height + depth * Math.tan((theta * Math.PI) / 180)));
const cx = vw / 2, ground = vh - 40;
const h = height * s;
const d = depth * s;
const rise = d * Math.tan((theta * Math.PI) / 180);
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={cx - d - 30} y1={ground} x2={cx + d + 30} y2={ground} stroke="#94a3b8" strokeWidth={1} />
{/* Pilares */}
<line x1={cx - d / 2} y1={ground} x2={cx - d / 2} y2={ground - h} stroke="#475569" strokeWidth={3} />
<line x1={cx + d / 2} y1={ground} x2={cx + d / 2} y2={ground - h} stroke="#475569" strokeWidth={3} />
{type === 'gable' && <line x1={cx} y1={ground} x2={cx} y2={ground - h} stroke="#475569" strokeWidth={3} />}
{/* Cobertura */}
{type === 'shed' ? (
<polygon points={`${cx - d / 2},${ground - h} ${cx + d / 2},${ground - h - rise} ${cx + d / 2},${ground - h - rise + 3} ${cx - d / 2},${ground - h + 3}`}
fill={cpeColor(cpeTop)} opacity={0.8} stroke="#334155" strokeWidth={1.5} />
) : (
<>
<line x1={cx - d / 2} y1={ground - h} x2={cx} y2={ground - h - rise} stroke="#334155" strokeWidth={2} />
<line x1={cx} y1={ground - h - rise} x2={cx + d / 2} y2={ground - h} stroke="#334155" strokeWidth={2} />
<polygon points={`${cx - d / 2},${ground - h} ${cx},${ground - h - rise} ${cx + d / 2},${ground - h}`}
fill={cpeColor(cpeTop)} opacity={0.6} stroke="#334155" strokeWidth={1.5} />
</>
)}
{/* Labels de zona */}
<text x={cx - d / 3} y={ground - h - rise * 0.3} textAnchor="middle" fontSize={9} fill="#1e293b" fontWeight="bold">Barl. {cpeWindward.toFixed(1)}</text>
<text x={cx + d / 3} y={ground - h - rise * 0.3} textAnchor="middle" fontSize={9} fill="#1e293b" fontWeight="bold">Sot. {cpeLeeward.toFixed(1)}</text>
<text x={cx} y={ground - h - rise - 8} textAnchor="middle" fontSize={9} fill="#7c3aed" fontWeight="bold">Topo {cpeTop.toFixed(1)}</text>
{/* Seta de força */}
{forceKN > 0 && (
<g>
<defs><marker id="iroof" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#ef4444" /></marker></defs>
<line x1={cx} y1={ground - h - rise - 12} x2={cx} y2={ground - h - rise - 12 - forceLen(forceKN)}
stroke="#ef4444" strokeWidth={2} markerEnd="url(#iroof)" />
<text x={cx + 12} y={ground - h - rise - 12 - forceLen(forceKN) / 2} fontSize={8} fill="#ef4444">{forceKN.toFixed(1)} kN</text>
</g>
)}
<text x={cx} y={ground + 18} textAnchor="middle" fontSize={9} fill="#475569">θ = {theta}° | h = {height}m | prof. = {depth}m</text>
</svg>
);
}
interface DynamicsProps {
height: number;
freq: number;
windSpeed: number;
scruton: number;
sectionShape: string;
sectionSize: number;
showVortexStreet: boolean;
showModeShape: boolean;
}
function DynamicsDiagram({ height, freq, scruton, sectionShape, sectionSize, showVortexStreet, showModeShape }: DynamicsProps) {
const vw = 400, vh = 300;
const s = Math.min((vw - 100) / sectionSize, (vh - 80) / height);
const cx = vw / 2, ground = vh - 40;
const h = height * s;
const w = sectionSize * s;
void sectionShape;
return (
<svg viewBox={`0 0 ${vw} ${vh}`} className="w-full h-full">
<rect width={vw} height={vh} fill="none" />
<line x1={cx - w - 40} y1={ground} x2={cx + w + 80} y2={ground} stroke="#94a3b8" strokeWidth={1} />
{/* Estrutura */}
{sectionShape === 'circle' ? (
<ellipse cx={cx} cy={ground - h / 2} rx={w / 2} ry={h / 2} fill="#3b82f6" opacity={0.6} stroke="#1e40af" strokeWidth={1.5} />
) : (
<rect x={cx - w / 2} y={ground - h} width={w} height={h} fill="#3b82f6" opacity={0.6} stroke="#1e40af" strokeWidth={1.5} rx={2} />
)}
{/* Modo de oscilação */}
{showModeShape && (
<path d={`M ${cx} ${ground} Q ${cx + 8} ${ground - h * 0.5} ${cx} ${ground - h}`}
fill="none" stroke="#ef4444" strokeWidth={2} strokeDasharray="4" />
)}
{/* Rua de vórtices */}
{showVortexStreet && (
<g>
{[0.2, 0.4, 0.6, 0.8, 1.0].map((_, i) => (
<circle key={i} cx={cx + w / 2 + 20 + i * 18} cy={ground - h / 2 + (i % 2 === 0 ? -1 : 1) * (10 + i * 3)}
r={4 - i * 0.5} fill="#a855f7" opacity={0.8 - i * 0.12} />
))}
</g>
)}
{/* Seta de vento */}
<defs><marker id="darrow" markerWidth={8} markerHeight={6} refX={8} refY={3} orient="auto"><polygon points="0,0 8,3 0,6" fill="#22c55e" /></marker></defs>
<line x1={30} y1={ground - h / 2} x2={70} y2={ground - h / 2} stroke="#22c55e" strokeWidth={2} markerEnd="url(#darrow)" />
<text x={50} y={ground - h / 2 - 8} textAnchor="middle" fontSize={8} fill="#22c55e">Vento</text>
<text x={cx} y={ground + 18} textAnchor="middle" fontSize={9} fill="#475569">h = {height}m | f = {freq}Hz | Sc = {scruton.toFixed(1)}</text>
</svg>
);
}
export type FallbackDiagramProps =
| { type: 'warehouse'; props: WarehouseProps }
| { type: 'cylinder'; props: CylinderProps }
| { type: 'vault'; props: VaultProps }
| { type: 'dome'; props: DomeProps }
| { type: 'sign'; props: SignProps }
| { type: 'bar'; props: BarProps }
| { type: 'bridge'; props: BridgeProps }
| { type: 'tower'; props: TowerProps }
| { type: 'isolatedRoof'; props: IsolatedRoofProps }
| { type: 'dynamics'; props: DynamicsProps };
export default function FallbackDiagram(input: FallbackDiagramProps) {
return (
<div style={{ width: '100%', height: '100%', minHeight: '300px', borderRadius: 'var(--radius-lg)', overflow: 'hidden' }}
className="glass-panel flex items-center justify-center bg-muted/10">
{input.type === 'warehouse' && <WarehouseDiagram {...input.props} />}
{input.type === 'cylinder' && <CylinderDiagram {...input.props} />}
{input.type === 'vault' && <VaultDiagram {...input.props} />}
{input.type === 'dome' && <DomeDiagram {...input.props} />}
{input.type === 'sign' && <SignDiagram {...input.props} />}
{input.type === 'bar' && <BarDiagram {...input.props} />}
{input.type === 'bridge' && <BridgeDiagram {...input.props} />}
{input.type === 'tower' && <TowerDiagram {...input.props} />}
{input.type === 'isolatedRoof' && <IsolatedRoofDiagram {...input.props} />}
{input.type === 'dynamics' && <DynamicsDiagram {...input.props} />}
</div>
);
}