Spaces:
Running
Running
the contact form is broken. improve it
Browse files- index.html +15 -12
- script.js +127 -9
- style.css +31 -10
index.html
CHANGED
|
@@ -370,35 +370,38 @@
|
|
| 370 |
</a>
|
| 371 |
</div>
|
| 372 |
</div>
|
| 373 |
-
|
| 374 |
<form id="contact-form" class="glass-card rounded-2xl border border-white/10 bg-[color:rgb(var(--bg)_/_0.7)] p-6">
|
| 375 |
<div class="grid grid-cols-2 gap-4">
|
| 376 |
<div class="col-span-2 sm:col-span-1">
|
| 377 |
-
<label class="label">Name
|
| 378 |
-
<input type="text" required placeholder="Jane Doe" />
|
|
|
|
| 379 |
</div>
|
| 380 |
<div class="col-span-2 sm:col-span-1">
|
| 381 |
-
<label class="label">Email
|
| 382 |
-
<input type="email" required placeholder="jane@company.com" />
|
|
|
|
| 383 |
</div>
|
| 384 |
<div class="col-span-2">
|
| 385 |
-
<label class="label">Subject
|
| 386 |
-
<input type="text" required placeholder="Let’s collaborate on a new project" />
|
|
|
|
| 387 |
</div>
|
| 388 |
<div class="col-span-2">
|
| 389 |
-
<label class="label">Message
|
| 390 |
-
<textarea rows="5" required placeholder="Tell me about your goals and timeline..."></textarea>
|
|
|
|
| 391 |
</div>
|
| 392 |
</div>
|
| 393 |
<div class="mt-6 flex items-center gap-3">
|
| 394 |
-
<button type="submit" class="btn-primary">
|
| 395 |
<i data-feather="send"></i>
|
| 396 |
-
Send message
|
| 397 |
</button>
|
| 398 |
<span id="form-status" class="text-sm text-zinc-400"></span>
|
| 399 |
</div>
|
| 400 |
</form>
|
| 401 |
-
|
| 402 |
</div>
|
| 403 |
</section>
|
| 404 |
</main>
|
|
|
|
| 370 |
</a>
|
| 371 |
</div>
|
| 372 |
</div>
|
|
|
|
| 373 |
<form id="contact-form" class="glass-card rounded-2xl border border-white/10 bg-[color:rgb(var(--bg)_/_0.7)] p-6">
|
| 374 |
<div class="grid grid-cols-2 gap-4">
|
| 375 |
<div class="col-span-2 sm:col-span-1">
|
| 376 |
+
<label for="name" class="label">Name *</label>
|
| 377 |
+
<input type="text" id="name" name="name" required placeholder="Jane Doe" class="form-field-input" />
|
| 378 |
+
<div class="error-message hidden" id="name-error">Please enter your name</div>
|
| 379 |
</div>
|
| 380 |
<div class="col-span-2 sm:col-span-1">
|
| 381 |
+
<label for="email" class="label">Email *</label>
|
| 382 |
+
<input type="email" id="email" name="email" required placeholder="jane@company.com" class="form-field-input" />
|
| 383 |
+
<div class="error-message hidden" id="email-error">Please enter a valid email address</div>
|
| 384 |
</div>
|
| 385 |
<div class="col-span-2">
|
| 386 |
+
<label for="subject" class="label">Subject *</label>
|
| 387 |
+
<input type="text" id="subject" name="subject" required placeholder="Let’s collaborate on a new project" class="form-field-input" />
|
| 388 |
+
<div class="error-message hidden" id="subject-error">Please enter a subject</div>
|
| 389 |
</div>
|
| 390 |
<div class="col-span-2">
|
| 391 |
+
<label for="message" class="label">Message *</label>
|
| 392 |
+
<textarea id="message" name="message" rows="5" required placeholder="Tell me about your goals and timeline..." class="form-field-input"></textarea>
|
| 393 |
+
<div class="error-message hidden" id="message-error">Please enter your message</div>
|
| 394 |
</div>
|
| 395 |
</div>
|
| 396 |
<div class="mt-6 flex items-center gap-3">
|
| 397 |
+
<button type="submit" id="submit-btn" class="btn-primary">
|
| 398 |
<i data-feather="send"></i>
|
| 399 |
+
<span id="submit-text">Send message</span>
|
| 400 |
</button>
|
| 401 |
<span id="form-status" class="text-sm text-zinc-400"></span>
|
| 402 |
</div>
|
| 403 |
</form>
|
| 404 |
+
</div>
|
| 405 |
</div>
|
| 406 |
</section>
|
| 407 |
</main>
|
script.js
CHANGED
|
@@ -45,20 +45,138 @@ window.addEventListener('scroll', () => {
|
|
| 45 |
backToTop?.classList.toggle('pointer-events-auto', scrolled > 400);
|
| 46 |
});
|
| 47 |
backToTop?.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
|
| 48 |
-
|
| 49 |
-
// Contact form (demo)
|
| 50 |
const form = document.getElementById('contact-form');
|
| 51 |
const status = document.getElementById('form-status');
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
e.preventDefault();
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
status.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
form.reset();
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
});
|
| 61 |
-
|
| 62 |
// Projects data
|
| 63 |
const projects = [
|
| 64 |
{
|
|
|
|
| 45 |
backToTop?.classList.toggle('pointer-events-auto', scrolled > 400);
|
| 46 |
});
|
| 47 |
backToTop?.addEventListener('click', () => window.scrollTo({ top: 0, behavior: 'smooth' }));
|
| 48 |
+
// Contact form with validation
|
|
|
|
| 49 |
const form = document.getElementById('contact-form');
|
| 50 |
const status = document.getElementById('form-status');
|
| 51 |
+
const submitBtn = document.getElementById('submit-btn');
|
| 52 |
+
const submitText = document.getElementById('submit-text');
|
| 53 |
+
|
| 54 |
+
// Validation functions
|
| 55 |
+
function validateEmail(email) {
|
| 56 |
+
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
| 57 |
+
return re.test(email);
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
function validateForm() {
|
| 61 |
+
let isValid = true;
|
| 62 |
+
|
| 63 |
+
// Clear previous errors
|
| 64 |
+
document.querySelectorAll('.error-message').forEach(error => {
|
| 65 |
+
error.classList.add('hidden');
|
| 66 |
+
});
|
| 67 |
+
document.querySelectorAll('.form-field-input').forEach(input => {
|
| 68 |
+
input.classList.remove('error');
|
| 69 |
+
});
|
| 70 |
+
|
| 71 |
+
// Get form data
|
| 72 |
+
const formData = new FormData(form);
|
| 73 |
+
const name = formData.get('name')?.trim();
|
| 74 |
+
const email = formData.get('email')?.trim();
|
| 75 |
+
const subject = formData.get('subject')?.trim();
|
| 76 |
+
const message = formData.get('message')?.trim();
|
| 77 |
+
|
| 78 |
+
// Validate name
|
| 79 |
+
if (!name) {
|
| 80 |
+
document.getElementById('name-error').classList.remove('hidden');
|
| 81 |
+
document.getElementById('name').classList.add('error');
|
| 82 |
+
isValid = false;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// Validate email
|
| 86 |
+
if (!email || !validateEmail(email)) {
|
| 87 |
+
document.getElementById('email-error').classList.remove('hidden');
|
| 88 |
+
document.getElementById('email').classList.add('error');
|
| 89 |
+
isValid = false;
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// Validate subject
|
| 93 |
+
if (!subject) {
|
| 94 |
+
document.getElementById('subject-error').classList.remove('hidden');
|
| 95 |
+
document.getElementById('subject').classList.add('error');
|
| 96 |
+
isValid = false;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
// Validate message
|
| 100 |
+
if (!message) {
|
| 101 |
+
document.getElementById('message-error').classList.remove('hidden');
|
| 102 |
+
document.getElementById('message').classList.add('error');
|
| 103 |
+
isValid = false;
|
| 104 |
+
} else if (message.length < 10) {
|
| 105 |
+
document.getElementById('message-error').textContent = 'Message must be at least 10 characters long';
|
| 106 |
+
document.getElementById('message-error').classList.remove('hidden');
|
| 107 |
+
document.getElementById('message').classList.add('error');
|
| 108 |
+
isValid = false;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
return isValid;
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
// Clear errors on input
|
| 115 |
+
document.querySelectorAll('.form-field-input').forEach(input => {
|
| 116 |
+
input.addEventListener('input', () => {
|
| 117 |
+
const errorElement = document.getElementById(input.id + '-error');
|
| 118 |
+
if (errorElement) {
|
| 119 |
+
errorElement.classList.add('hidden');
|
| 120 |
+
}
|
| 121 |
+
input.classList.remove('error');
|
| 122 |
+
});
|
| 123 |
+
});
|
| 124 |
+
|
| 125 |
+
form?.addEventListener('submit', async (e) => {
|
| 126 |
e.preventDefault();
|
| 127 |
+
|
| 128 |
+
if (!validateForm()) {
|
| 129 |
+
status.textContent = 'Please fix the errors above';
|
| 130 |
+
status.classList.add('text-red-400');
|
| 131 |
+
status.classList.remove('text-zinc-400');
|
| 132 |
+
return;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
// Reset status
|
| 136 |
+
status.textContent = '';
|
| 137 |
+
status.classList.remove('text-red-400');
|
| 138 |
+
status.classList.add('text-zinc-400');
|
| 139 |
+
|
| 140 |
+
// Disable form and show loading state
|
| 141 |
+
submitBtn.disabled = true;
|
| 142 |
+
submitText.textContent = 'Sending...';
|
| 143 |
+
submitBtn.classList.add('opacity-75');
|
| 144 |
+
|
| 145 |
+
try {
|
| 146 |
+
// Simulate form submission (replace with actual endpoint)
|
| 147 |
+
const formData = new FormData(form);
|
| 148 |
+
const data = Object.fromEntries(formData.entries());
|
| 149 |
+
|
| 150 |
+
// Simulate API call delay
|
| 151 |
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
| 152 |
+
|
| 153 |
+
// For demo purposes, just log the data
|
| 154 |
+
console.log('Form submitted:', data);
|
| 155 |
+
|
| 156 |
+
// Success
|
| 157 |
+
status.textContent = '✓ Message sent successfully! I\'ll get back to you soon.';
|
| 158 |
+
status.classList.remove('text-zinc-400');
|
| 159 |
+
status.classList.add('text-emerald-400');
|
| 160 |
+
|
| 161 |
form.reset();
|
| 162 |
+
|
| 163 |
+
// Reset button state
|
| 164 |
+
submitBtn.disabled = false;
|
| 165 |
+
submitText.textContent = 'Send message';
|
| 166 |
+
submitBtn.classList.remove('opacity-75');
|
| 167 |
+
|
| 168 |
+
} catch (error) {
|
| 169 |
+
console.error('Form submission error:', error);
|
| 170 |
+
status.textContent = '✗ Something went wrong. Please try again or email me directly.';
|
| 171 |
+
status.classList.remove('text-zinc-400');
|
| 172 |
+
status.classList.add('text-red-400');
|
| 173 |
+
|
| 174 |
+
// Reset button state
|
| 175 |
+
submitBtn.disabled = false;
|
| 176 |
+
submitText.textContent = 'Send message';
|
| 177 |
+
submitBtn.classList.remove('opacity-75');
|
| 178 |
+
}
|
| 179 |
});
|
|
|
|
| 180 |
// Projects data
|
| 181 |
const projects = [
|
| 182 |
{
|
style.css
CHANGED
|
@@ -84,35 +84,56 @@ html.light .chip {
|
|
| 84 |
border-color: rgba(15, 23, 42, 0.1);
|
| 85 |
color: rgb(71, 85, 105);
|
| 86 |
}
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
position: relative;
|
| 91 |
}
|
| 92 |
|
| 93 |
-
.form-input {
|
| 94 |
-
@apply
|
| 95 |
}
|
| 96 |
|
| 97 |
-
.form-input
|
| 98 |
-
@apply border-
|
| 99 |
}
|
| 100 |
|
| 101 |
-
.form-input::placeholder {
|
| 102 |
@apply text-zinc-500;
|
| 103 |
}
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
/* Light theme form inputs */
|
| 106 |
-
html.light .form-input {
|
| 107 |
background: rgba(255, 255, 255, 0.8);
|
| 108 |
border-color: rgba(15, 23, 42, 0.15);
|
| 109 |
color: rgb(15, 23, 42);
|
| 110 |
}
|
| 111 |
|
| 112 |
-
html.light .form-input::placeholder {
|
| 113 |
color: rgb(100, 116, 139);
|
| 114 |
}
|
| 115 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
* { -webkit-tap-highlight-color: transparent; }
|
| 117 |
|
| 118 |
html, body {
|
|
|
|
| 84 |
border-color: rgba(15, 23, 42, 0.1);
|
| 85 |
color: rgb(71, 85, 105);
|
| 86 |
}
|
| 87 |
+
/* Enhanced Form Styling */
|
| 88 |
+
.form-field-input {
|
| 89 |
+
@apply w-full rounded-lg border border-white/10 bg-white/5 text-zinc-200 placeholder:text-zinc-500 pl-4 pr-4 py-3 outline-none transition-all duration-200;
|
| 90 |
position: relative;
|
| 91 |
}
|
| 92 |
|
| 93 |
+
.form-field-input:focus {
|
| 94 |
+
@apply border-[color:rgb(var(--color-primary-500)_/_0.6)] ring-2 ring-[color:rgb(var(--color-primary-500)_/_0.2)];
|
| 95 |
}
|
| 96 |
|
| 97 |
+
.form-field-input.error {
|
| 98 |
+
@apply border-red-500 ring-2 ring-red-500/20;
|
| 99 |
}
|
| 100 |
|
| 101 |
+
.form-field-input::placeholder {
|
| 102 |
@apply text-zinc-500;
|
| 103 |
}
|
| 104 |
|
| 105 |
+
/* Error messages */
|
| 106 |
+
.error-message {
|
| 107 |
+
@apply text-red-400 text-xs mt-1 block;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
/* Light theme form inputs */
|
| 111 |
+
html.light .form-field-input {
|
| 112 |
background: rgba(255, 255, 255, 0.8);
|
| 113 |
border-color: rgba(15, 23, 42, 0.15);
|
| 114 |
color: rgb(15, 23, 42);
|
| 115 |
}
|
| 116 |
|
| 117 |
+
html.light .form-field-input::placeholder {
|
| 118 |
color: rgb(100, 116, 139);
|
| 119 |
}
|
| 120 |
|
| 121 |
+
html.light .form-field-input.error {
|
| 122 |
+
@apply border-red-500 ring-red-500/30;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
html.light .error-message {
|
| 126 |
+
@apply text-red-600;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
/* Button loading state */
|
| 130 |
+
.btn-primary:disabled {
|
| 131 |
+
@apply opacity-75 cursor-not-allowed;
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
.btn-primary:disabled:hover {
|
| 135 |
+
@apply scale-100;
|
| 136 |
+
}
|
| 137 |
* { -webkit-tap-highlight-color: transparent; }
|
| 138 |
|
| 139 |
html, body {
|