Add low-friction 3-step lead form to contact section (posts to n8n)

Soft conversion path alongside booking: situation -> optional detail -> name/email.
Honeypot-protected, posts JSON to https://n8n.shopwisdom.ca/webhook/oc-lead, with an
email fallback on error. Captures the 'interested but not ready to book' visitor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 21:26:47 -07:00
parent 05bf44e05c
commit 3537cd62f9
2 changed files with 110 additions and 1 deletions
+85 -1
View File
@@ -231,8 +231,39 @@
<p class="eyebrow">Start a conversation</p>
<h2>The people part doesn't have to be <em>a mystery.</em></h2>
<p>No pitch, no program to sign up for. Just a straight talk about what's getting in the way and whether we can help.</p>
<form class="lf" data-endpoint="https://n8n.shopwisdom.ca/webhook/oc-lead" data-source="open-communications" novalidate>
<div class="lf-progress" aria-hidden="true"><span class="lf-dot is-active"></span><span class="lf-dot"></span><span class="lf-dot"></span></div>
<fieldset class="lf-step is-active" data-step="1">
<legend class="lf-q">What's getting in the way?</legend>
<div class="lf-choices">
<label class="lf-choice"><input type="radio" name="topic" value="A change isn't sticking" required> A change that won't stick</label>
<label class="lf-choice"><input type="radio" name="topic" value="Team friction or resistance"> Team friction or resistance</label>
<label class="lf-choice"><input type="radio" name="topic" value="Succession or leadership transition"> Succession / a leadership handoff</label>
<label class="lf-choice"><input type="radio" name="topic" value="Just exploring"> Just exploring for now</label>
</div>
<div class="lf-nav"><button type="button" class="btn lf-next" data-next>Continue →</button></div>
</fieldset>
<fieldset class="lf-step" data-step="2">
<legend class="lf-q">Anything you want me to know? <span class="lf-opt">(optional)</span></legend>
<textarea class="lf-input" name="detail" rows="3" placeholder="A sentence or two is plenty."></textarea>
<div class="lf-nav"><button type="button" class="btn link lf-back" data-back>← Back</button><button type="button" class="btn lf-next" data-next>Continue →</button></div>
</fieldset>
<fieldset class="lf-step" data-step="3">
<legend class="lf-q">Where do I reach you?</legend>
<input class="lf-input" type="text" name="name" placeholder="Your name" required autocomplete="name">
<input class="lf-input" type="email" name="email" placeholder="Email" required autocomplete="email">
<input class="lf-input" type="tel" name="phone" placeholder="Phone (optional)" autocomplete="tel">
<input type="text" name="company_website" class="lf-hp" tabindex="-1" autocomplete="off" aria-hidden="true">
<div class="lf-nav"><button type="button" class="btn link lf-back" data-back>← Back</button><button type="submit" class="btn lf-submit">Send it →</button></div>
<p class="lf-err" hidden>Hmm — that didn't go through. Please <a href="mailto:michael@opencommunications.consulting">email me directly</a> and I'll get right back to you.</p>
</fieldset>
<div class="lf-done" hidden><p class="lf-done-msg">Got it — thank you. I'll be in touch personally, usually within a business day.</p></div>
</form>
<p class="lf-alt">Prefer to reach out directly?</p>
<p class="actions center">
<a class="btn" href="mailto:michael@opencommunications.consulting?subject=A%20conversation%20—%20Open%20Communications">Email Michael</a>
<a class="btn link" href="mailto:michael@opencommunications.consulting?subject=A%20conversation%20—%20Open%20Communications">Email Michael</a>
<a class="btn link tel" data-p1="778" data-p2="985" data-p3="6736" href="mailto:michael@opencommunications.consulting">Call 778&#8209;985&#8209;OPEN (6736)</a>
</p>
<p class="promise">The first conversation is free and commits you to nothing.</p>
@@ -319,6 +350,59 @@
})();
</script>
<!-- Lead form: 3-step navigation + submit to the n8n webhook (honeypot-protected) -->
<script>
(function () {
document.querySelectorAll('form.lf').forEach(function (form) {
var steps = [].slice.call(form.querySelectorAll('.lf-step'));
var dots = [].slice.call(form.querySelectorAll('.lf-dot'));
var done = form.querySelector('.lf-done');
var err = form.querySelector('.lf-err');
var progress = form.querySelector('.lf-progress');
var cur = 0;
function show(i) {
steps.forEach(function (s, k) { s.classList.toggle('is-active', k === i); });
dots.forEach(function (d, k) { d.classList.toggle('is-active', k <= i); });
cur = i;
var fld = steps[i].querySelector('input:not([type=radio]),textarea'); if (fld) { try { fld.focus(); } catch (e) {} }
}
function valid(i) {
var reqs = steps[i].querySelectorAll('[required]');
for (var j = 0; j < reqs.length; j++) { if (!reqs[j].checkValidity()) { reqs[j].reportValidity(); return false; } }
return true;
}
form.addEventListener('click', function (e) {
if (e.target.closest('[data-next]')) { if (valid(cur)) show(Math.min(cur + 1, steps.length - 1)); }
else if (e.target.closest('[data-back]')) { show(Math.max(cur - 1, 0)); }
});
form.addEventListener('submit', function (e) {
e.preventDefault();
if (!valid(cur)) return;
var hp = form.querySelector('.lf-hp');
if (hp && hp.value) { return; }
if (err) err.hidden = true;
var data = {};
new FormData(form).forEach(function (v, k) { if (k !== 'company_website') data[k] = v; });
data.source = form.getAttribute('data-source') || location.hostname;
data.submittedAt = new Date().toISOString();
var btn = form.querySelector('.lf-submit'); var label = btn.textContent;
btn.disabled = true; btn.textContent = 'Sending…';
fetch(form.getAttribute('data-endpoint'), {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data)
}).then(function (r) {
if (!r.ok) throw new Error('status ' + r.status);
steps.forEach(function (s) { s.style.display = 'none'; });
if (progress) progress.style.display = 'none';
if (done) done.hidden = false;
}).catch(function () {
btn.disabled = false; btn.textContent = label;
if (err) err.hidden = false;
});
});
});
})();
</script>
<!-- Cal.com booking embed (opens the booking popup on-brand; href is the no-JS fallback) -->
<script>
(function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; if (typeof namespace === "string") { cal.ns[namespace] = cal.ns[namespace] || api; p(cal.ns[namespace], ar); p(cal, ["initNamespace", namespace]); } else p(cal, ar); return; } p(cal, ar); }; })(window, "https://cal.shopwisdom.ca/embed/embed.js", "init");