@whykusanagi/corrupted-theme 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +253 -0
  2. package/README.md +97 -7
  3. package/docs/CAPABILITIES.md +209 -0
  4. package/docs/CHARACTER_LEVEL_CORRUPTION.md +264 -0
  5. package/docs/COMPONENTS_REFERENCE.md +295 -8
  6. package/docs/CORRUPTION_PHRASES.md +529 -0
  7. package/docs/FUTURE_WORK.md +189 -0
  8. package/docs/IMPLEMENTATION_VALIDATION.md +401 -0
  9. package/docs/LLM_PROVIDERS.md +345 -0
  10. package/docs/PERSONALITY.md +128 -0
  11. package/docs/ROADMAP.md +266 -0
  12. package/docs/ROUTING.md +324 -0
  13. package/docs/STYLE_GUIDE.md +605 -0
  14. package/docs/brand/BRAND_OVERVIEW.md +413 -0
  15. package/docs/brand/COLOR_SYSTEM.md +583 -0
  16. package/docs/brand/DESIGN_TOKENS.md +1009 -0
  17. package/docs/brand/TRANSLATION_FAILURE_AESTHETIC.md +525 -0
  18. package/docs/brand/TYPOGRAPHY.md +624 -0
  19. package/docs/components/ANIMATION_GUIDELINES.md +901 -0
  20. package/docs/components/COMPONENT_LIBRARY.md +1061 -0
  21. package/docs/components/GLASSMORPHISM.md +602 -0
  22. package/docs/components/INTERACTIVE_STATES.md +766 -0
  23. package/docs/governance/CONTRIBUTION_GUIDELINES.md +593 -0
  24. package/docs/governance/DESIGN_SYSTEM_GOVERNANCE.md +451 -0
  25. package/docs/governance/VERSION_MANAGEMENT.md +447 -0
  26. package/docs/governance/VERSION_REFERENCES.md +229 -0
  27. package/docs/platforms/CLI_IMPLEMENTATION.md +1025 -0
  28. package/docs/platforms/COMPONENT_MAPPING.md +579 -0
  29. package/docs/platforms/NPM_PACKAGE.md +854 -0
  30. package/docs/platforms/WEB_IMPLEMENTATION.md +1221 -0
  31. package/docs/standards/ACCESSIBILITY.md +715 -0
  32. package/docs/standards/ANTI_PATTERNS.md +554 -0
  33. package/docs/standards/SPACING_SYSTEM.md +549 -0
  34. package/examples/assets/celeste-avatar.png +0 -0
  35. package/examples/button.html +22 -10
  36. package/examples/card.html +22 -9
  37. package/examples/extensions-showcase.html +716 -0
  38. package/examples/form.html +22 -9
  39. package/examples/index.html +619 -396
  40. package/examples/layout.html +22 -8
  41. package/examples/nikke-team-builder.html +23 -9
  42. package/examples/showcase-complete.html +884 -28
  43. package/examples/showcase.html +21 -8
  44. package/package.json +14 -5
  45. package/src/css/components.css +676 -0
  46. package/src/css/extensions.css +933 -0
  47. package/src/css/theme.css +6 -74
  48. package/src/css/typography.css +5 -0
  49. package/src/lib/character-corruption.js +563 -0
  50. package/src/lib/components.js +283 -0
  51. package/src/lib/countdown-widget.js +609 -0
  52. package/src/lib/gallery.js +481 -0
