[object Object]

← back to Ventura Claw

/services demo widget: 'Try it now' domain+email orchestration plan; structured 7-step API call sequence + cost + deliverables + lead-modal pre-fill

03cee4106a32765b1d3c85253463584ea1ae73e0 · 2026-05-06 16:29:59 -0700 · Steve

Files touched

Diff

commit 03cee4106a32765b1d3c85253463584ea1ae73e0
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed May 6 16:29:59 2026 -0700

    /services demo widget: 'Try it now' domain+email orchestration plan; structured 7-step API call sequence + cost + deliverables + lead-modal pre-fill
---
 server/data/services-demo.jsonl |   1 +
 server/public/services.html     | 217 ++++++++++++++++++++++++++++++++++++++++
 server/server.js                |  72 +++++++++++++
 3 files changed, 290 insertions(+)

diff --git a/server/data/services-demo.jsonl b/server/data/services-demo.jsonl
new file mode 100644
index 0000000..61c67bb
--- /dev/null
+++ b/server/data/services-demo.jsonl
@@ -0,0 +1 @@
+{"at":"2026-05-06T23:29:54.632Z","kind":"domain-email","domain":"acmecorp.com","business_name":"Acme Corp","forward_to":"steve@example.com","ip":"127.0.0.1","ua":"curl/8.7.1"}
diff --git a/server/public/services.html b/server/public/services.html
index 2999ffb..b9919cc 100644
--- a/server/public/services.html
+++ b/server/public/services.html
@@ -311,6 +311,105 @@
   footer a { color: var(--ink-mute); }
   footer a:hover { color: var(--gold); }
 
