@whykusanagi/corrupted-theme 0.1.0 → 0.1.2
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 +162 -0
- package/README.md +209 -14
- package/docs/COMPONENTS_REFERENCE.md +295 -8
- package/examples/assets/celeste-avatar.png +0 -0
- package/examples/button.html +21 -9
- package/examples/card.html +21 -8
- package/examples/extensions-showcase.html +716 -0
- package/examples/form.html +21 -8
- package/examples/index.html +619 -396
- package/examples/layout.html +21 -7
- package/examples/nikke-team-builder.html +22 -8
- package/examples/showcase-complete.html +44 -13
- package/examples/showcase.html +20 -7
- package/package.json +12 -5
- package/src/css/components.css +101 -2
- package/src/css/extensions.css +933 -0
- package/src/css/theme.css +6 -176
- package/src/css/typography.css +5 -0
- package/src/css/variables.css +1 -1
- package/src/lib/countdown-widget.js +609 -0
- package/src/lib/gallery.js +481 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gallery.js — Gallery System with Lightbox and NSFW Support
|
|
3
|
+
*
|
|
4
|
+
* A complete gallery system with filtering, lightbox viewer, and NSFW content handling.
|
|
5
|
+
* Integrates with the Corrupted Theme design system.
|
|
6
|
+
*
|
|
7
|
+
* @module gallery
|
|
8
|
+
* @version 1.0.0
|
|
9
|
+
* @license MIT
|
|
10
|
+
*
|
|
11
|
+
* Features:
|
|
12
|
+
* - Responsive gallery grid
|
|
13
|
+
* - Category filtering with animated transitions
|
|
14
|
+
* - Fullscreen lightbox with keyboard navigation
|
|
15
|
+
* - NSFW content blur with click-to-reveal
|
|
16
|
+
* - Lazy loading support
|
|
17
|
+
* - Touch gesture support for mobile
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* ```html
|
|
21
|
+
* <div class="filter-bar">
|
|
22
|
+
* <button class="filter-btn active" data-filter="all">All</button>
|
|
23
|
+
* <button class="filter-btn" data-filter="photos">Photos</button>
|
|
24
|
+
* </div>
|
|
25
|
+
* <div class="gallery-container" id="gallery">
|
|
26
|
+
* <div class="gallery-item" data-tags="photos">
|
|
27
|
+
* <img src="image.jpg" alt="Description">
|
|
28
|
+
* <div class="gallery-caption">Caption</div>
|
|
29
|
+
* </div>
|
|
30
|
+
* </div>
|
|
31
|
+
*
|
|
32
|
+
* <script type="module">
|
|
33
|
+
* import { initGallery } from '@whykusanagi/corrupted-theme/src/lib/gallery.js';
|
|
34
|
+
* initGallery('#gallery');
|
|
35
|
+
* </script>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// CONFIGURATION
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
const DEFAULT_CONFIG = {
|
|
44
|
+
// Gallery selectors
|
|
45
|
+
gallerySelector: '.gallery-container',
|
|
46
|
+
itemSelector: '.gallery-item',
|
|
47
|
+
filterBarSelector: '.filter-bar',
|
|
48
|
+
filterBtnSelector: '.filter-btn',
|
|
49
|
+
|
|
50
|
+
// Lightbox
|
|
51
|
+
enableLightbox: true,
|
|
52
|
+
lightboxId: 'corrupted-lightbox',
|
|
53
|
+
|
|
54
|
+
// NSFW
|
|
55
|
+
enableNsfw: true,
|
|
56
|
+
nsfwSelector: '.nsfw-content',
|
|
57
|
+
nsfwWarning: '18+ Click to View',
|
|
58
|
+
|
|
59
|
+
// Animation
|
|
60
|
+
filterAnimation: true,
|
|
61
|
+
animationDuration: 300,
|
|
62
|
+
|
|
63
|
+
// Keyboard
|
|
64
|
+
enableKeyboard: true,
|
|
65
|
+
|
|
66
|
+
// Callbacks
|
|
67
|
+
onFilter: null,
|
|
68
|
+
onLightboxOpen: null,
|
|
69
|
+
onLightboxClose: null,
|
|
70
|
+
onNsfwReveal: null
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// STATE
|
|
75
|
+
// ============================================================================
|
|
76
|
+
|
|
77
|
+
let state = {
|
|
78
|
+
currentFilter: 'all',
|
|
79
|
+
lightboxOpen: false,
|
|
80
|
+
currentImageIndex: 0,
|
|
81
|
+
galleryImages: [],
|
|
82
|
+
config: { ...DEFAULT_CONFIG }
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// LIGHTBOX
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Creates and injects the lightbox HTML into the DOM
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
function createLightbox() {
|
|
94
|
+
if (document.getElementById(state.config.lightboxId)) return;
|
|
95
|
+
|
|
96
|
+
const lightbox = document.createElement('div');
|
|
97
|
+
lightbox.id = state.config.lightboxId;
|
|
98
|
+
lightbox.className = 'lightbox';
|
|
99
|
+
lightbox.innerHTML = `
|
|
100
|
+
<button class="lightbox-close" aria-label="Close lightbox">×</button>
|
|
101
|
+
<button class="lightbox-prev" aria-label="Previous image">
|
|
102
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
103
|
+
<path d="M15 18l-6-6 6-6"/>
|
|
104
|
+
</svg>
|
|
105
|
+
</button>
|
|
106
|
+
<img class="lightbox-image" src="" alt="">
|
|
107
|
+
<button class="lightbox-next" aria-label="Next image">
|
|
108
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
109
|
+
<path d="M9 18l6-6-6-6"/>
|
|
110
|
+
</svg>
|
|
111
|
+
</button>
|
|
112
|
+
<div class="lightbox-caption"></div>
|
|
113
|
+
<div class="lightbox-counter"></div>
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
document.body.appendChild(lightbox);
|
|
117
|
+
|
|
118
|
+
// Event listeners
|
|
119
|
+
lightbox.querySelector('.lightbox-close').addEventListener('click', closeLightbox);
|
|
120
|
+
lightbox.querySelector('.lightbox-prev').addEventListener('click', () => navigateLightbox(-1));
|
|
121
|
+
lightbox.querySelector('.lightbox-next').addEventListener('click', () => navigateLightbox(1));
|
|
122
|
+
lightbox.addEventListener('click', (e) => {
|
|
123
|
+
if (e.target === lightbox) closeLightbox();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Touch gestures
|
|
127
|
+
let touchStartX = 0;
|
|
128
|
+
lightbox.addEventListener('touchstart', (e) => {
|
|
129
|
+
touchStartX = e.touches[0].clientX;
|
|
130
|
+
}, { passive: true });
|
|
131
|
+
|
|
132
|
+
lightbox.addEventListener('touchend', (e) => {
|
|
133
|
+
const touchEndX = e.changedTouches[0].clientX;
|
|
134
|
+
const diff = touchStartX - touchEndX;
|
|
135
|
+
|
|
136
|
+
if (Math.abs(diff) > 50) {
|
|
137
|
+
navigateLightbox(diff > 0 ? 1 : -1);
|
|
138
|
+
}
|
|
139
|
+
}, { passive: true });
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Opens the lightbox with specified image
|
|
144
|
+
* @param {number} index - Index of image in gallery
|
|
145
|
+
*/
|
|
146
|
+
function openLightbox(index) {
|
|
147
|
+
const lightbox = document.getElementById(state.config.lightboxId);
|
|
148
|
+
if (!lightbox || !state.galleryImages[index]) return;
|
|
149
|
+
|
|
150
|
+
state.currentImageIndex = index;
|
|
151
|
+
state.lightboxOpen = true;
|
|
152
|
+
|
|
153
|
+
const imageData = state.galleryImages[index];
|
|
154
|
+
const img = lightbox.querySelector('.lightbox-image');
|
|
155
|
+
const caption = lightbox.querySelector('.lightbox-caption');
|
|
156
|
+
const counter = lightbox.querySelector('.lightbox-counter');
|
|
157
|
+
|
|
158
|
+
img.src = imageData.src;
|
|
159
|
+
img.alt = imageData.alt || '';
|
|
160
|
+
|
|
161
|
+
// Handle NSFW class
|
|
162
|
+
if (imageData.isNsfw) {
|
|
163
|
+
img.classList.add('nsfw-revealed');
|
|
164
|
+
} else {
|
|
165
|
+
img.classList.remove('nsfw-revealed');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
caption.textContent = imageData.caption || '';
|
|
169
|
+
caption.style.display = imageData.caption ? 'block' : 'none';
|
|
170
|
+
|
|
171
|
+
counter.textContent = `${index + 1} / ${state.galleryImages.length}`;
|
|
172
|
+
|
|
173
|
+
// Update navigation buttons
|
|
174
|
+
lightbox.querySelector('.lightbox-prev').disabled = index === 0;
|
|
175
|
+
lightbox.querySelector('.lightbox-next').disabled = index === state.galleryImages.length - 1;
|
|
176
|
+
|
|
177
|
+
lightbox.classList.add('active');
|
|
178
|
+
document.body.style.overflow = 'hidden';
|
|
179
|
+
|
|
180
|
+
if (state.config.onLightboxOpen) {
|
|
181
|
+
state.config.onLightboxOpen(imageData, index);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Closes the lightbox
|
|
187
|
+
*/
|
|
188
|
+
function closeLightbox() {
|
|
189
|
+
const lightbox = document.getElementById(state.config.lightboxId);
|
|
190
|
+
if (!lightbox) return;
|
|
191
|
+
|
|
192
|
+
lightbox.classList.remove('active');
|
|
193
|
+
document.body.style.overflow = '';
|
|
194
|
+
state.lightboxOpen = false;
|
|
195
|
+
|
|
196
|
+
if (state.config.onLightboxClose) {
|
|
197
|
+
state.config.onLightboxClose();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Navigates to next/previous image in lightbox
|
|
203
|
+
* @param {number} direction - 1 for next, -1 for previous
|
|
204
|
+
*/
|
|
205
|
+
function navigateLightbox(direction) {
|
|
206
|
+
const newIndex = state.currentImageIndex + direction;
|
|
207
|
+
|
|
208
|
+
if (newIndex >= 0 && newIndex < state.galleryImages.length) {
|
|
209
|
+
openLightbox(newIndex);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ============================================================================
|
|
214
|
+
// FILTERING
|
|
215
|
+
// ============================================================================
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Filters gallery items by category
|
|
219
|
+
* @param {string} filter - Filter value (tag name or 'all')
|
|
220
|
+
*/
|
|
221
|
+
function filterGallery(filter) {
|
|
222
|
+
const galleries = document.querySelectorAll(state.config.gallerySelector);
|
|
223
|
+
|
|
224
|
+
galleries.forEach(gallery => {
|
|
225
|
+
const items = gallery.querySelectorAll(state.config.itemSelector);
|
|
226
|
+
|
|
227
|
+
items.forEach(item => {
|
|
228
|
+
const tags = (item.dataset.tags || '').split(',').map(t => t.trim());
|
|
229
|
+
const shouldShow = filter === 'all' || tags.includes(filter);
|
|
230
|
+
|
|
231
|
+
if (state.config.filterAnimation) {
|
|
232
|
+
item.style.transition = `opacity ${state.config.animationDuration}ms ease, transform ${state.config.animationDuration}ms ease`;
|
|
233
|
+
|
|
234
|
+
if (shouldShow) {
|
|
235
|
+
item.style.opacity = '0';
|
|
236
|
+
item.style.transform = 'scale(0.9)';
|
|
237
|
+
item.style.display = '';
|
|
238
|
+
|
|
239
|
+
requestAnimationFrame(() => {
|
|
240
|
+
item.style.opacity = '1';
|
|
241
|
+
item.style.transform = 'scale(1)';
|
|
242
|
+
});
|
|
243
|
+
} else {
|
|
244
|
+
item.style.opacity = '0';
|
|
245
|
+
item.style.transform = 'scale(0.9)';
|
|
246
|
+
|
|
247
|
+
setTimeout(() => {
|
|
248
|
+
item.style.display = 'none';
|
|
249
|
+
}, state.config.animationDuration);
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
item.style.display = shouldShow ? '' : 'none';
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
state.currentFilter = filter;
|
|
258
|
+
updateGalleryImages();
|
|
259
|
+
|
|
260
|
+
// Update filter button active state
|
|
261
|
+
document.querySelectorAll(state.config.filterBtnSelector).forEach(btn => {
|
|
262
|
+
btn.classList.toggle('active', btn.dataset.filter === filter);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (state.config.onFilter) {
|
|
266
|
+
state.config.onFilter(filter);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ============================================================================
|
|
271
|
+
// NSFW HANDLING
|
|
272
|
+
// ============================================================================
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Reveals NSFW content on click
|
|
276
|
+
* @param {HTMLElement} element - NSFW content element
|
|
277
|
+
*/
|
|
278
|
+
function revealNsfwContent(element) {
|
|
279
|
+
if (element.classList.contains('revealed')) return;
|
|
280
|
+
|
|
281
|
+
element.classList.add('revealed');
|
|
282
|
+
|
|
283
|
+
if (state.config.onNsfwReveal) {
|
|
284
|
+
state.config.onNsfwReveal(element);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Initializes NSFW content handling
|
|
290
|
+
* @private
|
|
291
|
+
*/
|
|
292
|
+
function initNsfwContent() {
|
|
293
|
+
document.querySelectorAll(state.config.nsfwSelector).forEach(item => {
|
|
294
|
+
if (!item.dataset.warning) {
|
|
295
|
+
item.dataset.warning = state.config.nsfwWarning;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
item.addEventListener('click', (e) => {
|
|
299
|
+
if (!item.classList.contains('revealed')) {
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
e.stopPropagation();
|
|
302
|
+
revealNsfwContent(item);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ============================================================================
|
|
309
|
+
// KEYBOARD NAVIGATION
|
|
310
|
+
// ============================================================================
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Handles keyboard events for lightbox navigation
|
|
314
|
+
* @private
|
|
315
|
+
* @param {KeyboardEvent} e - Keyboard event
|
|
316
|
+
*/
|
|
317
|
+
function handleKeyboard(e) {
|
|
318
|
+
if (!state.lightboxOpen) return;
|
|
319
|
+
|
|
320
|
+
switch (e.key) {
|
|
321
|
+
case 'Escape':
|
|
322
|
+
closeLightbox();
|
|
323
|
+
break;
|
|
324
|
+
case 'ArrowLeft':
|
|
325
|
+
navigateLightbox(-1);
|
|
326
|
+
break;
|
|
327
|
+
case 'ArrowRight':
|
|
328
|
+
navigateLightbox(1);
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// UTILITY
|
|
335
|
+
// ============================================================================
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Updates the list of gallery images for lightbox navigation
|
|
339
|
+
* @private
|
|
340
|
+
*/
|
|
341
|
+
function updateGalleryImages() {
|
|
342
|
+
state.galleryImages = [];
|
|
343
|
+
|
|
344
|
+
document.querySelectorAll(`${state.config.gallerySelector} ${state.config.itemSelector}`).forEach((item, index) => {
|
|
345
|
+
if (item.style.display === 'none') return;
|
|
346
|
+
|
|
347
|
+
const img = item.querySelector('img');
|
|
348
|
+
const caption = item.querySelector('.gallery-caption');
|
|
349
|
+
|
|
350
|
+
if (img) {
|
|
351
|
+
state.galleryImages.push({
|
|
352
|
+
src: img.dataset.fullSrc || img.src,
|
|
353
|
+
alt: img.alt,
|
|
354
|
+
caption: caption ? caption.textContent : '',
|
|
355
|
+
isNsfw: item.classList.contains('nsfw-content'),
|
|
356
|
+
element: item,
|
|
357
|
+
originalIndex: index
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ============================================================================
|
|
364
|
+
// PUBLIC API
|
|
365
|
+
// ============================================================================
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Initializes the gallery system
|
|
369
|
+
* @param {string|HTMLElement} selector - Gallery container selector or element
|
|
370
|
+
* @param {Object} options - Configuration options
|
|
371
|
+
* @returns {Object} Gallery API
|
|
372
|
+
*/
|
|
373
|
+
export function initGallery(selector = '.gallery-container', options = {}) {
|
|
374
|
+
// Merge config
|
|
375
|
+
state.config = { ...DEFAULT_CONFIG, ...options };
|
|
376
|
+
|
|
377
|
+
if (typeof selector === 'string') {
|
|
378
|
+
state.config.gallerySelector = selector;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Create lightbox
|
|
382
|
+
if (state.config.enableLightbox) {
|
|
383
|
+
createLightbox();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Initialize filter buttons
|
|
387
|
+
document.querySelectorAll(state.config.filterBtnSelector).forEach(btn => {
|
|
388
|
+
btn.addEventListener('click', () => {
|
|
389
|
+
filterGallery(btn.dataset.filter || 'all');
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Initialize gallery items
|
|
394
|
+
document.querySelectorAll(`${state.config.gallerySelector} ${state.config.itemSelector}`).forEach((item, index) => {
|
|
395
|
+
const img = item.querySelector('img');
|
|
396
|
+
|
|
397
|
+
if (img && state.config.enableLightbox) {
|
|
398
|
+
img.style.cursor = 'pointer';
|
|
399
|
+
img.addEventListener('click', (e) => {
|
|
400
|
+
// Don't open lightbox for unrevealed NSFW content
|
|
401
|
+
if (item.classList.contains('nsfw-content') && !item.classList.contains('revealed')) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
e.stopPropagation();
|
|
406
|
+
const visibleIndex = state.galleryImages.findIndex(i => i.element === item);
|
|
407
|
+
if (visibleIndex !== -1) {
|
|
408
|
+
openLightbox(visibleIndex);
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Initialize NSFW handling
|
|
415
|
+
if (state.config.enableNsfw) {
|
|
416
|
+
initNsfwContent();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Initialize keyboard navigation
|
|
420
|
+
if (state.config.enableKeyboard) {
|
|
421
|
+
document.addEventListener('keydown', handleKeyboard);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Build initial image list
|
|
425
|
+
updateGalleryImages();
|
|
426
|
+
|
|
427
|
+
// Return public API
|
|
428
|
+
return {
|
|
429
|
+
filter: filterGallery,
|
|
430
|
+
openLightbox,
|
|
431
|
+
closeLightbox,
|
|
432
|
+
revealNsfw: revealNsfwContent,
|
|
433
|
+
refresh: updateGalleryImages,
|
|
434
|
+
getImages: () => [...state.galleryImages],
|
|
435
|
+
getCurrentFilter: () => state.currentFilter
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Destroys the gallery instance and removes event listeners
|
|
441
|
+
*/
|
|
442
|
+
export function destroyGallery() {
|
|
443
|
+
const lightbox = document.getElementById(state.config.lightboxId);
|
|
444
|
+
if (lightbox) {
|
|
445
|
+
lightbox.remove();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
document.removeEventListener('keydown', handleKeyboard);
|
|
449
|
+
|
|
450
|
+
state = {
|
|
451
|
+
currentFilter: 'all',
|
|
452
|
+
lightboxOpen: false,
|
|
453
|
+
currentImageIndex: 0,
|
|
454
|
+
galleryImages: [],
|
|
455
|
+
config: { ...DEFAULT_CONFIG }
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Auto-initialize if DOM is ready and elements exist
|
|
460
|
+
if (typeof document !== 'undefined') {
|
|
461
|
+
if (document.readyState === 'loading') {
|
|
462
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
463
|
+
if (document.querySelector('.gallery-container')) {
|
|
464
|
+
initGallery();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
} else {
|
|
468
|
+
if (document.querySelector('.gallery-container')) {
|
|
469
|
+
initGallery();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Export for global usage
|
|
475
|
+
if (typeof window !== 'undefined') {
|
|
476
|
+
window.CorruptedGallery = {
|
|
477
|
+
init: initGallery,
|
|
478
|
+
destroy: destroyGallery
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|