1(() => { 2 'use strict'; 3 4 const SVG_NS = 'http://www.w3.org/2000/svg'; 5 const assetRequests = new Map(); 6 7 const loadAsset = (url) => { 8 if (!assetRequests.has(url)) { 9 assetRequests.set( 10 url, 11 fetch(url, { credentials: 'same-origin' }) 12 .then((response) => { 13 if (!response.ok) throw new Error('Map request failed.'); 14 return response.text(); 15 }) 16 .then((markup) => { 17 const asset = new DOMParser().parseFromString(markup, 'image/svg+xml'); 18 if (asset.querySelector('parsererror')) throw new Error('Invalid map SVG.'); 19 return asset; 20 }) 21 ); 22 } 23 24 return assetRequests.get(url); 25 }; 26 27 const parseConfig = (widget) => { 28 const node = widget.querySelector('.bdm-v4__data'); 29 if (!node) return null; 30 31 try { 32 return JSON.parse(node.textContent); 33 } catch (error) { 34 return null; 35 } 36 }; 37 38 const showLoadError = (widget) => { 39 const map = widget.querySelector('.bdm-v4__map'); 40 if (!map) return; 41 42 map.setAttribute('aria-busy', 'false'); 43 const message = document.createElement('p'); 44 message.className = 'bdm-v4__load-error'; 45 message.textContent = 'The Bodrum map could not be loaded.'; 46 map.replaceChildren(message); 47 }; 48 49 const makePlace = (place) => { 50 const item = document.createElement(place.url ? 'a' : 'div'); 51 item.className = 'bdm-v4__place'; 52 item.textContent = place.name; 53 if (place.url) item.href = place.url; 54 return item; 55 }; 56 57 const updateTabs = (widget, viewKey) => { 58 widget.querySelectorAll('.bdm-v4__tab').forEach((button) => { 59 const active = button.dataset.view === viewKey; 60 button.setAttribute('aria-selected', active ? 'true' : 'false'); 61 button.tabIndex = active ? 0 : -1; 62 }); 63 }; 64 65 const clearSelection = (widget) => { 66 widget.querySelectorAll('.bdm-v4-marker').forEach((marker) => { 67 marker.classList.remove('is-selected'); 68 marker.setAttribute('aria-pressed', 'false'); 69 }); 70 71 widget.querySelectorAll('.bdm-v4__legend-button').forEach((button) => { 72 button.setAttribute('aria-pressed', 'false'); 73 }); 74 }; 75 76 const showDefaultPanel = (widget, state) => { 77 const view = state.config.views[state.viewKey]; 78 const title = state.viewKey === 'museum' ? 'Bodrum Museum' : view.label; 79 80 widget.querySelector('[data-bdm-v4-kicker]').textContent = view.panelLabel; 81 widget.querySelector('[data-bdm-v4-title]').textContent = title; 82 widget.querySelector('[data-bdm-v4-default]').textContent = view.prompt; 83 widget.querySelector('[data-bdm-v4-default]').hidden = false; 84 widget.querySelector('[data-bdm-v4-selected]').hidden = true; 85 clearSelection(widget); 86 }; 87 88 const buildLegend = (widget, state) => { 89 const view = state.config.views[state.viewKey]; 90 const legend = widget.querySelector('[data-bdm-v4-legend]'); 91 const destinations = Object.entries(view.destinations) 92 .filter(([, destination]) => destination.kind !== 'starting-point'); 93 94 const buttons = destinations.map(([destinationId, destination], index) => { 95 const button = document.createElement('button'); 96 button.className = 'bdm-v4__legend-button'; 97 button.type = 'button'; 98 button.dataset.destination = destinationId; 99 button.setAttribute('aria-pressed', 'false'); 100 101 const number = document.createElement('span'); 102 number.className = 'bdm-v4__legend-number'; 103 number.setAttribute('aria-hidden', 'true'); 104 number.textContent = String(index + 1); 105 106 const label = document.createElement('span'); 107 label.className = 'bdm-v4__legend-label'; 108 label.textContent = destination.name; 109 110 button.append(number, label); 111 return button; 112 }); 113 114 legend.style.setProperty('--bdm-v4-legend-count', String(buttons.length)); 115 legend.setAttribute('aria-label', `${view.label} destinations`); 116 legend.replaceChildren(...buttons); 117 }; 118 119 const prepareMarkers = (widget, state) => { 120 const view = state.config.views[state.viewKey]; 121
122 widget.querySelectorAll('.bdm-v4-marker[data-destination]').forEach((marker) => { 123 const destination = view.destinations[marker.dataset.destination]; 124 if (!destination) return; 125 126 marker.setAttribute('role', 'button'); 127 marker.setAttribute('tabindex', '0'); 128 marker.setAttribute('aria-label', destination.name); 129 marker.setAttribute('aria-pressed', 'false'); 130 }); 131 }; 132 133 const renderView = (widget, state, viewKey) => { 134 const view = state.config.views[viewKey]; 135 if (!view || !state.asset) return; 136 137 const symbol = state.asset.getElementById(view.symbol); 138 if (!symbol) { 139 showLoadError(widget); 140 return; 141 } 142 143 const svg = document.createElementNS(SVG_NS, 'svg'); 144 svg.setAttribute('viewBox', symbol.getAttribute('viewBox')); 145 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); 146 svg.setAttribute('role', 'img'); 147 svg.setAttribute('aria-label', view.mapLabel); 148 Array.from(symbol.childNodes).forEach((node) => svg.append(node.cloneNode(true))); 149 150 state.viewKey = viewKey; 151 widget.dataset.view = viewKey; 152 const map = widget.querySelector('.bdm-v4__map'); 153 map.replaceChildren(svg); 154 map.setAttribute('aria-label', view.mapLabel); 155 map.setAttribute('aria-busy', 'false'); 156 157 updateTabs(widget, viewKey); 158 buildLegend(widget, state); 159 prepareMarkers(widget, state); 160 showDefaultPanel(widget, state); 161 }; 162 163 const scrollToPanel = (widget) => { 164 if (!window.matchMedia('(max-width: 1020px)').matches) return; 165 166 const panel = widget.querySelector('.bdm-v4__panel'); 167 const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 168 window.requestAnimationFrame(() => { 169 panel.scrollIntoView({ 170 behavior: reduceMotion ? 'auto' : 'smooth', 171 block: 'start' 172 }); 173 }); 174 }; 175 176 const selectDestination = (widget, state, destinationId) => { 177 const view = state.config.views[state.viewKey]; 178 const destination = view.destinations[destinationId]; 179 if (!destination) return; 180 181 const kind = destination.kind || 'destination'; 182 const description = widget.querySelector('[data-bdm-v4-description]'); 183 const placesHeading = widget.querySelector('[data-bdm-v4-places-heading]'); 184 const places = widget.querySelector('[data-bdm-v4-places]'); 185 const button = widget.querySelector('[data-bdm-v4-button]'); 186 187 widget.querySelector('[data-bdm-v4-kicker]').textContent = 188 destination.panelLabel || view.panelLabel; 189 widget.querySelector('[data-bdm-v4-title]').textContent = destination.name; 190 widget.querySelector('[data-bdm-v4-default]').hidden = true; 191 widget.querySelector('[data-bdm-v4-selected]').hidden = false; 192 193 description.hidden = true; 194 description.textContent = ''; 195 placesHeading.hidden = true; 196 places.hidden = true; 197 places.replaceChildren(); 198 199 if (kind === 'department') { 200 description.textContent = destination.description || ''; 201 description.hidden = false; 202 } else if (kind === 'starting-point') { 203 places.replaceChildren(makePlace({ name: destination.message || 'You are here', url: '' })); 204 places.hidden = false; 205 } else { 206 placesHeading.textContent = destination.placesHeading || 'Places to explore'; 207 placesHeading.hidden = false; 208 places.replaceChildren(...(destination.places || []).map(makePlace)); 209 places.hidden = false; 210 } 211 212 if (kind !== 'starting-point' && destination.url) { 213 button.href = destination.url; 214 button.textContent = destination.buttonLabel || 'Learn more'; 215 button.hidden = false; 216 } else { 217 button.hidden = true; 218 button.removeAttribute('href'); 219 button.textContent = ''; 220 } 221 222 widget.querySelectorAll('.bdm-v4-marker').forEach((marker) => { 223 const active = marker.dataset.destination === destinationId; 224 marker.classList.toggle('is-selected', active); 225 marker.setAttribute('aria-pressed', active ? 'true' : 'false'); 226 }); 227 228 widget.querySelectorAll('.bdm-v4__legend-button').forEach((legendButton) => { 229 legendButton.setAttribute( 230 'aria-pressed', 231 legendButton.dataset.destination === destinationId ? 'true' : 'false' 232 ); 233 }); 234 235 scrollToPanel(widget); 236 }; 237 238 const moveTabFocus = (widget, current, direction) => { 239 const tabs = Array.from(widget.querySelectorAll('.bdm-v4__tab')); 240 const index = tabs.indexOf(current); 241 if (index < 0) return; 242 tabs[(index + direction + tabs.length) % tabs.length].focus(); 243 }; 244 245 const bindWidget = (widget) => { 246 if (widget.dataset.bdmV4Ready === 'true') return; 247 widget.dataset.bdmV4Ready = 'true'; 248 249 const config = parseConfig(widget); 250 const mapUrl = widget.dataset.mapSrc; 251 if (!config || !config.views || !mapUrl) { 252 showLoadError(widget); 253 return; 254 } 255 256 const state = { 257 asset: null, 258 config, 259 viewKey: config.defaultView || Object.keys(config.views)[0] 260 }; 261 262 widget.addEventListener('click', (event) => { 263 const tab = event.target.closest('.bdm-v4__tab'); 264 if (tab && widget.contains(tab)) {
265 renderView(widget, state, tab.dataset.view); 266 return; 267 } 268 269 const control = event.target.closest('[data-destination]'); 270 if (control && widget.contains(control)) { 271 selectDestination(widget, state, control.dataset.destination); 272 } 273 }); 274 275 widget.addEventListener('keydown', (event) => { 276 const tab = event.target.closest('.bdm-v4__tab'); 277 if (tab && widget.contains(tab)) { 278 if (event.key === 'ArrowRight' || event.key === 'ArrowDown') { 279 event.preventDefault(); 280 moveTabFocus(widget, tab, 1); 281 } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') { 282 event.preventDefault(); 283 moveTabFocus(widget, tab, -1); 284 } else if (event.key === 'Enter' || event.key === ' ') { 285 event.preventDefault(); 286 renderView(widget, state, tab.dataset.view); 287 } 288 return; 289 } 290 291 const marker = event.target.closest('.bdm-v4-marker[data-destination]'); 292 if (!marker || !widget.contains(marker)) return; 293 if (event.key !== 'Enter' && event.key !== ' ') return; 294 event.preventDefault(); 295 selectDestination(widget, state, marker.dataset.destination); 296 }); 297 298 loadAsset(mapUrl) 299 .then((asset) => { 300 state.asset = asset; 301 renderView(widget, state, state.viewKey); 302 }) 303 .catch(() => showLoadError(widget)); 304 }; 305 306 const initialize = () => { 307 document.querySelectorAll('[data-bdm-v4]').forEach(bindWidget); 308 }; 309 310 if (document.readyState === 'loading') { 311 document.addEventListener('DOMContentLoaded', initialize, { once: true }); 312 } else { 313 initialize(); 314 } 315})();
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.