Contact

Talk to us

Tell us what you are building. We will tell you if we can help.

Send us a message

Which team do you want to reach?

For questions that do not fit Sales, Partnerships, or Finance. We route every message to the right person.

Considering a project? Share the brief, a timeline, and your main technical constraint. We reply within two business days.

Resellers, technology partners, integrations, and co-builds. Tell us what you have in mind.

Finance and billing matters. We route these to the right person.

(function () { // ---- 1. Load Cloudflare Turnstile API so the widget renders ---- var s = document.createElement('script'); s.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'; s.async = true; s.defer = true; document.head.appendChild(s); // ---- 2. Tab switching + submit-state UX ---- document.addEventListener('DOMContentLoaded', function () { var form = document.querySelector('.c-form'); if (!form) return; // Tabs: update .is-active on labels and intros when a radio changes. var tabs = form.querySelectorAll('.c-form__tab'); var intros = form.querySelectorAll('.c-form__intro'); function syncTabs() { tabs.forEach(function (t) { var radio = t.querySelector('input[type="radio"]'); t.classList.toggle('is-active', !!(radio && radio.checked)); }); var selected = form.querySelector('input[name="department"]:checked'); var value = selected ? selected.value : 'General'; intros.forEach(function (p) { p.classList.toggle('is-active', p.getAttribute('data-intro') === value); }); } form.querySelectorAll('input[name="department"]').forEach(function (r) { r.addEventListener('change', syncTabs); }); syncTabs(); // Submit: disable button + show spinner so the user knows it is working. var btn = form.querySelector('button[type="submit"]'); if (btn) { form.addEventListener('submit', function () { setTimeout(function () { btn.disabled = true; btn.setAttribute('aria-busy', 'true'); btn.classList.add('is-submitting'); btn.textContent = 'Sending…'; var spinner = document.createElement('span'); spinner.className = 'btn__spinner'; spinner.setAttribute('aria-hidden', 'true'); btn.appendChild(spinner); }, 0); }); } }); // ---- 3. Banner based on ?sent=1 / ?error= ---- var params = new URLSearchParams(window.location.search); var sent = params.get('sent') === '1'; var errCd = params.get('error'); if (!sent && !errCd) return; var msg; if (sent) { msg = { kind: 'success', text: 'Thanks. Your message is on its way and we will reply shortly.' }; } else if (errCd === 'captcha') { msg = { kind: 'error', text: 'Captcha failed. Please try again.' }; } else if (errCd === 'fields') { msg = { kind: 'error', text: 'Please fill in name, email, and message.' }; } else if (errCd === 'rate') { msg = { kind: 'error', text: 'Too many submissions from your address. Please try again in a few minutes.' }; } else { msg = { kind: 'error', text: 'Something went wrong. Please try again or email [email protected] directly.' }; } document.addEventListener('DOMContentLoaded', function () { var form = document.querySelector('.c-form'); if (!form) return; var b = document.createElement('div'); b.className = 'c-form__banner c-form__banner--' + msg.kind; b.setAttribute('role', msg.kind === 'error' ? 'alert' : 'status'); b.textContent = msg.text; form.insertBefore(b, form.firstChild); b.scrollIntoView({ behavior: 'smooth', block: 'center' }); if (window.history && window.history.replaceState) { window.history.replaceState({}, '', '/contact/'); } }); })();