Skip to content

Commit 9794e95

Browse files
Add contact form via Formspree
- Contact form (name/email/message) on both EN and ZH pages - AJAX submission with inline success/error status, no page redirect - Form card styling matching site design system, responsive single column on mobile Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 5e0b6e1 commit 9794e95

4 files changed

Lines changed: 167 additions & 1 deletion

File tree

app.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,52 @@
719719

720720

721721
/* ─────────────────────────────────────────────
722-
8. SMOOTH SECTION ENTRANCE via CSS class
722+
8. CONTACT FORM (Formspree AJAX)
723+
───────────────────────────────────────────── */
724+
(function initContactForm() {
725+
const form = document.getElementById('contactForm');
726+
if (!form) return;
727+
const submitBtn = document.getElementById('cfSubmit');
728+
const status = document.getElementById('cfStatus');
729+
const isZh = document.body.classList.contains('lang-zh');
730+
731+
const msg = {
732+
sending: isZh ? '发送中…' : 'Sending…',
733+
success: isZh ? '✓ 留言已发送,我会尽快回复您!' : '✓ Message sent — I will get back to you soon!',
734+
error: isZh ? '✗ 发送失败,请稍后重试或直接发邮件。' : '✗ Something went wrong. Please try again or email me directly.',
735+
};
736+
737+
form.addEventListener('submit', async (e) => {
738+
e.preventDefault();
739+
submitBtn.disabled = true;
740+
status.className = 'form-status';
741+
status.textContent = msg.sending;
742+
743+
try {
744+
const res = await fetch(form.action, {
745+
method: 'POST',
746+
body: new FormData(form),
747+
headers: { 'Accept': 'application/json' },
748+
});
749+
if (res.ok) {
750+
status.classList.add('success');
751+
status.textContent = msg.success;
752+
form.reset();
753+
} else {
754+
throw new Error('Formspree error');
755+
}
756+
} catch {
757+
status.classList.add('error');
758+
status.textContent = msg.error;
759+
} finally {
760+
submitBtn.disabled = false;
761+
}
762+
});
763+
})();
764+
765+
766+
/* ─────────────────────────────────────────────
767+
9. SMOOTH SECTION ENTRANCE via CSS class
723768
───────────────────────────────────────────── */
724769
(function sectionFadeIn() {
725770
const sections = document.querySelectorAll('section');

index-zh.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -712,6 +712,25 @@ <h2 class="section-title">建立 <span class="highlight">连接</span></h2>
712712
<div class="contact-value">linkedin.com/in/zhangsenye</div>
713713
</a>
714714
</div>
715+
716+
<form class="contact-form" id="contactForm" action="https://formspree.io/f/mykapvdo" method="POST">
717+
<div class="form-row">
718+
<div class="form-field">
719+
<label for="cfName">姓名</label>
720+
<input type="text" id="cfName" name="name" placeholder="您的姓名" required />
721+
</div>
722+
<div class="form-field">
723+
<label for="cfEmail">邮箱</label>
724+
<input type="email" id="cfEmail" name="email" placeholder="you@example.com" required />
725+
</div>
726+
</div>
727+
<div class="form-field">
728+
<label for="cfMessage">留言</label>
729+
<textarea id="cfMessage" name="message" rows="5" placeholder="请描述您的项目或合作机会..." required></textarea>
730+
</div>
731+
<button type="submit" class="btn-primary form-submit" id="cfSubmit">发送留言</button>
732+
<p class="form-status" id="cfStatus" role="status"></p>
733+
</form>
715734
</div>
716735
</section>
717736

index.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -780,6 +780,25 @@ <h2 class="section-title">Let's <span class="highlight">Connect</span></h2>
780780
<div class="contact-value">linkedin.com/in/zhangsenye</div>
781781
</a>
782782
</div>
783+
784+
<form class="contact-form" id="contactForm" action="https://formspree.io/f/mykapvdo" method="POST">
785+
<div class="form-row">
786+
<div class="form-field">
787+
<label for="cfName">Name</label>
788+
<input type="text" id="cfName" name="name" placeholder="Your name" required />
789+
</div>
790+
<div class="form-field">
791+
<label for="cfEmail">Email</label>
792+
<input type="email" id="cfEmail" name="email" placeholder="you@example.com" required />
793+
</div>
794+
</div>
795+
<div class="form-field">
796+
<label for="cfMessage">Message</label>
797+
<textarea id="cfMessage" name="message" rows="5" placeholder="Tell me about your project or opportunity..." required></textarea>
798+
</div>
799+
<button type="submit" class="btn-primary form-submit" id="cfSubmit">Send Message</button>
800+
<p class="form-status" id="cfStatus" role="status"></p>
801+
</form>
783802
</div>
784803
</section>
785804

style.css

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,89 @@ section {
994994
font-weight: 500;
995995
}
996996

997+
/* ===== CONTACT FORM ===== */
998+
.contact-form {
999+
margin-top: 3rem;
1000+
background: var(--bg-card);
1001+
border: 1px solid var(--border);
1002+
border-radius: var(--radius);
1003+
padding: 2.5rem;
1004+
backdrop-filter: blur(20px);
1005+
display: flex;
1006+
flex-direction: column;
1007+
gap: 1.2rem;
1008+
}
1009+
1010+
.form-row {
1011+
display: grid;
1012+
grid-template-columns: 1fr 1fr;
1013+
gap: 1.2rem;
1014+
}
1015+
1016+
.form-field {
1017+
display: flex;
1018+
flex-direction: column;
1019+
gap: 6px;
1020+
}
1021+
1022+
.form-field label {
1023+
font-size: 0.8rem;
1024+
font-weight: 600;
1025+
letter-spacing: 0.08em;
1026+
text-transform: uppercase;
1027+
color: var(--text-secondary);
1028+
}
1029+
1030+
.form-field input,
1031+
.form-field textarea {
1032+
font-family: var(--font-main);
1033+
font-size: 0.95rem;
1034+
color: var(--text-primary);
1035+
background: rgba(59, 139, 255, 0.06);
1036+
border: 1px solid var(--border);
1037+
border-radius: 10px;
1038+
padding: 12px 16px;
1039+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
1040+
resize: vertical;
1041+
}
1042+
1043+
.form-field input::placeholder,
1044+
.form-field textarea::placeholder {
1045+
color: var(--text-muted);
1046+
}
1047+
1048+
.form-field input:focus,
1049+
.form-field textarea:focus {
1050+
outline: none;
1051+
border-color: var(--accent);
1052+
box-shadow: 0 0 12px rgba(59, 139, 255, 0.25);
1053+
}
1054+
1055+
.form-submit {
1056+
border: none;
1057+
align-self: flex-start;
1058+
}
1059+
1060+
.form-submit:disabled {
1061+
opacity: 0.6;
1062+
cursor: not-allowed;
1063+
transform: none;
1064+
}
1065+
1066+
.form-status {
1067+
font-size: 0.88rem;
1068+
min-height: 1.2em;
1069+
margin: 0;
1070+
}
1071+
1072+
.form-status.success { color: #4ade80; }
1073+
.form-status.error { color: #f87171; }
1074+
1075+
@media (max-width: 640px) {
1076+
.form-row { grid-template-columns: 1fr; }
1077+
.contact-form { padding: 1.5rem; }
1078+
}
1079+
9971080
/* ===== HOBBIES SECTION ===== */
9981081
.hobbies {
9991082
background: linear-gradient(180deg, transparent 0%, rgba(7, 21, 37, 0.5) 50%, transparent 100%);

0 commit comments

Comments
 (0)