// — 1. Données des activités à Bali —
// Ces données sont hardcodées directement dans le JavaScript pour cet exemple.
// Conformément aux contraintes, si un besoin en données externes existait, nous utiliserions
// une API publique et gratuite SANS clé. Cependant, pour un jeu de données spécifique comme celui-ci,
// le hardcoding est la solution la plus simple et la plus performante.
//
// Exemple hypothétique d’une URL d’API publique gratuite (si elle existait pour ce cas précis) :
// URL API: `https://api.example.com/bali-activities-october`
// Réponse JSON attendue (structure similaire) :
// [
// {
// “activité”: “Randonnée Mont Batur”,
// “durée_estimée”: “6 heures (aller-retour)”,
// “difficulté”: “Modérée”,
// “budget_moyen”: “50-80€ (guide inclus)”,
// “idéal_pour”: “Lever de soleil, vues panoramiques”,
// “conseil”: “Partir très tôt le matin”
// },
// …
// ]
const activitiesData = [
{
‘activité’: ‘Randonnée Mont Batur’,
‘durée_estimée’: ‘6 heures (aller-retour)’,
‘difficulté’: ‘Modérée’,
‘budget_moyen’: ’50-80€ (guide inclus)’,
‘idéal_pour’: ‘Lever de soleil, vues panoramiques à couper le souffle’,
‘conseil’: ‘Partir très tôt le matin (vers 2h du matin) pour atteindre le sommet avant le lever du soleil et profiter d’un spectacle inoubliable.’
},
{
‘activité’: ‘Plage de Seminyak’,
‘durée_estimée’: ‘Journée complète’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ’10-30€ (repas/boissons)’,
‘idéal_pour’: ‘Détente, couchers de soleil flamboyants, surf pour débutants’,
‘conseil’: ‘Profiter des beach clubs branchés pour une ambiance animée au coucher du soleil, cocktails à la main et musique relaxante.’
},
{
‘activité’: ‘Snorkeling/Plongée Nusa Penida’,
‘durée_estimée’: ‘1-2 jours’,
‘difficulté’: ‘Facile à modérée’,
‘budget_moyen’: ’80-150€ (excursion bateau)’,
‘idéal_pour’: ‘Vie marine abondante, raies manta majestueuses, falaises impressionnantes’,
‘conseil’: ‘Prévoir une nuit sur l’île pour explorer davantage des sites comme Kelingking Beach et Angel’s Billabong, et éviter la foule des excursions à la journée.’
},
{
‘activité’: ‘Visite des rizières de Tegallalang’,
‘durée_estimée’: ‘2-3 heures’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ‘5-10€ (entrée/don)’,
‘idéal_pour’: ‘Photographie, balade nature apaisante, découverte culturelle des systèmes d’irrigation Subak’,
‘conseil’: ‘Y aller tôt le matin (avant 9h) pour une lumière douce et moins de monde, ce qui est parfait pour les photos et la sérénité.’
},
{
‘activité’: ‘Cours de cuisine balinaise’,
‘durée_estimée’: ‘4-5 heures’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ’35-60€’,
‘idéal_pour’: ‘Immersion culturelle, gourmands, apprendre des recettes locales authentiques’,
‘conseil’: ‘Choisissez un cours qui inclut une visite de marché local pour comprendre l’origine des ingrédients et interagir avec les locaux.’
},
{
‘activité’: ‘Temple d’Uluwatu (Kecak Dance)’,
‘durée_estimée’: ‘3-4 heures’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ’10-20€ (entrée + spectacle)’,
‘idéal_pour’: ‘Culture, vues sur l’océan spectaculaires, couchers de soleil magiques, danse traditionnelle balinaise’,
‘conseil’: ‘Arriver en avance (au moins 1h avant le coucher du soleil) pour avoir une bonne place pour le spectacle Kecak et explorer le temple.’
},
{
‘activité’: ‘Balade en quad/ATV à Ubud’,
‘durée_estimée’: ‘2-3 heures’,
‘difficulté’: ‘Modérée’,
‘budget_moyen’: ’40-70€’,
‘idéal_pour’: ‘Aventure, sensations fortes, exploration hors des sentiers battus à travers jungle et rizières’,
‘conseil’: ‘Porter des vêtements qui ne craignent pas la boue et un maillot de bain si l’activité inclut un passage dans l’eau, et n’oubliez pas la crème solaire !’
},
{
‘activité’: ‘Excursion aux cascades de Sekumpul’,
‘durée_estimée’: ‘6-8 heures (avec trajet)’,
‘difficulté’: ‘Difficile’,
‘budget_moyen’: ’40-70€ (guide local)’,
‘idéal_pour’: ‘Amoureux de la nature, randonneurs expérimentés, paysages époustouflants et baignade rafraîchissante’,
‘conseil’: ‘Prévoir de bonnes chaussures de marche antidérapantes, être prêt à se mouiller et louer un guide local est fortement recommandé pour la sécurité.’
},
{
‘activité’: ‘Visite du Waterbom Bali’,
‘durée_estimée’: ‘Journée complète’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ’40-60€ (entrée)’,
‘idéal_pour’: ‘Familles, amateurs de sensations fortes aquatiques, journées de fun et de rires’,
‘conseil’: ‘Achetez vos billets en ligne à l’avance pour éviter les files d’attente et arrivez dès l’ouverture pour profiter un maximum des attractions.’
},
{
‘activité’: ‘Cours de yoga à Ubud’,
‘durée_estimée’: ‘1-2 heures’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ’10-15€ par session’,
‘idéal_pour’: ‘Détente, bien-être, connexion avec la nature environnante, apaisement de l’esprit’,
‘conseil’: ‘Essayez un cours dans un studio avec vue sur les rizières ou au bord d’une rivière pour une expérience encore plus immersive et relaxante.’
},
{
‘activité’: ‘Volcan Kintamani et Lac Bratan’,
‘durée_estimée’: ‘Journée complète’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ’20-50€ (transport + entrées)’,
‘idéal_pour’: ‘Paysages volcaniques majestueux, temples au bord de l’eau, fraîcheur des montagnes’,
‘conseil’: ‘Combinez avec la visite du temple Ulun Danu Beratan sur le lac Bratan et les sources chaudes naturelles pour une journée complète de découvertes et de détente.’
},
{
‘activité’: ‘Massage Balinais Traditionnel’,
‘durée_estimée’: ‘1-2 heures’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ’15-40€’,
‘idéal_pour’: ‘Détente, relaxation profonde, bien-être après une journée d’exploration’,
‘conseil’: ‘Négociez toujours le prix avant le début du massage et n’hésitez pas à essayer différents spas pour trouver celui qui vous convient le mieux.’
},
{
‘activité’: ‘Visite de Tanah Lot’,
‘durée_estimée’: ‘2-3 heures’,
‘difficulté’: ‘Facile’,
‘budget_moyen’: ‘5-10€ (entrée)’,
‘idéal_pour’: ‘Photographie, architecture balinaise unique, couchers de soleil emblématiques’,
‘conseil’: ‘Visitez au coucher du soleil pour des photos spectaculaires, mais attendez-vous à une foule importante.’
}
];
// Variable pour stocker l’état du tri actuel (clé de colonne et direction)
let currentSort = { key: null, direction: ‘asc’ };
// — 2. Fonctions utilitaires pour le traitement des données —
/**
* Convertit une chaîne de durée estimée en une valeur numérique en heures pour le tri.
* Gère des formats comme “6 heures”, “1-2 jours”, “Journée complète”.
* @param {string} durationStr – La chaîne de durée (ex: “6 heures (aller-retour)”).
* @returns {number} La durée en heures (moyenne pour les plages, 8h pour une journée complète).
*/
function parseDurationToHours(durationStr) {
durationStr = durationStr.toLowerCase();
if (durationStr.includes(‘journée complète’)) return 8; // Une “journée complète” est estimée à 8 heures d’activité
if (durationStr.includes(‘jour’)) {
const match = durationStr.match(/(d+)-?(d*)s*jour/);
if (match) {
const minDays = parseInt(match[1]);
const maxDays = match[2] ? parseInt(match[2]) : minDays;
return ((minDays + maxDays) / 2) * 24; // Convertir les jours en heures (24h/jour pour un calcul simple)
}
}
if (durationStr.includes(‘heure’)) {
const match = durationStr.match(/(d+)-?(d*)s*heure/);
if (match) {
const minHours = parseInt(match[1]);
const maxHours = match[2] ? parseInt(match[2]) : minHours;
return (minHours + maxHours) / 2; // Moyenne en heures
}
}
return 9999; // Valeur élevée pour les durées non parsables, les mettant à la fin lors du tri ascendant
}
/**
* Extrait la valeur numérique minimale d’un budget pour le tri.
* Gère des formats comme “50-80€”, “10-30€”, “10€”.
* @param {string} budgetStr – La chaîne de budget (ex: “50-80€ (guide inclus)”).
* @returns {number} Le budget minimal en euros, ou 0 si non parsable.
*/
function parseBudgetToNumber(budgetStr) {
if (!budgetStr) return 0;
const cleanedStr = budgetStr.replace(/[^d-]/g, ”).trim(); // Garde seulement chiffres et tirets
const parts = cleanedStr.split(‘-‘);
if (parts.length > 0 && parts[0]) {
return parseInt(parts[0]); // Prend le premier chiffre comme base pour le tri (souvent la borne inférieure)
}
return 0; // Si aucun chiffre n’est trouvé, ou chaîne vide
}
/**
* Crée une cellule de tableau (
) avec le contenu et les classes spécifiés.
* @param {string} content – Le contenu HTML ou texte de la cellule.
* @param {string} [classes=”] – Les classes CSS Tailwind à appliquer à la cellule.
* @returns {HTMLElement} L’élément
créé.
*/
function createTableCell(content, classes = ”) {
const cell = document.createElement(‘td’);
// `whitespace-normal` pour permettre au texte de s’enrouler
cell.className = `px-6 py-4 whitespace-normal text-sm text-gray-800 ${classes}`;
cell.innerHTML = content;
return cell;
}
// — 3. Rendu du tableau des activités —
/**
* Rend (ou re-rend) le tableau des activités dans le DOM en fonction des données filtrées et triées.
* @param {Array} dataToRender – Le tableau d’objets activités à afficher.
*/
function renderActivities(dataToRender) {
const tableBody = document.getElementById(‘activitiesTableBody’);
const noResults = document.getElementById(‘noResults’);
tableBody.innerHTML = ”; // Vide le contenu actuel du tableau
if (dataToRender.length === 0) {
noResults.classList.remove(‘hidden’); // Affiche le message “aucun résultat”
return;
} else {
noResults.classList.add(‘hidden’); // Cache le message s’il y a des résultats
}
dataToRender.forEach(activity => {
const row = document.createElement(‘tr’);
// Ajout d’un effet visuel au survol de la ligne
row.className = ‘hover:bg-blue-50 transition duration-150 ease-in-out’;
// Ajout des cellules au fur et à mesure
row.appendChild(createTableCell(activity[‘activité’], ‘font-medium text-blue-900’));
row.appendChild(createTableCell(activity[‘durée_estimée’]));
// Style dynamique de la difficulté selon sa valeur
let difficultyColor = ”;
switch (activity[‘difficulté’]) {
case ‘Facile’:
difficultyColor = ‘text-green-600’;
break;
case ‘Modérée’:
difficultyColor = ‘text-yellow-600’;
break;
case ‘Difficile’:
difficultyColor = ‘text-red-600’;
break;
}
row.appendChild(createTableCell(activity[‘difficulté’], `font-semibold ${difficultyColor}`));
row.appendChild(createTableCell(activity[‘budget_moyen’]));
row.appendChild(createTableCell(activity[‘idéal_pour’]));
row.appendChild(createTableCell(activity[‘conseil’], ‘italic text-gray-600 text-xs’));
tableBody.appendChild(row);
});
}
// — 4. Logique de filtrage et de tri des données —
/**
* Applique tous les filtres et options de tri aux données originales,
* puis déclenche le rendu du tableau mis à jour.
*/
function applyFiltersAndSort() {
let filteredData = […activitiesData]; // Crée une copie des données originales pour les manipulations
// 1. Filtrage par recherche textuelle (sur toutes les colonnes visibles)
const searchTerm = document.getElementById(‘searchInput’).value.toLowerCase();
if (searchTerm) {
filteredData = filteredData.filter(activity =>
// Vérifie si le terme de recherche est présent dans n’importe quelle valeur de l’activité
Object.values(activity).some(value =>
String(value).toLowerCase().includes(searchTerm)
)
);
}
// 2. Filtrage par difficulté
const difficultyFilter = document.getElementById(‘difficultyFilter’).value;
if (difficultyFilter) {
filteredData = filteredData.filter(activity =>
activity[‘difficulté’] === difficultyFilter
);
}
// 3. Tri via le menu déroulant (priorité sur le tri par colonne)
const sortOrderDropdown = document.getElementById(‘sortOrder’).value;
if (sortOrderDropdown) {
filteredData.sort((a, b) => {
let valA, valB;
if (sortOrderDropdown.includes(‘budget’)) {
valA = parseBudgetToNumber(a[‘budget_moyen’]);
valB = parseBudgetToNumber(b[‘budget_moyen’]);
} else if (sortOrderDropdown.includes(‘duration’)) {
valA = parseDurationToHours(a[‘durée_estimée’]);
valB = parseDurationToHours(b[‘durée_estimée’]);
} else if (sortOrderDropdown.includes(‘activité’)) {
valA = String(a[‘activité’]).toLowerCase();
valB = String(b[‘activité’]).toLowerCase();
}
if (sortOrderDropdown.endsWith(‘asc’)) {
return valA > valB ? 1 : valA < valB ? -1 : 0;
} else { // desc
return valA valB ? -1 : 0;
}
});
}
// 4. Tri par clic sur les entêtes de colonne (appliqué si aucun tri n’est sélectionné dans le dropdown)
else if (currentSort.key) {
filteredData.sort((a, b) => {
let valA, valB;
// Gérer le tri numérique pour budget et durée, sinon trier comme du texte
if (currentSort.key === ‘budget_moyen’) {
valA = parseBudgetToNumber(a[currentSort.key]);
valB = parseBudgetToNumber(b[currentSort.key]);
} else if (currentSort.key === ‘durée_estimée’) {
valA = parseDurationToHours(a[currentSort.key]);
valB = parseDurationToHours(b[currentSort.key]);
} else {
valA = String(a[currentSort.key]).toLowerCase();
valB = String(b[currentSort.key]).toLowerCase();
}
if (currentSort.direction === ‘asc’) {
return valA > valB ? 1 : valA < valB ? -1 : 0;
} else { // 'desc'
return valA valB ? -1 : 0;
}
});
}
// Enfin, met à jour l’affichage avec les données filtrées et triées
renderActivities(filteredData);
}
// — 5. Écouteurs d’événements pour l’interactivité —
document.addEventListener(‘DOMContentLoaded’, () => {
// Rend le tableau avec toutes les activités au chargement initial de la page
renderActivities(activitiesData);
// Écouteurs pour déclencher le filtrage/tri lors des interactions utilisateur
document.getElementById(‘searchInput’).addEventListener(‘input’, applyFiltersAndSort);
document.getElementById(‘difficultyFilter’).addEventListener(‘change’, applyFiltersAndSort);
document.getElementById(‘sortOrder’).addEventListener(‘change’, () => {
currentSort = { key: null, direction: ‘asc’ }; // Reset column sort when dropdown is used
updateSortIcons(); // Clear sort icons
applyFiltersAndSort();
});
// Écouteurs pour le tri des colonnes via les entêtes de tableau
document.querySelectorAll(‘th[data-sort-key]’).forEach(header => {
header.addEventListener(‘click’, () => {
const sortKey = header.getAttribute(‘data-sort-key’);
// Si la même colonne est cliquée, inverse la direction du tri
if (currentSort.key === sortKey) {
currentSort.direction = currentSort.direction === ‘asc’ ? ‘desc’ : ‘asc’;
} else {
// Si une nouvelle colonne est cliquée, réinitialise la direction à ‘asc’
currentSort.key = sortKey;
currentSort.direction = ‘asc’;
}
document.getElementById(‘sortOrder’).value = “”; // Réinitialise le tri dropdown
applyFiltersAndSort(); // Applique le nouveau tri
updateSortIcons(); // Met à jour les icônes de tri visuelles
});
});
/**
* Met à jour les icônes visuelles d’indication de tri sur les entêtes de colonne.
*/
function updateSortIcons() {
document.querySelectorAll(‘th[data-sort-key]’).forEach(th => {
const icon = th.querySelector(‘.sort-icon’);
if (icon) {
icon.innerHTML = ”; // Vide l’icône existante
icon.classList.remove(‘opacity-100’, ‘opacity-0’); // Réinitialise l’opacité
if (th.getAttribute(‘data-sort-key’) === currentSort.key) {
icon.innerHTML = currentSort.direction === ‘asc’ ? ‘▲’ : ‘▼’; // Flèche haut/bas
icon.classList.add(‘opacity-100’); // Affiche l’icône
} else {
icon.classList.add(‘opacity-0’); // Cache l’icône des colonnes non triées
}
}
});
}
});
{“@context”:”https://schema.org”,”@type”:”FAQPage”,”mainEntity”:[{“@type”:”Question”,”name”:”Quel type de vu00eatements dois-je emporter pour Bali en octobre ? ud83dudc55″,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Il est recommandu00e9 d’emporter des vu00eatements lu00e9gers, amples et respirants, principalement en coton ou en lin, adaptu00e9s u00e0 un climat tropical chaud et humide. Pru00e9voyez u00e9galement un maillot de bain, un chapeau, des lunettes de soleil, et un vu00eatement de pluie lu00e9ger pour les averses passagu00e8res. Des tenues respectueuses (u00e9paules et genoux couverts) sont nu00e9cessaires pour les visites de temples.”}},{“@type”:”Question”,”name”:”Est-ce que le surf est bon u00e0 Bali en octobre ? ud83cudfc4u200du2640ufe0f”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Oui, octobre est gu00e9nu00e9ralement un bon mois pour le surf u00e0 Bali. La saison des pluies commence tout juste, offrant encore de bonnes vagues, surtout sur la cu00f4te ouest de l’u00eele qui commence u00e0 u00eatre plus active. Il y a des spots adaptu00e9s aux du00e9butants et aux surfeurs plus expu00e9rimentu00e9s.”}},{“@type”:”Question”,”name”:”Les moustiques sont-ils un problu00e8me en octobre u00e0 Bali ? ud83eudd9f”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”u00c9tant donnu00e9 que la saison des pluies approche, l’humiditu00e9 augmente et les moustiques peuvent u00eatre plus pru00e9sents. Il est fortement conseillu00e9 d’utiliser un bon ru00e9pulsif, surtout au lever et au coucher du soleil, et de dormir sous une moustiquaire si votre hu00e9bergement en propose une.”}},{“@type”:”Question”,”name”:”Dois-je ru00e9server mon hu00e9bergement et mes excursions u00e0 l’avance pour octobre ? ud83duddd3ufe0f”,”acceptedAnswer”:{“@type”:”Answer”,”text”:”Bien qu’octobre soit une pu00e9riode de transition et moins chargu00e9e que la haute saison, il est toujours recommandu00e9 de ru00e9server votre hu00e9bergement et les excursions populaires u00e0 l’avance. Cela garantit la disponibilitu00e9, surtout si vous avez des pru00e9fu00e9rences spu00e9cifiques ou si vous voyagez en groupe.”}}]}
Découvrez la limonade Bonbon Anglais, la boisson gazeuse emblématique de Madagascar qui séduit petits et grands par son goût unique et rafraîchissant. Fabriquée avec des ingrédients de qualité et des arômes naturels, Bonbon Anglais vous offre une expérience gustative inégalée. Parfaite pour toutes les occasions, cette limonade apportera une touche d’exotisme et de fraîcheur à vos moments de détente. Essayez-la dès aujourd’hui et laissez-vous emporter par le peps et l’authenticité de Bonbon Anglais, la star des boissons malgaches !