PageSourceSearch

https://parklandposse.com/js/jscript.js?v=1.20

js parklandposse.com collected 2026-10-02 22:37:40 UTC 19,291 bytes, 591 lines download raw bytes

1const btn = document.getElementById('menu-btn')
2
3btn.addEventListener('click', navToggle)
4
5function navToggle() {
6    btn.classList.toggle('open')
7}
8
9function data() {
10    return {
11        sortBy: "",
12        sortAsc: false,
13        sortByColumn($event) {
14            if (this.sortBy === $event.target.innerText) {
15                if (this.sortAsc) {
16                    this.sortBy = "";
17                    this.sortAsc = false;
18                } else {
19                    this.sortAsc = !this.sortAsc;
20                }
21            } else {
22                this.sortBy = $event.target.innerText;
23            }
24
25            let rows = this.getTableRows()
26                .sort(
27                    this.sortCallback(
28                        Array.from($event.target.parentNode.children).indexOf(
29                            $event.target
30                        )
31                    )
32                )
33                .forEach((tr) => {
34                    this.$refs.tbody.appendChild(tr);
35                });
36        },
37        getTableRows() {
38            return Array.from(this.$refs.tbody.querySelectorAll("tr"));
39        },
40        getCellValue(row, index) {
41            return row.children[index].innerText;
42        },
43        sortCallback(index) {
44            return (a, b) =>
45                ((row1, row2) => {
46                    return row1 !== "" &&
47                        row2 !== "" &&
48                        !isNaN(row1) &&
49                        !isNaN(row2)
50                        ? row1 - row2
51                        : row1.toString().localeCompare(row2);
52                })(
53                    this.getCellValue(this.sortAsc ? a : b, index),
54                    this.getCellValue(this.sortAsc ? b : a, index)
55                );
56        }
57    };
58}
59
60// User Menu Shrink
61
62    function navShrink() {
63      return {
64        scale: 1,
65        padding: 1,
66        fontScale: 1,
67        get navStyle() {
68          return window.innerWidth >= 768
69            ? `padding-top:${this.padding}rem;padding-bottom:${this.padding}rem;`
70            : '';
71        },
72        get logoStyle() {
73          return window.innerWidth >= 768
74            ? `transform:scale(${this.scale});transform-origin:center;`
75            : '';
76        },
77        get linkStyle() {
78          return window.innerWidth >= 768
79            ? `transform:scale(${this.fontScale});transform-origin:center;`
80            : '';
81        },
82        init() {
83          const maxScroll = 150;
84          const minScale = 0.70;
85          const minPadding = 0; // rem
86          const maxPadding = 1;    // rem
87          const minFontScale = 0.95;
88
89          const update = () => {
90            if (window.innerWidth < 768) return;
91
92            const scrollY = Math.min(window.scrollY, maxScroll);
93            const progress = scrollY / maxScroll;
94
95            // interpolate sizes
96            this.scale = 1 - (1 - minScale) * progress;
97            this.padding = maxPadding - (maxPadding - minPadding) * progress;
98            this.fontScale = 1 - (1 - minFontScale) * progress;
99          };
100
101          // run once
102          update();
103
104          window.addEventListener('scroll', () => {
105            requestAnimationFrame(update);
106          }, { passive: true });
107
108          window.addEventListener('resize', update);
109        }
110      }
111    }
112
113
114// User Menu Listener & Overflow
115
116      function navMenu() {
117        return {
118          mobileOpen: false,
119          openMenu: null,
120          overflowItems: [],
121          init() {
122            this.handleOverflow();
123            this.setupResizeObserver();
124            window.addEventListener('resize', () => {
125              this.handleOverflow();
126              this.openMenu = null;
127              if(window.innerWidth < 768) this.mobileOpen = false;
128            });
129          },
130
131          setupResizeObserver() {
132            const menu = document.getElementById('user-menu');
133            if (!menu) return;
134            let scheduled = false;
135            this.resizeObserver = new ResizeObserver(() => {
136              if (scheduled) return;
137              scheduled = true;
138              requestAnimationFrame(() => {
139                this.handleOverflow();
140                scheduled = false;
141              });
142            });
143            this.resizeObserver.observe(menu);
144          },
145
146          handleOverflow() {
147            const menu = document.getElementById('user-menu');
148            if (!menu) return;
149            const moreMenu = document.getElementById('moreMenuButton');
150            if (!moreMenu) return;
151
152            const items = Array.from(menu.children).filter(li => li.id !== 'moreMenuLi');
153            const availableWidth = menu.clientWidth;
154            let usedWidth = 0;
155            const moreWidth = moreMenu.getBoundingClientRect().width || 80;
156
157            this.overflowItems = [];
158            items.forEach(i => i.style.display = 'inline-flex');
159
160            let lastVisibleIndex = items.length;
161
162            for(let i=0; i < items.length; i++) {
163              const itemWidth = items[i].getBoundingClientRect().width;
164              usedWidth += itemWidth;
165              if(usedWidth + moreWidth > availableWidth) {
166                lastVisibleIndex = i;
167                break;
168              }
169            }
170
171            if(lastVisibleIndex < items.length) {
172              for(let i = lastVisibleIndex; i < items.length; i++) {
173                this.overflowItems.push(items[i].outerHTML);
174                items[i].style.display = 'none';
175              }
176              document.getElementById('moreMenuLi').style.display = 'inline-flex';
177            } else {
178              document.getElementById('moreMenuLi').style.display = 'none';
179            }
180          },
181      
182          watch: {
183            mobileOpen(value) {
184              document.body.style.overflow = value ? 'hidden' : '';
185            }
186          },
187
188          toggleMobileMenu() {
189            this.mobileOpen = !this.mobileOpen;
190            this.openMenu = null;
191          },
192
193          toggleSubmenu(name) {
194            if (this.openMenu === name) {
195              this.openMenu = null;
196            } else {
197              this.openMenu = name;
198            }
199          },
200
201          closeMenus() {
202            this.openMenu = null;
203          }
204        }
205      }
206
207// Image Carousel-Lightbox Script
208
209function imageCarousel() {
210  return {
211    current: 0,
212    lightboxOpen: false,
213    activeImage: '',
214    activeTitle: '',
215    slides: [],
216
217    init() {
218      this.slides = Array.from(this.$refs.source.children).map(el => ({
219        thumb: el.dataset.thumb,
220        title: el.dataset.title
221      }));
222    },
223
224    next() {
225      if (this.current < this.slides.length - 1) this.current++;
226    },
227    prev() {
228      if (this.current > 0) this.current--;
229    },
230    goTo(i) {
231      this.current = i;
232    },
233
234    openLightbox(slide) {
235      this.activeImage = slide.thumb;
236      this.activeTitle = slide.title;
237      this.lightboxOpen = true;
238    },
239    closeLightbox() {
240      this.lightboxOpen = false;
241      this.activeImage = '';
242      this.activeTitle = '';
243    },
244
245    swipeStartX: null,
246    swipeDeltaX: null,
247    startSwipe(e) { this.swipeStartX = e.clientX; },
248    trackSwipe(e) {
249      if (this.swipeStartX !== null) this.swipeDeltaX = e.clientX - this.swipeStartX;
250    },
251    endSwipe() {
252      if (this.swipeDeltaX > 50) this.prev();
253      else if (this.swipeDeltaX < -50) this.next();
254      this.cancelSwipe();
255    },
256    cancelSwipe() {
257      this.swipeStartX = null;
258      this.swipeDeltaX = null;
259    }
260  };
261}
262      
263
264// Video Carousel-Lightbox Script
265
266function vidCarousel() {
267  return {
268    current: 0,
269    lightboxOpen: false,
270    activeVideo: null,
271    embedUrl: '',
272    slides: [],
273
274    async init() {
275      this.slides = Array.from(document.querySelectorAll('[data-type]'));
276
277      // Fetch thumbnails
278      for (let el of this.slides) {
279        if (el.dataset.type === 'youtube') {
280          el.dataset.thumb = `https://img.youtube.com/vi/${el.dataset.id}/hqdefault.jpg`;
281        } else if (el.dataset.type === 'vimeo') {
282          try {
283            const res = await fetch(`https://vimeo.com/api/oembed.json?url=https://vimeo.com/${el.dataset.id}`);
284            const data = await res.json();
285            el.dataset.thumb = data.thumbnail_url;
286          } catch {
287            el.dataset.thumb = `https://vumbnail.com/${el.dataset.id}.jpg`;
288          }
289        }
290      };
291    },
292
293    next() {
294      if (this.current < this.slides.length - 1) this.current++;
295    },
296    prev() {
297      if (this.current > 0) this.current--;
298    },
299    goTo(i) {
300      this.current = i;
301    },
302
303    openLightbox(dataset) {
304      this.activeVideo = dataset;
305      if (dataset.type === 'youtube') {
306        this.embedUrl = `https://www.youtube.com/embed/${dataset.id}?autoplay=1`;
307      } else if (dataset.type === 'vimeo') {
308        this.embedUrl = `https://player.vimeo.com/video/${dataset.id}?autoplay=1`;
309      }
310      this.lightboxOpen = true;
311    },
312
313    closeLightbox() {
314      this.lightboxOpen = false;
315      this.activeVideo = null;
316      this.embedUrl = '';
317    },
318
319    swipeStartX: null,
320    swipeDeltaX: null,
321    startSwipe(e) { this.swipeStartX = e.clientX; },
322    trackSwipe(e) {
323      if (this.swipeStartX !== null) this.swipeDeltaX = e.clientX - this.swipeStartX;
324    },
325    endSwipe() {
326      if (this.swipeDeltaX > 50) this.prev();
327      else if (this.swipeDeltaX < -50) this.next();
328      this.cancelSwipe();
329    },
330    cancelSwipe() {
331      this.swipeStartX = null;
332      this.swipeDeltaX = null;
333    }
334  };
335}
336      
337
338// Game Ticker
339
340function carousel() {
341      return {
342        currentIndex: 0,
343        totalCards: 0,
344        cardWidth: 0,
345        gap: 0,
346        step: 0,
347        visibleCards: 1,
348
349        init() {
350          this.syncFromDOM();
351          window.addEventListener('resize', () => this.syncFromDOM());
352          requestAnimationFrame(() => this.syncFromDOM());
353          setTimeout(() => this.syncFromDOM(), 150);
354        },
355
356        syncFromDOM() {
357          const track = this.$refs.track;
358          const viewport = this.$refs.viewport;
359          if (!track || !viewport || track.children.length === 0) return;
360
361          this.totalCards = track.children.length;
362
363          const firstCard = track.children[0];
364          const rect = firstCard.getBoundingClientRect();
365          const styles = getComputedStyle(firstCard);
366          this.cardWidth = rect.width;
367          this.gap = parseFloat(styles.marginRight || '0') || 0;
368          this.step = this.cardWidth + this.gap;
369
370          const viewportWidth = viewport.clientWidth;
371          this.visibleCards = Math.min(
372            Math.max(1, Math.floor((viewportWidth + this.gap) / this.step)),
373            this.totalCards
374          );
375
376          const lastIndex = Math.max(0, this.totalCards - this.visibleCards);
377          if (this.currentIndex > lastIndex) this.currentIndex = lastIndex;
378        },
379
380        next() {
381          const lastIndex = Math.max(0, this.totalCards - this.visibleCards);
382          if (this.currentIndex < lastIndex) {
383            this.currentIndex++;
384          } else {
385            this.currentIndex = 0;
386          }
387        },
388
389        prev() {
390          const lastIndex = Math.max(0, this.totalCards - this.visibleCards);
391          if (this.currentIndex > 0) {
392            this.currentIndex--;
393          } else {
394            this.currentIndex = lastIndex;
395          }
396        }
397      };
398    }
399
400// Game Ticker V2 - native momentum scroller (smooth paged arrows, mouse drag,
401// touch swipe via native overflow scrolling, scroll-snap). Modelled on the
402// old MSA4 slick slider feel.
403function gameTickerScroller() {
404    return {
405        atStart: true,
406        atEnd: false,
407        hasOverflow: false,
408
409        init() {
410            const vp = this.$refs.viewport;
411            if (!vp) return;
412
413            vp.addEventListener('scroll', () => this.update(), { passive: true });
414            window.addEventListener('resize', () => this.update());
415            this.update();
416            requestAnimationFrame(() => this.update());
417            setTimeout(() => this.update(), 150);
418
419            this.initDrag(vp);
420        },
421
422        update() {
423            const vp = this.$refs.viewport;
424            if (!vp) return;
425            const max = vp.scrollWidth - vp.clientWidth;
426            this.hasOverflow = max > 1;
427            this.atStart = vp.scrollLeft <= 1;
428            this.atEnd = vp.scrollLeft >= max - 1;
429        },
430
431        // Slide a full page of visible cards (slick slidesToScroll behavior)
432        page(dir) {
433            const vp = this.$refs.viewport;
434            if (!vp) return;
435            const card = vp.querySelector('.game-ticker-card');
436            const step = card ? card.getBoundingClientRect().width : 320;
437            const cardsPerPage = Math.max(1, Math.floor(vp.clientWidth / step));
438            vp.scrollBy({ left: dir * cardsPerPage * step, behavior: 'smooth' });
439        },
440
441        // Desktop mouse drag-to-scroll (touch devices scroll natively).
442        // Scrolls ONLY while the left button is held; releasing anywhere ends the
443        // drag immediately. Pointer capture keeps move/up events flowing to the
444        // viewport even when the cursor leaves it, so the drag can never get stuck.
445        initDrag(vp) {
446            let isDown = false, startX = 0, startLeft = 0, moved = false;
447
448            const endDrag = () => {
449                isDown = false;
450                vp.classList.remove('dragging');
451            };
452
453            // The cards are covered by anchor overlays - block native link/image
454            // dragging which would hijack the pointer stream mid-drag
455            vp.addEventListener('dragstart', (e) => e.preventDefault());
456
457            vp.addEventListener('pointerdown', (e) => {
458                if (e.pointerType !== 'mouse' || e.button !== 0) return;
459                isDown = true;
460                moved = false;
461                startX = e.clientX;
462                startLeft = vp.scrollLeft;
463                // No capture/preventDefault here - a clean stationary click must
464                // reach the card links; the drag only starts once the mouse moves
465            });
466
467            vp.addEventListener('pointermove', (e) => {
468                if (!isDown) return;
469                // Button no longer held (released outside, lost focus, etc.) - stop
470                if ((e.buttons & 1) !== 1) {
471                    endDrag();
472                    return;
473                }
474                const dx = e.clientX - startX;
475                if (!moved && Math.abs(dx) > 5) {
476                    // Threshold crossed - now it's a drag: capture the pointer so
477                    // move/up keep flowing to the viewport even off-element
478                    moved = true;
479                    vp.setPointerCapture(e.pointerId);
480                    vp.classList.add('dragging');
481                }
482                if (moved) vp.scrollLeft = startLeft - dx;
483            });
484
485            vp.addEventListener('pointerup', endDrag);
486            vp.addEventListener('pointercancel', endDrag);
487            vp.addEventListener('lostpointercapture', endDrag);
488
489            // Swallow the click that follows a drag so card links don't navigate
490            // (matches the old slick behavior: click-and-scroll never navigates)
491            vp.addEventListener('click', (e) => {
492                if (moved) {
493                    e.preventDefault();
494                    e.stopPropagation();
495                    moved = false;
496                }
497            }, true);
498        }
499    };
500}
501
502// Head to Head Bars
503
504function barStat(teamA, teamB, label, valueA, valueB) {
505      return {
506        teamA,
507        teamB,
508        label,
509        valueA,
510        valueB,
511        widthA: 0,
512        widthB: 0,
513        animate() {
514          setTimeout(() => {
515            this.widthA = this.valueA;
516            this.widthB = this.valueB;
517          }, 200);
518        }
519      }
520    }
521
522// Checkout: SportsPay embedded payment iframe callback (required by CheckoutPage.razor)
523window.__registerCheckoutCallback = function (dotNetRef) {
524    window.__checkoutRef = dotNetRef;
525    window.onEmbedPayResult = function (data) {
526        if (window.__checkoutRef) {
527            window.__checkoutRef.invokeMethodAsync('OnPaymentResult', JSON.stringify(data));
528        }
529    };
530};
531window.__checkoutCleanup = function () {
532    delete window.onEmbedPayResult;
533    window.__checkoutRef = null;
534};
535
536// Calendar subscribe: report the browser's IANA timezone (e.g. "America/Toronto")
537// so .ics feed links can carry the user's timezone as a query param.
538// (required by AssocMasterSchedule.razor)
539window.getBrowserTimeZone = function () {
540    try {
541        return Intl.DateTimeFormat().resolvedOptions().timeZone || '';
542    } catch (e) {
543        return '';
544    }
545};
546
547// Anchor Link Handler
548// <base href="/"> makes #hash links resolve to /#hash instead of /current-page#hash.
549// This intercepts hash-only clicks and handles them as same-page scroll.
550document.addEventListener('click', function (e) {
551    var link = e.target.closest('a[href]');
552    if (!link) return;
553    var href = link.getAttribute('href');
554    if (!href || !href.startsWith('#') || href.length < 2) return;
555    e.preventDefault();
556    var id = href.substring(1);
557    var target = document.getElementById(id) || document.querySelector('[name="' + id.replace(/"/g, '\\"') + '"]');
558    if (target) {
559        target.scrollIntoView({ behavior: 'smooth' });
560    }
561    history.replaceState(null, '', window.location.pathname + window.location.search + href);
562});
563
564// Hero Section Parallax Scrolling
565
566    (function () {
567      var reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
568      var section = document.querySelector('[data-parallax-section]');
569      var bg = document.querySelector('[data-parallax]');
570      if (!section || !bg) return;
571
572      var SPEED = 0.16;          // background moves at ~16% of the scroll distance
573      var ticking = false;
574
575      function apply() {
576        ticking = false;
577        if (reduce.matches) { bg.style.transform = 'translate3d(0,0,0)'; return; }
578        var rect = section.getBoundingClientRect();
579        var blockCenter = rect.top + rect.height / 2;
580        var delta = (window.innerHeight / 2) - blockCenter;  // 0 when block is centered
581        bg.style.transform = 'translate3d(0,' + (delta * SPEED).toFixed(2) + 'px,0)';
582      }
583      function onScroll() {
584        if (!ticking) { ticking = true; requestAnimationFrame(apply); }
585      }
586
587      window.addEventListener('scroll', onScroll, { passive: true });
588      window.addEventListener('resize', onScroll);
589      if (reduce.addEventListener) reduce.addEventListener('change', apply);
590      apply();
591    })();

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.