generated from knso/webklar-preview-template
431 lines
13 KiB
JavaScript
431 lines
13 KiB
JavaScript
/* ============================================
|
|
RESTAURANT COLIBRI — JavaScript
|
|
============================================ */
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
// ===== NAVIGATION — Scroll Effect =====
|
|
const nav = document.getElementById('nav');
|
|
|
|
const handleNavScroll = () => {
|
|
if (window.scrollY > 50) {
|
|
nav.classList.add('nav--scrolled');
|
|
} else {
|
|
nav.classList.remove('nav--scrolled');
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', handleNavScroll, { passive: true });
|
|
handleNavScroll();
|
|
|
|
// ===== HERO CAROUSEL =====
|
|
const heroSlides = document.querySelectorAll('.hero__slide');
|
|
const heroDots = document.querySelectorAll('.hero__dot');
|
|
const heroPrev = document.getElementById('heroPrev');
|
|
const heroNext = document.getElementById('heroNext');
|
|
let currentSlide = 0;
|
|
let heroInterval = null;
|
|
|
|
const goToSlide = (index) => {
|
|
heroSlides[currentSlide].classList.remove('active');
|
|
heroDots[currentSlide].classList.remove('active');
|
|
currentSlide = (index + heroSlides.length) % heroSlides.length;
|
|
heroSlides[currentSlide].classList.add('active');
|
|
heroDots[currentSlide].classList.add('active');
|
|
};
|
|
|
|
const startHeroAutoplay = () => {
|
|
heroInterval = setInterval(() => goToSlide(currentSlide + 1), 6000);
|
|
};
|
|
|
|
const resetHeroAutoplay = () => {
|
|
clearInterval(heroInterval);
|
|
startHeroAutoplay();
|
|
};
|
|
|
|
if (heroSlides.length > 1) {
|
|
heroPrev.addEventListener('click', () => {
|
|
goToSlide(currentSlide - 1);
|
|
resetHeroAutoplay();
|
|
});
|
|
|
|
heroNext.addEventListener('click', () => {
|
|
goToSlide(currentSlide + 1);
|
|
resetHeroAutoplay();
|
|
});
|
|
|
|
heroDots.forEach(dot => {
|
|
dot.addEventListener('click', () => {
|
|
goToSlide(parseInt(dot.dataset.slide));
|
|
resetHeroAutoplay();
|
|
});
|
|
});
|
|
|
|
startHeroAutoplay();
|
|
|
|
// Touch swipe support
|
|
const heroEl = document.querySelector('.hero');
|
|
let touchStartX = 0;
|
|
let touchEndX = 0;
|
|
|
|
heroEl.addEventListener('touchstart', (e) => {
|
|
touchStartX = e.changedTouches[0].screenX;
|
|
}, { passive: true });
|
|
|
|
heroEl.addEventListener('touchend', (e) => {
|
|
touchEndX = e.changedTouches[0].screenX;
|
|
const diff = touchStartX - touchEndX;
|
|
if (Math.abs(diff) > 50) {
|
|
if (diff > 0) {
|
|
goToSlide(currentSlide + 1);
|
|
} else {
|
|
goToSlide(currentSlide - 1);
|
|
}
|
|
resetHeroAutoplay();
|
|
}
|
|
}, { passive: true });
|
|
}
|
|
|
|
// ===== MOBILE MENU =====
|
|
const burger = document.getElementById('navBurger');
|
|
const mobileMenu = document.getElementById('mobileMenu');
|
|
const mobileLinks = document.querySelectorAll('.mobile-menu__link, .mobile-menu__cta');
|
|
|
|
if (burger && mobileMenu) {
|
|
burger.addEventListener('click', () => {
|
|
burger.classList.toggle('active');
|
|
mobileMenu.classList.toggle('active');
|
|
document.body.style.overflow = mobileMenu.classList.contains('active') ? 'hidden' : '';
|
|
});
|
|
|
|
mobileLinks.forEach(link => {
|
|
link.addEventListener('click', () => {
|
|
burger.classList.remove('active');
|
|
mobileMenu.classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
});
|
|
});
|
|
}
|
|
|
|
// ===== MENU TABS (with ARIA) =====
|
|
const tabs = document.querySelectorAll('.menu__tab');
|
|
const contents = document.querySelectorAll('.menu__content');
|
|
|
|
// Set ARIA roles
|
|
const tabList = document.querySelector('.menu__tabs');
|
|
if (tabList) tabList.setAttribute('role', 'tablist');
|
|
|
|
tabs.forEach((tab, i) => {
|
|
const target = tab.dataset.tab;
|
|
tab.setAttribute('role', 'tab');
|
|
tab.setAttribute('aria-controls', `tab-${target}`);
|
|
tab.setAttribute('aria-selected', tab.classList.contains('active') ? 'true' : 'false');
|
|
tab.id = `tab-btn-${target}`;
|
|
});
|
|
|
|
contents.forEach(content => {
|
|
content.setAttribute('role', 'tabpanel');
|
|
const id = content.id.replace('tab-', '');
|
|
content.setAttribute('aria-labelledby', `tab-btn-${id}`);
|
|
});
|
|
|
|
tabs.forEach(tab => {
|
|
tab.addEventListener('click', () => {
|
|
const target = tab.dataset.tab;
|
|
|
|
// Update active tab + ARIA
|
|
tabs.forEach(t => {
|
|
t.classList.remove('active');
|
|
t.setAttribute('aria-selected', 'false');
|
|
});
|
|
tab.classList.add('active');
|
|
tab.setAttribute('aria-selected', 'true');
|
|
|
|
// Show target content
|
|
contents.forEach(content => {
|
|
content.classList.remove('active');
|
|
content.classList.remove('scrolled-end');
|
|
if (content.id === `tab-${target}`) {
|
|
content.classList.add('active');
|
|
// Reset scroll position & check scroll indicator
|
|
const grid = content.querySelector('.menu__grid');
|
|
if (grid) {
|
|
grid.scrollLeft = 0;
|
|
checkScrollEnd(content, grid);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
// ===== SCROLL INDICATOR — fade gradient at end =====
|
|
const checkScrollEnd = (contentEl, gridEl) => {
|
|
const atEnd = gridEl.scrollLeft + gridEl.clientWidth >= gridEl.scrollWidth - 10;
|
|
if (atEnd) {
|
|
contentEl.classList.add('scrolled-end');
|
|
} else {
|
|
contentEl.classList.remove('scrolled-end');
|
|
}
|
|
};
|
|
|
|
document.querySelectorAll('.menu__grid').forEach(grid => {
|
|
grid.addEventListener('scroll', () => {
|
|
const contentEl = grid.closest('.menu__content');
|
|
if (contentEl) checkScrollEnd(contentEl, grid);
|
|
}, { passive: true });
|
|
});
|
|
|
|
// Initial check for active tab
|
|
const activeContent = document.querySelector('.menu__content.active');
|
|
if (activeContent) {
|
|
const activeGrid = activeContent.querySelector('.menu__grid');
|
|
if (activeGrid) checkScrollEnd(activeContent, activeGrid);
|
|
}
|
|
|
|
// ===== DIETARY FILTERS (multi-select) =====
|
|
const filterBtns = document.querySelectorAll('.menu__filter');
|
|
const filterReset = document.getElementById('filterReset');
|
|
const allCards = document.querySelectorAll('.menu-card');
|
|
|
|
const applyFilters = () => {
|
|
// Gather all active filters
|
|
const activeFilters = [];
|
|
filterBtns.forEach(b => {
|
|
if (b.classList.contains('active')) activeFilters.push(b.dataset.filter);
|
|
});
|
|
|
|
// Show/hide reset button
|
|
if (filterReset) {
|
|
filterReset.classList.toggle('visible', activeFilters.length > 0);
|
|
}
|
|
|
|
// No filters active = show everything
|
|
if (activeFilters.length === 0) {
|
|
allCards.forEach(card => card.classList.remove('filtered-out'));
|
|
} else {
|
|
allCards.forEach(card => {
|
|
const diet = (card.dataset.diet || '').split(' ').filter(Boolean);
|
|
const hasNuts = card.dataset.noix === 'true';
|
|
|
|
// Card must match ALL active filters (AND logic)
|
|
const pass = activeFilters.every(f => {
|
|
if (f === 'sg') return diet.includes('sg');
|
|
if (f === 'v') return diet.includes('v') || diet.includes('vg');
|
|
if (f === 'vg') return diet.includes('vg');
|
|
if (f === 'noix') return !hasNuts;
|
|
return true;
|
|
});
|
|
|
|
card.classList.toggle('filtered-out', !pass);
|
|
});
|
|
}
|
|
|
|
// Empty-state per tab panel
|
|
document.querySelectorAll('.menu__content').forEach(panel => {
|
|
const grids = panel.querySelectorAll('.menu__grid');
|
|
let allHidden = true;
|
|
grids.forEach(grid => {
|
|
if (grid.querySelectorAll('.menu-card:not(.filtered-out)').length > 0) allHidden = false;
|
|
});
|
|
|
|
let emptyMsg = panel.querySelector('.menu__empty-msg');
|
|
if (allHidden && activeFilters.length > 0) {
|
|
if (!emptyMsg) {
|
|
emptyMsg = document.createElement('p');
|
|
emptyMsg.className = 'menu__empty-msg visible';
|
|
emptyMsg.textContent = 'Aucun item ne correspond à ces filtres dans cette catégorie.';
|
|
panel.appendChild(emptyMsg);
|
|
} else {
|
|
emptyMsg.classList.add('visible');
|
|
}
|
|
} else if (emptyMsg) {
|
|
emptyMsg.classList.remove('visible');
|
|
}
|
|
});
|
|
};
|
|
|
|
// Toggle individual filter chips
|
|
filterBtns.forEach(btn => {
|
|
btn.addEventListener('click', () => {
|
|
btn.classList.toggle('active');
|
|
applyFilters();
|
|
});
|
|
});
|
|
|
|
// Reset all filters
|
|
if (filterReset) {
|
|
filterReset.addEventListener('click', () => {
|
|
filterBtns.forEach(b => b.classList.remove('active'));
|
|
applyFilters();
|
|
});
|
|
}
|
|
|
|
// ===== SMOOTH SCROLL for anchor links =====
|
|
document.querySelectorAll('a[href^="#"]').forEach(link => {
|
|
link.addEventListener('click', (e) => {
|
|
const targetId = link.getAttribute('href');
|
|
if (targetId === '#') return;
|
|
|
|
const target = document.querySelector(targetId);
|
|
if (target) {
|
|
e.preventDefault();
|
|
const navHeight = window.innerWidth < 768 ? 64 : 80;
|
|
const top = target.getBoundingClientRect().top + window.scrollY - navHeight;
|
|
|
|
window.scrollTo({
|
|
top,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// ===== SCROLL REVEAL ANIMATION =====
|
|
const revealElements = document.querySelectorAll(
|
|
'.section__header, .menu-card, .value-card, ' +
|
|
'.traiteur__visual, .traiteur__content, ' +
|
|
'.contact__info, .contact__form, .traiteur__feature'
|
|
);
|
|
|
|
revealElements.forEach(el => {
|
|
el.classList.add('reveal');
|
|
});
|
|
|
|
const revealOnScroll = () => {
|
|
const windowHeight = window.innerHeight;
|
|
|
|
document.querySelectorAll('.reveal').forEach(el => {
|
|
const elementTop = el.getBoundingClientRect().top;
|
|
const revealPoint = windowHeight - 80;
|
|
|
|
if (elementTop < revealPoint) {
|
|
el.classList.add('visible');
|
|
}
|
|
});
|
|
};
|
|
|
|
window.addEventListener('scroll', revealOnScroll, { passive: true });
|
|
revealOnScroll(); // trigger on load
|
|
|
|
// ===== ACTIVE NAV LINK on scroll =====
|
|
const sections = document.querySelectorAll('section[id]');
|
|
const navLinks = document.querySelectorAll('.nav__link');
|
|
|
|
const highlightNav = () => {
|
|
const scrollY = window.scrollY + 100;
|
|
|
|
sections.forEach(section => {
|
|
const top = section.offsetTop - 100;
|
|
const height = section.offsetHeight;
|
|
const id = section.getAttribute('id');
|
|
|
|
if (scrollY >= top && scrollY < top + height) {
|
|
navLinks.forEach(link => {
|
|
link.classList.remove('active');
|
|
if (link.getAttribute('href') === `#${id}`) {
|
|
link.classList.add('active');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
window.addEventListener('scroll', highlightNav, { passive: true });
|
|
|
|
// ===== CONTACT FORM — FormSubmit.co (AJAX) =====
|
|
const contactForm = document.getElementById('contactForm');
|
|
if (contactForm) {
|
|
const btn = contactForm.querySelector('button[type="submit"]');
|
|
const btnOriginalText = btn.textContent;
|
|
|
|
contactForm.addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
btn.textContent = 'Envoi en cours...';
|
|
btn.disabled = true;
|
|
|
|
const formData = new FormData(contactForm);
|
|
|
|
fetch(contactForm.action, {
|
|
method: 'POST',
|
|
body: formData,
|
|
headers: { 'Accept': 'application/json' }
|
|
})
|
|
.then(response => {
|
|
if (response.ok) {
|
|
// Success
|
|
contactForm.reset();
|
|
btn.textContent = 'Message envoyé ✓';
|
|
btn.style.background = 'var(--color-vert)';
|
|
setTimeout(() => {
|
|
btn.textContent = btnOriginalText;
|
|
btn.disabled = false;
|
|
btn.style.background = '';
|
|
}, 4000);
|
|
} else {
|
|
throw new Error('Erreur serveur');
|
|
}
|
|
})
|
|
.catch(() => {
|
|
btn.textContent = 'Erreur — Réessayer';
|
|
btn.style.background = '#c0392b';
|
|
btn.disabled = false;
|
|
setTimeout(() => {
|
|
btn.textContent = btnOriginalText;
|
|
btn.style.background = '';
|
|
}, 4000);
|
|
});
|
|
});
|
|
}
|
|
|
|
// ===== MOBILE STICKY CTA =====
|
|
const mobileCta = document.getElementById('mobileCta');
|
|
if (mobileCta) {
|
|
const heroSection = document.getElementById('hero');
|
|
const footerEl = document.querySelector('.footer');
|
|
|
|
const toggleMobileCta = () => {
|
|
if (window.innerWidth > 768) {
|
|
mobileCta.classList.remove('visible');
|
|
return;
|
|
}
|
|
|
|
const heroBottom = heroSection ? heroSection.offsetTop + heroSection.offsetHeight : 600;
|
|
const footerTop = footerEl ? footerEl.offsetTop : Infinity;
|
|
const scrollBottom = window.scrollY + window.innerHeight;
|
|
|
|
// Hide when a section CTA button is visible on screen
|
|
const sectionBtns = document.querySelectorAll('.menu__footer .btn, .contact__form .btn');
|
|
let sectionCtaVisible = false;
|
|
sectionBtns.forEach(btn => {
|
|
const rect = btn.getBoundingClientRect();
|
|
if (rect.top < window.innerHeight && rect.bottom > 0) {
|
|
sectionCtaVisible = true;
|
|
}
|
|
});
|
|
|
|
if (window.scrollY > heroBottom && scrollBottom < footerTop + 40 && !sectionCtaVisible) {
|
|
mobileCta.classList.add('visible');
|
|
} else {
|
|
mobileCta.classList.remove('visible');
|
|
}
|
|
};
|
|
|
|
window.addEventListener('scroll', toggleMobileCta, { passive: true });
|
|
toggleMobileCta();
|
|
}
|
|
|
|
// ===== PARALLAX subtle on hero =====
|
|
const heroContent = document.querySelector('.hero__content');
|
|
|
|
if (heroContent && window.innerWidth > 768) {
|
|
window.addEventListener('scroll', () => {
|
|
const scrollY = window.scrollY;
|
|
if (scrollY < window.innerHeight) {
|
|
heroContent.style.transform = `translateY(${scrollY * 0.15}px)`;
|
|
heroContent.style.opacity = 1 - (scrollY / (window.innerHeight * 0.8));
|
|
}
|
|
}, { passive: true });
|
|
}
|
|
|
|
});
|