PageSourceSearch

https://www.hawaii-guide.com/files/js/geo-toolbar-v1.js?v=f82da9b338

js hawaii-guide.com collected 2026-09-24 18:24:47 UTC 23,408 bytes, 546 lines download raw bytes

1/* ═══════════════════════════════════════════════════════════════
2   HawaiiGuide Geolocation Toolbar — v1
3   Swaps the top utility bar for "in-Hawaii" visitors.
4
5   Detection: Intl timezone === Pacific/Honolulu (instant, free,
6   no permission prompt). Island detection uses browser Geolocation
7   API only on-demand (Near Me / Dining tap).
8
9   TESTING in Chrome DevTools:
10     1. Open DevTools → three-dot menu → More tools → Sensors
11     2. Location dropdown → "Other…"
12     3. Enter coordinates:
13        Oahu:      21.3069, -157.8583  (Waikiki)
14        Maui:      20.8911, -156.5047  (Kahului)
15        Big Island: 19.7074, -155.0885 (Kona)
16        Kauai:     22.0881, -159.3380  (Lihue)
17     4. For timezone: set Override → Timezone → Pacific/Honolulu
18   ═══════════════════════════════════════════════════════════════ */
19(function () {
20  'use strict';
21
22  var STORAGE_KEY = 'hg_geo_mode';
23  var ISLAND_KEY  = 'hg_geo_island';
24
25  /* ── Island bounding boxes (lat/lng) ──────────────────────── */
26  var ISLANDS = [
27    { id: 'oahu',       name: 'Oahu',       latMin: 21.25, latMax: 21.72, lngMin: -158.28, lngMax: -157.65, dining: '/oahu/dining',       color: '#F27A24' },
28    { id: 'maui',        name: 'Maui',        latMin: 20.57, latMax: 21.03, lngMin: -156.70, lngMax: -155.95, dining: '/maui/dining',        color: '#1866B4' },
29    { id: 'big-island',  name: 'Big Island',  latMin: 18.90, latMax: 20.25, lngMin: -156.07, lngMax: -154.80, dining: '/big-island/dining',  color: '#c3232f' },
30    { id: 'kauai',       name: 'Kauai',       latMin: 21.86, latMax: 22.24, lngMin: -159.79, lngMax: -159.28, dining: '/kauai/dining',       color: '#1D9771' }
31  ];
32
33  /* ── Detect if user is in Hawaii ──────────────────────────── */
34  function detectHawaii() {
35    try {
36      return Intl.DateTimeFormat().resolvedOptions().timeZone === 'Pacific/Honolulu';
37    } catch (e) {
38      return false;
39    }
40  }
41
42  /* ── Get stored mode (null = not set, 'local' or 'visitor') ─ */
43  function getMode() {
44    try { return localStorage.getItem(STORAGE_KEY); } catch (e) { return null; }
45  }
46  function setMode(mode) {
47    try { localStorage.setItem(STORAGE_KEY, mode); } catch (e) {}
48    // Broadcast the change so other components (e.g. Whippoorwill player)
49    // can react without polling. Fired on every setMode call — including
50    // the initial "visitor" setup path at line ~131 — because downstream
51    // listeners may want to react to the first explicit mode too, not just
52    // user-driven toggles later. Failing silent if CustomEvent isn't
53    // supported (doesn't matter — nothing else on the site needs it pre-ES6).
54    try {
55      window.dispatchEvent(new CustomEvent('hg:geo-mode-change', { detail: { mode: mode } }));
56    } catch (e) {}
57  }
58  function getStoredIsland() {
59    try { return localStorage.getItem(ISLAND_KEY); } catch (e) { return null; }
60  }
61  function setStoredIsland(id) {
62    try { localStorage.setItem(ISLAND_KEY, id); } catch (e) {}
63  }
64
65  function getCurrentMode() {
66    return getMode() === 'local' ? 'local' : 'visitor';
67  }
68
69  /* ── Determine which island from lat/lng ──────────────────── */
70  function identifyIsland(lat, lng) {
71    for (var i = 0; i < ISLANDS.length; i++) {
72      var is = ISLANDS[i];
73      if (lat >= is.latMin && lat <= is.latMax && lng >= is.lngMin && lng <= is.lngMax) {
74        return is;
75      }
76    }
77    return null;
78  }
79
80  /* ── Get dining URL (island-specific if known) ────────────── */
81  function getDiningUrl() {
82    var islandId = getStoredIsland();
83    if (islandId) {
84      for (var i = 0; i < ISLANDS.length; i++) {
85        if (ISLANDS[i].id === islandId) return ISLANDS[i].dining;
86      }
87    }
88    return '/hawaii-eats';
89  }
90
91  /* ── Build the "in Hawaii" toolbar links ──────────────────── */
92  function swapToolbar() {
93    var leftContainer = document.querySelector('.hg-ubar-left');
94    if (!leftContainer) return;
95
96    var islandId = getStoredIsland();
97    var islandLabel = '';
98    if (islandId) {
99      for (var i = 0; i < ISLANDS.length; i++) {
100        if (ISLANDS[i].id === islandId) { islandLabel = ISLANDS[i].name; break; }
101      }
102    }
103
104    /* Clear existing left-side links */
105    leftContainer.innerHTML = '';
106
107    /* Keep the one-tap OS entrance on phones in local mode too. */
108    var osLocal = document.createElement('a');
109    osLocal.href = '/os';
110    osLocal.className = 'hg-ubar-link hg-ubar-os hg-ubar-os-local';
111    osLocal.title = 'HawaiiGuide OS \u2014 every travel tool on one home screen';
112    osLocal.setAttribute('aria-label', 'HawaiiGuide OS');
113    osLocal.innerHTML = '<img class="hg-os-compass-mark hg-ubar-os-compass" src="/files/images/branding/hawaiiguide-os-compass-v1.svg" alt="" width="22" height="22"><span class="hg-ubar-label-short">OS</span>';
114    leftContainer.appendChild(osLocal);
115
116    /* Near Me */
117    var nearMe = document.createElement('a');
118    nearMe.href = '/near-me';
119    nearMe.className = 'hg-ubar-link hg-ubar-nearme';
120    nearMe.innerHTML = '<i class="fa-location-arrow" aria-hidden="true"></i> <span>Near Me</span>';
121    leftContainer.appendChild(nearMe);
122
123    /* This Week */
124    var thisWeek = document.createElement('a');
125    thisWeek.href = '/this-week';
126    thisWeek.className = 'hg-ubar-link hg-ubar-thisweek';
127    thisWeek.innerHTML = '<i class="fa-sun" aria-hidden="true"></i> <span>This Week</span>';
128    leftContainer.appendChild(thisWeek);
129
130    /* Ocean Report */
131    var ocean = document.createElement('a');
132    ocean.href = '/ocean-report';
133    ocean.className = 'hg-ubar-link hg-ubar-ocean';
134    ocean.innerHTML = '<i class="fa-surf" aria-hidden="true"></i> <span>Ocean</span>';
135    leftContainer.appendChild(ocean);
136
137    /* Dining */
138    var dining = document.createElement('a');
139    dining.href = getDiningUrl();
140    dining.className = 'hg-ubar-link hg-ubar-dining';
141    dining.innerHTML = '<i class="fa-food" aria-hidden="true"></i> <span>' + (islandLabel ? islandLabel + ' Dining' : 'Dining') + '</span>';
142    leftContainer.appendChild(dining);
143
144    /* Mode toggle (small icon to switch back) */
145    var toggle = document.createElement('a');
146    toggle.href = '#';
147    toggle.className = 'hg-ubar-link hg-ubar-mode-toggle';
148    toggle.title = 'Switch to trip-planning toolbar';
149    toggle.setAttribute('aria-label', 'Switch to trip-planning toolbar');
150    toggle.innerHTML = '<i class="fa-plane" aria-hidden="true"></i>';
151    toggle.addEventListener('click', function (e) {
152      e.preventDefault();
153      setMode('visitor');
154      restoreToolbar();
155    });
156    leftContainer.appendChild(toggle);
157
158    /* Mark bar as swapped for CSS */
159    document.getElementById('hg-ubar').classList.add('hg-ubar-local');
160
161    /* Update dropdown toggle text to "Switch to Planning Mode" */
162    updateDropdownToggle('local');
163    updateMobileModeToggle('local');
164
165    /* Close dropdown if open */
166    closeDropdown();
167  }
168
169  /* ── Restore the original planning toolbar ────────────────── */
170  function restoreToolbar() {
171    var leftContainer = document.querySelector('.hg-ubar-left');
172    if (!leftContainer) return;
173
174    leftContainer.innerHTML = '';
175
176    /* HawaiiGuide OS */
177    var os = document.createElement('a');
178    os.href = '/os';
179    os.title = 'HawaiiGuide OS \u2014 every travel tool on one home screen';
180    os.className = 'hg-ubar-link hg-ubar-os';
181    os.setAttribute('aria-label', 'HawaiiGuide OS');
182    os.innerHTML = '<img class="hg-os-compass-mark hg-ubar-os-compass" src="/files/images/branding/hawaiiguide-os-compass-v1.svg" alt="" width="22" height="22"> <span class="hg-ubar-label-full">OS</span><span class="hg-ubar-label-short">OS</span>';
183    leftContainer.appendChild(os);
184
185    /* MyGuide */
186    var match = document.createElement('a');
187    match.href = '/myguide';
188    match.title = 'TripStart \u2014 find the right Hawaii tools for where you are right now';
189    match.className = 'hg-ubar-link hg-ubar-match';
190    match.innerHTML = '<i class="fa-compass" aria-hidden="true"></i> <span class="hg-ubar-label-full">TripStart</span><span class="hg-ubar-label-short">Start</span>';
191    leftContainer.appendChild(match);
192
193    /* Maps (desktop) */
194    var mapsD = document.createElement('a');
195    mapsD.href = '/maps';
196    mapsD.className = 'hg-ubar-link hg-ubar-maps hg-ubar-maps-desktop';
197    mapsD.setAttribute('aria-label', 'Hawaii maps');
198    mapsD.innerHTML = '<i class="fa-map-marker" aria-hidden="true"></i> <span>Maps</span>';
199    leftContainer.appendChild(mapsD);
200
201    /* Maps (mobile) */
202    var mapsM = document.createElement('a');
203    mapsM.href = '/hawaii-maps';
204    mapsM.className = 'hg-ubar-link hg-ubar-maps hg-ubar-maps-mobile';
205    mapsM.setAttribute('aria-label', 'Hawaii maps');
206    mapsM.innerHTML = '<i class="fa-map-marker" aria-hidden="true"></i> <span>Maps</span>';
207    leftContainer.appendChild(mapsM);
208
209    /* Concierge */
210    var concierge = document.createElement('a');
211    concierge.href = '#';
212    concierge.className = 'hg-ubar-link hg-ubar-concierge';
213    concierge.id = 'hg-ubar-concierge-btn';
214    concierge.innerHTML = '<i class="fa-callbell" aria-hidden="true"></i> <span>Concierge</span>';
215    concierge.addEventListener('click', function (e) {
216      e.preventDefault();
217      if (window.HGConciergeModal) { window.HGConciergeModal.open(); }
218    });
219    leftContainer.appendChild(concierge);
220
221    /* "More" dropdown trigger (tools grid + chevron). Shaka is reserved
222       for the one-tap HawaiiGuide OS entrance at every breakpoint. */
223    var moreBtn = document.createElement('a');
224    moreBtn.href = '#';
225    moreBtn.className = 'hg-ubar-link hg-ubar-more-btn';
226    moreBtn.id = 'hg-ubar-more-btn';
227    moreBtn.title = 'More Tools & Features';
228    moreBtn.setAttribute('aria-label', 'More Tools & Features');
229    moreBtn.setAttribute('aria-expanded', 'false');
230    moreBtn.innerHTML = '<i class="fa-grid hg-ubar-tools-grid" aria-hidden="true"></i><span class="hg-ubar-chevron" aria-hidden="true">\u25BE</span>';
231    moreBtn.addEventListener('click', function (e) {
232      e.preventDefault();
233      toggleDropdown();
234    });
235    leftContainer.appendChild(moreBtn);
236
237    document.getElementById('hg-ubar').classList.remove('hg-ubar-local');
238
239    /* Update dropdown toggle text back to "Local Mode" */
240    updateDropdownToggle('visitor');
241    updateMobileModeToggle('visitor');
242
243    /* Close dropdown if open */
244    closeDropdown();
245  }
246
247  /* ── Try to detect island via browser geolocation ─────────── */
248  function tryDetectIsland() {
249    if (!navigator.geolocation) return;
250    navigator.geolocation.getCurrentPosition(
251      function (pos) {
252        var island = identifyIsland(pos.coords.latitude, pos.coords.longitude);
253        if (island) {
254          setStoredIsland(island.id);
255          /* Update dining link if toolbar is in local mode */
256          var diningLink = document.querySelector('.hg-ubar-dining');
257          if (diningLink) {
258            diningLink.href = island.dining;
259            diningLink.querySelector('span').textContent = island.name + ' Dining';
260          }
261        }
262      },
263      function () { /* permission denied or error — that's fine, use generic links */ },
264      { enableHighAccuracy: false, timeout: 5000, maximumAge: 600000 }
265    );
266  }
267
268  /* ── Helper: update dropdown Hawaii toggle text ────────────── */
269  function updateDropdownToggle(currentMode) {
270    var ddToggle = document.getElementById('hg-ubar-dd-hawaii-toggle');
271    if (!ddToggle) return;
272    if (currentMode === 'local') {
273      ddToggle.innerHTML = '<i class="fa-plane" aria-hidden="true"></i> Switch to Planning Mode';
274    } else {
275      ddToggle.innerHTML = '<i class="fa-hawaii" aria-hidden="true"></i> I\u2019m in Hawai\u02BBi \u2014 Local Mode';
276    }
277  }
278
279  function updateMobileModeToggle(currentMode) {
280    var toggles = document.querySelectorAll('.hg-nav-local-mode-toggle');
281    if (!toggles.length) return;
282    var isLocal = currentMode === 'local';
283    for (var i = 0; i < toggles.length; i++) {
284      var toggle = toggles[i];
285      var text = toggle.querySelector('.hg-nav-local-mode-text');
286      toggle.setAttribute('aria-pressed', isLocal ? 'true' : 'false');
287      toggle.classList.toggle('is-local', isLocal);
288      if (text) text.textContent = isLocal ? 'Turn off Local Mode' : 'Turn on Local Mode';
289      toggle.title = isLocal ? 'Switch back to trip-planning mode' : 'Switch to local mode';
290    }
291  }
292
293  /* ── Helper: position dropdown beneath the trigger button ── */
294  function positionDropdown(dd, btn) {
295    /* On mobile (<=600px) the CSS makes it full-width, skip positioning */
296    if (window.innerWidth <= 600) {
297      dd.style.left = '0';
298      return;
299    }
300    var btnRect = btn.getBoundingClientRect();
301    var barRect = btn.closest('.hg-ubar').getBoundingClientRect();
302    /* Align left edge of dropdown with left edge of button */
303    var leftPos = btnRect.left - barRect.left;
304    /* Prevent dropdown from overflowing the right edge of the viewport */
305    var ddWidth = dd.offsetWidth || 260;
306    if (leftPos + ddWidth > barRect.width) {
307      leftPos = barRect.width - ddWidth;
308    }
309    if (leftPos < 0) leftPos = 0;
310    dd.style.left = leftPos + 'px';
311  }
312
313  /* ── Helper: toggle/close dropdown ───────────────────────── */
314  function toggleDropdown() {
315    var dd = document.getElementById('hg-ubar-dropdown');
316    var btn = document.getElementById('hg-ubar-more-btn');
317    if (!dd) return;
318    var isOpen = dd.style.display !== 'none';
319    dd.style.display = isOpen ? 'none' : 'block';
320    if (btn) {
321      btn.classList.toggle('is-open', !isOpen);
322      btn.setAttribute('aria-expanded', !isOpen ? 'true' : 'false');
323    }
324    if (!isOpen && btn) {
325      positionDropdown(dd, btn);
326    }
327    /* Close Maps dropdown if open */
328    var mapsDd = document.getElementById('hg-ubar-maps-dropdown');
329    if (mapsDd) mapsDd.style.display = 'none';
330    var mb = document.getElementById('hg-ubar-maps-btn');
331    if (mb) { mb.classList.remove('is-open'); mb.setAttribute('aria-expanded', 'false'); }
332  }
333
334  function closeDropdown() {
335    var dd = document.getElementById('hg-ubar-dropdown');
336    var btn = document.getElementById('hg-ubar-more-btn');
337    if (dd) dd.style.display = 'none';
338    if (btn) {
339      btn.classList.remove('is-open');
340      btn.setAttribute('aria-expanded', 'false');
341    }
342  }
343
344  /* Expose for itinerary.js to bind the initial button */
345  window.HGGeoToolbar = {
346    toggleDropdown: toggleDropdown,
347    closeDropdown: closeDropdown
348  };
349
350  /* ── Init ──────────────────────────────────────────────────── */
351  function init() {
352    var bar = document.getElementById('hg-ubar');
353    if (!bar) return;
354
355    /* Wire the dropdown's Hawaii toggle link */
356    var ddHawaiiToggle = document.getElementById('hg-ubar-dd-hawaii-toggle');
357    if (ddHawaiiToggle) {
358      ddHawaiiToggle.addEventListener('click', function (e) {
359        e.preventDefault();
360        var mode = getCurrentMode();
361        if (mode === 'local') {
362          applyMode('visitor');
363        } else {
364          applyMode('local');
365        }
366      });
367    }
368
369    var mobileModeToggles = document.querySelectorAll('.hg-nav-local-mode-toggle');
370    for (var mt = 0; mt < mobileModeToggles.length; mt++) {
371      mobileModeToggles[mt].addEventListener('click', function (e) {
372        e.preventDefault();
373        var nextMode = getCurrentMode() === 'local' ? 'visitor' : 'local';
374        applyMode(nextMode);
375        document.body.classList.remove('nav-is-toggled');
376      });
377    }
378
379    /* Wire the initial "More" button (from static HTML) */
380    var moreBtn = document.getElementById('hg-ubar-more-btn');
381    if (moreBtn) {
382      moreBtn.addEventListener('click', function (e) {
383        e.preventDefault();
384        toggleDropdown();
385      });
386    }
387
388    /* Wire the Maps dropdown */
389    var mapsBtn = document.getElementById('hg-ubar-maps-btn');
390    if (mapsBtn) {
391      mapsBtn.addEventListener('click', function (e) {
392        e.preventDefault();
393        var mapsDd = document.getElementById('hg-ubar-maps-dropdown');
394        var moreDd = document.getElementById('hg-ubar-dropdown');
395        var intlDd = document.getElementById('hg-ubar-intl-dropdown');
396        if (!mapsDd) return;
397        var isOpen = mapsDd.style.display !== 'none';
398        mapsDd.style.display = isOpen ? 'none' : 'block';
399        if (!isOpen) positionDropdown(mapsDd, mapsBtn);
400        mapsBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
401        mapsBtn.classList.toggle('is-open', !isOpen);
402        /* Close other dropdowns if open */
403        if (moreDd) moreDd.style.display = 'none';
404        var moreBtnEl = document.getElementById('hg-ubar-more-btn');
405        if (moreBtnEl) { moreBtnEl.classList.remove('is-open'); moreBtnEl.setAttribute('aria-expanded', 'false'); }
406        if (intlDd) intlDd.style.display = 'none';
407        var intlBtnEl = document.getElementById('hg-ubar-intl-btn');
408        if (intlBtnEl) { intlBtnEl.classList.remove('is-open'); intlBtnEl.setAttribute('aria-expanded', 'false'); }
409      });
410    }
411
412    /* Wire the International / language dropdown.
413       Mirrors the Maps pattern — click to toggle, click-outside / Esc closes,
414       opening this dropdown closes the other two. Also marks the active page
415       (e.g. user is on /de) so the dropdown shows where they currently are. */
416    var intlBtn = document.getElementById('hg-ubar-intl-btn');
417    if (intlBtn) {
418      intlBtn.addEventListener('click', function (e) {
419        e.preventDefault();
420        var intlDd = document.getElementById('hg-ubar-intl-dropdown');
421        var moreDd = document.getElementById('hg-ubar-dropdown');
422        var mapsDd = document.getElementById('hg-ubar-maps-dropdown');
423        if (!intlDd) return;
424        var isOpen = intlDd.style.display !== 'none';
425        intlDd.style.display = isOpen ? 'none' : 'block';
426        if (!isOpen) positionDropdown(intlDd, intlBtn);
427        intlBtn.setAttribute('aria-expanded', isOpen ? 'false' : 'true');
428        intlBtn.classList.toggle('is-open', !isOpen);
429        /* Close the other dropdowns if open */
430        if (moreDd) moreDd.style.display = 'none';
431        var moreBtnEl = document.getElementById('hg-ubar-more-btn');
432        if (moreBtnEl) { moreBtnEl.classList.remove('is-open'); moreBtnEl.setAttribute('aria-expanded', 'false'); }
433        if (mapsDd) mapsDd.style.display = 'none';
434        var mapsBtnEl = document.getElementById('hg-ubar-maps-btn');
435        if (mapsBtnEl) { mapsBtnEl.classList.remove('is-open'); mapsBtnEl.setAttribute('aria-expanded', 'false'); }
436      });
437
438      /* Active-page highlight: if user is currently on /de, /jp, /ca, etc.,
439         tag the matching dropdown row with .is-current. Pure path matching —
440         compares window.location.pathname (without trailing slash) against
441         each item's data-country-href. */
442      try {
443        var path = (window.location.pathname || '').replace(/\/$/, '');
444        if (path === '') path = '/';
445        var intlLinks = document.querySelectorAll('.hg-ubar-intl-link');
446        for (var li = 0; li < intlLinks.length; li++) {
447          if (intlLinks[li].getAttribute('data-country-href') === path) {
448            intlLinks[li].classList.add('is-current');
449            intlLinks[li].setAttribute('aria-current', 'page');
450          }
451        }
452      } catch (e) {}
453    }
454
455    /* Close dropdown on outside click */
456    document.addEventListener('click', function (e) {
457      var dd = document.getElementById('hg-ubar-dropdown');
458      if (dd && dd.style.display !== 'none') {
459        if (!e.target.closest('#hg-ubar-dropdown') && !e.target.closest('#hg-ubar-more-btn')) {
460          closeDropdown();
461        }
462      }
463      var mapsDd = document.getElementById('hg-ubar-maps-dropdown');
464      if (mapsDd && mapsDd.style.display !== 'none') {
465        if (!e.target.closest('#hg-ubar-maps-dropdown') && !e.target.closest('#hg-ubar-maps-btn')) {
466          mapsDd.style.display = 'none';
467          var mb = document.getElementById('hg-ubar-maps-btn');
468          if (mb) { mb.classList.remove('is-open'); mb.setAttribute('aria-expanded', 'false'); }
469        }
470      }
471      var intlDd = document.getElementById('hg-ubar-intl-dropdown');
472      if (intlDd && intlDd.style.display !== 'none') {
473        if (!e.target.closest('#hg-ubar-intl-dropdown') && !e.target.closest('#hg-ubar-intl-btn')) {
474          intlDd.style.display = 'none';
475          var ib = document.getElementById('hg-ubar-intl-btn');
476          if (ib) { ib.classList.remove('is-open'); ib.setAttribute('aria-expanded', 'false'); }
477        }
478      }
479    });
480
481    /* Close dropdown on Escape key */
482    document.addEventListener('keydown', function (e) {
483      if (e.key === 'Escape' || e.keyCode === 27) {
484        closeDropdown();
485        var mapsDd = document.getElementById('hg-ubar-maps-dropdown');
486        if (mapsDd) mapsDd.style.display = 'none';
487        var mb = document.getElementById('hg-ubar-maps-btn');
488        if (mb) { mb.classList.remove('is-open'); mb.setAttribute('aria-expanded', 'false'); }
489        var intlDd = document.getElementById('hg-ubar-intl-dropdown');
490        if (intlDd) intlDd.style.display = 'none';
491        var ib = document.getElementById('hg-ubar-intl-btn');
492        if (ib) { ib.classList.remove('is-open'); ib.setAttribute('aria-expanded', 'false'); }
493      }
494    });
495
496    var mode = getMode();
497
498    if (mode === 'local') {
499      /* User previously chose local mode */
500      swapToolbar();
501      tryDetectIsland();
502    } else if (mode === 'visitor') {
503      /* User previously chose visitor/planning mode */
504      updateDropdownToggle('visitor');
505      updateMobileModeToggle('visitor');
506    } else {
507      /* First visit — auto-detect */
508      if (detectHawaii()) {
509        setMode('local');
510        swapToolbar();
511        tryDetectIsland();
512      } else {
513        updateDropdownToggle('visitor');
514        updateMobileModeToggle('visitor');
515      }
516    }
517  }
518
519  /* Public API — lets other components (e.g. the Whippoorwill player modal)
520     flip Local Mode using the same code path as the header click. Both the
521     header and the modal end up writing localStorage, swapping/restoring
522     the toolbar DOM, and firing the hg:geo-mode-change event in one shot. */
523  function applyMode(mode) {
524    if (mode !== 'local' && mode !== 'visitor') return false;
525    var before = getMode();
526    setMode(mode);
527    if (mode === 'local') {
528      tryDetectIsland();
529      swapToolbar();
530    } else {
531      restoreToolbar();
532    }
533    return before !== mode;
534  }
535  window.HGGeo = {
536    setMode: applyMode,
537    getMode: getMode
538  };
539
540  /* Run after DOM is ready */
541  if (document.readyState === 'loading') {
542    document.addEventListener('DOMContentLoaded', init);
543  } else {
544    init();
545  }
546})();

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.