PageSourceSearch

https://www.visionsource-bittnervision.com/scripts/main.js

js visionsource-bittnervision.com collected 2026-10-08 00:09:22 UTC 10,707 bytes, 355 lines download raw bytes

1import Alpine from 'https://cdn.jsdelivr.net/npm/[email protected]/dist/module.esm.js';
2import collapse from 'https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/module.esm.js';
3
4import alpinePhonePlugin from '../scripts/utils-alpine-phone-plugin.js';
5import resolveCustomFieldsPlugin from "../scripts/resolve-custom-fields.js";
6
7// === CANVAS Carousel Detection ===
8import canvasCarouselDetector from "../scripts/utils-canvas-carousel-detector.js";
9
10// === Core UI Modules ===
11import navbarModule from "../scripts/js-navbars.js";
12import heroCarousel from "../scripts/js-hero-carousel.js";
13import modalComponent from "../scripts/js-modal.js";
14
15// Import the Swiper component
16import swiperComponent from "../scripts/js-swiper-component.js";
17
18// Vision Source - Services (Design Version 2) slider (loop + custom pagination on top of swiperComponent)
19import vsServicesSlider from "../scripts/vs-services-slider.js";
20import vsServicesDatasetSlider from "../scripts/vs-services-dataset-slider.js";
21
22// Import social sharing handler to make it available globally
23import '../scripts/social-sharing-handler.js';
24
25
26// === Configuration Data ===
27import {
28  generalConfig,
29  locationsConfig,
30  modalConfig,
31  heroConfig,
32  insurancesConfig,
33  welcomeConfig,
34  ctasConfig,
35  headingsConfig,
36  servicesConfig,
37  teamConfig,
38  brandsConfig,
39  testimonialsConfig,
40  faqConfig,
41  mapSettings,
42  uiState,
43  interiorConfig,
44} from "../scripts/config-index.js";
45
46// === Vision Source (client design system) ===
47import { visionSourceConfig } from "../scripts/vs-index.js";
48import { vsFieldValues } from "../scripts/vs-field-values.js";
49
50// === Alpine Stores (Make configs available before theme initialization) ===
51
52Alpine.store("servicesData", servicesConfig.allServices);
53Alpine.store("pageServicesData", servicesConfig.pageServices);
54Alpine.store("vsTeam", { active: 0 });
55Alpine.store("general", generalConfig);
56Alpine.store("locationsData", locationsConfig);
57Alpine.store("modalConfig", modalConfig);
58
59// Make generalConfig globally available for analytics and other early-loading scripts
60window.canvasSnapConfig = generalConfig;
61
62canvasCarouselDetector.init();
63
64function snap() {
65  return {
66    general: generalConfig,
67    locations: locationsConfig,
68    heroSettings: heroConfig,
69    homepage: {
70      headings: headingsConfig,
71      welcome: welcomeConfig,
72      ctas: ctasConfig,
73      services: servicesConfig,
74      team: teamConfig,
75      brands: brandsConfig,
76      insurances: insurancesConfig,
77      testimonials: testimonialsConfig,
78      faq: faqConfig,
79    },
80    interior: interiorConfig,
81	visionSource: visionSourceConfig,
82    mapSettings, // Make map settings available to Alpine components
83
84    // === UI Modules ===
85    navbar: navbarModule,
86    heroCarousel,
87
88
89	// === CANVAS Carousel Detector ===
90    canvasCarousels: canvasCarouselDetector,
91
92    // === UI State ===
93    resizeObserver: null,
94
95    init() {
96
97	  // Register CANVAS carousel detector as Alpine store
98      Alpine.store("canvasCarousels", canvasCarouselDetector);
99
100      // Initial setup with small delay to ensure DOM is ready
101      requestAnimationFrame(() => {
102        this.updateNavbarHeight();
103        this.setupEventListeners();
104
105		this.initializeMap(); // Dynamically load map
106		this.initializeGallery();
107      });
108
109      Alpine.effect(() => {
110        if (this.navbar.scrolled) {
111          this.updateNavbarHeight();
112        }
113      });
114    },
115
116    waitForElement(selector, timeoutMs = 5000) {
117      const existing = document.querySelector(selector);
118      if (existing) return Promise.resolve(existing);
119
120      return new Promise((resolve) => {
121        const done = (el) => {
122          observer.disconnect();
123          clearTimeout(timer);
124          resolve(el);
125        };
126        const observer = new MutationObserver(() => {
127          const el = document.querySelector(selector);
128          if (el) done(el);
129        });
130        const timer = setTimeout(() => done(null), timeoutMs);
131        observer.observe(document.body, { childList: true, subtree: true });
132      });
133    },
134
135    /**
136     * Dynamically loads and initializes the map once its container exists on the page.
137     */
138    initializeMap() {
139      this.waitForElement('.google-map-container').then((container) => {
140        if (!container) return;
141        import('../scripts/map-handler.js')
142          .then(module => {
143            const mapHandler = module.default();
144            mapHandler.init();
145            Alpine.store('mapHandler', mapHandler);
146          })
147          .catch(err => console.error('Error loading map module', err));
148      });
149
150      // Vision Source V2 locations: the design's own pin map, separate from the shared
151      // multi-location map-handler above.
152      this.waitForElement('.vs-pin-map').then((pinMap) => {
153        if (!pinMap) return;
154        import('../scripts/map-vs-pin-map.js')
155          .then(module => module.initVsPinMaps())
156          .catch(err => console.error('Error loading VS pin map module', err));
157      });
158    },
159
160	initializeGallery() {
161      this.waitForElement('.blueimp-gallery').then((overlay) => {
162        if (!overlay) return;
163        import('../scripts/gallery-lightbox.js')
164          .then(module => module.initGalleryLightboxes())
165          .catch(err => console.error('Error loading gallery lightbox module', err));
166      });
167    },
168		
169    initializeAnalytics() {
170      setTimeout(() => {
171        initPhoneTracking(this.general);
172        initFormRedirects(this.general);
173      }, 1000);
174    },
175
176    setupEventListeners() {
177      this.setupResizeListener();
178      this.setupThemeChangeListener();
179
180      document.addEventListener('canvas-carousel-slide-change', (event) => {
181        console.log('CANVAS Carousel slide changed:', event.detail);
182      });
183
184      this.$el.addEventListener('alpine:destroying', () => {
185        this.cleanup();
186      });
187
188      const externalFormUrl = (value) => {
189        const v = (value || "").trim();
190        if (/^(https?:)?\/\//i.test(v)) return v;
191        if (/^www\./i.test(v)) return `https://${v}`;
192        return null;
193      };
194
195      document.querySelectorAll('.ry-pdf-link').forEach((link) => {
196        if (externalFormUrl(link.getAttribute('href'))) link.textContent = 'Go to Form';
197      });
198
199      // PDF Viewer using event delegation
200      document.body.addEventListener('click', function(e) {
201        const target = e.target.closest('.ry-pdf-link');
202        if (target) {
203          e.preventDefault();
204          const external = externalFormUrl(target.getAttribute("href"));
205          if (external) {
206            window.open(external, "_blank", "noopener");
207            return;
208          }
209          const filename = (target.getAttribute("href") || "").trim();
210          const url = new URL(window.location.href);
211          const dirname = url.origin + url.pathname.substring(0, url.pathname.lastIndexOf("/"));
212          window.open(`${dirname}/viewer.html?open_file=${filename}`, "_blank");
213        }
214      });
215    },
216
217    cleanup() {
218      if (this.resizeObserver) {
219        this.resizeObserver.disconnect();
220      }
221
222      if (canvasCarouselDetector) {
223        canvasCarouselDetector.destroy();
224      }
225    },
226
227
228    setupResizeListener() {
229      // Use ResizeObserver for more efficient resize handling when available
230      if (window.ResizeObserver) {
231        this.resizeObserver = new ResizeObserver(
232          this.debouncedNavbarHeightUpdate.bind(this),
233        );
234        const navbarElement = document.querySelector("nav");
235        if (navbarElement) {
236          this.resizeObserver.observe(navbarElement);
237        }
238      } else {
239        // Fallback to window resize event with debounce
240        let resizeTimeout;
241        window.addEventListener("resize", () => {
242          cancelAnimationFrame(resizeTimeout);
243          resizeTimeout = requestAnimationFrame(() => {
244            this.updateNavbarHeight();
245          });
246        });
247      }
248    },
249
250
251    setupThemeChangeListener() {
252      window.addEventListener("themechange", (e) => {
253        this.isDarkMode = e.detail.theme === "dark";
254      });
255    },
256
257
258    debouncedNavbarHeightUpdate() {
259      cancelAnimationFrame(this._navbarUpdateFrame);
260      this._navbarUpdateFrame = requestAnimationFrame(() => {
261        this.updateNavbarHeight();
262      });
263    },
264
265    /**
266     * Cleans up all event listeners
267     */
268    cleanupEventListeners() {
269      if (this.resizeObserver) {
270        this.resizeObserver.disconnect();
271      }
272      cancelAnimationFrame(this._navbarUpdateFrame);
273    },
274
275    /**
276     * Updates the current navbar height and sets a CSS variable
277     */
278    updateNavbarHeight() {
279      const navbarElement = document.querySelector("nav");
280      if (navbarElement) {
281        const height = navbarElement.offsetHeight;
282
283        // Only update if height has changed
284        if (height !== this.navbar.height) {
285          this.navbar.height = height;
286          document.documentElement.style.setProperty(
287            "--navbar-height",
288            `${height}px`,
289          );
290        }
291      }
292    },
293
294    getHeroPositionClasses() {
295      return uiState.getHeroPositionClasses(this.heroSettings.contentPosition);
296    },
297  };
298}
299
300
301window.Alpine = Alpine;
302
303Alpine.data("servicesSection", servicesConfig.servicesComponent);
304Alpine.data("modalComponent", modalComponent);
305Alpine.data("swiperComponent", swiperComponent);
306Alpine.data("vsServicesSlider", vsServicesSlider);
307Alpine.data("vsServicesDatasetSlider", vsServicesDatasetSlider);
308Alpine.data("vsTeamAutoplay", () => ({
309  _timer: null,
310  _delay: 5000,
311  _count() {
312    return document.querySelectorAll(".module-team .group-photo .swiper-slide").length;
313  },
314
315  _scroll() {
316    const el = document.querySelector(".module-team .group-photo .swiper");
317    if (el && el.swiper) el.swiper.slideTo(this.$store.vsTeam.active, 600, false);
318  },
319  // Select a member (thumbnail / dot click) and bring it into view.
320  goTo(i) {
321    this.$store.vsTeam.active = i;
322    this._scroll();
323  },
324  _advance() {
325    const s = this.$store.vsTeam;
326    const n = this._count();
327    if (!n) return;
328    s.active = (s.active + 1) % n;
329    this._scroll();
330  },
331  play() {
332    this.stop();
333    this._timer = setInterval(() => this._advance(), this._delay);
334  },
335  stop() {
336    if (this._timer) {
337      clearInterval(this._timer);
338      this._timer = null;
339    }
340  },
341}));
342Alpine.data("faqSection", () => ({
343  homepage: faqConfig.homepage,
344  activeFaq: null,
345}));
346
347Alpine.data("snap", snap);
348Alpine.plugin(collapse);
349window.canvasFieldValues = Object.assign({}, vsFieldValues, window.canvasFieldValues);
350Alpine.plugin(resolveCustomFieldsPlugin);
351Alpine.plugin(alpinePhonePlugin);
352
353document.addEventListener('DOMContentLoaded', () => {
354    Alpine.start();
355});

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.