const rules = { required: (v) => v.trim() !== '' || 'This field is required.', email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || 'Enter a valid email.', min: (n) => (v) => v.length >= n || `Minimum ${n} characters required.`, max: (n) => (v) => v.length <= n || `Maximum ${n} characters allowed.`, url: (v) => { try { new URL(v); return true; } catch { return 'Enter a valid URL.'; } }, }; function validate(value, ...validators) { for (const fn of validators) { const result = fn(value); if (result !== true) return { valid: false, error: result }; } return { valid: true, error: null }; } function validateForm(form) { let isValid = true; form.querySelectorAll('[data-validate]').forEach(field => { const specs = field.dataset.validate.split(' '); const fns = specs.map(s => { const [name, arg] = s.split(':'); return arg ? rules[name](+arg) : rules[name]; }).filter(Boolean); const { valid, error } = validate(field.value, ...fns); const errEl = document.getElementById(`${field.id}-error`); field.setAttribute('aria-invalid', String(!valid)); if (errEl) errEl.textContent = error ?? ''; if (!valid) isValid = false; }); return isValid; }