/* ============================================================================
PRSM SOLAR — RACKING STRUCTURAL DIAGRAMS (native SVG)
Side elevation (section), array face, footing plan + the two option diagrams.
Faithful to locked geometry: 55° tilt · 6×6 back / 4×4 front posts ·
2×10 inside joists · 2×8 beams · 4–5 ft frost footings · 2-high × 7-wide.
Reuses the global ZoomPan from schematics.jsx.
========================================================================== */
const { useState: useStateRD } = React;
const RWire = ({ d, cls = "" }) => <path className={"wire " + cls} d={d} />;
const RSym = ({ d, cls = "" }) => <path className={"sym " + cls} d={d} />;
function Dim({ x1, y1, x2, y2, label, off = 0, anchor = "middle", flip = false }) {
const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
const horiz = Math.abs(x2 - x1) > Math.abs(y2 - y1);
const tx = horiz ? 0 : 5, ty = horiz ? 5 : 0;
return (
<g>
<line className="dim-l" x1={x1} y1={y1} x2={x2} y2={y2} />
<line className="dim-l" x1={x1 - tx} y1={y1 - ty} x2={x1 + tx} y2={y1 + ty} />
<line className="dim-l" x1={x2 - tx} y1={y2 - ty} x2={x2 + tx} y2={y2 + ty} />
<text className="dim-t" x={mx + (horiz ? 0 : (flip ? -10 : 10))} y={my + (horiz ? (flip ? -7 : 16) : 4)}
textAnchor={horiz ? "middle" : (flip ? "end" : "start")}>{label}</text>
</g>
);
}
function Call({ x, y, tx, ty, label, anchor = "start" }) {
return (
<g>
<line className="lead" x1={x} y1={y} x2={tx} y2={ty} />
<circle className="lead-dot" cx={x} cy={y} r="2.6" />
<text className="call-t" x={tx + (anchor === "end" ? -6 : 6)} y={ty + 3.5} textAnchor={anchor}>{label}</text>
</g>
);
}
function Concrete({ x, w, yTop, yBot, label }) {
const dots = [];
for (let i = 0; i < 9; i++) {
dots.push(<circle key={i} cx={x - w / 2 + 6 + Math.random() * (w - 12)} cy={yTop + 14 + Math.random() * (yBot - yTop - 20)} r={1.3 + Math.random()} className="agg" />);
}
return (
<g>
<path className="footing" d={`M${x - w / 2} ${yTop} L${x + w / 2} ${yTop} L${x + w / 2 - 4} ${yBot} Q${x} ${yBot + 8} ${x - w / 2 + 4} ${yBot} Z`} />
{dots}
{label && <text className="slabel" x={x} y={(yTop + yBot) / 2 + 30} textAnchor="middle">{label}</text>}
</g>
);
}
// reusable 1.8 m human silhouette for scale (front view)
function Human({ cx, feetY, S, label }) {
const head = 0.13 * S, topY = feetY - 1.8 * S;
const shoulder = topY + head * 2;
return (
<g>
<g className="human">
<circle cx={cx} cy={topY + head} r={head} />
<path d={`M ${cx} ${shoulder}
L ${cx - head * 1.15} ${shoulder + 0.6 * S}
L ${cx - head * 0.7} ${feetY}
L ${cx + head * 0.15} ${feetY}
L ${cx + head * 0.22} ${shoulder + 0.48 * S}
L ${cx + head * 1.18} ${shoulder + 0.6 * S} Z`} />
</g>
{label && <text className="slabel" x={cx} y={feetY + 16} textAnchor="middle">{label}</text>}
</g>
);
}
// graphic scale bar (alternating 0.5 m segments)
function ScaleBar({ x, y, S, meters = 2 }) {
const seg = 0.5, n = Math.round(meters / seg), w = meters * S, h = 7;
const rects = [];
for (let i = 0; i < n; i++)
rects.push(<rect key={i} x={x + i * seg * S} y={y} width={seg * S} height={h} className={i % 2 ? "sb-b" : "sb-a"} />);
const ft = (meters * 3.28084).toFixed(1);
return (
<g>
{rects}
<rect x={x} y={y} width={w} height={h} className="sb-frame" />
<text className="slabel" x={x} y={y - 6}>SCALE</text>
<text className="slabel" x={x} y={y + 20}>0</text>
<text className="slabel" x={x + w} y={y + 20} textAnchor="middle">{meters} m / {ft} ft</text>
</g>
);
}
/* ============================ SIDE ELEVATION (TRUE SCALE) ============= */
function RackElevation() {
const S = 86, G = 392, X0 = 340;
const M = (xm, ym) => ({ x: X0 + xm * S, y: G - ym * S });
const t = 55 * Math.PI / 180;
const cT = Math.cos(t), sT = Math.sin(t);
const L = 2.26, clr = 0.5;
const run = L * cT, rise = L * sT;
const footM = 1.4, over = 0.26;
const lowE = M(0, clr);
const highE = M(run, clr + rise);
const highO = M(run + over * cT, clr + rise + over * sT);
const fpBase = M(0, -footM), bpBase = M(run, -footM);
const bpTopM = clr + rise - 0.12;
const bpTop = M(run, bpTopM);
const dx = highO.x - lowE.x, dy = highO.y - lowE.y;
const len = Math.hypot(dx, dy); const ux = dx / len, uy = dy / len;
const nx = uy, ny = -ux;
const off = (p, d) => ({ x: p.x + nx * d, y: p.y + ny * d });
const INCH = 0.0254;
const post4 = 3.5 * INCH * S, post6 = 5.5 * INCH * S;
const joistD = 9.25 * INCH * S;
const beamW = 1.5 * INCH * S, beamD = 7.25 * INCH * S;
const panelT = 1.6 * INCH * S;
const po = joistD / 2 + 0.05 * S + panelT / 2;
const joistPoly = [off(lowE, joistD / 2), off(highO, joistD / 2), off(highO, -joistD / 2), off(lowE, -joistD / 2)].map(p => `${p.x},${p.y}`).join(" ");
const panelPoly = [off(lowE, po - panelT / 2), off(highO, po - panelT / 2), off(highO, po + panelT / 2), off(lowE, po + panelT / 2)].map(p => `${p.x},${p.y}`).join(" ");
const midC = { x: (lowE.x + highO.x) / 2, y: (lowE.y + highO.y) / 2 };
const pMid = off(midC, po);
const fpTopY = off(lowE, -joistD / 2).y, bpTopY = off(highE, -joistD / 2).y;
const hx = X0 - 1.05 * S;
return (
<ZoomPan contentW={720} contentH={560} height={530} fig="FIG. R1 · SIDE ELEVATION (TRUE SCALE)">
<line className="grade" x1={120} y1={G} x2={660} y2={G} />
{[...Array(17)].map((_, i) => <line key={i} className="hatch" x1={130 + i * 32} y1={G} x2={122 + i * 32} y2={G + 12} />)}
<text className="slabel" x={130} y={G + 28}>GRADE</text>
<line className="frost" x1={290} y1={G + footM * S - 6} x2={520} y2={G + footM * S - 6} />
<text className="slabel" x={520} y={G + footM * S - 12} textAnchor="end">FROST LINE ≈ 4 ft</text>
<Concrete x={lowE.x} w={0.32 * S} yTop={G - 6} yBot={fpBase.y} label="30 MPa" />
<Concrete x={highE.x} w={0.36 * S} yTop={G - 6} yBot={bpBase.y} label="30 MPa" />
<rect className="post" x={lowE.x - post4 / 2} y={fpTopY} width={post4} height={fpBase.y - fpTopY - 22} rx="1" />
<rect className="post" x={highE.x - post6 / 2} y={bpTopY} width={post6} height={bpBase.y - bpTopY - 22} rx="1" />
<rect className="beam" x={lowE.x - beamW / 2} y={fpTopY - beamD} width={beamW} height={beamD} />
<rect className="beam" x={highE.x - beamW / 2} y={bpTopY - beamD} width={beamW} height={beamD} />
<polygon className="joist-band" points={joistPoly} />
<polygon className="panel-poly" points={panelPoly} />
<line className="panel-div" x1={pMid.x - nx * (panelT / 2 + 1.5)} y1={pMid.y - ny * (panelT / 2 + 1.5)} x2={pMid.x + nx * (panelT / 2 + 1.5)} y2={pMid.y + ny * (panelT / 2 + 1.5)} />
<line className="dim-l" x1={lowE.x} y1={lowE.y} x2={lowE.x + 86} y2={lowE.y} />
<path className="arc" d={`M ${lowE.x + 64} ${lowE.y} A 64 64 0 0 0 ${lowE.x + 64 * cT} ${lowE.y - 64 * sT}`} />
<text className="dim-t" x={lowE.x + 52} y={lowE.y - 22}>55° tilt</text>
<Human cx={hx} feetY={G} S={S} label="1.8 m / 5′11″" />
<Dim x1={lowE.x - 0.42 * S} y1={lowE.y} x2={lowE.x - 0.42 * S} y2={G} label="0.5 m / 20 in" flip />
<Dim x1={lowE.x - 0.42 * S} y1={G} x2={lowE.x - 0.42 * S} y2={fpBase.y} label="4–5 ft" />
<Dim x1={highO.x + 0.5 * S} y1={highE.y} x2={highO.x + 0.5 * S} y2={G} label="2.35 m / 7′9″ to top edge" />
<Call x={highE.x} y={(bpTop.y + G) / 2} tx={highO.x + 0.5 * S} ty={(bpTop.y + G) / 2 + 60} label="6×6 back post" />
<Call x={lowE.x} y={(lowE.y + G) / 2} tx={lowE.x + 0.5 * S} ty={(lowE.y + G) / 2 + 28} label="4×4 front post" />
<Call x={pMid.x} y={pMid.y} tx={pMid.x - 1.1 * S} ty={pMid.y - 0.5 * S} label="14 × 575 W · 2-high" anchor="end" />
<Call x={off(midC, 0).x} y={off(midC, 0).y} tx={lowE.x - 0.1 * S} ty={highO.y + 0.7 * S} label="2×10 joist · cut 55°" anchor="end" />
<text className="slabel" x={120} y={48}>MEMBERS DRAWN TO TRUE DRESSED-LUMBER CROSS-SECTION</text>
<text className="slabel" x={120} y={62}>4×4 = 3.5″ · 6×6 = 5.5″ · 2×10 = 9.25″ deep · panel ≈ 40 mm</text>
</ZoomPan>
);
}
/* ============================ ARRAY FACE ============================== */
function RackFront() {
const S = 42;
const pw = 2.3, ph = 1.13, cols = 7, rows = 2;
const cw = pw * S, rh = ph * S;
const clr = 0.5, x0 = 250, yTop = 150;
const x1 = x0 + cw * cols, yBot = yTop + rh * rows;
const grade = yBot + clr * S;
const cells = [];
const gx = cw * 0.03, gy = rh * 0.03;
for (let r = 0; r < rows; r++)
for (let c = 0; c < cols; c++)
cells.push(<rect key={r + "-" + c} className="panel-cell" x={x0 + c * cw + gx} y={yTop + r * rh + gy} width={cw - 2 * gx} height={rh - 2 * gy} rx="2" />);
const posts = [];
const post4f = 3.5 * 0.0254 * S;
for (let i = 0; i <= cols; i++) {
const px = x0 + i * cw;
posts.push(<line key={i} className="post-front" x1={px} y1={yBot} x2={px} y2={grade} strokeWidth={post4f} />);
posts.push(<line key={"d" + i} className="joist-back" x1={px} y1={yTop - 5} x2={px} y2={yBot} />);
}
const hx = 150;
return (
<ZoomPan contentW={1080} contentH={384} height={420} fig="FIG. R2 · ARRAY FACE (FRONT ELEVATION)">
<line className="grade" x1={90} y1={grade} x2={x1 + 0.7 * S} y2={grade} />
{[...Array(27)].map((_, i) => <line key={i} className="hatch" x1={100 + i * 32} y1={grade} x2={92 + i * 32} y2={grade + 12} />)}
<text className="slabel" x={100} y={grade + 28}>GRADE</text>
{posts}
{cells}
<rect className="array-frame" x={x0} y={yTop} width={x1 - x0} height={yBot - yTop} rx="3" />
<Human cx={hx} feetY={grade} S={S} label="1.8 m / 5′11″" />
<Dim x1={x0} y1={yBot + 52} x2={x1} y2={yBot + 52} label="16.1 m (52.8 ft) · 7 panels wide · 7 bays" />
<line className="dim-l" x1={x0 - 24} y1={yTop} x2={x0 - 24} y2={yBot} />
<line className="dim-l" x1={x0 - 29} y1={yTop} x2={x0 - 19} y2={yTop} />
<line className="dim-l" x1={x0 - 29} y1={yBot} x2={x0 - 19} y2={yBot} />
<text className="dim-t" transform={`translate(${x0 - 34} ${(yTop + yBot) / 2}) rotate(-90)`} textAnchor="middle">2.26 m (7.4 ft) · 2 high</text>
<Dim x1={x1 + 0.5 * S} y1={yBot} x2={x1 + 0.5 * S} y2={grade} label="0.5 m / 20 in" />
<ScaleBar x={x0} y={grade + 58} S={S} meters={2} />
<text className="slabel" x={x0} y={yTop - 46}>ARRAY FACE · 14 PANELS · LANDSCAPE 2-HIGH × 7-WIDE</text>
<text className="slabel" x={x0} y={yTop - 32}>EACH PANEL 90.6 × 44.6 in (2.3 × 1.13 m) · DRAWN TO SCALE</text>
<text className="slabel" x={x0} y={yTop - 18}>4×4 FRONT POSTS · LOW EDGE 0.5 m / 20 in OFF GRADE · TILT & TOP → SIDE ELEVATION</text>
</ZoomPan>
);
}
/* ============================ FOOTING PLAN =========================== */
function RackPlan() {
const x0 = 90, cols = 8, cw = 96;
const yBack = 110, yFront = 240;
const Sp = cw / 2.3;
const footR = 0.16 * Sp;
const p6 = 5.5 * 0.0254 * Sp, p4 = 3.5 * 0.0254 * Sp;
const beamW = 1.5 * 0.0254 * Sp;
const pts = [];
for (let i = 0; i < cols; i++) {
const px = x0 + i * cw;
pts.push(<g key={i}>
<circle className="foot-plan" cx={px} cy={yBack} r={footR} />
<rect className="post-top" x={px - p6 / 2} y={yBack - p6 / 2} width={p6} height={p6} />
<circle className="foot-plan" cx={px} cy={yFront} r={footR} />
<rect className="post-top" x={px - p4 / 2} y={yFront - p4 / 2} width={p4} height={p4} />
<line className="joist-back" x1={px} y1={yBack} x2={px} y2={yFront} />
</g>);
}
return (
<ZoomPan contentW={980} contentH={368} height={400} fig="FIG. R3 · FOOTING PLAN (TOP VIEW)">
<line className="beam-line" x1={x0} y1={yBack} x2={x0 + (cols - 1) * cw} y2={yBack} strokeWidth={beamW} />
<line className="beam-line" x1={x0} y1={yFront} x2={x0 + (cols - 1) * cw} y2={yFront} strokeWidth={beamW} />
{pts}
<Dim x1={x0} y1={290} x2={x0 + (cols - 1) * cw} y2={290} label="16.1 m (52.8 ft) · 7 bays" />
<Dim x1={x0 + (cols - 1) * cw + 46} y1={yBack} x2={x0 + (cols - 1) * cw + 46} y2={yFront} label="~1.3 m / 4.3 ft run" />
<ScaleBar x={x0} y={322} S={Sp} meters={4} />
<text className="slabel" x={x0 - 13} y={yBack - 28}>BACK ROW · 6×6 posts · 8 footings</text>
<text className="slabel" x={x0 - 13} y={yFront + 40}>FRONT ROW · 4×4 posts · 8 footings</text>
<text className="slabel" x={x0 - 13} y={yBack - 44}>16 FROST FOOTINGS · ≈320 mm PIERS · 4–5 ft DEEP · 30 MPa</text>
</ZoomPan>
);
}
/* ===================== OPTION 2 — SEASONAL PRESET ==================== */
function Opt2Elevation() {
const S = 80, G = 392, X0 = 250;
const M = (xm, ym) => ({ x: X0 + xm * S, y: G - ym * S });
const D = Math.PI / 180, L = 2.26, clr = 0.5, footM = 1.4;
const P0 = M(0, clr);
const HE = (deg) => M(L * Math.cos(deg * D), clr + L * Math.sin(deg * D));
const he = HE(55);
const dx = he.x - P0.x, dy = he.y - P0.y, len = Math.hypot(dx, dy);
const nx = dy / len, ny = -dx / len, po = 8;
const pL = { x: P0.x + nx * po, y: P0.y + ny * po }, pH = { x: he.x + nx * po, y: he.y + ny * po };
const bx = M(1.35, 0).x;
const holes = [55, 45, 35, 30].map((a, i) => ({ a, y: M(1.35, 1.18 - i * 0.2).y }));
const hx = X0 - 0.95 * S;
return (
<ZoomPan contentW={760} contentH={560} height={520} fig="FIG. R4 · OPTION 2 — SEASONAL ADJUSTABLE (PRESETS)">
<line className="grade" x1={120} y1={G} x2={700} y2={G} />
{[...Array(18)].map((_, i) => <line key={i} className="hatch" x1={130 + i * 32} y1={G} x2={122 + i * 32} y2={G + 12} />)}
<text className="slabel" x={130} y={G + 28}>GRADE</text>
<Concrete x={P0.x} w={0.3 * S} yTop={G - 6} yBot={M(0, -footM).y} label="" />
<Concrete x={bx} w={0.3 * S} yTop={G - 6} yBot={M(1.35, -footM).y} label="30 MPa · 4–5 ft" />
{[45, 35, 30].map((a) => { const h = HE(a); return <line key={a} className="ghost-panel" x1={P0.x} y1={P0.y} x2={h.x} y2={h.y} />; })}
{[45, 35, 30].map((a) => { const h = HE(a); return <text key={"t" + a} className="ghost-lbl" x={h.x + 7} y={h.y + 4}>{a}°</text>; })}
<rect className="post" x={P0.x - 4} y={P0.y} width={8} height={M(0, -footM).y - P0.y - 22} rx="1" />
<rect className="post" x={bx - 5} y={holes[0].y - 10} width={10} height={M(1.35, -footM).y - holes[0].y - 12} rx="1" />
{holes.map((h, i) => <g key={i}>
<circle className={"hole" + (i === 0 ? " on" : "")} cx={bx} cy={h.y} r={3.4} />
<text className="slabel" x={bx + 10} y={h.y + 3.5}>{h.a}°</text>
</g>)}
<line className="lock-strut" x1={he.x - nx * 6} y1={he.y - ny * 6} x2={bx} y2={holes[0].y} />
<line className="joist2" x1={P0.x} y1={P0.y} x2={he.x} y2={he.y} />
<line className="panel-line" x1={pL.x} y1={pL.y} x2={pH.x} y2={pH.y} />
<circle className="pivot-bolt" cx={P0.x} cy={P0.y} r={5} />
<Call x={P0.x} y={P0.y} tx={P0.x - 0.7 * S} ty={P0.y + 0.5 * S} label="T-hinge pivot (low edge)" anchor="end" />
<Call x={bx} y={holes[1].y} tx={bx + 0.7 * S} ty={holes[1].y + 0.4 * S} label="drilled rear brace · 4 presets" />
<Call x={(P0.x + he.x) / 2 + nx * po} y={(P0.y + he.y) / 2 + ny * po} tx={(P0.x + he.x) / 2 - 1.0 * S} ty={(P0.y + he.y) / 2 - 0.6 * S} label="55° (set) · ghosts 45/35/30°" anchor="end" />
<Human cx={hx} feetY={G} S={S} label="1.8 m" />
<text className="slabel" x={120} y={44}>BUILT AS 3–4 INDEPENDENTLY-TILTABLE 4-PANEL BAYS (~260 lb EACH)</text>
<text className="slabel" x={120} y={58}>FLIP ~4×/YR · OPEN-SOURCE (PEARCE, DESIGNS 2022)</text>
</ZoomPan>
);
}
/* ===================== OPTION 3 — SINGLE-PIVOT TABLE ================= */
function Opt3Elevation() {
const S = 80, G = 392, X0 = 360;
const M = (xm, ym) => ({ x: X0 + xm * S, y: G - ym * S });
const D = Math.PI / 180, L = 2.26, footM = 1.4;
const Pc = M(0, 1.45);
const ang = (deg) => ({ ux: Math.cos(deg * D), uy: Math.sin(deg * D) });
const half = L / 2;
const seg = (deg) => { const a = ang(deg); return { lo: { x: Pc.x - a.ux * half * S, y: Pc.y + a.uy * half * S }, hi: { x: Pc.x + a.ux * half * S, y: Pc.y - a.uy * half * S } }; };
const s55 = seg(55);
const a55 = ang(55), nx = a55.uy, ny = a55.ux, po = 8;
const pL = { x: s55.lo.x + nx * po, y: s55.lo.y - ny * po }, pH = { x: s55.hi.x + nx * po, y: s55.hi.y - ny * po };
const postTop = M(0, 1.5), postBase = M(0, -footM);
const hx = X0 - 1.45 * S;
return (
<ZoomPan contentW={780} contentH={560} height={520} fig="FIG. R5 · OPTION 3 — SINGLE-PIVOT TILTING TABLE">
<line className="grade" x1={120} y1={G} x2={720} y2={G} />
{[...Array(19)].map((_, i) => <line key={i} className="hatch" x1={130 + i * 32} y1={G} x2={122 + i * 32} y2={G + 12} />)}
<text className="slabel" x={130} y={G + 28}>GRADE</text>
<Concrete x={Pc.x} w={0.34 * S} yTop={G - 6} yBot={postBase.y} label="30 MPa · 4–5 ft" />
{[30, 75].map((a) => { const s = seg(a); return <line key={a} className="ghost-panel" x1={s.lo.x} y1={s.lo.y} x2={s.hi.x} y2={s.hi.y} />; })}
<path className="arc" d={`M ${Pc.x + 0.6 * S} ${Pc.y} A ${0.6 * S} ${0.6 * S} 0 0 0 ${Pc.x + 0.6 * S * Math.cos(75 * D)} ${Pc.y - 0.6 * S * Math.sin(75 * D)}`} />
<rect className="post" x={postTop.x - 6} y={postTop.y} width={12} height={postBase.y - postTop.y - 22} rx="1" />
<line className="joist2" x1={s55.lo.x} y1={s55.lo.y} x2={s55.hi.x} y2={s55.hi.y} />
<line className="panel-line" x1={pL.x} y1={pL.y} x2={pH.x} y2={pH.y} />
<line className="lock-strut" x1={s55.lo.x + (s55.hi.x - s55.lo.x) * 0.28} y1={s55.lo.y + (s55.hi.y - s55.lo.y) * 0.28} x2={postTop.x} y2={M(0, 0.7).y} />
<circle className="hole on" cx={postTop.x} cy={M(0, 0.7).y} r={3.4} />
<circle className="bushing-out" cx={Pc.x} cy={Pc.y} r={9} />
<circle className="pivot-bolt" cx={Pc.x} cy={Pc.y} r={4.5} />
<path className="bond-jumper" d={`M ${Pc.x - 16} ${Pc.y + 16} q 6 -8 12 0 q 6 8 12 0 q 6 -8 12 0`} />
<path className="service-loop" d={`M ${Pc.x + 14} ${Pc.y + 6} a 7 7 0 1 1 -0.1 0`} />
<Call x={Pc.x} y={Pc.y} tx={Pc.x + 0.7 * S} ty={Pc.y - 0.7 * S} label="through-bolt pivot + steel bushing" />
<Call x={postTop.x} y={M(0, 0.7).y} tx={postTop.x - 0.8 * S} ty={M(0, 0.7).y + 0.3 * S} label="positive wind-lock pin (mandatory)" anchor="end" />
<Call x={Pc.x - 10} y={Pc.y + 18} tx={Pc.x - 0.9 * S} ty={Pc.y + 0.8 * S} label="bonding jumper + PV service loop" anchor="end" />
<Call x={pH.x} y={pH.y} tx={pH.x - 0.4 * S} ty={pH.y - 0.5 * S} label="≈390 ft² plane = a sail" anchor="end" />
<Human cx={hx} feetY={G} S={S} label="1.8 m" />
<text className="slabel" x={120} y={44}>SUB-SECTION ~910 lb PLANE INTO ~4-PANEL BAYS ON 6–8 POSTS</text>
<text className="slabel" x={120} y={58}>UP-SIZED FOR 575 W / VT · ENGINEER REVIEW BEFORE BUILD</text>
</ZoomPan>
);
}
/* ============================ GALLERY ================================ */
const RACK_VIEWS = [
{ id: "elev", label: "Side elevation", Comp: RackElevation },
{ id: "front", label: "Array face", Comp: RackFront },
{ id: "plan", label: "Footing plan", Comp: RackPlan },
];
function RackingVisual() {
const [i, setI] = useStateRD(0);
const Cur = RACK_VIEWS[i].Comp;
return (
<div className="sheet pad" style={{ marginBottom: 26 }}>
<div className="tab-toolbar" style={{ marginBottom: 12 }}>
<SectionHead n="—" kicker="Visualization" title="What it looks like"
sub="Engineering views of the Phase-1 ground-mount. Schematic, not to scale — drag to pan, scroll to zoom." />
<div className="seg">
{RACK_VIEWS.map((v, idx) => (
<button key={v.id} className={idx === i ? "on" : ""} onClick={() => setI(idx)}>{v.label}</button>
))}
</div>
</div>
<Cur key={RACK_VIEWS[i].id} />
<div className="sch-notes" style={{ marginTop: 14 }}>
<div className="stamp warn">Est · not stamped</div>
<p className="mono">Geometry reflects the locked design — 55° tilt, 6×6 back / 4×4 front posts, 2×10 inside joists, 4–5 ft frost footings, 2-high × 7-wide. Dimensions are nominal; the cut list (exact lengths + 55° miters) follows once Waaree mounting-hole spacing is confirmed (action A2). For a photoreal site render, drop a file into the 3D Flyover tab.</p>
</div>
</div>
);
}
Object.assign(window, { RackingVisual, RackElevation, RackFront, RackPlan, Opt2Elevation, Opt3Elevation });