Catalogue des offres photo

Portrait, mode, produits, événements, mariages et entreprise — trouvez l’offre parfaite.

Votre panier

Ajustez les quantités et validez.

Sous-total €0,00

En validant, vous acceptez nos conditions et la politique de confidentialité.

Favoris

Vos offres enregistrées.

`; } function fallbackFooterHTML(){ return ` `; } function setCounters(){ $('#favCount').textContent = favs.size; const count = cart.reduce((a,b)=>a+(b.qty||1),0); $('#cartCount').textContent = count; } function buildCategoryCheckboxes(){ const categories = ["Portrait","Mode","Produit","Événement","Mariage","Entreprise"]; const wrap = $('#catsWrap'); wrap.innerHTML = ''; categories.forEach(cat=>{ const id = 'cat_'+cat; const lbl = document.createElement('label'); lbl.className = "flex items-center gap-2 px-3 py-2 rounded-xl border border-neutral-200 dark:border-neutral-700 cursor-pointer hover:bg-neutral-50 dark:hover:bg-neutral-800"; lbl.innerHTML = `${cat}`; wrap.appendChild(lbl); }); } function chip(label, onX){ const el = document.createElement('span'); el.className = "inline-flex items-center gap-2 px-3 py-1.5 rounded-full bg-neutral-100 dark:bg-neutral-800 text-sm"; el.innerHTML = `${label}`; el.querySelector('button').addEventListener('click', onX); return el; } function updateActiveFilters(){ const af = $('#activeFilters'); af.innerHTML = ''; if(state.search){ af.appendChild(chip(`Recherche: ${state.search}`, ()=>{ $('#searchInput').value=''; state.search=''; applyFilters(true); })); } if(state.categories.size){ state.categories.forEach(c=>af.appendChild(chip(c, ()=>{ toggleCategory(c,false); }))); } if(state.tags.size){ state.tags.forEach(t=>af.appendChild(chip(`#${t}`, ()=>{ toggleTag(t,false); }))); } if(state.price.min>state.priceBounds.min || state.price.max{ setPriceBounds(state.priceBounds.min, state.priceBounds.max, true); })); } if(state.duration.min>state.durationBounds.min || state.duration.max{ setDurationBounds(state.durationBounds.min, state.durationBounds.max, true); })); } if(state.availability){ af.appendChild(chip(`Disponible`, ()=>{ $('#availability').checked=false; state.availability=false; applyFilters(true); })); } if(state.ratingMin>0){ af.appendChild(chip(`${state.ratingMin}+ étoiles`, ()=>{ $('#ratingMin').value='0'; state.ratingMin=0; applyFilters(true); })); } } function toggleCategory(cat, rerender=true){ if(state.categories.has(cat)) state.categories.delete(cat); else state.categories.add(cat); const boxes = $$('#catsWrap input[type=checkbox]'); boxes.forEach(b=>{ if(b.value===cat) b.checked = state.categories.has(cat); }); if(rerender) applyFilters(true); } function toggleTag(tag, rerender=true){ if(state.tags.has(tag)) state.tags.delete(tag); else state.tags.add(tag); const btns = $$('#tagsWrap button[data-tag]'); btns.forEach(b=>{ if(b.dataset.tag===tag) b.classList.toggle('bg-primary/10', state.tags.has(tag)); b.classList.toggle('border-primary', state.tags.has(tag)); }); if(rerender) applyFilters(true); } function setPriceBounds(min, max, rerender){ state.price.min = Math.max(state.priceBounds.min, Number(min)||state.priceBounds.min); state.price.max = Math.min(state.priceBounds.max, Number(max)||state.priceBounds.max); $('#priceMin').value = state.price.min; $('#priceMax').value = state.price.max; if(rerender) applyFilters(true); } function setDurationBounds(min, max, rerender){ state.duration.min = Math.max(state.durationBounds.min, Number(min)||state.durationBounds.min); state.duration.max = Math.min(state.durationBounds.max, Number(max)||state.durationBounds.max); $('#durMin').value = state.duration.min; $('#durMax').value = state.duration.max; if(rerender) applyFilters(true); } function buildTagsCloud(){ const wrap = $('#tagsWrap'); wrap.innerHTML = ''; const freq = {}; state.data.forEach(it => (it.tags||[]).forEach(t => freq[t] = (freq[t]||0)+1)); const tags = Object.entries(freq).sort((a,b)=>b[1]-a[1]).slice(0,24).map(t=>t[0]); tags.forEach(t=>{ const b = document.createElement('button'); b.type='button'; b.dataset.tag = t; b.className = "px-3 py-1.5 rounded-full border border-neutral-200 dark:border-neutral-700 text-sm hover:border-primary"; b.innerText = '#'+t; b.addEventListener('click', ()=>toggleTag(t,true)); wrap.appendChild(b); }); } function createStars(rating){ const r = Math.round(rating*2)/2; const out = []; for(let i=1;i<=5;i++){ let path='M12 .587l3.668 7.431 8.2 1.192-5.934 5.786 1.401 8.164L12 18.896l-7.335 3.864 1.401-8.164L.132 9.21l8.2-1.192L12 .587z'; const fill = i<=Math.floor(r) ? 'currentColor' : (i-0.5===r ? 'url(#halfGrad)' : 'none'); const stroke = i<=Math.floor(r) ? 'currentColor' : '#9CA3AF'; out.push(``); } return out.join(''); } function renderGrid(){ const grid = $('#grid'); grid.innerHTML = ''; if(!state.filtered.length){ $('#emptyState').classList.remove('hidden'); $('#pagination').innerHTML=''; return; } else { $('#emptyState').classList.add('hidden'); } const start = (state.page-1)*state.perPage; const end = start + state.perPage; const pageItems = state.filtered.slice(start, end); pageItems.forEach(item=>{ const card = document.createElement('article'); card.className = "rounded-2xl border border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900 overflow-hidden p2aql"; const imgSrc = item.thumbnail || (item.images&&item.images[0]) || "./images/placeholder_silky_gradient_soft_lighting_camera_lens_blur_minimalist_background.jpg"; card.innerHTML = `
${item.title}
${item.category}

