@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 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"}