@mozert-studio/mozert-slider 0.0.0-stage → 1.1.1

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.
@@ -0,0 +1,3169 @@
1
+ /*!
2
+ * Mozert 1.1.1
3
+ * A dependency-free, responsive, touch-friendly carousel / slider.
4
+ * MIT License
5
+ */
6
+ const Mozert = (function () {
7
+ const VERSION = '1.1.1';
8
+
9
+ const DEFAULTS = {
10
+ accessibility: true,
11
+ adaptiveHeight: false,
12
+ appendArrows: null,
13
+ appendDots: null,
14
+ arrows: true,
15
+ asNavFor: null,
16
+ autoplay: false,
17
+ disableOnInteraction: false,
18
+ autoplayReverse: false,
19
+ autoplaySpeed: 3000,
20
+ centerMode: false,
21
+ centerPadding: '50px',
22
+ cssEase: 'ease',
23
+ customPaging: null,
24
+ dots: false,
25
+ dotsClass: 'mozert-dots',
26
+ dragThreshold: 6,
27
+ draggable: true,
28
+ easing: 'linear',
29
+ edgeFriction: 0.15,
30
+ edgePadding: 0,
31
+ effect: 'slide',
32
+ fade: false,
33
+ focusOnSelect: false,
34
+ freeMode: false,
35
+ infinite: true,
36
+ initialSlide: 0,
37
+ labels: null,
38
+ lazyLoad: 'ondemand',
39
+ mobileFirst: false,
40
+ nextArrow: null,
41
+ pauseOnDotsHover: false,
42
+ pauseOnFocus: true,
43
+ pauseOnHover: true,
44
+ prevArrow: null,
45
+ respectReducedMotion: true,
46
+ respondTo: 'window',
47
+ responsive: null,
48
+ rows: 1,
49
+ rtl: false,
50
+ slide: '',
51
+ slideWidth: 0,
52
+ slidesPerRow: 1,
53
+ slidesToScroll: 1,
54
+ slidesToShow: 1,
55
+ spaceBetween: 0,
56
+ speed: 300,
57
+ stopOnLastSlide: false,
58
+ swipe: true,
59
+ swipeToSlide: false,
60
+ touchMove: true,
61
+ touchRatio: 1,
62
+ touchThreshold: 5,
63
+ useCSS: true,
64
+ useTransform: true,
65
+ variableWidth: false,
66
+ vertical: false,
67
+ verticalSwiping: false,
68
+ waitForAnimate: true,
69
+ zIndex: 1000,
70
+ on: null,
71
+ };
72
+
73
+ const LABELS = { slide: 'Slide {n} of {total}', prev: 'Previous slide', next: 'Next slide', goTo: 'Go to slide {n}', fullscreen: 'Toggle fullscreen', close: 'Close', zoom: 'Zoom', lightbox: 'Image viewer', imageOf: 'Image {n} of {total}', magnifier: 'Magnifier' };
74
+ const DEFAULT_PREV = '<button type="button" class="mozert-prev" aria-label="Previous slide">Previous</button>';
75
+ const DEFAULT_NEXT = '<button type="button" class="mozert-next" aria-label="Next slide">Next</button>';
76
+ const FOCUSABLE = 'a[href],button,input,select,textarea,[tabindex],[contenteditable="true"]';
77
+ const STASH_ATTRS = ['class', 'style', 'role', 'aria-hidden', 'aria-label', 'aria-roledescription', 'data-mozert-index'];
78
+
79
+ const STACKED = ['fade', 'flip', 'cube', 'cards', 'creative'];
80
+ const EASINGS = {
81
+ linear: (t) => t,
82
+ swing: (t) => 0.5 - Math.cos(t * Math.PI) / 2,
83
+ easeInQuad: (t) => t * t,
84
+ easeOutQuad: (t) => t * (2 - t),
85
+ easeInOutQuad: (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
86
+ easeInCubic: (t) => t * t * t,
87
+ easeOutCubic: (t) => --t * t * t + 1,
88
+ easeInOutCubic: (t) => (t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1),
89
+ easeInOutQuart: (t) => (t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t),
90
+ };
91
+
92
+ const registry = {
93
+ get: (e) => e._mozert || undefined,
94
+ set: (e, v) => { e._mozert = v; },
95
+ delete: (e) => { delete e._mozert; },
96
+ };
97
+ const isEl = (v) => typeof Element !== 'undefined' && v instanceof Element;
98
+ const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
99
+
100
+ function htmlToEl(html) {
101
+ const t = document.createElement('template');
102
+ t.innerHTML = String(html).trim();
103
+ return t.content.firstElementChild;
104
+ }
105
+
106
+ function resolveNode(v) {
107
+ if (!v) return null;
108
+ if (isEl(v)) return v;
109
+ if (typeof v === 'string') {
110
+ const t = v.trim();
111
+ return t.charAt(0) === '<' ? htmlToEl(t) : document.querySelector(t);
112
+ }
113
+ if (v[0] && isEl(v[0])) return v[0];
114
+ return null;
115
+ }
116
+
117
+ function parseData(el) {
118
+ const raw = el.getAttribute('data-mozert');
119
+ if (!raw) return {};
120
+ try {
121
+ return JSON.parse(raw);
122
+ } catch (e) {
123
+ console.warn('Mozert: could not parse data-mozert JSON', e);
124
+ return {};
125
+ }
126
+ }
127
+
128
+ class Mozert {
129
+ constructor(el, options = {}) {
130
+ if (typeof el === 'string') el = document.querySelector(el);
131
+ if (!isEl(el)) throw new TypeError('Mozert: a valid element or selector is required');
132
+ const existing = registry.get(el);
133
+ if (existing) return existing;
134
+
135
+ this.el = el;
136
+ const _o = Object.assign({}, options, parseData(el));
137
+ Mozert._boot.forEach((f) => f(el, _o));
138
+ this._base = Object.assign({}, DEFAULTS, options, parseData(el));
139
+ this.options = this._base;
140
+ this._dormant = false;
141
+ this.active = false;
142
+ this.canNav = false;
143
+ this.current = 0;
144
+ this.animating = false;
145
+ this.n = 0;
146
+
147
+ this._handlers = {};
148
+ this._sources = [];
149
+ this._filter = null;
150
+ this._stash = new WeakMap();
151
+ this._logical = 0;
152
+ this._t = 0;
153
+ this._cur = 0;
154
+ this._bp = null;
155
+ this._off = [];
156
+ this._life = [];
157
+ this._pause = new Set();
158
+ this._fxHooks = [];
159
+ this._mi = [];
160
+ this._release = null;
161
+ this._lockDrag = false;
162
+ this._autoOn = false;
163
+ this._dir = 1;
164
+ this._built = false;
165
+ this._initDone = false;
166
+ this._origDir = el.getAttribute('dir');
167
+ this._lastW = el.clientWidth;
168
+
169
+ registry.set(el, this);
170
+ this._collect();
171
+ this._bindLife();
172
+ this._apply(true);
173
+ }
174
+
175
+ /* ------------------------------------------------------------------ */
176
+ /* Public API */
177
+ /* ------------------------------------------------------------------ */
178
+
179
+ /** Read-only snapshot of the slider's current state. */
180
+ getState() {
181
+ return {
182
+ active: this.active,
183
+ current: this.current,
184
+ count: this.n,
185
+ total: this.trackLen || 0,
186
+ inner: this.Lc || 0,
187
+ clones: this.cloneCount || 0,
188
+ offset: this._cur || 0,
189
+ animating: this.animating,
190
+ canNavigate: this.canNav,
191
+ playing: this.isPlaying(),
192
+ breakpoint: this._bp,
193
+ };
194
+ }
195
+
196
+ slideCount() {
197
+ return this.n;
198
+ }
199
+
200
+ currentSlide() {
201
+ return this.current;
202
+ }
203
+
204
+ isPlaying() {
205
+ return !!(this.active && this._autoOn && !this._pause.has('user'));
206
+ }
207
+
208
+ get _c() {
209
+ return this.cloneCount;
210
+ }
211
+
212
+ next() {
213
+ if (!this.active || !this.canNav) return this;
214
+ this._slideTo(this.current + this.s.slidesToScroll);
215
+ return this;
216
+ }
217
+
218
+ prev() {
219
+ if (!this.active || !this.canNav) return this;
220
+ this._slideTo(this.current - this.s.slidesToScroll);
221
+ return this;
222
+ }
223
+
224
+ goTo(index, dontAnimate = false) {
225
+ if (!this.active || !this.canNav) return this;
226
+ index = parseInt(index, 10);
227
+ if (isNaN(index)) return this;
228
+ this._slideTo(this.s.infinite ? this._norm(index) : clamp(index, 0, Math.max(0, this.n - 1)), { animate: !dontAnimate });
229
+ return this;
230
+ }
231
+
232
+ play() {
233
+ const was = this.isPlaying();
234
+ this._autoOn = true;
235
+ this._pause.delete('user');
236
+ this._schedule();
237
+ this._live();
238
+ if (!was) this.emit('autoplayStart');
239
+ return this;
240
+ }
241
+
242
+ pause() {
243
+ const was = this.isPlaying();
244
+ this._autoOn = false;
245
+ this._pause.add('user');
246
+ clearTimeout(this._apTimer);
247
+ cancelAnimationFrame(this._apRaf);
248
+ this._live();
249
+ if (was) this.emit('autoplayStop');
250
+ return this;
251
+ }
252
+
253
+ getSlides() {
254
+ return this._units ? this._units.slice() : this._activeItems();
255
+ }
256
+
257
+ getOption(name) {
258
+ return this.options[name];
259
+ }
260
+
261
+ setOption(name, value, refresh) {
262
+ if (name && typeof name === 'object') {
263
+ refresh = value;
264
+ Object.assign(this._base, name);
265
+ } else {
266
+ this._base[name] = value;
267
+ }
268
+ if (refresh === false) {
269
+ // Only re-resolve settings that are read live (autoplay, pauseOn*, ...).
270
+ this._applyLive();
271
+ } else {
272
+ this._apply(false);
273
+ }
274
+ return this;
275
+ }
276
+
277
+ _applyLive() {
278
+ const r = this._resolve();
279
+ this.options = r.s;
280
+ if (this.active) {
281
+ this.s = this._prepare(r.s);
282
+ this._autoOn = !!this.s.autoplay && this.canNav;
283
+ if (this._autoOn) this._pause.delete('user');
284
+ this._live();
285
+ this._schedule();
286
+ }
287
+ }
288
+
289
+ add(markup, index, addBefore) {
290
+ const node = resolveNode(typeof markup === 'string' && markup.trim().charAt(0) !== '<' ? `<div>${markup}</div>` : markup);
291
+ if (!node) return false;
292
+ if (typeof index === 'boolean') {
293
+ addBefore = index;
294
+ index = null;
295
+ }
296
+ const items = this._activeItems();
297
+ if (index != null && (index < 0 || index >= items.length)) return false;
298
+ if (index == null || !items.length) {
299
+ if (addBefore) this._sources.unshift(node);
300
+ else this._sources.push(node);
301
+ } else {
302
+ const at = this._sources.indexOf(items[index]);
303
+ this._sources.splice(at + (addBefore ? 0 : 1), 0, node);
304
+ }
305
+ this._refreshKeep();
306
+ return this;
307
+ }
308
+
309
+ remove(index, removeBefore) {
310
+ const items = this._activeItems();
311
+ if (typeof index === 'boolean') {
312
+ removeBefore = index;
313
+ index = removeBefore ? 0 : items.length - 1;
314
+ } else {
315
+ index = removeBefore ? index - 1 : index;
316
+ }
317
+ if (index == null || index < 0 || index >= items.length) return false;
318
+ const node = items[index];
319
+ this._sources.splice(this._sources.indexOf(node), 1);
320
+ this._restoreItem(node);
321
+ if (node.parentNode) node.parentNode.removeChild(node);
322
+ if (index < this.current) this.current -= 1;
323
+ this._refreshKeep();
324
+ return this;
325
+ }
326
+
327
+ removeAll() {
328
+ this._activeItems().forEach((node) => {
329
+ this._sources.splice(this._sources.indexOf(node), 1);
330
+ this._restoreItem(node);
331
+ if (node.parentNode) node.parentNode.removeChild(node);
332
+ });
333
+ this.current = 0;
334
+ this._refreshKeep();
335
+ return this;
336
+ }
337
+
338
+ filter(selectorOrFn) {
339
+ this._filter = selectorOrFn || null;
340
+ this.current = 0;
341
+ this._refreshKeep();
342
+ return this;
343
+ }
344
+
345
+ unfilter() {
346
+ return this.filter(null);
347
+ }
348
+
349
+ /** Re-read options and rebuild everything, keeping the current slide. */
350
+ refresh() {
351
+ this._refreshKeep();
352
+ this.emit('reInit');
353
+ return this;
354
+ }
355
+
356
+ /** Re-measure and reposition, e.g. after the container was resized by script. */
357
+ setPosition() {
358
+ if (!this.active) return this;
359
+ this._layout();
360
+ this._paintCurrent(false);
361
+ this.emit('setPosition');
362
+ return this;
363
+ }
364
+
365
+ destroy() {
366
+ this.emit('destroy');
367
+ if (this.active) this._teardown();
368
+ this._sources.forEach((n) => this.el.appendChild(n));
369
+ this.active = false;
370
+ this._life.forEach((fn) => fn());
371
+ this._life = [];
372
+ if (this._ro) this._ro.disconnect();
373
+ registry.delete(this.el);
374
+ this._dormant = false;
375
+ this._handlers = {};
376
+ return this;
377
+ }
378
+
379
+ on(name, fn) {
380
+ (this._handlers[name] = this._handlers[name] || []).push(fn);
381
+ return this;
382
+ }
383
+
384
+ once(name, fn) {
385
+ const wrap = (...a) => {
386
+ this.off(name, wrap);
387
+ fn.apply(this, a);
388
+ };
389
+ return this.on(name, wrap);
390
+ }
391
+
392
+ one(name, fn) {
393
+ return this.once(name, fn);
394
+ }
395
+
396
+ off(name, fn) {
397
+ if (!name) this._handlers = {};
398
+ else if (!fn) delete this._handlers[name];
399
+ else this._handlers[name] = (this._handlers[name] || []).filter((f) => f !== fn);
400
+ return this;
401
+ }
402
+
403
+ emit(name, ...args) {
404
+ (this._handlers[name] || []).slice().forEach((fn) => fn.call(this, this, ...args));
405
+ const cb = this._base.on && this._base.on[name];
406
+ if (typeof cb === 'function') cb.call(this, this, ...args);
407
+ this.el.dispatchEvent(new CustomEvent('mozert:' + name, { bubbles: true, detail: { slider: this, args } }));
408
+ if (Mozert._bridge) Mozert._bridge(this, name, args);
409
+ }
410
+
411
+ static get(el) {
412
+ if (typeof el === 'string') el = document.querySelector(el);
413
+ return (isEl(el) && registry.get(el)) || null;
414
+ }
415
+
416
+ static initAll(root, options) {
417
+ const scope = root && root.querySelectorAll ? root : document;
418
+ const found = Array.from(scope.querySelectorAll('[data-mozert]'));
419
+ if (scope !== document && scope.matches && scope.matches('[data-mozert]')) found.unshift(scope);
420
+ return found.map((n) => Mozert.get(n) || new Mozert(n, options));
421
+ }
422
+
423
+ /* ------------------------------------------------------------------ */
424
+ /* Settings and responsive handling */
425
+ /* ------------------------------------------------------------------ */
426
+
427
+ _respondWidth() {
428
+ const mode = this._base.respondTo;
429
+ const w = window.innerWidth;
430
+ if (mode === 'slider') return this.el.clientWidth;
431
+ if (mode === 'min') return Math.min(w, this.el.clientWidth);
432
+ return w;
433
+ }
434
+
435
+ _resolve() {
436
+ const base = this._base;
437
+ const list = Array.isArray(base.responsive) ? base.responsive.filter((r) => r && typeof r.breakpoint === 'number') : [];
438
+ const w = this._respondWidth();
439
+ let hit = null;
440
+ if (base.mobileFirst) {
441
+ list.sort((a, b) => a.breakpoint - b.breakpoint).forEach((r) => {
442
+ if (w >= r.breakpoint) hit = r;
443
+ });
444
+ } else {
445
+ list.sort((a, b) => b.breakpoint - a.breakpoint).forEach((r) => {
446
+ if (w <= r.breakpoint) hit = r;
447
+ });
448
+ }
449
+ if (hit && hit.settings === 'destroy') return { bp: hit.breakpoint, dormant: true, s: Object.assign({}, base) };
450
+ return { bp: hit ? hit.breakpoint : null, dormant: false, s: Object.assign({}, base, hit ? hit.settings : {}) };
451
+ }
452
+
453
+ _prepare(raw) {
454
+ const s = Object.assign({}, raw);
455
+ s.slidesToShow = Math.max(1, parseFloat(s.slidesToShow) || 1);
456
+ s.slidesToScroll = Math.max(1, parseInt(s.slidesToScroll, 10) || 1);
457
+ s.rows = Math.max(1, parseInt(s.rows, 10) || 1);
458
+ s.slidesPerRow = Math.max(1, parseInt(s.slidesPerRow, 10) || 1);
459
+ s.effect = s.fade ? 'fade' : s.effect || 'slide';
460
+ s.fade = s.effect === 'fade';
461
+ s.stacked = STACKED.indexOf(s.effect) > -1;
462
+ if (s.stacked) {
463
+ s.slidesToShow = 1;
464
+ s.slidesToScroll = 1;
465
+ }
466
+ if (s.effect === 'coverflow' || s.effect === 'expand') s.centerMode = true;
467
+ if (s.freeMode || s.virtual || s.bleed) s.infinite = false;
468
+ if (s.marquee) {
469
+ s.infinite = true;
470
+ s.arrows = false;
471
+ s.dots = false;
472
+ s.autoplay = false;
473
+ }
474
+ s.spaceBetween = parseFloat(s.spaceBetween) || 0;
475
+ if (s.respectReducedMotion && window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
476
+ s.speed = 0;
477
+ s.autoplay = false;
478
+ }
479
+ return s;
480
+ }
481
+
482
+ _apply(first) {
483
+ const r = this._resolve();
484
+ const changed = r.bp !== this._bp;
485
+ this._bp = r.bp;
486
+ this._raw = r.s;
487
+ this.options = r.s;
488
+ this._dormant = !!r.dormant;
489
+ if (r.dormant) {
490
+ if (this.active) {
491
+ this._teardown();
492
+ this.active = false;
493
+ this.emit('destroy');
494
+ }
495
+ if (changed && !first) this.emit('breakpoint', r.bp);
496
+ return;
497
+ }
498
+ this.s = this._prepare(r.s);
499
+ if (this.active) this._teardown();
500
+ this._build(!first);
501
+ if (changed && !first) this.emit('breakpoint', r.bp);
502
+ }
503
+
504
+ _refreshKeep() {
505
+ if (!this.active) {
506
+ this._apply(false);
507
+ return;
508
+ }
509
+ this._teardown();
510
+ this._build(true);
511
+ }
512
+
513
+ _bindLife() {
514
+ let timer;
515
+ const onResize = () => {
516
+ clearTimeout(timer);
517
+ timer = setTimeout(() => this._onResize(), 40);
518
+ };
519
+ window.addEventListener('resize', onResize);
520
+ window.addEventListener('orientationchange', onResize);
521
+ const onVis = () => {
522
+ if (document.hidden) this._holdOn('hidden');
523
+ else this._holdOff('hidden');
524
+ };
525
+ document.addEventListener('visibilitychange', onVis);
526
+ this._life.push(() => {
527
+ clearTimeout(timer);
528
+ window.removeEventListener('resize', onResize);
529
+ window.removeEventListener('orientationchange', onResize);
530
+ document.removeEventListener('visibilitychange', onVis);
531
+ });
532
+ if (typeof ResizeObserver !== 'undefined') {
533
+ this._ro = new ResizeObserver(() => {
534
+ const w = this.el.clientWidth;
535
+ if (w !== this._lastW) {
536
+ this._lastW = w;
537
+ onResize();
538
+ }
539
+ });
540
+ this._ro.observe(this.el);
541
+ }
542
+ }
543
+
544
+ _onResize() {
545
+ const r = this._resolve();
546
+ const must = r.bp !== this._bp || (r.dormant && this.active) || (!r.dormant && !this.active);
547
+ if (must) {
548
+ this._apply(false);
549
+ return;
550
+ }
551
+ if (!this.active) return;
552
+ this._layout();
553
+ if (this._computeCan() !== this.canNav) {
554
+ this._refreshKeep();
555
+ return;
556
+ }
557
+ this._paintCurrent(false);
558
+ this.emit('setPosition');
559
+ }
560
+
561
+ /* ------------------------------------------------------------------ */
562
+ /* DOM build / teardown */
563
+ /* ------------------------------------------------------------------ */
564
+
565
+ _collect() {
566
+ const kids = Array.from(this.el.children);
567
+ const sel = this._base.slide;
568
+ this._sources = sel ? kids.filter((k) => k.matches(sel)) : kids;
569
+ }
570
+
571
+ _activeItems() {
572
+ const f = this._filter;
573
+ if (!f) return this._sources.slice();
574
+ return this._sources.filter((el, i) => (typeof f === 'function' ? f.call(el, el, i) : el.matches(f)));
575
+ }
576
+
577
+ _remember(el) {
578
+ if (this._stash.has(el)) return;
579
+ const o = {};
580
+ STASH_ATTRS.forEach((a) => (o[a] = el.getAttribute(a)));
581
+ this._stash.set(el, o);
582
+ }
583
+
584
+ _restoreItem(el) {
585
+ const o = this._stash.get(el);
586
+ if (!o) return;
587
+ STASH_ATTRS.forEach((a) => (o[a] === null ? el.removeAttribute(a) : el.setAttribute(a, o[a])));
588
+ el.querySelectorAll('[data-mozert-tabindex]').forEach((f) => {
589
+ const v = f.getAttribute('data-mozert-tabindex');
590
+ if (v === '') f.removeAttribute('tabindex');
591
+ else f.setAttribute('tabindex', v);
592
+ f.removeAttribute('data-mozert-tabindex');
593
+ });
594
+ el.querySelectorAll('[data-mozert-bg]').forEach((b) => b.removeAttribute('data-mozert-bg'));
595
+ this._stash.delete(el);
596
+ }
597
+
598
+ _label(key, vars) {
599
+ const l = Object.assign({}, LABELS, this.s && this.s.labels);
600
+ return String(l[key]).replace(/\{(\w+)\}/g, (m, k) => (vars && vars[k] !== undefined ? vars[k] : m));
601
+ }
602
+
603
+ _px(v) {
604
+ const p = String(v || '0');
605
+ if (p.indexOf('%') > -1) return (parseFloat(p) / 100) * this.el.clientWidth;
606
+ return parseFloat(p) || 0;
607
+ }
608
+
609
+ _padPx() {
610
+ return this._px(this.s.centerPadding);
611
+ }
612
+
613
+ _edgePad() {
614
+ const s = this.s;
615
+ return s.centerMode ? this._padPx() : this._px(s.edgePadding);
616
+ }
617
+
618
+ /** Works out how many slides fit (can be fractional) before the first layout. */
619
+ _calcShow() {
620
+ const s = this.s;
621
+ let show = s.slidesToShow;
622
+ if (s.slideWidth && !s.vertical && !s.variableWidth) {
623
+ const inner = Math.max(1, (this.list ? this.list.clientWidth : this.el.clientWidth) - 2 * this._edgePad());
624
+ show = Math.max(1, (inner + s.spaceBetween) / (parseFloat(s.slideWidth) + s.spaceBetween));
625
+ }
626
+ this._show = show;
627
+ this._showC = Math.ceil(show - 1e-6);
628
+ }
629
+
630
+ _build(keep) {
631
+ const s = this.s;
632
+ const el = this.el;
633
+ const items = this._activeItems();
634
+ // Slides hidden by filter() leave the DOM until unfilter() (or destroy) puts them back.
635
+ this._sources.forEach((n) => {
636
+ if (items.indexOf(n) === -1 && n.parentNode === el) el.removeChild(n);
637
+ });
638
+
639
+ el.classList.add('mozert-slider', 'mozert-initialized');
640
+ el.classList.toggle('mozert-vertical', !!s.vertical);
641
+ el.classList.toggle('mozert-rtl', !!s.rtl);
642
+ el.classList.toggle('mozert-fade', !!s.fade);
643
+ el.classList.toggle('mozert-fx', s.effect !== 'slide' && !s.fade);
644
+ if (s.effect !== 'slide') el.classList.add('mozert-effect-' + s.effect);
645
+ el.classList.toggle('mozert-center', !!s.centerMode);
646
+ el.classList.toggle('mozert-adaptive', !!s.adaptiveHeight);
647
+ el.classList.toggle('mozert-draggable', !!(s.draggable || s.swipe));
648
+ if (s.rtl) el.setAttribute('dir', 'rtl');
649
+ el.setAttribute('role', 'region');
650
+ el.setAttribute('aria-roledescription', 'carousel');
651
+
652
+ this.list = document.createElement('div');
653
+ this.list.className = 'mozert-list';
654
+ this.track = document.createElement('div');
655
+ this.track.className = 'mozert-track';
656
+ this.list.appendChild(this.track);
657
+ el.appendChild(this.list);
658
+
659
+ // Group into pages when grid mode is on.
660
+ let units = items;
661
+ if (s.rows > 1 || s.slidesPerRow > 1) {
662
+ units = [];
663
+ const per = s.rows * s.slidesPerRow;
664
+ for (let i = 0; i < items.length; i += per) {
665
+ const page = document.createElement('div');
666
+ page.className = 'mozert-page';
667
+ for (let r = 0; r < s.rows; r++) {
668
+ const row = document.createElement('div');
669
+ row.className = 'mozert-row';
670
+ for (let c = 0; c < s.slidesPerRow; c++) {
671
+ const it = items[i + r * s.slidesPerRow + c];
672
+ if (!it) break;
673
+ this._remember(it);
674
+ it.classList.add('mozert-cell');
675
+ it.style.width = 100 / s.slidesPerRow + '%';
676
+ row.appendChild(it);
677
+ }
678
+ if (row.children.length) page.appendChild(row);
679
+ }
680
+ units.push(page);
681
+ }
682
+ }
683
+ this._items = items;
684
+ this._units = units;
685
+ this.n = units.length;
686
+ this._calcShow();
687
+
688
+ units.forEach((u, i) => {
689
+ this._remember(u);
690
+ u.classList.add('mozert-slide');
691
+ u.setAttribute('data-mozert-index', i);
692
+ u.setAttribute('role', 'group');
693
+ u.setAttribute('aria-roledescription', 'slide');
694
+ u.setAttribute('aria-label', this._label('slide', { n: i + 1, total: units.length }));
695
+ u.querySelectorAll('[data-lazy-bg]').forEach((b, k) => b.setAttribute('data-mozert-bg', String(k)));
696
+ this.track.appendChild(u);
697
+ });
698
+ this.dom = units.slice();
699
+ this.cloneCount = 0;
700
+
701
+ // First pass (only for variable widths) so we know whether navigation is needed.
702
+ if (s.variableWidth) this._layout();
703
+ this.canNav = this._computeCan();
704
+
705
+ if (s.infinite && this.canNav && !s.stacked) this._addClones();
706
+
707
+ this._layout();
708
+ this.maxIndex = this._maxIndex();
709
+ this.current = clamp(keep ? this.current : parseInt(s.initialSlide, 10) || 0, 0, s.infinite ? Math.max(0, this.n - 1) : this.maxIndex);
710
+ if (!this.canNav) this.current = 0;
711
+ this._logical = this.current;
712
+
713
+ if (!this.canNav) el.classList.add('mozert-static');
714
+ else el.classList.remove('mozert-static');
715
+
716
+ this._buildArrows();
717
+ this._buildDots();
718
+ this._bindEvents();
719
+ this.active = true;
720
+ this._built = true;
721
+ this._paintCurrent(false);
722
+ if (s.fade) {
723
+ // Show the first slide immediately, then switch the cross-fade on.
724
+ this.dom.forEach((e) => (e.style.transition = 'none'));
725
+ void this.track.offsetWidth;
726
+ this.dom.forEach((e) => (e.style.transition = s.speed > 0 ? 'opacity ' + s.speed + 'ms ' + s.cssEase : 'none'));
727
+ }
728
+ this._updateNav();
729
+ this._lazy();
730
+ this._mi = Mozert._mods.map((m) => (m.create ? m.create(this) : null)).filter(Boolean);
731
+
732
+ this._autoOn = !!s.autoplay && this.canNav;
733
+ if (!this._initDone) {
734
+ this._initDone = true;
735
+ this._autoOn = !!s.autoplay && this.canNav;
736
+ this.emit('init');
737
+ } else if (keep) {
738
+ this.emit('reInit');
739
+ }
740
+ this._live();
741
+ this._schedule();
742
+ this.emit('setPosition');
743
+ }
744
+
745
+ _addClones() {
746
+ const s = this.s;
747
+ const n = this.n;
748
+ const count = s.variableWidth ? n * 2 : this._showC + s.slidesToScroll + (s.centerMode ? 1 : 0);
749
+ this.cloneCount = count;
750
+ const mk = (idx) => {
751
+ const base = this._units[((idx % n) + n) % n];
752
+ const c = base.cloneNode(true);
753
+ c.removeAttribute('id');
754
+ c.querySelectorAll('[id]').forEach((x) => x.removeAttribute('id'));
755
+ c.classList.remove('mozert-active', 'mozert-center');
756
+ c.classList.add('mozert-cloned');
757
+ c.setAttribute('aria-hidden', 'true');
758
+ c.removeAttribute('role');
759
+ c.removeAttribute('aria-label');
760
+ c.removeAttribute('aria-roledescription');
761
+ c.querySelectorAll(FOCUSABLE).forEach((f) => {
762
+ f.setAttribute('data-mozert-tabindex', f.getAttribute('tabindex') || '');
763
+ f.setAttribute('tabindex', '-1');
764
+ });
765
+ return c;
766
+ };
767
+ const before = [];
768
+ const after = [];
769
+ for (let i = -count; i < 0; i++) before.push(mk(i));
770
+ for (let i = 0; i < count; i++) after.push(mk(n + i));
771
+ const first = this._units[0];
772
+ before.forEach((c) => this.track.insertBefore(c, first));
773
+ after.forEach((c) => this.track.appendChild(c));
774
+ this.dom = before.concat(this._units, after);
775
+ }
776
+
777
+ _teardown() {
778
+ this._mi.forEach((m) => m.destroy && m.destroy());
779
+ this._mi = [];
780
+ this._fxHooks = [];
781
+ this._release = null;
782
+ this._lockDrag = false;
783
+ this._off.forEach((fn) => fn());
784
+ this._off = [];
785
+ clearTimeout(this._animTimer);
786
+ clearTimeout(this._apTimer);
787
+ if (this._raf) cancelAnimationFrame(this._raf);
788
+ this._raf = 0;
789
+ this.animating = false;
790
+ const el = this.el;
791
+
792
+ (this._items || []).forEach((it) => {
793
+ this._restoreItem(it);
794
+ // Slides removed through remove()/removeAll() must not come back.
795
+ if (this._sources.indexOf(it) > -1) el.appendChild(it);
796
+ });
797
+ (this._units || []).forEach((u) => {
798
+ if (this._items.indexOf(u) === -1) this._stash.delete(u);
799
+ });
800
+ if (this.list && this.list.parentNode) this.list.parentNode.removeChild(this.list);
801
+ (this._navNodes || []).forEach((fn) => fn());
802
+ this._navNodes = [];
803
+
804
+ Array.from(el.classList)
805
+ .filter((c) => c.indexOf('mozert') === 0)
806
+ .forEach((c) => el.classList.remove(c));
807
+ el.removeAttribute('role');
808
+ el.removeAttribute('aria-roledescription');
809
+ if (this._origDir === null) el.removeAttribute('dir');
810
+ else el.setAttribute('dir', this._origDir);
811
+ this.dom = [];
812
+ this.active = false;
813
+ }
814
+
815
+ /* ------------------------------------------------------------------ */
816
+ /* Measuring */
817
+ /* ------------------------------------------------------------------ */
818
+
819
+ _layout() {
820
+ const s = this.s;
821
+ const v = !!s.vertical;
822
+ const list = this.list;
823
+ const track = this.track;
824
+ const dom = this.dom;
825
+ this._calcShow();
826
+ const show = this._show;
827
+ const gap = s.spaceBetween;
828
+ const pad = this._edgePad();
829
+ const mProp = v ? 'marginBottom' : s.rtl ? 'marginLeft' : 'marginRight';
830
+ list.style.padding = v ? pad + 'px 0' : '0 ' + pad + 'px';
831
+
832
+ if (v) {
833
+ dom.forEach((e) => (e.style.height = ''));
834
+ track.style.height = '';
835
+ let h = 0;
836
+ this._units.forEach((u) => (h = Math.max(h, u.offsetHeight)));
837
+ this.slideSize = h;
838
+ dom.forEach((e) => {
839
+ e.style.height = h + 'px';
840
+ e.style[mProp] = gap ? gap + 'px' : '';
841
+ });
842
+ this.Lc = h * show + gap * (show - 1);
843
+ list.style.height = this.Lc + 2 * pad + 'px';
844
+ this.trackLen = (h + gap) * dom.length - gap;
845
+ track.style.height = this.trackLen + gap + 'px';
846
+ } else {
847
+ this.Lc = Math.max(0, list.clientWidth - 2 * pad);
848
+ if (!s.variableWidth) {
849
+ const w = s.slideWidth ? parseFloat(s.slideWidth) : (this.Lc - gap * (show - 1)) / show;
850
+ this.slideSize = w;
851
+ dom.forEach((e) => {
852
+ e.style.width = w + 'px';
853
+ e.style[mProp] = gap ? gap + 'px' : '';
854
+ });
855
+ this.trackLen = (w + gap) * dom.length - gap;
856
+ track.style.width = this.trackLen + gap + 'px';
857
+ } else {
858
+ dom.forEach((e) => (e.style[mProp] = gap ? gap + 'px' : ''));
859
+ track.style.width = '100000px';
860
+ }
861
+ }
862
+
863
+ const tr = track.getBoundingClientRect();
864
+ this._mags = [];
865
+ this._sizes = [];
866
+ dom.forEach((e) => {
867
+ const r = e.getBoundingClientRect();
868
+ if (v) {
869
+ this._mags.push(r.top - tr.top);
870
+ this._sizes.push(r.height);
871
+ } else if (s.rtl) {
872
+ this._mags.push(tr.right - r.right);
873
+ this._sizes.push(r.width);
874
+ } else {
875
+ this._mags.push(r.left - tr.left);
876
+ this._sizes.push(r.width);
877
+ }
878
+ });
879
+ if (!v && s.variableWidth) {
880
+ let end = 0;
881
+ this._mags.forEach((m, i) => (end = Math.max(end, m + this._sizes[i])));
882
+ this.trackLen = Math.ceil(end);
883
+ track.style.width = this.trackLen + 'px';
884
+ }
885
+ const cc = this.cloneCount;
886
+ const last = cc + this.n - 1;
887
+ this._realLen = this.n ? this._mags[last] + this._sizes[last] - this._mags[cc] : 0;
888
+
889
+ if (s.stacked) {
890
+ const sz = this.slideSize;
891
+ dom.forEach((e, i) => {
892
+ e.style.position = 'relative';
893
+ if (v) e.style.top = -i * sz + 'px';
894
+ else e.style.left = (s.rtl ? i * sz : -i * sz) + 'px';
895
+ });
896
+ }
897
+ this._adaptHeight();
898
+ this._arrowY();
899
+ this.maxIndex = this._maxIndex();
900
+ }
901
+
902
+ _computeCan() {
903
+ const s = this.s;
904
+ if (s.variableWidth) return this.n > 1 && this._realLen > this.Lc + 1;
905
+ return this.n > this._show + 1e-6;
906
+ }
907
+
908
+ _maxIndex() {
909
+ const s = this.s;
910
+ if (s.infinite || s.stacked) return Math.max(0, this.n - 1);
911
+ if (s.centerMode) return Math.max(0, this.n - 1);
912
+ if (s.variableWidth) {
913
+ const need = this._realLen - this.Lc - 0.5;
914
+ if (need <= 0) return 0;
915
+ for (let i = 0; i < this.n; i++) if (this._mags[this.cloneCount + i] - this._mags[this.cloneCount] >= need) return i;
916
+ return this.n - 1;
917
+ }
918
+ return Math.max(0, Math.ceil(this.n - this._show - 1e-6));
919
+ }
920
+
921
+ _adaptHeight() {
922
+ const s = this.s;
923
+ if (!s.adaptiveHeight || s.vertical || this._show !== 1) return;
924
+ const e = this.dom[this.cloneCount + this._logical] || this.dom[0];
925
+ if (!e) return;
926
+ this.list.style.transition = s.speed > 0 ? 'height ' + s.speed + 'ms ' + s.cssEase : 'none';
927
+ this.list.style.height = e.offsetHeight + 'px';
928
+ this._arrowY(e.offsetHeight);
929
+ }
930
+
931
+ /** Keeps arrows centred on the slides, not on the whole slider (dots/pagination sit below the list). */
932
+ _arrowY(h) {
933
+ const l = this.list;
934
+ if (!l || this.s.vertical) return;
935
+ const hh = h === undefined ? l.offsetHeight : h;
936
+ this.el.style.setProperty('--mozert-arrow-y', l.offsetTop + hh / 2 + 'px');
937
+ }
938
+
939
+ _norm(i) {
940
+ const n = this.n;
941
+ if (this.s.infinite || this.s.stacked) return ((i % n) + n) % n;
942
+ return clamp(i, 0, this.maxIndex);
943
+ }
944
+
945
+ _pos(L) {
946
+ const s = this.s;
947
+ const k = this.cloneCount + L;
948
+ if (this._mags[k] === undefined) return this._t;
949
+ let mag = this._mags[k];
950
+ let c = 0;
951
+ if (s.centerMode) c = (this.Lc - this._sizes[k]) / 2;
952
+ else if (!s.infinite) mag = Math.min(mag, Math.max(0, this.trackLen - this.Lc));
953
+ const dir = s.rtl && !s.vertical ? -1 : 1;
954
+ return dir * (c - mag);
955
+ }
956
+
957
+ /* ------------------------------------------------------------------ */
958
+ /* Movement */
959
+ /* ------------------------------------------------------------------ */
960
+
961
+ _paint(val, animate) {
962
+ const s = this.s;
963
+ const track = this.track;
964
+ this._cur = val;
965
+ if (s.useTransform) track.style.transform = s.vertical ? 'translate3d(0,' + val + 'px,0)' : 'translate3d(' + val + 'px,0,0)';
966
+ else track.style[s.vertical ? 'top' : 'left'] = val + 'px';
967
+ if (this._fxHooks.length) this._fx(this._pvFromT(val), !!animate);
968
+ }
969
+
970
+ /** Visual position in slide units (0 = first slide at the anchor), used by effects, scrollbar and parallax. */
971
+ _pvFromT(t) {
972
+ const s = this.s;
973
+ if (s.variableWidth || !this._mags || !this._mags.length) return this._logical;
974
+ const dir = s.rtl && !s.vertical ? -1 : 1;
975
+ const c = s.centerMode ? (this.Lc - this.slideSize) / 2 : 0;
976
+ const pitch = this.slideSize + s.spaceBetween;
977
+ return (c - dir * t - this._mags[this.cloneCount]) / (pitch || 1);
978
+ }
979
+
980
+ _fx(pv, animate) {
981
+ for (let i = 0; i < this._fxHooks.length; i++) this._fxHooks[i](pv, animate, this.s.speed);
982
+ }
983
+
984
+ /** For modules: run fn(pv, animate, speed) whenever the visual position changes. */
985
+ _onFx(fn) {
986
+ this._fxHooks.push(fn);
987
+ }
988
+
989
+ /** For modules: subscribe to a slider event for the life of this build. */
990
+ _sub(name, fn) {
991
+ this.on(name, fn);
992
+ this._off.push(() => this.off(name, fn));
993
+ }
994
+
995
+ _user() {
996
+ if (this.s.disableOnInteraction && this._autoOn) this.pause();
997
+ }
998
+
999
+ _setPos(t, animate, opts) {
1000
+ const s = this.s;
1001
+ const track = this.track;
1002
+ if (this._raf) cancelAnimationFrame(this._raf);
1003
+ this._raf = 0;
1004
+ if (animate && s.speed > 0) {
1005
+ const dur = opts && opts.duration ? opts.duration : s.speed;
1006
+ if (s.useCSS) {
1007
+ track.style.transition = (s.useTransform ? 'transform' : s.vertical ? 'top' : 'left') + ' ' + dur + 'ms ' + (opts && opts.ease ? opts.ease : s.cssEase);
1008
+ this._paint(t, true);
1009
+ } else {
1010
+ track.style.transition = 'none';
1011
+ const from = this._cur;
1012
+ const ease = typeof s.easing === 'function' ? s.easing : EASINGS[s.easing] || EASINGS.linear;
1013
+ const t0 = performance.now();
1014
+ const step = (now) => {
1015
+ const p = Math.min(1, (now - t0) / s.speed);
1016
+ this._paint(from + (t - from) * ease(p));
1017
+ this._raf = p < 1 ? requestAnimationFrame(step) : 0;
1018
+ };
1019
+ this._raf = requestAnimationFrame(step);
1020
+ }
1021
+ } else {
1022
+ track.style.transition = 'none';
1023
+ this._paint(t);
1024
+ void track.offsetWidth;
1025
+ }
1026
+ this._t = t;
1027
+ }
1028
+
1029
+ _paintCurrent(animate) {
1030
+ if (this.s.stacked) this._fx(this._logical, false);
1031
+ else this._setPos(this._pos(this._logical), animate);
1032
+ this._updateSlideClasses(this._logical);
1033
+ this._adaptHeight();
1034
+ }
1035
+
1036
+ _snapBack() {
1037
+ if (this.s.stacked) this._fx(this._logical, true);
1038
+ else this._setPos(this._pos(this._logical), true);
1039
+ }
1040
+
1041
+ _slideTo(target, opts = {}) {
1042
+ const s = this.s;
1043
+ if (!this.active) return false;
1044
+ if (!this.canNav) {
1045
+ // A strip that shows every slide cannot move, but a synced partner can still change which slide is highlighted.
1046
+ if (opts.fromSync) {
1047
+ const sel = opts.sel !== undefined && opts.sel !== 0 ? opts.sel : null;
1048
+ if (sel !== this._sel) {
1049
+ this._sel = sel;
1050
+ this._updateSlideClasses(this._logical);
1051
+ }
1052
+ }
1053
+ return false;
1054
+ }
1055
+ if (this.animating && !opts.fromSync && s.waitForAnimate) return false;
1056
+ let next = target;
1057
+ if (!s.infinite || s.stacked) next = s.stacked ? this._norm(next) : clamp(next, 0, this.maxIndex);
1058
+ const real = this._norm(next);
1059
+ // A synced partner follows the requested slide even when this slider cannot scroll that far
1060
+ // (a thumbnail strip stops at its last full page, but the main slider should still go to the clicked one).
1061
+ const want = s.infinite || s.stacked ? real : clamp(target, 0, this.n - 1);
1062
+ if (next === this.current && !this.animating && !opts.force) {
1063
+ if (opts.fromSync) {
1064
+ // Already at the clamped position: only move the highlight to the slide the partner asked for.
1065
+ const sel = opts.sel !== undefined && opts.sel !== next ? opts.sel : null;
1066
+ if (sel !== this._sel) {
1067
+ this._sel = sel;
1068
+ this._updateSlideClasses(this._logical);
1069
+ }
1070
+ } else {
1071
+ // The strip already sits on this index (its last full page), but the partner may still be elsewhere.
1072
+ if (want !== next) {
1073
+ this._sel = want;
1074
+ this._updateSlideClasses(this._logical);
1075
+ }
1076
+ this._syncNav(want);
1077
+ }
1078
+ return false;
1079
+ }
1080
+
1081
+ const old = this.current;
1082
+ this.emit('beforeChange', old, real);
1083
+ this._sel = opts.fromSync ? (opts.sel !== undefined && opts.sel !== next ? opts.sel : null) : want !== next ? want : null;
1084
+ if (!opts.fromSync) this._syncNav(want);
1085
+ clearTimeout(this._animTimer);
1086
+ this.current = real;
1087
+ this._logical = next;
1088
+ this._updateNav();
1089
+ this._updateSlideClasses(next);
1090
+ this._lazy();
1091
+
1092
+ const animate = opts.animate !== false;
1093
+ if (s.stacked) this._fx(next, animate && s.speed > 0);
1094
+ else this._setPos(this._pos(next), animate);
1095
+ if (!animate || s.speed <= 0) {
1096
+ this._finish();
1097
+ } else {
1098
+ this.animating = true;
1099
+ this._animTimer = setTimeout(() => this._finish(), s.speed);
1100
+ }
1101
+ return true;
1102
+ }
1103
+
1104
+ _finish() {
1105
+ this.animating = false;
1106
+ if (this._logical !== this.current) {
1107
+ this._logical = this.current;
1108
+ if (!this.s.stacked) this._setPos(this._pos(this.current), false);
1109
+ this._updateSlideClasses(this._logical);
1110
+ }
1111
+ this._adaptHeight();
1112
+ this.emit('afterChange', this.current);
1113
+ this._schedule();
1114
+ }
1115
+
1116
+ _updateSlideClasses(L) {
1117
+ const s = this.s;
1118
+ const show = this._show;
1119
+ const showC = this._showC;
1120
+ const half = Math.floor(showC / 2);
1121
+ const t = s.stacked ? 0 : this._pos(L);
1122
+ this.dom.forEach((e, k) => {
1123
+ const l = k - this.cloneCount;
1124
+ let active;
1125
+ let centered = false;
1126
+ if (s.stacked) {
1127
+ active = l === L;
1128
+ } else if (s.variableWidth) {
1129
+ const d = s.rtl ? this._mags[k] - t : this._mags[k] + t;
1130
+ active = d + this._sizes[k] > 0.5 && d < this.Lc - 0.5;
1131
+ centered = s.centerMode && l === L;
1132
+ } else if (s.centerMode) {
1133
+ active = l >= L - half && l <= L + half - (showC % 2 === 0 ? 1 : 0);
1134
+ centered = l === L;
1135
+ } else {
1136
+ active = l >= L && l <= L + showC - 1;
1137
+ }
1138
+ e.classList.toggle('mozert-active', active);
1139
+ e.classList.toggle('mozert-center', centered);
1140
+ e.classList.toggle('mozert-current', l === (this._sel != null ? this._sel : L));
1141
+ e.setAttribute('aria-hidden', active ? 'false' : 'true');
1142
+ if (s.stacked) e.style.zIndex = l === L ? s.zIndex : s.zIndex - 2;
1143
+ if (!e.classList.contains('mozert-cloned')) {
1144
+ e.querySelectorAll(FOCUSABLE).forEach((f) => {
1145
+ if (active) {
1146
+ if (f.hasAttribute('data-mozert-tabindex')) {
1147
+ const v = f.getAttribute('data-mozert-tabindex');
1148
+ if (v === '') f.removeAttribute('tabindex');
1149
+ else f.setAttribute('tabindex', v);
1150
+ f.removeAttribute('data-mozert-tabindex');
1151
+ }
1152
+ } else if (!f.hasAttribute('data-mozert-tabindex')) {
1153
+ f.setAttribute('data-mozert-tabindex', f.getAttribute('tabindex') || '');
1154
+ f.setAttribute('tabindex', '-1');
1155
+ }
1156
+ });
1157
+ }
1158
+ });
1159
+ }
1160
+
1161
+ /* ------------------------------------------------------------------ */
1162
+ /* Arrows and dots */
1163
+ /* ------------------------------------------------------------------ */
1164
+
1165
+ _buildArrows() {
1166
+ this._navNodes = this._navNodes || [];
1167
+ this._arrows = { prev: null, next: null };
1168
+ this._prev = this._next = null;
1169
+ const s = this.s;
1170
+ if (!s.arrows || !this.canNav) return;
1171
+ const mount = resolveNode(s.appendArrows) || this.el;
1172
+ const make = (val, html, cls, handler) => {
1173
+ const node = resolveNode(val) || htmlToEl(html);
1174
+ const created = !(val && !(typeof val === 'string' && val.trim().charAt(0) === '<'));
1175
+ node.classList.add('mozert-arrow', cls);
1176
+ if (!node.parentNode || created) mount.appendChild(node);
1177
+ const lab = this._label(cls === 'mozert-prev' ? 'prev' : 'next');
1178
+ if (!val || (!node.getAttribute('aria-label') && !node.textContent.trim())) node.setAttribute('aria-label', lab);
1179
+ node.addEventListener('click', handler);
1180
+ this._navNodes.push(() => {
1181
+ node.removeEventListener('click', handler);
1182
+ node.classList.remove('mozert-arrow', cls, 'mozert-disabled');
1183
+ node.removeAttribute('aria-disabled');
1184
+ if (created && node.parentNode) node.parentNode.removeChild(node);
1185
+ });
1186
+ return node;
1187
+ };
1188
+ this._arrows.prev = make(s.prevArrow, DEFAULT_PREV, 'mozert-prev', (e) => {
1189
+ e.preventDefault();
1190
+ this._user();
1191
+ this.prev();
1192
+ });
1193
+ this._arrows.next = make(s.nextArrow, DEFAULT_NEXT, 'mozert-next', (e) => {
1194
+ e.preventDefault();
1195
+ this._user();
1196
+ this.next();
1197
+ });
1198
+ this._prev = this._arrows.prev;
1199
+ this._next = this._arrows.next;
1200
+ }
1201
+
1202
+ _dotCount() {
1203
+ const s = this.s;
1204
+ if (!this.canNav) return 0;
1205
+ if (s.infinite) return Math.ceil(this.n / s.slidesToScroll);
1206
+ return Math.ceil(this.maxIndex / s.slidesToScroll) + 1;
1207
+ }
1208
+
1209
+ _buildDots() {
1210
+ this._dots = null;
1211
+ const s = this.s;
1212
+ if (!s.dots || !this.canNav) return;
1213
+ const count = this._dotCount();
1214
+ const ul = document.createElement('ul');
1215
+ ul.className = s.dotsClass;
1216
+ ul.setAttribute('role', 'tablist');
1217
+ for (let i = 0; i < count; i++) {
1218
+ const li = document.createElement('li');
1219
+ li.setAttribute('role', 'presentation');
1220
+ let inner = typeof s.customPaging === 'function' ? s.customPaging(this, i) : null;
1221
+ if (typeof inner === 'string') inner = htmlToEl(inner.trim().charAt(0) === '<' ? inner : '<button type="button">' + inner + '</button>');
1222
+ if (!isEl(inner)) {
1223
+ inner = document.createElement('button');
1224
+ inner.type = 'button';
1225
+ inner.setAttribute('aria-label', this._label('goTo', { n: i + 1 }));
1226
+ }
1227
+ inner.setAttribute('role', 'tab');
1228
+ li.appendChild(inner);
1229
+ li.addEventListener('click', (e) => {
1230
+ e.preventDefault();
1231
+ this._user();
1232
+ this._slideTo(this._dotTarget(i));
1233
+ });
1234
+ ul.appendChild(li);
1235
+ }
1236
+ (resolveNode(s.appendDots) || this.el).appendChild(ul);
1237
+ this._dots = ul;
1238
+ this._navNodes.push(() => ul.parentNode && ul.parentNode.removeChild(ul));
1239
+ if (s.pauseOnDotsHover) {
1240
+ const a = () => this._holdOn('dots');
1241
+ const b = () => this._holdOff('dots');
1242
+ ul.addEventListener('mouseenter', a);
1243
+ ul.addEventListener('mouseleave', b);
1244
+ }
1245
+ }
1246
+
1247
+ _dotTarget(i) {
1248
+ const s = this.s;
1249
+ const t = i * s.slidesToScroll;
1250
+ return s.infinite ? t : Math.min(t, this.maxIndex);
1251
+ }
1252
+
1253
+ _updateNav() {
1254
+ const s = this.s;
1255
+ if (this._dots) {
1256
+ const total = this._dots.children.length;
1257
+ const idx = s.infinite ? Math.floor(this.current / s.slidesToScroll) : Math.ceil(this.current / s.slidesToScroll);
1258
+ Array.from(this._dots.children).forEach((li, i) => {
1259
+ const on = i === Math.min(total - 1, idx);
1260
+ li.classList.toggle('mozert-active', on);
1261
+ if (li.firstElementChild) li.firstElementChild.setAttribute('aria-selected', on ? 'true' : 'false');
1262
+ });
1263
+ }
1264
+ const { prev, next } = this._arrows || {};
1265
+ if (!s.infinite && prev && next) {
1266
+ const setD = (node, d) => {
1267
+ node.classList.toggle('mozert-disabled', d);
1268
+ node.setAttribute('aria-disabled', d ? 'true' : 'false');
1269
+ if ('disabled' in node) node.disabled = d;
1270
+ };
1271
+ setD(prev, this.current <= 0);
1272
+ setD(next, this.current >= this.maxIndex);
1273
+ }
1274
+ }
1275
+
1276
+ /* ------------------------------------------------------------------ */
1277
+ /* Events: drag, keyboard, hover, focus */
1278
+ /* ------------------------------------------------------------------ */
1279
+
1280
+ _axis() {
1281
+ const s = this.s;
1282
+ if (s.vertical) return s.verticalSwiping ? 'y' : null;
1283
+ return 'x';
1284
+ }
1285
+
1286
+ _on(target, type, fn, opts) {
1287
+ target.addEventListener(type, fn, opts);
1288
+ this._off.push(() => target.removeEventListener(type, fn, opts));
1289
+ }
1290
+
1291
+ _bindEvents() {
1292
+ const s = this.s;
1293
+ const el = this.el;
1294
+ const list = this.list;
1295
+ const axis = this._axis();
1296
+ list.style.touchAction = axis === 'x' ? 'pan-y' : axis === 'y' ? 'pan-x' : '';
1297
+ if (s.accessibility) list.setAttribute('tabindex', '0');
1298
+
1299
+ // Keyboard
1300
+ this._on(el, 'keydown', (e) => {
1301
+ if (!s.accessibility || !this.canNav) return;
1302
+ const t = e.target;
1303
+ if (t && (/^(INPUT|TEXTAREA|SELECT)$/.test(t.tagName) || t.isContentEditable)) return;
1304
+ const rtl = s.rtl && !s.vertical;
1305
+ let handled = true;
1306
+ if (e.key === 'ArrowLeft' || (s.vertical && e.key === 'ArrowUp')) rtl ? this.next() : this.prev();
1307
+ else if (e.key === 'ArrowRight' || (s.vertical && e.key === 'ArrowDown')) rtl ? this.prev() : this.next();
1308
+ else if (e.key === 'Home') this.goTo(0);
1309
+ else if (e.key === 'End') this.goTo(this.n - 1);
1310
+ else handled = false;
1311
+ if (handled) e.preventDefault();
1312
+ });
1313
+
1314
+ // Pause on hover/focus
1315
+ if (s.pauseOnHover) {
1316
+ this._on(el, 'mouseenter', () => this._holdOn('hover'));
1317
+ this._on(el, 'mouseleave', () => this._holdOff('hover'));
1318
+ }
1319
+ if (s.pauseOnFocus) {
1320
+ this._on(el, 'focusin', () => this._holdOn('focus'));
1321
+ this._on(el, 'focusout', (e) => {
1322
+ if (!el.contains(e.relatedTarget)) this._holdOff('focus');
1323
+ });
1324
+ }
1325
+
1326
+ // Click handling (select-to-focus, suppress after drag)
1327
+ this._on(
1328
+ list,
1329
+ 'click',
1330
+ (e) => {
1331
+ if (this._dragged) {
1332
+ e.preventDefault();
1333
+ e.stopPropagation();
1334
+ this._dragged = false;
1335
+ return;
1336
+ }
1337
+ if (s.focusOnSelect && (this.canNav || s.asNavFor)) {
1338
+ const slide = e.target.closest ? e.target.closest('.mozert-slide') : null;
1339
+ if (slide && this.track.contains(slide)) {
1340
+ const i = parseInt(slide.getAttribute('data-mozert-index'), 10);
1341
+ if (!isNaN(i)) {
1342
+ if (this.canNav) this.goTo(i);
1343
+ else {
1344
+ // Nothing to scroll: just highlight the clicked slide and tell the partner.
1345
+ this._sel = i;
1346
+ this._updateSlideClasses(this._logical);
1347
+ this._syncNav(i);
1348
+ }
1349
+ }
1350
+ }
1351
+ }
1352
+ },
1353
+ true
1354
+ );
1355
+ this._on(list, 'dragstart', (e) => e.preventDefault());
1356
+ this._on(list, 'selectstart', (e) => {
1357
+ if (this._pointerDown) e.preventDefault();
1358
+ });
1359
+
1360
+ // Images that load late change heights
1361
+ this._on(
1362
+ this.track,
1363
+ 'load',
1364
+ () => {
1365
+ if (!this.active) return;
1366
+ clearTimeout(this._imgTimer);
1367
+ this._imgTimer = setTimeout(() => this.setPosition(), 30);
1368
+ },
1369
+ true
1370
+ );
1371
+
1372
+ if (axis && this.canNav) this._bindDrag(axis);
1373
+ }
1374
+
1375
+ _bindDrag(axis) {
1376
+ const s = this.s;
1377
+ const list = this.list;
1378
+ let d = null;
1379
+
1380
+ const onDown = (e) => {
1381
+ if (this._lockDrag) return;
1382
+ if (this.animating && s.waitForAnimate) return;
1383
+ if (e.pointerType === 'mouse') {
1384
+ if (e.button !== 0 || !s.draggable) return;
1385
+ } else if (!s.swipe) {
1386
+ return;
1387
+ }
1388
+ this._dragged = false;
1389
+ this._pointerDown = true;
1390
+ d = { id: e.pointerId, x: e.clientX, y: e.clientY, started: false, base: this._t, delta: 0, samples: [] };
1391
+ window.addEventListener('pointermove', onMove);
1392
+ window.addEventListener('pointerup', onUp);
1393
+ window.addEventListener('pointercancel', onCancel);
1394
+ };
1395
+
1396
+ const limits = () => {
1397
+ const a = this._pos(0);
1398
+ const b = this._pos(this.maxIndex);
1399
+ return [Math.min(a, b), Math.max(a, b)];
1400
+ };
1401
+
1402
+ const onMove = (e) => {
1403
+ if (!d || e.pointerId !== d.id) return;
1404
+ if (this._lockDrag && !d.started) return;
1405
+ const dx = e.clientX - d.x;
1406
+ const dy = e.clientY - d.y;
1407
+ const main = (axis === 'x' ? dx : dy) * s.touchRatio;
1408
+ const cross = axis === 'x' ? dy : dx;
1409
+ if (!d.started) {
1410
+ if (Math.abs(main) < s.dragThreshold || Math.abs(main) < Math.abs(cross)) return;
1411
+ d.started = true;
1412
+ this.el.classList.add('mozert-dragging');
1413
+ this._holdOn('drag');
1414
+ try {
1415
+ list.setPointerCapture(e.pointerId);
1416
+ } catch (err) {
1417
+ /* ignore */
1418
+ }
1419
+ this.track.style.transition = 'none';
1420
+ }
1421
+ d.delta = main;
1422
+ d.samples.push({ t: performance.now(), v: main });
1423
+ if (d.samples.length > 6) d.samples.shift();
1424
+ if (!s.touchMove) return;
1425
+ if (s.stacked) {
1426
+ const sgn = s.rtl && !s.vertical ? 1 : -1;
1427
+ let pv = this._logical + (sgn * main) / (axis === 'x' ? this.list.clientWidth : this.list.clientHeight);
1428
+ if (!s.infinite) {
1429
+ if (pv < 0) pv *= s.edgeFriction;
1430
+ else if (pv > this.maxIndex) pv = this.maxIndex + (pv - this.maxIndex) * s.edgeFriction;
1431
+ }
1432
+ this._fx(pv, false);
1433
+ return;
1434
+ }
1435
+ let raw = d.base + main;
1436
+ if (!s.infinite) {
1437
+ const [lo, hi] = limits();
1438
+ if (raw > hi) raw = hi + (raw - hi) * s.edgeFriction;
1439
+ else if (raw < lo) raw = lo - (lo - raw) * s.edgeFriction;
1440
+ }
1441
+ this._paint(raw);
1442
+ };
1443
+
1444
+ const end = () => {
1445
+ window.removeEventListener('pointermove', onMove);
1446
+ window.removeEventListener('pointerup', onUp);
1447
+ window.removeEventListener('pointercancel', onCancel);
1448
+ this._pointerDown = false;
1449
+ this.el.classList.remove('mozert-dragging');
1450
+ this._holdOff('drag');
1451
+ };
1452
+
1453
+ const onCancel = () => {
1454
+ if (!d) return;
1455
+ const started = d.started;
1456
+ d = null;
1457
+ end();
1458
+ if (started) this._snapBack();
1459
+ };
1460
+
1461
+ const onUp = (e) => {
1462
+ if (!d || e.pointerId !== d.id) return;
1463
+ const st = d;
1464
+ d = null;
1465
+ end();
1466
+ if (!st.started) return;
1467
+ this._dragged = true;
1468
+ const delta = st.delta;
1469
+ const size = axis === 'x' ? this.list.clientWidth : this.list.clientHeight;
1470
+ this._user();
1471
+ if (this._release && this._release(st, size, axis)) return;
1472
+ const fingerDir = axis === 'x' ? (delta < 0 ? 'left' : 'right') : delta < 0 ? 'up' : 'down';
1473
+ const rtl = s.rtl && !s.vertical;
1474
+ const forward = rtl ? delta > 0 : delta < 0;
1475
+ if (Math.abs(delta) < size / s.touchThreshold) {
1476
+ this._snapBack();
1477
+ return;
1478
+ }
1479
+ this.emit('swipe', fingerDir);
1480
+ let target;
1481
+ if (s.swipeToSlide) {
1482
+ const raw = st.base + delta;
1483
+ let best = this.current;
1484
+ let bestD = Infinity;
1485
+ const lo = s.infinite ? -this.cloneCount : 0;
1486
+ const hi = s.infinite ? this.n + this.cloneCount - 1 : this.maxIndex;
1487
+ for (let i = lo; i <= hi; i++) {
1488
+ const dist = Math.abs(this._pos(i) - raw);
1489
+ if (dist < bestD) {
1490
+ bestD = dist;
1491
+ best = i;
1492
+ }
1493
+ }
1494
+ target = best;
1495
+ } else {
1496
+ target = this.current + (forward ? 1 : -1) * s.slidesToScroll;
1497
+ }
1498
+ if (!s.infinite) {
1499
+ const clamped = clamp(target, 0, this.maxIndex);
1500
+ if (clamped === this.current) {
1501
+ this.emit('edge', fingerDir);
1502
+ this._snapBack();
1503
+ return;
1504
+ }
1505
+ target = clamped;
1506
+ }
1507
+ if (target === this.current) {
1508
+ this._snapBack();
1509
+ return;
1510
+ }
1511
+ this._slideTo(target);
1512
+ };
1513
+
1514
+ this._on(list, 'pointerdown', onDown);
1515
+ this._off.push(() => {
1516
+ window.removeEventListener('pointermove', onMove);
1517
+ window.removeEventListener('pointerup', onUp);
1518
+ window.removeEventListener('pointercancel', onCancel);
1519
+ });
1520
+ }
1521
+
1522
+ /* ------------------------------------------------------------------ */
1523
+ /* Autoplay */
1524
+ /* ------------------------------------------------------------------ */
1525
+
1526
+ _holdOn(reason) {
1527
+ this._pause.add(reason);
1528
+ clearTimeout(this._apTimer);
1529
+ }
1530
+
1531
+ _holdOff(reason) {
1532
+ this._pause.delete(reason);
1533
+ this._schedule();
1534
+ }
1535
+
1536
+ _live() {
1537
+ if (this.track) this.track.setAttribute('aria-live', this._autoOn && !this._pause.has('user') ? 'off' : 'polite');
1538
+ }
1539
+
1540
+ _schedule() {
1541
+ clearTimeout(this._apTimer);
1542
+ cancelAnimationFrame(this._apRaf);
1543
+ if (!this.active || !this.canNav || !this._autoOn) return;
1544
+ if (this._pause.size) return;
1545
+ const delay = this.s.autoplaySpeed;
1546
+ this._apTimer = setTimeout(() => this._tick(), delay);
1547
+ const cb = this.options.on && this.options.on.autoplayTimeLeft;
1548
+ if ((this._handlers.autoplayTimeLeft && this._handlers.autoplayTimeLeft.length) || cb) {
1549
+ const t0 = performance.now();
1550
+ const loop = () => {
1551
+ const left = Math.max(0, delay - (performance.now() - t0));
1552
+ this.emit('autoplayTimeLeft', left, left / delay);
1553
+ if (left > 0) this._apRaf = requestAnimationFrame(loop);
1554
+ };
1555
+ loop();
1556
+ }
1557
+ }
1558
+
1559
+ _tick() {
1560
+ const s = this.s;
1561
+ if (this._pause.size || !this.active) return;
1562
+ if (this.animating) {
1563
+ this._schedule();
1564
+ return;
1565
+ }
1566
+ const rev = s.autoplayReverse;
1567
+ if (s.infinite) {
1568
+ if (rev) this.prev();
1569
+ else this.next();
1570
+ return;
1571
+ }
1572
+ if (s.stopOnLastSlide && (rev ? this.current <= 0 : this.current >= this.maxIndex)) {
1573
+ this.pause();
1574
+ return;
1575
+ }
1576
+ if (this.current >= this.maxIndex) this._dir = -1;
1577
+ else if (this.current <= 0) this._dir = 1;
1578
+ if ((this._dir === 1) !== !!rev) this.next();
1579
+ else this.prev();
1580
+ }
1581
+
1582
+ /* ------------------------------------------------------------------ */
1583
+ /* Syncing and lazy loading */
1584
+ /* ------------------------------------------------------------------ */
1585
+
1586
+ _navTargets() {
1587
+ const v = this.s.asNavFor;
1588
+ if (!v) return [];
1589
+ let els = [];
1590
+ if (v instanceof Mozert) return v.active && v !== this ? [v] : [];
1591
+ if (typeof v === 'string') els = Array.from(document.querySelectorAll(v));
1592
+ else if (isEl(v)) els = [v];
1593
+ else if (v.length) els = Array.from(v);
1594
+ return els.map((e) => registry.get(e)).filter((i) => i && i !== this && i.active);
1595
+ }
1596
+
1597
+ _syncNav(index) {
1598
+ this._navTargets().forEach((t) => t._slideTo(t.s.infinite ? index : clamp(index, 0, t.maxIndex), { fromSync: true, sel: index }));
1599
+ }
1600
+
1601
+ _lazy() {
1602
+ const s = this.s;
1603
+ if (!this.track || !this.n) return;
1604
+ const mode = s.lazyLoad;
1605
+ if (mode !== 'ondemand' && mode !== 'progressive') return;
1606
+ const from = this.current - (s.centerMode ? Math.floor(this._show / 2) : 0);
1607
+ const to = this.current + this._show - 1 + (s.centerMode ? Math.floor(this._show / 2) : 1);
1608
+ const seen = new Set();
1609
+ for (let i = from; i <= to; i++) {
1610
+ const r = ((i % this.n) + this.n) % this.n;
1611
+ if (!seen.has(r)) {
1612
+ seen.add(r);
1613
+ this._loadSlide(r);
1614
+ }
1615
+ }
1616
+ if (mode === 'progressive' && !this._progressing) this._progressive();
1617
+ }
1618
+
1619
+ _progressive() {
1620
+ this._progressing = true;
1621
+ const step = () => {
1622
+ if (!this.active) {
1623
+ this._progressing = false;
1624
+ return;
1625
+ }
1626
+ const next = this._units.findIndex((u) => u.querySelector('img[data-lazy]:not([data-mozert-loading]), [data-lazy-bg]:not([data-mozert-loading])'));
1627
+ if (next === -1) {
1628
+ this._progressing = false;
1629
+ return;
1630
+ }
1631
+ this._loadSlide(next, step);
1632
+ };
1633
+ step();
1634
+ }
1635
+
1636
+ _loadSlide(index, done) {
1637
+ const unit = this._units[index];
1638
+ if (!unit) return;
1639
+ const imgs = unit.querySelectorAll('img[data-lazy]');
1640
+ const bgs = unit.querySelectorAll('[data-lazy-bg]');
1641
+ if (!imgs.length && !bgs.length) {
1642
+ if (done) done();
1643
+ return;
1644
+ }
1645
+ let left = imgs.length + bgs.length;
1646
+ const fin = () => {
1647
+ if (--left === 0 && done) done();
1648
+ };
1649
+ bgs.forEach((el) => {
1650
+ if (el.hasAttribute('data-mozert-loading')) {
1651
+ fin();
1652
+ return;
1653
+ }
1654
+ el.setAttribute('data-mozert-loading', '');
1655
+ const src = el.getAttribute('data-lazy-bg');
1656
+ const k = el.getAttribute('data-mozert-bg');
1657
+ const apply = (n) => {
1658
+ n.style.backgroundImage = 'url("' + src.replace(/"/g, '%22') + '")';
1659
+ n.removeAttribute('data-lazy-bg');
1660
+ n.removeAttribute('data-mozert-loading');
1661
+ };
1662
+ const probe = new Image();
1663
+ probe.onload = () => {
1664
+ apply(el);
1665
+ this.dom.forEach((e) => {
1666
+ if (!e.classList.contains('mozert-cloned') || parseInt(e.getAttribute('data-mozert-index'), 10) !== index) return;
1667
+ const c = e.querySelector('[data-mozert-bg="' + k + '"]');
1668
+ if (c) apply(c);
1669
+ });
1670
+ this.emit('lazyLoaded', el, src);
1671
+ fin();
1672
+ };
1673
+ probe.onerror = () => {
1674
+ el.removeAttribute('data-lazy-bg');
1675
+ el.removeAttribute('data-mozert-loading');
1676
+ el.classList.add('mozert-lazy-error');
1677
+ this.emit('lazyLoadError', el, src);
1678
+ fin();
1679
+ };
1680
+ probe.src = src;
1681
+ });
1682
+ imgs.forEach((img) => {
1683
+ if (img.hasAttribute('data-mozert-loading')) {
1684
+ fin();
1685
+ return;
1686
+ }
1687
+ img.setAttribute('data-mozert-loading', '');
1688
+ img.classList.add('mozert-loading');
1689
+ const src = img.getAttribute('data-lazy');
1690
+ const srcset = img.getAttribute('data-lazy-srcset');
1691
+ const sizes = img.getAttribute('data-lazy-sizes');
1692
+ const probe = new Image();
1693
+ probe.onload = () => {
1694
+ if (srcset) img.srcset = srcset;
1695
+ if (sizes) img.sizes = sizes;
1696
+ img.src = src;
1697
+ img.removeAttribute('data-lazy');
1698
+ img.removeAttribute('data-mozert-loading');
1699
+ img.classList.remove('mozert-loading');
1700
+ img.classList.add('mozert-loaded');
1701
+ this._lazyClones(index, src, srcset, sizes, img);
1702
+ this.emit('lazyLoaded', img, src);
1703
+ fin();
1704
+ };
1705
+ probe.onerror = () => {
1706
+ img.removeAttribute('data-lazy');
1707
+ img.removeAttribute('data-mozert-loading');
1708
+ img.classList.remove('mozert-loading');
1709
+ img.classList.add('mozert-lazy-error');
1710
+ this.emit('lazyLoadError', img, src);
1711
+ fin();
1712
+ };
1713
+ if (srcset) probe.srcset = srcset;
1714
+ probe.src = src;
1715
+ });
1716
+ }
1717
+
1718
+ _lazyClones(index, src, srcset, sizes, realImg) {
1719
+ const imgsInUnit = Array.from(this._units[index].querySelectorAll('img'));
1720
+ const pos = imgsInUnit.indexOf(realImg);
1721
+ this.dom.forEach((e) => {
1722
+ if (!e.classList.contains('mozert-cloned') || parseInt(e.getAttribute('data-mozert-index'), 10) !== index) return;
1723
+ const c = e.querySelectorAll('img')[pos];
1724
+ if (!c) return;
1725
+ if (srcset) c.srcset = srcset;
1726
+ if (sizes) c.sizes = sizes;
1727
+ c.src = src;
1728
+ c.removeAttribute('data-lazy');
1729
+ c.classList.add('mozert-loaded');
1730
+ });
1731
+ }
1732
+ }
1733
+
1734
+ Mozert._mods = [];
1735
+ Mozert._boot = [];
1736
+ /** Register a module: { name, defaults?, create(slider) => { destroy() } | null }. */
1737
+ Mozert.use = function (mod) {
1738
+ if (mod && Mozert._mods.indexOf(mod) < 0) {
1739
+ Mozert._mods.push(mod);
1740
+ Object.keys(mod.defaults || {}).forEach((k) => {
1741
+ if (!(k in DEFAULTS)) DEFAULTS[k] = mod.defaults[k];
1742
+ });
1743
+ }
1744
+ return Mozert;
1745
+ };
1746
+ Mozert.version = VERSION;
1747
+ Mozert.defaults = DEFAULTS;
1748
+ Mozert._bridge = null;
1749
+
1750
+ /* ---- Mozert modules: effects, parallax, zoom, virtual, hash, scrollbar, pagination, free mode, mousewheel, marquee, video, fullscreen.
1751
+ * This file is a fragment: it runs inside the Mozert closure (see scripts/build.mjs) and uses `Mozert`. */
1752
+ (function () {
1753
+ const clampN = (v, a, b) => Math.min(b, Math.max(a, v));
1754
+ const num = (v, d) => (v === undefined || v === null || isNaN(parseFloat(v)) ? d : parseFloat(v));
1755
+ const objOpt = (v, d) => Object.assign({}, d, v && typeof v === 'object' ? v : {});
1756
+ const mk = (tag, cls, html) => {
1757
+ const e = document.createElement(tag);
1758
+ if (cls) e.className = cls;
1759
+ if (html != null) e.innerHTML = html;
1760
+ return e;
1761
+ };
1762
+ /** Signed distance (in slides) of slide i from visual position pv; wraps for loops. */
1763
+ const relOf = (i, pv, n, loop) => {
1764
+ let r = i - pv;
1765
+ if (loop) r = ((((r + n / 2) % n) + n) % n) - n / 2;
1766
+ return r;
1767
+ };
1768
+ const pct = (v, size) => (typeof v === 'string' && v.indexOf('%') > -1 ? (parseFloat(v) / 100) * size : parseFloat(v) || 0);
1769
+
1770
+ /* ------------------------------------------------------------ effects */
1771
+ const FX = ['coverflow', 'cube', 'flip', 'cards', 'creative', 'expand'];
1772
+ Mozert.use({
1773
+ name: 'effects',
1774
+ defaults: {
1775
+ coverflowEffect: { rotate: 45, depth: 120, scale: 1, stretch: 0, modifier: 1, lift: 0, fade: 0, shadow: true },
1776
+ expandEffect: { extra: 1.4 },
1777
+ cubeEffect: { shadow: true, slideShadows: true, perspective: 1200 },
1778
+ flipEffect: { perspective: 1400, limitRotation: true },
1779
+ cardsEffect: { perSlideOffset: 10, perSlideRotate: 2, rotate: true, visible: 4 },
1780
+ creativeEffect: {
1781
+ limitProgress: 1,
1782
+ prev: { translate: ['-120%', 0, -300], rotate: [0, 0, 0], opacity: 0, scale: 1 },
1783
+ next: { translate: ['120%', 0, 0], rotate: [0, 0, 0], opacity: 0, scale: 1 },
1784
+ },
1785
+ },
1786
+ create(sl) {
1787
+ const s = sl.s;
1788
+ const fx = s.effect;
1789
+ if (FX.indexOf(fx) < 0) return null;
1790
+ const o = objOpt(s[fx + 'Effect'], Mozert.defaults[fx + 'Effect']);
1791
+ const stacked = s.stacked;
1792
+ const n = sl.n;
1793
+ const list = sl.list;
1794
+ const prevR = [];
1795
+ const persp = fx === 'coverflow' ? 1200 : o.perspective || 1200;
1796
+ list.style.perspective = persp + 'px';
1797
+ sl.track.style.transformStyle = 'preserve-3d';
1798
+
1799
+ const slides = () => (stacked ? sl._units : sl.dom);
1800
+ const place = (e, tr, op, z, animate, speed, jump) => {
1801
+ e.style.transition = animate && !jump ? 'transform ' + speed + 'ms ' + s.cssEase + ', opacity ' + speed + 'ms ' + s.cssEase : 'none';
1802
+ e.style.transform = tr;
1803
+ e.style.opacity = op;
1804
+ e.style.zIndex = z;
1805
+ e.style.pointerEvents = op > 0.5 && z >= 0 ? '' : 'none';
1806
+ e.style.backfaceVisibility = fx === 'flip' || fx === 'cube' ? 'hidden' : '';
1807
+ };
1808
+
1809
+ const apply = (pv, animate, speed) => {
1810
+ const W = sl.slideSize || sl.list.clientWidth;
1811
+ const items = slides();
1812
+ const loop = stacked ? true : false;
1813
+ for (let k = 0; k < items.length; k++) {
1814
+ const i = stacked ? k : k - sl.cloneCount;
1815
+ const r = relOf(i, pv, n, loop);
1816
+ const jump = prevR[k] !== undefined && Math.abs(r - prevR[k]) > 1.5;
1817
+ prevR[k] = r;
1818
+ const e = items[k];
1819
+ const a = Math.abs(r);
1820
+ let tr = '';
1821
+ let op = 1;
1822
+ let z = Math.round(1000 - a * 10);
1823
+ if (fx === 'coverflow') {
1824
+ const rot = clampN(-r, -3, 3) * o.rotate * o.modifier;
1825
+ const tz = -a * o.depth;
1826
+ const sc = 1 - Math.min(a, 3) * (1 - o.scale) * 0.5;
1827
+ tr = 'translate3d(' + -r * o.stretch + 'px,' + -Math.min(a, 3) * o.lift + 'px,' + tz + 'px) rotateY(' + rot + 'deg) scale(' + sc + ')';
1828
+ op = a > 4 ? 0 : Math.max(0, 1 - Math.min(a, 4) * o.fade);
1829
+ } else if (fx === 'expand') {
1830
+ // The active card grows wider, its neighbours slide out of the way.
1831
+ // Both follow the same linear rule, so dragging stays continuous.
1832
+ const m = Math.min(1, a);
1833
+ const w0 = e.offsetWidth;
1834
+ const grow = o.extra * w0 * (1 - m);
1835
+ tr = 'translate3d(' + (r < 0 ? -1 : 1) * ((o.extra * w0) / 2) * m + 'px,0,0)';
1836
+ const card = e.firstElementChild;
1837
+ if (card) {
1838
+ card.style.transition = animate && !jump ? 'width ' + speed + 'ms ' + s.cssEase + ', margin-left ' + speed + 'ms ' + s.cssEase : 'none';
1839
+ card.style.width = w0 + grow + 'px';
1840
+ card.style.marginLeft = -grow / 2 + 'px';
1841
+ card.style.maxWidth = 'none';
1842
+ }
1843
+ } else if (fx === 'cube') {
1844
+ tr = 'translate3d(0,0,' + -W / 2 + 'px) rotateY(' + r * 90 + 'deg) translate3d(0,0,' + W / 2 + 'px)';
1845
+ op = a <= 1.01 ? 1 : 0;
1846
+ } else if (fx === 'flip') {
1847
+ const rr = o.limitRotation ? clampN(r, -1, 1) : r;
1848
+ tr = 'rotateY(' + rr * -180 + 'deg)';
1849
+ op = a < 1 ? 1 : 0;
1850
+ z = Math.round(1000 - a * 100);
1851
+ } else if (fx === 'cards') {
1852
+ const vis = o.visible;
1853
+ if (r < 0) {
1854
+ const t = Math.max(r, -1);
1855
+ tr = 'translate3d(' + t * 105 + '%,0,0)' + (o.rotate ? ' rotate(' + t * 12 + 'deg)' : '');
1856
+ op = 1 + t * 0.3;
1857
+ z = 2000;
1858
+ } else {
1859
+ tr = 'translate3d(0,' + r * -o.perSlideOffset + 'px,' + -r * 40 + 'px) scale(' + (1 - r * 0.05) + ')' + (o.rotate ? ' rotate(' + r * o.perSlideRotate * (i % 2 ? 1 : -1) + 'deg)' : '');
1860
+ op = r > vis ? 0 : 1;
1861
+ z = Math.round(1000 - r * 10);
1862
+ }
1863
+ } else {
1864
+ const lim = o.limitProgress;
1865
+ const p = clampN(r, -lim, lim);
1866
+ const d = p < 0 ? o.prev : o.next;
1867
+ const m = Math.abs(p);
1868
+ const t = d.translate || [0, 0, 0];
1869
+ const rt = d.rotate || [0, 0, 0];
1870
+ tr =
1871
+ 'translate3d(' + pct(t[0], W) * m + 'px,' + pct(t[1], sl.list.clientHeight) * m + 'px,' + (parseFloat(t[2]) || 0) * m + 'px) rotateX(' + (rt[0] || 0) * m + 'deg) rotateY(' + (rt[1] || 0) * m + 'deg) rotateZ(' + (rt[2] || 0) * m + 'deg) scale(' + (1 + ((d.scale === undefined ? 1 : d.scale) - 1) * m) + ')';
1872
+ op = 1 + ((d.opacity === undefined ? 1 : d.opacity) - 1) * m;
1873
+ if (a > lim + 0.01) op = d.opacity === undefined ? 1 : d.opacity;
1874
+ z = d.zIndex !== undefined && p !== 0 ? d.zIndex : Math.round(1000 - a * 10);
1875
+ }
1876
+ place(e, tr, op, z, animate, speed, jump);
1877
+ }
1878
+ };
1879
+ sl._onFx(apply);
1880
+ sl.el.classList.add('mozert-fx-' + fx);
1881
+ sl._paintCurrent(false);
1882
+ return {
1883
+ destroy() {
1884
+ list.style.perspective = '';
1885
+ sl.track.style.transformStyle = '';
1886
+ list.style.overflow = '';
1887
+ sl.el.classList.remove('mozert-fx-' + fx);
1888
+ slides().forEach((e) => {
1889
+ if (fx === 'expand' && e.firstElementChild) e.firstElementChild.style.width = e.firstElementChild.style.marginLeft = e.firstElementChild.style.transition = e.firstElementChild.style.maxWidth = '';
1890
+ e.style.transform = e.style.opacity = e.style.zIndex = e.style.transition = e.style.pointerEvents = e.style.backfaceVisibility = '';
1891
+ });
1892
+ },
1893
+ };
1894
+ },
1895
+ });
1896
+
1897
+ /* ----------------------------------------------------------- parallax */
1898
+ Mozert.use({
1899
+ name: 'parallax',
1900
+ defaults: { parallax: false },
1901
+ create(sl) {
1902
+ if (!sl.s.parallax) return null;
1903
+ const items = [];
1904
+ const stacked = sl.s.stacked;
1905
+ const slides = stacked ? sl._units : sl.dom;
1906
+ slides.forEach((slide, k) => {
1907
+ const els = [];
1908
+ if (slide.hasAttribute('data-parallax-x') || slide.hasAttribute('data-parallax-y')) els.push(slide);
1909
+ slide.querySelectorAll('[data-parallax-x],[data-parallax-y],[data-parallax-scale],[data-parallax-opacity]').forEach((e) => els.push(e));
1910
+ items.push(els);
1911
+ });
1912
+ const bg = sl.el.querySelector('[data-parallax-bg]');
1913
+ const apply = (pv, animate, speed) => {
1914
+ const W = sl.slideSize || sl.list.clientWidth;
1915
+ slides.forEach((slide, k) => {
1916
+ const i = stacked ? k : k - sl.cloneCount;
1917
+ const r = clampN(relOf(i, pv, sl.n, stacked), -2, 2);
1918
+ items[k].forEach((e) => {
1919
+ const x = e.getAttribute('data-parallax-x');
1920
+ const y = e.getAttribute('data-parallax-y');
1921
+ const sc = e.getAttribute('data-parallax-scale');
1922
+ const op = e.getAttribute('data-parallax-opacity');
1923
+ const tx = x ? pct(x, e === slide ? W : e.offsetWidth || W) * r : 0;
1924
+ const ty = y ? pct(y, e.offsetHeight || 100) * r : 0;
1925
+ e.style.transition = animate ? 'transform ' + speed + 'ms ' + sl.s.cssEase + ', opacity ' + speed + 'ms ' + sl.s.cssEase : 'none';
1926
+ e.style.transform = 'translate3d(' + tx + 'px,' + ty + 'px,0)' + (sc ? ' scale(' + (1 + (parseFloat(sc) - 1) * Math.abs(r)) + ')' : '');
1927
+ if (op !== null) e.style.opacity = 1 + (parseFloat(op) - 1) * Math.abs(r);
1928
+ });
1929
+ });
1930
+ if (bg) {
1931
+ const amt = parseFloat(bg.getAttribute('data-parallax-bg')) || 0.5;
1932
+ bg.style.transition = animate ? 'transform ' + speed + 'ms ' + sl.s.cssEase : 'none';
1933
+ bg.style.transform = 'translate3d(' + -pv * W * amt + 'px,0,0)';
1934
+ }
1935
+ };
1936
+ sl._onFx(apply);
1937
+ sl._paintCurrent(false);
1938
+ return {
1939
+ destroy() {
1940
+ items.forEach((l) => l.forEach((e) => (e.style.transform = e.style.opacity = e.style.transition = '')));
1941
+ if (bg) bg.style.transform = '';
1942
+ },
1943
+ };
1944
+ },
1945
+ });
1946
+
1947
+ /* --------------------------------------------------------------- zoom */
1948
+ Mozert.use({
1949
+ name: 'zoom',
1950
+ defaults: { zoom: false },
1951
+ create(sl) {
1952
+ if (!sl.s.zoom) return null;
1953
+ const o = objOpt(sl.s.zoom, { max: 3, min: 1, toggle: true, step: 0.5 });
1954
+ let scale = 1;
1955
+ let tx = 0;
1956
+ let ty = 0;
1957
+ let target = null;
1958
+ const ptrs = new Map();
1959
+ let startDist = 0;
1960
+ let startScale = 1;
1961
+ let pan = null;
1962
+ const cur = () => sl._units[sl.current];
1963
+ const tgt = () => {
1964
+ const u = cur();
1965
+ return u ? u.querySelector('.mozert-zoom-target') || u.querySelector('img,picture,canvas,video') : null;
1966
+ };
1967
+ const paint = (animate) => {
1968
+ if (!target) return;
1969
+ target.style.transition = animate ? 'transform .25s ease' : 'none';
1970
+ target.style.transform = scale === 1 && !tx && !ty ? '' : 'translate3d(' + tx + 'px,' + ty + 'px,0) scale(' + scale + ')';
1971
+ target.style.transformOrigin = '50% 50%';
1972
+ target.style.cursor = scale > 1 ? 'grab' : '';
1973
+ sl._lockDrag = scale > 1;
1974
+ sl.el.classList.toggle('mozert-zoomed', scale > 1);
1975
+ };
1976
+ const bound = () => {
1977
+ if (!target) return;
1978
+ const mx = (target.offsetWidth * (scale - 1)) / 2;
1979
+ const my = (target.offsetHeight * (scale - 1)) / 2;
1980
+ tx = clampN(tx, -mx, mx);
1981
+ ty = clampN(ty, -my, my);
1982
+ };
1983
+ const reset = (animate) => {
1984
+ if (scale === 1 && !tx && !ty) {
1985
+ target = tgt();
1986
+ return;
1987
+ }
1988
+ scale = 1;
1989
+ tx = ty = 0;
1990
+ paint(animate);
1991
+ target = tgt();
1992
+ };
1993
+ const setScale = (v, animate) => {
1994
+ target = tgt();
1995
+ if (!target) return;
1996
+ scale = clampN(v, o.min, o.max);
1997
+ if (scale === 1) tx = ty = 0;
1998
+ bound();
1999
+ paint(animate);
2000
+ sl.emit('zoomChange', scale);
2001
+ };
2002
+ sl.zoom = {
2003
+ in: () => setScale(scale + o.step, true),
2004
+ out: () => setScale(scale - o.step, true),
2005
+ toggle: () => setScale(scale > 1 ? 1 : o.max, true),
2006
+ reset: () => reset(true),
2007
+ get scale() {
2008
+ return scale;
2009
+ },
2010
+ };
2011
+ const onDbl = (e) => {
2012
+ if (!o.toggle || !e.target.closest || !cur() || !cur().contains(e.target)) return;
2013
+ e.preventDefault();
2014
+ sl.zoom.toggle();
2015
+ };
2016
+ const onDown = (e) => {
2017
+ if (!cur() || !cur().contains(e.target)) return;
2018
+ ptrs.set(e.pointerId, { x: e.clientX, y: e.clientY });
2019
+ if (ptrs.size === 2) {
2020
+ const [a, b] = Array.from(ptrs.values());
2021
+ startDist = Math.hypot(a.x - b.x, a.y - b.y);
2022
+ startScale = scale;
2023
+ sl._lockDrag = true;
2024
+ } else if (scale > 1) pan = { x: e.clientX, y: e.clientY, tx, ty };
2025
+ };
2026
+ const onMove = (e) => {
2027
+ if (!ptrs.has(e.pointerId)) return;
2028
+ ptrs.set(e.pointerId, { x: e.clientX, y: e.clientY });
2029
+ if (ptrs.size === 2 && startDist) {
2030
+ const [a, b] = Array.from(ptrs.values());
2031
+ setScale((startScale * Math.hypot(a.x - b.x, a.y - b.y)) / startDist, false);
2032
+ } else if (pan && scale > 1) {
2033
+ tx = pan.tx + (e.clientX - pan.x);
2034
+ ty = pan.ty + (e.clientY - pan.y);
2035
+ bound();
2036
+ paint(false);
2037
+ }
2038
+ };
2039
+ const onUp = (e) => {
2040
+ ptrs.delete(e.pointerId);
2041
+ if (ptrs.size < 2) startDist = 0;
2042
+ if (!ptrs.size) pan = null;
2043
+ if (scale <= 1.001) {
2044
+ scale = 1;
2045
+ tx = ty = 0;
2046
+ paint(true);
2047
+ }
2048
+ };
2049
+ const onWheel = (e) => {
2050
+ if (!e.ctrlKey || !cur() || !cur().contains(e.target)) return;
2051
+ e.preventDefault();
2052
+ setScale(scale - e.deltaY * 0.01, false);
2053
+ };
2054
+ sl._on(sl.list, 'dblclick', onDbl);
2055
+ sl._on(sl.list, 'pointerdown', onDown);
2056
+ sl._on(window, 'pointermove', onMove);
2057
+ sl._on(window, 'pointerup', onUp);
2058
+ sl._on(window, 'pointercancel', onUp);
2059
+ sl._on(sl.list, 'wheel', onWheel, { passive: false });
2060
+ sl._sub('beforeChange', () => reset(false));
2061
+ target = tgt();
2062
+ return {
2063
+ destroy() {
2064
+ reset(false);
2065
+ sl._lockDrag = false;
2066
+ delete sl.zoom;
2067
+ },
2068
+ };
2069
+ },
2070
+ });
2071
+
2072
+ /* ------------------------------------------------------------ virtual */
2073
+ Mozert._boot.push((el, o) => {
2074
+ const v = o.virtual;
2075
+ if (!v || !v.slides || el.children.length) return;
2076
+ v.slides.forEach((d, i) => {
2077
+ const sh = mk('div', 'mozert-v');
2078
+ sh.setAttribute('data-vi', i);
2079
+ el.appendChild(sh);
2080
+ });
2081
+ });
2082
+ Mozert.use({
2083
+ name: 'virtual',
2084
+ defaults: { virtual: false },
2085
+ create(sl) {
2086
+ const v = sl.s.virtual;
2087
+ if (!v || !v.slides) return null;
2088
+ const before = num(v.addSlidesBefore, 2);
2089
+ const after = num(v.addSlidesAfter, 2);
2090
+ const store = v.slides;
2091
+ const hostOf = (u) => (u.classList.contains('mozert-v') ? u : u.querySelector('.mozert-v') || u);
2092
+ const make = (i) => {
2093
+ const d = store[i];
2094
+ const html = typeof v.renderSlide === 'function' ? v.renderSlide(d, i) : d;
2095
+ return html;
2096
+ };
2097
+ const render = () => {
2098
+ const a = Math.max(0, sl.current - before);
2099
+ const b = Math.min(sl.n - 1, sl.current + sl._showC - 1 + after);
2100
+ const added = [];
2101
+ sl._units.forEach((u, i) => {
2102
+ const h = hostOf(u);
2103
+ const inWin = i >= a && i <= b;
2104
+ if (inWin && !h.hasAttribute('data-vr')) {
2105
+ const c = make(i);
2106
+ if (typeof c === 'string') h.innerHTML = c;
2107
+ else if (c && c.nodeType) {
2108
+ h.innerHTML = '';
2109
+ h.appendChild(c);
2110
+ }
2111
+ h.setAttribute('data-vr', '1');
2112
+ added.push(i);
2113
+ } else if (!inWin && h.hasAttribute('data-vr') && v.cache === false) {
2114
+ h.innerHTML = '';
2115
+ h.removeAttribute('data-vr');
2116
+ }
2117
+ });
2118
+ if (added.length) {
2119
+ sl._arrowY();
2120
+ sl._lazy();
2121
+ sl.emit('virtualRender', added);
2122
+ }
2123
+ };
2124
+ sl._sub('beforeChange', (_s, old, real) => {
2125
+ sl.current = real;
2126
+ render();
2127
+ });
2128
+ render();
2129
+ sl.virtual = {
2130
+ slides: store,
2131
+ appendSlide(d) {
2132
+ store.push(d);
2133
+ sl.add(mk('div', 'mozert-v'));
2134
+ return sl;
2135
+ },
2136
+ prependSlide(d) {
2137
+ store.unshift(d);
2138
+ sl.add(mk('div', 'mozert-v'), true);
2139
+ return sl;
2140
+ },
2141
+ removeSlide(i) {
2142
+ store.splice(i, 1);
2143
+ sl.remove(i);
2144
+ return sl;
2145
+ },
2146
+ update() {
2147
+ sl._units.forEach((u) => {
2148
+ const h = hostOf(u);
2149
+ h.innerHTML = '';
2150
+ h.removeAttribute('data-vr');
2151
+ });
2152
+ render();
2153
+ },
2154
+ };
2155
+ return {
2156
+ destroy() {
2157
+ delete sl.virtual;
2158
+ },
2159
+ };
2160
+ },
2161
+ });
2162
+
2163
+ /* ----------------------------------------------------- hash navigation */
2164
+ Mozert.use({
2165
+ name: 'hash',
2166
+ defaults: { hashNavigation: false },
2167
+ create(sl) {
2168
+ const h = sl.s.hashNavigation;
2169
+ if (!h || typeof window === 'undefined') return null;
2170
+ const o = objOpt(h, { watchState: true, replaceState: true, prefix: '' });
2171
+ const hashes = sl._units.map((u, i) => (u.getAttribute('data-hash') || (u.firstElementChild && u.firstElementChild.getAttribute('data-hash')) || String(i + 1)));
2172
+ const read = () => decodeURIComponent((window.location.hash || '').replace(/^#/, '').replace(o.prefix, ''));
2173
+ const go = (anim) => {
2174
+ const i = hashes.indexOf(read());
2175
+ if (i > -1 && i !== sl.current) sl.goTo(i, !anim);
2176
+ };
2177
+ go(false);
2178
+ sl._sub('afterChange', () => {
2179
+ const val = o.prefix + hashes[sl.current];
2180
+ try {
2181
+ if (o.replaceState && window.history && window.history.replaceState) window.history.replaceState(null, '', '#' + val);
2182
+ else window.location.hash = val;
2183
+ } catch (e) {}
2184
+ });
2185
+ if (o.watchState) sl._on(window, 'hashchange', () => go(true));
2186
+ return null;
2187
+ },
2188
+ });
2189
+
2190
+ /* ---------------------------------------------------------- scrollbar */
2191
+ Mozert.use({
2192
+ name: 'scrollbar',
2193
+ defaults: { scrollbar: false },
2194
+ create(sl) {
2195
+ const c = sl.s.scrollbar;
2196
+ if (!c || !sl.canNav) return null;
2197
+ const o = objOpt(c, { draggable: true, hide: false, el: null });
2198
+ const host = o.el ? (typeof o.el === 'string' ? document.querySelector(o.el) : o.el) : null;
2199
+ const bar = host || mk('div', 'mozert-scrollbar');
2200
+ const thumb = mk('div', 'mozert-scrollbar-drag');
2201
+ bar.classList.add('mozert-scrollbar');
2202
+ bar.appendChild(thumb);
2203
+ if (!host) sl.el.appendChild(bar);
2204
+ const show = () => Math.min(sl.n, sl._show);
2205
+ const apply = (pv) => {
2206
+ const max = Math.max(0.0001, sl.n - show());
2207
+ const frac = sl.s.stacked ? (pv - 0) / Math.max(1, sl.n - 1) : clampN(pv / max, 0, 1);
2208
+ const tw = sl.s.stacked ? 100 / sl.n : (show() / sl.n) * 100;
2209
+ thumb.style.width = tw + '%';
2210
+ thumb.style.left = clampN(frac, 0, 1) * (100 - tw) + '%';
2211
+ };
2212
+ sl._onFx(apply);
2213
+ sl._paintCurrent(false);
2214
+ let hideT;
2215
+ if (o.hide) {
2216
+ bar.classList.add('mozert-scrollbar-hide');
2217
+ sl._sub('beforeChange', () => {
2218
+ bar.classList.add('mozert-scrollbar-show');
2219
+ clearTimeout(hideT);
2220
+ hideT = setTimeout(() => bar.classList.remove('mozert-scrollbar-show'), 1200);
2221
+ });
2222
+ }
2223
+ if (o.draggable) {
2224
+ let drag = false;
2225
+ const move = (e) => {
2226
+ const r = bar.getBoundingClientRect();
2227
+ const f = clampN((e.clientX - r.left) / (r.width || 1), 0, 1);
2228
+ const idx = Math.round(f * (sl.s.stacked ? sl.n - 1 : sl.maxIndex));
2229
+ sl.goTo(idx);
2230
+ };
2231
+ const down = (e) => {
2232
+ drag = true;
2233
+ move(e);
2234
+ e.preventDefault();
2235
+ };
2236
+ sl._on(bar, 'pointerdown', down);
2237
+ sl._on(window, 'pointermove', (e) => drag && move(e));
2238
+ sl._on(window, 'pointerup', () => (drag = false));
2239
+ }
2240
+ return {
2241
+ destroy() {
2242
+ clearTimeout(hideT);
2243
+ if (host) {
2244
+ if (thumb.parentNode) thumb.parentNode.removeChild(thumb);
2245
+ } else if (bar.parentNode) bar.parentNode.removeChild(bar);
2246
+ },
2247
+ };
2248
+ },
2249
+ });
2250
+
2251
+ /* --------------------------------------------------------- pagination */
2252
+ Mozert.use({
2253
+ name: 'pagination',
2254
+ defaults: { pagination: false },
2255
+ create(sl) {
2256
+ const c = sl.s.pagination;
2257
+ if (!c || !sl.canNav) return null;
2258
+ const o = objOpt(c, { type: 'bullets', el: null, clickable: true, dynamicMainBullets: 1, progressbarOpposite: false, hideOnClick: false });
2259
+ const host = o.el ? (typeof o.el === 'string' ? document.querySelector(o.el) : o.el) : null;
2260
+ const root = host || mk('div');
2261
+ root.classList.add('mozert-pagination', 'mozert-pagination-' + o.type);
2262
+ if (!host) sl.el.appendChild(root);
2263
+ const total = sl.n;
2264
+ const cur = () => sl.current;
2265
+ let bullets = [];
2266
+ let fill = null;
2267
+ const buildBullets = () => {
2268
+ root.innerHTML = '';
2269
+ bullets = [];
2270
+ for (let i = 0; i < total; i++) {
2271
+ let b;
2272
+ if (typeof o.renderBullet === 'function') {
2273
+ const html = o.renderBullet(i, 'mozert-bullet');
2274
+ const t = mk('div', null, String(html).trim());
2275
+ b = t.firstElementChild || mk('span', 'mozert-bullet');
2276
+ } else b = mk('button', 'mozert-bullet');
2277
+ if (b.tagName === 'BUTTON') {
2278
+ b.type = 'button';
2279
+ b.setAttribute('aria-label', sl._label('goTo', { n: i + 1 }));
2280
+ }
2281
+ if (o.clickable) {
2282
+ b.addEventListener('click', (e) => {
2283
+ e.preventDefault();
2284
+ sl._user();
2285
+ sl.goTo(i);
2286
+ });
2287
+ }
2288
+ root.appendChild(b);
2289
+ bullets.push(b);
2290
+ }
2291
+ };
2292
+ const update = () => {
2293
+ const i = cur();
2294
+ if (o.type === 'bullets' || o.type === 'dynamic') {
2295
+ const m = o.dynamicMainBullets;
2296
+ bullets.forEach((b, k) => {
2297
+ b.classList.toggle('mozert-bullet-active', k === i);
2298
+ if (o.type === 'dynamic') {
2299
+ const d = Math.abs(k - i);
2300
+ b.classList.toggle('mozert-bullet-active-main', d <= (m - 1) / 2 || k === i);
2301
+ b.classList.toggle('mozert-bullet-near', d === 1 || d === 2);
2302
+ b.classList.toggle('mozert-bullet-far', d === 3 || d === 4);
2303
+ b.classList.toggle('mozert-bullet-hidden', d > 4 + (m - 1));
2304
+ }
2305
+ });
2306
+ } else if (o.type === 'fraction') {
2307
+ const cs = '<span class="mozert-pg-current">' + (i + 1) + '</span>';
2308
+ const ts = '<span class="mozert-pg-total">' + total + '</span>';
2309
+ root.innerHTML = typeof o.renderFraction === 'function' ? o.renderFraction('mozert-pg-current', 'mozert-pg-total', i + 1, total) : cs + ' / ' + ts;
2310
+ } else if (o.type === 'custom' && typeof o.renderCustom === 'function') {
2311
+ root.innerHTML = o.renderCustom(sl, i + 1, total);
2312
+ }
2313
+ };
2314
+ if (o.type === 'bullets' || o.type === 'dynamic') buildBullets();
2315
+ else if (o.type === 'progressbar') {
2316
+ fill = mk('span', 'mozert-pg-fill');
2317
+ root.appendChild(fill);
2318
+ if (o.progressbarOpposite) root.classList.add('mozert-pg-opposite');
2319
+ sl._onFx((pv) => {
2320
+ const p = sl.s.stacked ? (relOf(0, -pv, total, false) + 1) / total : clampN((pv + Math.min(sl._show, total)) / total, 0, 1);
2321
+ fill.style.transform = 'scaleX(' + clampN(sl.s.stacked ? (pv + 1) / total : p, 0, 1) + ')';
2322
+ });
2323
+ sl._paintCurrent(false);
2324
+ }
2325
+ update();
2326
+ sl._sub('beforeChange', (_s, old, real) => {
2327
+ sl.current = real;
2328
+ update();
2329
+ });
2330
+ sl._sub('afterChange', update);
2331
+ return {
2332
+ destroy() {
2333
+ if (host) root.innerHTML = '';
2334
+ else if (root.parentNode) root.parentNode.removeChild(root);
2335
+ root.classList.remove('mozert-pagination', 'mozert-pagination-' + o.type);
2336
+ },
2337
+ };
2338
+ },
2339
+ });
2340
+
2341
+ /* ---------------------------------------------------- free mode + wheel */
2342
+ const bounds = (sl) => [sl._pos(sl.maxIndex), sl._pos(0)]; // [min, max]
2343
+ const syncCurrent = (sl) => {
2344
+ const s = sl.s;
2345
+ if (s.stacked) return;
2346
+ const pv = sl._pvFromT(sl._cur);
2347
+ const idx = clampN(Math.round(pv), 0, sl.maxIndex);
2348
+ if (idx !== sl.current) {
2349
+ const old = sl.current;
2350
+ sl.emit('beforeChange', old, idx);
2351
+ sl.current = idx;
2352
+ sl._logical = idx;
2353
+ sl._updateNav();
2354
+ sl._updateSlideClasses(idx);
2355
+ sl._lazy();
2356
+ sl.emit('afterChange', idx);
2357
+ }
2358
+ };
2359
+ Mozert.use({
2360
+ name: 'freeMode',
2361
+ defaults: { freeMode: false },
2362
+ create(sl) {
2363
+ const s = sl.s;
2364
+ if (!s.freeMode || s.stacked) return null;
2365
+ const o = objOpt(s.freeMode, { momentum: true, momentumRatio: 1, momentumBounce: true, sticky: false, minimumVelocity: 0.02 });
2366
+ let timer;
2367
+ sl.el.classList.add('mozert-free');
2368
+ sl._release = (st, size, axis) => {
2369
+ const sm = st.samples;
2370
+ let v = 0;
2371
+ if (sm.length > 1) {
2372
+ const a = sm[0];
2373
+ const b = sm[sm.length - 1];
2374
+ v = (b.v - a.v) / Math.max(1, b.t - a.t);
2375
+ }
2376
+ if (performance.now() - (sm.length ? sm[sm.length - 1].t : 0) > 120) v = 0;
2377
+ const [mn, mx] = bounds(sl);
2378
+ const cur = sl._cur;
2379
+ let dist = o.momentum && Math.abs(v) > o.minimumVelocity ? v * 350 * o.momentumRatio : 0;
2380
+ let target = clampN(cur + dist, mn, mx);
2381
+ const dur = clampN(Math.abs(target - cur) * 2.2 + 250, 250, 1400);
2382
+ sl._setPos(target, true, { duration: dur, ease: 'cubic-bezier(.17,.67,.35,1)' });
2383
+ clearTimeout(timer);
2384
+ timer = setTimeout(() => {
2385
+ if (o.sticky) {
2386
+ const idx = clampN(Math.round(sl._pvFromT(sl._cur)), 0, sl.maxIndex);
2387
+ sl._setPos(sl._pos(idx), true);
2388
+ }
2389
+ syncCurrent(sl);
2390
+ }, dur + 20);
2391
+ sl.emit('freeModeEnd', target);
2392
+ return true;
2393
+ };
2394
+ return {
2395
+ destroy() {
2396
+ clearTimeout(timer);
2397
+ sl.el.classList.remove('mozert-free');
2398
+ },
2399
+ };
2400
+ },
2401
+ });
2402
+
2403
+ Mozert.use({
2404
+ name: 'mousewheel',
2405
+ defaults: { mousewheel: false },
2406
+ create(sl) {
2407
+ const c = sl.s.mousewheel;
2408
+ if (!c) return null;
2409
+ const o = objOpt(c, { sensitivity: 1, forceToAxis: false, releaseOnEdges: true, thresholdDelta: 4, thresholdTime: 450 });
2410
+ let last = 0;
2411
+ let timer;
2412
+ const free = !!sl.s.freeMode && !sl.s.stacked;
2413
+ const on = (e) => {
2414
+ if (!sl.canNav || !sl.active) return;
2415
+ let d = sl.s.vertical ? e.deltaY : o.forceToAxis ? e.deltaX : Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.deltaY;
2416
+ if (e.shiftKey && !sl.s.vertical) d = e.deltaY || e.deltaX;
2417
+ if (!d) return;
2418
+ if (d > 0 === (sl.s.rtl && !sl.s.vertical)) d = d; // direction handled below
2419
+ const edgeFwd = d > 0 && sl.current >= (sl.s.infinite || sl.s.stacked ? Infinity : sl.maxIndex) && (!free || sl._cur <= bounds(sl)[0] + 1);
2420
+ const edgeBack = d < 0 && sl.current <= (sl.s.infinite || sl.s.stacked ? -Infinity : 0) && (!free || sl._cur >= bounds(sl)[1] - 1);
2421
+ if (o.releaseOnEdges && (edgeFwd || edgeBack)) return;
2422
+ e.preventDefault();
2423
+ sl._user();
2424
+ if (free) {
2425
+ const [mn, mx] = bounds(sl);
2426
+ const dir = sl.s.rtl && !sl.s.vertical ? 1 : -1;
2427
+ sl._setPos(clampN(sl._cur + dir * d * o.sensitivity, mn, mx), false);
2428
+ clearTimeout(timer);
2429
+ timer = setTimeout(() => syncCurrent(sl), 120);
2430
+ return;
2431
+ }
2432
+ const now = performance.now();
2433
+ if (Math.abs(d) < o.thresholdDelta && now - last < o.thresholdTime) return;
2434
+ if (now - last < o.thresholdTime) return;
2435
+ last = now;
2436
+ if (d > 0) sl.next();
2437
+ else sl.prev();
2438
+ };
2439
+ sl._on(sl.el, 'wheel', on, { passive: false });
2440
+ return {
2441
+ destroy() {
2442
+ clearTimeout(timer);
2443
+ },
2444
+ };
2445
+ },
2446
+ });
2447
+
2448
+ /* ---------------------------------------------------------- keyboard */
2449
+ Mozert.use({
2450
+ name: 'globalKeyboard',
2451
+ defaults: { globalKeyboard: false },
2452
+ create(sl) {
2453
+ const c = sl.s.globalKeyboard;
2454
+ if (!c) return null;
2455
+ const o = objOpt(c, { onlyInViewport: true });
2456
+ sl._on(document, 'keydown', (e) => {
2457
+ if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey) return;
2458
+ const t = e.target;
2459
+ if (t && (/^(input|textarea|select)$/i.test(t.tagName) || t.isContentEditable)) return;
2460
+ if (o.onlyInViewport) {
2461
+ const r = sl.el.getBoundingClientRect();
2462
+ const vh = window.innerHeight || document.documentElement.clientHeight;
2463
+ if (r.bottom < 0 || r.top > vh) return;
2464
+ }
2465
+ const v = sl.s.vertical;
2466
+ const k = e.key;
2467
+ const rtl = sl.s.rtl && !v;
2468
+ if ((!v && (k === 'ArrowLeft' || k === 'ArrowRight')) || (v && (k === 'ArrowUp' || k === 'ArrowDown'))) {
2469
+ const fwd = k === 'ArrowRight' || k === 'ArrowDown';
2470
+ sl._user();
2471
+ if (fwd !== rtl) sl.next();
2472
+ else sl.prev();
2473
+ e.preventDefault();
2474
+ }
2475
+ });
2476
+ return null;
2477
+ },
2478
+ });
2479
+
2480
+ /* ------------------------------------------------------------ marquee */
2481
+ Mozert.use({
2482
+ name: 'marquee',
2483
+ defaults: { marquee: false },
2484
+ create(sl) {
2485
+ const c = sl.s.marquee;
2486
+ if (!c || !sl.canNav) return null;
2487
+ const o = objOpt(c, { speed: 60, pauseOnHover: true, direction: 'left' });
2488
+ if (typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches && sl.s.respectReducedMotion) return null;
2489
+ const pitch = sl.slideSize + sl.s.spaceBetween;
2490
+ const setLen = pitch * sl.n;
2491
+ const base = sl._pos(0);
2492
+ let off = 0;
2493
+ let last = 0;
2494
+ let raf = 0;
2495
+ let hold = false;
2496
+ sl._lockDrag = true;
2497
+ sl.track.style.transition = 'none';
2498
+ const dir = (o.direction === 'right' ? -1 : 1) * (sl.s.rtl ? -1 : 1);
2499
+ const loop = (t) => {
2500
+ if (!last) last = t;
2501
+ const dt = Math.min(64, t - last);
2502
+ last = t;
2503
+ if (!hold) {
2504
+ off += (o.speed * dt) / 1000;
2505
+ const m = ((off % setLen) + setLen) % setLen;
2506
+ const v = o.direction === 'right' ? base - setLen + m : base - m;
2507
+ sl._paint(sl.s.rtl ? -v : v);
2508
+ sl._t = sl._cur;
2509
+ }
2510
+ raf = requestAnimationFrame(loop);
2511
+ };
2512
+ raf = requestAnimationFrame(loop);
2513
+ if (o.pauseOnHover) {
2514
+ sl._on(sl.el, 'mouseenter', () => (hold = true));
2515
+ sl._on(sl.el, 'mouseleave', () => {
2516
+ hold = false;
2517
+ last = 0;
2518
+ });
2519
+ }
2520
+ sl.marquee = {
2521
+ pause: () => (hold = true),
2522
+ resume: () => {
2523
+ hold = false;
2524
+ last = 0;
2525
+ },
2526
+ };
2527
+ return {
2528
+ destroy() {
2529
+ cancelAnimationFrame(raf);
2530
+ sl._lockDrag = false;
2531
+ delete sl.marquee;
2532
+ },
2533
+ };
2534
+ },
2535
+ });
2536
+
2537
+ /* -------------------------------------------------------------- video */
2538
+ Mozert.use({
2539
+ name: 'video',
2540
+ defaults: { videoAutoplay: false },
2541
+ create(sl) {
2542
+ const c = sl.s.videoAutoplay;
2543
+ if (!c) return null;
2544
+ const o = objOpt(c, { wait: false, pauseOthers: true, rewind: true });
2545
+ const vids = (u) => Array.from(u.querySelectorAll('video'));
2546
+ const ended = () => sl._holdOff('video');
2547
+ const sync = () => {
2548
+ sl._units.forEach((u, i) => {
2549
+ vids(u).forEach((v) => {
2550
+ if (i === sl.current) {
2551
+ try {
2552
+ const p = v.play();
2553
+ if (p && p.catch) p.catch(() => {});
2554
+ } catch (e) {}
2555
+ if (o.wait) {
2556
+ sl._holdOn('video');
2557
+ v.addEventListener('ended', ended, { once: true });
2558
+ }
2559
+ } else if (o.pauseOthers) {
2560
+ try {
2561
+ v.pause();
2562
+ if (o.rewind) v.currentTime = 0;
2563
+ } catch (e) {}
2564
+ }
2565
+ });
2566
+ });
2567
+ if (o.wait && !vids(sl._units[sl.current] || document.createElement('i')).length) sl._holdOff('video');
2568
+ };
2569
+ sl._sub('afterChange', sync);
2570
+ sync();
2571
+ return {
2572
+ destroy() {
2573
+ sl._holdOff('video');
2574
+ },
2575
+ };
2576
+ },
2577
+ });
2578
+
2579
+ /* --------------------------------------------------------- fullscreen */
2580
+ Mozert.use({
2581
+ name: 'fullscreen',
2582
+ defaults: { fullscreen: false },
2583
+ create(sl) {
2584
+ if (!sl.s.fullscreen) return null;
2585
+ const btn = mk('button', 'mozert-fullscreen-btn');
2586
+ btn.type = 'button';
2587
+ btn.setAttribute('aria-label', sl._label('fullscreen') || 'Toggle fullscreen');
2588
+ btn.innerHTML = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg>';
2589
+ sl.el.appendChild(btn);
2590
+ const toggle = () => {
2591
+ const on = !sl.el.classList.contains('mozert-fullscreen');
2592
+ if (on && sl.el.requestFullscreen) sl.el.requestFullscreen().catch(() => sl.el.classList.add('mozert-fullscreen'));
2593
+ else if (!on && document.fullscreenElement && document.exitFullscreen) document.exitFullscreen();
2594
+ else sl.el.classList.toggle('mozert-fullscreen', on);
2595
+ setTimeout(() => window.dispatchEvent(new Event('resize')), 60);
2596
+ };
2597
+ btn.addEventListener('click', toggle);
2598
+ sl._on(document, 'fullscreenchange', () => {
2599
+ sl.el.classList.toggle('mozert-fullscreen', document.fullscreenElement === sl.el);
2600
+ window.dispatchEvent(new Event('resize'));
2601
+ });
2602
+ sl.fullscreen = { toggle };
2603
+ return {
2604
+ destroy() {
2605
+ if (btn.parentNode) btn.parentNode.removeChild(btn);
2606
+ delete sl.fullscreen;
2607
+ },
2608
+ };
2609
+ },
2610
+ });
2611
+
2612
+ /* -------------------------------------------------------------- bleed */
2613
+ // Slides start aligned with the container, overflow towards the viewport edge (right, left or both) and the last slide ends aligned with the container.
2614
+ Mozert.use({
2615
+ name: 'bleed',
2616
+ defaults: { bleed: false },
2617
+ create(sl) {
2618
+ const c = sl.s.bleed;
2619
+ if (!c || sl.s.vertical) return null;
2620
+ const side = c === 'left' || c === 'right' ? c : 'both';
2621
+ const list = sl.list;
2622
+ list.style.overflow = 'visible';
2623
+ sl.el.classList.add('mozert-bleed');
2624
+ // Overflowing slides must not create a horizontal page scrollbar: clip the page on the x axis (restored on destroy).
2625
+ const body = document.body;
2626
+ const prevClip = body.style.overflowX;
2627
+ let clipped = false;
2628
+ if (getComputedStyle(body).overflowX === 'visible') {
2629
+ body.style.overflowX = 'clip';
2630
+ clipped = true;
2631
+ }
2632
+ const update = () => {
2633
+ const r = sl.el.getBoundingClientRect();
2634
+ const vw = document.documentElement.clientWidth || window.innerWidth;
2635
+ const L = side === 'right' ? 0 : Math.max(0, r.left);
2636
+ const R = side === 'left' ? 0 : Math.max(0, vw - r.right);
2637
+ list.style.clipPath = 'inset(-48px ' + -R + 'px -48px ' + -L + 'px)';
2638
+ };
2639
+ update();
2640
+ sl._on(window, 'resize', update);
2641
+ sl._sub('setPosition', update);
2642
+ const t = setTimeout(update, 120);
2643
+ return {
2644
+ destroy() {
2645
+ clearTimeout(t);
2646
+ list.style.overflow = '';
2647
+ list.style.clipPath = '';
2648
+ if (clipped) body.style.overflowX = prevClip;
2649
+ sl.el.classList.remove('mozert-bleed');
2650
+ },
2651
+ };
2652
+ },
2653
+ });
2654
+
2655
+ /* ------------------------------------------------------------- thumbs */
2656
+ // thumbs: '#nav' (an existing strip), true (create one below the slider) or { el, options, count }.
2657
+ // An empty strip is filled automatically from each slide's data-thumb, first <img> or background.
2658
+ Mozert.use({
2659
+ name: 'thumbs',
2660
+ defaults: { thumbs: false },
2661
+ create(sl) {
2662
+ const cfg = sl.s.thumbs;
2663
+ if (!cfg || sl.n < 2) return null;
2664
+ const o = objOpt(cfg, { el: null, options: {}, count: 5, position: 'bottom', breakpoint: 640 });
2665
+ const want = ['left', 'right', 'top', 'bottom'].indexOf(o.position) > -1 ? o.position : 'bottom';
2666
+ // A side strip moves below the picture on narrow screens (viewport <= breakpoint px).
2667
+ const mq = (want === 'left' || want === 'right') && o.breakpoint > 0 && typeof matchMedia === 'function' ? matchMedia('(max-width:' + o.breakpoint + 'px)') : null;
2668
+ const pos = mq && mq.matches ? 'bottom' : want;
2669
+ const side = pos === 'left' || pos === 'right';
2670
+ const onMq = () => sl.active && sl.refresh();
2671
+ if (mq) mq.addEventListener ? mq.addEventListener('change', onMq) : mq.addListener(onMq);
2672
+ let host = null;
2673
+ let made = false;
2674
+ let wrap = null;
2675
+ const ref = cfg === true ? null : typeof cfg === 'object' && !cfg.nodeType ? o.el : cfg;
2676
+ if (ref) host = typeof ref === 'string' ? document.querySelector(ref) : ref && ref.nodeType ? ref : null;
2677
+ if (!host) {
2678
+ if (ref) return null;
2679
+ host = mk('div', 'mozert-thumbs');
2680
+ made = true;
2681
+ if (side) {
2682
+ wrap = mk('div', 'mozert-gallery mozert-gallery-' + pos);
2683
+ sl.el.parentNode.insertBefore(wrap, sl.el);
2684
+ wrap.appendChild(sl.el);
2685
+ wrap.appendChild(host);
2686
+ } else if (pos === 'top') sl.el.parentNode.insertBefore(host, sl.el);
2687
+ else sl.el.parentNode.insertBefore(host, sl.el.nextSibling);
2688
+ }
2689
+ host.setAttribute('data-mozert-thumbs', pos);
2690
+ const auto = host.children.length === 0;
2691
+ if (auto) {
2692
+ (sl._items || []).forEach((src, i) => {
2693
+ const cell = mk('div');
2694
+ const first = src.firstElementChild;
2695
+ const img = src.querySelector('img');
2696
+ const url = src.getAttribute('data-thumb') || (img && (img.getAttribute('data-thumb') || img.getAttribute('src') || img.getAttribute('data-lazy')));
2697
+ if (url) {
2698
+ const t = mk('img', 'mozert-thumb');
2699
+ t.src = url;
2700
+ t.alt = '';
2701
+ t.draggable = false;
2702
+ cell.appendChild(t);
2703
+ } else {
2704
+ const t = mk('div', 'mozert-thumb mozert-thumb-fill');
2705
+ const bg = first ? getComputedStyle(first).backgroundImage : 'none';
2706
+ if (bg && bg !== 'none') t.style.backgroundImage = bg;
2707
+ else t.style.background = (first && getComputedStyle(first).backgroundColor) || '#ccd';
2708
+ t.textContent = String(i + 1);
2709
+ cell.appendChild(t);
2710
+ }
2711
+ host.appendChild(cell);
2712
+ });
2713
+ }
2714
+ const count = Math.max(1, Math.min(o.count, sl.n));
2715
+ const gap = o.options && o.options.spaceBetween != null ? o.options.spaceBetween : 10;
2716
+ const fit = () => {
2717
+ // vertical strip: size each thumb so `count` of them fill exactly the height of the main slider
2718
+ const H = sl.el.offsetHeight;
2719
+ if (!H) return;
2720
+ host.style.setProperty('--mozert-thumb-h', Math.max(24, (H - gap * (count - 1)) / count) + 'px');
2721
+ host.style.height = H + 'px';
2722
+ th.refresh();
2723
+ };
2724
+ const th = new Mozert(host, Object.assign({ slidesToShow: count, slidesToScroll: 1, arrows: false, dots: false, infinite: false, swipeToSlide: true, spaceBetween: gap, focusOnSelect: true, initialSlide: 0, vertical: side }, o.options, { asNavFor: sl, thumbs: false, lightbox: false }));
2725
+ let ro = null;
2726
+ if (side) {
2727
+ fit();
2728
+ if (typeof ResizeObserver !== 'undefined') {
2729
+ let lastH = sl.el.offsetHeight;
2730
+ ro = new ResizeObserver(() => {
2731
+ if (sl.el.offsetHeight !== lastH) {
2732
+ lastH = sl.el.offsetHeight;
2733
+ fit();
2734
+ }
2735
+ });
2736
+ ro.observe(sl.el);
2737
+ }
2738
+ }
2739
+ const prevNav = sl.s.asNavFor;
2740
+ if (!prevNav) sl.s.asNavFor = th;
2741
+ host.classList.add('mozert-thumbs');
2742
+ sl._syncNav(sl.current);
2743
+ return {
2744
+ destroy() {
2745
+ if (sl.s.asNavFor === th) sl.s.asNavFor = prevNav;
2746
+ if (ro) ro.disconnect();
2747
+ if (mq) mq.removeEventListener ? mq.removeEventListener('change', onMq) : mq.removeListener(onMq);
2748
+ host.removeAttribute('data-mozert-thumbs');
2749
+ host.style.removeProperty('--mozert-thumb-h');
2750
+ if (side) host.style.height = '';
2751
+ th.destroy();
2752
+ if (wrap && wrap.parentNode) {
2753
+ wrap.parentNode.insertBefore(sl.el, wrap);
2754
+ wrap.parentNode.removeChild(wrap);
2755
+ } else if (made) host.parentNode && host.parentNode.removeChild(host);
2756
+ else if (auto) host.innerHTML = '';
2757
+ },
2758
+ };
2759
+ },
2760
+ });
2761
+
2762
+ /* ----------------------------------------------------------- lightbox */
2763
+ // lightbox: true | { zoom: true, loop: true }. Click a slide to open it full screen.
2764
+ Mozert.use({
2765
+ name: 'lightbox',
2766
+ defaults: { lightbox: false },
2767
+ create(sl) {
2768
+ if (!sl.s.lightbox) return null;
2769
+ const o = objOpt(sl.s.lightbox, { zoom: true, loop: true, maxZoom: 2.5 });
2770
+ let box = null;
2771
+ let idx = 0;
2772
+ let opener = null;
2773
+ let zoomed = false;
2774
+ let zrect = null;
2775
+ let drag = null;
2776
+ const items = () => sl._items || [];
2777
+ const isImg = (h) => /^data:image\//i.test(h || '') || /\.(jpe?g|png|gif|webp|avif|svg)(\?|#|$)/i.test(h || '');
2778
+ const info = (src) => {
2779
+ const img = src.querySelector('img');
2780
+ const a = src.querySelector('a[href]');
2781
+ const full = src.getAttribute('data-full') || (img && img.getAttribute('data-full')) || (a && isImg(a.getAttribute('href')) ? a.getAttribute('href') : '') || (img && (img.currentSrc || img.getAttribute('src'))) || '';
2782
+ return { src: full, alt: (img && img.getAttribute('alt')) || '', cap: src.getAttribute('data-caption') || (img && img.getAttribute('data-caption')) || '', node: src };
2783
+ };
2784
+ const stageContent = (it) => {
2785
+ if (it.src) {
2786
+ const im = mk('img', 'mozert-lb-img');
2787
+ im.src = it.src;
2788
+ im.alt = it.alt;
2789
+ im.draggable = false;
2790
+ return im;
2791
+ }
2792
+ // Slides without an image: show a larger copy of the slide itself.
2793
+ const w = mk('div', 'mozert-lb-clone');
2794
+ w.innerHTML = it.node.innerHTML;
2795
+ return w;
2796
+ };
2797
+ const setZoom = (on, ev) => {
2798
+ const el = box && box.querySelector('.mozert-lb-img,.mozert-lb-clone');
2799
+ if (!el || !o.zoom) return;
2800
+ zoomed = on;
2801
+ box.classList.toggle('mozert-lb-zoomed', on);
2802
+ if (on) {
2803
+ const r = el.getBoundingClientRect();
2804
+ zrect = r;
2805
+ const ox = ev ? ((ev.clientX - r.left) / r.width) * 100 : 50;
2806
+ const oy = ev ? ((ev.clientY - r.top) / r.height) * 100 : 50;
2807
+ el.style.transformOrigin = ox + '% ' + oy + '%';
2808
+ el.style.transform = 'scale(' + o.maxZoom + ')';
2809
+ } else {
2810
+ el.style.transform = '';
2811
+ }
2812
+ };
2813
+ const show = (i, dir) => {
2814
+ const list = items();
2815
+ if (!list.length) return;
2816
+ if (o.loop) i = (i + list.length) % list.length;
2817
+ else i = clampN(i, 0, list.length - 1);
2818
+ idx = i;
2819
+ setZoom(false);
2820
+ const it = info(list[i]);
2821
+ const stage = box.querySelector('.mozert-lb-stage');
2822
+ stage.innerHTML = '';
2823
+ const c = stageContent(it);
2824
+ stage.appendChild(c);
2825
+ c.classList.add('mozert-lb-in');
2826
+ if (dir) c.style.setProperty('--lb-from', dir > 0 ? '40px' : '-40px');
2827
+ box.querySelector('.mozert-lb-count').textContent = i + 1 + ' / ' + list.length;
2828
+ box.querySelector('.mozert-lb-cap').textContent = it.cap;
2829
+ box.setAttribute('aria-label', sl._label('imageOf', { n: i + 1, total: list.length }));
2830
+ const edge = !o.loop && list.length > 1;
2831
+ box.querySelector('.mozert-lb-prev').disabled = edge && i === 0;
2832
+ box.querySelector('.mozert-lb-next').disabled = edge && i === list.length - 1;
2833
+ // Preload the neighbours.
2834
+ [i - 1, i + 1].forEach((k) => {
2835
+ const n = list[(k + list.length) % list.length];
2836
+ const u = n && info(n).src;
2837
+ if (u) new Image().src = u;
2838
+ });
2839
+ if (sl.emit) sl.emit('lightboxChange', i);
2840
+ };
2841
+ const key = (e) => {
2842
+ if (!box) return;
2843
+ if (e.key === 'Escape') {
2844
+ e.preventDefault();
2845
+ close();
2846
+ } else if (e.key === 'ArrowRight') show(idx + 1, 1);
2847
+ else if (e.key === 'ArrowLeft') show(idx - 1, -1);
2848
+ else if (e.key === 'Tab') {
2849
+ const f = Array.from(box.querySelectorAll('button:not([disabled])'));
2850
+ if (!f.length) return;
2851
+ const first = f[0];
2852
+ const last = f[f.length - 1];
2853
+ if (e.shiftKey && document.activeElement === first) {
2854
+ e.preventDefault();
2855
+ last.focus();
2856
+ } else if (!e.shiftKey && document.activeElement === last) {
2857
+ e.preventDefault();
2858
+ first.focus();
2859
+ }
2860
+ }
2861
+ };
2862
+ const open = (i) => {
2863
+ if (box || !items().length) return;
2864
+ opener = document.activeElement;
2865
+ const lb = (k) => sl._label(k);
2866
+ const single = items().length < 2;
2867
+ box = mk('div', 'mozert-lb');
2868
+ box.setAttribute('role', 'dialog');
2869
+ box.setAttribute('aria-modal', 'true');
2870
+ box.innerHTML =
2871
+ '<div class="mozert-lb-stage"></div>' +
2872
+ '<div class="mozert-lb-bar"><span class="mozert-lb-count"></span>' +
2873
+ (o.zoom ? '<button type="button" class="mozert-lb-zoom" aria-label="' + lb('zoom') + '"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3M11 8v6M8 11h6"/></svg></button>' : '') +
2874
+ '<button type="button" class="mozert-lb-close" aria-label="' + lb('close') + '"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M5 5l14 14M19 5L5 19"/></svg></button></div>' +
2875
+ (single ? '' : '<button type="button" class="mozert-lb-prev" aria-label="' + lb('prev') + '"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.4" aria-hidden="true"><path d="M15 5l-7 7 7 7"/></svg></button><button type="button" class="mozert-lb-next" aria-label="' + lb('next') + '"><svg viewBox="0 0 24 24" width="22" height="22" fill="none" stroke="currentColor" stroke-width="2.4" aria-hidden="true"><path d="M9 5l7 7-7 7"/></svg></button>') +
2876
+ '<div class="mozert-lb-cap"></div>';
2877
+ if (single) {
2878
+ box.insertAdjacentHTML('beforeend', '<button type="button" class="mozert-lb-prev" hidden></button><button type="button" class="mozert-lb-next" hidden></button>');
2879
+ }
2880
+ document.body.appendChild(box);
2881
+ document.documentElement.classList.add('mozert-lb-open');
2882
+ show(i);
2883
+ requestAnimationFrame(() => box && box.classList.add('mozert-lb-show'));
2884
+ const stage = box.querySelector('.mozert-lb-stage');
2885
+ box.querySelector('.mozert-lb-close').addEventListener('click', close);
2886
+ const pv = box.querySelector('.mozert-lb-prev');
2887
+ const nx = box.querySelector('.mozert-lb-next');
2888
+ pv.addEventListener('click', () => show(idx - 1, -1));
2889
+ nx.addEventListener('click', () => show(idx + 1, 1));
2890
+ const zb = box.querySelector('.mozert-lb-zoom');
2891
+ if (zb) zb.addEventListener('click', () => setZoom(!zoomed));
2892
+ // Click outside the picture closes, click on the picture toggles zoom, swipe changes picture.
2893
+ stage.addEventListener('click', (e) => {
2894
+ if (drag && drag.moved) return;
2895
+ if (e.target.closest && e.target.closest('.mozert-lb-img,.mozert-lb-clone')) setZoom(!zoomed, e);
2896
+ else if (e.target === stage) close();
2897
+ });
2898
+ stage.addEventListener('pointerdown', (e) => {
2899
+ drag = { x: e.clientX, y: e.clientY, moved: false, id: e.pointerId };
2900
+ });
2901
+ stage.addEventListener('pointermove', (e) => {
2902
+ // While zoomed, the mouse pans the picture (the zoom follows the pointer).
2903
+ if (zoomed && zrect && e.pointerType === 'mouse' && !drag) {
2904
+ const z = box.querySelector('.mozert-lb-img,.mozert-lb-clone');
2905
+ if (z) z.style.transformOrigin = clampN(((e.clientX - zrect.left) / zrect.width) * 100, 0, 100) + '% ' + clampN(((e.clientY - zrect.top) / zrect.height) * 100, 0, 100) + '%';
2906
+ }
2907
+ if (!drag) return;
2908
+ const dx = e.clientX - drag.x;
2909
+ if (Math.abs(dx) > 6 || Math.abs(e.clientY - drag.y) > 6) drag.moved = true;
2910
+ });
2911
+ stage.addEventListener('pointerup', (e) => {
2912
+ if (!drag) return;
2913
+ const dx = e.clientX - drag.x;
2914
+ const moved = drag.moved;
2915
+ if (moved && !zoomed && Math.abs(dx) > 50) show(idx + (dx < 0 ? 1 : -1), dx < 0 ? 1 : -1);
2916
+ setTimeout(() => (drag = null), 0);
2917
+ });
2918
+ document.addEventListener('keydown', key, true);
2919
+ (box.querySelector('.mozert-lb-close') || box).focus();
2920
+ if (sl.emit) sl.emit('lightboxOpen', i);
2921
+ };
2922
+ const close = () => {
2923
+ if (!box) return;
2924
+ const b = box;
2925
+ box = null;
2926
+ document.removeEventListener('keydown', key, true);
2927
+ document.documentElement.classList.remove('mozert-lb-open');
2928
+ b.classList.remove('mozert-lb-show');
2929
+ setTimeout(() => b.parentNode && b.parentNode.removeChild(b), 180);
2930
+ if (sl.active && idx !== sl.current && sl.canNav) sl.goTo(idx);
2931
+ if (opener && opener.focus) {
2932
+ try {
2933
+ opener.focus({ preventScroll: true });
2934
+ } catch (e) {}
2935
+ }
2936
+ if (sl.emit) sl.emit('lightboxClose', idx);
2937
+ };
2938
+ sl.el.classList.add('mozert-has-lightbox');
2939
+ sl._on(sl.list, 'click', (e) => {
2940
+ const slide = e.target.closest && e.target.closest('.mozert-slide');
2941
+ if (!slide || !sl.track.contains(slide)) return;
2942
+ if (e.target.closest('button, input, select, textarea, [data-no-lightbox]')) return;
2943
+ const a = e.target.closest('a[href]');
2944
+ if (a && !isImg(a.getAttribute('href'))) return;
2945
+ const i = parseInt(slide.getAttribute('data-mozert-index'), 10);
2946
+ if (isNaN(i)) return;
2947
+ if (a) e.preventDefault();
2948
+ open(i % Math.max(1, items().length));
2949
+ });
2950
+ sl.lightbox = { open, close, get isOpen() { return !!box; } };
2951
+ return {
2952
+ destroy() {
2953
+ if (box) {
2954
+ box.parentNode && box.parentNode.removeChild(box);
2955
+ box = null;
2956
+ document.removeEventListener('keydown', key, true);
2957
+ document.documentElement.classList.remove('mozert-lb-open');
2958
+ }
2959
+ sl.el.classList.remove('mozert-has-lightbox');
2960
+ delete sl.lightbox;
2961
+ },
2962
+ };
2963
+ },
2964
+ });
2965
+
2966
+ /* ---------------------------------------------------------- magnifier */
2967
+ // magnifier: true | { zoom: 2.2, button: true, active: false }. Hover zoom like a product page: with the magnifier on,
2968
+ // the picture under the mouse is enlarged around the pointer. A small button in the top right corner turns it on and off.
2969
+ // A slide's image can name a sharper file with data-zoom (or data-full), which is loaded the first time it is magnified.
2970
+ Mozert.use({
2971
+ name: 'magnifier',
2972
+ defaults: { magnifier: false },
2973
+ create(sl) {
2974
+ if (!sl.s.magnifier) return null;
2975
+ const o = objOpt(sl.s.magnifier, { zoom: 2.2, button: true, active: false });
2976
+ let on = sl._magOn !== undefined ? sl._magOn : !!o.active;
2977
+ let cur = null;
2978
+ let btn = null;
2979
+ let pressed = false;
2980
+ sl.el.classList.add('mozert-has-magnifier');
2981
+ const targetOf = (slide) => slide.querySelector('img,picture,canvas,video') || slide.firstElementChild;
2982
+ const reset = () => {
2983
+ if (!cur) return;
2984
+ cur.node.style.transform = '';
2985
+ cur.node.style.transformOrigin = '';
2986
+ cur.node.classList.remove('mozert-mag-node');
2987
+ cur.slide.classList.remove('mozert-mag-host');
2988
+ cur = null;
2989
+ };
2990
+ const hires = (node) => {
2991
+ const img = node.tagName === 'IMG' ? node : node.querySelector && node.querySelector('img');
2992
+ const url = img && (img.getAttribute('data-zoom') || img.getAttribute('data-full'));
2993
+ if (!img || !url || img.__mozMag) return;
2994
+ img.__mozMag = 1;
2995
+ const pre = new Image();
2996
+ pre.onload = () => {
2997
+ img.src = url;
2998
+ };
2999
+ pre.src = url;
3000
+ };
3001
+ const move = (e) => {
3002
+ if (!on || pressed || e.pointerType !== 'mouse') return;
3003
+ const slide = e.target.closest && e.target.closest('.mozert-slide');
3004
+ if (!slide || !sl.track.contains(slide) || (e.target.closest && e.target.closest('.mozert-mag-btn'))) {
3005
+ reset();
3006
+ return;
3007
+ }
3008
+ if (!cur || cur.slide !== slide) {
3009
+ reset();
3010
+ const node = targetOf(slide);
3011
+ if (!node) return;
3012
+ cur = { slide, node };
3013
+ slide.classList.add('mozert-mag-host');
3014
+ node.classList.add('mozert-mag-node');
3015
+ hires(node);
3016
+ }
3017
+ const r = cur.slide.getBoundingClientRect();
3018
+ cur.node.style.transformOrigin = clampN(((e.clientX - r.left) / r.width) * 100, 0, 100) + '% ' + clampN(((e.clientY - r.top) / r.height) * 100, 0, 100) + '%';
3019
+ cur.node.style.transform = 'scale(' + o.zoom + ')';
3020
+ };
3021
+ const paint = () => {
3022
+ sl.el.classList.toggle('mozert-mag-on', on);
3023
+ if (btn) {
3024
+ btn.setAttribute('aria-pressed', on ? 'true' : 'false');
3025
+ btn.classList.toggle('mozert-on', on);
3026
+ }
3027
+ if (!on) reset();
3028
+ };
3029
+ const set = (v) => {
3030
+ on = !!v;
3031
+ sl._magOn = on;
3032
+ paint();
3033
+ sl.emit('magnifier', on);
3034
+ };
3035
+ sl._on(sl.list, 'pointermove', move);
3036
+ sl._on(sl.list, 'pointerleave', reset);
3037
+ // Dragging the slider must feel normal: drop the zoom while the button is held down.
3038
+ sl._on(sl.list, 'pointerdown', () => {
3039
+ pressed = true;
3040
+ reset();
3041
+ });
3042
+ sl._on(window, 'pointerup', () => {
3043
+ pressed = false;
3044
+ });
3045
+ sl._sub('beforeChange', reset);
3046
+ if (o.button) {
3047
+ btn = mk('button', 'mozert-mag-btn');
3048
+ btn.type = 'button';
3049
+ btn.setAttribute('aria-label', sl._label('magnifier'));
3050
+ btn.innerHTML = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="10.5" cy="10.5" r="6.5"/><path d="M15.5 15.5L21 21"/></svg>';
3051
+ btn.addEventListener('click', (e) => {
3052
+ e.stopPropagation();
3053
+ set(!on);
3054
+ });
3055
+ sl.el.appendChild(btn);
3056
+ }
3057
+ paint();
3058
+ sl.magnifier = { enable: () => set(true), disable: () => set(false), toggle: () => set(!on), isOn: () => on };
3059
+ return {
3060
+ destroy() {
3061
+ reset();
3062
+ if (btn && btn.parentNode) btn.parentNode.removeChild(btn);
3063
+ sl.el.classList.remove('mozert-has-magnifier', 'mozert-mag-on');
3064
+ delete sl.magnifier;
3065
+ },
3066
+ };
3067
+ },
3068
+ });
3069
+
3070
+ /* -------------------------------------------------------- scrollDriven */
3071
+ // scrollDriven: true | { speed: 1.6, step: 0, top: 'center' | 0, offset: 0 }.
3072
+ // The slider sticks while the user scrolls and the scroll moves the slides. `speed` is how many px the slides travel
3073
+ // per px of page scroll (higher = less page height used); `step` forces a fixed number of px per slide instead.
3074
+ // `top: 'center'` pins the slider in the middle of the screen, a number pins it that many px from the top;
3075
+ // `offset` adds room for a sticky header. Works with non-looping, horizontal sliders.
3076
+ Mozert.use({
3077
+ name: 'scrollDriven',
3078
+ defaults: { scrollDriven: false },
3079
+ create(sl) {
3080
+ const c = sl.s.scrollDriven;
3081
+ if (!c || sl.s.vertical || sl.s.infinite || sl.s.stacked || !sl.canNav) return null;
3082
+ const o = objOpt(c, { step: 0, top: 0, speed: 1.6, offset: 0, fill: false, bottom: 0 });
3083
+ const el = sl.el;
3084
+ const wrap = mk('div', 'mozert-sd');
3085
+ el.parentNode.insertBefore(wrap, el);
3086
+ wrap.appendChild(el);
3087
+ el.classList.add('mozert-sd-stick');
3088
+ let topPx = 0;
3089
+ const place = () => {
3090
+ if (o.fill) {
3091
+ // fill: the pinned slider takes the whole screen height (minus offset/bottom), so there is no empty space around it
3092
+ topPx = (o.top === 'center' ? 0 : parseFloat(o.top) || 0) + (parseFloat(o.offset) || 0);
3093
+ el.classList.add('mozert-sd-fill');
3094
+ el.style.height = Math.max(160, window.innerHeight - topPx - (parseFloat(o.bottom) || 0)) + 'px';
3095
+ el.style.top = topPx + 'px';
3096
+ return;
3097
+ }
3098
+ const h = el.offsetHeight;
3099
+ topPx = o.top === 'center' ? Math.max(0, Math.round((window.innerHeight - h) / 2)) : parseFloat(o.top) || 0;
3100
+ topPx += parseFloat(o.offset) || 0;
3101
+ el.style.top = topPx + 'px';
3102
+ };
3103
+ let raf = 0;
3104
+ let lastIdx = -1;
3105
+ const steps = () => Math.max(1, sl.maxIndex);
3106
+ const totalPx = () => {
3107
+ if (o.step) return steps() * o.step;
3108
+ const travel = Math.abs(sl._pos(sl.maxIndex) - sl._pos(0));
3109
+ return Math.max(240, Math.round(travel / Math.max(0.2, o.speed)));
3110
+ };
3111
+ const size = () => {
3112
+ place();
3113
+ wrap.style.height = el.offsetHeight + totalPx() + 'px';
3114
+ };
3115
+ const update = () => {
3116
+ raf = 0;
3117
+ if (!sl.active) return;
3118
+ const r = wrap.getBoundingClientRect();
3119
+ const total = totalPx();
3120
+ const p = clampN((topPx - r.top) / total, 0, 1);
3121
+ const f = p * steps();
3122
+ const i0 = Math.floor(f);
3123
+ const i1 = Math.min(sl.maxIndex, i0 + 1);
3124
+ const k = f - i0;
3125
+ const idx = Math.round(f);
3126
+ if (idx !== lastIdx) {
3127
+ lastIdx = idx;
3128
+ sl.goTo(idx, true);
3129
+ }
3130
+ sl._setPos(sl._pos(i0) + (sl._pos(i1) - sl._pos(i0)) * k, false);
3131
+ wrap.style.setProperty('--sd-progress', p.toFixed(4));
3132
+ };
3133
+ const queue = () => {
3134
+ if (!raf) raf = requestAnimationFrame(update);
3135
+ };
3136
+ size();
3137
+ sl._on(window, 'scroll', queue, { passive: true });
3138
+ sl._on(window, 'resize', () => {
3139
+ size();
3140
+ queue();
3141
+ });
3142
+ sl._sub('reInit', () => {
3143
+ size();
3144
+ queue();
3145
+ });
3146
+ update();
3147
+ sl.scrollDriven = { update };
3148
+ return {
3149
+ destroy() {
3150
+ if (raf) cancelAnimationFrame(raf);
3151
+ el.classList.remove('mozert-sd-stick', 'mozert-sd-fill');
3152
+ el.style.top = '';
3153
+ if (o.fill) el.style.height = '';
3154
+ if (wrap.parentNode) {
3155
+ wrap.parentNode.insertBefore(el, wrap);
3156
+ wrap.parentNode.removeChild(wrap);
3157
+ }
3158
+ delete sl.scrollDriven;
3159
+ },
3160
+ };
3161
+ },
3162
+ });
3163
+ })();
3164
+
3165
+
3166
+ return Mozert;
3167
+ })();
3168
+ export { Mozert };
3169
+ export default Mozert;