@thisiscrowd/crowdbox 1.0.0
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/README.md +348 -0
- package/dist/crowdbox.css +1 -0
- package/dist/crowdbox.css.map +1 -0
- package/dist/crowdbox.esm.js +1557 -0
- package/dist/crowdbox.esm.js.map +1 -0
- package/dist/crowdbox.umd.js +8 -0
- package/dist/crowdbox.umd.js.map +1 -0
- package/package.json +59 -0
- package/src/adapters/iframe.js +30 -0
- package/src/adapters/image.js +91 -0
- package/src/adapters/video.js +44 -0
- package/src/adapters/vimeo.js +52 -0
- package/src/adapters/youtube.js +54 -0
- package/src/browser.js +7 -0
- package/src/core/Crowdbox.js +739 -0
- package/src/core/EventEmitter.js +43 -0
- package/src/core/LightboxGallery.js +2 -0
- package/src/core/Registry.js +26 -0
- package/src/core/utils.js +115 -0
- package/src/index.js +61 -0
- package/src/plugins/download/download.js +50 -0
- package/src/plugins/fullscreen/fullscreen.js +65 -0
- package/src/plugins/share/share.js +63 -0
- package/src/plugins/thumbs/thumbs.js +108 -0
- package/src/plugins/zoom/zoom.js +51 -0
- package/src/react/GalleryGrid.jsx +75 -0
- package/src/react/Lightbox.jsx +36 -0
- package/src/react/useLightbox.js +34 -0
- package/src/styles/_animations.scss +62 -0
- package/src/styles/_mosaic.scss +46 -0
- package/src/styles/_responsive.scss +34 -0
- package/src/styles/_thumbnails.scss +66 -0
- package/src/styles/_toolbar.scss +150 -0
- package/src/styles/_variables.scss +23 -0
- package/src/styles/main.scss +164 -0
- package/types/index.d.ts +239 -0
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# Crowdbox
|
|
2
|
+
|
|
3
|
+
A modern, lightweight, extensible JavaScript Crowdbox gallery plugin.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Image & Video lightbox** — images, MP4/WebM, YouTube, Vimeo, iframes
|
|
8
|
+
- **Mixed media galleries** — combine any media types in one gallery
|
|
9
|
+
- **Thumbnail strip** — lazy-loaded, scrollable, active-highlighted
|
|
10
|
+
- **Zoom & pan** — click, double-click, pinch (mobile), keyboard shortcuts
|
|
11
|
+
- **Touch & drag** — swipe to navigate, drag to pan when zoomed
|
|
12
|
+
- **Fullscreen** — native browser fullscreen API
|
|
13
|
+
- **Keyboard navigation** — arrows, Escape, `+`/`-` for zoom, `f` for fullscreen
|
|
14
|
+
- **Autoplay** — configurable interval
|
|
15
|
+
- **Framework adapters** — React component, hooks, Next.js SSR-safe dynamic import
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## Quick Start — Vanilla JS
|
|
21
|
+
|
|
22
|
+
### HTML data-attribute approach (auto-init)
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<link rel="stylesheet" href="crowdbox.css" />
|
|
26
|
+
|
|
27
|
+
<!-- Gallery group -->
|
|
28
|
+
<div data-lgx-gallery="my-photos">
|
|
29
|
+
<a href="full.jpg" data-lgx data-lgx-gallery="my-photos"
|
|
30
|
+
data-lgx-thumb="thumb.jpg" data-lgx-caption="Caption text">
|
|
31
|
+
<img src="thumb.jpg" alt="Photo" />
|
|
32
|
+
</a>
|
|
33
|
+
<a href="full2.jpg" data-lgx data-lgx-gallery="my-photos"
|
|
34
|
+
data-lgx-thumb="thumb2.jpg">
|
|
35
|
+
<img src="thumb2.jpg" alt="Photo 2" />
|
|
36
|
+
</a>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<script src="crowdbox.umd.js"></script>
|
|
40
|
+
<script>
|
|
41
|
+
const gallery = new Crowdbox({
|
|
42
|
+
selector: '[data-lgx]',
|
|
43
|
+
thumbnails: true,
|
|
44
|
+
captions: true,
|
|
45
|
+
zoom: true,
|
|
46
|
+
loop: true,
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### JavaScript API
|
|
52
|
+
|
|
53
|
+
```js
|
|
54
|
+
import Crowdbox from 'crowdbox';
|
|
55
|
+
import 'crowdbox/styles';
|
|
56
|
+
|
|
57
|
+
const gallery = new Crowdbox({ loop: true, thumbnails: true });
|
|
58
|
+
|
|
59
|
+
// Open with an array of items
|
|
60
|
+
gallery.open([
|
|
61
|
+
{ src: 'photo.jpg', type: 'image', caption: 'A photo' },
|
|
62
|
+
{ src: 'https://www.youtube.com/watch?v=VIDEO_ID', type: 'youtube' },
|
|
63
|
+
{ src: 'clip.mp4', type: 'video', poster: 'poster.jpg' },
|
|
64
|
+
{ src: 'https://vimeo.com/VIDEO_ID', type: 'vimeo' },
|
|
65
|
+
], 0 /* startIndex */);
|
|
66
|
+
|
|
67
|
+
// Navigate
|
|
68
|
+
gallery.next();
|
|
69
|
+
gallery.prev();
|
|
70
|
+
gallery.goTo(2);
|
|
71
|
+
|
|
72
|
+
// Zoom
|
|
73
|
+
gallery.zoomIn();
|
|
74
|
+
gallery.zoomOut();
|
|
75
|
+
gallery.resetZoom();
|
|
76
|
+
|
|
77
|
+
// Events
|
|
78
|
+
gallery
|
|
79
|
+
.on('open', ({ index, item }) => console.log('opened', index))
|
|
80
|
+
.on('slide', ({ index, item }) => console.log('slide', index))
|
|
81
|
+
.on('close', () => console.log('closed'));
|
|
82
|
+
|
|
83
|
+
// Cleanup
|
|
84
|
+
gallery.destroy();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Options
|
|
90
|
+
|
|
91
|
+
| Option | Type | Default | Description |
|
|
92
|
+
|---|---|---|---|
|
|
93
|
+
| `selector` | `string` | `'[data-lgx]'` | CSS selector for trigger elements |
|
|
94
|
+
| `loop` | `boolean` | `true` | Loop at gallery ends |
|
|
95
|
+
| `keyboard` | `boolean` | `true` | Arrow keys, Escape, +/- zoom |
|
|
96
|
+
| `touch` | `boolean` | `true` | Swipe to navigate |
|
|
97
|
+
| `drag` | `boolean` | `true` | Mouse drag |
|
|
98
|
+
| `zoom` | `boolean` | `true` | Zoom controls & pinch zoom |
|
|
99
|
+
| `thumbnails` | `boolean` | `true` | Thumbnail strip |
|
|
100
|
+
| `captions` | `boolean` | `true` | Caption display |
|
|
101
|
+
| `counter` | `boolean` | `true` | "1 of 6" counter |
|
|
102
|
+
| `fullscreen` | `boolean` | `true` | Fullscreen button |
|
|
103
|
+
| `download` | `boolean` | `false` | Download button |
|
|
104
|
+
| `share` | `boolean` | `false` | Share button (Web Share API → clipboard fallback) |
|
|
105
|
+
| `closeOnBackdrop` | `boolean` | `true` | Click backdrop to close |
|
|
106
|
+
| `closeOnEscape` | `boolean` | `true` | Escape key closes |
|
|
107
|
+
| `animationDuration` | `number` | `300` | Open/close animation (ms) |
|
|
108
|
+
| `slideAnimationDuration` | `number` | `250` | Slide transition (ms) |
|
|
109
|
+
| `zoomStep` | `number` | `0.5` | Zoom per click |
|
|
110
|
+
| `zoomMax` | `number` | `4` | Maximum zoom |
|
|
111
|
+
| `lazyLoad` | `boolean` | `true` | Lazy-load adjacent images |
|
|
112
|
+
| `preload` | `number` | `1` | Slides to preload ahead/behind |
|
|
113
|
+
| `autoplay` | `boolean` | `false` | Autoplay slideshow |
|
|
114
|
+
| `showAutoplay` | `boolean` | `true` | Show play button |
|
|
115
|
+
| `autoplayInterval` | `number` | `4000` | Autoplay interval (ms) |
|
|
116
|
+
| `i18n` | `object` | see source | Localisation strings |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## GalleryItem properties
|
|
121
|
+
|
|
122
|
+
| Property | Type | Description |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `src` | `string` | **Required.** Media URL |
|
|
125
|
+
| `type` | `string` | `image` \| `video` \| `youtube` \| `vimeo` \| `iframe` (auto-detected) |
|
|
126
|
+
| `thumb` | `string` | Thumbnail URL |
|
|
127
|
+
| `caption` | `string` | Caption HTML |
|
|
128
|
+
| `alt` | `string` | Image alt text |
|
|
129
|
+
| `poster` | `string` | Video poster image |
|
|
130
|
+
| `autoplay` | `boolean` | Autoplay media on open |
|
|
131
|
+
| `loop` | `boolean` | Loop video |
|
|
132
|
+
| `muted` | `boolean` | Start muted |
|
|
133
|
+
| `download` | `string` | Override download URL |
|
|
134
|
+
| `shareUrl` | `string` | URL used by share button |
|
|
135
|
+
| `start` | `number` | YouTube start time (seconds) |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Data attributes
|
|
140
|
+
|
|
141
|
+
| Attribute | Description |
|
|
142
|
+
|---|---|
|
|
143
|
+
| `data-lgx` | Marks an element as a gallery trigger |
|
|
144
|
+
| `data-lgx-gallery="group"` | Groups items into one gallery |
|
|
145
|
+
| `data-lgx-src="url"` | Override the href/src URL |
|
|
146
|
+
| `data-lgx-type="youtube"` | Explicit media type |
|
|
147
|
+
| `data-lgx-thumb="url"` | Thumbnail URL |
|
|
148
|
+
| `data-lgx-caption="text"` | Caption text |
|
|
149
|
+
| `data-lgx-poster="url"` | Video poster |
|
|
150
|
+
| `data-lgx-autoplay` | Presence = autoplay |
|
|
151
|
+
| `data-lgx-init='{"loop":true}'` | Auto-init a container with JSON options |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## React
|
|
156
|
+
|
|
157
|
+
### GalleryGrid (simplest)
|
|
158
|
+
|
|
159
|
+
```jsx
|
|
160
|
+
import { GalleryGrid } from 'crowdbox/react';
|
|
161
|
+
import 'crowdbox/styles';
|
|
162
|
+
|
|
163
|
+
const items = [
|
|
164
|
+
{ src: 'photo1.jpg', type: 'image', thumb: 'thumb1.jpg', caption: 'Photo 1' },
|
|
165
|
+
{ src: 'https://www.youtube.com/watch?v=ID', type: 'youtube' },
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
export default function Page() {
|
|
169
|
+
return <GalleryGrid items={items} columns={3} />;
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Controlled Lightbox component
|
|
174
|
+
|
|
175
|
+
```jsx
|
|
176
|
+
import { Lightbox } from 'crowdbox/react';
|
|
177
|
+
|
|
178
|
+
const [open, setOpen] = useState(false);
|
|
179
|
+
|
|
180
|
+
<Lightbox
|
|
181
|
+
items={items}
|
|
182
|
+
open={open}
|
|
183
|
+
startIndex={0}
|
|
184
|
+
onClose={() => setOpen(false)}
|
|
185
|
+
thumbnails captions zoom fullscreen
|
|
186
|
+
/>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### useLightbox hook
|
|
190
|
+
|
|
191
|
+
```jsx
|
|
192
|
+
import { useLightbox } from 'crowdbox/react';
|
|
193
|
+
|
|
194
|
+
function MyGallery({ items }) {
|
|
195
|
+
const { open } = useLightbox({ thumbnails: true, captions: true });
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<button onClick={() => open(items, 0)}>
|
|
199
|
+
Open Gallery
|
|
200
|
+
</button>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Next.js (App Router)
|
|
208
|
+
|
|
209
|
+
```jsx
|
|
210
|
+
'use client';
|
|
211
|
+
import { useEffect, useRef } from 'react';
|
|
212
|
+
|
|
213
|
+
export default function Gallery({ items }) {
|
|
214
|
+
const galleryRef = useRef(null);
|
|
215
|
+
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
let g;
|
|
218
|
+
// Dynamic import keeps Crowdbox out of the server bundle
|
|
219
|
+
import('crowdbox').then(({ Crowdbox }) => {
|
|
220
|
+
import('crowdbox/styles');
|
|
221
|
+
g = new Crowdbox({ selector: null, thumbnails: true });
|
|
222
|
+
galleryRef.current = g;
|
|
223
|
+
});
|
|
224
|
+
return () => g?.destroy();
|
|
225
|
+
}, []);
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8 }}>
|
|
229
|
+
{items.map((item, i) => (
|
|
230
|
+
<img
|
|
231
|
+
key={i}
|
|
232
|
+
src={item.thumb}
|
|
233
|
+
alt={item.caption}
|
|
234
|
+
onClick={() => galleryRef.current?.open(items, i)}
|
|
235
|
+
style={{ cursor: 'pointer', width: '100%', aspectRatio: '1', objectFit: 'cover' }}
|
|
236
|
+
/>
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
```js
|
|
245
|
+
import { Registry } from 'crowdbox';
|
|
246
|
+
|
|
247
|
+
const WatermarkPlugin = {
|
|
248
|
+
pluginName: 'watermark',
|
|
249
|
+
|
|
250
|
+
init(gallery) { this.gallery = gallery; },
|
|
251
|
+
|
|
252
|
+
afterSlide(index, item) {
|
|
253
|
+
const stage = this.gallery._dom.stage;
|
|
254
|
+
stage.querySelectorAll('.my-watermark').forEach(el => el.remove());
|
|
255
|
+
const wm = document.createElement('div');
|
|
256
|
+
wm.className = 'my-watermark';
|
|
257
|
+
wm.textContent = '© My Site';
|
|
258
|
+
wm.style.cssText = 'position:absolute;bottom:8px;right:8px;color:rgba(255,255,255,.4);font-size:12px;pointer-events:none;';
|
|
259
|
+
stage.appendChild(wm);
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
destroy() {},
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
Registry.registerPlugin('watermark', WatermarkPlugin);
|
|
266
|
+
|
|
267
|
+
// Then instantiate with watermark: true
|
|
268
|
+
const gallery = new Crowdbox({ watermark: true });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Custom media adapter
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
import { Registry } from 'crowdbox';
|
|
275
|
+
|
|
276
|
+
Registry.registerAdapter('soundcloud', {
|
|
277
|
+
type: 'soundcloud',
|
|
278
|
+
canHandle: (item) => /soundcloud\.com/.test(item.src),
|
|
279
|
+
render(item) {
|
|
280
|
+
const wrapper = document.createElement('div');
|
|
281
|
+
wrapper.className = 'lgx-content lgx-content--iframe';
|
|
282
|
+
const iframe = document.createElement('iframe');
|
|
283
|
+
iframe.src = `https://w.soundcloud.com/player/?url=${encodeURIComponent(item.src)}`;
|
|
284
|
+
iframe.className = 'lgx-iframe';
|
|
285
|
+
wrapper.appendChild(iframe);
|
|
286
|
+
return wrapper;
|
|
287
|
+
},
|
|
288
|
+
getThumbnail: (item) => item.thumb ?? null,
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## CSS Theming
|
|
295
|
+
|
|
296
|
+
Override CSS custom properties on `:root` or a parent element:
|
|
297
|
+
|
|
298
|
+
```css
|
|
299
|
+
:root {
|
|
300
|
+
--lgx-backdrop: rgba(10, 10, 30, 0.96); /* dark blue overlay */
|
|
301
|
+
--lgx-accent: #ff6b35; /* orange accent */
|
|
302
|
+
--lgx-btn-size: 48px;
|
|
303
|
+
--lgx-thumb-size: 80px;
|
|
304
|
+
--lgx-duration: 400ms;
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Keyboard shortcuts
|
|
311
|
+
|
|
312
|
+
| Key | Action |
|
|
313
|
+
|---|---|
|
|
314
|
+
| `←` / `→` | Previous / Next |
|
|
315
|
+
| `Escape` | Close |
|
|
316
|
+
| `f` | Toggle fullscreen |
|
|
317
|
+
| `+` / `=` | Zoom in |
|
|
318
|
+
| `-` | Zoom out |
|
|
319
|
+
| `0` | Reset zoom |
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Events
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
gallery
|
|
327
|
+
.on('open', ({ index, item }) => {})
|
|
328
|
+
.on('close', () => {})
|
|
329
|
+
.on('slide', ({ index, item }) => {})
|
|
330
|
+
.on('zoom', ({ zoom }) => {})
|
|
331
|
+
.on('download', ({ item }) => {})
|
|
332
|
+
.on('share', ({ item, method }) => {})
|
|
333
|
+
.on('autoplay:start', () => {})
|
|
334
|
+
.on('autoplay:stop', () => {});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Browser support
|
|
340
|
+
|
|
341
|
+
All modern browsers (Chrome 80+, Firefox 75+, Safari 13.1+, Edge 80+). Uses:
|
|
342
|
+
- Pointer Events / Touch Events
|
|
343
|
+
- IntersectionObserver (lazy loading)
|
|
344
|
+
- CSS custom properties
|
|
345
|
+
- Web Animations API (via CSS keyframes)
|
|
346
|
+
- Fullscreen API
|
|
347
|
+
|
|
348
|
+
---
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--lgx-backdrop: rgba(0, 0, 0, 0.92);--lgx-bg: #111;--lgx-text: #fff;--lgx-text-muted: rgba(255, 255, 255, 0.6);--lgx-accent: #4f8ef7;--lgx-btn-bg: rgba(255, 255, 255, 0.12);--lgx-btn-hover: rgba(255, 255, 255, 0.22);--lgx-btn-size: 44px;--lgx-btn-radius: 50%;--lgx-nav-size: 54px;--lgx-thumb-size: 72px;--lgx-thumb-gap: 6px;--lgx-toolbar-height: 56px;--lgx-caption-height: 48px;--lgx-thumb-strip-h: 90px;--lgx-radius: 8px;--lgx-z: 9999;--lgx-duration: 300ms;--lgx-ease: cubic-bezier(0.4, 0, 0.2, 1);--lgx-slide-duration: 360ms}@keyframes lgx-fade-in{from{opacity:0}to{opacity:1}}@keyframes lgx-fade-out{from{opacity:1}to{opacity:0}}@keyframes lgx-slide-in-right{from{transform:translate3d(32px, 0, 0) scale(0.98);opacity:0}to{transform:translate3d(0, 0, 0) scale(1);opacity:1}}@keyframes lgx-slide-in-left{from{transform:translate3d(-32px, 0, 0) scale(0.98);opacity:0}to{transform:translate3d(0, 0, 0) scale(1);opacity:1}}@keyframes lgx-slide-out-left{from{transform:translate3d(0, 0, 0) scale(1);opacity:1}to{transform:translate3d(-32px, 0, 0) scale(0.98);opacity:0}}@keyframes lgx-slide-out-right{from{transform:translate3d(0, 0, 0) scale(1);opacity:1}to{transform:translate3d(32px, 0, 0) scale(0.98);opacity:0}}@keyframes lgx-spin{to{transform:rotate(360deg)}}@keyframes lgx-pulse{0%,100%{opacity:1}50%{opacity:.4}}.lgx-slide--in-right,.lgx-slide--in-left,.lgx-slide--out-left,.lgx-slide--out-right{will-change:transform,opacity;backface-visibility:hidden;-webkit-backface-visibility:hidden}.lgx-slide--active{transform:translate3d(0, 0, 0) scale(1);opacity:1}.lgx-slide--in-right{animation:lgx-slide-in-right var(--lgx-slide-duration) cubic-bezier(0.22, 1, 0.36, 1) both}.lgx-slide--in-left{animation:lgx-slide-in-left var(--lgx-slide-duration) cubic-bezier(0.22, 1, 0.36, 1) both}.lgx-slide--out-left{animation:lgx-slide-out-left var(--lgx-slide-duration) cubic-bezier(0.22, 1, 0.36, 1) forwards}.lgx-slide--out-right{animation:lgx-slide-out-right var(--lgx-slide-duration) cubic-bezier(0.22, 1, 0.36, 1) forwards}.lgx-toolbar{position:absolute;top:0;right:0;z-index:10;display:flex;align-items:center;gap:6px;padding:8px 12px;background:linear-gradient(to bottom, rgba(0, 0, 0, 0.55) 0%, transparent 100%);border-radius:0 0 0 var(--lgx-radius)}.lgx-btn{display:inline-flex;align-items:center;justify-content:center;width:var(--lgx-btn-size);height:var(--lgx-btn-size);border:none;border-radius:var(--lgx-btn-radius);background:var(--lgx-btn-bg);color:var(--lgx-text);cursor:pointer;transition:background .18s ease,transform .15s ease,opacity .15s ease;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)}.lgx-btn svg{width:20px;height:20px}.lgx-btn:hover:not(:disabled){background:var(--lgx-btn-hover);transform:scale(1.08)}.lgx-btn:active:not(:disabled){transform:scale(0.95)}.lgx-btn:disabled{opacity:.3;cursor:default}.lgx-btn:focus-visible{outline:2px solid var(--lgx-accent);outline-offset:2px}.lgx-btn--close{background:hsla(0,0%,100%,.15)}.lgx-btn--close:hover{background:rgba(220,60,60,.7)}.lgx-btn--active,.lgx-btn--autoplay[aria-pressed=true]{background:#f5da2e;color:#000}.lgx-btn--nav{position:absolute;top:50%;transform:translateY(-50%);width:var(--lgx-nav-size);height:var(--lgx-nav-size);border-radius:var(--lgx-btn-radius);z-index:10;transition:background .18s ease,transform .15s ease,opacity .2s ease}.lgx-btn--nav svg{width:26px;height:26px}.lgx-btn--nav:hover:not(:disabled){transform:translateY(-50%) scale(1.1)}.lgx-btn--prev{left:12px}.lgx-btn--next{right:12px}.lgx-counter{position:absolute;top:16px;left:50%;transform:translateX(-50%);font-size:13px;font-weight:600;color:var(--lgx-text-muted);letter-spacing:.5px;pointer-events:none;user-select:none}.lgx-caption{flex-shrink:0;padding:10px 24px;text-align:center;font-size:14px;line-height:1.5;color:var(--lgx-text);background:rgba(0,0,0,.55);border-top:1px solid hsla(0,0%,100%,.06)}.lgx-spinner{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none}.lgx-spinner__ring{width:44px;height:44px;border:3px solid hsla(0,0%,100%,.15);border-top-color:var(--lgx-text);border-radius:50%;animation:lgx-spin .75s linear infinite}.lgx-container:has(.lgx-content--loaded) .lgx-spinner{display:none}.lgx-toast{position:absolute;bottom:24px;left:50%;transform:translateX(-50%);padding:8px 18px;background:rgba(0,0,0,.75);color:#fff;font-size:13px;border-radius:20px;pointer-events:none;animation:lgx-fade-in .2s ease,lgx-fade-out .3s 2.2s ease forwards;white-space:nowrap}.lgx-thumbs{display:flex;align-items:center;justify-content:center;gap:var(--lgx-thumb-gap);overflow-x:auto;overflow-y:hidden;padding:8px 12px;height:var(--lgx-thumb-strip-h);background:rgba(0,0,0,.5);backdrop-filter:blur(4px);scrollbar-width:thin;scrollbar-color:hsla(0,0%,100%,.3) rgba(0,0,0,0);flex-shrink:0}.lgx-thumbs::-webkit-scrollbar{height:4px}.lgx-thumbs::-webkit-scrollbar-thumb{background:hsla(0,0%,100%,.3);border-radius:2px}.lgx-thumb{flex-shrink:0;width:var(--lgx-thumb-size);height:calc(var(--lgx-thumb-strip-h) - 20px);border:2px solid rgba(0,0,0,0);border-radius:var(--lgx-radius);overflow:hidden;cursor:pointer;padding:0;background:hsla(0,0%,100%,.08);transition:border-color .2s var(--lgx-ease),opacity .2s var(--lgx-ease),transform .2s var(--lgx-ease);opacity:.55}.lgx-thumb img{width:100%;height:100%;object-fit:cover;display:block;pointer-events:none}.lgx-thumb:hover{opacity:.85;transform:scale(1.05)}.lgx-thumb--active{border-color:var(--lgx-accent);opacity:1;transform:scale(1.08)}.lgx-thumb--no-image{display:flex;align-items:center;justify-content:center;color:var(--lgx-text-muted);font-size:13px;font-weight:600}.lgx-thumb:focus-visible{outline:2px solid var(--lgx-accent);outline-offset:2px}.lgx-mosaic{position:absolute;inset:0;display:grid;pointer-events:none;z-index:3;overflow:hidden;border-radius:inherit}.lgx-mosaic__tile{--lgx-tile-delay: 0ms;will-change:transform,opacity,border-radius;animation:lgx-mosaic-out 420ms cubic-bezier(0.4, 0, 0.6, 1) var(--lgx-tile-delay, 0ms) both}@keyframes lgx-mosaic-out{0%{transform:scale(1);opacity:1;border-radius:0}45%{transform:scale(1.06);border-radius:4px;opacity:1}100%{transform:scale(0) rotate(12deg);border-radius:50%;opacity:0}}@media(prefers-reduced-motion: reduce){.lgx-mosaic__tile{animation:none;opacity:0}}@media(max-width: 640px){:root{--lgx-btn-size: 38px;--lgx-nav-size: 42px;--lgx-thumb-size: 56px;--lgx-thumb-strip-h: 76px}.lgx-toolbar{padding:6px 8px;gap:4px}.lgx-btn--prev{left:4px}.lgx-btn--next{right:4px}.lgx-caption{font-size:12px;padding:8px 16px}}@media(hover: none){.lgx-btn--nav{opacity:1 !important}}@media(prefers-reduced-motion: reduce){*,*::before,*::after{animation-duration:.01ms !important;animation-iteration-count:1 !important;transition-duration:.01ms !important}}@media(prefers-color-scheme: dark){:root{--lgx-btn-bg: rgba(255, 255, 255, 0.15)}}.lgx-modal{position:fixed;inset:0;z-index:var(--lgx-z);display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;color:var(--lgx-text);-webkit-font-smoothing:antialiased}.lgx-backdrop{position:absolute;inset:0;background:var(--lgx-backdrop)}.lgx-container{position:relative;display:flex;flex-direction:column;flex:1;overflow:hidden;outline:none}.lgx-stage{position:relative;flex:1;min-height:0;display:flex;align-items:center;justify-content:center;overflow:hidden;user-select:none}.lgx-slide-wrapper{position:relative;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden}.lgx-content{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;min-width:0;min-height:0;will-change:transform,opacity;backface-visibility:hidden;-webkit-backface-visibility:hidden}.lgx-content--image{display:grid;place-items:center;overflow:hidden;padding:clamp(16px,4vh,42px) clamp(16px,5vw,72px);box-sizing:border-box}.lgx-image{max-width:100%;max-height:100%;width:auto;height:auto;margin:auto;object-fit:contain;display:block;border-radius:2px;transform-origin:center center;will-change:transform;transition:transform .1s ease;pointer-events:none}.lgx-video{max-width:90vw;max-height:calc(90vh - var(--lgx-toolbar-height) - var(--lgx-caption-height) - var(--lgx-thumb-strip-h));border-radius:var(--lgx-radius);background:#000;display:block}.lgx-content--iframe{padding:clamp(16px,4vh,42px) clamp(16px,5vw,72px);box-sizing:border-box;overflow:visible}.lgx-content--iframe:not(.lgx-content--loaded)::after{content:"";position:absolute;left:50%;top:50%;width:min(90vw,960px);height:min(56.25vw,540px,90vh - var(--lgx-toolbar-height) - var(--lgx-caption-height) - var(--lgx-thumb-strip-h) - 80px);transform:translate(-50%, -50%);border-radius:var(--lgx-radius);background:linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.04) 50%, transparent 100%);background-size:200% 100%;animation:lgx-pulse 1.5s ease infinite;pointer-events:none}.lgx-iframe{width:min(90vw,960px);height:min(56.25vw,540px,90vh - var(--lgx-toolbar-height) - var(--lgx-caption-height) - var(--lgx-thumb-strip-h) - 80px);max-width:100%;max-height:100%;border:none;display:block;background:#000;border-radius:var(--lgx-radius);overflow:hidden}.lgx-content--error{color:var(--lgx-text-muted);font-size:14px;padding:24px;text-align:center}.lgx-content--error::before{content:"⚠ Failed to load media";display:block}.lgx-modal--fullscreen .lgx-image{max-height:100vh;max-width:100vw}.lgx-modal--fullscreen .lgx-iframe{width:100vw;max-width:100vw;height:100vh}/*# sourceMappingURL=crowdbox.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sourceRoot":"","sources":["../src/styles/_variables.scss","../src/styles/_animations.scss","../src/styles/_toolbar.scss","../src/styles/_thumbnails.scss","../src/styles/_mosaic.scss","../src/styles/_responsive.scss","../src/styles/main.scss"],"names":[],"mappings":"CACA,MACE,oCACA,eACA,iBACA,2CACA,sBACA,wCACA,2CACA,qBACA,sBACA,qBACA,uBACA,qBACA,2BACA,2BACA,0BACA,kBACA,cACA,sBACA,yCACA,4BCrBF,uBACE,eACA,cAGF,wBACE,eACA,cAGF,8BACE,6DACA,sDAGF,6BACE,8DACA,sDAGF,8BACE,uDACA,6DAGF,+BACE,uDACA,4DAGF,oBACE,6BAGF,qBACE,kBACA,gBAQF,oFAIE,8BACA,2BACA,mCAGF,mBACE,wCACA,UAGF,gHACA,8GACA,oHACA,sHC5DA,aACE,kBACA,MACA,QACA,WACA,aACA,mBACA,QACA,iBACA,gFACA,sCAIF,SACE,oBACA,mBACA,uBACA,0BACA,2BACA,YACA,oCACA,6BACA,sBACA,eACA,sEACA,0BACA,kCAEA,oCAEA,8BACE,gCACA,sBAGF,+BACE,sBAGF,kBACE,WACA,eAGF,uBACE,oCACA,mBAGF,gBACE,+BACA,oDAGF,uDAEE,mBACA,WAMJ,cACE,kBACA,QACA,2BACA,0BACA,2BACA,oCACA,WACA,qEAEA,yCAEA,yEAGF,yBACA,0BAGA,aACE,kBACA,SACA,SACA,2BACA,eACA,gBACA,4BACA,oBACA,oBACA,iBAGF,aAGE,cACA,kBACA,kBACA,eACA,gBACA,sBACA,2BACA,yCAIF,aACE,kBACA,QACA,aACA,mBACA,uBACA,oBAEA,mBACE,WACA,YACA,qCACA,iCACA,kBACA,wCAMJ,sDACE,aAIF,WACE,kBACA,YACA,SACA,2BACA,iBACA,2BACA,WACA,eACA,mBACA,oBACA,mEACA,mBCnJF,YACE,aACA,mBACA,uBACA,yBACA,gBACA,kBACA,iBACA,gCACA,0BACA,0BACA,qBACA,iDACA,cAEA,0CACA,qFAGF,WACE,cACA,4BACA,6CACA,+BACA,gCACA,gBACA,eACA,UACA,+BACA,sGACA,YAEA,eACE,WACA,YACA,iBACA,cACA,oBAGF,iBACE,YACA,sBAGF,mBACE,+BACA,UACA,sBAGF,qBACE,aACA,mBACA,uBACA,4BACA,eACA,gBAGF,yBACE,oCACA,mBC1DJ,YACE,kBACA,QACA,aACA,oBACA,UACA,gBACA,sBAGF,kBACE,sBACA,4CACA,4FAGF,0BACE,GACE,mBACA,UACA,gBAEF,IACE,sBACA,kBACA,UAEF,KACE,iCACA,kBACA,WAKJ,uCACE,kBACE,eACA,WC1CJ,yBACE,MACE,qBACA,qBACA,uBACA,0BAGF,qCACA,wBACA,yBACA,8CAGF,oBAEE,oCAGF,uCACE,qBACE,oCACA,uCACA,sCAKJ,mCACE,MACE,yCCvBJ,WACE,eACA,QACA,qBACA,aACA,sBACA,6EACA,sBACA,mCAGF,cACE,kBACA,QACA,+BAGF,eACE,kBACA,aACA,sBACA,OACA,gBACA,aAIF,WACE,kBACA,OACA,aACA,aACA,mBACA,uBACA,gBACA,iBAGF,mBACE,kBACA,WACA,YACA,YACA,aACA,gBAKF,aACE,kBACA,QACA,aACA,mBACA,uBACA,YACA,aACA,8BACA,2BACA,mCAKF,oBACE,aACA,mBACA,gBACA,kDACA,sBAGF,WACE,eACA,gBACA,WACA,YACA,YACA,mBACA,cACA,kBACA,+BACA,sBACA,8BACA,oBAIF,WACE,eACA,yGACA,gCACA,gBACA,cAIF,qBACE,kDACA,sBACA,iBAGA,sDACE,WACA,kBACA,SACA,QACA,sBACA,yHACA,gCACA,gCACA,mGACA,0BACA,uCACA,oBAIJ,YACE,sBACA,yHACA,eACA,gBACA,YACA,cACA,gBACA,gCACA,gBAIF,oBACE,4BACA,eACA,aACA,kBAEA,4BACE,iCACA,cAMF,kCACE,iBACA,gBAEF,mCACE,YACA,gBACA","file":"crowdbox.css"}
|