${item.title}

${createStars(item.rating||0)}
${item.reviewsCount||0} avis
${fmtPrice(item.price||0)}
${item.durationMinutes||60} min
${(item.tags||[]).slice(0,3).map(t=>`#${t}`).join('')}
`; grid.appendChild(card); }); grid.addEventListener('click', gridClickHandler); renderPagination(); } function gridClickHandler(e){ const favBtn = e.target.closest('button[data-fav]'); const openBtn = e.target.closest('button[data-open]'); const addBtn = e.target.closest('button[data-add]'); if(favBtn){ const id = favBtn.dataset.fav; toggleFav(id); const svg = favBtn.querySelector('svg'); svg.classList.toggle('text-primary', favs.has(id)); } if(openBtn){ openModal(openBtn.dataset.open); } if(addBtn){ addToCart(addBtn.dataset.add, 1, []); showToast('Ajouté au panier'); } } function renderPagination(){ const total = state.filtered.length; const pages = Math.max(1, Math.ceil(total / state.perPage)); if(state.page>pages) state.page = pages; const pag = $('#pagination'); const makeBtn = (label, page, disabled=false, active=false) => { const b = document.createElement('button'); b.className = `px-3 py-2 rounded-xl border ${active?'bg-primary text-white border-primary':'border-neutral-200 dark:border-neutral-800 hover:bg-neutral-100 dark:hover:bg-neutral-800'}`; b.textContent = label; b.disabled = disabled; b.dataset.page = page; return b; }; pag.innerHTML = ''; const left = document.createElement('div'); left.className="flex items-center gap-2"; const right = document.createElement('div'); right.className="flex items-center gap-2"; left.appendChild(makeBtn('Précédent', state.page-1, state.page===1)); const windowSize = 5; let start = Math.max(1, state.page - Math.floor(windowSize/2)); let end = Math.min(pages, start + windowSize - 1); start = Math.max(1, end - windowSize + 1); for(let p=start; p<=end; p++){ left.appendChild(makeBtn(String(p), p, false, p===state.page)); } left.appendChild(makeBtn('Suivant', state.page+1, state.page===pages)); const info = document.createElement('div'); const a = (state.page-1)*state.perPage+1; const b = Math.min(state.page*state.perPage, total); info.className="text-sm text-neutral-600 dark:text-neutral-300"; info.textContent = `${a}–${b} sur ${total} résultats`; right.appendChild(info); pag.appendChild(left); pag.appendChild(right); pag.onclick = (e)=>{ const btn = e.target.closest('button[data-page]'); if(!btn) return; const p = Number(btn.dataset.page); const totalPages = Math.max(1, Math.ceil(state.filtered.length/state.perPage)); if(p<1 || p>totalPages) return; state.page = p; updateURL(); renderGrid(); window.scrollTo({top:0, behavior:'smooth'}); } } function applyFilters(resetPage=false){ const q = state.search.trim().toLowerCase(); const cats = state.categories; const tags = state.tags; let arr = state.data.filter(item=>{ const meetsQ = !q || [item.title, item.description, item.details, item.category, item.slug, ...(item.tags||[])].filter(Boolean).join(' ').toLowerCase().includes(q); const meetsCat = cats.size===0 || cats.has(item.category); const meetsTag = tags.size===0 || (item.tags||[]).some(t=>tags.has(t)); const price = item.price||0; const dur = item.durationMinutes||0; const meetsPrice = price>=state.price.min && price<=state.price.max; const meetsDur = dur>=state.duration.min && dur<=state.duration.max; const meetsAvail = !state.availability || !!item.availability; const meetsRating = (item.rating||0) >= state.ratingMin; return meetsQ && meetsCat && meetsTag && meetsPrice && meetsDur && meetsAvail && meetsRating; }); switch(state.sort){ case 'price-asc': arr.sort((a,b)=> (a.price||0)-(b.price||0)); break; case 'price-desc': arr.sort((a,b)=> (b.price||0)-(a.price||0)); break; case 'rating-desc': arr.sort((a,b)=> (b.rating||0)-(a.rating||0)); break; case 'popular': arr.sort((a,b)=> (b.reviewsCount||0)-(a.reviewsCount||0)); break; case 'duration-asc': arr.sort((a,b)=> (a.durationMinutes||0)-(b.durationMinutes||0)); break; default: arr.sort((a,b)=> new Date(b.createdAt)-new Date(a.createdAt)); } state.filtered = arr; if(resetPage) state.page = 1; updateURL(); updateActiveFilters(); renderGrid(); } function updateURL(){ const params = new URLSearchParams(); if(state.search) params.set('q', state.search); if(state.categories.size) params.set('cat', Array.from(state.categories).join(',')); if(state.tags.size) params.set('tags', Array.from(state.tags).join(',')); if(state.price.min>state.priceBounds.min) params.set('pmin', state.price.min); if(state.price.maxstate.durationBounds.min) params.set('dmin', state.duration.min); if(state.duration.max0) params.set('r', state.ratingMin); if(state.sort!=='recent') params.set('sort', state.sort); if(state.perPage!==12) params.set('pp', state.perPage); if(state.page>1) params.set('page', state.page); const newUrl = `${location.pathname}?${params.toString()}`; history.replaceState(null, '', newUrl); } function parseURL(){ const p = new URLSearchParams(location.search); state.search = p.get('q')||''; if($('#searchInput')) $('#searchInput').value = state.search; const cats = (p.get('cat')||'').split(',').filter(Boolean); state.categories = new Set(cats); const tags = (p.get('tags')||'').split(',').filter(Boolean); state.tags = new Set(tags); if(p.has('pmin')) state.price.min = Number(p.get('pmin')); if(p.has('pmax')) state.price.max = Number(p.get('pmax')); if(p.has('dmin')) state.duration.min = Number(p.get('dmin')); if(p.has('dmax')) state.duration.max = Number(p.get('dmax')); state.availability = p.get('avail')==='1'; state.ratingMin = Number(p.get('r')||0); state.sort = p.get('sort')||'recent'; state.perPage = Number(p.get('pp')||12); state.page = Number(p.get('page')||1); } function syncControls(){ // Categories $$('#catsWrap input[type=checkbox]').forEach(b=>b.checked = state.categories.has(b.value)); // Tags $$('#tagsWrap button[data-tag]').forEach(b=>{ const active = state.tags.has(b.dataset.tag); b.classList.toggle('bg-primary/10', active); b.classList.toggle('border-primary', active); }); // Others $('#searchInput').value = state.search||''; $('#priceMin').value = state.price.min; $('#priceMax').value = state.price.max; $('#durMin').value = state.duration.min; $('#durMax').value = state.duration.max; $('#availability').checked = state.availability; $('#ratingMin').value = String(state.ratingMin); $('#sortSelect').value = state.sort; $('#perPage').value = String(state.perPage); } function toggleFav(id){ if(favs.has(id)){ favs.delete(id); showToast('Retiré des favoris'); } else { favs.add(id); showToast('Ajouté aux favoris'); } localStorage.setItem('lc_favs', JSON.stringify(Array.from(favs))); setCounters(); renderFavs(); } function addToCart(id, qty=1, options=[]){ const found = cart.find(i=>i.id===id && JSON.stringify(i.options||[])===JSON.stringify(options||[])); if(found) found.qty += qty; else cart.push({id, qty, options}); localStorage.setItem('lc_cart', JSON.stringify(cart)); setCounters(); renderCart(); } function removeFromCart(idx){ cart.splice(idx,1); localStorage.setItem('lc_cart', JSON.stringify(cart)); setCounters(); renderCart(); } function showToast(msg){ const h = $('#toastHost'); const t = document.createElement('div'); t.className = "px-4 py-3 rounded-xl bg-neutral-900 text-white shadow-lg m2t9e"; t.textContent = msg; h.appendChild(t); setTimeout(()=>{ t.style.opacity='0'; t.style.transform='translateY(6px)'; t.style.transition='all .2s'; setTimeout(()=>t.remove(), 200); }, 2200); } function openModal(id){ const item = state.data.find(i=>i.id===id); if(!item) return; const overlay = $('#modalOverlay'); const modal = $('#itemModal'); overlay.classList.remove('hidden'); modal.classList.remove('hidden'); modal.classList.add('flex'); $('#modalTitle').textContent = item.title; $('#modalMeta').innerHTML = `${item.category}${item.durationMinutes||60} min${createStars(item.rating||0)} (${item.reviewsCount||0})`; $('#modalPrice').textContent = fmtPrice(item.price||0); $('#modalAvailability').innerHTML = item.availability? `Disponible immédiatement` : `Sur demande`; $('#modalDesc').textContent = item.description||''; $('#modalDetails').innerHTML = (item.details||'').replace(/\n/g,'
'); const imgs = (item.images&&item.images.length?item.images:[item.thumbnail]).filter(Boolean); state.imagesById[item.id] = imgs.length?imgs:["./images/placeholder_silky_gradient_soft_lighting_camera_lens_blur_minimalist_background.jpg"]; state.thumbsIndexById[item.id] = 0; $('#modalImage').src = state.imagesById[item.id][0]; renderModalThumbs(item.id); // Options const optWrap = $('#modalOptionsWrap'); optWrap.innerHTML = ''; if(item.options && item.options.length){ const title = document.createElement('h4'); title.className = "font-semibold mb-2"; title.textContent = "Options"; optWrap.appendChild(title); const list = document.createElement('div'); list.className = "flex flex-wrap gap-2"; item.options.forEach(opt=>{ const idOpt = 'opt_'+Math.random().toString(36).slice(2,7); const label = document.createElement('label'); label.className = "px-3 py-1.5 rounded-full border border-neutral-300 dark:border-neutral-700 cursor-pointer"; label.innerHTML = `${opt}`; list.appendChild(label); label.addEventListener('click', (e)=>{ const input = label.querySelector('input'); input.checked = !input.checked; label.classList.toggle('bg-primary/10', input.checked); label.classList.toggle('border-primary', input.checked); }); }); optWrap.appendChild(list); } $('#modalFav').onclick = ()=>toggleFav(item.id); $('#modalAdd').onclick = ()=>{ const opts = Array.from($('#modalOptionsWrap').querySelectorAll('input[type=checkbox]:checked')).map(i=>i.value); addToCart(item.id, 1, opts); showToast('Ajouté au panier'); }; } function renderModalThumbs(id){ const wrap = $('#modalThumbs'); wrap.innerHTML = ''; const arr = state.imagesById[id]||[]; arr.forEach((src, idx)=>{ const b = document.createElement('button'); b.type='button'; b.className = "w-16 h-16 rounded-lg overflow-hidden border border-neutral-200 dark:border-neutral-800"; b.innerHTML = ``; b.addEventListener('click', ()=>{ state.thumbsIndexById[id] = idx; $('#modalImage').src = src; }); wrap.appendChild(b); }); $('#prevImg').onclick = ()=>{ const i = state.thumbsIndexById[id]; const ni = (i-1+arr.length)%arr.length; state.thumbsIndexById[id] = ni; $('#modalImage').src = arr[ni]; }; $('#nextImg').onclick = ()=>{ const i = state.thumbsIndexById[id]; const ni = (i+1)%arr.length; state.thumbsIndexById[id] = ni; $('#modalImage').src = arr[ni]; }; $('#closeModal').onclick = closeModal; $('#modalOverlay').onclick = closeModal; } function closeModal(){ $('#modalOverlay').classList.add('hidden'); $('#itemModal').classList.add('hidden'); $('#itemModal').classList.remove('flex'); } function renderCart(){ const list = $('#cartList'); list.innerHTML = ''; let subtotal = 0; cart.forEach((row, idx)=>{ const item = state.data.find(i=>i.id===row.id); if(!item) return; const price = item.price||0; const line = price * (row.qty||1); subtotal += line; const div = document.createElement('div'); const imgSrc = item.thumbnail || (item.images&&item.images[0]) || "./images/placeholder_silky_gradient_soft_lighting_camera_lens_blur_minimalist_background.jpg"; div.className = "rounded-xl border border-neutral-200 dark:border-neutral-800 p-3 flex gap-3"; div.innerHTML = ` ${item.title}

