PageSourceSearch

https://popupstoreparis.com/assets/js/app.js

js popupstoreparis.com collected 2026-10-02 23:24:57 UTC 24,025 bytes, 397 lines download raw bytes

1var I=window.I18N||{eralbl_pop:'Pop-up stores',eralbl_her:'Heritage — the history of the address',heritage_sub:'Heritage · subpage',cremerie:'Cremerie No',cremerie_def:'Cremerie de Paris',no_match:'No match',detail:'Details \u2192',popups:'pop-ups',all:'All',none:'None',hc:'Off-site'};
2var ALL=POPS.concat(HERITAGE);
3var locURL=window.locURL||function(u){return u;};   // absolu popupstoreparis.com -> chemin local (défini dans lang.js)
4function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;')}
5function thumbBg(p){return 'linear-gradient(150deg,'+p.col+'cc,'+p.col+'33)'}
6function firstWord(n){return n.split(' ')[0]}
7// ---------- 8 FILTRES GLOBAUX (regroupent les 19 catégories) ----------
8var LANGX=window.LANG||'en';
9var GDEF=[
10  {k:'mode',    en:'Fashion & Beauty',  fr:'Mode & Beauté',      col:'#ff2e93', cats:['Mode','Beauté']},
11  {k:'heritage',en:'Heritage',          fr:'Patrimoine',         col:'#b08733', cats:['Heritage']},
12  {k:'food',    en:'Food & Drink',      fr:'Food & Boissons',    col:'#ffb020', cats:['Food','B2B']},
13  {k:'tech',    en:'Tech & Apps',       fr:'Tech & Apps',        col:'#00c2d1', cats:['Tech','Apps','Fintech']},
14  {k:'gaming',  en:'Gaming & Music',    fr:'Gaming & Musique',   col:'#8b5cff', cats:['Gaming','Musique']},
15  {k:'retail',  en:'Retail & Lifestyle',fr:'Retail & Lifestyle', col:'#35c07a', cats:['Retail','Maison','Divers','—']},
16  {k:'art',     en:'Art & Culture',     fr:'Art & Culture',      col:'#d98cc0', cats:['Art','Tourisme','Santé']},
17  {k:'sport',   en:'Sport & Auto',      fr:'Sport & Auto',       col:'#2ee6a0', cats:['Sport','Auto']}
18];
19var CAT2G={},GLABEL={},GCOL={};
20GDEF.forEach(function(g){GLABEL[g.k]=g[LANGX]||g.en;GCOL[g.k]=g.col;g.cats.forEach(function(c){CAT2G[c]=g.k;});});
21function groupOf(c){return CAT2G[c]||'retail';}
22// ---------- card ----------
23function cardHTML(p){
24  var venue=p.h?I.heritage_sub:(p.hc?(p.venue||'Paris'):(p.v?I.cremerie+p.v:I.cremerie_def));
25  var thumb;
26  if(p.hc){
27    thumb='<div class="thumb logo'+(p.logo?'':' failed')+'"><span class="lfb">'+esc(firstWord(p.n))+'</span>'+
28      (p.logo?'<img class="fav" src="'+p.logo+'" alt="'+esc(p.n)+' logo" loading="lazy" onerror="this.parentNode.classList.add(\'failed\');this.remove()">':'')+'</div>';
29  }else if(p.img){
30    thumb='<div class="thumb"><img src="'+locURL(p.img)+'" alt="'+esc(p.n)+'" loading="lazy" onerror="this.parentNode.style.background=\''+thumbBg(p)+'\';this.remove()"></div>';
31  }else{
32    thumb='<div class="thumb" style="background:'+thumbBg(p)+'"><span>'+esc(firstWord(p.n))+'</span></div>';
33  }
34  var L=p.n.replace(/^[^A-Za-z]*/,'')[0]; L=L?L.toUpperCase():'#';
35  var gk=groupOf(p.c);
36  return '<a class="card'+(p.h?' heritage':'')+(p.hc?' offsite':'')+'" href="'+p.u+'" data-cat="'+gk+'" data-letter="'+L+'" data-hc="'+(p.hc?1:0)+'">'+
37    thumb+'<div class="cbody">'+
38      '<div class="cat"><span class="chip2" style="background:'+GCOL[gk]+'">'+esc(GLABEL[gk])+'</span></div>'+
39      '<div class="nm">'+esc(p.n)+'</div>'+
40      '<div class="meta"><span>'+venue+'</span>'+(p.hc?'<span class="hc-badge">'+(I.hc||'Off-site')+'</span>':'')+'</div>'+
41    '</div></a>';
42}
43// ---------- voyage ----------
44function hueFor(y){
45  if(y>=2023)return'radial-gradient(66% 70% at 82% 14%,rgba(217,181,103,.28),transparent 55%),linear-gradient(165deg,#241d15,#171310 64%)';
46  if(y>=2012)return'radial-gradient(66% 70% at 82% 14%,rgba(176,135,51,.24),transparent 55%),linear-gradient(165deg,#211a15,#161009 64%)';
47  if(y>=1900)return'radial-gradient(64% 68% at 78% 18%,rgba(150,104,52,.22),transparent 55%),linear-gradient(165deg,#1e160e,#130d08 64%)';
48  return'radial-gradient(60% 66% at 76% 20%,rgba(120,95,60,.2),transparent 58%),linear-gradient(165deg,#191309,#0f0a06 64%)';
49}
50var voyage=document.getElementById('voyage');
51var byYear={};ALL.forEach(function(p){(byYear[p.y]=byYear[p.y]||[]).push(p)});
52var years=Object.keys(byYear).map(Number).sort(function(a,b){return b-a});
53document.getElementById('yearHud').textContent=years[0];
54// LAZY-LOAD : on crée les coquilles de section (année + filigrane) mais les CARTES ne sont
55// construites que pour la date la plus récente au chargement ; les autres se remplissent à
56// l'approche du scroll. Gain de rendu initial ; les données restent dans eraLists.
57var eraLists={};
58years.forEach(function(y){
59  var s=document.createElement('section');s.className='era';s.dataset.y=y;s.id='era-'+y;
60  var list=byYear[y].slice().sort(function(a,b){return a.n<b.n?-1:1});
61  var heritage=list.some(function(x){return x.h});
62  eraLists[y]=list;
63  s.innerHTML='<div class="inner"><div class="era-ghost" aria-hidden="true">'+y+'</div><div class="eralbl">'+(heritage?I.eralbl_her:I.eralbl_pop)+'</div><div class="yr">'+y+'</div><div class="cards"></div></div>';
64  voyage.appendChild(s);
65});
66var eras=[].slice.call(document.querySelectorAll('.era'));
67function buildEra(s){
68  if(!s||s.dataset.built)return;
69  s.dataset.built='1';
70  var list=eraLists[s.dataset.y]||[];
71  s.querySelector('.cards').innerHTML=list.map(cardHTML).join('');
72  // applique le filtre courant aux nouvelles cartes + recalcule l'état de la section
73  s.querySelectorAll('.card').forEach(function(c){c.classList.toggle('hide',!active[c.dataset.cat]);});
74  s.classList.toggle('hide', s.querySelectorAll('.card:not(.hide)').length===0);
75  if(typeof buildRail==='function')buildRail();
76  if(typeof positionRail==='function')positionRail();
77}
78// animation de révélation
79var io=new IntersectionObserver(function(es){es.forEach(function(en){
80  if(en.isIntersecting)en.target.classList.add('show');
81})},{threshold:.4});
82eras.forEach(function(e){io.observe(e)});
83// remplissage paresseux : configuré à l'init (après définition de active/cremerieOnly)
84var lazyIO=new IntersectionObserver(function(es){es.forEach(function(en){
85  if(en.isIntersecting){buildEra(en.target);lazyIO.unobserve(en.target);}
86})},{rootMargin:'1200px 0px'});
87// ---------- featured ----------
88// « The collection » : les 8 pop-ups les plus récents.
89var grid=document.getElementById('grid');
90(function(){
91  POPS.slice().sort(function(a,b){return b.y-a.y}).slice(0,8).forEach(function(p){grid.insertAdjacentHTML('beforeend',cardHTML(p))});
92})();
93// ---------- A–Z ---------- (construit paresseusement au 1er passage en mode A–Z)
94var az=document.getElementById('azgrid'),azBuilt=false;
95function buildAZ(){
96  if(azBuilt)return; azBuilt=true;
97  ALL.slice().sort(function(a,b){return a.n.toLowerCase()<b.n.toLowerCase()?-1:1}).forEach(function(p){
98    var L=p.n.replace(/^[^A-Za-z]*/,'')[0];L=L?L.toUpperCase():'#';
99    if(az.dataset.l!==L){az.dataset.l=L;az.insertAdjacentHTML('beforeend','<div class="azletter" id="az-'+L+'" data-l="'+L+'">'+L+'</div>')}
100    az.insertAdjacentHTML('beforeend',cardHTML(p));
101  });
102  if(typeof applyFilter==='function')applyFilter();   // applique le filtre courant aux cartes A–Z
103}
104// ---------- FILTRES (8 groupes) ----------
105var gCount={};GDEF.forEach(function(g){gCount[g.k]=0;});ALL.forEach(function(p){var k=groupOf(p.c);gCount[k]=(gCount[k]||0)+1;});
106var active={};GDEF.forEach(function(g){active[g.k]=true;});
107var chips=document.getElementById('chips');
108chips.innerHTML='<button class="chip reset" data-all="1">'+I.all+'</button><button class="chip reset" data-none="1">'+I.none+'</button>'+
109  '<button class="navbtn" data-scroll="-1" aria-label="Scroll left">‹</button>'+
110  '<button class="navbtn" data-scroll="1" aria-label="Scroll right">›</button>'+
111  '<div class="chipscroll" id="chipscroll">'+GDEF.map(function(g){
112  return '<button class="chip" data-c="'+g.k+'"><span class="dot" style="background:'+g.col+'"></span>'+esc(GLABEL[g.k])+' <span style="opacity:.6">'+gCount[g.k]+'</span></button>';
113}).join('')+'</div>';
114var chipscroll=document.getElementById('chipscroll');
115var arrPrev=chips.querySelector('button[data-scroll="-1"]'),arrNext=chips.querySelector('button[data-scroll="1"]');
116function updateArrows(){
117  var max=chipscroll.scrollWidth-chipscroll.clientWidth-1;
118  arrPrev.classList.toggle('dis',chipscroll.scrollLeft<=0);
119  arrNext.classList.toggle('dis',chipscroll.scrollLeft>=max);
120}
121chipscroll.addEventListener('scroll',updateArrows);
122window.addEventListener('resize',updateArrows);
123updateArrows();
124chips.addEventListener('click',function(e){
125  var b=e.target.closest('button');if(!b)return;
126  if(b.dataset.scroll){chipscroll.scrollBy({left:Number(b.dataset.scroll)*260,behavior:'smooth'});return;}
127  if(b.dataset.all){GDEF.forEach(function(g){active[g.k]=true});}
128  else if(b.dataset.none){GDEF.forEach(function(g){active[g.k]=false});}
129  else if(b.dataset.c){active[b.dataset.c]=!active[b.dataset.c];}
130  syncChips();applyFilter();
131});
132function syncChips(){[].forEach.call(chips.querySelectorAll('.chip[data-c]'),function(b){b.classList.toggle('off',!active[b.dataset.c])})}
133// ---------- FILTER + RAIL ----------
134function applyFilter(){
135  document.querySelectorAll('.card').forEach(function(c){c.classList.toggle('hide',!active[c.dataset.cat])});
136  eras.forEach(function(e){e.classList.toggle('hide', !!e.dataset.built && e.querySelectorAll('.card:not(.hide)').length===0)});
137  [].forEach.call(document.querySelectorAll('.azletter'),function(h){
138    var any=[].some.call(document.querySelectorAll('#azgrid .card[data-letter="'+h.dataset.l+'"]'),function(c){return !c.classList.contains('hide')});
139    h.classList.toggle('hide',!any);
140  });
141  buildRail();
142  if(typeof positionRail==='function')positionRail();
143}
144var rail=document.getElementById('rail');
145function buildRail(){
146  var azMode=document.body.classList.contains('mode-az');
147  rail.innerHTML='';
148  if(azMode){
149    'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').forEach(function(L){
150      var has=[].some.call(document.querySelectorAll('#azgrid .card[data-letter="'+L+'"]'),function(c){return !c.classList.contains('hide')});
151      var a=document.createElement('a');a.textContent=L;a.dataset.l=L;
152      if(has)a.onclick=function(){var el=document.getElementById('az-'+L);if(el)el.scrollIntoView({behavior:'smooth'})};
153      else a.classList.add('off');
154      rail.appendChild(a);
155    });
156  }else{
157    years.forEach(function(y){
158      var era=document.getElementById('era-'+y);var has=era&&!era.classList.contains('hide');
159      var a=document.createElement('a');a.textContent=y;a.dataset.y=y;
160      if(has)a.onclick=function(){var idx=eras.indexOf(era);for(var i=0;i<=idx;i++)buildEra(eras[i]);era.scrollIntoView({behavior:'smooth'});};
161      else a.classList.add('off');
162      rail.appendChild(a);
163    });
164  }
165}
166// ---------- MODE ----------
167function ensureLatest(){ // la SECTION LA PLUS RÉCENTE (eras[0], quelle que soit l'année) : jamais en lazy → construite + révélée d'emblée
168  if(eras[0]){buildEra(eras[0]);eras[0].classList.add('show');}
169}
170function setMode(m){
171  if(m==='az')buildAZ();   // construit la grille A–Z au 1er passage
172  else ensureLatest();     // Timeline : la date la plus récente s'affiche immédiatement (hors lazy)
173  document.body.classList.toggle('mode-az',m==='az');
174  [].forEach.call(document.getElementById('sort').children,function(b){b.classList.toggle('on',b.dataset.m===m)});
175  buildRail();window.scrollTo({top:0});positionRail();
176}
177document.getElementById('sort').addEventListener('click',function(e){if(e.target.dataset.m)setMode(e.target.dataset.m)});
178// --- mobile bottom-bar navigation ---
179function setActiveNav(i){var a=document.querySelectorAll('#appbar a');a.forEach(function(x,ix){x.classList.toggle('active',ix===i)});}
180function navHome(){if(!(window.matchMedia&&window.matchMedia('(max-width:768px)').matches))setMode('timeline');setActiveNav(0);window.scrollTo({top:0,behavior:'smooth'});}
181function navTimeline(){ // active le filtre Timeline + remonte sur la 1re date
182  setMode('timeline');setActiveNav(1);
183  var first=document.querySelector('#voyage .era:not(.hide)');
184  if(first){setTimeout(function(){first.scrollIntoView({behavior:'smooth',block:'start'});},50);}
185}
186function navAZ(){ // active le filtre A–Z + remonte sur la lettre A (haut de liste)
187  setMode('az');setActiveNav(2);
188  var first=document.querySelector('#azgrid .azletter:not(.hide)');
189  setTimeout(function(){ if(first)first.scrollIntoView({behavior:'smooth',block:'start'}); else window.scrollTo({top:0,behavior:'smooth'}); },50);
190}
191function navMap(){setActiveNav(3);if(typeof buildAllForBottom==='function')buildAllForBottom();var m=document.getElementById('map');if(m)m.scrollIntoView({behavior:'smooth'});}
192
193// --- RAIL FIXE (ne glisse pas) + GROSSE DATE propre à chaque section ---
194// Le rail est ancré sous les filtres (2026 en haut) et ne bouge PAS au scroll : seule la
195// surbrillance descend. La grosse date est un filigrane interne à chaque .era (.era-ghost)
196// qui défile avec sa section. L'alignement Gauche(date)/Centre(en-tête)/Droite(rail) se
197// produit à l'instant où l'en-tête d'une section croise sa propre entrée de rail.
198var headerEl=document.querySelector('header');
199var RAIL_GAP=14;
200var railLastKey=null;
201function stickyBottom(){
202  var hb=headerEl?headerEl.getBoundingClientRect().bottom:0;
203  var sub=document.querySelector('.subbar');
204  if(!sub)return hb;
205  return Math.max(hb, sub.getBoundingClientRect().bottom);   // suit la barre puis se colle
206}
207function centerY(el){var r=el.getBoundingClientRect();return r.top+r.height/2;}
208function positionRail(){
209  if(getComputedStyle(rail).display==='none')return;
210  rail.style.maxHeight='none';rail.style.overflow='visible';
211  var azMode=document.body.classList.contains('mode-az');
212  rail.style.top=(stickyBottom()+RAIL_GAP)+'px';               // ancré sous les filtres — FIXE, ne glisse pas
213  var list=[];
214  if(azMode){
215    [].forEach.call(document.querySelectorAll('#azgrid .azletter'),function(el){
216      if(el.classList.contains('hide'))return;
217      var e=rail.querySelector('a[data-l="'+el.dataset.l+'"]');
218      if(e)list.push({key:el.dataset.l,hy:centerY(el),entry:e});
219    });
220  }else{
221    years.forEach(function(y){
222      var era=document.getElementById('era-'+y);if(!era||era.classList.contains('hide'))return;
223      var yr=era.querySelector('.yr'),e=rail.querySelector('a[data-y="'+y+'"]');
224      if(yr&&e)list.push({key:String(y),hy:centerY(yr),entry:e});
225    });
226  }
227  if(!list.length)return;
228  // surbrillance = section dont l'en-tête est la plus proche de SA propre entrée de rail
229  var ref=list[0],best=Infinity;
230  list.forEach(function(o){var d=Math.abs(o.hy-centerY(o.entry));if(d<best){best=d;ref=o;}});
231  [].forEach.call(rail.children,function(a){a.classList.toggle('on',(azMode?a.dataset.l:a.dataset.y)===ref.key);});
232  if(!azMode && ref.key!==railLastKey){
233    document.getElementById('bg').style.background=hueFor(Number(ref.key));
234    railLastKey=ref.key;
235  }
236}
237var railTick=false;
238window.addEventListener('scroll',function(){if(!railTick){railTick=true;window.requestAnimationFrame(function(){positionRail();railTick=false;});}},{passive:true});
239window.addEventListener('resize',positionRail);
240positionRail();
241// init
242applyFilter();
243// LAZY : construire la date la plus récente maintenant, puis observer les autres sections
244ensureLatest();   // date la plus récente : construite + révélée immédiatement (hors lazy)
245eras.forEach(function(e){if(!e.dataset.built)lazyIO.observe(e);});
246// A–Z ACTIF PAR DÉFAUT (PC + mobile). Sur PC le bascule Timeline/A–Z reste dispo ; sur mobile il est masqué.
247setMode('az');
248// ---------- SEARCH ----------
249var q=document.getElementById('q'),ac=document.getElementById('ac'),hl=-1,cur=[];
250function matchPops(v){return ALL.filter(function(p){return p.n.toLowerCase().indexOf(v)>-1||String(p.y).indexOf(v)>-1||p.c.toLowerCase().indexOf(v)>-1}).slice(0,8);}
251function resultsHTML(list){
252  return list.length?list.map(function(p,i){
253    var t=p.img?'style="background-image:url('+locURL(p.img)+')"':'style="background:'+thumbBg(p)+'"';
254    return '<a href="'+p.u+'" data-i="'+i+'"><span class="t" '+t+'>'+(p.img?'':esc(firstWord(p.n))[0])+'</span><span><span class="nm">'+esc(p.n)+'</span><br><span class="mt">'+p.c+(p.h?' · heritage':'')+'</span></span><span class="yr">'+p.y+'</span></a>';
255  }).join(''):'<a style="cursor:default;color:var(--muted-d)">'+I.no_match+'</a>';
256}
257function search(v){
258  v=v.trim().toLowerCase();
259  if(!v){ac.classList.remove('show');return}
260  cur=matchPops(v);
261  ac.innerHTML=resultsHTML(cur);
262  ac.classList.add('show');hl=-1;
263}
264q.addEventListener('input',function(){search(this.value)});
265q.addEventListener('keydown',function(e){
266  var items=ac.querySelectorAll('a[href]');
267  if(e.key==='ArrowDown'){e.preventDefault();hl=Math.min(hl+1,items.length-1)}
268  else if(e.key==='ArrowUp'){e.preventDefault();hl=Math.max(hl-1,0)}
269  else if(e.key==='Enter'){if(cur[hl]||cur[0])location.href=(cur[hl]||cur[0]).u;return}
270  else return;
271  items.forEach(function(a,i){a.classList.toggle('hl',i===hl)});
272});
273document.addEventListener('click',function(e){if(!e.target.closest('.search'))ac.classList.remove('show')});
274// --- recherche MOBILE (barre flottante au-dessus de l'appbar, indépendante du scroll) ---
275var mq=document.getElementById('mq'),mac=document.getElementById('mac');
276if(mq&&mac){
277  mq.addEventListener('input',function(){
278    var v=this.value.trim().toLowerCase();
279    if(!v){mac.classList.remove('show');mac.innerHTML='';return;}
280    mac.innerHTML=resultsHTML(matchPops(v));mac.classList.add('show');
281  });
282}
283window.toggleSearch=function(){
284  var m=document.getElementById('msearch');if(!m)return;
285  var open=m.classList.toggle('open');
286  if(open&&mq){mq.focus();}
287  if(!open&&mac){mac.classList.remove('show');}
288};
289// ---------- MAP (Leaflet) ----------
290(function(){
291  if(typeof L==='undefined')return;
292  var map=L.map('lmap',{scrollWheelZoom:false}).setView([48.86003477363194,2.3462419779980492],17);
293  L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',{maxZoom:20,subdomains:'abcd',attribution:'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> © <a href="https://carto.com/attributions">CARTO</a>'}).addTo(map);
294  var V=[
295    {no:'1',ll:[48.86003477363194,2.3462419779980492],a:'9-11 rue des Déchargeurs — VB',n:56,big:true},
296    {no:'2·3',ll:[48.85976458091869,2.346570595253106],a:'11 rue des Halles',n:2},
297    {no:'6·7·8',ll:[48.86040,2.34795],a:'12 rue de la Ferronnerie',n:11},
298    {no:'9',ll:[48.86095,2.34520],a:'45 rue Saint-Honoré',n:11}
299  ];
300  V.forEach(function(v){
301    var sz=v.big?46:34;
302    var ic=L.divIcon({className:'',html:'<div class="venpin'+(v.big?' big':'')+'" style="width:'+sz+'px;height:'+sz+'px">No'+v.no+'</div>',iconSize:[sz,sz],iconAnchor:[sz/2,sz/2]});
303    L.marker(v.ll,{icon:ic}).addTo(map)
304      .bindPopup('<b>Cremerie No'+v.no+'</b><br>'+v.a+'<br>'+v.n+' '+I.popups+'<br><a href="#brands">'+I.detail+'</a>');
305  });
306})();
307// ---------- SNAP DU HERO (directionnel) ----------
308// La 1re partie (hero) se scrolle « d'un coup ». On complète dans le SENS du scroll :
309// vers le bas → timeline (menu + filtres + date la plus récente) ; vers le haut → hero.
310// Jamais de retour en arrière : si tu descends, tu vas en bas ; si tu montes, tu vas en haut.
311(function(){
312  var intro=document.querySelector('.intro'); if(!intro) return;
313  var sub=document.querySelector('.subbar');
314  var hdr=document.querySelector('header');
315  var snapping=false,tmr,lastY=window.scrollY,dir=1;
316  function landing(){                         // scrollY qui amène l'en-tête de la 1re section juste sous les barres collantes
317    var era=document.querySelector('#voyage .era:not(.hide)'); if(!era) return 0;
318    var head=era.querySelector('.eralbl')||era.querySelector('.yr')||era;
319    var desired=74+(sub?sub.offsetHeight:120)+14;             // juste sous header + subbar collés
320    var docY=head.getBoundingClientRect().top+window.scrollY;
321    return Math.max(0,Math.round(docY-desired));
322  }
323  window.addEventListener('scroll',function(){
324    var sy=window.scrollY;
325    if(sy!==lastY){dir=sy>lastY?1:-1;lastY=sy;}   // sens du dernier mouvement
326    if(snapping) return;
327    if(document.body.classList.contains('mode-az')) return;   // snap uniquement en Timeline
328    clearTimeout(tmr);
329    tmr=setTimeout(function(){
330      var s=window.scrollY,L=landing();
331      if(s>4 && s<L-4){                       // arrêté dans le hero → on complète dans le sens du scroll
332        snapping=true;
333        var target=dir>0?L:0;                 // bas = timeline, haut = hero
334        window.scrollTo({top:target,behavior:'smooth'});
335        setTimeout(function(){snapping=false;lastY=window.scrollY;},640);
336      }
337    },90);
338  },{passive:true});
339})();
340// ---------- ONGLETS DU MENU (Brands / Timeline / Map / Your Pop-Up) ----------
341function listTop(){ // haut de la liste : 1re section visible (Timeline) ou 1re lettre (A–Z)
342  if(document.body.classList.contains('mode-az')){
343    var l=document.querySelector('#azgrid .azletter:not(.hide)'); if(l){l.scrollIntoView();return;}
344  }else{
345    ensureLatest();
346    var e=document.querySelector('#voyage .era:not(.hide)'); if(e){e.scrollIntoView();return;}
347  }
348  window.scrollTo({top:0});
349}
350function buildAllForBottom(){ // sections du BAS (Map / Your Pop-Up) : on construit toute la liste d'abord pour un scroll fiable (sinon le lazy-load fait dériver la cible)
351  if(document.body.classList.contains('mode-az')){ if(typeof buildAZ==='function')buildAZ(); }
352  else { eras.forEach(buildEra); }
353}
354function scrollToEl(el){ // la liste est déjà entièrement construite (buildAllForBottom) → le scroll fluide atterrit juste ; 2e passe de sécurité après l'animation
355  if(!el)return; el.scrollIntoView(); setTimeout(function(){el.scrollIntoView();},900);
356}
357function gotoHash(h){
358  if(h==='#brands'){listTop();}
359  else if(h==='#timeline'){setMode('timeline');listTop();}
360  else if(h==='#map'){buildAllForBottom();scrollToEl(document.getElementById('map'));}
361  else if(h==='#yourpopup'){buildAllForBottom();scrollToEl(document.getElementById('yourpopup'));}
362}
363[].forEach.call(document.querySelectorAll('.menu a'),function(a){
364  var m=(a.getAttribute('href')||'').match(/#(brands|timeline|map|yourpopup)$/);
365  if(m){a.addEventListener('click',function(e){
366    e.preventDefault();
367    var hdr=document.querySelector('header'); if(hdr)hdr.classList.remove('menu-open'); // referme le menu burger mobile
368    history.replaceState(null,'','#'+m[1]);
369    gotoHash('#'+m[1]);
370  });}
371});
372window.addEventListener('hashchange',function(){gotoHash(location.hash);});
373if(/^#(brands|timeline|map|yourpopup)$/.test(location.hash)){setTimeout(function(){gotoHash(location.hash);},160);}
374
375// ---------- FORMULAIRE « Your Pop-Up » (mailto [email protected] + honeypot + captcha simple) ----------
376(function(){
377  var f=document.getElementById('ypForm'); if(!f)return;
378  var TT=window.T||{};
379  var A=Math.floor(Math.random()*8)+1, B2=Math.floor(Math.random()*8)+1;   // captcha additif régénéré à chaque chargement
380  var q=document.getElementById('ypCapQ'); if(q)q.textContent=(TT.yp_cap||'Anti-spam — how much is')+' '+A+' + '+B2+' ?';
381  var note=document.getElementById('ypNote');
382  function say(msg,ok){ if(note){note.textContent=msg;note.className='yp-note '+(ok?'ok':'err');} }
383  f.addEventListener('submit',function(e){
384    e.preventDefault();
385    var hp=document.getElementById('ypHp'); if(hp&&hp.value){return;}   // honeypot rempli = bot → ignoré silencieusement
386    var cap=document.getElementById('ypCap');
387    if(parseInt((cap&&cap.value)||'',10)!==A+B2){ say(TT.yp_capbad||'Incorrect answer.',false); if(cap)cap.focus(); return; }
388    var em=(document.getElementById('ypEmail').value||'').trim();
389    var nm=(document.getElementById('ypName').value||'').trim();
390    var ms=(document.getElementById('ypMsg').value||'').trim();
391    if(!em||!nm||!ms){ say(TT.yp_incomplete||'Please fill in all fields.',false); return; }
392    var subj=encodeURIComponent('Pop-Up project — '+nm);
393    var body=encodeURIComponent('Email: '+em+'\nPop-up store: '+nm+'\n\n'+ms);
394    say(TT.yp_ok||'Opening your email app…',true);
395    window.location.href='mailto:[email protected]?subject='+subj+'&body='+body;
396  });
397})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.