Files
soleprint/soleprint/artery/plexuses/bundle/app/index.html
2026-09-24 00:43:18 -03:00

270 lines
14 KiB
HTML

<!DOCTYPE html>
<!--
Bundle plexus — the template. `make dist bundle` inlines everything marked
%%…%% and writes one self-contained file to gen/plexuses/bundle/index.html.
What this page is FOR: showing the three themes doing real work. Every panel
renders genuine output — showcase.py runs modelgen, datagen and shuntgen over
the fixtures at build time — so the page is simultaneously a demo of the tools
and a specimen of the theme. A list of tool names would prove neither.
Three rules, all consequences of "must open from a double-clicked file on a
machine with no egress":
no fetch() file:// treats every sibling file as cross-origin
no /theme.css an absolute path assumes a server at the root
no webfont a blocked stylesheet is a stall, not a fallback
-->
<html lang="en" data-theme="%%DEFAULT_THEME%%">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>%%TITLE%%</title>
<style>
%%THEME_CSS%%
* { box-sizing: border-box; }
body { margin: 0; padding: var(--space-6); }
main { max-width: 64rem; margin: 0 auto; }
header { display: flex; align-items: flex-start; gap: var(--space-4); flex-wrap: wrap;
padding-bottom: var(--space-4); border-bottom: var(--hairline) solid var(--border); }
h1 { margin: 0; font-size: 1.5rem; }
h1 .ok { color: var(--status-ok); }
.sub { color: var(--muted); margin: .35rem 0 0; max-width: 46rem; }
.built { color: var(--dim); font-family: var(--font-mono); font-size: 11px; margin: .5rem 0 0; }
h2 { font-size: .75rem; text-transform: uppercase; letter-spacing: .1em;
color: var(--muted); margin: var(--space-8) 0 var(--space-2); font-weight: 600; }
h2 .n { color: var(--dim); font-weight: 400; letter-spacing: 0; text-transform: none; }
.lede { color: var(--muted); margin: 0 0 var(--space-3); font-size: .9rem; max-width: 46rem; }
.panel { background: var(--surface); border: var(--hairline) solid var(--border);
border-radius: var(--radius-lg); padding: var(--space-4); }
.split { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); align-items: start; }
@media (max-width: 820px) { .split { grid-template-columns: 1fr; } }
pre { margin: 0; background: var(--bg); border: var(--hairline) solid var(--border);
border-radius: var(--radius); padding: var(--space-3); overflow-x: auto;
font-family: var(--font-mono); font-size: 11.5px; line-height: 1.55; color: var(--text); }
.cap { font-family: var(--font-mono); font-size: 10px; text-transform: uppercase;
letter-spacing: .08em; color: var(--dim); margin-bottom: 6px; }
table { width: 100%; border-collapse: collapse; font-size: 12px; }
th { text-align: left; padding: 5px 8px; color: var(--muted); font-weight: 600; font-size: 10px;
text-transform: uppercase; letter-spacing: .05em; border-bottom: var(--hairline) solid var(--border); }
td { padding: 5px 8px; border-bottom: var(--hairline) solid var(--border); font-family: var(--font-mono); }
tr:last-child td { border-bottom: none; }
tr:hover td { background: var(--bg-2); }
.verb { font-weight: 600; font-size: 10px; padding: 1px 6px; border-radius: var(--radius-sm);
border: 1px solid currentColor; }
.GET { color: var(--status-info); } .POST { color: var(--status-ok); }
.PUT { color: var(--status-warn); } .DELETE { color: var(--status-error); }
.chip { display: inline-block; font-family: var(--font-mono); font-size: 9.5px;
padding: 0 5px; border-radius: var(--radius-sm);
border: var(--hairline) solid var(--border); color: var(--dim); }
.chip.pk { border-color: var(--accent); color: var(--accent-text); }
.chip.fk { border-color: var(--status-ok); color: var(--status-ok); }
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--space-3); }
.card { background: var(--bg); border: var(--hairline) solid var(--border);
border-radius: var(--radius); overflow: hidden; }
.card h3 { margin: 0; padding: 6px 10px; font-size: 12px; background: var(--surface-raised);
border-bottom: var(--hairline) solid var(--border); font-family: var(--font-mono); }
.card .row { display: flex; gap: 6px; align-items: baseline; padding: 3px 10px; font-size: 11px;
font-family: var(--font-mono); }
.card .row .f { flex: 1; }
.card .row .t { color: var(--dim); font-size: 10px; }
/* Swatches: the palette, read back from the live page rather than written out,
so what you see is what the theme actually resolved to. */
.swatches { display: grid; grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); gap: 8px; }
.sw { border: var(--hairline) solid var(--border); border-radius: var(--radius); overflow: hidden; }
.sw .chipc { height: 34px; }
.sw .meta { padding: 4px 7px; font-family: var(--font-mono); font-size: 9.5px; }
.sw .meta b { display: block; color: var(--text); font-weight: 600; }
.sw .meta span { color: var(--dim); }
.themes { display: flex; margin-left: auto; }
.themes button { font-family: var(--font-mono); font-size: 10px; padding: 5px 11px;
text-transform: uppercase; letter-spacing: .06em; border-radius: 0; }
.themes button:first-child { border-radius: var(--radius) 0 0 var(--radius); }
.themes button:last-child { border-radius: 0 var(--radius) var(--radius) 0; }
.themes button + button { border-left: none; }
.themes button[aria-pressed="true"] { background: var(--accent); color: var(--bg); border-color: var(--accent); }
.flow { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap;
font-family: var(--font-mono); font-size: 12px; margin-bottom: var(--space-3); }
.flow .box { border: var(--hairline) solid var(--border); border-radius: var(--radius);
padding: 5px 10px; background: var(--bg); }
.flow .arrow { color: var(--accent); }
.flow .box b { color: var(--accent-text); }
.diagram { overflow-x: auto; border: var(--hairline) solid var(--border);
border-radius: var(--radius); background: var(--bg); padding: var(--space-3); }
.diagram svg { max-width: 100%; height: auto; display: block; margin: 0 auto; }
.diagram .graph > polygon { fill: var(--bg); }
.diagram .node ellipse, .diagram .node polygon, .diagram .node path { fill: var(--surface); stroke: var(--border); }
.diagram .node text { fill: var(--text); }
.diagram .edge path { stroke: var(--dim); }
.diagram .edge polygon { fill: var(--dim); stroke: var(--dim); }
.diagram .edge text { fill: var(--muted); }
.diagram .cluster polygon, .diagram .cluster path { fill: var(--bg-2); stroke: var(--border); }
.diagram .cluster text { fill: var(--muted); }
.diagram .node.accent ellipse, .diagram .node.accent polygon, .diagram .node.accent path,
.diagram .node.ok ellipse, .diagram .node.ok polygon, .diagram .node.ok path { stroke: var(--accent); stroke-width: 1.5; }
.diagram .node.accent-text text { fill: var(--accent-text); }
.diagram .node.ok text { fill: var(--status-ok); }
.diagram .cluster.artery polygon, .diagram .cluster.artery path { stroke: var(--sys-artery); }
.diagram .cluster.atlas polygon, .diagram .cluster.atlas path { stroke: var(--sys-atlas); }
.diagram .cluster.station polygon, .diagram .cluster.station path { stroke: var(--sys-station); }
.diagram .cluster.artery text { fill: var(--sys-artery); }
.diagram .cluster.atlas text { fill: var(--sys-atlas); }
.diagram .cluster.station text { fill: var(--sys-station); }
.diagram .edge.artery path, .diagram .edge.artery polygon { stroke: var(--sys-artery); fill: var(--sys-artery); }
.diagram .edge.atlas path, .diagram .edge.atlas polygon { stroke: var(--sys-atlas); fill: var(--sys-atlas); }
.diagram .edge.station path, .diagram .edge.station polygon { stroke: var(--sys-station);fill: var(--sys-station); }
:root { --sys-artery: #b91c1c; --sys-atlas: #15803d; --sys-station: #1d4ed8; }
[data-theme="lucid"] { --sys-artery: #c0392b; --sys-atlas: #1a7f45; --sys-station: #2b5fd9; }
[data-theme="mcrn"] { --sys-artery: #c0392b; --sys-atlas: #2ecc71; --sys-station: #5dade2; }
@media print { .themes { display: none; } .panel, .card { break-inside: avoid; } }
</style>
</head>
<body>
<main>
<header>
<div>
<h1><span class="ok">IT WORKS</span> — %%TITLE%%</h1>
<p class="sub">%%DESCRIPTION%%</p>
<p class="built">%%BUILT%%</p>
</div>
<div class="themes" id="themes" role="group" aria-label="Theme"></div>
</header>
<div id="body"></div>
<h2>Architecture</h2>
<p class="lede">Rendered from <code>docs/graphs/*.dot</code> by the same palette
as this page. Inlined rather than linked, so it follows the theme switch.</p>
<div class="diagram">%%GRAPH%%</div>
</main>
<script>
var BUNDLE = %%BUNDLE%%;
var THEMES = ["lucid", "soleprint", "mcrn"];
function esc(s) {
return String(s == null ? "" : s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function el(html) { return html; }
var S = BUNDLE.showcase || {};
var out = [];
/* ── the headline: spreadsheets in, a working service out ───────────────── */
if (S.tabular) {
var t = S.tabular;
out.push('<h2>Spreadsheets to a typed API <span class="n">— modelgen · datagen · shuntgen</span></h2>');
out.push('<p class="lede">Nobody wrote a model. These are the real files in ' +
'<code>fixtures/sheets/</code>, and everything below was generated from them at build time.</p>');
out.push('<div class="flow">' +
'<span class="box"><b>' + t.counts.files + '</b> files · <b>' + t.counts.rows + '</b> rows</span>' +
'<span class="arrow">&rarr;</span><span class="box"><b>' + t.counts.models + '</b> models</span>' +
'<span class="arrow">&rarr;</span><span class="box"><b>' +
(S.shunt ? S.shunt.count : 0) + '</b> routes</span></div>');
var sample = (t.inputs.filter(function (i) { return i.sample; })[0]) || null;
out.push('<div class="panel"><div class="split">' +
'<div><div class="cap">in — ' + esc(sample ? sample.name : "sheet") + '</div><pre>' +
esc(sample ? sample.sample : "") + '</pre></div>' +
'<div><div class="cap">out — models.py</div><pre>' + esc(t.pydantic) + '</pre></div>' +
'</div></div>');
if (t.models && t.models.length) {
out.push('<h2>Inferred schema <span class="n">— types, keys and relations, from the data</span></h2>');
out.push('<div class="cards">' + t.models.map(function (m) {
return '<div class="card"><h3>' + esc(m.name) + '</h3>' + m.fields.map(function (f) {
var kind = f.pk ? '<span class="chip pk">pk</span>'
: (String(f.type).indexOf("FK:") === 0 ? '<span class="chip fk">fk</span>' : "");
return '<div class="row"><span class="f">' + esc(f.name) + "</span>" +
'<span class="t">' + esc(f.type) + "</span>" + kind + "</div>";
}).join("") + "</div>";
}).join("") + "</div>");
}
if (t.records && t.records.length) {
out.push('<h2>Generated records <span class="n">— datagen, sampling the imported rows</span></h2>');
out.push('<div class="panel"><pre>' + esc(JSON.stringify(t.records, null, 2)) + "</pre></div>");
}
}
if (S.shunt) {
out.push('<h2>The service it becomes <span class="n">— shuntgen</span></h2>');
out.push('<p class="lede">Five routes per table, backed by the real rows. ' +
'<code>python run.py</code> and it answers.</p>');
out.push('<div class="panel"><div class="split">' +
'<div><div class="cap">routes</div><table><tbody>' +
S.shunt.routes.slice(0, 10).map(function (r) {
return "<tr><td><span class=\"verb " + r.method + '">' + r.method + "</span></td>" +
"<td>" + esc(r.path) + "</td><td>" + esc(r.operation) + "</td></tr>";
}).join("") + "</tbody></table></div>" +
'<div><div class="cap">GET /customers</div><pre>' +
esc(JSON.stringify(S.shunt.response, null, 2)) + "</pre></div></div></div>");
}
if (S.cabinet) {
out.push('<h2>Dependencies it declares <span class="n">— cabinets</span></h2>');
out.push('<p class="lede">An environment asks for <code>' + esc(S.cabinet.name) +
'</code> once. This is merged into its compose file on a laptop; the same name ' +
'installs as a rig addon in a cluster.</p>');
out.push('<div class="panel"><pre>' + esc(S.cabinet.fragment) + "</pre></div>");
}
/* ── the palette, read back from the live page ──────────────────────────── */
var TOKENS = ["--bg", "--bg-2", "--surface", "--surface-raised", "--border",
"--text", "--muted", "--dim",
"--accent", "--accent-text", "--status-ok", "--status-info",
"--status-warn", "--status-error"];
out.push('<h2>The theme itself <span class="n">— read back from this page, live</span></h2>');
out.push('<p class="lede">Switch above and every panel, table, badge and the diagram follow. ' +
'Nothing here is fetched: the whole thing is one file.</p>');
out.push('<div class="panel"><div class="swatches" id="swatches"></div></div>');
document.getElementById("body").innerHTML = out.join("");
function paintSwatches() {
var cs = getComputedStyle(document.documentElement);
document.getElementById("swatches").innerHTML = TOKENS.map(function (name) {
var v = cs.getPropertyValue(name).trim();
return '<div class="sw"><div class="chipc" style="background:' + v + '"></div>' +
'<div class="meta"><b>' + name + "</b><span>" + (v || "—") + "</span></div></div>";
}).join("");
}
var root = document.documentElement;
function apply(theme) {
root.setAttribute("data-theme", theme);
try { localStorage.setItem("spr-bundle-theme", theme); } catch (e) { /* file:// */ }
var bs = document.querySelectorAll("#themes button");
for (var i = 0; i < bs.length; i++) {
bs[i].setAttribute("aria-pressed", bs[i].dataset.theme === theme ? "true" : "false");
}
paintSwatches();
}
document.getElementById("themes").innerHTML = THEMES.map(function (t) {
return '<button type="button" data-theme="' + t + '">' + t + "</button>";
}).join("");
document.getElementById("themes").addEventListener("click", function (e) {
if (e.target.dataset.theme) apply(e.target.dataset.theme);
});
var saved = null;
try { saved = localStorage.getItem("spr-bundle-theme"); } catch (e) { /* file:// */ }
apply(THEMES.indexOf(saved) !== -1 ? saved : root.getAttribute("data-theme"));
</script>
</body>
</html>