1+ // Lazy loading countdown timer system
2+ // Keeps Luxon for date/time handling as requested
3+ ( function ( ) {
4+ 'use strict' ;
5+
6+ // Timer management
7+ const activeTimers = new Map ( ) ;
8+ const TIMER_BATCH_SIZE = 10 ;
9+ const UPDATE_INTERVAL = 1000 ;
10+
11+ // Single shared timer for all countdowns
12+ let globalTimer = null ;
13+ let visibleConferences = new Set ( ) ;
14+
15+ // Initialize Intersection Observer for lazy loading
16+ const observerOptions = {
17+ root : null ,
18+ rootMargin : '50px' ,
19+ threshold : 0.01
20+ } ;
21+
22+ const observer = new IntersectionObserver ( ( entries ) => {
23+ entries . forEach ( entry => {
24+ const confId = entry . target . dataset . confId ;
25+
26+ if ( entry . isIntersecting ) {
27+ // Conference card is visible
28+ if ( ! visibleConferences . has ( confId ) ) {
29+ visibleConferences . add ( confId ) ;
30+ initializeCountdown ( entry . target ) ;
31+ }
32+ } else {
33+ // Conference card is hidden
34+ if ( visibleConferences . has ( confId ) ) {
35+ visibleConferences . delete ( confId ) ;
36+ cleanupCountdown ( confId ) ;
37+ }
38+ }
39+ } ) ;
40+
41+ // Manage global timer based on visible conferences
42+ manageGlobalTimer ( ) ;
43+ } , observerOptions ) ;
44+
45+ function initializeCountdown ( element ) {
46+ const confId = element . dataset . confId ;
47+ const countdownElements = element . querySelectorAll ( '.countdown-display' ) ;
48+
49+ if ( ! countdownElements . length ) return ;
50+
51+ // Get deadline from the countdown element's data attribute
52+ const deadlineStr = countdownElements [ 0 ] . dataset . deadline ;
53+ if ( ! deadlineStr ) return ;
54+
55+ // Parse deadline using Luxon (keeping existing library)
56+ const deadline = luxon . DateTime . fromISO ( deadlineStr ) ;
57+
58+ // Store timer data for both regular and small countdown
59+ countdownElements . forEach ( ( el , index ) => {
60+ const timerId = `${ confId } -${ index } ` ;
61+ activeTimers . set ( timerId , {
62+ element : el ,
63+ deadline : deadline ,
64+ isSmall : el . classList . contains ( 'countdown-small' )
65+ } ) ;
66+
67+ // Immediate update
68+ updateCountdown ( timerId ) ;
69+ } ) ;
70+ }
71+
72+ function cleanupCountdown ( confId ) {
73+ // Clean up all timers for this conference
74+ const keysToDelete = [ ] ;
75+ activeTimers . forEach ( ( timer , key ) => {
76+ if ( key . startsWith ( confId + '-' ) ) {
77+ keysToDelete . push ( key ) ;
78+ }
79+ } ) ;
80+ keysToDelete . forEach ( key => activeTimers . delete ( key ) ) ;
81+ }
82+
83+ function updateCountdown ( timerId ) {
84+ const timer = activeTimers . get ( timerId ) ;
85+ if ( ! timer ) return ;
86+
87+ const now = luxon . DateTime . now ( ) ;
88+ const diff = timer . deadline . diff ( now , [ 'days' , 'hours' , 'minutes' , 'seconds' ] ) ;
89+
90+ if ( diff . toMillis ( ) <= 0 ) {
91+ // Deadline passed
92+ if ( timer . element ) {
93+ timer . element . innerHTML = timer . isSmall ? 'Passed' : 'Deadline passed' ;
94+ }
95+ activeTimers . delete ( timerId ) ;
96+ } else {
97+ // Update countdown display
98+ const days = Math . floor ( diff . days ) ;
99+ const hours = Math . floor ( diff . hours ) ;
100+ const minutes = Math . floor ( diff . minutes ) ;
101+ const seconds = Math . floor ( diff . seconds ) ;
102+
103+ if ( timer . element ) {
104+ if ( timer . isSmall ) {
105+ // Small format: "2d 14:30:45"
106+ timer . element . innerHTML = `${ days } d ${ hours . toString ( ) . padStart ( 2 , '0' ) } :${ minutes . toString ( ) . padStart ( 2 , '0' ) } :${ seconds . toString ( ) . padStart ( 2 , '0' ) } ` ;
107+ } else {
108+ // Full format: "2 days 14h 30m 45s"
109+ timer . element . innerHTML = `${ days } days ${ hours } h ${ minutes } m ${ seconds } s` ;
110+ }
111+ }
112+ }
113+ }
114+
115+ function updateAllCountdowns ( ) {
116+ // Update only visible countdowns
117+ activeTimers . forEach ( ( timer , timerId ) => {
118+ updateCountdown ( timerId ) ;
119+ } ) ;
120+
121+ // Remove expired timers
122+ if ( activeTimers . size === 0 ) {
123+ stopGlobalTimer ( ) ;
124+ }
125+ }
126+
127+ function manageGlobalTimer ( ) {
128+ if ( activeTimers . size > 0 && ! globalTimer ) {
129+ // Start global timer
130+ globalTimer = setInterval ( updateAllCountdowns , UPDATE_INTERVAL ) ;
131+ } else if ( activeTimers . size === 0 && globalTimer ) {
132+ // Stop global timer
133+ stopGlobalTimer ( ) ;
134+ }
135+ }
136+
137+ function stopGlobalTimer ( ) {
138+ if ( globalTimer ) {
139+ clearInterval ( globalTimer ) ;
140+ globalTimer = null ;
141+ }
142+ }
143+
144+ // Public API for filtering integration
145+ window . CountdownManager = {
146+ // Called when conferences are filtered
147+ onFilterUpdate : function ( ) {
148+ // Re-observe visible conferences
149+ document . querySelectorAll ( '.ConfItem:not([style*="display: none"])' ) . forEach ( conf => {
150+ observer . observe ( conf ) ;
151+ } ) ;
152+
153+ // Unobserve hidden conferences
154+ document . querySelectorAll ( '.ConfItem[style*="display: none"]' ) . forEach ( conf => {
155+ observer . unobserve ( conf ) ;
156+ const confId = conf . dataset . confId ;
157+ if ( confId && visibleConferences . has ( confId ) ) {
158+ visibleConferences . delete ( confId ) ;
159+ cleanupCountdown ( confId ) ;
160+ }
161+ } ) ;
162+
163+ manageGlobalTimer ( ) ;
164+ } ,
165+
166+ // Initialize on page load
167+ init : function ( ) {
168+ // Observe all conference cards
169+ document . querySelectorAll ( '.ConfItem' ) . forEach ( conf => {
170+ // Add conf ID if not present
171+ if ( ! conf . dataset . confId ) {
172+ const link = conf . querySelector ( 'a[id]' ) ;
173+ if ( link ) {
174+ conf . dataset . confId = link . id ;
175+ }
176+ }
177+
178+ // Start observing
179+ observer . observe ( conf ) ;
180+ } ) ;
181+ } ,
182+
183+ // Cleanup
184+ destroy : function ( ) {
185+ observer . disconnect ( ) ;
186+ stopGlobalTimer ( ) ;
187+ activeTimers . clear ( ) ;
188+ visibleConferences . clear ( ) ;
189+ }
190+ } ;
191+
192+ // Auto-initialize when DOM is ready
193+ if ( document . readyState === 'loading' ) {
194+ document . addEventListener ( 'DOMContentLoaded' , ( ) => CountdownManager . init ( ) ) ;
195+ } else {
196+ CountdownManager . init ( ) ;
197+ }
198+ } ) ( ) ;
0 commit comments