@@ -0,0 +1,609 @@
1
+ /**
2
+ * countdown-widget.js — Event Countdown Widget with Configurable Shapes
3
+ *
4
+ * A sophisticated countdown widget with JSON configuration support,
5
+ * multiple shape containers, and animated popup messages.
6
+ *
7
+ * @module countdown-widget
8
+ * @version 1.0.0
9
+ * @license MIT
10
+ *
11
+ * Features:
12
+ * - JSON-based configuration
13
+ * - Multiple shape containers (diamond, circle, heart, star, hexagon, octagon)
14
+ * - Character image with optional overlay
15
+ * - Animated popup messages
16
+ * - Real-time countdown timer
17
+ * - Completion state handling
18
+ * - Responsive design
19
+ *
20
+ * Usage:
21
+ * ```html
22
+ * <div id="countdown-widget"></div>
23
+ *
24
+ * <script type="module">
25
+ * import { initCountdown } from '@whykusanagi/corrupted-theme/src/lib/countdown-widget.js';
26
+ *
27
+ * // Using URL parameter: ?event=kirara loads /data/countdown/kirara.json
28
+ * initCountdown();
29
+ *
30
+ * // Or with inline config:
31
+ * initCountdown({
32
+ * config: {
33
+ * title: 'Launch Countdown',
34
+ * eventDate: '2025-04-01T00:00:00-07:00',
35
+ * character: { image: 'character.png' }
36
+ * }
37
+ * });
38
+ * </script>
39
+ * ```
40
+ */
41
+
42
+ // ============================================================================
43
+ // CONFIGURATION SCHEMA
44
+ // ============================================================================
45
+
46
+ /**
47
+ * @typedef {Object} CountdownConfig
48
+ * @property {string} title - Title displayed above countdown
49
+ * @property {string} eventDate - ISO 8601 date string for target time
50
+ * @property {string} [basicMessage] - Short description
51
+ * @property {string} [detailedMessage] - HTML message with links
52
+ * @property {string} [completedMessage] - Message shown when countdown ends
53
+ * @property {string} [style='compact'] - Widget style variant
54
+ * @property {CharacterConfig} [character] - Character image configuration
55
+ * @property {PopupConfig} [popup] - Popup message configuration
56
+ * @property {ColorConfig} [colors] - Color overrides
57
+ */
58
+
59
+ /**
60
+ * @typedef {Object} CharacterConfig
61
+ * @property {string} image - Character image URL or path
62
+ * @property {number} [rotation=0] - Image rotation in degrees
63
+ * @property {string} [objectPosition] - CSS object-position value
64
+ * @property {BackgroundConfig} [background] - Shape background config
65
+ * @property {OverlayConfig} [overlay] - Overlay image config
66
+ */
67
+
68
+ /**
69
+ * @typedef {Object} BackgroundConfig
70
+ * @property {string} [type='diamond'] - Shape type
71
+ * @property {string} [color] - CSS background value
72
+ * @property {string} [borderColor] - Hex color for border
73
+ * @property {boolean} [pattern=false] - Use pattern overlay
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} OverlayConfig
78
+ * @property {string} [image] - Overlay image URL
79
+ * @property {string} [position='behind'] - 'behind' or 'front'
80
+ * @property {string} [animation] - Animation type ('float' or null)
81
+ * @property {number} [rotation] - Overlay rotation
82
+ */
83
+
84
+ /**
85
+ * @typedef {Object} PopupConfig
86
+ * @property {string} message - HTML content for popup
87
+ * @property {number} [frequency=10000] - Ms between popups
88
+ * @property {number} [duration=5000] - Ms popup stays visible
89
+ * @property {Object} [colors] - Popup color overrides
90
+ */
91
+
92
+ // ============================================================================
93
+ // DEFAULT CONFIGURATION
94
+ // ============================================================================
95
+
96
+ const DEFAULT_CONFIG = {
97
+ title: 'Event Countdown',
98
+ eventDate: null,
99
+ basicMessage: '',
100
+ detailedMessage: '',
101
+ completedMessage: 'Event is Live!',
102
+ style: 'compact',
103
+ character: {
104
+ image: null,
105
+ rotation: 0,
106
+ objectPosition: 'center',
107
+ background: {
108
+ type: 'diamond',
109
+ color: null,
110
+ borderColor: null,
111
+ pattern: false
112
+ },
113
+ overlay: null
114
+ },
115
+ popup: null,
116
+ colors: {
117
+ primary: null,
118
+ accent: null,
119
+ text: null
120
+ }
121
+ };
122
+
123
+ const WIDGET_OPTIONS = {
124
+ containerId: 'countdown-widget',
125
+ configPath: 'data/countdown',
126
+ assetBasePath: ''
127
+ };
128
+
129
+ // ============================================================================
130
+ // STATE
131
+ // ============================================================================
132
+
133
+ let state = {
134
+ config: null,
135
+ countdownInterval: null,
136
+ popupInterval: null,
137
+ isCompleted: false
138
+ };
139
+
140
+ // ============================================================================
141
+ // ASSET HANDLING
142
+ // ============================================================================
143
+
144
+ /**
145
+ * Converts relative asset paths to full URLs
146
+ * @private
147
+ * @param {string} path - Asset path
148
+ * @returns {string} Full URL
149
+ */
150
+ function resolveAssetPath(path) {
151
+ if (!path) return '';
152
+
153
+ // Already a full URL
154
+ if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('data:')) {
155
+ return path;
156
+ }
157
+
158
+ // Check for global AssetConfig (for environment-aware paths)
159
+ if (typeof window !== 'undefined' && window.AssetConfig?.convertUrl) {
160
+ return window.AssetConfig.convertUrl(path);
161
+ }
162
+
163
+ // Use base path if configured
164
+ if (WIDGET_OPTIONS.assetBasePath) {
165
+ return `${WIDGET_OPTIONS.assetBasePath}/${path}`;
166
+ }
167
+
168
+ return path;
169
+ }
170
+
171
+ // ============================================================================
172
+ // CONFIG LOADING
173
+ // ============================================================================
174
+
175
+ /**
176
+ * Loads countdown configuration from JSON file
177
+ * @private
178
+ * @param {string} eventName - Event name (filename without .json)
179
+ * @returns {Promise<CountdownConfig>}
180
+ */
181
+ async function loadConfigFromJson(eventName) {
182
+ const url = `${WIDGET_OPTIONS.configPath}/${eventName}.json`;
183
+
184
+ try {
185
+ const response = await fetch(url, { cache: 'no-store' });
186
+
187
+ if (!response.ok) {
188
+ throw new Error(`Failed to load config: ${response.status}`);
189
+ }
190
+
191
+ return await response.json();
192
+ } catch (error) {
193
+ console.error(`[CountdownWidget] Error loading config for "${eventName}":`, error);
194
+ throw error;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Gets URL parameter value
200
+ * @private
201
+ * @param {string} name - Parameter name
202
+ * @returns {string|null}
203
+ */
204
+ function getUrlParam(name) {
205
+ if (typeof window === 'undefined') return null;
206
+ const params = new URLSearchParams(window.location.search);
207
+ return params.get(name);
208
+ }
209
+
210
+ /**
211
+ * Merges config with URL parameter overrides
212
+ * @private
213
+ * @param {CountdownConfig} config - Base config
214
+ * @returns {CountdownConfig}
215
+ */
216
+ function applyUrlOverrides(config) {
217
+ const overrides = {};
218
+
219
+ const dateOverride = getUrlParam('date');
220
+ if (dateOverride) overrides.eventDate = dateOverride;
221
+
222
+ const titleOverride = getUrlParam('title');
223
+ if (titleOverride) overrides.title = decodeURIComponent(titleOverride);
224
+
225
+ return { ...config, ...overrides };
226
+ }
227
+
228
+ // ============================================================================
229
+ // RENDERING
230
+ // ============================================================================
231
+
232
+ /**
233
+ * Renders the countdown widget HTML
234
+ * @private
235
+ * @param {CountdownConfig} config
236
+ * @returns {Object} DOM element references
237
+ */
238
+ function renderWidget(config) {
239
+ const container = document.getElementById(WIDGET_OPTIONS.containerId);
240
+ if (!container) {
241
+ throw new Error(`[CountdownWidget] Container #${WIDGET_OPTIONS.containerId} not found`);
242
+ }
243
+
244
+ container.innerHTML = '';
245
+
246
+ const wrapper = document.createElement('div');
247
+ wrapper.className = 'countdown-container';
248
+
249
+ // Shape container
250
+ const shapeType = config.character?.background?.type || 'diamond';
251
+ const shapeContainer = document.createElement('div');
252
+ shapeContainer.className = `shape-container ${shapeType}`;
253
+
254
+ // Apply custom colors
255
+ if (config.character?.background?.color) {
256
+ shapeContainer.style.background = config.character.background.color;
257
+ }
258
+ if (config.character?.background?.borderColor) {
259
+ shapeContainer.style.borderColor = config.character.background.borderColor;
260
+ }
261
+
262
+ const shapeContent = document.createElement('div');
263
+ shapeContent.className = 'shape-content';
264
+
265
+ // Overlay (behind character)
266
+ if (config.character?.overlay?.image && config.character.overlay.position !== 'front') {
267
+ const overlayWrapper = createOverlay(config.character.overlay);
268
+ shapeContent.appendChild(overlayWrapper);
269
+ }
270
+
271
+ // Character image
272
+ if (config.character?.image) {
273
+ const characterImg = document.createElement('img');
274
+ characterImg.className = 'countdown-character';
275
+ characterImg.src = resolveAssetPath(config.character.image);
276
+ characterImg.alt = config.title || 'Event Character';
277
+
278
+ if (config.character.rotation) {
279
+ characterImg.style.transform = `translate(-50%, -50%) scale(0.9) rotate(${config.character.rotation}deg)`;
280
+ }
281
+ if (config.character.objectPosition) {
282
+ characterImg.style.objectPosition = config.character.objectPosition;
283
+ }
284
+
285
+ shapeContent.appendChild(characterImg);
286
+ }
287
+
288
+ // Overlay (front of character)
289
+ if (config.character?.overlay?.image && config.character.overlay.position === 'front') {
290
+ const overlayWrapper = createOverlay(config.character.overlay);
291
+ overlayWrapper.classList.add('front');
292
+ shapeContent.appendChild(overlayWrapper);
293
+ }
294
+
295
+ shapeContainer.appendChild(shapeContent);
296
+ wrapper.appendChild(shapeContainer);
297
+
298
+ // Countdown box
299
+ const countdownBox = document.createElement('div');
300
+ countdownBox.className = 'countdown-box';
301
+ countdownBox.innerHTML = `
302
+ <div class="countdown-title">${escapeHtml(config.title)}</div>
303
+ <div class="countdown-timer">
304
+ <span class="unit days">--</span><span class="separator">D</span>
305
+ <span class="unit hours">--</span><span class="separator">H</span>
306
+ <span class="unit minutes">--</span><span class="separator">M</span>
307
+ <span class="unit seconds">--</span><span class="separator">S</span>
308
+ </div>
309
+ `;
310
+ wrapper.appendChild(countdownBox);
311
+
312
+ // Popup (optional)
313
+ let popup = null;
314
+ if (config.popup?.message) {
315
+ popup = document.createElement('div');
316
+ popup.className = 'countdown-popup';
317
+ popup.innerHTML = config.popup.message;
318
+
319
+ if (config.popup.colors) {
320
+ if (config.popup.colors.bg) popup.style.background = config.popup.colors.bg;
321
+ if (config.popup.colors.border) popup.style.borderColor = config.popup.colors.border;
322
+ if (config.popup.colors.text) popup.style.color = config.popup.colors.text;
323
+ }
324
+
325
+ wrapper.appendChild(popup);
326
+ }
327
+
328
+ container.appendChild(wrapper);
329
+
330
+ return { countdownBox, popup };
331
+ }
332
+
333
+ /**
334
+ * Creates overlay element
335
+ * @private
336
+ * @param {OverlayConfig} overlay
337
+ * @returns {HTMLElement}
338
+ */
339
+ function createOverlay(overlay) {
340
+ const wrapper = document.createElement('div');
341
+ wrapper.className = 'countdown-overlay-wrapper';
342
+
343
+ const img = document.createElement('img');
344
+ img.className = 'countdown-overlay-img';
345
+ img.src = resolveAssetPath(overlay.image);
346
+ img.alt = '';
347
+
348
+ if (overlay.animation === 'float') {
349
+ img.classList.add('animate-float');
350
+ }
351
+
352
+ if (overlay.rotation) {
353
+ img.style.transform = `rotate(${overlay.rotation}deg)`;
354
+ }
355
+
356
+ wrapper.appendChild(img);
357
+ return wrapper;
358
+ }
359
+
360
+ /**
361
+ * Escapes HTML to prevent XSS
362
+ * @private
363
+ * @param {string} text
364
+ * @returns {string}
365
+ */
366
+ function escapeHtml(text) {
367
+ const div = document.createElement('div');
368
+ div.textContent = text;
369
+ return div.innerHTML;
370
+ }
371
+
372
+ // ============================================================================
373
+ // COUNTDOWN LOGIC
374
+ // ============================================================================
375
+
376
+ /**
377
+ * Updates the countdown timer display
378
+ * @private
379
+ * @param {Date} targetDate
380
+ * @param {HTMLElement} countdownBox
381
+ * @param {string} completedMessage
382
+ */
383
+ function updateCountdown(targetDate, countdownBox, completedMessage) {
384
+ const now = new Date().getTime();
385
+ const distance = targetDate.getTime() - now;
386
+
387
+ if (distance <= 0) {
388
+ // Countdown completed
389
+ if (!state.isCompleted) {
390
+ state.isCompleted = true;
391
+ countdownBox.classList.add('completed');
392
+ countdownBox.querySelector('.countdown-timer').innerHTML = `
393
+ <span class="completed-message">${escapeHtml(completedMessage)}</span>
394
+ `;
395
+
396
+ // Stop intervals
397
+ if (state.countdownInterval) {
398
+ clearInterval(state.countdownInterval);
399
+ state.countdownInterval = null;
400
+ }
401
+ }
402
+ return;
403
+ }
404
+
405
+ const days = Math.floor(distance / (1000 * 60 * 60 * 24));
406
+ const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
407
+ const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
408
+ const seconds = Math.floor((distance % (1000 * 60)) / 1000);
409
+
410
+ const daysEl = countdownBox.querySelector('.days');
411
+ const hoursEl = countdownBox.querySelector('.hours');
412
+ const minutesEl = countdownBox.querySelector('.minutes');
413
+ const secondsEl = countdownBox.querySelector('.seconds');
414
+
415
+ if (daysEl) daysEl.textContent = String(days).padStart(2, '0');
416
+ if (hoursEl) hoursEl.textContent = String(hours).padStart(2, '0');
417
+ if (minutesEl) minutesEl.textContent = String(minutes).padStart(2, '0');
418
+ if (secondsEl) secondsEl.textContent = String(seconds).padStart(2, '0');
419
+ }
420
+
421
+ /**
422
+ * Starts the countdown timer
423
+ * @private
424
+ * @param {string} eventDate
425
+ * @param {HTMLElement} countdownBox
426
+ * @param {string} completedMessage
427
+ */
428
+ function startCountdown(eventDate, countdownBox, completedMessage) {
429
+ const targetDate = new Date(eventDate);
430
+
431
+ if (isNaN(targetDate.getTime())) {
432
+ console.error('[CountdownWidget] Invalid event date:', eventDate);
433
+ countdownBox.querySelector('.countdown-timer').textContent = 'Invalid Date';
434
+ return;
435
+ }
436
+
437
+ // Initial update
438
+ updateCountdown(targetDate, countdownBox, completedMessage);
439
+
440
+ // Update every second
441
+ state.countdownInterval = setInterval(() => {
442
+ updateCountdown(targetDate, countdownBox, completedMessage);
443
+ }, 1000);
444
+ }
445
+
446
+ // ============================================================================
447
+ // POPUP LOGIC
448
+ // ============================================================================
449
+
450
+ /**
451
+ * Starts the popup cycle
452
+ * @private
453
+ * @param {PopupConfig} popupConfig
454
+ * @param {HTMLElement} popupElement
455
+ */
456
+ function startPopup(popupConfig, popupElement) {
457
+ if (!popupConfig?.message || !popupElement) return;
458
+
459
+ const frequency = popupConfig.frequency || 10000;
460
+ const duration = popupConfig.duration || 5000;
461
+
462
+ // Show popup initially after a delay
463
+ setTimeout(() => {
464
+ showPopup(popupElement, duration);
465
+ }, 2000);
466
+
467
+ // Start cycle
468
+ state.popupInterval = setInterval(() => {
469
+ showPopup(popupElement, duration);
470
+ }, frequency);
471
+ }
472
+
473
+ /**
474
+ * Shows popup for specified duration
475
+ * @private
476
+ * @param {HTMLElement} popup
477
+ * @param {number} duration
478
+ */
479
+ function showPopup(popup, duration) {
480
+ popup.classList.add('active');
481
+
482
+ setTimeout(() => {
483
+ popup.classList.remove('active');
484
+ }, duration);
485
+ }
486
+
487
+ // ============================================================================
488
+ // PUBLIC API
489
+ // ============================================================================
490
+
491
+ /**
492
+ * Initializes the countdown widget
493
+ * @param {Object} options - Initialization options
494
+ * @param {string} [options.event] - Event name to load config from JSON
495
+ * @param {CountdownConfig} [options.config] - Inline configuration
496
+ * @param {string} [options.containerId] - Container element ID
497
+ * @param {string} [options.configPath] - Path to config JSON files
498
+ * @param {string} [options.assetBasePath] - Base path for assets
499
+ * @returns {Promise<Object>} Widget API
500
+ */
501
+ export async function initCountdown(options = {}) {
502
+ // Apply options
503
+ if (options.containerId) WIDGET_OPTIONS.containerId = options.containerId;
504
+ if (options.configPath) WIDGET_OPTIONS.configPath = options.configPath;
505
+ if (options.assetBasePath) WIDGET_OPTIONS.assetBasePath = options.assetBasePath;
506
+
507
+ // Clean up previous instance
508
+ destroyCountdown();
509
+
510
+ let config;
511
+
512
+ try {
513
+ if (options.config) {
514
+ // Use inline config
515
+ config = { ...DEFAULT_CONFIG, ...options.config };
516
+ } else {
517
+ // Load from JSON
518
+ const eventName = options.event || getUrlParam('event');
519
+
520
+ if (!eventName) {
521
+ throw new Error('No event specified. Use ?event=<name> or provide config.');
522
+ }
523
+
524
+ const loadedConfig = await loadConfigFromJson(eventName);
525
+ config = { ...DEFAULT_CONFIG, ...loadedConfig };
526
+ }
527
+
528
+ // Apply URL overrides
529
+ config = applyUrlOverrides(config);
530
+ state.config = config;
531
+
532
+ // Validate required fields
533
+ if (!config.eventDate) {
534
+ throw new Error('eventDate is required in configuration');
535
+ }
536
+
537
+ // Render widget
538
+ const { countdownBox, popup } = renderWidget(config);
539
+
540
+ // Start countdown
541
+ startCountdown(config.eventDate, countdownBox, config.completedMessage);
542
+
543
+ // Start popup cycle
544
+ if (popup && config.popup) {
545
+ startPopup(config.popup, popup);
546
+ }
547
+
548
+ // Return API
549
+ return {
550
+ getConfig: () => ({ ...state.config }),
551
+ isCompleted: () => state.isCompleted,
552
+ destroy: destroyCountdown
553
+ };
554
+
555
+ } catch (error) {
556
+ console.error('[CountdownWidget] Initialization error:', error);
557
+
558
+ const container = document.getElementById(WIDGET_OPTIONS.containerId);
559
+ if (container) {
560
+ container.innerHTML = `
561
+ <div class="countdown-error" style="
562
+ padding: 2rem;
563
+ text-align: center;
564
+ color: var(--text-secondary, #888);
565
+ background: var(--glass, rgba(20, 12, 40, 0.7));
566
+ border-radius: var(--radius-lg, 12px);
567
+ border: 1px solid var(--border, #3a2555);
568
+ ">
569
+ <p style="margin-bottom: 0.5rem; color: var(--accent, #d94f90);">⚠️ Countdown Error</p>
570
+ <p style="font-size: 0.9rem;">${escapeHtml(error.message)}</p>
571
+ </div>
572
+ `;
573
+ }
574
+
575
+ throw error;
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Destroys the countdown widget and cleans up resources
581
+ */
582
+ export function destroyCountdown() {
583
+ if (state.countdownInterval) {
584
+ clearInterval(state.countdownInterval);
585
+ state.countdownInterval = null;
586
+ }
587
+
588
+ if (state.popupInterval) {
589
+ clearInterval(state.popupInterval);
590
+ state.popupInterval = null;
591
+ }
592
+
593
+ state.config = null;
594
+ state.isCompleted = false;
595
+
596
+ const container = document.getElementById(WIDGET_OPTIONS.containerId);
597
+ if (container) {
598
+ container.innerHTML = '';
599
+ }
600
+ }
601
+
602
+ // Export for global usage
603
+ if (typeof window !== 'undefined') {
604
+ window.CorruptedCountdown = {
605
+ init: initCountdown,
606
+ destroy: destroyCountdown
607
+ };
608
+ }
609
+