(function(){
const WP = window.BUND_CONFIG || {};
let { STANDS, CATEGORIES, NEWS } = window.BUND;
if (WP.stands && WP.stands.length) STANDS = WP.stands;
if (WP.news   && WP.news.length)   NEWS   = WP.news;
const $  = (s,c=document)=>c.querySelector(s);
const $$ = (s,c=document)=>[...c.querySelectorAll(s)]; let lang = localStorage.getItem('bund-lang') || 'fr';
function applyLang(){
document.documentElement.lang = lang;
$$('[data-i18n]').forEach(el=>{
const v = window.BUND.I18N[el.dataset.i18n];
if(v) el.innerHTML = v[lang];
});
$$('[data-i18n-html]').forEach(el=>{ // contenu déjà rendu (stands…)
const fr = el.dataset.fr, en = el.dataset.en;
if(fr!=null) el.textContent = lang==='fr'?fr:en;
});
$$('.lang button').forEach(b=>b.classList.toggle('on', b.dataset.lang===lang));
refreshOpenModalLang();
}
function setLang(l){ lang=l; localStorage.setItem('bund-lang',l); applyLang(); } function startFlicker(rootSel, unitSel){
if(window.matchMedia('(prefers-reduced-motion:reduce)').matches) return;
unitSel = unitSel || 'path';
let active = 0;
(function tick(){
setTimeout(()=>{
const free = [...document.querySelectorAll(rootSel+' '+unitSel+':not(.flicker)')];
if(active < 2 && free.length && Math.random() < .8){
const el = free[Math.floor(Math.random()*free.length)];
el.style.animationDelay = '0s';
el.style.animationDuration = (0.4 + Math.random()*0.85).toFixed(2)+'s';
el.classList.add('flicker'); active++;
el.addEventListener('animationend', function done(){
el.classList.remove('flicker');
el.style.animationDuration=''; el.style.animationDelay='';
active--; el.removeEventListener('animationend', done);
});
}
tick();
}, 450 + Math.random()*1700);
})();
}
function flickerHeroTitle(){ startFlicker('.hero__title', 'path');
startFlicker('.footer__beast', 'path');
} function groupHeroBund(){
const svg = document.querySelector('.hero__title--svg .heroTitleSvg');
if(!svg || svg.classList.contains('grouped')) return;
const NS='http://www.w3.org/2000/svg';
const gRest=document.createElementNS(NS,'g'); gRest.setAttribute('class','bund-rest');
const gN=document.createElementNS(NS,'g'); gN.setAttribute('class','bund-n');
svg.querySelectorAll('.cls-2').forEach(n=>gRest.appendChild(n));
svg.querySelectorAll('.cls-1').forEach(n=>gN.appendChild(n));
svg.appendChild(gRest); svg.appendChild(gN);   // N au-dessus
svg.classList.add('grouped');
} const IMG = (window.BUND_CONFIG && window.BUND_CONFIG.assetsUrl) ? window.BUND_CONFIG.assetsUrl : 'assets/img/';
const GAL_PHOTOS_RAW = ['wok-steam-concept.jpg','maki-salmon-delivery.jpg','chinese-greens-feast.jpg',
'pho-steam-lift.jpg','bulgogi-sandwich.jpg','pho-shrimp-closeup.jpg','wok-steam-street.jpg',
'night-stall-fire.jpg','noodles-fries-table.jpg','uramaki-tobiko.jpg','salmon-roll-dark.jpg'];
const GAL_PHOTOS = (WP.gallery && WP.gallery.length)
? WP.gallery.map(g => typeof g === 'object' ? (g.url || '') : String(g))
: GAL_PHOTOS_RAW.map(f => IMG + f);
const IG_PHOTOS = ['bibimbap-red.jpg','maki-salmon-delivery.jpg','night-stall-fire.jpg','pho-steam-lift.jpg',
'pho-shrimp-closeup.jpg','wok-friedmeat-plate.jpg','wok-steam-street.jpg','bund-skyline-night.jpg',
'bulgogi-sandwich.jpg','pho-pink.jpg'];
function photo(file,alt){
const f = (file && typeof file === 'object') ? (file.url || '') : String(file || '');
const src = (f.startsWith('http') || f.startsWith('/')) ? f : IMG+f;
return `<img src="${src}" alt="${alt||''}" loading="lazy">`;} const DISH_IMG = IMG+'dish-placeholder.png';
const NAME_LINES = {
seoul:['Seoul','Station'], saigon:['Saigon','Bangkok'], dimsum:['Dim Sum','Spot'],
thai:['Thai','Bowl'], bombay:['Bombay','Bowl'], shibuya:['Shibuya'],
sweet:['Sweet','Asia Café','& Tea'], street:['Asian','Street','Food'],
express:['Bund','Express'], bar:['Le Bar'],
};
const COMPLEMENT = {
'#FED001':'#2E9BD6', '#FFCF00':'#2E9BD6',   // jaune → bleu
'#1BAEDE':'#F4C500',                          // bleu  → jaune
'#E6292B':'#2C8C4A', '#E52A2B':'#2C8C4A',   // rouge → vert
'#289E50':'#E63329',                          // vert  → rouge
'#16161A':'#F4C500',                          // near-black → jaune
};
function complementFor(ground){ return COMPLEMENT[ground.toUpperCase()] || '#F4C500'; } function renderStands(){
const host = $('#stands-list');
host.innerHTML = STANDS.map(s=>{
const wide = s.wide ? ' stand-card--wide' : '';
return `
<article class="stand-card${wide}" data-cat="${s.cat}" data-stand="${s.id}" style="--c:${s.color}" data-reveal>
<div class="stand-card__media">${photo(s.photo, s.name)}</div>
<div class="stand-card__scrim"></div>
<div class="stand-card__body">
<div class="stand-card__head">
<span class="stand-card__cuisine" data-i18n-html data-fr="${s.eyebrow.fr}" data-en="${s.eyebrow.en}">${s.eyebrow.fr}</span>
<div class="stand-card__namerow">
<span class="stand-card__side" aria-hidden="true">${s.script}</span>
<h3 class="stand-card__name">${s.name}</h3>
<span class="stand-card__side" aria-hidden="true">${s.script}</span>
</div>
</div>
</div>
</article>`;
}).join('');
}  function applyFilter(cat){
const grid = $('#stands-list');
const cards = $$('.stand-card', grid);
const keep = c => cat==='all' || c.dataset.cat===cat;
if(window.matchMedia('(prefers-reduced-motion:reduce)').matches || !grid.animate){
cards.forEach(st=>st.classList.toggle('is-hidden', !keep(st)));
return;
}
grid.style.position = 'relative'; const first = new Map();
cards.forEach(c=>{
if(!c.classList.contains('is-hidden'))
first.set(c,{left:c.offsetLeft, top:c.offsetTop, w:c.offsetWidth, h:c.offsetHeight});
});
const leaving=[], entering=[], staying=[];
cards.forEach(c=>{
const hidden = c.classList.contains('is-hidden');
if(keep(c)) (hidden ? entering : staying).push(c);
else if(!hidden) leaving.push(c);
}); leaving.forEach(c=>{
const m = first.get(c);
c.style.position='absolute';
c.style.left=m.left+'px'; c.style.top=m.top+'px';
c.style.width=m.w+'px'; c.style.height=m.h+'px';
c.style.zIndex='1'; c.style.pointerEvents='none';
const a = c.animate(
[{opacity:1, transform:'scale(1)'},{opacity:0, transform:'scale(.8)'}],
{duration:280, easing:'cubic-bezier(.4,0,.2,1)'});
a.onfinish = ()=>{
c.classList.add('is-hidden');
['position','left','top','width','height','zIndex','pointerEvents'].forEach(p=>c.style[p]=''); c.classList.toggle('is-hidden', !(currentCat==='all' || c.dataset.cat===currentCat));
};
}); entering.forEach(c=>c.classList.remove('is-hidden')); staying.forEach(c=>{
const f = first.get(c);
const dx = f.left - c.offsetLeft, dy = f.top - c.offsetTop;
if(dx||dy) c.animate(
[{transform:`translate(${dx}px,${dy}px)`},{transform:'translate(0,0)'}],
{duration:340, easing:'cubic-bezier(.3,.9,.35,1)'});
});
entering.forEach((c,i)=>{
c.animate(
[{opacity:0, transform:'scale(.8)'},{opacity:1, transform:'scale(1)'}],
{duration:300, delay:i*50, easing:'cubic-bezier(.3,.9,.35,1)', fill:'backwards'});
});
}
let currentCat = 'all';
function renderFilters(){
const host = $('#filters');
host.innerHTML = CATEGORIES.map((c,i)=>`
<button class="filter ${i===0?'on':''}" data-cat="${c.key}">
<span class="filter__label" data-i18n-html data-fr="${c.fr}" data-en="${c.en}">${c.fr}</span>
<span class="filter__tick" aria-hidden="true">
<svg viewBox="0 0 20 20" fill="none"><path d="M5 10.5L9 14.5L15 7.5"></path></svg>
</span>
</button>`).join('');
host.addEventListener('click',e=>{
const b = e.target.closest('.filter'); if(!b) return;
$$('.filter',host).forEach(x=>x.classList.toggle('on',x===b));
currentCat = b.dataset.cat;
applyFilter(currentCat);
});
} function renderNews(){
const host = $('#news');
if(!host) return;
host.innerHTML = `
<div class="news__media">
${NEWS.map((n,i)=>`<img class="news__img${i===0?' on':''}" data-idx="${i}" src="${n.img&&n.img.startsWith('http')?n.img:IMG+n.img}" alt="">`).join('')}
</div>
<div class="news__list">
${NEWS.map((n,i)=>`
<div class="news__entry" data-idx="${i}">
<span class="news__date" data-i18n-html data-fr="${n.date.fr}" data-en="${n.date.en}">${n.date.fr}</span>
<span class="news__etitle" data-i18n-html data-fr="${n.title.fr}" data-en="${n.title.en}">${n.title.fr}</span>
<span class="news__ebody" data-i18n-html data-fr="${n.body.fr}" data-en="${n.body.en}">${n.body.fr}</span>
<span class="news__bar" aria-hidden="true"></span>
</div>`).join('')}
</div>`;
let cur = 0;
const imgs = $$('.news__img', host); $$('.news__entry', host).forEach(entry=>{
entry.addEventListener('mouseenter', ()=>{
cur = +entry.dataset.idx;
imgs.forEach(im=>im.classList.toggle('on', +im.dataset.idx===cur));
});
});
} function renderGallery(){
const rows = [$('#gallery-row-1'), $('#gallery-row-2')];
if(!rows[0] || !rows[1]) return;
const half = Math.ceil(GAL_PHOTOS.length/2);
[GAL_PHOTOS.slice(0,half), GAL_PHOTOS.slice(half)].forEach((set,i)=>{
let base = set.slice();
while(base.length < 10) base = base.concat(set);   // assez large pour couvrir l'écran
const seq = base.concat(base);                      // 2 moitiés identiques → boucle à -50%
rows[i].style.setProperty('--dur', (base.length*6)+'s');
rows[i].innerHTML = seq.map(f=>{
const src = (f && (f.startsWith('http') || f.startsWith('/'))) ? f : IMG+f;
return `<div class="g-photo"><img src="${src}" alt="" loading="lazy"></div>`;
}).join('');
});
} function cloneGalleryLogo(){
const src = document.querySelector('.hero__title--svg');
const dest = $('#gallery-logo');
if(!src || !dest || dest.childElementCount) return;
const clone = src.cloneNode(true);
clone.removeAttribute('id');
clone.removeAttribute('aria-label');
clone.setAttribute('aria-hidden','true');
dest.appendChild(clone);
} function renderIG(){
$('#ig').innerHTML = IG_PHOTOS.slice(0,5).map((f,i)=>`
<div class="ig__post">
${photo(f,'@thebund')}
<span class="ig__tag">@thebund</span>
</div>`).join('');
} function observeReveal(){
if(!('IntersectionObserver' in window)){$$('[data-reveal]').forEach(e=>e.classList.add('in'));return;}
const io = new IntersectionObserver((ents)=>{
ents.forEach(en=>{
if(en.isIntersecting){
en.target.classList.add('in');
$$('.neon-flicker',en.target).forEach(f=>f.classList.add('in'));
if(en.target.matches('.stand')) en.target.classList.add('in');
io.unobserve(en.target);
}
});
},{threshold:.16, rootMargin:'0px 0px -8% 0px'});
$$('[data-reveal]').forEach(e=>io.observe(e));
$$('.stand').forEach(e=>io.observe(e));
} function navBehaviour(){
const nav = $('.nav');
const onScroll = ()=>nav.classList.toggle('scrolled', window.scrollY>40);
onScroll(); window.addEventListener('scroll',onScroll,{passive:true});
$('.nav__burger').addEventListener('click',()=>nav.classList.toggle('open'));
$$('.nav__link').forEach(l=>l.addEventListener('click',()=>nav.classList.remove('open')));
const links = $$('.nav__link');
const map = new Map(links.map(l=>[l.getAttribute('href').slice(1),l]));
const so = new IntersectionObserver((ents)=>{
ents.forEach(en=>{
if(en.isIntersecting){
links.forEach(x=>x.classList.remove('active'));
map.get(en.target.id)?.classList.add('active');
}
});
},{rootMargin:'-45% 0px -50% 0px'});
['concept','stands','plan','infos'].forEach(id=>{const s=document.getElementById(id);if(s)so.observe(s);});
} function parallax(){
if(matchMedia('(prefers-reduced-motion:reduce)').matches) return;
const bg = $('.hero__bg'); if(!bg) return;
let raf=null;
const upd=()=>{
bg.style.transform = `translateY(${window.scrollY*0.28}px) scale(1.08)`;
raf=null;
};
window.addEventListener('scroll',()=>{if(!raf)raf=requestAnimationFrame(upd)},{passive:true});
}  function heroVideo(){
const v = $('.hero__video'); if(!v) return;
const tryPlay = ()=>{ const p=v.play(); if(p&&p.catch) p.catch(()=>{}); };
tryPlay();
v.addEventListener('canplay', tryPlay, {once:true});
document.addEventListener('visibilitychange', ()=>{ if(!document.hidden) tryPlay(); });
['pointerdown','touchstart','keydown'].forEach(ev=>
window.addEventListener(ev, tryPlay, {once:true, passive:true}));
} let _modalOpen=false, _lastCard=null, _openStand=null;
function buildStandModal(){
let el = $('#stand-modal');
if(el) return el;
el = document.createElement('div');
el.className='stand-modal';
el.id='stand-modal';
el.setAttribute('role','dialog');
el.setAttribute('aria-modal','true');
el.setAttribute('aria-hidden','true');
el.innerHTML = `
<div class="stand-modal__bg"></div>
<button class="stand-modal__close" aria-label="Fermer">&times;</button>
<div class="stand-modal__inner">
<header class="stand-modal__head">
<span class="stand-card__cuisine stand-modal__cuisine"></span>
<div class="stand-modal__namerow">
<span class="stand-card__side stand-modal__side" aria-hidden="true"></span>
<h3 class="stand-card__name stand-modal__name"></h3>
<span class="stand-card__side stand-modal__side" aria-hidden="true"></span>
</div>
<p class="stand-modal__desc"></p>
</header>
<div class="stand-modal__grid"></div>
</div>`;
document.body.appendChild(el);
el.querySelector('.stand-modal__close').addEventListener('click', closeStand);
return el;
} const LIGHT_GROUNDS = ['#FED001','#FFCF00','#F4C500'];
function groundFor(s){ return s.color.toUpperCase()==='#FFFFFF' ? '#16161A' : s.color; }
function inkFor(ground){ return LIGHT_GROUNDS.includes(ground.toUpperCase()) ? '#16161A' : '#FFFFFF'; }  function fitModalSides(modal){
if(!modal) return;
const name = modal.querySelector('.stand-modal__name');
if(!name) return;
const nh = name.getBoundingClientRect().height;
modal.querySelectorAll('.stand-modal__side').forEach(s=>{
s.style.fontSize = '';
const sh = s.getBoundingClientRect().height;
if(sh > nh + 1){
const base = parseFloat(getComputedStyle(s).fontSize);
s.style.fontSize = (base * nh / sh).toFixed(2) + 'px';
}
});
}
function fitDishGrid(modal){
if(!modal) return;
fitModalSides(modal);
const grid = modal.querySelector('.stand-modal__grid');
if(!grid) return;
const N = grid.children.length;
if(!N) return;
const availW = grid.clientWidth;
if(availW < 10) return;
const gap    = Math.max(12, Math.min(22, Math.round(availW*0.016))); if(window.matchMedia('(max-width:560px)').matches){
const cols = Math.min(N, 2);
const size = Math.max(120, Math.floor((availW - (cols-1)*gap)/cols));
grid.style.setProperty('--cols', cols);
grid.style.setProperty('--img-size', size+'px');
grid.style.setProperty('--gap', gap+'px'); grid.style.alignContent = 'start';
grid.style.flex = '0 0 auto';
return;
} const cols = Math.min(N > 0 ? Math.ceil(Math.sqrt(N * 1.5)) : 4, 6);
const size = Math.max(130, Math.min(Math.floor((availW - (cols-1)*gap) / cols), 200));
grid.style.setProperty('--cols', cols);
grid.style.setProperty('--img-size', size+'px');
grid.style.setProperty('--gap', gap+'px');
grid.style.alignContent = 'start';
grid.style.flex = '0 0 auto';  // la grille s'étend, l'inner scrolle
} function refreshOpenModalLang(){
if(!_modalOpen || !_openStand) return;
const modal = $('#stand-modal'); if(!modal) return;
const s = _openStand;
modal.querySelector('.stand-modal__cuisine').textContent = (lang==='en'?s.eyebrow.en:s.eyebrow.fr);
modal.querySelector('.stand-modal__desc').textContent =
s.desc ? (lang==='en'?s.desc.en:s.desc.fr) : '';
// Relancer l'ouverture du stand pour re-rendre le menu dans la bonne langue
openStand(_openStand, _lastCard);
}
function openStand(s, card){
const modal = buildStandModal();
const ground = groundFor(s), ink = inkFor(ground);
modal.style.setProperty('--c', s.color);
modal.style.setProperty('--ground', ground);
modal.style.setProperty('--ink', ink);
modal.dataset.ground = (ground.toUpperCase()==='#16161A') ? 'dark' : 'color';
modal.dataset.stand = s.id;
modal.style.setProperty('--dish-bg', complementFor(ground));
modal.querySelector('.stand-modal__cuisine').textContent = (lang==='en'?s.eyebrow.en:s.eyebrow.fr);
const lines = NAME_LINES[s.id] || [s.name];
modal.querySelector('.stand-modal__name').innerHTML =
lines.map(l=>`<span class="stand-modal__nl">${l}</span>`).join('');
modal.querySelectorAll('.stand-modal__side').forEach(x=>x.textContent=s.script);
modal.querySelector('.stand-modal__desc').textContent =
s.desc ? (lang==='en'?s.desc.en:s.desc.fr) : '';
const grid = modal.querySelector('.stand-modal__grid');
grid.removeAttribute('style');
// Bouton PDF
const inner = modal.querySelector('.stand-modal__inner');
const oldPdf = inner.querySelector('.stand-modal__pdf');
if (oldPdf) oldPdf.remove();
if (s.menu_pdf) {
const pdfEl = document.createElement('a');
pdfEl.href = s.menu_pdf;
pdfEl.className = 'stand-modal__pdf';
pdfEl.download = true;
pdfEl.target = '_blank';
pdfEl.rel = 'noopener';
pdfEl.innerHTML = `<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>${lang==='en'?'Download menu':'Télécharger le menu'}`;
modal.querySelector('.stand-modal__desc').insertAdjacentElement('afterend', pdfEl);
}
// Rendu par catégories
const menuItems = (lang==='en' && s.menu_en && s.menu_en.length) ? s.menu_en : (s.menu||[]);
const CAT_LABELS = {entrees:{fr:'Entrées',en:'Starters'},plats:{fr:'Plats',en:'Mains'},desserts:{fr:'Desserts',en:'Desserts'}};
const hasCats = menuItems.some(m => m.cat && m.cat !== 'default');
function makeDish(m) {
const f = m.img||'';
const src = (f.startsWith('http')||f.startsWith('/')) ? f : (f ? IMG+f : DISH_IMG);
const price = m.p ? `<span class="dish__price">${m.p}</span>` : '';
return `<figure class="dish">
<div class="dish__photo"><img src="${src}" alt="${m.n}" loading="lazy"></div>
<figcaption class="dish__name">${m.n}${price}</figcaption>
</figure>`;
}
if (!hasCats) {
grid.innerHTML = menuItems.map(makeDish).join('');
} else {
let html = '';
['entrees','plats','desserts'].forEach(cat => {
const items = menuItems.filter(m => (m.cat||'default') === cat);
if (!items.length) return;
const lbl = CAT_LABELS[cat][lang]||CAT_LABELS[cat].fr;
html += `<div class="dish-category">
<h4 class="dish-category__title">${lbl}</h4>
<div class="dish-category__grid">${items.map(makeDish).join('')}</div>
</div>`;
});
// Plats sans catégorie
const uncategorized = menuItems.filter(m => !m.cat || m.cat === 'default');
if (uncategorized.length) html += uncategorized.map(makeDish).join('');
grid.innerHTML = html;
}
_lastCard = card;
_openStand = s;
_modalOpen = true;
modal.classList.add('open');
modal.setAttribute('aria-hidden','false');
document.body.classList.add('modal-lock');
requestAnimationFrame(()=>fitDishGrid(modal)); const bg = modal.querySelector('.stand-modal__bg');
const reduced = window.matchMedia('(prefers-reduced-motion:reduce)').matches;
if(reduced || !bg.animate){
modal.classList.add('revealed');
requestAnimationFrame(()=>fitDishGrid(modal));
return;
}
const r = card.getBoundingClientRect();
const inset = `inset(${r.top}px ${window.innerWidth-r.right}px ${window.innerHeight-r.bottom}px ${r.left}px round 20px)`;
bg.animate(
[{clipPath:inset},{clipPath:'inset(0px 0px 0px 0px round 0px)'}],
{duration:520, easing:'cubic-bezier(.3,.9,.35,1)'});
bg.animate(
[{transform:'scale(1.07)'},{transform:'scale(1)'}],
{duration:700, easing:'cubic-bezier(.3,.9,.35,1)'});
setTimeout(()=>{ if(_modalOpen){ modal.classList.add('revealed'); fitDishGrid(modal); } }, 240);
}
function closeStand(){
const modal = $('#stand-modal');
if(!modal || !_modalOpen) return;
_modalOpen = false;
modal.classList.remove('revealed');
const bg = modal.querySelector('.stand-modal__bg');
const reduced = window.matchMedia('(prefers-reduced-motion:reduce)').matches;
const finish = ()=>{
modal.classList.remove('open');
modal.setAttribute('aria-hidden','true');
document.body.classList.remove('modal-lock');
};
if(reduced || !bg.animate){ finish(); return; } let anim;
if(_lastCard){
const r = _lastCard.getBoundingClientRect();
const inset = `inset(${r.top}px ${window.innerWidth-r.right}px ${window.innerHeight-r.bottom}px ${r.left}px round 20px)`;
anim = bg.animate(
[{clipPath:'inset(0px 0px 0px 0px round 0px)', opacity:1},
{clipPath:inset, opacity:1, offset:.82},
{clipPath:inset, opacity:0}],
{duration:480, delay:90, easing:'cubic-bezier(.4,0,.2,1)', fill:'forwards'});
}else{
anim = modal.animate([{opacity:1},{opacity:0}],{duration:300, easing:'ease', fill:'forwards'});
}
anim.finished.catch(()=>{}).then(()=>{ finish(); anim.cancel(); });
}
function wireStandClicks(){
const host = $('#stands-list');
if(host) host.addEventListener('click', e=>{
const card = e.target.closest('.stand-card');
if(!card) return;
const s = STANDS.find(x=>x.id===card.dataset.stand);
if(s) openStand(s, card);
});
document.addEventListener('keydown', e=>{ if(e.key==='Escape') closeStand(); }); let _fitM;
window.addEventListener('resize', ()=>{
if(!_modalOpen) return;
clearTimeout(_fitM);
_fitM = setTimeout(()=>fitDishGrid($('#stand-modal')), 120);
});
if(document.fonts && document.fonts.ready)
document.fonts.ready.then(()=>{ if(_modalOpen) fitDishGrid($('#stand-modal')); });
}  function loadMapLibre(cb){
if(typeof maplibregl !== 'undefined'){ cb(); return; }
const base = 'https://unpkg.com/maplibre-gl@4.7.1/dist/';
if(!document.getElementById('maplibre-css')){
const l = document.createElement('link');
l.id='maplibre-css'; l.rel='stylesheet'; l.href=base+'maplibre-gl.css';
document.head.appendChild(l);
}
const sc = document.createElement('script');
sc.src = base+'maplibre-gl.js';
sc.onload = cb;
sc.onerror = ()=>{};   // hors-ligne : la section reste élégamment vide
document.head.appendChild(sc);
}
function planMap(){
const el = $('#plan-map');
if(!el) return;
const boot = ()=>loadMapLibre(initMap);
if('IntersectionObserver' in window){
const io = new IntersectionObserver((ents)=>{
if(ents.some(e=>e.isIntersecting)){ io.disconnect(); boot(); }
},{rootMargin:'900px 0px'});
io.observe(el);
}else{
boot();
}
}
function initMap(){
const el = $('#plan-map');
if(!el || typeof maplibregl === 'undefined') return;
const CENTER = [2.3667, 48.8459]; // 12 quai de la Rapée, 75012 Paris
const map = new maplibregl.Map({
container: el,
style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
center: CENTER,
zoom: 14.2,
pitch: 0,
attributionControl: { compact: true },
cooperativeGestures: true,   // évite de capturer le scroll de la page
renderWorldCopies: false
});
map.addControl(new maplibregl.NavigationControl({ showCompass:false }), 'bottom-right');
const dot = document.createElement('div');
dot.className = 'plan__marker';
new maplibregl.Marker({ element: dot }).setLngLat(CENTER)
.setPopup(new maplibregl.Popup({ offset:18, closeButton:false })
.setHTML('<strong>外滩 · The Bund</strong><br>12 quai de la Rapée, 75012 Paris'))
.addTo(map);
map.on('load', ()=> map.resize());
} function softImageLoad(){
$$('img[loading="lazy"]').forEach(img=>{
if(img.complete) return;
img.classList.add('img-pending');
const done = ()=>img.classList.remove('img-pending');
img.addEventListener('load', done, {once:true});
img.addEventListener('error', done, {once:true});
});
} function fitCardSides(){
const mobile = window.matchMedia('(max-width:560px)').matches;
$$('.stand-card').forEach(card=>{
const name = card.querySelector('.stand-card__name');
const sides = card.querySelectorAll('.stand-card__side');
sides.forEach(s=>{ s.style.transform=''; });
if(!mobile || !name) return;
const nh = name.getBoundingClientRect().height;
sides.forEach(s=>{
const sh = s.getBoundingClientRect().height;
if(sh > nh + 1) s.style.transform = `scale(${(nh/sh).toFixed(3)})`;
});
});
}
function init(){
renderStands(); renderFilters(); renderNews(); renderGallery(); renderIG();
softImageLoad();
applyLang();
$$('.lang button').forEach(b=>b.addEventListener('click',()=>setLang(b.dataset.lang)));
observeReveal(); navBehaviour(); parallax(); heroVideo();
groupHeroBund();
cloneGalleryLogo();
flickerHeroTitle();
wireStandClicks();
planMap();
fitCardSides();
if(document.fonts && document.fonts.ready) document.fonts.ready.then(fitCardSides);
let _fitT;
window.addEventListener('resize', ()=>{ clearTimeout(_fitT); _fitT = setTimeout(fitCardSides, 150); });
}
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',init);
else init(); window.__bundSetLang = setLang;
})();  .stand-modal__grid h4.dish-category__title {
grid-column: 1 / -1;
width: 100%;
margin: 22px 0 6px;
padding-bottom: 8px;
border-bottom: 1px solid currentColor;
opacity: .5;
font-family: var(--f-display);
font-weight: 900;
font-stretch: 62%;
font-variation-settings: var(--display-vary);
font-size: clamp(20px, 2.2vw, 30px);
text-transform: uppercase;
letter-spacing: 0;
line-height: .97;
color: inherit;
}
.stand-modal__grid h4.dish-category__title:first-child { margin-top: 4px; } .stand-modal__inner { overflow-y: auto !important; }
.stand-modal__grid  { flex: 0 0 auto !important; align-content: start !important; } .dish__price {
display: block;
font-size: 11px;
font-weight: 700;
opacity: .7;
margin-top: 2px;
letter-spacing: .04em;
} .stand-modal__pdf {
display: inline-flex;
align-items: center;
gap: 7px;
margin: 10px 0 16px;
padding: 9px 18px;
border: 1.5px solid currentColor;
border-radius: 100px;
font-size: 11px;
font-weight: 700;
letter-spacing: .1em;
text-transform: uppercase;
text-decoration: none !important;
color: inherit !important;
opacity: .8;
transition: opacity .2s;
background: none !important;
box-shadow: none !important;
}
.stand-modal__pdf:hover { opacity: 1; }