Skip to content

Commit cea877b

Browse files
committed
add contact form with human verification and styling enhancements
1 parent ad565bd commit cea877b

3 files changed

Lines changed: 170 additions & 0 deletions

File tree

index.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,44 @@ <h2>Achievements & Activities</h2>
286286
<section id="contact" class="section card reveal">
287287
<h2>Contact</h2>
288288
<p>Open to collaborations in AI research, LLM applications, and software engineering opportunities.</p>
289+
<p class="privacy-note">Privacy-first form with lightweight bot protection. No analytics or ad trackers are loaded.</p>
290+
291+
<form id="contact-form" class="contact-form" novalidate>
292+
<div class="form-grid">
293+
<label>
294+
Name
295+
<input type="text" name="name" autocomplete="name" required>
296+
</label>
297+
<label>
298+
Email
299+
<input type="email" name="email" autocomplete="email" required>
300+
</label>
301+
</div>
302+
303+
<label>
304+
Subject
305+
<input type="text" name="subject" required>
306+
</label>
307+
308+
<label>
309+
Message
310+
<textarea name="message" rows="6" required></textarea>
311+
</label>
312+
313+
<label id="human-check-label" data-answer="">
314+
<span id="human-check-question">Human check</span>
315+
<input type="number" id="human-check-input" inputmode="numeric" required>
316+
</label>
317+
318+
<input type="text" name="_honey" class="hp-field" tabindex="-1" autocomplete="off" aria-hidden="true">
319+
<input type="hidden" name="_subject" value="Portfolio Contact Form Submission">
320+
<input type="hidden" name="_captcha" value="false">
321+
<input type="hidden" id="form-started-at" value="">
322+
323+
<button class="btn btn-primary" type="submit">Send Message</button>
324+
<p id="form-status" class="form-status" role="status" aria-live="polite"></p>
325+
</form>
326+
289327
<div class="contact-grid">
290328
<a href="mailto:f.shafi@queensu.ca">f.shafi@queensu.ca</a>
291329
<a href="tel:+16135616661">+1 613 561 6661</a>

script.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,68 @@ window.addEventListener('scroll', activateNav);
3535
window.addEventListener('load', activateNav);
3636

3737
document.getElementById('year').textContent = new Date().getFullYear();
38+
39+
const contactForm = document.getElementById('contact-form');
40+
const formStatus = document.getElementById('form-status');
41+
const humanCheckLabel = document.getElementById('human-check-label');
42+
const humanCheckQuestion = document.getElementById('human-check-question');
43+
const humanCheckInput = document.getElementById('human-check-input');
44+
const formStartedAt = document.getElementById('form-started-at');
45+
46+
const createHumanCheck = () => {
47+
const a = Math.floor(Math.random() * 8) + 1;
48+
const b = Math.floor(Math.random() * 8) + 1;
49+
humanCheckQuestion.textContent = `Human check: What is ${a} + ${b}?`;
50+
humanCheckLabel.dataset.answer = String(a + b);
51+
};
52+
53+
if (contactForm && formStatus && humanCheckLabel && humanCheckQuestion && humanCheckInput && formStartedAt) {
54+
formStartedAt.value = String(Date.now());
55+
createHumanCheck();
56+
57+
contactForm.addEventListener('submit', async (event) => {
58+
event.preventDefault();
59+
formStatus.textContent = '';
60+
61+
if (!contactForm.checkValidity()) {
62+
formStatus.textContent = 'Please complete all required fields.';
63+
contactForm.reportValidity();
64+
return;
65+
}
66+
67+
const elapsed = Date.now() - Number(formStartedAt.value);
68+
if (elapsed < 4000) {
69+
formStatus.textContent = 'Please wait a few seconds and try again.';
70+
return;
71+
}
72+
73+
if (humanCheckInput.value.trim() !== humanCheckLabel.dataset.answer) {
74+
formStatus.textContent = 'Human check answer is incorrect.';
75+
createHumanCheck();
76+
humanCheckInput.value = '';
77+
return;
78+
}
79+
80+
const formData = new FormData(contactForm);
81+
const endpoint = 'https://formsubmit.co/ajax/f.shafi@queensu.ca';
82+
83+
try {
84+
const response = await fetch(endpoint, {
85+
method: 'POST',
86+
headers: { Accept: 'application/json' },
87+
body: formData,
88+
});
89+
90+
if (!response.ok) {
91+
throw new Error('Request failed');
92+
}
93+
94+
formStatus.textContent = 'Thanks! Your message has been sent.';
95+
contactForm.reset();
96+
createHumanCheck();
97+
formStartedAt.value = String(Date.now());
98+
} catch {
99+
formStatus.textContent = 'Could not send right now. Please email me directly at f.shafi@queensu.ca.';
100+
}
101+
});
102+
}

styles.css

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ article.card {
315315
display: grid;
316316
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
317317
gap: 0.65rem;
318+
margin-top: 1rem;
318319
}
319320

320321
.contact-grid a,
@@ -331,6 +332,68 @@ article.card {
331332
border-color: rgba(255, 122, 24, 0.45);
332333
}
333334

335+
.privacy-note {
336+
color: var(--muted);
337+
font-size: 0.94rem;
338+
}
339+
340+
.contact-form {
341+
margin-top: 1rem;
342+
padding: 1rem;
343+
border: 1px solid var(--stroke);
344+
border-radius: 14px;
345+
background: rgba(255, 255, 255, 0.6);
346+
display: grid;
347+
gap: 0.8rem;
348+
}
349+
350+
.form-grid {
351+
display: grid;
352+
grid-template-columns: repeat(2, minmax(0, 1fr));
353+
gap: 0.75rem;
354+
}
355+
356+
.contact-form label {
357+
display: grid;
358+
gap: 0.35rem;
359+
font-weight: 600;
360+
color: #1a2b40;
361+
font-size: 0.94rem;
362+
}
363+
364+
.contact-form input,
365+
.contact-form textarea {
366+
width: 100%;
367+
font: inherit;
368+
color: #132033;
369+
background: #ffffff;
370+
border: 1px solid rgba(19, 32, 51, 0.18);
371+
border-radius: 10px;
372+
padding: 0.62rem 0.72rem;
373+
}
374+
375+
.contact-form input:focus,
376+
.contact-form textarea:focus {
377+
outline: none;
378+
border-color: rgba(255, 122, 24, 0.65);
379+
box-shadow: 0 0 0 3px rgba(255, 122, 24, 0.14);
380+
}
381+
382+
.form-status {
383+
margin: 0;
384+
min-height: 1.25rem;
385+
font-size: 0.92rem;
386+
color: #1f3c5f;
387+
}
388+
389+
.hp-field {
390+
position: absolute;
391+
left: -9999px;
392+
width: 1px;
393+
height: 1px;
394+
opacity: 0;
395+
}
396+
334397
.site-footer {
335398
text-align: center;
336399
padding: 0 1rem 2.1rem;
@@ -401,6 +464,10 @@ article.card {
401464
justify-self: center;
402465
}
403466

467+
.form-grid {
468+
grid-template-columns: 1fr;
469+
}
470+
404471
.timeline {
405472
padding-left: 0.8rem;
406473
}

0 commit comments

Comments
 (0)