PageSourceSearch

https://www.hawaii-guide.com/files/js/homepage-island-intro-v1.js?v=1091d8172e

js hawaii-guide.com collected 2026-09-24 18:24:29 UTC 6,660 bytes, 217 lines download raw bytes

1(function () {
2  'use strict';
3
4  var root = document.querySelector('[data-hg-island-intro]');
5  if (!root) return;
6
7  var inputs = Array.prototype.slice.call(root.querySelectorAll('[data-hg-island-input]'));
8  var cards = Array.prototype.slice.call(root.querySelectorAll('[data-hg-island-card]'));
9  var statuses = Array.prototype.slice.call(root.querySelectorAll('[data-island-status]'));
10  var videos = Array.prototype.slice.call(root.querySelectorAll('[data-hg-island-video]'));
11  var viewport = root.querySelector('[data-hg-island-viewport]');
12  var previousButton = root.querySelector('[data-hg-island-prev]');
13  var nextButton = root.querySelector('[data-hg-island-next]');
14  var reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
15  var scrollTimer = 0;
16  var resizeTimer = 0;
17  var suppressScrollSyncUntil = 0;
18
19  if (!inputs.length || !cards.length || !viewport || !previousButton || !nextButton) return;
20
21  function normalizeIndex(index) {
22    return (index + inputs.length) % inputs.length;
23  }
24
25  function currentIndex() {
26    var index = inputs.findIndex(function (input) {
27      return input.checked;
28    });
29    return index === -1 ? 0 : index;
30  }
31
32  function islandName(index) {
33    return inputs[normalizeIndex(index)].getAttribute('data-island-name') || 'island';
34  }
35
36  function pauseInactiveVideos(activeIsland) {
37    videos.forEach(function (video) {
38      var panel = video.closest('[data-hg-island-panel]');
39      if (panel && panel.getAttribute('data-island') !== activeIsland && !video.paused) {
40        video.pause();
41      }
42    });
43  }
44
45  function centerCard(index, behavior) {
46    if (viewport.scrollWidth <= viewport.clientWidth + 2) return;
47
48    var card = cards[index];
49    var viewportRect = viewport.getBoundingClientRect();
50    var cardRect = card.getBoundingClientRect();
51    var desiredLeft = viewport.scrollLeft
52      + cardRect.left
53      - viewportRect.left
54      - ((viewportRect.width - cardRect.width) / 2);
55    var maximumLeft = Math.max(0, viewport.scrollWidth - viewport.clientWidth);
56
57    suppressScrollSyncUntil = Date.now() + (behavior === 'smooth' ? 650 : 120);
58    viewport.scrollTo({
59      left: Math.max(0, Math.min(maximumLeft, desiredLeft)),
60      behavior: behavior
61    });
62  }
63
64  function updateInterface(index, options) {
65    var selectedInput = inputs[index];
66    var selectedIsland = selectedInput.value;
67    var shouldScroll = !options || options.scroll !== false;
68    var scrollBehavior = reducedMotion.matches ? 'auto' : 'smooth';
69
70    root.setAttribute('data-active-island', selectedIsland);
71
72    cards.forEach(function (card, cardIndex) {
73      if (cardIndex === index) {
74        card.setAttribute('aria-current', 'true');
75      } else {
76        card.removeAttribute('aria-current');
77      }
78    });
79
80    statuses.forEach(function (status) {
81      status.hidden = status.getAttribute('data-island-status') !== selectedIsland;
82    });
83
84    previousButton.setAttribute(
85      'aria-label',
86      'Show previous island: ' + islandName(index - 1)
87    );
88    nextButton.setAttribute(
89      'aria-label',
90      'Show next island: ' + islandName(index + 1)
91    );
92
93    pauseInactiveVideos(selectedIsland);
94
95    if (shouldScroll) {
96      centerCard(index, scrollBehavior);
97    }
98  }
99
100  function selectIsland(index, options) {
101    var normalizedIndex = normalizeIndex(index);
102    var selectedInput = inputs[normalizedIndex];
103
104    if (!selectedInput.checked) {
105      selectedInput.checked = true;
106    }
107
108    updateInterface(normalizedIndex, options);
109
110    if (options && options.focus) {
111      selectedInput.focus({ preventScroll: true });
112    }
113  }
114
115  function nearestCardIndex() {
116    var viewportRect = viewport.getBoundingClientRect();
117    var viewportCenter = viewportRect.left + (viewportRect.width / 2);
118    var nearestIndex = 0;
119    var nearestDistance = Infinity;
120
121    cards.forEach(function (card, index) {
122      var cardRect = card.getBoundingClientRect();
123      var cardCenter = cardRect.left + (cardRect.width / 2);
124      var distance = Math.abs(cardCenter - viewportCenter);
125
126      if (distance < nearestDistance) {
127        nearestDistance = distance;
128        nearestIndex = index;
129      }
130    });
131
132    return nearestIndex;
133  }
134
135  function syncSelectionFromScroll() {
136    if (Date.now() < suppressScrollSyncUntil) return;
137    if (viewport.scrollWidth <= viewport.clientWidth + 2) return;
138
139    var nearestIndex = nearestCardIndex();
140    if (nearestIndex !== currentIndex()) {
141      selectIsland(nearestIndex, { scroll: false });
142    }
143  }
144
145  root.setAttribute('data-enhanced', 'true');
146  previousButton.hidden = false;
147  previousButton.disabled = false;
148  nextButton.hidden = false;
149  nextButton.disabled = false;
150
151  inputs.forEach(function (input, index) {
152    input.addEventListener('change', function () {
153      if (input.checked) updateInterface(index);
154    });
155
156    input.addEventListener('keydown', function (event) {
157      var targetIndex = null;
158
159      if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
160        targetIndex = index + 1;
161      } else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
162        targetIndex = index - 1;
163      } else if (event.key === 'Home') {
164        targetIndex = 0;
165      } else if (event.key === 'End') {
166        targetIndex = inputs.length - 1;
167      }
168
169      if (targetIndex === null) return;
170
171      event.preventDefault();
172      selectIsland(targetIndex, { focus: true });
173    });
174  });
175
176  previousButton.addEventListener('click', function () {
177    selectIsland(currentIndex() - 1);
178  });
179
180  nextButton.addEventListener('click', function () {
181    selectIsland(currentIndex() + 1);
182  });
183
184  viewport.addEventListener('pointerdown', function () {
185    suppressScrollSyncUntil = 0;
186  }, { passive: true });
187
188  viewport.addEventListener('touchstart', function () {
189    suppressScrollSyncUntil = 0;
190  }, { passive: true });
191
192  viewport.addEventListener('scroll', function () {
193    window.clearTimeout(scrollTimer);
194    scrollTimer = window.setTimeout(syncSelectionFromScroll, 120);
195  }, { passive: true });
196
197  videos.forEach(function (video) {
198    video.addEventListener('play', function () {
199      videos.forEach(function (otherVideo) {
200        if (otherVideo !== video && !otherVideo.paused) otherVideo.pause();
201      });
202    });
203  });
204
205  window.addEventListener('resize', function () {
206    window.clearTimeout(resizeTimer);
207    resizeTimer = window.setTimeout(function () {
208      centerCard(currentIndex(), 'auto');
209    }, 140);
210  }, { passive: true });
211
212  updateInterface(currentIndex(), { scroll: false });
213
214  window.requestAnimationFrame(function () {
215    centerCard(currentIndex(), 'auto');
216  });
217}());

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.