PageSourceSearch

https://lodgingconference.com/wp-content/themes/the-lodging-conference/js/agenda.js?ver=1767120955

js lodgingconference.com collected 2026-09-26 01:02:50 UTC 7,203 bytes, 253 lines download raw bytes

1/**
2 * Agenda.js - Handles agenda page functionality
3 *
4 * This script provides:
5 * 1. Tab functionality for day switching
6 * 2. Current time slot highlighting
7 * 3. Animations for content
8 */
9document.addEventListener('DOMContentLoaded', () => {
10  // Get tab elements
11  const tabs = document.querySelectorAll('.agenda-tab');
12  const panels = document.querySelectorAll('.agenda-day');
13
14  // Add subtle animation to agenda slots
15  const slots = document.querySelectorAll('.agenda-slot');
16  addAnimationToSlots(slots);
17
18  // Enhance current time slot highlighting
19  highlightCurrentTimeSlot();
20
21  // Setup tabs functionality
22  setupTabs();
23
24  /**
25   * Sets up tab functionality
26   */
27  function setupTabs() {
28    // Check for hash in URL to set initial active tab
29    const initialTabId = location.hash.replace('#day-', '');
30    if (initialTabId) {
31      const tabToActivate = document.getElementById(`day-${initialTabId}`);
32      if (tabToActivate) {
33        activateTab(tabToActivate);
34      }
35    }
36
37    // Add click event to each tab
38    tabs.forEach(tab => {
39      tab.addEventListener('click', function () {
40        activateTab(this);
41        // Update URL hash without scrolling
42        const id = this.id.replace('day-', '');
43        history.replaceState(null, '', `#day-${id}`);
44      });
45
46      // Add keyboard accessibility
47      tab.addEventListener('keydown', e => {
48        if (e.key === 'Enter' || e.key === ' ') {
49          e.preventDefault();
50          activateTab(tab);
51        }
52      });
53    });
54  }
55
56  /**
57   * Activates a specific tab and its corresponding panel
58   */
59  function activateTab(tabToActivate) {
60    // Deactivate all tabs and panels
61    tabs.forEach(tab => {
62      tab.classList.remove('active');
63      tab.setAttribute('aria-selected', 'false');
64    });
65
66    panels.forEach(panel => {
67      panel.classList.remove('active');
68    });
69
70    // Activate the selected tab and panel
71    tabToActivate.classList.add('active');
72    tabToActivate.setAttribute('aria-selected', 'true');
73
74    const panelId = tabToActivate.getAttribute('aria-controls');
75    const panel = document.getElementById(panelId);
76    if (panel) {
77      panel.classList.add('active');
78
79      // Re-apply animations to visible slots
80      resetSlotAnimations(panel);
81    }
82  }
83
84  /**
85   * Resets animations for slots when a tab is activated
86   */
87  function resetSlotAnimations(panel) {
88    const visibleSlots = panel.querySelectorAll('.agenda-slot');
89    visibleSlots.forEach((slot, index) => {
90      // Reset animation
91      slot.classList.remove('visible');
92
93      // Force reflow to restart animation
94      void slot.offsetWidth;
95
96      // Add visible class with staggered timing
97      setTimeout(() => {
98        slot.classList.add('visible');
99      }, 50 + index * 100);
100    });
101  }
102
103  /**
104   * Adds subtle entrance animation to agenda slots
105   */
106  function addAnimationToSlots(slots) {
107    if (window.IntersectionObserver) {
108      const observer = new IntersectionObserver(
109        entries => {
110          entries.forEach(entry => {
111            if (entry.isIntersecting) {
112              const delay = parseInt(entry.target.dataset.delay || 0);
113              setTimeout(() => {
114                entry.target.classList.add('visible');
115              }, delay);
116              observer.unobserve(entry.target);
117            }
118          });
119        },
120        {
121          root: null,
122          rootMargin: '0px',
123          threshold: 0.1,
124        }
125      );
126
127      slots.forEach(slot => {
128        observer.observe(slot);
129      });
130    } else {
131      // Fallback for browsers without Intersection Observer
132      slots.forEach((slot, index) => {
133        setTimeout(() => {
134          slot.classList.add('visible');
135        }, 100 + index * 100);
136      });
137    }
138  }
139
140  /**
141   * Finds and highlights the current timeslot if viewing today's agenda
142   */
143  function highlightCurrentTimeSlot() {
144    const today = new Date();
145
146    // Process each day section
147    panels.forEach(panel => {
148      const dateText = getDateFromElement(panel);
149      if (!dateText) return;
150
151      // Compare with today's date
152      const eventDate = new Date(dateText);
153      if (isNaN(eventDate.getTime())) return;
154
155      // If this is today's panel, highlight it and also activate its tab
156      if (isSameDay(eventDate, today)) {
157        highlightActiveSlots(panel, today);
158
159        // Find the tab that controls this panel and activate it
160        const tabId = panel.getAttribute('aria-labelledby');
161        const todayTab = document.getElementById(tabId);
162        if (todayTab && !todayTab.classList.contains('active')) {
163          activateTab(todayTab);
164        }
165      }
166    });
167  }
168
169  /**
170   * Highlights time slots that are currently active
171   */
172  function highlightActiveSlots(panelElement, now) {
173    const slots = panelElement.querySelectorAll('.agenda-slot');
174
175    slots.forEach(slot => {
176      const timeElement = slot.querySelector('.slot-time');
177      if (!timeElement) return;
178
179      // Get all spans but exclude those with icon class
180      const timeNodes = Array.from(timeElement.childNodes).filter(
181        node =>
181 node.nodeType === 1 && !node.classList.contains('time-icon')
182      );
183
184      const timeSpans = timeNodes.length ? timeNodes : timeElement.querySelectorAll('span');
185      if (timeSpans.length === 0) return;
186
187      const startText = timeSpans[0]?.textContent?.trim();
188      const endText =
189        timeSpans.length > 1 ? timeSpans[timeSpans.length - 1]?.textContent?.trim() : null;
190
191      if (!startText) return;
192
193      const start = parseTime(startText, now);
194      // Default to 1 hour duration if no end time
195      const end = endText ? parseTime(endText, now) : new Date(start.getTime() + 60 * 60 * 1000);
196
197      if (now >= start && now <= end) {
198        slot.classList.add('slot-now');
199      }
200    });
201  }
202
203  /**
204   * Utility function to extract date text from a day element
205   */
206  function getDateFromElement(element) {
207    if (!element) return null;
208    const dateElement = element.querySelector('.agenda-day-date');
209    return dateElement ? dateElement.textContent.trim() : null;
210  }
211
212  /**
213   * Utility function to check if two dates are the same day
214   */
215  function isSameDay(date1, date2) {
216    return (
217      date1.getFullYear() === date2.getFullYear() &&
218      date1.getMonth() === date2.getMonth() &&
219      date1.getDate() === date2.getDate()
220    );
221  }
222
223  /**
224   * Utility function to parse time string into Date object
225   */
226  function parseTime(timeStr, referenceDate) {
227    // Handle various time formats
228    let time = timeStr;
229    let period = '';
230
231    if (timeStr.includes(' ')) {
232      [time, period] = timeStr.split(' ');
233    } else if (timeStr.includes('AM') || timeStr.includes('PM')) {
234      period = timeStr.slice(-2);
235      time = timeStr.slice(0, -2);
236    }
237
238    const [h, m] = time.split(':').map(Number);
239
240    // Clone reference date to avoid modifying the original
241    const date = new Date(referenceDate.getTime());
242
243    let hours = h || 0;
244
245    if (period) {
246      if (period.toUpperCase() === 'PM' && hours < 12) hours += 12;
247      if (period.toUpperCase() === 'AM' && hours === 12) hours = 0;
248    }
249
250    date.setHours(hours, m || 0, 0, 0);
251    return date;
252  }
253});

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.