pagev1
13
restaurantcolibri.ca/_downloads.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title> Downloads </title>
|
||||
</head>
|
||||
<body>
|
||||
<h1> Downloads </h1>
|
||||
<ul>
|
||||
<li><a id="https://restaurantcolibri.ca/" href="index.html"> Colibri — Bon pour vrai, prêt quand vous l'êtes. </a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
1
restaurantcolibri.ca/_vercel/insights/script.js
Normal file
@@ -0,0 +1 @@
|
||||
"use strict";(()=>{function e(e){let t=location.href;if(e){let n=new URL(t);if(n.pathname!==e)return n.pathname=e,n.search="",n.href}return t}function t(){return!!(navigator.webdriver||navigator.userAgent.includes("Headless"))}var n,a,r,i,o=(r=()=>{},()=>(r&&(i=r(r=0)),i));(n=()=>{o(),function(){var n;let a=e=>e,r=document,i=r.currentScript;if(t())return;let o=null!=(n=null==i?void 0:i.dataset)?n:{},l=d(o,"view"),s=d(o,"event"),u=d(o,"session"),c=null,p=null,f=!0;function d(e,t){return e[`${t}Endpoint`]||("endpoint"in e?`${e.endpoint}/${t}`:`${null!=i&&i.src.includes("/va/")?"/va":"/_vercel/insights"}/${t}`)}async function h(e){if(e&&!Array.isArray(e))return{p:e};let t=r.querySelectorAll("[data-flag-values]");if(!i||!t.length)return;let n=new URL(i.src),a=n.pathname.split("/"),o=a.pop();return o&&a.push("flags",o),n.pathname=a.join("/"),import(n.href).then(n=>n.gather(t,e))}async function v({type:t,data:n,options:i}){var u;let f=e(p),d=r.referrer,v=a({type:t,url:f,payload:n});if(!1===v||null===v)return;v&&(f=v.url,n=null!=(u=v.payload)?u:n);let w=d.includes(location.host),y={o:f,sv:"0.1.3",sdkn:o.sdkn,sdkv:o.sdkv,ts:Date.now(),...c&&{dp:c},...null!=i&&i.withReferrer&&!w?{r:d}:{},..."event"===t&&n&&{en:n.name,ed:n.data},f:await h(null==i?void 0:i.flags).catch(()=>{})};try{await fetch("pageview"===t?l:s,{method:"POST",keepalive:!0,headers:{"Content-Type":"application/json"},body:JSON.stringify(y)})}catch(g){}}async function w(e={}){return v({type:"pageview",options:{withReferrer:e.withReferrer}})}async function y(e,t,n){return v({type:"event",data:{name:e,data:t},options:{withReferrer:!0,flags:null==n?void 0:n.flags}})}async function g(){await fetch(u,{method:"GET",keepalive:!0}).catch(()=>{})}function m(e){return e.pathname===new URL(k).pathname}function R(e){let t=e?"string"==typeof e?new URL(e,location.origin):new URL(e.href):null;!t||m(t)||t.hash&&m(t)||w()}let k=e(),S=()=>{var e;window.va=function(e,t){"beforeSend"===e?a=t:"event"===e?t&&y(t.name,t.data,t.options):"pageview"===e&&t&&(t.route&&(c=t.route),t.path&&(p=t.path),w({withReferrer:f}),f=!1),"enableCookie"===e&&g()},null==(e=window.vaq)||e.forEach(([e,t])=>{window.va(e,t)})};(()=>{if(window.vai||(window.vai=!0,S(),o.disableAutoTrack))return;w({withReferrer:!0});let t=history.pushState.bind(history);history.pushState=function(...n){t(...n);try{R(n[2]),k=e()}catch(a){}},window.addEventListener("popstate",function(){R(e()),k=e()})})()}()},()=>(a||n((a={exports:{}}).exports,a),a.exports))()})();
|
||||
BIN
restaurantcolibri.ca/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
restaurantcolibri.ca/fonts/CenturyOldStyleStd-Bold.woff2.woff2
Normal file
BIN
restaurantcolibri.ca/fonts/TTNorms-Bold.woff2.woff2
Normal file
BIN
restaurantcolibri.ca/fonts/TTNorms-BoldItalic.woff2.woff2
Normal file
BIN
restaurantcolibri.ca/fonts/TTNorms-Medium.woff2.woff2
Normal file
BIN
restaurantcolibri.ca/fonts/TTNorms-Regular.woff2.woff2
Normal file
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Commande en ligne — Restaurant Colibri</title>
|
||||
<meta name="description" content="Commandez en ligne chez Restaurant Colibri. Cuisine fraîche et locale à Québec.">
|
||||
|
||||
<link rel="icon" href="../../favicon.ico" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="../../images/favicon-192.png" sizes="192x192">
|
||||
<link rel="apple-touch-icon" href="../../images/apple-touch-icon.png">
|
||||
|
||||
<link rel="stylesheet" href="../../styles.css">
|
||||
|
||||
<style>.order-page {
|
||||
min-height: 100vh;
|
||||
padding-top: 100px;
|
||||
}
|
||||
.order-page__header {
|
||||
text-align: center;
|
||||
padding: 2rem 1rem 1rem;
|
||||
}
|
||||
.order-page__back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--color-vert);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.order-page__back:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
#ueat-order {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem 4rem;
|
||||
min-height: 600px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Minimal header with back link -->
|
||||
<div class="order-page">
|
||||
<div class="order-page__header">
|
||||
<a href="../../index.html" class="order-page__back">← Retour au site</a>
|
||||
<h1 style="font-family: var(--font-serif); color: var(--color-vert); font-size: 2rem;">Commande en ligne</h1>
|
||||
</div>
|
||||
|
||||
<!-- UEAT Integration -->
|
||||
<div id="ueat-order"></div>
|
||||
</div>
|
||||
|
||||
<script id="ueat-integration" src="https://order.ueat.io/integration/8725227c-800a-4af4-9876-11ab21977bae/fr-CA.js"></script>
|
||||
<script defer src="../../_vercel/insights/script.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
restaurantcolibri.ca/images/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
restaurantcolibri.ca/images/assiette-pancakes.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
restaurantcolibri.ca/images/assiette-poulet-beurre.png
Normal file
|
After Width: | Height: | Size: 820 KiB |
BIN
restaurantcolibri.ca/images/assiette-poulet-cari.png
Normal file
|
After Width: | Height: | Size: 853 KiB |
BIN
restaurantcolibri.ca/images/assiette-poulet-pesto.png
Normal file
|
After Width: | Height: | Size: 896 KiB |
BIN
restaurantcolibri.ca/images/assiette-poulet-zaatar.png
Normal file
|
After Width: | Height: | Size: 830 KiB |
BIN
restaurantcolibri.ca/images/assiette-quinoa-falafel.png
Normal file
|
After Width: | Height: | Size: 893 KiB |
BIN
restaurantcolibri.ca/images/boite-a-lunch.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
restaurantcolibri.ca/images/boule-energie-matcha.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
restaurantcolibri.ca/images/boule-energie.png
Normal file
|
After Width: | Height: | Size: 626 KiB |
BIN
restaurantcolibri.ca/images/brownie.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
restaurantcolibri.ca/images/cafe-filtre.png
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
restaurantcolibri.ca/images/cafelimo-orange.png
Normal file
|
After Width: | Height: | Size: 563 KiB |
BIN
restaurantcolibri.ca/images/cafelimo.png
Normal file
|
After Width: | Height: | Size: 490 KiB |
BIN
restaurantcolibri.ca/images/carlsberg.png
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
restaurantcolibri.ca/images/changua.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
restaurantcolibri.ca/images/chocolat-chaud.png
Normal file
|
After Width: | Height: | Size: 472 KiB |
BIN
restaurantcolibri.ca/images/colibri_logo.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
restaurantcolibri.ca/images/creme-tomate-poivron.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
restaurantcolibri.ca/images/dejeuner-classique-1.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
restaurantcolibri.ca/images/dejeuner-classique-2.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
restaurantcolibri.ca/images/double-espresso.png
Normal file
|
After Width: | Height: | Size: 416 KiB |
BIN
restaurantcolibri.ca/images/equipe-colibri.jpg
Normal file
|
After Width: | Height: | Size: 422 KiB |
BIN
restaurantcolibri.ca/images/falafel-4.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
restaurantcolibri.ca/images/favicon-192.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
restaurantcolibri.ca/images/gldn-melon.png
Normal file
|
After Width: | Height: | Size: 245 KiB |
BIN
restaurantcolibri.ca/images/gldn-pamplemousse.png
Normal file
|
After Width: | Height: | Size: 284 KiB |
BIN
restaurantcolibri.ca/images/hero-chocolat.jpg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
restaurantcolibri.ca/images/hero-pancakes.jpg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
restaurantcolibri.ca/images/hero-salle-pro.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
restaurantcolibri.ca/images/hollandaise.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
restaurantcolibri.ca/images/jus-de-pomme.png
Normal file
|
After Width: | Height: | Size: 347 KiB |
BIN
restaurantcolibri.ca/images/kronenbourg.png
Normal file
|
After Width: | Height: | Size: 472 KiB |
BIN
restaurantcolibri.ca/images/latte.png
Normal file
|
After Width: | Height: | Size: 453 KiB |
BIN
restaurantcolibri.ca/images/limonade.png
Normal file
|
After Width: | Height: | Size: 493 KiB |
BIN
restaurantcolibri.ca/images/loop-morning-glory.png
Normal file
|
After Width: | Height: | Size: 257 KiB |
BIN
restaurantcolibri.ca/images/mate-libre-gingembre.png
Normal file
|
After Width: | Height: | Size: 346 KiB |
BIN
restaurantcolibri.ca/images/mate-libre-original.png
Normal file
|
After Width: | Height: | Size: 355 KiB |
BIN
restaurantcolibri.ca/images/montellier.png
Normal file
|
After Width: | Height: | Size: 484 KiB |
BIN
restaurantcolibri.ca/images/muffin-aux-bleuets.png
Normal file
|
After Width: | Height: | Size: 822 KiB |
|
After Width: | Height: | Size: 749 KiB |
BIN
restaurantcolibri.ca/images/patate-dejeuner.png
Normal file
|
After Width: | Height: | Size: 946 KiB |
BIN
restaurantcolibri.ca/images/patate-douce-rotie.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
restaurantcolibri.ca/images/potage-courge.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
restaurantcolibri.ca/images/pouding-de-chia-format-collation.png
Normal file
|
After Width: | Height: | Size: 902 KiB |
BIN
restaurantcolibri.ca/images/quiche.png
Normal file
|
After Width: | Height: | Size: 954 KiB |
BIN
restaurantcolibri.ca/images/rabaska-cidre.png
Normal file
|
After Width: | Height: | Size: 310 KiB |
BIN
restaurantcolibri.ca/images/rooibos.png
Normal file
|
After Width: | Height: | Size: 725 KiB |
BIN
restaurantcolibri.ca/images/saison-libre.png
Normal file
|
After Width: | Height: | Size: 459 KiB |
BIN
restaurantcolibri.ca/images/salade-buffalo-bleu.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
restaurantcolibri.ca/images/salade-cobb.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
restaurantcolibri.ca/images/salade-de-chou.png
Normal file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
restaurantcolibri.ca/images/salade-falafel-chevre.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
restaurantcolibri.ca/images/salade-kale-cesar.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
restaurantcolibri.ca/images/salade-nicoise.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
restaurantcolibri.ca/images/salade-pomme-ranch.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
restaurantcolibri.ca/images/salade-teriyaki.png
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
BIN
restaurantcolibri.ca/images/salade-tex-mex.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
restaurantcolibri.ca/images/sandwich-bacon.png
Normal file
|
After Width: | Height: | Size: 776 KiB |
BIN
restaurantcolibri.ca/images/sandwich-blt.png
Normal file
|
After Width: | Height: | Size: 834 KiB |
BIN
restaurantcolibri.ca/images/sandwich-ranch.png
Normal file
|
After Width: | Height: | Size: 766 KiB |
BIN
restaurantcolibri.ca/images/sandwich-tofu.png
Normal file
|
After Width: | Height: | Size: 927 KiB |
BIN
restaurantcolibri.ca/images/sencha.png
Normal file
|
After Width: | Height: | Size: 648 KiB |
BIN
restaurantcolibri.ca/images/shakshuka.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
restaurantcolibri.ca/images/sleeman.png
Normal file
|
After Width: | Height: | Size: 420 KiB |
BIN
restaurantcolibri.ca/images/smoothie-fraise-banane.png
Normal file
|
After Width: | Height: | Size: 364 KiB |
BIN
restaurantcolibri.ca/images/smoothie-framboise-bleuet.png
Normal file
|
After Width: | Height: | Size: 398 KiB |
BIN
restaurantcolibri.ca/images/smoothie-mangue-passion.png
Normal file
|
After Width: | Height: | Size: 360 KiB |
BIN
restaurantcolibri.ca/images/the-chai.png
Normal file
|
After Width: | Height: | Size: 631 KiB |
BIN
restaurantcolibri.ca/images/the-earl-grey.png
Normal file
|
After Width: | Height: | Size: 572 KiB |
BIN
restaurantcolibri.ca/images/tisane-menthe.png
Normal file
|
After Width: | Height: | Size: 581 KiB |
BIN
restaurantcolibri.ca/images/toast-avocat-oeuf.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
restaurantcolibri.ca/images/toast-avocat-tofu.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
restaurantcolibri.ca/images/traiteur-service.jpg
Normal file
|
After Width: | Height: | Size: 833 KiB |
BIN
restaurantcolibri.ca/images/wrap-cesar.png
Normal file
|
After Width: | Height: | Size: 724 KiB |
BIN
restaurantcolibri.ca/images/wrap-club.png
Normal file
|
After Width: | Height: | Size: 775 KiB |
BIN
restaurantcolibri.ca/images/wrap-falafel.png
Normal file
|
After Width: | Height: | Size: 792 KiB |
BIN
restaurantcolibri.ca/images/wrap-oeufs.png
Normal file
|
After Width: | Height: | Size: 771 KiB |
BIN
restaurantcolibri.ca/images/wrap-tex-mex.png
Normal file
|
After Width: | Height: | Size: 724 KiB |
BIN
restaurantcolibri.ca/images/wrap-thon.png
Normal file
|
After Width: | Height: | Size: 768 KiB |
1502
restaurantcolibri.ca/index.html
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 });
|
||||
}
|
||||
|
||||
});
|
||||