PageSourceSearch

https://mug.org.ar/wp-content/plugins/dilux-one-countdown/assets/js/countdown.js?ver=1.0.0

js mug.org.ar collected 2026-10-02 11:48:02 UTC 13,896 bytes, 410 lines download raw bytes

1/**
2 * Dilux One Countdown - Frontend JavaScript
3 * Handles countdown timer logic and SVG progress animations
4 */
5
6(function() {
7    'use strict';
8
9    /**
10     * Initialize all countdowns on page
11     */
12    function initCountdowns() {
13        const countdowns = document.querySelectorAll('.dilux-countdown:not(.dilux-countdown-expired-state)');
14
15        countdowns.forEach(function(countdown) {
16            const countdownObj = new DiluxCountdown(countdown);
17            countdownObj.start();
18        });
19    }
20
21    /**
22     * Countdown Timer Class
23     */
24    class DiluxCountdown {
25        constructor(element) {
26            this.element = element;
27            this.expiryTimestamp = parseInt(element.dataset.expiry);
28            this.showMessage = element.dataset.showMessage === '1';
29            this.blurOnExpiry = element.dataset.blur === '1';
30            this.message = element.dataset.message || 'This countdown has expired';
31            this.animation = element.dataset.animation || 'none';
32            this.celebrationType = element.dataset.celebrationType || 'classic';
33            this.units = element.querySelectorAll('.dilux-countdown-unit');
34            this.interval = null;
35        }
36
37        /**
38         * Start the countdown
39         */
40        start() {
41            // Initial update
42            this.update();
43
44            // Update every second
45            this.interval = setInterval(() => {
46                this.update();
47            }, 1000);
48        }
49
50        /**
51         * Stop the countdown
52         */
53        stop() {
54            if (this.interval) {
55                clearInterval(this.interval);
56                this.interval = null;
57            }
58        }
59
60        /**
61         * Update countdown display
62         */
63        update() {
64            const now = Math.floor(Date.now() / 1000);
65            const remaining = this.expiryTimestamp - now;
66
67            if (remaining <= 0) {
68                this.handleExpiry();
69                return;
70            }
71
72            const time = this.calculateTime(remaining);
73            this.updateDisplay(time);
74        }
75
76        /**
77         * Calculate time units from remaining seconds
78         */
79        calculateTime(seconds) {
80            return {
81                days: Math.floor(seconds / 86400),
82                hours: Math.floor((seconds % 86400) / 3600),
83                minutes: Math.floor((seconds % 3600) / 60),
84                seconds: seconds % 60
85            };
86        }
87
88        /**
89         * Update display for each unit
90         */
91        updateDisplay(time) {
92            this.units.forEach((unit) => {
93                const unitType = unit.dataset.unit;
94                const value = time[unitType];
95                const max = parseInt(unit.dataset.max);
96
97                // Update number
98                const numberEl = unit.querySelector('.dilux-countdown-number');
99                if (numberEl) {
100                    numberEl.textContent = value;
101                }
102
103                // Update progress circle
104                this.updateProgress(unit, value, max);
105            });
106        }
107
108        /**
109         * Update SVG circle progress
110         */
111        updateProgress(unit, currentValue, maxValue) {
112            const circle = unit.querySelector('.dilux-countdown-progress');
113            if (!circle) return;
114
115            const radius = parseFloat(circle.getAttribute('r'));
116            const circumference = 2 * Math.PI * radius;
117
118            // Calculate progress (0 to 1)
119            // For countdown, we want progress to decrease as value decreases
120            const progress = currentValue / maxValue;
121
122            // Calculate stroke-dashoffset
123            // When progress = 1 (full), offset = 0 (full circle)
124            // When progress = 0 (empty), offset = circumference (empty circle)
125            const offset = circumference * (1 - progress);
126
127            circle.style.strokeDashoffset = offset;
128        }
129
130        /**
131         * Handle countdown expiry
132         * Supports: show_expiry_message, blur_on_expiry, expiry_animation (all independent)
133         */
134        handleExpiry() {
135            this.stop();
136
137            // Set all values to zero
138            const time = { days: 0, hours: 0, minutes: 0, seconds: 0 };
139            this.updateDisplay(time);
140
141            // Set all progress circles to empty
142            this.units.forEach((unit) => {
143                this.updateProgress(unit, 0, parseInt(unit.dataset.max));
144            });
145
146            // Clone the current countdown element
147            const countdownClone = this.element.cloneNode(true);
148
149            // Build wrapper with appropriate classes
150            const wrapper = document.createElement('div');
151            wrapper.className = 'dilux-countdown-expired-wrapper';
152            wrapper.dataset.countdownId = this.element.dataset.countdownId;
153            wrapper.dataset.animation = this.animation;
154            wrapper.dataset.celebrationType = this.celebrationType;
155            wrapper.dataset.blur = this.blurOnExpiry ? '1' : '0';
156
157            // Add animation class if specified
158            if (this.animation && this.animation !== 'none') {
159                wrapper.classList.add('dilux-animation-' + this.animation);
160            }
161
162            // Add blur class if specified
163            if (this.blurOnExpiry) {
164                wrapper.classList.add('dilux-expired-blur');
165            }
166
167            // Case 1: Show message (with or without blur and animation)
168            if (this.showMessage) {
169                // Create background container with countdown
170                const background = document.createElement('div');
171                background.className = 'dilux-countdown-expired-background';
172                background.appendChild(countdownClone);
173
174                // Create message overlay
175                const messageDiv = document.createElement('div');
176                messageDiv.className = 'dilux-countdown-expired-message';
177                messageDiv.textContent = this.message;
178
179                // Assemble the structure
180                wrapper.appendChild(background);
181                wrapper.appendChild(messageDiv);
182
183                // Add animation elements
184                this.addAnimationElements(wrapper, this.animation);
185
186                // Replace the countdown element with the wrapper
187                this.element.parentNode.replaceChild(wrapper, this.element);
188            }
189            // Case 2: No message, just countdown with optional blur and animation
190            else {
191                // Wrap countdown without message
192                wrapper.appendChild(countdownClone);
193
194                // Add animation elements
195                this.addAnimationElements(wrapper, this.animation);
196
197                // Replace the countdown element with the wrapper
198                this.element.parentNode.replaceChild(wrapper, this.element);
199            }
200        }
201
202        /**
203         * Add animation elements based on animation type
204         */
205        addAnimationElements(wrapper, animationType) {
206            if (!animationType || animationType === 'none') {
207                return;
208            }
209
210            switch (animationType) {
211                case 'celebration':
212                    this.createConfetti(wrapper);
213                    break;
214                case 'warning':
215                    this.createWarningIcons(wrapper);
216                    break;
217                case 'elegant':
218                    this.createElegantOverlay(wrapper);
219                    break;
220            }
221        }
222
223        /**
224         * Create confetti animation using canvas-confetti library
225         */
226        createConfetti(wrapper) {
227            // Check if canvas-confetti is loaded
228            if (typeof confetti === 'undefined') {
229                return;
230            }
231
232            const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24', '#6c5ce7', '#fd79a8'];
233
234            switch (this.celebrationType) {
235                case 'classic':
236                    // Classic explosion from center
237                    confetti({
238                        particleCount: 100,
239                        spread: 70,
240                        origin: { y: 0.6 },
241                        colors: colors
242                    });
243                    break;
244
245                case 'cannons':
246                    // Side cannons shooting confetti
247                    const duration = 2 * 1000;
248                    const end = Date.now() + duration;
249
250                    (function frame() {
251                        confetti({
252                            particleCount: 2,
253                            angle: 60,
254                            spread: 55,
255                            origin: { x: 0, y: 0.6 },
256                            colors: colors
257                        });
258                        confetti({
259                            particleCount: 2,
260                            angle: 120,
261                            spread: 55,
262                            origin: { x: 1, y: 0.6 },
263                            colors: colors
264                        });
265
266                        if (Date.now() < end) {
267                            requestAnimationFrame(frame);
268                        }
269                    }());
270                    break;
271
272                case 'rain':
273                    // Confetti falling from top for 3 seconds
274                    const rainDuration = 3 * 1000;
275                    const rainEnd = Date.now() + rainDuration;
276
277                    (function rainFrame() {
278                        confetti({
279                            particleCount: 3,
280                            angle: 90,
281                            spread: 45,
282                            origin: { x: Math.random(), y: 0 },
283                            colors: colors,
284                            gravity: 1.2,
285                            scalar: 0.8
286                        });
287
288                        if (Date.now() < rainEnd) {
289                            requestAnimationFrame(rainFrame);
290                        }
291                    }());
292                    break;
293
294                case 'fireworks':
295                    // Multiple coordinated bursts
296                    const fireworksDuration = 3 * 1000;
297                    const fireworksEnd = Date.now() + fireworksDuration;
298                    const fireworksInterval = 400;
299
300                    (function fireworksFrame() {
301                        confetti({
302                            particleCount: 50,
303                            spread: 100,
304                            origin: {
305                                x: Math.random() * 0.6 + 0.2,
306                                y: Math.random() * 0.4 + 0.3
307                            },
308                            colors: colors
309                        });
310
311                        if (Date.now() < fireworksEnd) {
312                            setTimeout(fireworksFrame, fireworksInterval);
313                        }
314                    }());
315                    break;
316
317                case 'stars':
318                    // Star-shaped confetti explosion
319                    const defaults = {
320                        spread: 360,
321                        ticks: 50,
322                        gravity: 0,
323                        decay: 0.94,
324                        startVelocity: 30,
325                        colors: colors
326                    };
327
328                    function shoot() {
329                        confetti({
330                            ...defaults,
331                            particleCount: 40,
332                            scalar: 1.2,
333                            shapes: ['star']
334                        });
335
336                        confetti({
337                            ...defaults,
338                            particleCount: 10,
339                            scalar: 0.75,
340                            shapes: ['circle']
341                        });
342                    }
343
344                    setTimeout(shoot, 0);
345                    setTimeout(shoot, 100);
346                    setTimeout(shoot, 200);
347                    break;
348
349                default:
350                    // Fallback to classic
351                    confetti({
352                        particleCount: 100,
353                        spread: 70,
354                        origin: { y: 0.6 },
355                        colors: colors
356                    });
357            }
358        }
359
360        /**
361         * Create warning icons animation
362         */
363        createWarningIcons(wrapper) {
364            const positions = [
365                { top: '10%', left: '10%' },
366                { top: '10%', right: '10%' },
367                { bottom: '10%', left: '10%' },
368                { bottom: '10%', right: '10%' }
369            ];
370
371            positions.forEach((pos, index) => {
372                const icon = document.createElement('div');
373                icon.className = 'dilux-warning-icon';
374                icon.textContent = '⚠';
375                Object.assign(icon.style, pos);
376                icon.style.animationDelay = (index * 0.2) + 's';
377                wrapper.appendChild(icon);
378            });
379        }
380
381        /**
382         * Create elegant overlay animation
383         */
384        createElegantOverlay(wrapper) {
385            const overlay = document.createElement('div');
386            overlay.className = 'dilux-elegant-overlay';
387            wrapper.appendChild(overlay);
388        }
389
390        /**
391         * Escape HTML to prevent XSS
392         */
393        escapeHtml(text) {
394            const div = document.createElement('div');
395            div.textContent = text;
396            return div.innerHTML;
397        }
398    }
399
400    // Initialize when DOM is ready
401    if (document.readyState === 'loading') {
402        document.addEventListener('DOMContentLoaded', initCountdowns);
403    } else {
404        initCountdowns();
405    }
406
407    // Expose for debugging
408    window.DiluxCountdown = DiluxCountdown;
409
410})();

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.