<div class="trip-overview-raw" style="display: none;"> <script> (function(){ try { var mapData = {"summary":{"title":"7-Day Brazil Luxury Trip","dates":"February 2026","travelers":"1 Traveler"},"weather":{"desc":"Warm and humid with occasional rain","temp":"25-35°C"},"security":{"status":"Medium Risk","tip":"Stay aware of your surroundings and avoid isolated areas at night."},"budget":{"total":"€2,620–3,850","items":[{"label":"Accommodations","val":"€1,500–2,100"},{"label":"Transport","val":"€700–1,050"},{"label":"Activities & Food","val":"€420–700"}]},"map":{"waypoints":[{"name":"Rio de Janeiro","lat":-22.9068,"lon":-43.1729,"days":"Day 1-3","desc":"Vibrant city known for its Carnival and beaches.","photo":"","highlights":["Sugarloaf Mountain","Ipanema Beach"]},{"name":"Paraty","lat":-23.2192,"lon":-44.71,"days":"Day 4-5","desc":"Charming colonial town with stunning nature.","photo":"","highlights":["UNESCO-listed old town","Saco do Mamanguá"]},{"name":"São Paulo","lat":-23.5505,"lon":-46.6333,"days":"Day 6-7","desc":"Cultural hub with vibrant nightlife.","photo":"","highlights":["Vila Madalena","Beco do Batman"]}],"route":[0,1,2]},"days":[{"day_index":1,"day_title":"Arrival in Rio de Janeiro","image_query":"Rio de Janeiro Ipanema Beach Brazil","html_placeholder":"[DAY_IMAGE_01]"},{"day_index":2,"day_title":"Rio – Carnival & Nightlife","image_query":"Rio de Janeiro Carnival Brazil","html_placeholder":"[DAY_IMAGE_02]"},{"day_index":3,"day_title":"Rio – Beach & Urban Exploration","image_query":"Rio de Janeiro Sugarloaf Mountain Brazil","html_placeholder":"[DAY_IMAGE_03]"},{"day_index":4,"day_title":"Rio to Paraty – Colonial Charm & Nature","image_query":"Paraty Colonial Town Brazil","html_placeholder":"[DAY_IMAGE_04]"},{"day_index":5,"day_title":"Paraty – Islands & Waterfalls","image_query":"Paraty Saco do Mamanguá Brazil","html_placeholder":"[DAY_IMAGE_05]"},{"day_index":6,"day_title":"Paraty to São Paulo – Urban Pulse","image_query":"São Paulo Vila Madalena Brazil","html_placeholder":"[DAY_IMAGE_06]"},{"day_index":7,"day_title":"São Paulo – Culture & Departure","image_query":"São Paulo Beco do Batman Brazil","html_placeholder":"[DAY_IMAGE_07]"}]}; var dayImagesRaw = [{"day_index":1,"day_title":"Arrival in Rio de Janeiro","placeholder":"[DAY_IMAGE_01]","day_image_url":"https://images.unsplash.com/photo-1636491627811-3c27fb9016d5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxSaW8lMjBkZSUyMEphbmVpcm8lMjBJcGFuZW1hJTIwQmVhY2glMjBCcmF6aWx8ZW58MHwwfHx8MTc2OTc2Mjk0NXww&ixlib=rb-4.1.0&q=80&w=1080","day_image_html":"<img src=\"https://images.unsplash.com/photo-1636491627811-3c27fb9016d5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxSaW8lMjBkZSUyMEphbmVpcm8lMjBJcGFuZW1hJTIwQmVhY2glMjBCcmF6aWx8ZW58MHwwfHx8MTc2OTc2Mjk0NXww&ixlib=rb-4.1.0&q=80&w=1080\" width=\"640\" alt=\"Arrival in Rio de Janeiro\" style=\"display:block;margin:20px auto;border-radius:8px;\">"}, {"day_index":2,"day_title":"Rio – Carnival & Nightlife","placeholder":"[DAY_IMAGE_02]","day_image_url":"https://images.unsplash.com/photo-1592082990682-492e47517e53?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxSaW8lMjBkZSUyMEphbmVpcm8lMjBDYXJuaXZhbCUyMEJyYXppbHxlbnwwfDB8fHwxNzY5NzYyOTQ1fDA&ixlib=rb-4.1.0&q=80&w=1080","day_image_html":"<img src=\"https://images.unsplash.com/photo-1592082990682-492e47517e53?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxSaW8lMjBkZSUyMEphbmVpcm8lMjBDYXJuaXZhbCUyMEJyYXppbHxlbnwwfDB8fHwxNzY5NzYyOTQ1fDA&ixlib=rb-4.1.0&q=80&w=1080\" width=\"640\" alt=\"Rio – Carnival & Nightlife\" style=\"display:block;margin:20px auto;border-radius:8px;\">"}, {"day_index":3,"day_title":"Rio – Beach & Urban Exploration","placeholder":"[DAY_IMAGE_03]","day_image_url":"https://images.unsplash.com/photo-1619546952812-520e98064a52?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxSaW8lMjBkZSUyMEphbmVpcm8lMjBTdWdhcmxvYWYlMjBNb3VudGFpbiUyMEJyYXppbHxlbnwwfDB8fHwxNzY5NzYyOTQ2fDA&ixlib=rb-4.1.0&q=80&w=1080","day_image_html":"<img src=\"https://images.unsplash.com/photo-1619546952812-520e98064a52?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxSaW8lMjBkZSUyMEphbmVpcm8lMjBTdWdhcmxvYWYlMjBNb3VudGFpbiUyMEJyYXppbHxlbnwwfDB8fHwxNzY5NzYyOTQ2fDA&ixlib=rb-4.1.0&q=80&w=1080\" width=\"640\" alt=\"Rio – Beach & Urban Exploration\" style=\"display:block;margin:20px auto;border-radius:8px;\">"}, {"day_index":4,"day_title":"Rio to Paraty – Colonial Charm & Nature","placeholder":"[DAY_IMAGE_04]","day_image_url":"https://images.unsplash.com/photo-1568796165028-048b7810ea13?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxQYXJhdHklMjBDb2xvbmlhbCUyMFRvd24lMjBCcmF6aWx8ZW58MHwwfHx8MTc2OTc2Mjk0Nnww&ixlib=rb-4.1.0&q=80&w=1080","day_image_html":"<img src=\"https://images.unsplash.com/photo-1568796165028-048b7810ea13?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxQYXJhdHklMjBDb2xvbmlhbCUyMFRvd24lMjBCcmF6aWx8ZW58MHwwfHx8MTc2OTc2Mjk0Nnww&ixlib=rb-4.1.0&q=80&w=1080\" width=\"640\" alt=\"Rio to Paraty – Colonial Charm & Nature\" style=\"display:block;margin:20px auto;border-radius:8px;\">"}, {"day_index":5,"day_title":"Paraty – Islands & Waterfalls","placeholder":"[DAY_IMAGE_05]","day_image_url":"https://images.unsplash.com/photo-1578166833233-b6c23deaa535?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwyfHxCcmF6aWwlMjB0cmF2ZWwlMjBsYW5kc2NhcGV8ZW58MHwwfHx8MTc2OTc2MjkzOXww&ixlib=rb-4.1.0&q=80&w=1080","day_image_html":"<img src=\"https://images.unsplash.com/photo-1578166833233-b6c23deaa535?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwyfHxCcmF6aWwlMjB0cmF2ZWwlMjBsYW5kc2NhcGV8ZW58MHwwfHx8MTc2OTc2MjkzOXww&ixlib=rb-4.1.0&q=80&w=1080\" width=\"640\" alt=\"Paraty – Islands & Waterfalls\" style=\"display:block;margin:20px auto;border-radius:8px;\">"}, {"day_index":6,"day_title":"Paraty to São Paulo – Urban Pulse","placeholder":"[DAY_IMAGE_06]","day_image_url":"https://images.unsplash.com/photo-1597582983019-60807aa25825?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxTJUMzJUEzbyUyMFBhdWxvJTIwVmlsYSUyME1hZGFsZW5hJTIwQnJhemlsfGVufDB8MHx8fDE3Njk3NjI5NDd8MA&ixlib=rb-4.1.0&q=80&w=1080","day_image_html":"<img src=\"https://images.unsplash.com/photo-1597582983019-60807aa25825?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxTJUMzJUEzbyUyMFBhdWxvJTIwVmlsYSUyME1hZGFsZW5hJTIwQnJhemlsfGVufDB8MHx8fDE3Njk3NjI5NDd8MA&ixlib=rb-4.1.0&q=80&w=1080\" width=\"640\" alt=\"Paraty to São Paulo – Urban Pulse\" style=\"display:block;margin:20px auto;border-radius:8px;\">"}, {"day_index":7,"day_title":"São Paulo – Culture & Departure","placeholder":"[DAY_IMAGE_07]","day_image_url":"https://images.unsplash.com/photo-1502315321875-1c72a28b1b10?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxTJUMzJUEzbyUyMFBhdWxvJTIwQmVjbyUyMGRvJTIwQmF0bWFuJTIwQnJhemlsfGVufDB8MHx8fDE3Njk3NjI5NDh8MA&ixlib=rb-4.1.0&q=80&w=1080","day_image_html":"<img src=\"https://images.unsplash.com/photo-1502315321875-1c72a28b1b10?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w4NjAxNDN8MHwxfHNlYXJjaHwxfHxTJUMzJUEzbyUyMFBhdWxvJTIwQmVjbyUyMGRvJTIwQmF0bWFuJTIwQnJhemlsfGVufDB8MHx8fDE3Njk3NjI5NDh8MA&ixlib=rb-4.1.0&q=80&w=1080\" width=\"640\" alt=\"São Paulo – Culture & Departure\" style=\"display:block;margin:20px auto;border-radius:8px;\">"}]; var dayImages = Array.isArray(dayImagesRaw) ? dayImagesRaw : (dayImagesRaw ? [dayImagesRaw] : []); var travelerName = "Florian de BauerWebPro"; var destination = "Brazil"; function extractDays(dayRange) { if (!dayRange) return []; var match = dayRange.toString().match(/Day\s+(\d+)(?:-(\d+))?/); if (!match) return []; var start = parseInt(match[1]); var end = match[2] ? parseInt(match[2]) : start; var days = []; for (var i = start; i <= end; i++) { days.push(i); } return days; } function findImageForWaypoint(waypoint, dayImages) { var days = extractDays(waypoint.days); if (days.length === 0) return null; var firstDay = days[0]; for (var i = 0; i < dayImages.length; i++) { if (dayImages[i].day_index === firstDay) { return dayImages[i].day_image_url; } } return null; } if (mapData && mapData.map && mapData.map.waypoints) { for (var i = 0; i < mapData.map.waypoints.length; i++) { var wp = mapData.map.waypoints[i]; var imageUrl = findImageForWaypoint(wp, dayImages); if (imageUrl) { wp.photo = imageUrl; } } } window.TD_MAP_DATA_ENRICHED = mapData; window.travelerName = travelerName; window.destination = destination; } catch(e) { console.error('Error enriching map data:', e); window.TD_MAP_DATA_ENRICHED = {"summary":{"title":"7-Day Brazil Luxury Trip","dates":"February 2026","travelers":"1 Traveler"},"weather":{"desc":"Warm and humid with occasional rain","temp":"25-35°C"},"security":{"status":"Medium Risk","tip":"Stay aware of your surroundings and avoid isolated areas at night."},"budget":{"total":"€2,620–3,850","items":[{"label":"Accommodations","val":"€1,500–2,100"},{"label":"Transport","val":"€700–1,050"},{"label":"Activities & Food","val":"€420–700"}]},"map":{"waypoints":[{"name":"Rio de Janeiro","lat":-22.9068,"lon":-43.1729,"days":"Day 1-3","desc":"Vibrant city known for its Carnival and beaches.","photo":"","highlights":["Sugarloaf Mountain","Ipanema Beach"]},{"name":"Paraty","lat":-23.2192,"lon":-44.71,"days":"Day 4-5","desc":"Charming colonial town with stunning nature.","photo":"","highlights":["UNESCO-listed old town","Saco do Mamanguá"]},{"name":"São Paulo","lat":-23.5505,"lon":-46.6333,"days":"Day 6-7","desc":"Cultural hub with vibrant nightlife.","photo":"","highlights":["Vila Madalena","Beco do Batman"]}],"route":[0,1,2]},"days":[{"day_index":1,"day_title":"Arrival in Rio de Janeiro","image_query":"Rio de Janeiro Ipanema Beach Brazil","html_placeholder":"[DAY_IMAGE_01]"},{"day_index":2,"day_title":"Rio – Carnival & Nightlife","image_query":"Rio de Janeiro Carnival Brazil","html_placeholder":"[DAY_IMAGE_02]"},{"day_index":3,"day_title":"Rio – Beach & Urban Exploration","image_query":"Rio de Janeiro Sugarloaf Mountain Brazil","html_placeholder":"[DAY_IMAGE_03]"},{"day_index":4,"day_title":"Rio to Paraty – Colonial Charm & Nature","image_query":"Paraty Colonial Town Brazil","html_placeholder":"[DAY_IMAGE_04]"},{"day_index":5,"day_title":"Paraty – Islands & Waterfalls","image_query":"Paraty Saco do Mamanguá Brazil","html_placeholder":"[DAY_IMAGE_05]"},{"day_index":6,"day_title":"Paraty to São Paulo – Urban Pulse","image_query":"São Paulo Vila Madalena Brazil","html_placeholder":"[DAY_IMAGE_06]"},{"day_index":7,"day_title":"São Paulo – Culture & Departure","image_query":"São Paulo Beco do Batman Brazil","html_placeholder":"[DAY_IMAGE_07]"}]} || {}; window.travelerName = "Florian de BauerWebPro"; window.destination = "Brazil"; } })(); </script> </div> <script> (function () { 'use strict'; console.log('[TD Map V8] Initializing...'); function loadLeaflet(callback) { if (typeof L !== 'undefined') { callback(); return; } console.log('[TD Map V8] Loading Leaflet libraries...'); var link = document.createElement('link'); link.rel = 'stylesheet'; link.href = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.css'; document.head.appendChild(link); var script = document.createElement('script'); script.src = 'https://unpkg.com/leaflet@1.9.4/dist/leaflet.js'; script.onload = function () { console.log('[TD Map V8] Leaflet loaded!'); callback(); }; script.onerror = function () { console.error('[TD Map V8] Failed to load Leaflet.'); var container = document.getElementById('td-map-v8-container'); if (container) container.innerHTML = 'Map Error: Could not load libraries.'; }; document.head.appendChild(script); } function decodeHTML(str) { var txt = document.createElement('textarea'); var decoded = str; var max = 5, i = 0; while ((decoded.includes('<') || decoded.includes('&')) && i < max) { txt.innerHTML = decoded; decoded = txt.value; i++; } return decoded; } function extractMapData(content) { var startMarkers = ['var mapData=', 'var mapData =', 'mapData=']; for (var m = 0; m < startMarkers.length; m++) { var startIdx = content.indexOf(startMarkers[m]); if (startIdx !== -1) { var braceStart = content.indexOf('{', startIdx); if (braceStart === -1) continue; var depth = 0, braceEnd = -1; for (var i = braceStart; i < content.length; i++) { if (content[i] === '{') depth++; if (content[i] === '}') depth--; if (depth === 0) { braceEnd = i; break; } } if (braceEnd !== -1) { var jsonStr = content.substring(braceStart, braceEnd + 1); try { return JSON.parse(jsonStr); } catch (e) { jsonStr = jsonStr.replace(/&/g, '&').replace(/\\"/g, '"'); try { return JSON.parse(jsonStr); } catch (e2) { } } } } } return null; } function extractVariable(content, varName) { var match = content.match(new RegExp('var\\s+' + varName + '\\s*=\\s*["\']([^"\']+)["\']')); return match ? match[1] : null; } function initMap() { var mapData = window.TD_MAP_DATA_ENRICHED; var travelerName = window.travelerName || 'Traveler'; var destination = window.destination || 'Destination'; if (!mapData) { var rawEl = document.querySelector('.trip-overview-raw') || document.querySelector('#trip-overview-raw'); if (!rawEl) { console.warn('[TD Map V8] No data element found.'); return; } var content = decodeHTML(rawEl.innerHTML || rawEl.textContent); rawEl.style.display = 'none'; mapData = extractMapData(content); if (!mapData) { console.error('[TD Map V8] JSON extraction failed.'); return; } travelerName = extractVariable(content, 'travelerName') || travelerName; destination = extractVariable(content, 'destination') || destination; } var container = document.getElementById('td-map-v8-container'); if (!container) { container = document.createElement('div'); container.id = 'td-map-v8-container'; container.style.width = '100%'; container.style.display = 'flex'; container.style.flexDirection = 'column'; var rawEl = document.querySelector('.trip-overview-raw'); if (rawEl && rawEl.parentNode) { rawEl.parentNode.insertBefore(container, rawEl.nextSibling); } else { document.body.appendChild(container); } } container.innerHTML = ''; buildMapUI(container, mapData, travelerName, destination); } function buildMapUI(container, mapData, travelerName, destination) { var waypoints = mapData.map.waypoints || []; if (waypoints.length === 0) return; // Préparer les données pour la STATS BAR var weatherTemp = mapData.weather ? mapData.weather.temp : ''; var weatherDesc = mapData.weather ? mapData.weather.desc : ''; var securityStatus = mapData.security ? mapData.security.status : ''; var securityTip = mapData.security ? mapData.security.tip : ''; var budgetTotal = mapData.budget ? mapData.budget.total : ''; container.innerHTML = '<div id="td-map-v8" style="font-family: \'Inter\', sans-serif; width: 100%; background: #fff; border-radius: 0.75rem; overflow: hidden; box-shadow: 0 4px 20px rgba(0,0,0,0.08);"><div style="background: #001427; padding: 1.1rem 1.5rem;"><div style="display: flex; justify-content: space-between; align-items: center;"><div><h2 style="margin: 0; font-family: \'Poppins\', sans-serif; font-size: 1.1rem; font-weight: 600; color: white;">' + travelerName + '\'s Journey</h2><p style="margin: 0.25rem 0 0; font-size: 0.8rem; color: rgba(255,255,255,0.7);">' + (mapData.summary ? mapData.summary.dates : '') + ' • ' + (mapData.summary ? mapData.summary.travelers : '') + '</p></div><div style="text-align: right;"><span style="display: block; font-size: 0.7rem; text-transform: uppercase; color: rgba(255,255,255,0.5); letter-spacing: 1px; font-weight: 600;">Est. Budget</span><span style="font-size: 1rem; font-weight: 700; color: #E98A15;">' + budgetTotal + '</span></div></div></div><div style="display: grid; grid-template-columns: 1fr 1fr; background: #f8f9fa; border-left: 1px solid #eef0f2; border-right: 1px solid #eef0f2; padding: 0.9rem 1.5rem;"><div style="border-right: 1px solid #eef0f2; padding-right: 0.9rem;"><span style="display: block; font-size: 0.65rem; text-transform: uppercase; color: #8898aa; font-weight: 700; letter-spacing: 0.5px; margin-bottom: 0.25rem;">Weather</span><div style="display: flex; align-items: center; gap: 0.5rem;"><span style="font-size: 1.1rem;">☀️</span><div><span style="display: block; font-size: 0.8rem; font-weight: 600; color: #001427;">' + weatherTemp + '</span><span style="display: block; font-size: 0.7rem; color: #525f7f;">' + weatherDesc + '</span></div></div></div><div style="padding-left: 0.9rem;"><span style="display: block; font-size: 0.65rem; text-transform: uppercase; color: #8898aa; font-weight: 700; letter-spacing: 0.5px; margin-bottom: 0.25rem;">Safety</span><div style="display: flex; align-items: center; gap: 0.5rem;"><span style="font-size: 1.1rem;">🛡️</span><div><span style="display: block; font-size: 0.8rem; font-weight: 600; color: #001427;">' + securityStatus + '</span><span style="display: block; font-size: 0.7rem; color: #525f7f; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 120px;">' + securityTip + '</span></div></div></div></div><div id="leaflet-map-v8" style="width: 100%; height: 25rem; background: #e7e0da; z-index: 1;"></div><div id="timeline-v8" style="background: #f8f9fa; padding: 1rem 1.25rem; border-top: 3px solid #e98a15; overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch;"><div style="display: inline-flex; gap: 0.5rem; padding-bottom: 5px;"></div></div><div style="display: flex; justify-content: center; gap: 1.5rem; padding: 0.8rem; font-size: 0.75rem; color: #001427; font-weight: 500; border-top: 1px solid #eee;"><span style="display: flex; align-items: center; gap: 0.4rem;"><span style="width: 0.6rem; height: 0.6rem; background: #e98a15; border-radius: 50%;"></span> ' + destination + '</span><span style="display: flex; align-items: center; gap: 0.4rem;"><span>📷</span> Click markers for photos</span></div></div><style>.leaflet-popup-content-wrapper { border-radius: 0.75rem !important; padding: 0 !important; overflow: hidden; box-shadow: 0 10px 25px rgba(0,0,0,0.15) !important; }.leaflet-popup-content { margin: 0 !important; width: 280px !important; }.leaflet-container { font-family: \'Inter\', sans-serif !important; }.timeline-item-v8 { display: inline-flex; flex-direction: column; align-items: center; cursor: pointer; padding: 0.5rem 0.75rem; border-radius: 0.5rem; min-width: 4.5rem; transition: all 0.2s ease; border: 1px solid transparent; }.timeline-item-v8:hover { background: #fff; border-color: #ddd; transform: translateY(-2px); }.timeline-item-v8.active { background: #e98a15; color: white !important; box-shadow: 0 4px 12px rgba(233, 138, 21, 0.4); }.timeline-item-v8.active span { color: white !important; }#timeline-v8::-webkit-scrollbar { height: 6px; }#timeline-v8::-webkit-scrollbar-track { background: transparent; }#timeline-v8::-webkit-scrollbar-thumb { background: #ddd; border-radius: 10px; }#timeline-v8::-webkit-scrollbar-thumb:hover { background: #ccc; }</style>'; var map = L.map('leaflet-map-v8', { scrollWheelZoom: true, zoomControl: true }).setView([0, 0], 2); L.tileLayer('https://{s}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{r}.png', { maxZoom: 19, attribution: '© <a href="https://carto.com/">CARTO</a>' }).addTo(map); var markers = []; waypoints.forEach(function(wp, i) { var dayLabelRaw = wp.days ? wp.days.toString() : ""; var dayLabelClean = dayLabelRaw.split('-')[0]; var iconHtml = '<div style="background: linear-gradient(135deg, #e98a15, #d47710); color: white; width: 2.2rem; height: 2.2rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; border: 3px solid white; box-shadow: 0 4px 15px rgba(233,138,21,0.5); font-size: 0.9rem;">' + (i + 1) + '</div>'; var icon = L.divIcon({ html: iconHtml, className: 'td-marker-v8', iconSize: [36, 36], iconAnchor: [18, 18], popupAnchor: [0, -24] }); var popupContent = '<div style="font-family: \'Inter\', sans-serif;"><div style="width: 100%; height: 9rem; background: #eee; overflow: hidden; position: relative;"><img src="' + (wp.photo || '') + '" style="width: 100%; height: 100%; object-fit: cover; display: block;" onerror="this.parentElement.style.display=\'none\'"></div><div style="padding: 1rem;"><span style="background: rgba(233,138,21,0.1); color: #d47710; padding: 0.25rem 0.6rem; border-radius: 2rem; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;">' + wp.days + '</span><h4 style="margin: 0.5rem 0 0.25rem; font-size: 1rem; color: #001427; font-weight: 700; line-height: 1.3;">' + wp.name + '</h4><p style="font-size: 0.8rem; color: #666; margin: 0; line-height: 1.4;">' + (wp.desc || '') + '</p></div></div>'; var marker = L.marker([wp.lat, wp.lon], { icon: icon }).bindPopup(popupContent).addTo(map); markers.push(marker); var tlItem = document.createElement('div'); tlItem.className = 'timeline-item-v8'; var timelineDayText = dayLabelClean; if (/^\d+$/.test(timelineDayText.trim())) { timelineDayText = "Day " + timelineDayText; } tlItem.innerHTML = '<span style="font-size: 0.65rem; color: #999; font-weight: 600; text-transform: uppercase;">' + timelineDayText + '</span><span style="font-size: 0.8rem; font-weight: 700; color: #001427;">' + wp.name + '</span>'; tlItem.onclick = function() { map.setView([wp.lat, wp.lon], 11, { animate: true }); marker.openPopup(); var allItems = document.querySelectorAll('.timeline-item-v8'); for (var j = 0; j < allItems.length; j++) { allItems[j].classList.remove('active'); } tlItem.classList.add('active'); tlItem.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); }; marker.on('click', function() { var allItems = document.querySelectorAll('.timeline-item-v8'); for (var j = 0; j < allItems.length; j++) { allItems[j].classList.remove('active'); } tlItem.classList.add('active'); tlItem.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' }); }); document.querySelector('#timeline-v8 > div').appendChild(tlItem); }); var route = waypoints.map(function(wp) { return [wp.lat, wp.lon]; }); L.polyline(route, { color: '#e98a15', weight: 4, dashArray: '10, 10', opacity: 0.8, lineCap: 'round' }).addTo(map); if (markers.length > 0) { map.fitBounds(L.latLngBounds(waypoints.map(function(wp) { return [wp.lat, wp.lon]; })).pad(0.1)); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { loadLeaflet(initMap); }); } else { loadLeaflet(initMap); } })(); </script>