Current directory: /home/klas4s23/domains/585455.klas4s23.mid-ica.nl/public_html/Gastenboek/uploads
/*=============== EMAIL JS - CONTACT FORM ===============*/
/**
* EmailJS Configuration
* Prefer configuration coming from `window.EMAILJS_CONFIG` (set in assets/js/config.js).
* Falls back to the older window.EMAIL_CONFIG shape for compatibility, then to hardcoded defaults.
*/
const _cfg = window.EMAILJS_CONFIG || window.EMAIL_CONFIG || {};
const EMAIL_CONFIG = {
serviceId: _cfg.serviceId || _cfg.service || '',
templateId: _cfg.templateId || _cfg.template || '',
publicKey: _cfg.publicKey || _cfg.user || '',
minMessageLength: typeof _cfg.minMessageLength === 'number' ? _cfg.minMessageLength : (_cfg.min_length || 10),
// successDuration controls the visible overlay timeout; errorDuration used for messageDuration fallback
successDuration: typeof _cfg.successDuration === 'number' ? _cfg.successDuration : (_cfg.success || 3000),
messageDuration: typeof _cfg.errorDuration === 'number' ? _cfg.errorDuration : (_cfg.errorDuration || (_cfg.messageDuration || 5000))
};
// Validate that config is loaded (log but keep running so devs can still test without config)
if (!EMAIL_CONFIG.serviceId || !EMAIL_CONFIG.templateId || !EMAIL_CONFIG.publicKey) {
console.warn('EmailJS configuration missing/partial. Make sure assets/js/config.js defines window.EMAILJS_CONFIG. Falling back to defaults where possible.');
}
/**
* Creates animated success overlay with checkmark
*/
const createSuccessAnimation = () => {
const overlay = document.createElement('div');
overlay.className = 'email-success-overlay';
overlay.innerHTML = `
<div class="email-success-content">
<div class="geometric-box"></div>
<div class="email-success-checkmark">
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
<path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
</svg>
</div>
<h3 class="email-success-title">Message Sent!</h3>
<p class="email-success-text">Thank you for reaching out. I'll get back to you soon!</p>
</div>
<div class="confetti-container" aria-hidden></div>
`;
document.body.appendChild(overlay);
// start entrance
requestAnimationFrame(() => overlay.classList.add('show'));
// trigger SVG strokes after small delay
setTimeout(() => {
const circle = overlay.querySelector('.checkmark__circle');
const check = overlay.querySelector('.checkmark__check');
if (circle) circle.style.strokeDashoffset = '0';
if (check) check.style.strokeDashoffset = '0';
}, 80);
// burst confetti
launchConfetti(overlay.querySelector('.confetti-container'));
// remove overlay after configured duration
setTimeout(() => {
overlay.classList.remove('show');
setTimeout(() => overlay.remove(), 420);
}, EMAIL_CONFIG.successDuration);
};
/**
* Creates loading spinner element
*/
const createLoadingSpinner = () => {
const spinner = document.createElement('span');
spinner.className = 'email-loading-spinner';
spinner.innerHTML = '<span class="spinner-ring"></span>';
return spinner;
};
/**
* Validates form inputs and displays errors
* @param {HTMLFormElement} contactForm - The contact form element
* @param {HTMLElement} contactMessage - The message display element
* @returns {boolean} True if all fields are valid
*/
const validateForm = (contactForm, contactMessage) => {
const name = contactForm.querySelector('[name="user_name"]');
const email = contactForm.querySelector('[name="user_email"]');
const message = contactForm.querySelector('[name="user_message"]');
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const errors = [];
let isValid = true;
// Clear previous error states
[name, email, message].forEach(field => field?.classList.remove('error-field'));
// Validate name
if (!name?.value.trim()) {
errors.push('Name is required');
name.classList.add('error-field');
isValid = false;
}
// Validate email
if (!email?.value.trim()) {
errors.push('Email is required');
email.classList.add('error-field');
isValid = false;
} else if (!emailPattern.test(email.value.trim())) {
errors.push('Please enter a valid email address');
email.classList.add('error-field');
isValid = false;
}
// Validate message
const messageLength = message?.value.trim().length || 0;
if (messageLength === 0) {
errors.push('Message is required');
message?.classList.add('error-field');
isValid = false;
} else if (messageLength < EMAIL_CONFIG.minMessageLength) {
errors.push(`Message must be at least ${EMAIL_CONFIG.minMessageLength} characters`);
message?.classList.add('error-field');
isValid = false;
}
if (!isValid) {
showMessage(contactMessage, errors.join('<br>'), 'error');
}
return isValid;
};
/**
* Displays message to user with animation
* @param {HTMLElement} contactMessage - The message display element
* @param {string} text - Message text (HTML supported)
* @param {string} type - 'success' or 'error'
*/
const showMessage = (contactMessage, text, type = 'success') => {
if (!contactMessage) return;
contactMessage.innerHTML = text;
contactMessage.className = `contact__message ${type}`;
contactMessage.style.display = 'block';
requestAnimationFrame(() => contactMessage.classList.add('show'));
setTimeout(() => {
contactMessage.classList.remove('show');
setTimeout(() => {
contactMessage.style.display = 'none';
contactMessage.innerHTML = '';
}, 400);
}, EMAIL_CONFIG.messageDuration);
};
/**
* Lightweight confetti generator - creates colorful DOM pieces and animates with CSS
* @param {HTMLElement} container - Element where confetti pieces will be appended
* @param {number} count - Number of confetti pieces to emit
*/
const launchConfetti = (container, count = 28) => {
if (!container) return;
const colors = ['color-1','color-2','color-3','color-4','color-5'];
const width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
const height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
const pieces = [];
for (let i = 0; i < count; i++) {
const piece = document.createElement('div');
const colorClass = colors[Math.floor(Math.random() * colors.length)];
piece.className = `confetti-piece ${colorClass}`;
// Horizontal start between 10% and 90% of width
const startX = Math.round((Math.random() * 80) + 10);
const left = (startX / 100) * width;
piece.style.left = `${left}px`;
// randomized size and rotation
const w = 6 + Math.random() * 12;
const h = 10 + Math.random() * 18;
piece.style.width = `${w}px`;
piece.style.height = `${h}px`;
piece.style.top = `${-20 - Math.random() * 40}px`;
piece.style.transform = `rotate(${Math.random() * 360}deg)`;
// animation timing
const duration = 1800 + Math.random() * 1600;
const delay = Math.random() * 200;
piece.style.animation = `confetti-fall ${duration}ms cubic-bezier(.15,.6,.3,1) ${delay}ms forwards, confetti-sway ${900 + Math.random() * 700}ms ease-in-out ${delay}ms infinite alternate`;
container.appendChild(piece);
pieces.push(piece);
}
// cleanup after longest animation finishes
const maxDuration = 3000;
setTimeout(() => {
pieces.forEach(p => p.remove());
}, maxDuration);
};
/**
* Handles form submission
* @param {Event} e - Submit event
* @param {HTMLFormElement} contactForm - The contact form element
* @param {HTMLElement} contactMessage - The message display element
*/
const sendEmail = (e, contactForm, contactMessage) => {
e.preventDefault();
if (!validateForm(contactForm, contactMessage)) return;
const submitBtn = contactForm.querySelector('.contact__button');
if (!submitBtn) return;
const originalBtnText = submitBtn.innerHTML;
const spinner = createLoadingSpinner();
// Set loading state
submitBtn.disabled = true;
submitBtn.appendChild(spinner);
submitBtn.classList.add('loading');
// Send via EmailJS
emailjs.sendForm(
EMAIL_CONFIG.serviceId,
EMAIL_CONFIG.templateId,
'#contact-form',
EMAIL_CONFIG.publicKey
)
.then(() => {
// Reset button
spinner.remove();
submitBtn.classList.remove('loading');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
// Show success
createSuccessAnimation();
showMessage(contactMessage, '🎉 Message sent successfully!', 'success');
// Reset form
setTimeout(() => {
contactForm.classList.add('form-reset');
setTimeout(() => {
contactForm.reset();
contactForm.classList.remove('form-reset');
}, 300);
}, 500);
})
.catch((error) => {
console.error('Email send failed:', error);
// Reset button
spinner.remove();
submitBtn.classList.remove('loading');
submitBtn.disabled = false;
submitBtn.innerHTML = originalBtnText;
// Show error
showMessage(contactMessage, 'Failed to send message. Please try again later.', 'error');
});
};
/**
* Initialize EmailJS
*/
let isEmailInitialized = false;
const initializeEmail = () => {
// Prevent multiple initializations
if (isEmailInitialized) {
return;
}
const contactForm = document.getElementById('contact-form');
const contactMessage = document.getElementById('contact-message');
if (!contactForm) {
console.warn('Contact form not found - waiting for components to load');
return;
}
// Mark as initialized
isEmailInitialized = true;
// Initialize EmailJS
emailjs.init(EMAIL_CONFIG.publicKey);
// Attach form submit handler
contactForm.addEventListener('submit', (e) => sendEmail(e, contactForm, contactMessage));
// Real-time validation - clear errors on input
contactForm.querySelectorAll('input, textarea').forEach(input => {
input.addEventListener('input', () => {
if (input.value.trim()) {
input.classList.remove('error-field');
}
});
});
console.log('Email form initialized successfully');
};
// Initialize when components are loaded (for dynamically loaded forms)
window.addEventListener('componentsLoaded', initializeEmail);
// Fallback timeout in case componentsLoaded event doesn't fire
setTimeout(() => {
if (!isEmailInitialized) {
initializeEmail();
}
}, 1500);