${item.title}

${fmtPrice(price)} • ${item.durationMinutes||60} min

${row.options && row.options.length ? `

Options: ${row.options.join(', ')}

`:''}
${fmtPrice(line)}
`; list.appendChild(div); }); $('#cartSubtotal').textContent = fmtPrice(subtotal); list.onclick = (e)=>{ const rm = e.target.closest('button[data-rm]'); const inc = e.target.closest('button[data-inc]'); const dec = e.target.closest('button[data-dec]'); if(rm){ removeFromCart(Number(rm.dataset.rm)); return; } if(inc){ const i=Number(inc.dataset.inc); cart[i].qty=(cart[i].qty||1)+1; localStorage.setItem('lc_cart', JSON.stringify(cart)); renderCart(); setCounters(); } if(dec){ const i=Number(dec.dataset.dec); cart[i].qty=Math.max(1,(cart[i].qty||1)-1); localStorage.setItem('lc_cart', JSON.stringify(cart)); renderCart(); setCounters(); } }; list.onchange = (e)=>{ const qty = e.target.closest('input[data-qty]'); if(qty){ const i=Number(qty.dataset.qty); cart[i].qty=Math.max(1, Number(qty.value)||1); localStorage.setItem('lc_cart', JSON.stringify(cart)); renderCart(); setCounters(); } } } function renderFavs(){ const wrap = $('#favList'); wrap.innerHTML = ''; const ids = new Set(favs); state.data.filter(i=>ids.has(i.id)).forEach(item=>{ const card = document.createElement('div'); const imgSrc = item.thumbnail || (item.images&&item.images[0]) || "./images/placeholder_silky_gradient_soft_lighting_camera_lens_blur_minimalist_background.jpg"; card.className = "rounded-xl border border-neutral-200 dark:border-neutral-800 p-3 flex gap-3"; card.innerHTML = ` ${item.title}

