PageSourceSearch

https://www.experiencebypacome.com/assets/home.js?v=20261004a

js experiencebypacome.com collected 2026-10-04 19:24:54 UTC 10,313 bytes, 221 lines download raw bytes

1const gradients = [
2  "linear-gradient(160deg,#8FB1C4,#4E7288 60%,#2F4B5C)",
3  "linear-gradient(160deg,#E3C48B,#C99552 55%,#A9702F)",
4  "linear-gradient(160deg,#B7C9A8,#7E9C6E 55%,#5A7548)",
5  "linear-gradient(160deg,#D9B7A6,#B87F63 55%,#8F5A40)",
6  "linear-gradient(160deg,#9FB6C7,#5F84A0 55%,#3D5E77)"
7];
8
9const SEARCH_SYNONYMS = {
10  "private-shore-excursion-villefranche-nice-eze-monaco": "shore excursion cruise ship port tender pier croisière escale bateau paquebot crucero barco puerto villefranche",
11  "private-tour-eze-haute-couture-perfume-creation-workshop-village": "perfume parfum perfume workshop atelier fragonard galimard grasse",
12  "private-tour-nice-eze-village-haute-couture-perfume-creation-workshop": "perfume parfum atelier fragonard galimard",
13  "private-tour-monaco-monte-carlo-eze-haute-couture-perfume-creation-workshop-village": "perfume parfum atelier fragonard galimard",
14  "private-tour-nice-eze-monaco-monte-carlo-optional-perfume-workshop": "perfume parfum atelier",
15  "private-half-day-tour-museum-villa-kerylos-villa-rothschild": "villa kerylos rothschild ephrussi museum musée museo beaulieu cap ferrat garden jardin",
16  "private-full-day-tour-nice-and-its-market-old-town-museum-villa-kerylos-villa-rothschild": "villa kerylos rothschild ephrussi museum musée museo cap ferrat",
17  "private-tour-nice-hidden-river-escape": "nature river rivière río swim baignade hike randonnée",
18  "private-tour-saint-paul-de-vence-hidden-river-escape": "nature river rivière río swim baignade hike randonnée chagall maeght",
19  "private-tour-villages-provence-gourdon-saint-paul-de-vence-tourettes-sur-loup": "provence villages village perché gourdon tourrettes",
20  "private-tour-nice-old-town-market-menton-lemon": "menton lemon citron limón italy italie garden jardin",
21  "private-tour-antibes-cannes-half-day-port-market-film-festival": "cannes croisette festival antibes picasso",
22  "private-tour-cannes-film-festival-antibes-port-saint-paul-de-vence-medieval-village": "cannes croisette festival antibes picasso saint paul",
23  "private-tour-monaco-monte-carlo-casino-panoramas": "monaco monte carlo casino grand prix palace palais",
24  "private-tour-monaco-monte-carlo-casino-eze-village-medieval": "monaco casino eze village",
25};
26
27const EXP_BASE_PATH = (typeof EXP_LINK_PREFIX !== "undefined") ? EXP_LINK_PREFIX : "experiences/";
28
29const grid = document.getElementById('expGrid');
30const gridCount = document.getElementById('gridCount');
31const emptyState = document.getElementById('emptyState');
32const resultsPanel = document.getElementById('resultsPanel');
33const toolbarAction = document.getElementById('clearBtn');
34
35const RANK_LABELS = {
36  en: {1:"#1 Most Booked", 2:"#2 Most Booked", 3:"#3 Most Booked"},
37  fr: {1:"N°1 le plus réservé", 2:"N°2 le plus réservé", 3:"N°3 le plus réservé"},
38  es: {1:"N.º 1 más reservado", 2:"N.º 2 más reservado", 3:"N.º 3 más reservado"}
39};
40const PRIVATE_LABEL = {en:"Private", fr:"Privé", es:"Privado"};
41const VEHICLE_INCLUDED_LABEL = {en:"Vehicle Included", fr:"Véhicule inclus", es:"Vehículo incluido"};
42const PAGE_LANG_HOME = (document.documentElement.lang === "fr") ? "fr" : (document.documentElement.lang === "es") ? "es" : "en";
43
44EXPERIENCES.forEach((exp, i) => {
45  const card = document.createElement('a');
46  card.className = 'exp-card';
47  card.href = EXP_BASE_PATH + exp.slug + '.html';
48  card.dataset.locs = exp.locs.join(',');
49  card.dataset.dur = exp.dur;
50  card.dataset.rec = exp.rec ? '1' : '0';
51  // 10/09 : le slug (mots anglais) + des synonymes multilingues entrent dans la recherche,
52  // pour que "shore", "cruise", "croisière", "villefranche", "perfume"... trouvent la bonne fiche dans les 3 langues.
53  const extra = (SEARCH_SYNONYMS[exp.slug] || '') + ' ' + exp.slug.replace(/[-.]/g, ' ');
54  card.dataset.search = (exp.title + ' ' + exp.locs.map(l=>LOC_LABELS[l]).join(' ') + ' ' + exp.tags.join(' ') + ' ' + extra).toLowerCase();
55
56  let priceHtml;
57  if (exp.original_price) {
58    priceHtml = '<span class="price"><span style="text-decoration:line-through;color:var(--ink-soft);font-weight:500;">€' + exp.original_price + '</span> €' + exp.price + ' <span class="per-group">' + UI_STRINGS.perGroup + '</span></span>';
59  } else if (exp.price) {
60    priceHtml = '<span class="price">€' + exp.price + ' <span class="per-group">' + UI_STRINGS.perGroup + '</span></span>';
61  } else {
62    priceHtml = '<span class="price">' + UI_STRINGS.priceOnRequest + '</span>';
63  }
64  const primaryLoc = exp.headerLoc || exp.locs[0];
65  const photo = (typeof LOCATION_IMAGES !== "undefined") ? LOCATION_IMAGES[primaryLoc] : null;
66  const photoUrl = photo ? (photo.url.startsWith("http") ? photo.url : (typeof ASSETS_PREFIX !== "undefined" ? ASSETS_PREFIX : "assets/") + photo.url) : null;
67  const photoPos = (photo && photo.position) ? photo.position : "center";
68  const bgStyle = photo
69    ? `background-image:url('${photoUrl}'), ${gradients[i % gradients.length]};background-size:cover,cover;background-position:${photoPos},center;`
70    : `background:${gradients[i % gradients.length]};`;
71
72  let badgeHtml;
73  if (exp.rank) {
74    badgeHtml = '<span class="badge badge-rank badge-rank-' + exp.rank + '">' + RANK_LABELS[PAGE_LANG_HOME][exp.rank] + '</span>';
75  } else if (exp.rec) {
76    badgeHtml = '<span class="badge badge-rec">' + (UI_STRINGS.recommendedBadge || 'Recommended') + '</span>';
77  } else {
78    badgeHtml = '<span></span>';
79  }
80
81  card.innerHTML =
82    '<div class="exp-photo" style="' + bgStyle + '">' +
83      '<span class="badge badge-tag">' + UI_STRINGS.privateTourBadge + '</span>' +
84      badgeHtml +
85    '</div>' +
86    '<div class="exp-body-compact">' +
87      '<h3 class="exp-title-clean">' + exp.title + '</h3>' +
88      '<div class="exp-meta-row"><span class="exp-duration">' + exp.durLabel + '</span>' + priceHtml + '</div>' +
89    '</div>';
90  grid.appendChild(card);
91});
92
93let activeLoc = 'all';
94let activeDur = 'all';
95let activeTag = null;
96let defaultView = true;
97let visibleCount = 6;
98let hasScrolledForSearch = false;
99
100function computeMatches(){
101  const q = document.getElementById('searchInput').value.trim().toLowerCase();
102  let visible = 0;
103
104  if (defaultView) {
105    const cards = Array.from(document.querySelectorAll('.exp-card'));
106    cards.forEach((card, i) => {
107      const show = i < visibleCount;
108      card.hidden = !show;
109      if (show) visible++;
110    });
111    return visible;
112  }
113
114  document.querySelectorAll('.exp-card').forEach(card => {
115    const locs = card.dataset.locs.split(',');
116    const matchesLoc = activeLoc === 'all' || locs.includes(activeLoc);
117    const matchesDur = activeDur === 'all' || card.dataset.dur === activeDur;
118    const matchesSearch = !q || card.dataset.search.includes(q);
119    const matchesTag = !activeTag || card.dataset.search.includes(activeTag);
120    const show = matchesLoc && matchesDur && matchesSearch && matchesTag;
121    card.hidden = !show;
122    if (show) visible++;
123  });
124  return visible;
125}
126
127function updateToolbar(visible){
128  if (defaultView) {
129    const label = visible === 1 ? UI_STRINGS.recommendedSingular : UI_STRINGS.recommendedPlural;
130    gridCount.textContent = UI_STRINGS.resultsPrefix + ' ' + visible + ' ' + label;
131    if (visibleCount < EXPERIENCES.length) {
132      toolbarAction.hidden = false;
133      toolbarAction.textContent = UI_STRINGS.seeMore || 'See more';
134      toolbarAction.onclick = () => {
135        visibleCount = Math.min(visibleCount + 3, EXPERIENCES.length);
136        applyFilters({scroll:false});
137      };
138    } else {
139      toolbarAction.hidden = true;
140    }
141  } else {
142    toolbarAction.hidden = false;
143    const label = visible === 1 ? UI_STRINGS.experienceSingular : UI_STRINGS.experiencePlural;
144    gridCount.textContent = visible + ' ' + label;
145    toolbarAction.textContent = UI_STRINGS.clearSearch;
146    toolbarAction.onclick = clearSearch;
147  }
148}
149
150function applyFilters(opts){
151  opts = opts || {};
152  resultsPanel.classList.add('updating');
153  requestAnimationFrame(() => {
154    const visible = computeMatches();
155    updateToolbar(visible);
156    emptyState.style.display = visible === 0 ? 'block' : 'none';
157    setTimeout(() => resultsPanel.classList.remove('updating'), 30);
158  });
159  if (opts.scroll && !hasScrolledForSearch) {
160    hasScrolledForSearch = true;
161    setTimeout(() => resultsPanel.scrollIntoView({behavior:'smooth', block:'start'}), 160);
162  }
163}
164
165function clearSearch(){
166  document.getElementById('searchInput').value = '';
167  activeLoc = 'all'; activeDur = 'all'; activeTag = null;
168  defaultView = true;
169  hasScrolledForSearch = false;
170  document.querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
171  applyFilters({scroll:false});
172  document.getElementById('search').scrollIntoView({behavior:'smooth', block:'start'});
173}
174
175document.getElementById('locChips').addEventListener('click', e => {
176  if (!e.target.matches('.chip')) return;
177  document.querySelectorAll('#locChips .chip').forEach(c => c.classList.remove('active'));
178  e.target.classList.add('active');
179  activeLoc = e.target.dataset.loc;
180  activeTag = null;
181  defaultView = false;
182  applyFilters({scroll:true});
183});
184
185document.getElementById('durChips').addEventListener('click', e => {
186  if (!e.target.matches('.chip')) return;
187  document.querySelectorAll('#durChips .chip').forEach(c => c.classList.remove('active'));
188  e.target.classList.add('active');
189  activeDur = e.target.dataset.dur;
190  activeTag = null;
191  defaultView = false;
192  applyFilters({scroll:true});
193});
194
195document.getElementById('exploreBtn').addEventListener('click', () => {
196  defaultView = false;
197  applyFilters({scroll:true});
198});
199
200let typingTimer;
201document.getElementById('searchInput').addEventListener('input', () => {
202  clearTimeout(typingTimer);
203  const delay = defaultView ? 350 : 120;
204  typingTimer = setTimeout(() => {
205    defaultView = false;
206    applyFilters({scroll:true});
207  }, delay);
208});
209
210updateToolbar(computeMatches());
211
212// Support ?duration=short (and similar) to auto-apply a duration filter,
213// used by the vehicle-notice modal's "show me experiences without a vehicle" link.
214(function(){
215  const params = new URLSearchParams(window.location.search);
216  const wantedDur = params.get('duration');
217  if (wantedDur) {
218    const chip = document.querySelector('#durChips .chip[data-dur="' + wantedDur + '"]');
219    if (chip) chip.click();
220  }
221})();

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.