pagev1
This commit is contained in:
430
restaurantcolibri.ca/script.js
Normal file
430
restaurantcolibri.ca/script.js
Normal file
@@ -0,0 +1,430 @@
|
||||
/* ============================================
|
||||
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 });
|
||||
}
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user