@whykusanagi/corrupted-theme 0.1.6 → 0.1.8
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.
- package/CHANGELOG.md +38 -0
- package/README.md +207 -42
- package/docs/COMPONENTS_REFERENCE.md +142 -35
- package/docs/governance/VERSION_MANAGEMENT.md +2 -2
- package/docs/governance/VERSION_REFERENCES.md +56 -199
- package/docs/platforms/NPM_PACKAGE.md +9 -7
- package/examples/advanced/glsl-vortex.html +298 -0
- package/examples/advanced/particles-bg.html +264 -0
- package/examples/basic/multi-gallery.html +155 -0
- package/examples/button.html +5 -2
- package/examples/card.html +5 -2
- package/examples/extensions-showcase.html +41 -2
- package/examples/form.html +5 -2
- package/examples/index.html +34 -6
- package/examples/interactive-components.html +223 -0
- package/examples/layout.html +5 -2
- package/examples/nikke-team-builder.html +6 -3
- package/examples/showcase-complete.html +14 -13
- package/examples/showcase.html +6 -3
- package/package.json +8 -5
- package/src/core/corrupted-text.js +25 -5
- package/src/core/event-tracker.js +46 -0
- package/src/core/timer-registry.js +94 -0
- package/src/core/typing-animation.js +36 -17
- package/src/css/components.css +108 -0
- package/src/lib/carousel.js +308 -0
- package/src/lib/celeste-widget.js +178 -47
- package/src/lib/character-corruption.js +33 -8
- package/src/lib/components.js +357 -25
- package/src/lib/corrupted-particles.js +309 -0
- package/src/lib/corrupted-text.js +21 -5
- package/src/lib/corrupted-vortex.js +329 -0
- package/src/lib/corruption-loading.js +40 -10
- package/src/lib/countdown-widget.js +25 -6
- package/src/lib/gallery.js +420 -354
package/src/lib/components.js
CHANGED
|
@@ -4,13 +4,25 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Provides helper functions for:
|
|
6
6
|
* - Accordion/Collapse
|
|
7
|
+
* - Modal
|
|
8
|
+
* - Dropdown
|
|
9
|
+
* - Tabs
|
|
7
10
|
* - Toast Notifications
|
|
8
|
-
* - Auto-initialization
|
|
11
|
+
* - Auto-initialization via data-ct-* attributes
|
|
9
12
|
*
|
|
10
13
|
* @module components
|
|
11
|
-
* @version
|
|
14
|
+
* @version 2.0.0
|
|
12
15
|
*/
|
|
13
16
|
|
|
17
|
+
import { EventTracker } from '../core/event-tracker.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Shared tracker for listeners created during auto-initialization.
|
|
21
|
+
* Cleaned up by destroyComponents().
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
const _initTracker = new EventTracker();
|
|
25
|
+
|
|
14
26
|
// ========== ACCORDION / COLLAPSE ==========
|
|
15
27
|
|
|
16
28
|
/**
|
|
@@ -28,7 +40,7 @@ export function initAccordions() {
|
|
|
28
40
|
const header = item.querySelector('.accordion-header');
|
|
29
41
|
if (!header) return;
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
_initTracker.add(header, 'click', () => {
|
|
32
44
|
const wasActive = item.classList.contains('active');
|
|
33
45
|
|
|
34
46
|
// Close all items in this accordion (unless it's already active)
|
|
@@ -137,32 +149,38 @@ class ToastManager {
|
|
|
137
149
|
const toast = document.createElement('div');
|
|
138
150
|
toast.className = `toast ${type}`;
|
|
139
151
|
|
|
140
|
-
// Create toast content
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
// Create toast content using safe DOM methods
|
|
153
|
+
if (title) {
|
|
154
|
+
const header = document.createElement('div');
|
|
155
|
+
header.className = 'toast-header';
|
|
156
|
+
const titleSpan = document.createElement('span');
|
|
157
|
+
titleSpan.textContent = title;
|
|
158
|
+
header.appendChild(titleSpan);
|
|
159
|
+
toast.appendChild(header);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const body = document.createElement('div');
|
|
163
|
+
body.className = 'toast-body';
|
|
164
|
+
body.textContent = message;
|
|
165
|
+
toast.appendChild(body);
|
|
166
|
+
|
|
167
|
+
const closeBtn = document.createElement('button');
|
|
168
|
+
closeBtn.className = 'toast-close';
|
|
169
|
+
closeBtn.setAttribute('aria-label', 'Close');
|
|
170
|
+
closeBtn.textContent = '\u00d7';
|
|
155
171
|
closeBtn.addEventListener('click', () => {
|
|
156
172
|
this.dismiss(toast, onClose);
|
|
157
173
|
});
|
|
174
|
+
toast.appendChild(closeBtn);
|
|
158
175
|
|
|
159
176
|
// Add to container
|
|
160
177
|
container.appendChild(toast);
|
|
161
178
|
this.toasts.push(toast);
|
|
162
179
|
|
|
163
|
-
// Auto-dismiss
|
|
180
|
+
// Auto-dismiss (tracked for cleanup on manual dismiss)
|
|
164
181
|
if (duration > 0) {
|
|
165
|
-
setTimeout(() => {
|
|
182
|
+
toast._autoDismissId = setTimeout(() => {
|
|
183
|
+
toast._autoDismissId = null;
|
|
166
184
|
this.dismiss(toast, onClose);
|
|
167
185
|
}, duration);
|
|
168
186
|
}
|
|
@@ -178,6 +196,12 @@ class ToastManager {
|
|
|
178
196
|
dismiss(toast, onClose = null) {
|
|
179
197
|
if (!toast || !document.contains(toast)) return;
|
|
180
198
|
|
|
199
|
+
// Clear auto-dismiss timer if dismissing early
|
|
200
|
+
if (toast._autoDismissId) {
|
|
201
|
+
clearTimeout(toast._autoDismissId);
|
|
202
|
+
toast._autoDismissId = null;
|
|
203
|
+
}
|
|
204
|
+
|
|
181
205
|
toast.classList.add('hiding');
|
|
182
206
|
|
|
183
207
|
setTimeout(() => {
|
|
@@ -201,7 +225,19 @@ class ToastManager {
|
|
|
201
225
|
* Dismiss all toasts
|
|
202
226
|
*/
|
|
203
227
|
dismissAll() {
|
|
204
|
-
this.toasts.forEach(toast => this.dismiss(toast));
|
|
228
|
+
[...this.toasts].forEach(toast => this.dismiss(toast));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Tear down the toast system: dismiss all toasts and remove container.
|
|
233
|
+
*/
|
|
234
|
+
destroy() {
|
|
235
|
+
this.dismissAll();
|
|
236
|
+
if (this.container && this.container.parentNode) {
|
|
237
|
+
this.container.parentNode.removeChild(this.container);
|
|
238
|
+
}
|
|
239
|
+
this.container = null;
|
|
240
|
+
this.toasts = [];
|
|
205
241
|
}
|
|
206
242
|
|
|
207
243
|
// Convenience methods for different toast types
|
|
@@ -247,16 +283,305 @@ export const toast = {
|
|
|
247
283
|
dismissAll: () => toastManager.dismissAll()
|
|
248
284
|
};
|
|
249
285
|
|
|
286
|
+
// ========== MODAL ==========
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Modal manager with lifecycle management
|
|
290
|
+
*
|
|
291
|
+
* Usage:
|
|
292
|
+
* ```html
|
|
293
|
+
* <button data-ct-toggle="modal" data-ct-target="#my-modal">Open</button>
|
|
294
|
+
* <div class="modal-overlay" id="my-modal">
|
|
295
|
+
* <div class="modal">
|
|
296
|
+
* <div class="modal-header">
|
|
297
|
+
* <h3 class="modal-title">Title</h3>
|
|
298
|
+
* <button class="modal-close">×</button>
|
|
299
|
+
* </div>
|
|
300
|
+
* <div class="modal-body">Content</div>
|
|
301
|
+
* </div>
|
|
302
|
+
* </div>
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
class ModalManager {
|
|
306
|
+
constructor() {
|
|
307
|
+
this._events = new EventTracker();
|
|
308
|
+
this._initialized = new Set();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Initialize a modal overlay
|
|
313
|
+
* @param {string|HTMLElement} selector - Modal overlay selector or element
|
|
314
|
+
*/
|
|
315
|
+
init(selector) {
|
|
316
|
+
const overlay = typeof selector === 'string'
|
|
317
|
+
? document.querySelector(selector) : selector;
|
|
318
|
+
if (!overlay || this._initialized.has(overlay)) return;
|
|
319
|
+
|
|
320
|
+
const closeBtn = overlay.querySelector('.modal-close');
|
|
321
|
+
if (closeBtn) {
|
|
322
|
+
this._events.add(closeBtn, 'click', () => this.close(overlay));
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Close on overlay click (outside modal content)
|
|
326
|
+
this._events.add(overlay, 'click', (e) => {
|
|
327
|
+
if (e.target === overlay) this.close(overlay);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
this._initialized.add(overlay);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Open a modal
|
|
335
|
+
* @param {string|HTMLElement} selector - Modal overlay selector or element
|
|
336
|
+
*/
|
|
337
|
+
open(selector) {
|
|
338
|
+
const overlay = typeof selector === 'string'
|
|
339
|
+
? document.querySelector(selector) : selector;
|
|
340
|
+
if (!overlay) return;
|
|
341
|
+
|
|
342
|
+
overlay.classList.add('active');
|
|
343
|
+
document.body.style.overflow = 'hidden';
|
|
344
|
+
|
|
345
|
+
overlay.dispatchEvent(new CustomEvent('modal:open', { bubbles: true }));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Close a modal
|
|
350
|
+
* @param {string|HTMLElement} selector - Modal overlay selector or element
|
|
351
|
+
*/
|
|
352
|
+
close(selector) {
|
|
353
|
+
const overlay = typeof selector === 'string'
|
|
354
|
+
? document.querySelector(selector) : selector;
|
|
355
|
+
if (!overlay) return;
|
|
356
|
+
|
|
357
|
+
overlay.classList.remove('active');
|
|
358
|
+
document.body.style.overflow = '';
|
|
359
|
+
|
|
360
|
+
overlay.dispatchEvent(new CustomEvent('modal:close', { bubbles: true }));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Tear down all tracked listeners and state
|
|
365
|
+
*/
|
|
366
|
+
destroy() {
|
|
367
|
+
this._events.removeAll();
|
|
368
|
+
this._initialized.clear();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const modalManager = new ModalManager();
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Open a modal by selector
|
|
376
|
+
* @param {string|HTMLElement} selector
|
|
377
|
+
*/
|
|
378
|
+
export function openModal(selector) {
|
|
379
|
+
modalManager.open(selector);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Close a modal by selector
|
|
384
|
+
* @param {string|HTMLElement} selector
|
|
385
|
+
*/
|
|
386
|
+
export function closeModal(selector) {
|
|
387
|
+
modalManager.close(selector);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ========== DROPDOWN ==========
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Dropdown manager with click-outside-to-close
|
|
394
|
+
*
|
|
395
|
+
* Usage:
|
|
396
|
+
* ```html
|
|
397
|
+
* <div class="dropdown">
|
|
398
|
+
* <button class="dropdown-toggle" data-ct-toggle="dropdown">Menu</button>
|
|
399
|
+
* <div class="dropdown-menu">
|
|
400
|
+
* <a class="dropdown-item" href="#">Item 1</a>
|
|
401
|
+
* </div>
|
|
402
|
+
* </div>
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
class DropdownManager {
|
|
406
|
+
constructor() {
|
|
407
|
+
this._events = new EventTracker();
|
|
408
|
+
this._initialized = false;
|
|
409
|
+
this._outsideClickBound = false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Initialize dropdown toggle behavior
|
|
414
|
+
* @param {HTMLElement} toggle - The dropdown-toggle element
|
|
415
|
+
*/
|
|
416
|
+
init(toggle) {
|
|
417
|
+
const menu = toggle.parentElement?.querySelector('.dropdown-menu');
|
|
418
|
+
if (!menu) return;
|
|
419
|
+
|
|
420
|
+
this._events.add(toggle, 'click', (e) => {
|
|
421
|
+
e.stopPropagation();
|
|
422
|
+
const isOpen = menu.classList.contains('active');
|
|
423
|
+
|
|
424
|
+
// Close all other dropdowns first
|
|
425
|
+
this.closeAll();
|
|
426
|
+
|
|
427
|
+
if (!isOpen) {
|
|
428
|
+
menu.classList.add('active');
|
|
429
|
+
toggle.classList.add('active');
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// Setup global click-outside handler once
|
|
434
|
+
if (!this._outsideClickBound) {
|
|
435
|
+
this._events.add(document, 'click', () => this.closeAll());
|
|
436
|
+
this._outsideClickBound = true;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Close all open dropdowns
|
|
442
|
+
*/
|
|
443
|
+
closeAll() {
|
|
444
|
+
document.querySelectorAll('.dropdown-menu.active').forEach(menu => {
|
|
445
|
+
menu.classList.remove('active');
|
|
446
|
+
});
|
|
447
|
+
document.querySelectorAll('.dropdown-toggle.active').forEach(toggle => {
|
|
448
|
+
toggle.classList.remove('active');
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Tear down all tracked listeners
|
|
454
|
+
*/
|
|
455
|
+
destroy() {
|
|
456
|
+
this.closeAll();
|
|
457
|
+
this._events.removeAll();
|
|
458
|
+
this._outsideClickBound = false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const dropdownManager = new DropdownManager();
|
|
463
|
+
|
|
464
|
+
// ========== TABS ==========
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Tab manager
|
|
468
|
+
*
|
|
469
|
+
* Usage:
|
|
470
|
+
* ```html
|
|
471
|
+
* <div class="tabs">
|
|
472
|
+
* <button class="tab active" data-ct-target="#panel-1">Tab 1</button>
|
|
473
|
+
* <button class="tab" data-ct-target="#panel-2">Tab 2</button>
|
|
474
|
+
* </div>
|
|
475
|
+
* <div class="tab-content active" id="panel-1">Panel 1</div>
|
|
476
|
+
* <div class="tab-content" id="panel-2">Panel 2</div>
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
class TabManager {
|
|
480
|
+
constructor() {
|
|
481
|
+
this._events = new EventTracker();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Initialize a tab container
|
|
486
|
+
* @param {HTMLElement} tabsContainer - The .tabs element
|
|
487
|
+
*/
|
|
488
|
+
init(tabsContainer) {
|
|
489
|
+
const tabs = tabsContainer.querySelectorAll('.tab[data-ct-target]');
|
|
490
|
+
|
|
491
|
+
tabs.forEach(tab => {
|
|
492
|
+
this._events.add(tab, 'click', () => {
|
|
493
|
+
// Deactivate all sibling tabs
|
|
494
|
+
tabsContainer.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
495
|
+
|
|
496
|
+
// Hide all associated panels
|
|
497
|
+
tabs.forEach(t => {
|
|
498
|
+
const panel = document.querySelector(t.dataset.ctTarget);
|
|
499
|
+
if (panel) panel.classList.remove('active');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Activate clicked tab and its panel
|
|
503
|
+
tab.classList.add('active');
|
|
504
|
+
const panel = document.querySelector(tab.dataset.ctTarget);
|
|
505
|
+
if (panel) panel.classList.add('active');
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Tear down all tracked listeners
|
|
512
|
+
*/
|
|
513
|
+
destroy() {
|
|
514
|
+
this._events.removeAll();
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const tabManager = new TabManager();
|
|
519
|
+
|
|
250
520
|
// ========== AUTO-INITIALIZATION ==========
|
|
251
521
|
|
|
252
522
|
/**
|
|
253
|
-
* Initialize all components on page load
|
|
523
|
+
* Initialize all components on page load.
|
|
524
|
+
* Scans for data-ct-* attributes and wires up behavior.
|
|
525
|
+
* All listeners are tracked via _initTracker or manager EventTrackers.
|
|
254
526
|
*/
|
|
255
527
|
function initComponents() {
|
|
256
|
-
//
|
|
528
|
+
// Accordions
|
|
257
529
|
if (document.querySelector('.accordion')) {
|
|
258
530
|
initAccordions();
|
|
259
531
|
}
|
|
532
|
+
|
|
533
|
+
// Modals — init overlays and wire triggers
|
|
534
|
+
document.querySelectorAll('.modal-overlay').forEach(overlay => {
|
|
535
|
+
modalManager.init(overlay);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
document.querySelectorAll('[data-ct-toggle="modal"]').forEach(trigger => {
|
|
539
|
+
const targetSel = trigger.dataset.ctTarget;
|
|
540
|
+
if (!targetSel) return;
|
|
541
|
+
_initTracker.add(trigger, 'click', () => modalManager.open(targetSel));
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// Escape key closes active modals
|
|
545
|
+
if (document.querySelector('.modal-overlay')) {
|
|
546
|
+
_initTracker.add(document, 'keydown', (e) => {
|
|
547
|
+
if (e.key === 'Escape') {
|
|
548
|
+
document.querySelectorAll('.modal-overlay.active').forEach(overlay => {
|
|
549
|
+
modalManager.close(overlay);
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Dropdowns
|
|
556
|
+
document.querySelectorAll('[data-ct-toggle="dropdown"]').forEach(toggle => {
|
|
557
|
+
dropdownManager.init(toggle);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
// Tabs
|
|
561
|
+
document.querySelectorAll('.tabs').forEach(tabsContainer => {
|
|
562
|
+
if (tabsContainer.querySelector('.tab[data-ct-target]')) {
|
|
563
|
+
tabManager.init(tabsContainer);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
// Collapse triggers
|
|
568
|
+
document.querySelectorAll('[data-ct-toggle="collapse"]').forEach(trigger => {
|
|
569
|
+
const targetSel = trigger.dataset.ctTarget;
|
|
570
|
+
if (!targetSel) return;
|
|
571
|
+
_initTracker.add(trigger, 'click', () => toggleCollapse(targetSel));
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Destroy all component managers and clean up listeners.
|
|
577
|
+
* Removes all tracked listeners from auto-initialization and managers.
|
|
578
|
+
*/
|
|
579
|
+
export function destroyComponents() {
|
|
580
|
+
_initTracker.removeAll();
|
|
581
|
+
modalManager.destroy();
|
|
582
|
+
dropdownManager.destroy();
|
|
583
|
+
tabManager.destroy();
|
|
584
|
+
toastManager.destroy();
|
|
260
585
|
}
|
|
261
586
|
|
|
262
587
|
// Auto-initialize on DOM ready
|
|
@@ -271,13 +596,20 @@ if (typeof window !== 'undefined') {
|
|
|
271
596
|
// ========== EXPORTS ==========
|
|
272
597
|
|
|
273
598
|
export default {
|
|
274
|
-
// Accordion
|
|
599
|
+
// Accordion / Collapse
|
|
275
600
|
initAccordions,
|
|
276
601
|
toggleCollapse,
|
|
277
602
|
showCollapse,
|
|
278
603
|
hideCollapse,
|
|
279
604
|
|
|
605
|
+
// Modal
|
|
606
|
+
openModal,
|
|
607
|
+
closeModal,
|
|
608
|
+
|
|
280
609
|
// Toast
|
|
281
610
|
showToast,
|
|
282
|
-
toast
|
|
611
|
+
toast,
|
|
612
|
+
|
|
613
|
+
// Lifecycle
|
|
614
|
+
destroyComponents
|
|
283
615
|
};
|