best shot
This commit is contained in:
@@ -3,6 +3,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initSmoothScroll();
|
||||
initFormValidation();
|
||||
initAnimations();
|
||||
initScrollProgress();
|
||||
initStarryBackground();
|
||||
initCookieConsent();
|
||||
});
|
||||
|
||||
function initThemeToggle() {
|
||||
@@ -26,6 +29,36 @@ function initThemeToggle() {
|
||||
|
||||
function updateIcon(isLight) {
|
||||
icon.className = isLight ? 'fas fa-moon' : 'fas fa-sun';
|
||||
icon.style.animation = 'iconBounce 0.5s ease';
|
||||
setTimeout(() => icon.style.animation = '', 500);
|
||||
}
|
||||
}
|
||||
|
||||
function initScrollProgress() {
|
||||
const progressBar = document.querySelector('.scroll-progress');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
const winScroll = document.body.scrollTop || document.documentElement.scrollTop;
|
||||
const height = document.documentElement.scrollHeight - document.documentElement.clientHeight;
|
||||
const scrolled = (winScroll / height) * 100;
|
||||
progressBar.style.transform = `scaleX(${scrolled / 100})`;
|
||||
});
|
||||
}
|
||||
|
||||
function initStarryBackground() {
|
||||
const starsContainer = document.querySelector('.stars');
|
||||
const numStars = 50;
|
||||
|
||||
// Create stars
|
||||
for (let i = 0; i < numStars; i++) {
|
||||
const star = document.createElement('div');
|
||||
star.className = 'star';
|
||||
star.style.width = Math.random() * 3 + 'px';
|
||||
star.style.height = star.style.width;
|
||||
star.style.left = Math.random() * 100 + '%';
|
||||
star.style.top = Math.random() * 100 + '%';
|
||||
star.style.animationDelay = Math.random() * 2 + 's';
|
||||
starsContainer.appendChild(star);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,35 +85,125 @@ function initFormValidation() {
|
||||
const form = document.querySelector('.contact-form');
|
||||
if (!form) return;
|
||||
|
||||
const nameInput = form.querySelector('#name');
|
||||
const emailInput = form.querySelector('#email');
|
||||
const messageInput = form.querySelector('#message');
|
||||
const inputs = {
|
||||
name: form.querySelector('#name'),
|
||||
email: form.querySelector('#email'),
|
||||
phone: form.querySelector('#phone'),
|
||||
message: form.querySelector('#message')
|
||||
};
|
||||
const submitButton = form.querySelector('.submit-button');
|
||||
const buttonText = submitButton.querySelector('.button-text');
|
||||
const loadingSpinner = submitButton.querySelector('.loading-spinner');
|
||||
const feedbackDiv = form.querySelector('.form-feedback');
|
||||
const messageCounter = form.querySelector('.char-counter');
|
||||
|
||||
const validateInput = (input) => {
|
||||
const isValid = input.checkValidity();
|
||||
input.classList.toggle('invalid', !isValid);
|
||||
return isValid;
|
||||
};
|
||||
// Initialize character counter
|
||||
if (inputs.message && messageCounter) {
|
||||
const maxLength = inputs.message.getAttribute('maxlength');
|
||||
updateCharCounter(inputs.message.value.length, maxLength);
|
||||
|
||||
[nameInput, emailInput, messageInput].forEach(input => {
|
||||
input.addEventListener('input', () => validateInput(input));
|
||||
input.addEventListener('blur', () => validateInput(input));
|
||||
inputs.message.addEventListener('input', (e) => {
|
||||
const length = e.target.value.length;
|
||||
updateCharCounter(length, maxLength);
|
||||
});
|
||||
}
|
||||
|
||||
function updateCharCounter(current, max) {
|
||||
messageCounter.textContent = `${current} / ${max}`;
|
||||
// Add visual feedback when approaching limit
|
||||
if (current >= max * 0.9) {
|
||||
messageCounter.style.color = 'var(--error-red)';
|
||||
} else if (current >= max * 0.8) {
|
||||
messageCounter.style.color = 'var(--warning-yellow)';
|
||||
} else {
|
||||
messageCounter.style.color = 'var(--text-muted)';
|
||||
}
|
||||
}
|
||||
|
||||
// Real-time validation
|
||||
Object.values(inputs).forEach(input => {
|
||||
if (!input) return;
|
||||
|
||||
['input', 'blur'].forEach(eventType => {
|
||||
input.addEventListener(eventType, () => {
|
||||
validateInput(input);
|
||||
updateSubmitButton();
|
||||
});
|
||||
});
|
||||
|
||||
// Add focus effects
|
||||
input.addEventListener('focus', () => {
|
||||
input.closest('.input-wrapper').classList.add('focused');
|
||||
});
|
||||
|
||||
input.addEventListener('blur', () => {
|
||||
input.closest('.input-wrapper').classList.remove('focused');
|
||||
});
|
||||
});
|
||||
|
||||
function validateInput(input) {
|
||||
if (!input.required && input.value === '') {
|
||||
// Optional fields are valid when empty
|
||||
const wrapper = input.closest('.input-wrapper');
|
||||
wrapper.classList.remove('invalid', 'valid');
|
||||
return true;
|
||||
}
|
||||
|
||||
const isValid = input.checkValidity();
|
||||
const wrapper = input.closest('.input-wrapper');
|
||||
|
||||
wrapper.classList.toggle('invalid', !isValid);
|
||||
wrapper.classList.toggle('valid', isValid);
|
||||
|
||||
// Show validation message
|
||||
let errorMessage = '';
|
||||
if (!isValid) {
|
||||
if (input.validity.valueMissing) {
|
||||
errorMessage = 'Dieses Feld ist erforderlich';
|
||||
} else if (input.validity.typeMismatch) {
|
||||
errorMessage = 'Bitte geben Sie ein gültiges Format ein';
|
||||
} else if (input.validity.patternMismatch) {
|
||||
errorMessage = input.title;
|
||||
} else if (input.validity.tooShort) {
|
||||
const minLength = input.getAttribute('minlength');
|
||||
errorMessage = `Mindestens ${minLength} Zeichen erforderlich`;
|
||||
}
|
||||
}
|
||||
|
||||
let errorElement = wrapper.querySelector('.error-message');
|
||||
if (!errorElement && errorMessage) {
|
||||
errorElement = document.createElement('div');
|
||||
errorElement.className = 'error-message';
|
||||
wrapper.appendChild(errorElement);
|
||||
}
|
||||
|
||||
if (errorElement) {
|
||||
if (errorMessage) {
|
||||
errorElement.textContent = errorMessage;
|
||||
errorElement.style.display = 'flex';
|
||||
} else {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
function updateSubmitButton() {
|
||||
const requiredInputs = Object.values(inputs).filter(input => input && input.required);
|
||||
const isFormValid = requiredInputs.every(input => input.checkValidity());
|
||||
submitButton.disabled = !isFormValid;
|
||||
|
||||
// Visual feedback
|
||||
submitButton.classList.toggle('ready', isFormValid);
|
||||
}
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Validate all inputs
|
||||
const isNameValid = validateInput(nameInput);
|
||||
const isEmailValid = validateInput(emailInput);
|
||||
const isMessageValid = validateInput(messageInput);
|
||||
|
||||
if (!isNameValid || !isEmailValid || !isMessageValid) {
|
||||
showFeedback('Bitte füllen Sie alle Felder korrekt aus.', false);
|
||||
const requiredInputs = Object.values(inputs).filter(input => input && input.required);
|
||||
if (!requiredInputs.every(input => validateInput(input))) {
|
||||
showFeedback('Bitte füllen Sie alle Pflichtfelder korrekt aus.', false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,6 +218,22 @@ function initFormValidation() {
|
||||
|
||||
showFeedback('Vielen Dank für Ihre Nachricht! Wir werden uns in Kürze bei Ihnen melden.', true);
|
||||
form.reset();
|
||||
|
||||
// Reset validation states
|
||||
Object.values(inputs).forEach(input => {
|
||||
if (!input) return;
|
||||
const wrapper = input.closest('.input-wrapper');
|
||||
wrapper.classList.remove('valid', 'invalid', 'focused');
|
||||
const errorElement = wrapper.querySelector('.error-message');
|
||||
if (errorElement) {
|
||||
errorElement.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Reset character counter
|
||||
if (messageCounter) {
|
||||
updateCharCounter(0, inputs.message.getAttribute('maxlength'));
|
||||
}
|
||||
} catch (error) {
|
||||
showFeedback('Es ist ein Fehler aufgetreten. Bitte versuchen Sie es später erneut.', false);
|
||||
} finally {
|
||||
@@ -102,16 +241,27 @@ function initFormValidation() {
|
||||
submitButton.disabled = false;
|
||||
buttonText.style.opacity = '1';
|
||||
loadingSpinner.style.display = 'none';
|
||||
submitButton.classList.remove('ready');
|
||||
}
|
||||
});
|
||||
|
||||
function showFeedback(message, isSuccess) {
|
||||
feedbackDiv.textContent = message;
|
||||
feedbackDiv.className = `form-feedback ${isSuccess ? 'success' : 'error'}`;
|
||||
feedbackDiv.style.display = 'block';
|
||||
|
||||
// Scroll feedback into view
|
||||
feedbackDiv.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||
|
||||
// Animate feedback
|
||||
feedbackDiv.style.animation = 'scaleIn 0.3s ease forwards';
|
||||
|
||||
// Auto-hide feedback after 5 seconds
|
||||
setTimeout(() => {
|
||||
feedbackDiv.style.display = 'none';
|
||||
feedbackDiv.style.animation = 'fadeOut 0.3s ease forwards';
|
||||
setTimeout(() => {
|
||||
feedbackDiv.style.display = 'none';
|
||||
}, 300);
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
@@ -126,13 +276,53 @@ function initAnimations() {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
observer.unobserve(entry.target); // Stop observing once animated
|
||||
|
||||
// Add stagger effect to child elements
|
||||
const children = entry.target.querySelectorAll('.animate-stagger');
|
||||
children.forEach((child, index) => {
|
||||
child.style.animationDelay = `${index * 0.1}s`;
|
||||
child.classList.add('visible');
|
||||
});
|
||||
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
}, observerOptions);
|
||||
|
||||
// Observe all service cards and vision items
|
||||
document.querySelectorAll('.service-card, .vision-item').forEach(el => {
|
||||
// Observe elements
|
||||
document.querySelectorAll('.service-card, .vision-item, .fade-in-scroll').forEach(el => {
|
||||
observer.observe(el);
|
||||
});
|
||||
}
|
||||
|
||||
function initCookieConsent() {
|
||||
const cookieConsent = document.getElementById('cookie-consent');
|
||||
const acceptButton = document.getElementById('accept-cookies');
|
||||
const rejectButton = document.getElementById('reject-cookies');
|
||||
|
||||
// Check if user has already made a choice
|
||||
const cookieChoice = localStorage.getItem('cookieChoice');
|
||||
if (!cookieChoice) {
|
||||
setTimeout(() => {
|
||||
cookieConsent.style.display = 'flex';
|
||||
cookieConsent.style.animation = 'slideUp 0.5s ease forwards';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
acceptButton.addEventListener('click', () => {
|
||||
localStorage.setItem('cookieChoice', 'accepted');
|
||||
hideCookieConsent();
|
||||
});
|
||||
|
||||
rejectButton.addEventListener('click', () => {
|
||||
localStorage.setItem('cookieChoice', 'rejected');
|
||||
hideCookieConsent();
|
||||
});
|
||||
|
||||
function hideCookieConsent() {
|
||||
cookieConsent.style.animation = 'slideDown 0.5s ease forwards';
|
||||
setTimeout(() => {
|
||||
cookieConsent.style.display = 'none';
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user