patx/afterdarklabs
improve formspree connect
Commit a427491 · patx · 2026-05-16T15:50:19-04:00
Comments
No comments yet.
Diff
diff --git a/index.html b/index.html
index 571ff78..5e3c5ff 100644
--- a/index.html
+++ b/index.html
@@ -231,7 +231,7 @@
<div class="max-w-3xl mx-auto text-center px-6">
<h2 class="heading text-6xl font-bold tracking-tighter mb-6">Ready to build something exceptional?</h2>
<p class="text-xl text-gray-400 mb-10">Tell us about your project. We reply the same day!</p>
- <form class="space-y-6" action="https://formspree.io/f/xeedjery" method="POST">
+ <form id="contact-form" class="space-y-6" action="https://formspree.io/f/xeedjery" method="POST">
<input type="hidden" name="_subject" value="New project inquiry from After Dark Labs">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<input type="text" name="name" placeholder="Your Name" autocomplete="name" required class="bg-zinc-900 border border-white/10 focus:border-[#ccff00] rounded-2xl px-6 py-5 outline-none transition-colors">
@@ -242,6 +242,7 @@
Send Message
</button>
</form>
+ <div id="contact-status" class="hidden rounded-3xl border border-white/10 bg-zinc-900 px-8 py-10 text-left" tabindex="-1" aria-live="polite"></div>
</div>
</section>
@@ -262,6 +263,83 @@
</div>
<script>
+ (function () {
+ const form = document.getElementById('contact-form');
+ const status = document.getElementById('contact-status');
+
+ if (!form || !status) return;
+
+ const submitButton = form.querySelector('button[type="submit"]');
+ const defaultButtonText = submitButton.textContent.trim();
+
+ function showStatus(type, title, message) {
+ const isSuccess = type === 'success';
+ status.className = [
+ 'rounded-3xl border px-8 py-10 text-left',
+ isSuccess ? 'border-[#ccff00]/40 bg-[#ccff00]/10' : 'border-red-400/40 bg-red-950/30',
+ ].join(' ');
+ status.setAttribute('role', isSuccess ? 'status' : 'alert');
+
+ const eyebrow = document.createElement('p');
+ eyebrow.className = `text-sm font-semibold uppercase tracking-[0.2em] ${isSuccess ? 'text-[#ccff00]' : 'text-red-300'}`;
+ eyebrow.textContent = isSuccess ? 'Message sent' : 'Message not sent';
+
+ const heading = document.createElement('h3');
+ heading.className = 'heading mt-3 text-3xl font-semibold tracking-tighter text-white';
+ heading.textContent = title;
+
+ const body = document.createElement('p');
+ body.className = 'mt-4 text-lg leading-8 text-gray-300';
+ body.textContent = message;
+
+ status.replaceChildren(eyebrow, heading, body);
+ form.remove();
+ status.focus({ preventScroll: true });
+ }
+
+ form.addEventListener('submit', async event => {
+ event.preventDefault();
+
+ if (!form.reportValidity()) return;
+
+ submitButton.disabled = true;
+ submitButton.textContent = 'Sending...';
+ submitButton.classList.add('cursor-wait', 'opacity-80');
+
+ try {
+ const response = await fetch(form.action, {
+ method: form.method,
+ body: new FormData(form),
+ headers: { Accept: 'application/json' },
+ });
+ const data = await response.json().catch(() => ({}));
+
+ if (!response.ok) {
+ const errors = Array.isArray(data.errors)
+ ? data.errors.map(error => error.message).filter(Boolean).join(' ')
+ : '';
+ throw new Error(errors || 'The message could not be submitted.');
+ }
+
+ showStatus(
+ 'success',
+ 'Thank you. We will be in touch soon.',
+ 'Your project details were sent successfully, and we will reply as soon as possible.'
+ );
+ } catch (error) {
+ showStatus(
+ 'error',
+ 'We could not send your message.',
+ error.message || 'Please refresh the page and try again in a moment.'
+ );
+ } finally {
+ submitButton.disabled = false;
+ submitButton.textContent = defaultButtonText;
+ submitButton.classList.remove('cursor-wait', 'opacity-80');
+ }
+ });
+ })();
+
(function () {
const modal = document.getElementById('work-modal');
const modalImage = document.getElementById('work-modal-image');