const MAP_SVG_SOURCE = 'assets/world_time_zones.svg'; const OFFSET_PRECISION = 15; // Minuten-Schrittweite const mapContainer = document.getElementById('map-container'); const overlayEl = document.getElementById('map-overlay'); const tooltipEl = document.getElementById('map-tooltip'); if (mapContainer) { loadMap(); } function loadMap() { fetch(MAP_SVG_SOURCE) .then((response) => response.text()) .then((svgText) => { mapContainer.insertAdjacentHTML('afterbegin', svgText); const svg = mapContainer.querySelector('svg'); if (!svg) { throw new Error('SVG nicht gefunden.'); } svg.setAttribute('viewBox', '0 0 3600 1500'); svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); svg.style.width = '100%'; svg.style.height = '100%'; attachMapListeners(svg); }) .catch((error) => { console.error('Karte konnte nicht geladen werden:', error); mapContainer.innerHTML = '
Die Zeitzonenkarte konnte nicht geladen werden.
'; }); } function attachMapListeners(svg) { const svgPoint = svg.createSVGPoint(); function getRelativePoint(event) { svgPoint.x = event.clientX; svgPoint.y = event.clientY; const ctm = svg.getScreenCTM(); if (!ctm) { return { x: 0, y: 0 }; } const inverse = ctm.inverse(); const transformed = svgPoint.matrixTransform(inverse); return { x: transformed.x, y: transformed.y }; } function handlePointerMove(event) { const { x, y } = getRelativePoint(event); const lon = normalizeLongitude(x, svg.viewBox.baseVal.width); const lat = normalizeLatitude(y, svg.viewBox.baseVal.height); const offsetMinutes = approximateOffsetMinutes(lon); const { offsetLabel } = describeOffset(offsetMinutes); positionTooltip(event.clientX, event.clientY, `${offsetLabel}\n${formatCoordinate(lat, lon)}`); } function handlePointerLeave() { tooltipEl?.classList.remove('visible'); } function handleClick(event) { const { x } = getRelativePoint(event); const lon = normalizeLongitude(x, svg.viewBox.baseVal.width); const offsetMinutes = approximateOffsetMinutes(lon); const { offsetLabel, offsetMinutesRounded } = describeOffset(offsetMinutes); showOverlay(offsetLabel); const candidate = findTimezoneByOffset(offsetMinutesRounded); if (candidate && typeof addSelection === 'function') { addSelection(candidate); renderSelections(); } } svg.addEventListener('pointermove', handlePointerMove); svg.addEventListener('pointerleave', handlePointerLeave); svg.addEventListener('click', handleClick); } function normalizeLongitude(x, width) { const clampedWidth = width || 3600; return (x / clampedWidth) * 360 - 180; } function normalizeLatitude(y, height) { const clampedHeight = height || 1500; return 90 - (y / clampedHeight) * 180; } function approximateOffsetMinutes(longitude) { const offsetHours = Math.round((longitude / 15) * (60 / OFFSET_PRECISION)) * (OFFSET_PRECISION / 60); const clampedHours = Math.min(14, Math.max(-12, offsetHours)); return Math.round(clampedHours * 60); } function describeOffset(offsetMinutes) { const clamped = Math.round(offsetMinutes / OFFSET_PRECISION) * OFFSET_PRECISION; const hours = Math.trunc(clamped / 60); const minutes = Math.abs(clamped % 60); const sign = clamped >= 0 ? '+' : '-'; return { offsetMinutesRounded: clamped, offsetLabel: `UTC${sign}${String(Math.abs(hours)).padStart(2, '0')}:${String(minutes).padStart(2, '0')}` }; } function positionTooltip(clientX, clientY, text) { if (!tooltipEl) { return; } const containerRect = mapContainer.getBoundingClientRect(); tooltipEl.textContent = text; tooltipEl.style.left = `${clientX - containerRect.left}px`; tooltipEl.style.top = `${clientY - containerRect.top}px`; tooltipEl.classList.add('visible'); } let overlayTimeoutId = null; function showOverlay(text) { if (!overlayEl) { return; } overlayEl.textContent = text; overlayEl.classList.add('visible'); if (overlayTimeoutId) { clearTimeout(overlayTimeoutId); } overlayTimeoutId = window.setTimeout(() => { overlayEl?.classList.remove('visible'); }, 1500); } function formatCoordinate(lat, lon) { const formatValue = (value, positiveSuffix, negativeSuffix) => { const suffix = value >= 0 ? positiveSuffix : negativeSuffix; return `${Math.abs(value).toFixed(1)}°${suffix}`; }; return `${formatValue(lat, 'N', 'S')} · ${formatValue(lon, 'E', 'W')}`; } function findTimezoneByOffset(targetOffsetMinutes) { const now = new Date(); let bestCandidate = null; let bestDelta = Infinity; SORTED_TIMEZONE_DATA.forEach((entry) => { const offset = getOffsetMinutes(now, entry.timeZone); const delta = Math.abs(offset - targetOffsetMinutes); if (delta < bestDelta) { bestDelta = delta; bestCandidate = entry; } }); return bestCandidate; }