diff --git a/public/app.js b/public/app.js
index 9dbd040..0c28cb8 100644
--- a/public/app.js
+++ b/public/app.js
@@ -1,5 +1,5 @@
(function () {
- document.querySelectorAll("[data-copy]").forEach((btn) => {
+ function bindCopyButton(btn) {
const original = btn.textContent;
btn.addEventListener("click", async () => {
const text = btn.getAttribute("data-copy") || "";
@@ -14,5 +14,40 @@
btn.textContent = original;
}, 1600);
});
- });
+ }
+
+ document.querySelectorAll("[data-copy]").forEach(bindCopyButton);
+
+ const input = document.querySelector("[data-builder-input]");
+ const command = document.querySelector("[data-builder-command]");
+ const copy = document.querySelector("[data-builder-copy]");
+ const templates = Array.from(document.querySelectorAll("[data-template]"));
+
+ function commandFor(value) {
+ const text = value.trim();
+ const url = text.match(/https?:\/\/[^\s"']+/i)?.[0];
+ if (url) return `npx proofloop target --url ${url} --write-runner-plan`;
+ return "npx proofloop target --write-runner-plan";
+ }
+
+ function updateBuilder() {
+ if (!input || !command || !copy) return;
+ const next = commandFor(input.value || "");
+ command.textContent = next;
+ copy.setAttribute("data-copy", next);
+ }
+
+ if (input && command && copy) {
+ input.addEventListener("input", updateBuilder);
+ templates.forEach((template) => {
+ template.addEventListener("click", () => {
+ templates.forEach((item) => item.removeAttribute("data-selected"));
+ template.setAttribute("data-selected", "true");
+ input.value = template.getAttribute("data-template") || "";
+ input.focus();
+ updateBuilder();
+ });
+ });
+ updateBuilder();
+ }
})();
diff --git a/public/index.html b/public/index.html
index 4ef722c..04dc73e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -61,6 +61,77 @@
The gate decides when it’s done .
+
+ Guided setup
+ Build a proof loop
+
+ Hey! I’m here to help you set up a proof loop in just a couple of minutes.
+ What app or workflow are you trying to prove? Describe it in your own words,
+ or tap a template below.
+
+
+
+ Use case
+
+
+
+
+
+ Live URL flow
+ Click, submit, and verify the real page.
+
+
+ Accounting agent
+ Reconcile, score, and chart receipts.
+
+
+ Memory pipeline
+ Documents to memory with receipts.
+
+
+ Research copilot
+ Sources, claims, and proof reports.
+
+
+
+
+
+ Kickoff command
+ npx proofloop target --write-runner-plan
+
+
+ Copy
+
+
+
+
diff --git a/public/styles.css b/public/styles.css
index f5a29dd..73db608 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -268,6 +268,139 @@ code {
display: none;
}
+.builder {
+ margin-top: 52px;
+ padding: 24px;
+ border: 1px solid var(--line-strong);
+ border-radius: 10px;
+ background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.015));
+}
+
+.eyebrow {
+ margin: 0 0 8px;
+ color: var(--accent-hover);
+ font-family: var(--mono);
+ font-size: 11px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.builder h2 {
+ margin: 0;
+ color: var(--text);
+ font-size: 26px;
+ line-height: 1.18;
+ font-weight: 700;
+}
+
+.builder-copy {
+ margin: 12px 0 20px;
+ color: var(--text-muted);
+ font-size: 14px;
+ line-height: 1.65;
+}
+
+.prompt-box {
+ display: grid;
+ gap: 8px;
+}
+
+.prompt-box span,
+.builder-output span {
+ color: var(--text-dim);
+ font-family: var(--mono);
+ font-size: 11px;
+ font-weight: 700;
+ text-transform: uppercase;
+}
+
+.prompt-box textarea {
+ width: 100%;
+ min-height: 112px;
+ resize: vertical;
+ border: 1px solid var(--line-strong);
+ border-radius: 8px;
+ background: rgba(11, 11, 13, 0.72);
+ color: var(--text);
+ font: inherit;
+ font-size: 14px;
+ line-height: 1.55;
+ padding: 12px 14px;
+}
+
+.prompt-box textarea::placeholder {
+ color: var(--text-dim);
+}
+
+.prompt-box textarea:focus {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.template-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 10px;
+ margin-top: 16px;
+}
+
+.template-card {
+ min-height: 96px;
+ padding: 14px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: rgba(255, 255, 255, 0.025);
+ color: var(--text);
+ text-align: left;
+ cursor: pointer;
+ transition: border-color 0.15s, background 0.15s, transform 0.15s;
+}
+
+.template-card:hover,
+.template-card:focus-visible,
+.template-card[data-selected="true"] {
+ border-color: var(--accent);
+ background: var(--accent-tint);
+ transform: translateY(-1px);
+}
+
+.template-card span {
+ display: block;
+ font-size: 14px;
+ font-weight: 700;
+}
+
+.template-card small {
+ display: block;
+ margin-top: 6px;
+ color: var(--text-muted);
+ font-size: 12.5px;
+ line-height: 1.45;
+}
+
+.builder-output {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ margin-top: 18px;
+ padding: 14px;
+ border: 1px solid var(--line);
+ border-radius: 8px;
+ background: rgba(11, 11, 13, 0.62);
+}
+
+.builder-output div {
+ min-width: 0;
+}
+
+.builder-output code {
+ display: block;
+ margin-top: 6px;
+ overflow-x: auto;
+ white-space: nowrap;
+}
+
.capabilities {
margin-top: 76px;
padding-top: 40px;
@@ -377,6 +510,23 @@ button:focus-visible {
font-size: 12px;
}
+ .builder {
+ padding: 18px;
+ }
+
+ .template-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .builder-output {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .builder-output .copy-btn {
+ width: 100%;
+ }
+
.capabilities {
margin-top: 56px;
}
diff --git a/tests/site.test.ts b/tests/site.test.ts
index d3ea6cc..4b2d45c 100644
--- a/tests/site.test.ts
+++ b/tests/site.test.ts
@@ -22,6 +22,17 @@ describe("proofloop.live site", () => {
expect(normalizedHtml).toContain("The gate decides");
});
+ it("includes the guided setup builder with local templates", () => {
+ expect(normalizedHtml).toContain("Build a proof loop");
+ expect(normalizedHtml).toContain("Describe it in your own words");
+ expect(normalizedHtml).toContain("Live URL flow");
+ expect(normalizedHtml).toContain("Accounting agent");
+ expect(normalizedHtml).toContain("Memory pipeline");
+ expect(normalizedHtml).toContain("Research copilot");
+ expect(script).toContain("data-builder-input");
+ expect(script).toContain("npx proofloop target --url");
+ });
+
it("states the honesty boundary from the CLI without implying an unbuilt hosted backend", () => {
expect(normalizedHtml).toContain("product-path proof");
expect(normalizedHtml).toContain("proxy benchmark proof");