+  /* Live demo widget — domain + email setup */
+  .demo-section { padding-top: 36px; padding-bottom: 36px; }
+  .demo-form {
+    display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px;
+    margin-bottom: 20px;
+    align-items: end;
+  }
+  .demo-row { display: flex; flex-direction: column; gap: 4px; }
+  .demo-row label {
+    font-family: var(--mono); font-size: 9px; letter-spacing: 0.18em; text-transform: uppercase;
+    color: var(--ink-mute);
+  }
+  .demo-row label .opt { color: var(--ink-faint); text-transform: none; letter-spacing: 0.04em; }
+  .demo-row input {
+    background: var(--bg-2); color: var(--ink);
+    border: 1px solid var(--rule); border-radius: 3px;
+    padding: 12px 14px;
+    font-family: var(--mono); font-size: 14px; font-weight: 400;
+    outline: none; transition: border-color 200ms;
+  }
+  .demo-row input:focus { border-color: var(--gold); box-shadow: 0 0 0 2px var(--gold-soft); }
+  .demo-go {
+    background: var(--gold); color: var(--bg); border: 0;
+    padding: 13px 20px;
+    font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; font-weight: 600;
+    cursor: pointer; border-radius: 3px;
+    transition: background 200ms;
+    grid-column: span 1;
+  }
+  .demo-go:hover { background: var(--gold-glow); }
+  .demo-go:disabled { opacity: 0.55; cursor: wait; }
+
+  .demo-result { display: none; margin-top: 24px; }
+  .demo-result.show { display: block; }
+  .demo-summary {
+    padding: 18px 22px;
+    background: linear-gradient(180deg, rgba(212,160,74,0.10), rgba(212,160,74,0.02));
+    border: 1px solid var(--gold);
+    border-left: 3px solid var(--gold);
+    margin-bottom: 18px;
+  }
+  .demo-summary .total {
+    font-family: var(--serif); font-size: 28px; color: var(--gold); font-style: italic;
+    font-weight: 500; letter-spacing: -0.005em;
+  }
+  .demo-summary .total .lbl {
+    font-family: var(--mono); font-size: 9px; letter-spacing: 0.18em;
+    text-transform: uppercase; color: var(--ink-mute);
+    font-style: normal; margin-right: 8px;
+  }
+  .demo-summary .breakdown {
+    font-family: var(--mono); font-size: 11px; color: var(--ink-soft); margin-top: 6px;
+  }
+  .demo-summary .summary-line {
+    color: var(--ink); font-size: 14px; margin-top: 12px; line-height: 1.55;
+  }
+  .demo-steps { background: var(--bg-2); border: 1px solid var(--rule); margin-bottom: 18px; }
+  .demo-step { padding: 14px 18px; border-bottom: 1px solid var(--rule); display: grid; grid-template-columns: 36px 1fr; gap: 16px; }
+  .demo-step:last-child { border-bottom: 0; }
+  .demo-step .n {
+    font-family: var(--mono); font-size: 11px; color: var(--gold); font-weight: 600;
+    letter-spacing: 0.06em;
+    text-align: right;
+  }
+  .demo-step .api { font-family: var(--mono); font-size: 11px; color: var(--ink-mute); margin-bottom: 4px; }
+  .demo-step .action { font-size: 14px; color: var(--ink); margin-bottom: 4px; }
+  .demo-step .meta {
+    font-family: var(--mono); font-size: 10px; color: var(--ink-mute); display: flex; gap: 14px; flex-wrap: wrap;
+  }
+  .demo-step .meta .risk { color: var(--ink-faint); }
+  .demo-step .meta .risk.destructive { color: var(--bad); }
+  .demo-step .meta .cost { color: var(--gold); }
+  .demo-step .meta .needs { color: var(--bad); }
+
+  .demo-deliv { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
+  @media (max-width: 720px) { .demo-deliv { grid-template-columns: 1fr; } }
+  .demo-deliv .panel {
+    background: var(--bg-2); border: 1px solid var(--rule);
+    padding: 16px 20px;
+  }
+  .demo-deliv .panel h4 {
+    font-family: var(--mono); font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
+    color: var(--gold); margin: 0 0 10px;
+  }
+  .demo-deliv .panel.not h4 { color: var(--ink-mute); }
+  .demo-deliv .panel ul { margin: 0; padding-left: 18px; color: var(--ink-soft); font-size: 13px; line-height: 1.6; }
+
+  .demo-cta-row {
+    margin-top: 20px; display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
+    padding: 16px 18px; background: var(--bg-3); border: 1px solid var(--gold);
+  }
+  .demo-cta-row .pre { color: var(--ink-soft); font-size: 13px; flex: 1; min-width: 240px; }
+  .demo-cta-run {
+    background: var(--gold); color: var(--bg); border: 0;
+    padding: 12px 22px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; font-weight: 600;
+    cursor: pointer; border-radius: 3px;
+  }
+  .demo-cta-run:hover { background: var(--gold-glow); }
+
   /* "How it actually runs" 3-step flow */
   .how-section { padding-top: 56px; }
   .flow-grid {
@@ -632,6 +731,37 @@
   </div>
 </section>
 
+<!-- DEMO WIDGET — domain + email setup, real plumbing -->
+<section class="cat-section demo-section" id="try-it">
+  <div class="cat-head">
+    <div>
+      <span class="cat-icon">▲ TRY IT NOW · LIVE</span>
+      <h2>Type a domain. See exactly <em>what we'd do.</em></h2>
+    </div>
+    <div class="cat-blurb">No payment, no signup. Type the domain you'd want, we return the precise sequence of API calls (Porkbun → Cloudflare → DNS → SPF/DKIM/DMARC → email forwarding) we'd fire, with cost. Run it for $39 + the registration fee when you're ready.</div>
+  </div>
+
+  <form class="demo-form" id="demoForm" autocomplete="off">
+    <div class="demo-row">
+      <label for="demoDomain">Domain you want</label>
+      <input type="text" id="demoDomain" name="domain" required
+             placeholder="mybiz.com" pattern="[a-z0-9-]+\.[a-z]{2,24}"
+             autocapitalize="off" spellcheck="false" />
+    </div>
+    <div class="demo-row">
+      <label for="demoBiz">Business name <span class="opt">(optional)</span></label>
+      <input type="text" id="demoBiz" name="business_name" placeholder="Acme Consulting LLC" maxlength="80" />
+    </div>
+    <div class="demo-row">
+      <label for="demoFwd">Forward info@ to <span class="opt">(your inbox)</span></label>
+      <input type="email" id="demoFwd" name="forward_to" placeholder="you@gmail.com" />
+    </div>
+    <button type="submit" class="demo-go">Show the plan →</button>
+  </form>
+
+  <div class="demo-result" id="demoResult"></div>
+</section>
+
 <!-- (Formation section removed — government-portal services pivoted out 2026-05-06; Operating Agreement relocated to Documents) -->
 
 <!-- BANKING -->
@@ -790,6 +920,93 @@
 </main>
 
 <script>
+  // ----- TRY-IT-NOW domain demo widget -----
+  (function wireDemo() {
+    const form = document.getElementById('demoForm');
+    const result = document.getElementById('demoResult');
+    if (!form || !result) return;
+    form.addEventListener('submit', async e => {
+      e.preventDefault();
+      const btn = form.querySelector('.demo-go');
+      btn.disabled = true; btn.textContent = 'Calling APIs…';
+      const payload = {
+        domain: document.getElementById('demoDomain').value.trim().toLowerCase(),
+        business_name: document.getElementById('demoBiz').value.trim(),
+        forward_to: document.getElementById('demoFwd').value.trim(),
+      };
+      try {
+        const r = await fetch('/api/services-demo/domain-email', {
+          method: 'POST', headers: {'Content-Type':'application/json'},
+          body: JSON.stringify(payload)
+        });
+        const j = await r.json();
+        if (!r.ok) throw new Error(j.msg || j.error || 'Demo failed.');
+        renderDemo(j.plan, payload);
+      } catch (err) {
+        result.innerHTML = `<div style="padding:14px;border:1px solid var(--bad);color:var(--bad);font-size:13px">${err.message}</div>`;
+        result.classList.add('show');
+      } finally {
+        btn.disabled = false; btn.textContent = 'Show the plan →';
+      }
+    });
+
+    function escHtml(s) { return (s||'').replace(/[<>&"]/g, c=>({'<':'&lt;','>':'&gt;','&':'&amp;','"':'&quot;'}[c])); }
+
+    function renderDemo(plan, payload) {
+      const stepsHtml = plan.steps.map(s => `<div class="demo-step">
+        <div class="n">${s.n}</div>
+        <div>
+          <div class="api">${escHtml(s.api)}</div>
+          <div class="action">${escHtml(s.action)}</div>
+          <div class="meta">
+            <span class="risk ${s.risk === 'destructive: changes ownership of DNS' ? 'destructive' : ''}">${escHtml(s.risk||'')}</span>
+            ${s.cost ? `<span class="cost">${escHtml(s.cost)}</span>` : ''}
+            ${s.needs_input ? `<span class="needs">needs your input</span>` : ''}
+          </div>
+        </div>
+      </div>`).join('');
+
+      const youGet = plan.you_get.map(x => `<li>${escHtml(x)}</li>`).join('');
+      const notIncl = plan.not_included.map(x => `<li>${escHtml(x)}</li>`).join('');
+
+      result.innerHTML = `
+        <div class="demo-summary">
+          <div class="total"><span class="lbl">Total</span>$${plan.fees.total.toFixed(2)}</div>
+          <div class="breakdown">
+            VenturaClaw orchestration: <b>$${plan.fees.ventura_claw_orchestration.toFixed(2)}</b>
+            · Domain registration (year 1): <b>$${plan.fees.domain_registration.toFixed(2)}</b>
+            · Cloudflare DNS: <b>free</b>
+            · ETA: <b>~${plan.estimated_total_seconds}s end-to-end</b>
+          </div>
+          <div class="summary-line">${escHtml(plan.summary)}</div>
+        </div>
+        <div class="demo-steps">${stepsHtml}</div>
+        <div class="demo-deliv">
+          <div class="panel"><h4>You get</h4><ul>${youGet}</ul></div>
+          <div class="panel not"><h4>Not included</h4><ul>${notIncl}</ul></div>
+        </div>
+        <div class="demo-cta-row">
+          <div class="pre">Like the plan? Hit the button — opens a Get-Started form pre-filled with these inputs and connects you to a human who'll run the API calls live (we don't run them automatically yet).</div>
+          <button type="button" class="demo-cta-run" id="demoCtaRun">Run this plan →</button>
+        </div>`;
+      result.classList.add('show');
+      result.scrollIntoView({ behavior: 'smooth', block: 'start' });
+
+      const runBtn = document.getElementById('demoCtaRun');
+      runBtn.addEventListener('click', () => {
+        // Open the lead modal pre-filled with the demo inputs
+        document.getElementById('leadSvcTag').textContent = 'Domain + email setup';
+        document.getElementById('leadService').value = 'Domain + email setup';
+        document.getElementById('leadModalTitle').textContent = 'Run: Domain + email setup';
+        document.getElementById('leadEmail').value = payload.forward_to || '';
+        document.getElementById('leadNotes').value = `Demo plan for ${payload.domain}${payload.business_name ? ' / '+payload.business_name : ''}. Forward to ${payload.forward_to || '(unspecified)'}. Total ~$${plan.fees.total.toFixed(2)}, ~${plan.estimated_total_seconds}s.`;
+        document.getElementById('leadPrice').innerHTML = `<b>Total:</b> $${plan.fees.total.toFixed(2)}  ·  <b>ETA:</b> ~${plan.estimated_total_seconds}s`;
+        document.getElementById('leadModal').classList.add('show');
+        setTimeout(() => document.getElementById('leadEmail').focus(), 100);
+      });
+    }
+  })();
+
   // ----- "not legal advice" badge on every service card -----
   document.querySelectorAll('.svc').forEach(el => {
     const b = document.createElement('span');
diff --git a/server/server.js b/server/server.js
index 8874f24..db5f8da 100644
--- a/server/server.js
+++ b/server/server.js
@@ -654,6 +654,78 @@ app.post("/api/demo-classify", demoLimiter, express.json({ limit: "2kb" }), asyn
     res.status(500).json({ error: "classify_failed" });
   }
 });
+// Public services-demo endpoint — "Try it now" widget.
+// Returns a structured plan-of-API-calls for a given operation (no external hits yet — risk-free demo).
+// Captures the desired domain + visitor IP for follow-up via the lead viewer.
+const SERVICES_DEMO_PATH = path.join(__dirname, "data", "services-demo.jsonl");
+const servicesDemoLimiter = rateLimit({
+  windowMs: 60 * 1000, max: 30, standardHeaders: true, legacyHeaders: false,
+  message: { error: "rate_limited", msg: "Slow down — 30/min." }
+});
+app.post("/api/services-demo/domain-email", servicesDemoLimiter, express.json({ limit: "2kb" }), async (req, res) => {
+  const { domain, business_name, forward_to } = req.body || {};
+  if (typeof domain !== "string" || !/^[a-z0-9-]+\.[a-z]{2,24}$/i.test(domain.trim()) || domain.length > 60) {
+    return res.status(400).json({ error: "bad_domain", msg: "Domain must be a valid hostname like 'mybiz.com'." });
+  }
+  const dom = domain.trim().toLowerCase();
+  const tld = dom.split(".").pop();
+
+  // Approximate pricing per TLD. Real Porkbun pricing API would replace this; for the demo it's a fixed table.
+  const priceTable = { com: 9.13, net: 11.55, org: 9.66, io: 32.99, ai: 70.45, co: 26.41, app: 15.18, dev: 16.30, me: 24.99, biz: 17.99, info: 6.95 };
+  const reg_price = priceTable[tld] ?? 18.99;
+
+  const plan = {
+    summary: `Register ${dom}, point DNS at Cloudflare, set up info@${dom} with SPF/DKIM/DMARC, return working business email.`,
+    estimated_total_seconds: 47,
+    steps: [
+      { n: 1, api: "Porkbun /api/json/v3/domain/checkAvailability", action: `check ${dom}`, expected: "available / unavailable + reg price", risk: "read-only" },
+      { n: 2, api: "Porkbun /api/json/v3/domain/registerDomain", action: `register ${dom} for 1 year`, expected: `success → domain in your account`, cost: `$${reg_price.toFixed(2)} reg fee` },
+      { n: 3, api: "Cloudflare POST /zones", action: `create zone for ${dom}`, expected: "zone_id + nameservers", risk: "create" },
+      { n: 4, api: "Porkbun /api/json/v3/domain/updateNs", action: `set nameservers to Cloudflare`, expected: "propagation begins (~5 min)", risk: "destructive: changes ownership of DNS" },
+      { n: 5, api: "Cloudflare POST /zones/{id}/dns_records ×6", action: `create A, MX, SPF, DKIM, DMARC, _domainkey records`, expected: "all records green in CF dashboard", risk: "create" },
+      { n: 6, api: "Cloudflare email-routing API", action: `enable forwarding info@${dom} → ${forward_to || "[your inbox]"}`, expected: "rule active", risk: "create", needs_input: !forward_to },
+      { n: 7, api: "internal verification job", action: `wait for SPF/DKIM/DMARC to validate via Cloudflare`, expected: "all 3 = pass", risk: "read-only" },
+    ],
+    fees: {
+      ventura_claw_orchestration: 39.00,
+      domain_registration: reg_price,
+      cloudflare: 0,
+      total: +(39 + reg_price).toFixed(2),
+    },
+    you_get: [
+      `${dom} registered in your name (you own it, not us)`,
+      `info@${dom} forwarding to wherever you want`,
+      `SPF / DKIM / DMARC configured so your email lands in inboxes`,
+      `Cloudflare dashboard handed over to you`,
+      `Audit trail of every API call we fired (Cloudflare CFs, Porkbun receipt)`,
+    ],
+    not_included: [
+      "Mailbox storage (we set up forwarding, not Gmail/Outlook)",
+      "Trademark search on the domain name",
+      "WHOIS privacy (Porkbun does it free; on by default)",
+    ],
+    next_step: forward_to && business_name ? "ready_to_run" : "needs_inputs",
+  };
+
+  // Persist for follow-up
+  try {
+    fs.mkdirSync(path.dirname(SERVICES_DEMO_PATH), { recursive: true });
+    fs.appendFileSync(SERVICES_DEMO_PATH, JSON.stringify({
+      at: new Date().toISOString(),
+      kind: "domain-email",
+      domain: dom,
+      business_name: (business_name || "").slice(0, 80) || null,
+      forward_to: (forward_to || "").slice(0, 200) || null,
+      ip: (req.headers["x-forwarded-for"] || req.ip || "").toString().split(",")[0].trim(),
+      ua: (req.headers["user-agent"] || "").slice(0, 200),
+    }) + "\n");
+  } catch (e) {
+    console.warn("[services-demo] persist failed:", e.message);
+  }
+
+  res.json({ ok: true, plan });
+});
+
 // Public services-lead endpoint — captures "Get started" form submissions.
 // Writes to JSONL (lossless) + best-effort email via George Gmail (non-blocking).
 const SERVICES_LEADS_PATH = path.join(__dirname, "data", "services-leads.jsonl");

← ff436d8 /services chat: welcome message points at API offerings (Str  ·  back to Ventura Claw  ·  ENV_KEY_TO_CONNECTOR: add ELEVENLABS_API_KEY, PURELYMAIL_API 0c593b6 →