/* ============================================================================
PRSM SOLAR — APP SHELL
========================================================================== */
const { useState: useStateA, useEffect: useEffectA } = React;
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"theme": "field",
"texture": true,
"typeScale": 1.0,
"leadAccent": "pine"
}/*EDITMODE-END*/;
const TABS = [
{ id: "overview", n: "01", label: "Overview", Comp: () => <Overview /> },
{ id: "bom", n: "02", label: "Bill of Materials", Comp: () => <BOM /> },
{ id: "costs", n: "03", label: "Procurement & Costs", Comp: () => <Costs /> },
{ id: "schematics", n: "04", label: "Schematics", Comp: () => <Schematics /> },
{ id: "racking", n: "05", label: "Racking", Comp: () => <Racking /> },
{ id: "flyover", n: "06", label: "3D Flyover", Comp: () => <Flyover /> },
];
function Masthead() {
const s = window.PRSM.site;
return (
<header className="masthead">
<div className="mast-top">
<div className="mast-id">
<div className="mast-mark" />
<div>
<div className="mast-title">PRSM SOLAR</div>
<div className="mast-sub">{s.subtitle}</div>
</div>
</div>
<div className="mast-meta">
<div><b>{s.place}</b> · {s.zip}</div>
<div>{s.latlon}</div>
<div>{s.region} · captured {s.captured}</div>
</div>
</div>
<div className="mast-facts">
<span className="fact"><b>Land</b> {s.land}</span>
<span className="fact"><b>Mount</b> {s.mount}</span>
<span className="fact"><b>Tilt</b> {s.tilt}</span>
<span className="fact"><b>Frost</b> {s.frost}</span>
<span className="fact"><b>Snow</b> {s.snowLoad}</span>
<span className="fact"><b>Wind</b> {s.windDesign}</span>
</div>
</header>
);
}
function App() {
const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
const [tab, setTab] = useStateA("overview");
useEffectA(() => {
const r = document.documentElement;
r.setAttribute("data-theme", t.theme);
r.style.fontSize = (16 * (t.typeScale || 1)) + "px";
}, [t.theme, t.typeScale]);
// lead-accent swaps the nav underline + mark dot emphasis
useEffectA(() => {
document.documentElement.style.setProperty("--lead", t.leadAccent === "amber" ? "var(--amber)" : "var(--pine)");
}, [t.leadAccent]);
const Active = TABS.find((x) => x.id === tab).Comp;
return (
<div className="app">
<Masthead />
<nav className="nav">
<div className="nav-in">
{TABS.map((x) => (
<button key={x.id} className={"nav-btn" + (tab === x.id ? " on" : "")} onClick={() => setTab(x.id)}>
<span className="n">{x.n}</span>{x.label}
</button>
))}
</div>
</nav>
<Active />
<TweaksPanel>
<TweakSection label="Overall look" />
<TweakRadio label="Theme" value={t.theme}
options={["field", "topo", "birch"]}
onChange={(v) => setTweak("theme", v)} />
<TweakToggle label="Contour texture" value={t.texture}
onChange={(v) => setTweak("texture", v)} />
<TweakSection label="Accent & type" />
<TweakRadio label="Lead accent" value={t.leadAccent}
options={["pine", "amber"]}
onChange={(v) => setTweak("leadAccent", v)} />
<TweakSlider label="Type scale" value={t.typeScale} min={0.9} max={1.15} step={0.05}
onChange={(v) => setTweak("typeScale", v)} />
</TweaksPanel>
<TextureToggle on={t.texture} />
</div>
);
}
// mounts/unmounts the contour background based on the tweak
function TextureToggle({ on }) {
useEffectA(() => {
let el = document.querySelector(".topo-bg");
if (on && !el) {
el = document.createElement("div");
el.className = "topo-bg";
document.body.insertBefore(el, document.body.firstChild);
} else if (!on && el) {
el.remove();
}
}, [on]);
return null;
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);