${item.title}

${fmtPrice(item.price||0)}

`; wrap.appendChild(card); }); wrap.onclick = (e)=>{ const open = e.target.closest('button[data-open]'); const add = e.target.closest('button[data-add]'); const unf = e.target.closest('button[data-unf]'); if(open){ openModal(open.dataset.open); } if(add){ addToCart(add.dataset.add, 1, []); showToast('Ajouté au panier'); } if(unf){ favs.delete(unf.dataset.unf); localStorage.setItem('lc_favs', JSON.stringify(Array.from(favs))); renderFavs(); setCounters(); } } } function openCart(open=true){ const panel = $('#cartPanel'); const backdrop = $('#cartBackdrop'); if(open){ panel.style.transform='translateX(0)'; backdrop.classList.remove('hidden'); } else { panel.style.transform='translateX(100%)'; backdrop.classList.add('hidden'); } } function openFav(open=true){ const panel = $('#favPanel'); const backdrop = $('#favBackdrop'); if(open){ panel.style.transform='translateX(0)'; backdrop.classList.remove('hidden'); } else { panel.style.transform='translateX(100%)'; backdrop.classList.add('hidden'); } } async function loadCatalog(){ const res = await fetch('./catalog.json', {cache:'no-store'}); if(!res.ok) throw new Error('catalog.json introuvable'); const data = await res.json(); state.data = Array.isArray(data)?data: (data.items||[]); if(!Array.isArray(state.data)) state.data = []; const prices = state.data.map(i=>i.price||0); const durs = state.data.map(i=>i.durationMinutes||0); state.priceBounds = {min: Math.floor(Math.min(...prices, 0)), max: Math.ceil(Math.max(...prices, 0))}; state.durationBounds = {min: Math.floor(Math.min(...durs, 0)), max: Math.ceil(Math.max(...durs, 0))}; if(state.price.min===0 && state.price.max===0){ state.price.min = state.priceBounds.min; state.price.max = state.priceBounds.max; } if(state.duration.min===0 && state.duration.max===0){ state.duration.min = state.durationBounds.min; state.duration.max = state.durationBounds.max; } $('#priceMin').value = state.price.min; $('#priceMax').value = state.price.max; $('#durMin').value = state.duration.min; $('#durMax').value = state.duration.max; buildTagsCloud(); applyFilters(true); renderCart(); renderFavs(); setCounters(); } function bindFilters(){ $('#filtersForm').addEventListener('submit', (e)=>{ e.preventDefault(); applyFilters(true); }); $('#searchInput').addEventListener('input', (e)=>{ state.search = e.target.value; }); $('#priceMin').addEventListener('change', e=> setPriceBounds(e.target.value, $('#priceMax').value, true)); $('#priceMax').addEventListener('change', e=> setPriceBounds($('#priceMin').value, e.target.value, true)); $('#durMin').addEventListener('change', e=> setDurationBounds(e.target.value, $('#durMax').value, true)); $('#durMax').addEventListener('change', e=> setDurationBounds($('#durMin').value, e.target.value, true)); $('#availability').addEventListener('change', e=> { state.availability = e.target.checked; applyFilters(true); }); $('#ratingMin').addEventListener('change', e=> { state.ratingMin = Number(e.target.value); applyFilters(true); }); $('#sortSelect').addEventListener('change', e=> { state.sort = e.target.value; applyFilters(false); }); $('#perPage').addEventListener('change', e=> { state.perPage = Number(e.target.value); applyFilters(true); }); $('#resetFilters').addEventListener('click', ()=>{ state.search=''; $('#searchInput').value=''; state.categories.clear(); $$('#catsWrap input').forEach(i=>i.checked=false); state.tags.clear(); $$('#tagsWrap button').forEach(b=>{ b.classList.remove('bg-primary/10','border-primary'); }); setPriceBounds(state.priceBounds.min, state.priceBounds.max, false); setDurationBounds(state.durationBounds.min, state.durationBounds.max, false); $('#availability').checked=false; state.availability=false; $('#ratingMin').value='0'; state.ratingMin=0; $('#sortSelect').value='recent'; state.sort='recent'; $('#perPage').value='12'; state.perPage=12; applyFilters(true); }); $('#catsClear').addEventListener('click', ()=>{ state.categories.clear(); $$('#catsWrap input').forEach(i=>i.checked=false); applyFilters(true); }); $('#tagsClear').addEventListener('click', ()=>{ state.tags.clear(); $$('#tagsWrap button').forEach(b=>{ b.classList.remove('bg-primary/10','border-primary'); }); applyFilters(true); }); $('#catsWrap').addEventListener('change', (e)=>{ if(e.target && e.target.type==='checkbox'){ const v = e.target.value; if(e.target.checked) state.categories.add(v); else state.categories.delete(v); applyFilters(true); } }); } function bindHeaderFooterInteractions(){ const btn = $('#hdrMenu'); if(btn){ btn.onclick = ()=> $('#hdrDrawer').classList.toggle('hidden'); } $('#themeToggle').onclick = ()=>{ const isDark = document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', isDark?'dark':'light'); }; $('#cartOpen').onclick = ()=> openCart(true); $('#cartClose').onclick = ()=> openCart(false); $('#cartBackdrop').onclick = ()=> openCart(false); $('#favOpen').onclick = ()=> openFav(true); $('#favClose').onclick = ()=> openFav(false); $('#favBackdrop').onclick = ()=> openFav(false); $('#cartCheckout').onclick = ()=>{ if(!cart.length){ showToast('Votre panier est vide'); return; } showToast('Redirection vers le paiement sécurisé...'); }; const nlForm = $('#nlForm'); if(nlForm){ nlForm.addEventListener('submit',(e)=>{ e.preventDefault(); const email = $('#nlEmail').value.trim(); const msg = $('#nlMsg'); const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); if(!ok){ msg.textContent='Veuillez saisir un email valide.'; msg.className='text-red-600'; return; } msg.textContent='Merci pour votre abonnement !'; msg.className='text-emerald-600'; nlForm.reset(); }); } const year = $('#yearNow'); if(year) year.textContent = new Date().getFullYear(); } function setupCookie(){ try{ consent = JSON.parse(localStorage.getItem('lc_cookie_consent')||'null'); }catch(e){ consent = null } if(!consent){ $('#cookieBanner').classList.remove('hidden'); } $('#cookieAccept').onclick = ()=>{ consent={necessary:true, analytics:true, marketing:true}; localStorage.setItem('lc_cookie_consent', JSON.stringify(consent)); $('#cookieBanner').classList.add('hidden'); showToast('Préférences enregistrées'); }; $('#cookieReject').onclick = ()=>{ consent={necessary:true, analytics:false, marketing:false}; localStorage.setItem('lc_cookie_consent', JSON.stringify(consent)); $('#cookieBanner').classList.add('hidden'); showToast('Cookies non essentiels refusés'); }; $('#cookiePrefs').onclick = ()=>{ $('#cookieModal').classList.remove('hidden'); $('#consentAnalytics').checked = !!(consent&&consent.analytics); $('#consentMarketing').checked = !!(consent&&consent.marketing); }; $('#cookieClose').onclick = ()=> $('#cookieModal').classList.add('hidden'); $('#cookieSave').onclick = ()=>{ consent = {necessary:true, analytics: $('#consentAnalytics').checked, marketing: $('#consentMarketing').checked}; localStorage.setItem('lc_cookie_consent', JSON.stringify(consent)); $('#cookieModal').classList.add('hidden'); $('#cookieBanner').classList.add('hidden'); showToast('Préférences enregistrées'); }; } async function bootstrap(){ await includeComponent('headerMount', './header.html', fallbackHeaderHTML()); await includeComponent('footerMount', './footer.html', fallbackFooterHTML()); bindHeaderFooterInteractions(); buildCategoryCheckboxes(); bindFilters(); parseURL(); syncControls(); setupCookie(); try{ await loadCatalog(); }catch(e){ $('#grid').innerHTML = `

Impossible de charger le catalogue

Vérifiez le fichier ./catalog.json

`; } document.addEventListener('keydown', (e)=>{ if(e.key==='Escape'){ closeModal(); openCart(false); openFav(false); } }); } bootstrap();