diff --git a/src/pages/contacto.astro b/src/pages/contacto.astro
index 516ebf6..e6b3a8b 100644
--- a/src/pages/contacto.astro
+++ b/src/pages/contacto.astro
@@ -84,9 +84,13 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
required
autocomplete="name"
maxlength="100"
+ pattern="[A-Za-záéíóúüñÁÉÍÓÚÜÑçÇ\s]+"
+ inputmode="text"
+ aria-describedby="name-error"
class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors"
style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);"
/>
+
@@ -100,9 +104,11 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
required
autocomplete="email"
maxlength="200"
+ aria-describedby="email-error"
class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors"
style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);"
/>
+
@@ -153,6 +159,47 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
const successMsg = isEn ? 'Message sent. We will reply soon.' : 'Mensaje enviado. Te respondemos pronto.';
const errorMsg = isEn ? 'Could not send. Please try again or contact us via WhatsApp.' : 'No se pudo enviar. Probá de nuevo o contactanos por WhatsApp.';
+ const nameInput = document.getElementById('name');
+ const emailInput = document.getElementById('email');
+ const nameError = document.getElementById('name-error');
+ const emailError = document.getElementById('email-error');
+ const nameRegex = /[^a-záéíóúüñç\s]/gi;
+ const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/;
+ const nameErrMsg = isEn ? 'Letters and spaces only' : 'Solo letras y espacios';
+ const emailErrMsg = isEn ? 'Invalid email address' : 'Dirección de correo no válida';
+
+ function showFieldError(input, errorEl, msg) {
+ errorEl.textContent = msg;
+ errorEl.classList.remove('hidden');
+ input.style.borderColor = 'var(--color-hds-error)';
+ }
+ function clearFieldError(input, errorEl) {
+ errorEl.classList.add('hidden');
+ errorEl.textContent = '';
+ input.style.borderColor = '';
+ }
+
+ nameInput.addEventListener('beforeinput', (e) => {
+ if (e.data && nameRegex.test(e.data)) {
+ e.preventDefault();
+ }
+ });
+ nameInput.addEventListener('input', () => {
+ const clean = nameInput.value.replace(nameRegex, '');
+ if (clean !== nameInput.value) {
+ nameInput.value = clean;
+ }
+ });
+ emailInput.addEventListener('input', () => {
+ emailInput.value = emailInput.value.toLowerCase();
+ });
+ [nameInput, emailInput].forEach(input => {
+ input.addEventListener('focus', () => {
+ const err = document.getElementById(input.id + '-error');
+ if (err) clearFieldError(input, err);
+ });
+ });
+
window.addEventListener('load', () => {
if (window.grecaptcha && window.grecaptcha.ready) {
window.grecaptcha.ready();
@@ -169,6 +216,22 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
status.textContent = '';
status.style.color = 'var(--hds-fg-soft)';
+ let hasError = false;
+ if (nameInput.value.trim() && nameRegex.test(nameInput.value)) {
+ showFieldError(nameInput, nameError, nameErrMsg);
+ hasError = true;
+ }
+ const emailVal = emailInput.value.trim();
+ if (emailVal && !emailRegex.test(emailVal)) {
+ showFieldError(emailInput, emailError, emailErrMsg);
+ hasError = true;
+ }
+ if (hasError) {
+ label.textContent = originalLabel;
+ submit.disabled = false;
+ return;
+ }
+
try {
let token = '';
if (window.grecaptcha && window.grecaptcha.execute) {
diff --git a/src/pages/en/contact.astro b/src/pages/en/contact.astro
index 516ebf6..e6b3a8b 100644
--- a/src/pages/en/contact.astro
+++ b/src/pages/en/contact.astro
@@ -84,9 +84,13 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
required
autocomplete="name"
maxlength="100"
+ pattern="[A-Za-záéíóúüñÁÉÍÓÚÜÑçÇ\s]+"
+ inputmode="text"
+ aria-describedby="name-error"
class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors"
style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);"
/>
+
@@ -100,9 +104,11 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
required
autocomplete="email"
maxlength="200"
+ aria-describedby="email-error"
class="w-full px-3.5 py-2.5 rounded-md outline-none transition-colors"
style="background: var(--hds-bg); color: var(--hds-fg); border: 1px solid var(--hds-line);"
/>
+
@@ -153,6 +159,47 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
const successMsg = isEn ? 'Message sent. We will reply soon.' : 'Mensaje enviado. Te respondemos pronto.';
const errorMsg = isEn ? 'Could not send. Please try again or contact us via WhatsApp.' : 'No se pudo enviar. Probá de nuevo o contactanos por WhatsApp.';
+ const nameInput = document.getElementById('name');
+ const emailInput = document.getElementById('email');
+ const nameError = document.getElementById('name-error');
+ const emailError = document.getElementById('email-error');
+ const nameRegex = /[^a-záéíóúüñç\s]/gi;
+ const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/;
+ const nameErrMsg = isEn ? 'Letters and spaces only' : 'Solo letras y espacios';
+ const emailErrMsg = isEn ? 'Invalid email address' : 'Dirección de correo no válida';
+
+ function showFieldError(input, errorEl, msg) {
+ errorEl.textContent = msg;
+ errorEl.classList.remove('hidden');
+ input.style.borderColor = 'var(--color-hds-error)';
+ }
+ function clearFieldError(input, errorEl) {
+ errorEl.classList.add('hidden');
+ errorEl.textContent = '';
+ input.style.borderColor = '';
+ }
+
+ nameInput.addEventListener('beforeinput', (e) => {
+ if (e.data && nameRegex.test(e.data)) {
+ e.preventDefault();
+ }
+ });
+ nameInput.addEventListener('input', () => {
+ const clean = nameInput.value.replace(nameRegex, '');
+ if (clean !== nameInput.value) {
+ nameInput.value = clean;
+ }
+ });
+ emailInput.addEventListener('input', () => {
+ emailInput.value = emailInput.value.toLowerCase();
+ });
+ [nameInput, emailInput].forEach(input => {
+ input.addEventListener('focus', () => {
+ const err = document.getElementById(input.id + '-error');
+ if (err) clearFieldError(input, err);
+ });
+ });
+
window.addEventListener('load', () => {
if (window.grecaptcha && window.grecaptcha.ready) {
window.grecaptcha.ready();
@@ -169,6 +216,22 @@ const reCAPTCHA_site_key = '6LeOxaEaAAAAAPD8rQQLqw4woxn1dMjvF_Ooy0BG';
status.textContent = '';
status.style.color = 'var(--hds-fg-soft)';
+ let hasError = false;
+ if (nameInput.value.trim() && nameRegex.test(nameInput.value)) {
+ showFieldError(nameInput, nameError, nameErrMsg);
+ hasError = true;
+ }
+ const emailVal = emailInput.value.trim();
+ if (emailVal && !emailRegex.test(emailVal)) {
+ showFieldError(emailInput, emailError, emailErrMsg);
+ hasError = true;
+ }
+ if (hasError) {
+ label.textContent = originalLabel;
+ submit.disabled = false;
+ return;
+ }
+
try {
let token = '';
if (window.grecaptcha && window.grecaptcha.execute) {