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. +

+ + + +
+ + + + +
+ +
+
+ Kickoff command + npx proofloop target --write-runner-plan +
+ +
+
+
  1. 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");