PageSourceSearch

https://reactbits.dev/assets/ImageTrailDemo-CzIIRD8d.js

js reactbits.dev collected 2026-10-03 19:23:44 UTC 169,960 bytes, 5,413 lines download raw bytes

1import{r as u,k as e,y as v,x as s,T as o}from"./index-Bn1bbOAe.js";import{u as g,C as P,T as p,P as M,O as I,a as x,b as y,c as f}from"./useComponentProps-CkrCaLvB.js";import{D as E}from"./Dependencies-BritF_2p.js";import{u as L}from"./useForceRerender-CvsfwHEi.js";import{C as T}from"./Customize-DBLruEgy.js";import{P as b}from"./PreviewSelect-CW4bTxJK.js";import R from"./ImageTrail-Cz7kGx8T.js";import"./index-CKdJydTQ.js";import"./backgroundThemeProps-BAOOEp3Q.js";import"./ShikiCode-CUach_VP.js";import"./aiExport--nnywH9U.js";import"./IconSelect-CgfYlisg.js";import"./sparkles-DtoGwhqa.js";import"./maximize-2-DQy3aI7-.js";import"./copy--0PRdOLE.js";import"./preview-slider-B9GA5uEa.js";const D=`'use client';
2
3import { useRef, useEffect } from 'react';
4import { gsap } from 'gsap';
5
6import './ImageTrail.css';
7
8function lerp(a, b, n) {
9  return (1 - n) * a + n * b;
10}
11
12function getLocalPointerPos(e, rect) {
13  let clientX = 0,
14    clientY = 0;
15  if (e.touches && e.touches.length > 0) {
16    clientX = e.touches[0].clientX;
17    clientY = e.touches[0].clientY;
18  } else {
19    clientX = e.clientX;
20    clientY = e.clientY;
21  }
22  return {
23    x: clientX - rect.left,
24    y: clientY - rect.top
25  };
26}
27function getMouseDistance(p1, p2) {
28  const dx = p1.x - p2.x;
29  const dy = p1.y - p2.y;
30  return Math.hypot(dx, dy);
31}
32
33class ImageItem {
34  DOM = { el: null, inner: null };
35  defaultStyle = { scale: 1, x: 0, y: 0, opacity: 0 };
36  rect = null;
37
38  constructor(DOM_el) {
39    this.DOM.el = DOM_el;
40    this.DOM.inner = this.DOM.el.querySelector('.content__img-inner');
41    this.getRect();
42    this.initEvents();
43  }
44  initEvents() {
45    this.resize = () => {
46      gsap.set(this.DOM.el, this.defaultStyle);
47      this.getRect();
48    };
49    window.addEventListener('resize', this.resize);
50  }
51  getRect() {
52    this.rect = this.DOM.el.getBoundingClientRect();
53  }
54
55  destroy() {
56    window.removeEventListener('resize', this.resize);
57  }
58}
59
60class ImageTrailVariant1 {
61  constructor(container) {
62    this.container = container;
63    this.rafId = null;
64    this.destroyed = false;
65    this.DOM = { el: container };
66    this.images = [...this.DOM.el.querySelectorAll('.content__img')].map(img => new ImageItem(img));
67    this.imagesTotal = this.images.length;
68    this.imgPosition = 0;
69    this.zIndexVal = 1;
70    this.activeImagesCount = 0;
71    this.isIdle = true;
72    this.threshold = 80;
73
74    this.mousePos = { x: 0, y: 0 };
75    this.lastMousePos = { x: 0, y: 0 };
76    this.cacheMousePos = { x: 0, y: 0 };
77
78    const handlePointerMove = ev => {
79      const rect = this.container.getBoundingClientRect();
80      this.mousePos = getLocalPointerPos(ev, rect);
81    };
82    container.addEventListener('mousemove', handlePointerMove);
83    container.addEventListener('touchmove', handlePointerMove);
84
85    const initRender = ev => {
86      const rect = this.container.getBoundingClientRect();
87      this.mousePos = getLocalPointerPos(ev, rect);
88      this.cacheMousePos = { ...this.mousePos };
89
90      this.rafId = requestAnimationFrame(() => this.render());
91
92      container.removeEventListener('mousemove', initRender);
93      container.removeEventListener('touchmove', initRender);
94    };
95    container.addEventListener('mousemove', initRender);
96    container.addEventListener('touchmove', initRender);
97    this.handlePointerMove = handlePointerMove;
98    this.initRender = initRender;
99  }
100
101  render() {
102    if (this.destroyed) return;
103
104    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
105    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
106    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
107
108    if (distance > this.threshold) {
109      this.showNextImage();
110      this.lastMousePos = { ...this.mousePos };
111    }
112    if (this.isIdle && this.zIndexVal !== 1) {
113      this.zIndexVal = 1;
114    }
115    this.rafId = requestAnimationFrame(() => this.render());
116  }
117
118  showNextImage() {
119    ++this.zIndexVal;
120    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
121    const img = this.images[this.imgPosition];
122
123    gsap.killTweensOf(img.DOM.el);
124    gsap
125      .timeline({
126        onStart: () => this.onImageActivated(),
127        onComplete: () => this.onImageDeactivated()
128      })
129      .fromTo(
130        img.DOM.el,
131        {
132          opacity: 1,
133          scale: 1,
134          zIndex: this.zIndexVal,
135          x: this.cacheMousePos.x - img.rect.width / 2,
136          y: this.cacheMousePos.y - img.rect.height / 2
137        },
138        {
139          duration: 0.4,
140          ease: 'power1',
141          x: this.mousePos.x - img.rect.width / 2,
142          y: this.mousePos.y - img.rect.height / 2
143        },
144        0
145      )
146      .to(
147        img.DOM.el,
148        {
149          duration: 0.4,
150          ease: 'power3',
151          opacity: 0,
152          scale: 0.2
153        },
154        0.4
155      );
156  }
157
158  destroy() {
159    this.destroyed = true;
160    if (this.rafId !== null) {
161      cancelAnimationFrame(this.rafId);
162      this.rafId = null;
163    }
164    this.container.removeEventListener('mousemove', this.handlePointerMove);
165    this.container.removeEventListener('touchmove', this.handlePointerMove);
166    this.container.removeEventListener('mousemove', this.initRender);
167    this.container.removeEventListener('touchmove', this.initRender);
168    this.images.forEach(img => {
169      gsap.killTweensOf(img.DOM.el);
170      img.destroy();
171    });
172  }
173
174  onImageActivated() {
175    this.activeImagesCount++;
176    this.isIdle = false;
177  }
178  onImageDeactivated() {
179    this.activeImagesCount--;
180    if (this.activeImagesCount === 0) {
181      this.isIdle = true;
182    }
183  }
184}
185
186class ImageTrailVariant2 {
187  constructor(container) {
188    this.container = container;
189    this.rafId = null;
190    this.destroyed = false;
191    this.DOM = { el: container };
192    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
193    this.imagesTotal = this.images.length;
194    this.imgPosition = 0;
195    this.zIndexVal = 1;
196    this.activeImagesCount = 0;
197    this.isIdle = true;
198    this.threshold = 80;
199
200    this.mousePos = { x: 0, y: 0 };
201    this.lastMousePos = { x: 0, y: 0 };
202    this.cacheMousePos = { x: 0, y: 0 };
203
204    const handlePointerMove = ev => {
205      const rect = container.getBoundingClientRect();
206      this.mousePos = getLocalPointerPos(ev, rect);
207    };
208    container.addEventListener('mousemove', handlePointerMove);
209    container.addEventListener('touchmove', handlePointerMove);
210
211    const initRender = ev => {
212      const rect = container.getBoundingClientRect();
213      this.mousePos = getLocalPointerPos(ev, rect);
214      this.cacheMousePos = { ...this.mousePos };
215
216      this.rafId = requestAnimationFrame(() => this.render());
217
218      container.removeEventListener('mousemove', initRender);
219      container.removeEventListener('touchmove', initRender);
220    };
221    container.addEventListener('mousemove', initRender);
222    container.addEventListener('touchmove', initRender);
223    this.handlePointerMove = handlePointerMove;
224    this.initRender = initRender;
225  }
226
227  render() {
228    if (this.destroyed) return;
229
230    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
231    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
232    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
233
234    if (distance > this.threshold) {
235      this.showNextImage();
236      this.lastMousePos = { ...this.mousePos };
237    }
238    if (this.isIdle && this.zIndexVal !== 1) {
239      this.zIndexVal = 1;
240    }
241    this.rafId = requestAnimationFrame(() => this.render());
242  }
243
244  showNextImage() {
245    ++this.zIndexVal;
246    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
247    const img = this.images[this.imgPosition];
248
249    gsap.killTweensOf(img.DOM.el);
250    gsap
251      .timeline({
252        onStart: () => this.onImageActivated(),
253        onComplete: () => this.onImageDeactivated()
254      })
255      .fromTo(
256        img.DOM.el,
257        {
258          opacity: 1,
259          scale: 0,
260          zIndex: this.zIndexVal,
261          x: this.cacheMousePos.x - img.rect.width / 2,
262          y: this.cacheMousePos.y - img.rect.height / 2
263        },
264        {
265          duration: 0.4,
266          ease: 'power1',
267          scale: 1,
268          x: this.mousePos.x - img.rect.width / 2,
269          y: this.mousePos.y - img.rect.height / 2
270        },
271        0
272      )
273      .fromTo(
274        img.DOM.inner,
275        {
276          scale: 2.8,
277          filter: 'brightness(250%)'
278        },
279        {
280          duration: 0.4,
281          ease: 'power1',
282          scale: 1,
283          filter: 'brightness(100%)'
284        },
285        0
286      )
287      .to(
288        img.DOM.el,
289        {
290          duration: 0.4,
291          ease: 'power2',
292          opacity: 0,
293          scale: 0.2
294        },
295        0.45
296      );
297  }
298
299  destroy() {
300    this.destroyed = true;
301    if (this.rafId !== null) {
302      cancelAnimationFrame(this.rafId);
303      this.rafId = null;
304    }
305    this.container.removeEventListener('mousemove', this.handlePointerMove);
306    this.container.removeEventListener('touchmove', this.handlePointerMove);
307    this.container.removeEventListener('mousemove', this.initRender);
308    this.container.removeEventListener('touchmove', this.initRender);
309    this.images.forEach(img => {
310      gsap.killTweensOf(img.DOM.el);
311      img.destroy();
312    });
313  }
314
315  onImageActivated() {
316    this.activeImagesCount++;
317    this.isIdle = false;
318  }
319  onImageDeactivated() {
320    this.activeImagesCount--;
321    if (this.activeImagesCount === 0) this.isIdle = true;
322  }
323}
324
325class ImageTrailVariant3 {
326  constructor(container) {
327    this.container = container;
328    this.rafId = null;
329    this.destroyed = false;
330    this.DOM = { el: container };
331    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
332    this.imagesTotal = this.images.length;
333    this.imgPosition = 0;
334    this.zIndexVal = 1;
335    this.activeImagesCount = 0;
336    this.isIdle = true;
337    this.threshold = 80;
338
339    this.mousePos = { x: 0, y: 0 };
340    this.lastMousePos = { x: 0, y: 0 };
341    this.cacheMousePos = { x: 0, y: 0 };
342
343    const handlePointerMove = ev => {
344      const rect = container.getBoundingClientRect();
345      this.mousePos = getLocalPointerPos(ev, rect);
346    };
347    container.addEventListener('mousemove', handlePointerMove);
348    container.addEventListener('touchmove', handlePointerMove);
349
350    const initRender = ev => {
351      const rect = container.getBoundingClientRect();
352      this.mousePos = getLocalPointerPos(ev, rect);
353      this.cacheMousePos = { ...this.mousePos };
354
355      this.rafId = requestAnimationFrame(() => this.render());
356      container.removeEventListener('mousemove', initRender);
357      container.removeEventListener('touchmove', initRender);
358    };
359    container.addEventListener('mousemove', initRender);
360    container.addEventListener('touchmove', initRender);
361    this.handlePointerMove = handlePointerMove;
362    this.initRender = initRender;
363  }
364
365  render() {
366    if (this.destroyed) return;
367
368    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
369    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
370    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
371
372    if (distance > this.threshold) {
373      this.showNextImage();
374      this.lastMousePos = { ...this.mousePos };
375    }
376    if (this.isIdle && this.zIndexVal !== 1) {
377      this.zIndexVal = 1;
378    }
379    this.rafId = requestAnimationFrame(() => this.render());
380  }
381
382  showNextImage() {
383    ++this.zIndexVal;
384    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
385    const img = this.images[this.imgPosition];
386    gsap.killTweensOf(img.DOM.el);
387
388    gsap
389      .timeline({
390        onStart: () => this.onImageActivated(),
391        onComplete: () => this.onImageDeactivated()
392      })
393      .fromTo(
394        img.DOM.el,
395        {
396          opacity: 1,
397          scale: 0,
398          zIndex: this.zIndexVal,
399          xPercent: 0,
400          yPercent: 0,
401          x: this.cacheMousePos.x - img.rect.width / 2,
402          y: this.cacheMousePos.y - img.rect.height / 2
403        },
404        {
405          duration: 0.4,
406          ease: 'power1',
407          scale: 1,
408          x: this.mousePos.x - img.rect.width / 2,
409          y: this.mousePos.y - img.rect.height / 2
410        },
411        0
412      )
413      .fromTo(
414        img.DOM.inner,
415        {
416          scale: 1.2
417        },
418        {
419          duration: 0.4,
420          ease: 'power1',
421          scale: 1
422        },
423        0
424      )
425      .to(
426        img.DOM.el,
427        {
428          duration: 0.6,
429          ease: 'power2',
430          opacity: 0,
431          scale: 0.2,
432          xPercent: () => gsap.utils.random(-30, 30),
433          yPercent: -200
434        },
435        0.6
436      );
437  }
438
439  destroy() {
440    this.destroyed = true;
441    if (this.rafId !== null) {
442      cancelAnimationFrame(this.rafId);
443      this.rafId = null;
444    }
445    this.container.removeEventListener('mousemove', this.handlePointerMove);
446    this.container.removeEventListener('touchmove', this.handlePointerMove);
447    this.container.removeEventListener('mousemove', this.initRender);
448    this.container.removeEventListener('touchmove', this.initRender);
449    this.images.forEach(img => {
450      gsap.killTweensOf(img.DOM.el);
451      img.destroy();
452    });
453  }
454
455  onImageActivated() {
456    this.activeImagesCount++;
457    this.isIdle = false;
458  }
459  onImageDeactivated() {
460    this.activeImagesCount--;
461    if (this.activeImagesCount === 0) this.isIdle = true;
462  }
463}
464
465class ImageTrailVariant4 {
466  constructor(container) {
467    this.container = container;
468    this.rafId = null;
469    this.destroyed = false;
470    this.DOM = { el: container };
471    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
472    this.imagesTotal = this.images.length;
473    this.imgPosition = 0;
474    this.zIndexVal = 1;
475    this.activeImagesCount = 0;
476    this.isIdle = true;
477    this.threshold = 80;
478
479    this.mousePos = { x: 0, y: 0 };
480    this.lastMousePos = { x: 0, y: 0 };
481    this.cacheMousePos = { x: 0, y: 0 };
482
483    const handlePointerMove = ev => {
484      const rect = container.getBoundingClientRect();
485      this.mousePos = getLocalPointerPos(ev, rect);
486    };
487    container.addEventListener('mousemove', handlePointerMove);
488    container.addEventListener('touchmove', handlePointerMove);
489
490    const initRender = ev => {
491      const rect = container.getBoundingClientRect();
492      this.mousePos = getLocalPointerPos(ev, rect);
493      this.cacheMousePos = { ...this.mousePos };
494      this.rafId = requestAnimationFrame(() => this.render());
495      container.removeEventListener('mousemove', initRender);
496      container.removeEventListener('touchmove', initRender);
497    };
498    container.addEventListener('mousemove', initRender);
499    container.addEventListener('touchmove', initRender);
500    this.handlePointerMove = handlePointerMove;
501    this.initRender = initRender;
502  }
503
504  render() {
505    if (this.destroyed) return;
506
507    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
508    if (distance > this.threshold) {
509      this.showNextImage();
510      this.lastMousePos = { ...this.mousePos };
511    }
512    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
513    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
514
515    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
516    this.rafId = requestAnimationFrame(() => this.render());
517  }
518
519  showNextImage() {
520    ++this.zIndexVal;
521    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
522    const img = this.images[this.imgPosition];
523    gsap.killTweensOf(img.DOM.el);
524
525    let dx = this.mousePos.x - this.cacheMousePos.x;
526    let dy = this.mousePos.y - this.cacheMousePos.y;
527    let distance = Math.sqrt(dx * dx + dy * dy);
528    if (distance !== 0) {
529      dx /= distance;
530      dy /= distance;
531    }
532    dx *= distance / 100;
533    dy *= distance / 100;
534
535    gsap
536      .timeline({
537        onStart: () => this.onImageActivated(),
538        onComplete: () => this.onImageDeactivated()
539      })
540      .fromTo(
541        img.DOM.el,
542        {
543          opacity: 1,
544          scale: 0,
545          zIndex: this.zIndexVal,
546          x: this.cacheMousePos.x - img.rect.width / 2,
547          y: this.cacheMousePos.y - img.rect.height / 2
548        },
549        {
550          duration: 0.4,
551          ease: 'power1',
552          scale: 1,
553          x: this.mousePos.x - img.rect.width / 2,
554          y: this.mousePos.y - img.rect.height / 2
555        },
556        0
557      )
558      .fromTo(
559        img.DOM.inner,
560        {
561          scale: 2,
562          filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max((400 * distance) / 100, 100)}%)\`
563        },
564        {
565          duration: 0.4,
566          ease: 'power1',
567          scale: 1,
568          filter: 'brightness(100%) contrast(100%)'
569        },
570        0
571      )
572      .to(
573        img.DOM.el,
574        {
575          duration: 0.4,
576          ease: 'power3',
577          opacity: 0
578        },
579        0.4
580      )
581      .to(
582        img.DOM.el,
583        {
584          duration: 1.5,
585          ease: 'power4',
586          x: \`+=\${dx * 110}\`,
587          y: \`+=\${dy * 110}\`
588        },
589        0.05
590      );
591  }
592
593  destroy() {
594    this.destroyed = true;
595    if (this.rafId !== null) {
596      cancelAnimationFrame(this.rafId);
597      this.rafId = null;
598    }
599    this.container.removeEventListener('mousemove', this.handlePointerMove);
600    this.container.removeEventListener('touchmove', this.handlePointerMove);
601    this.container.removeEventListener('mousemove', this.initRender);
602    this.container.removeEventListener('touchmove', this.initRender);
603    this.images.forEach(img => {
604      gsap.killTweensOf(img.DOM.el);
605      img.destroy();
606    });
607  }
608
609  onImageActivated() {
610    this.activeImagesCount++;
611    this.isIdle = false;
612  }
613  onImageDeactivated() {
614    this.activeImagesCount--;
615    if (this.activeImagesCount === 0) this.isIdle = true;
616  }
617}
618
619class ImageTrailVariant5 {
620  constructor(container) {
621    this.container = container;
622    this.rafId = null;
623    this.destroyed = false;
624    this.DOM = { el: container };
625    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
626    this.imagesTotal = this.images.length;
627    this.imgPosition = 0;
628    this.zIndexVal = 1;
629    this.activeImagesCount = 0;
630    this.isIdle = true;
631    this.threshold = 80;
632
633    this.mousePos = { x: 0, y: 0 };
634    this.lastMousePos = { x: 0, y: 0 };
635    this.cacheMousePos = { x: 0, y: 0 };
636    this.lastAngle = 0;
637
638    const handlePointerMove = ev => {
639      const rect = container.getBoundingClientRect();
640      this.mousePos = getLocalPointerPos(ev, rect);
641    };
642    container.addEventListener('mousemove', handlePointerMove);
643    container.addEventListener('touchmove', handlePointerMove);
644
645    const initRender = ev => {
646      const rect = container.getBoundingClientRect();
647      this.mousePos = getLocalPointerPos(ev, rect);
648      this.cacheMousePos = { ...this.mousePos };
649      this.rafId = requestAnimationFrame(() => this.render());
650      container.removeEventListener('mousemove', initRender);
651      container.removeEventListener('touchmove', initRender);
652    };
653    container.addEventListener('mousemove', initRender);
654    container.addEventListener('touchmove', initRender);
655    this.handlePointerMove = handlePointerMove;
656    this.initRender = initRender;
657  }
658
659  render() {
660    if (this.destroyed) return;
661
662    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
663    if (distance > this.threshold) {
664      this.showNextImage();
665      this.lastMousePos = { ...this.mousePos };
666    }
667    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
668    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
669    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
670    this.rafId = requestAnimationFrame(() => this.render());
671  }
672
673  showNextImage() {
674    let dx = this.mousePos.x - this.cacheMousePos.x;
675    let dy = this.mousePos.y - this.cacheMousePos.y;
676    let angle = Math.atan2(dy, dx) * (180 / Math.PI);
677    if (angle < 0) angle += 360;
678    if (angle > 90 && angle <= 270) angle += 180;
679    const isMovingClockwise = angle >= this.lastAngle;
680    this.lastAngle = angle;
681    let startAngle = isMovingClockwise ? angle - 10 : angle + 10;
682    let distance = Math.sqrt(dx * dx + dy * dy);
683    if (distance !== 0) {
684      dx /= distance;
685      dy /= distance;
686    }
687    dx *= distance / 150;
688    dy *= distance / 150;
689
690    ++this.zIndexVal;
691    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
692    const img = this.images[this.imgPosition];
693    gsap.killTweensOf(img.DOM.el);
694
695    gsap
696      .timeline({
697        onStart: () => this.onImageActivated(),
698        onComplete: () => this.onImageDeactivated()
699      })
700      .fromTo(
701        img.DOM.el,
702        {
703          opacity: 1,
704          filter: 'brightness(80%)',
705          scale: 0.1,
706          zIndex: this.zIndexVal,
707          x: this.cacheMousePos.x - img.rect.width / 2,
708          y: this.cacheMousePos.y - img.rect.height / 2,
709          rotation: startAngle
710        },
711        {
712          duration: 1,
713          ease: 'power2',
714          scale: 1,
715          filter: 'brightness(100%)',
716          x: this.mousePos.x - img.rect.width / 2 + dx * 70,
717          y: this.mousePos.y - img.rect.height / 2 + dy * 70,
718          rotation: this.lastAngle
719        },
720        0
721      )
722      .to(
723        img.DOM.el,
724        {
725          duration: 0.4,
726          ease: 'expo',
727          opacity: 0
728        },
729        0.5
730      )
731      .to(
732        img.DOM.el,
733        {
734          duration: 1.5,
735          ease: 'power4',
736          x: \`+=\${dx * 120}\`,
737          y: \`+=\${dy * 120}\`
738        },
739        0.05
740      );
741  }
742
743  destroy() {
744    this.destroyed = true;
745    if (this.rafId !== null) {
746      cancelAnimationFrame(this.rafId);
747      this.rafId = null;
748    }
749    this.container.removeEventListener('mousemove', this.handlePointerMove);
750    this.container.removeEventListener('touchmove', this.handlePointerMove);
751    this.container.removeEventListener('mousemove', this.initRender);
752    this.container.removeEventListener('touchmove', this.initRender);
753    this.images.forEach(img => {
754      gsap.killTweensOf(img.DOM.el);
755      img.destroy();
756    });
757  }
758
759  onImageActivated() {
760    this.activeImagesCount++;
761    this.isIdle = false;
762  }
763  onImageDeactivated() {
764    this.activeImagesCount--;
765    if (this.activeImagesCount === 0) this.isIdle = true;
766  }
767}
768
769class ImageTrailVariant6 {
770  constructor(container) {
771    this.container = container;
772    this.rafId = null;
773    this.destroyed = false;
774    this.DOM = { el: container };
775    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
776    this.imagesTotal = this.images.length;
777    this.imgPosition = 0;
778    this.zIndexVal = 1;
779    this.activeImagesCount = 0;
780    this.isIdle = true;
781    this.threshold = 80;
782
783    this.mousePos = { x: 0, y: 0 };
784    this.lastMousePos = { x: 0, y: 0 };
785    this.cacheMousePos = { x: 0, y: 0 };
786
787    const handlePointerMove = ev => {
788      const rect = container.getBoundingClientRect();
789      this.mousePos = getLocalPointerPos(ev, rect);
790    };
791    container.addEventListener('mousemove', handlePointerMove);
792    container.addEventListener('touchmove', handlePointerMove);
793
794    const initRender = ev => {
795      const rect = container.getBoundingClientRect();
796      this.mousePos = getLocalPointerPos(ev, rect);
797      this.cacheMousePos = { ...this.mousePos };
798      this.rafId = requestAnimationFrame(() => this.render());
799      container.removeEventListener('mousemove', initRender);
800      container.removeEventListener('touchmove', initRender);
801    };
802    container.addEventListener('mousemove', initRender);
803    container.addEventListener('touchmove', initRender);
804    this.handlePointerMove = handlePointerMove;
805    this.initRender = initRender;
806  }
807
808  render() {
809    if (this.destroyed) return;
810
811    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
812    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
813    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
814
815    if (distance > this.threshold) {
816      this.showNextImage();
817      this.lastMousePos = { ...this.mousePos };
818    }
819    if (this.isIdle && this.zIndexVal !== 1) {
820      this.zIndexVal = 1;
821    }
822    this.rafId = requestAnimationFrame(() => this.render());
823  }
824
825  mapSpeedToSize(speed, minSize, maxSize) {
826    const maxSpeed = 200;
827    return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1);
828  }
829  mapSpeedToBrightness(speed, minB, maxB) {
830    const maxSpeed = 70;
831    return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1);
832  }
833  mapSpeedToBlur(speed, minBlur, maxBlur) {
834    const maxSpeed = 90;
835    return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1);
836  }
837  mapSpeedToGrayscale(speed, minG, maxG) {
838    const maxSpeed = 90;
839    return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1);
840  }
841
842  showNextImage() {
843    let dx = this.mousePos.x - this.cacheMousePos.x;
844    let dy = this.mousePos.y - this.cacheMousePos.y;
845    let speed = Math.sqrt(dx * dx + dy * dy);
846
847    ++this.zIndexVal;
848    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
849    const img = this.images[this.imgPosition];
850
851    let scaleFactor = this.mapSpeedToSize(speed, 0.3, 2);
852    let brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3);
853    let blurValue = this.mapSpeedToBlur(speed, 20, 0);
854    let grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0);
855
856    gsap.killTweensOf(img.DOM.el);
857    gsap
858      .timeline({
859        onStart: () => this.onImageActivated(),
860        onComplete: () => this.onImageDeactivated()
861      })
862      .fromTo(
863        img.DOM.el,
864        {
865          opacity: 1,
866          scale: 0,
867          zIndex: this.zIndexVal,
868          x: this.cacheMousePos.x - img.rect.width / 2,
869          y: this.cacheMousePos.y - img.rect.height / 2
870        },
871        {
872          duration: 0.8,
873          ease: 'power3',
874          scale: scaleFactor,
875          filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`,
876          x: this.mousePos.x - img.rect.width / 2,
877          y: this.mousePos.y - img.rect.height / 2
878        },
879        0
880      )
881      .fromTo(
882        img.DOM.inner,
883        {
884          scale: 2
885        },
886        {
887          duration: 0.8,
888          ease: 'power3',
889          scale: 1
890        },
891        0
892      )
893      .to(
894        img.DOM.el,
895        {
896          duration: 0.4,
897          ease: 'power3.in',
898          opacity: 0,
899          scale: 0.2
900        },
901        0.45
902      );
903  }
904
905  destroy() {
906    this.destroyed = true;
907    if (this.rafId !== null) {
908      cancelAnimationFrame(this.rafId);
909      this.rafId = null;
910    }
911    this.container.removeEventListener('mousemove', this.handlePointerMove);
912    this.container.removeEventListener('touchmove', this.handlePointerMove);
913    this.container.removeEventListener('mousemove', this.initRender);
914    this.container.removeEventListener('touchmove', this.initRender);
915    this.images.forEach(img => {
916      gsap.killTweensOf(img.DOM.el);
917      img.destroy();
918    });
919  }
920
921  onImageActivated() {
922    this.activeImagesCount++;
923    this.isIdle = false;
924  }
925  onImageDeactivated() {
926    this.activeImagesCount--;
927    if (this.activeImagesCount === 0) {
928      this.isIdle = true;
929    }
930  }
931}
932
933function getNewPosition(position, offset, arr) {
934  const realOffset = Math.abs(offset) % arr.length;
935  if (position - realOffset >= 0) {
936    return position - realOffset;
937  } else {
938    return arr.length - (realOffset - position);
939  }
940}
941class ImageTrailVariant7 {
942  constructor(container) {
943    this.container = container;
944    this.rafId = null;
945    this.destroyed = false;
946    this.DOM = { el: container };
947    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
948    this.imagesTotal = this.images.length;
949    this.imgPosition = 0;
950    this.zIndexVal = 1;
951    this.activeImagesCount = 0;
952    this.isIdle = true;
953    this.threshold = 80;
954
955    this.mousePos = { x: 0, y: 0 };
956    this.lastMousePos = { x: 0, y: 0 };
957    this.cacheMousePos = { x: 0, y: 0 };
958
959    this.visibleImagesCount = 0;
960    this.visibleImagesTotal = 9;
961    this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1);
962
963    const handlePointerMove = ev => {
964      const rect = container.getBoundingClientRect();
965      this.mousePos = getLocalPointerPos(ev, rect);
966    };
967    container.addEventListener('mousemove', handlePointerMove);
968    container.addEventListener('touchmove', handlePointerMove);
969
970    const initRender = ev => {
971      const rect = container.getBoundingClientRect();
972      this.mousePos = getLocalPointerPos(ev, rect);
973      this.cacheMousePos = { ...this.mousePos };
974      this.rafId = requestAnimationFrame(() => this.render());
975      container.removeEventListener('mousemove', initRender);
976      container.removeEventListener('touchmove', initRender);
977    };
978    container.addEventListener('mousemove', initRender);
979    container.addEventListener('touchmove', initRender);
980    this.handlePointerMove = handlePointerMove;
981    this.initRender = initRender;
982  }
983
984  render() {
985    if (this.destroyed) return;
986
987    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
988    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
989    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
990
991    if (distance > this.threshold) {
992      this.showNextImage();
993      this.lastMousePos = { ...this.mousePos };
994    }
995    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
996
997    this.rafId = requestAnimationFrame(() => this.render());
998  }
999
1000  showNextImage() {
1001    ++this.zIndexVal;
1002    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
1003    const img = this.images[this.imgPosition];
1004    ++this.visibleImagesCount;
1005
1006    gsap.killTweensOf(img.DOM.el);
1007    const scaleValue = gsap.utils.random(0.5, 1.6);
1008
1009    gsap
1010      .timeline({
1011        onStart: () => this.onImageActivated(),
1012        onComplete: () => this.onImageDeactivated()
1013      })
1014      .fromTo(
1015        img.DOM.el,
1016        {
1017          scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0),
1018          rotationZ: 0,
1019          opacity: 1,
1020          zIndex: this.zIndexVal,
1021          x: this.cacheMousePos.x - img.rect.width / 2,
1022          y: this.cacheMousePos.y - img.rect.height / 2
1023        },
1024        {
1025          duration: 0.4,
1026          ease: 'power3',
1027          scale: scaleValue,
1028          rotationZ: gsap.utils.random(-3, 3),
1029          x: this.mousePos.x - img.rect.width / 2,
1030          y: this.mousePos.y - img.rect.height / 2
1031        },
1032        0
1033      );
1034
1035    if (this.visibleImagesCount >= this.visibleImagesTotal) {
1036      const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images);
1037      const oldImg = this.images[lastInQueue];
1038      gsap.to(oldImg.DOM.el, {
1039        duration: 0.4,
1040        ease: 'power4',
1041        opacity: 0,
1042        scale: 1.3,
1043        onComplete: () => {
1044          if (this.activeImagesCount === 0) {
1045            this.isIdle = true;
1046          }
1047        }
1048      });
1049    }
1050  }
1051
1052  destroy() {
1053    this.destroyed = true;
1054    if (this.rafId !== null) {
1055      cancelAnimationFrame(this.rafId);
1056      this.rafId = null;
1057    }
1058    this.container.removeEventListener('mousemove', this.handlePointerMove);
1059    this.container.removeEventListener('touchmove', this.handlePointerMove);
1060    this.container.removeEventListener('mousemove', this.initRender);
1061    this.container.removeEventListener('touchmove', this.initRender);
1062    this.images.forEach(img => {
1063      gsap.killTweensOf(img.DOM.el);
1064      img.destroy();
1065    });
1066  }
1067
1068  onImageActivated() {
1069    this.activeImagesCount++;
1070    this.isIdle = false;
1071  }
1072  onImageDeactivated() {
1073    this.activeImagesCount--;
1074  }
1075}
1076
1077class ImageTrailVariant8 {
1078  constructor(container) {
1079    this.container = container;
1080    this.rafId = null;
1081    this.destroyed = false;
1082    this.DOM = { el: container };
1083    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
1084    this.imagesTotal = this.images.length;
1085    this.imgPosition = 0;
1086    this.zIndexVal = 1;
1087    this.activeImagesCount = 0;
1088    this.isIdle = true;
1089    this.threshold = 80;
1090
1091    this.mousePos = { x: 0, y: 0 };
1092    this.lastMousePos = { x: 0, y: 0 };
1093    this.cacheMousePos = { x: 0, y: 0 };
1094
1095    this.rotation = { x: 0, y: 0 };
1096    this.cachedRotation = { x: 0, y: 0 };
1097    this.zValue = 0;
1098    this.cachedZValue = 0;
1099
1100    const handlePointerMove = ev => {
1101      const rect = container.getBoundingClientRect();
1102      this.mousePos = getLocalPointerPos(ev, rect);
1103    };
1104    container.addEventListener('mousemove', handlePointerMove);
1105    container.addEventListener('touchmove', handlePointerMove);
1106
1107    const initRender = ev => {
1108      const rect = container.getBoundingClientRect();
1109      this.mousePos = getLocalPointerPos(ev, rect);
1110      this.cacheMousePos = { ...this.mousePos };
1111      this.rafId = requestAnimationFrame(() => this.render());
1112      container.removeEventListener('mousemove', initRender);
1113      container.removeEventListener('touchmove', initRender);
1114    };
1115    container.addEventListener('mousemove', initRender);
1116    container.addEventListener('touchmove', initRender);
1117    this.handlePointerMove = handlePointerMove;
1118    this.initRender = initRender;
1119  }
1120
1121  render() {
1122    if (this.destroyed) return;
1123
1124    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
1125    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
1126    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
1127
1128    if (distance > this.threshold) {
1129      this.showNextImage();
1130      this.lastMousePos = { ...this.mousePos };
1131    }
1132    if (this.isIdle && this.zIndexVal !== 1) {
1133      this.zIndexVal = 1;
1134    }
1135    this.rafId = requestAnimationFrame(() => this.render());
1136  }
1137
1138  showNextImage() {
1139    const rect = this.container.getBoundingClientRect();
1140    const centerX = rect.width / 2;
1141    const centerY = rect.height / 2;
1142    const relX = this.mousePos.x - centerX;
1143    const relY = this.mousePos.y - centerY;
1144
1145    this.rotation.x = -(relY / centerY) * 30;
1146    this.rotation.y = (relX / centerX) * 30;
1147    this.cachedRotation = { ...this.rotation };
1148
1149    const distanceFromCenter = Math.sqrt(relX * relX + relY * relY);
1150    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
1151    const proportion = distanceFromCenter / maxDistance;
1152    this.zValue = proportion * 1200 - 600;
1153    this.cachedZValue = this.zValue;
1154    const normalizedZ = (this.zValue + 600) / 1200;
1155    const brightness = 0.2 + normalizedZ * 2.3;
1156
1157    ++this.zIndexVal;
1158    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
1159    const img = this.images[this.imgPosition];
1160    gsap.killTweensOf(img.DOM.el);
1161
1162    gsap
1163      .timeline({
1164        onStart: () => this.onImageActivated(),
1165        onComplete: () => this.onImageDeactivated()
1166      })
1167      .set(this.DOM.el, { perspective: 1000 }, 0)
1168      .fromTo(
1169        img.DOM.el,
1170        {
1171          opacity: 1,
1172          z: 0,
1173          scale: 1 + this.cachedZValue / 1000,
1174          zIndex: this.zIndexVal,
1175          x: this.cacheMousePos.x - img.rect.width / 2,
1176          y: this.cacheMousePos.y - img.rect.height / 2,
1177          rotationX: this.cachedRotation.x,
1178          rotationY: this.cachedRotation.y,
1179          filter: \`brightness(\${brightness})\`
1180        },
1181        {
1182          duration: 1,
1183          ease: 'expo',
1184          scale: 1 + this.zValue / 1000,
1185          x: this.mousePos.x - img.rect.width / 2,
1186          y: this.mousePos.y - img.rect.height / 2,
1187          rotationX: this.rotation.x,
1188          rotationY: this.rotation.y
1189        },
1190        0
1191      )
1192      .to(
1193        img.DOM.el,
1194        {
1195          duration: 0.4,
1196          ease: 'power2',
1197          opacity: 0,
1198          z: -800
1199        },
1200        0.3
1201      );
1202  }
1203
1204  destroy() {
1205    this.destroyed = true;
1206    if (this.rafId !== null) {
1207      cancelAnimationFrame(this.rafId);
1208      this.rafId = null;
1209    }
1210    this.container.removeEventListener('mousemove', this.handlePointerMove);
1211    this.container.removeEventListener('touchmove', this.handlePointerMove);
1212    this.container.removeEventListener('mousemove', this.initRender);
1213    this.container.removeEventListener('touchmove', this.initRender);
1214    this.images.forEach(img => {
1215      gsap.killTweensOf(img.DOM.el);
1216      img.destroy();
1217    });
1218  }
1219
1220  onImageActivated() {
1221    this.activeImagesCount++;
1222    this.isIdle = false;
1223  }
1224  onImageDeactivated() {
1225    this.activeImagesCount--;
1226    if (this.activeImagesCount === 0) this.isIdle = true;
1227  }
1228}
1229
1230const variantMap = {
1231  1: ImageTrailVariant1,
1232  2: ImageTrailVariant2,
1233  3: ImageTrailVariant3,
1234  4: ImageTrailVariant4,
1235  5: ImageTrailVariant5,
1236  6: ImageTrailVariant6,
1237  7: ImageTrailVariant7,
1238  8: ImageTrailVariant8
1239};
1240
1241export default function ImageTrail({ items = [], variant = 1 }) {
1242  const containerRef = useRef(null);
1243
1244  useEffect(() => {
1245    if (!containerRef.current) return;
1246
1247    const Cls = variantMap[variant] || variantMap[1];
1248    const instance = new Cls(containerRef.current);
1249
1250    return () => {
1251      instance.destroy();
1252    };
1253  }, [variant, items]);
1254
1255  return (
1256    <div className="content" ref={containerRef}>
1257      {items.map((url, i) => (
1258        <div className="content__img" key={i}>
1259          <div className="content__img-inner" style={{ backgroundImage: \`url(\${url})\` }} />
1260        </div>
1261      ))}
1262    </div>
1263  );
1264}
1265`,w=`.content {
1266  width: 100%;
1267  height: 100%;
1268  position: relative;
1269  z-index: 100;
1270  border-radius: 8px;
1271  background: transparent;
1272  overflow: visible;
1273}
1274
1275.content__img {
1276  width: 190px;
1277  aspect-ratio: 1.1;
1278  border-radius: 15px;
1279  position: absolute;
1280  top: 0;
1281  left: 0;
1282  opacity: 0;
1283  overflow: hidden;
1284  will-change: transform, filter;
1285}
1286
1287.content__img-inner {
1288  background-position: 50% 50%;
1289  width: calc(100% + 20px);
1290  height: calc(100% + 20px);
1291  background-size: cover;
1292  position: absolute;
1293  top: calc(-1 * 20px / 2);
1294  left: calc(-1 * 20px / 2);
1295}
1296`,V=`'use client';
1297
1298import { useRef, useEffect } from 'react';
1299import { gsap } from 'gsap';
1300
1301function lerp(a, b, n) {
1302  return (1 - n) * a + n * b;
1303}
1304
1305function getLocalPointerPos(e, rect) {
1306  let clientX = 0,
1307    clientY = 0;
1308  if (e.touches && e.touches.length > 0) {
1309    clientX = e.touches[0].clientX;
1310    clientY = e.touches[0].clientY;
1311  } else {
1312    clientX = e.clientX;
1313    clientY = e.clientY;
1314  }
1315  return {
1316    x: clientX - rect.left,
1317    y: clientY - rect.top
1318  };
1319}
1320function getMouseDistance(p1, p2) {
1321  const dx = p1.x - p2.x;
1322  const dy = p1.y - p2.y;
1323  return Math.hypot(dx, dy);
1324}
1325
1326class ImageItem {
1327  DOM = { el: null, inner: null };
1328  defaultStyle = { scale: 1, x: 0, y: 0, opacity: 0 };
1329  rect = null;
1330
1331  constructor(DOM_el) {
1332    this.DOM.el = DOM_el;
1333    this.DOM.inner = this.DOM.el.querySelector('.content__img-inner');
1334    this.getRect();
1335    this.initEvents();
1336  }
1337  initEvents() {
1338    this.resize = () => {
1339      gsap.set(this.DOM.el, this.defaultStyle);
1340      this.getRect();
1341    };
1342    window.addEventListener('resize', this.resize);
1343  }
1344  getRect() {
1345    this.rect = this.DOM.el.getBoundingClientRect();
1346  }
1347
1348  destroy() {
1349    window.removeEventListener('resize', this.resize);
1350  }
1351}
1352
1353class ImageTrailVariant1 {
1354  constructor(container) {
1355    this.container = container;
1356    this.rafId = null;
1357    this.destroyed = false;
1358    this.DOM = { el: container };
1359    this.images = [...this.DOM.el.querySelectorAll('.content__img')].map(img => new ImageItem(img));
1360    this.imagesTotal = this.images.length;
1361    this.imgPosition = 0;
1362    this.zIndexVal = 1;
1363    this.activeImagesCount = 0;
1364    this.isIdle = true;
1365    this.threshold = 80;
1366
1367    this.mousePos = { x: 0, y: 0 };
1368    this.lastMousePos = { x: 0, y: 0 };
1369    this.cacheMousePos = { x: 0, y: 0 };
1370
1371    const handlePointerMove = ev => {
1372      const rect = this.container.getBoundingClientRect();
1373      this.mousePos = getLocalPointerPos(ev, rect);
1374    };
1375    container.addEventListener('mousemove', handlePointerMove);
1376    container.addEventListener('touchmove', handlePointerMove);
1377
1378    const initRender = ev => {
1379      const rect = this.container.getBoundingClientRect();
1380      this.mousePos = getLocalPointerPos(ev, rect);
1381      this.cacheMousePos = { ...this.mousePos };
1382
1383      this.rafId = requestAnimationFrame(() => this.render());
1384
1385      container.removeEventListener('mousemove', initRender);
1386      container.removeEventListener('touchmove', initRender);
1387    };
1388    container.addEventListener('mousemove', initRender);
1389    container.addEventListener('touchmove', initRender);
1390    this.handlePointerMove = handlePointerMove;
1391    this.initRender = initRender;
1392  }
1393
1394  render() {
1395    if (this.destroyed) return;
1396
1397    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
1398    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
1399    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
1400
1401    if (distance > this.threshold) {
1402      this.showNextImage();
1403      this.lastMousePos = { ...this.mousePos };
1404    }
1405    if (this.isIdle && this.zIndexVal !== 1) {
1406      this.zIndexVal = 1;
1407    }
1408    this.rafId = requestAnimationFrame(() => this.render());
1409  }
1410
1411  showNextImage() {
1412    ++this.zIndexVal;
1413    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
1414    const img = this.images[this.imgPosition];
1415
1416    gsap.killTweensOf(img.DOM.el);
1417    gsap
1418      .timeline({
1419        onStart: () => this.onImageActivated(),
1420        onComplete: () => this.onImageDeactivated()
1421      })
1422      .fromTo(
1423        img.DOM.el,
1424        {
1425          opacity: 1,
1426          scale: 1,
1427          zIndex: this.zIndexVal,
1428          x: this.cacheMousePos.x - img.rect.width / 2,
1429          y: this.cacheMousePos.y - img.rect.height / 2
1430        },
1431        {
1432          duration: 0.4,
1433          ease: 'power1',
1434          x: this.mousePos.x - img.rect.width / 2,
1435          y: this.mousePos.y - img.rect.height / 2
1436        },
1437        0
1438      )
1439      .to(
1440        img.DOM.el,
1441        {
1442          duration: 0.4,
1443          ease: 'power3',
1444          opacity: 0,
1445          scale: 0.2
1446        },
1447        0.4
1448      );
1449  }
1450
1451  destroy() {
1452    this.destroyed = true;
1453    if (this.rafId !== null) {
1454      cancelAnimationFrame(this.rafId);
1455      this.rafId = null;
1456    }
1457    this.container.removeEventListener('mousemove', this.handlePointerMove);
1458    this.container.removeEventListener('touchmove', this.handlePointerMove);
1459    this.container.removeEventListener('mousemove', this.initRender);
1460    this.container.removeEventListener('touchmove', this.initRender);
1461    this.images.forEach(img => {
1462      gsap.killTweensOf(img.DOM.el);
1463      img.destroy();
1464    });
1465  }
1466
1467  onImageActivated() {
1468    this.activeImagesCount++;
1469    this.isIdle = false;
1470  }
1471  onImageDeactivated() {
1472    this.activeImagesCount--;
1473    if (this.activeImagesCount === 0) {
1474      this.isIdle = true;
1475    }
1476  }
1477}
1478
1479class ImageTrailVariant2 {
1480  constructor(container) {
1481    this.container = container;
1482    this.rafId = null;
1483    this.destroyed = false;
1484    this.DOM = { el: container };
1485    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
1486    this.imagesTotal = this.images.length;
1487    this.imgPosition = 0;
1488    this.zIndexVal = 1;
1489    this.activeImagesCount = 0;
1490    this.isIdle = true;
1491    this.threshold = 80;
1492
1493    this.mousePos = { x: 0, y: 0 };
1494    this.lastMousePos = { x: 0, y: 0 };
1495    this.cacheMousePos = { x: 0, y: 0 };
1496
1497    const handlePointerMove = ev => {
1498      const rect = container.getBoundingClientRect();
1499      this.mousePos = getLocalPointerPos(ev, rect);
1500    };
1501    container.addEventListener('mousemove', handlePointerMove);
1502    container.addEventListener('touchmove', handlePointerMove);
1503
1504    const initRender = ev => {
1505      const rect = container.getBoundingClientRect();
1506      this.mousePos = getLocalPointerPos(ev, rect);
1507      this.cacheMousePos = { ...this.mousePos };
1508
1509      this.rafId = requestAnimationFrame(() => this.render());
1510
1511      container.removeEventListener('mousemove', initRender);
1512      container.removeEventListener('touchmove', initRender);
1513    };
1514    container.addEventListener('mousemove', initRender);
1515    container.addEventListener('touchmove', initRender);
1516    this.handlePointerMove = handlePointerMove;
1517    this.initRender = initRender;
1518  }
1519
1520  render() {
1521    if (this.destroyed) return;
1522
1523    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
1524    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
1525    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
1526
1527    if (distance > this.threshold) {
1528      this.showNextImage();
1529      this.lastMousePos = { ...this.mousePos };
1530    }
1531    if (this.isIdle && this.zIndexVal !== 1) {
1532      this.zIndexVal = 1;
1533    }
1534    this.rafId = requestAnimationFrame(() => this.render());
1535  }
1536
1537  showNextImage() {
1538    ++this.zIndexVal;
1539    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
1540    const img = this.images[this.imgPosition];
1541
1542    gsap.killTweensOf(img.DOM.el);
1543    gsap
1544      .timeline({
1545        onStart: () => this.onImageActivated(),
1546        onComplete: () => this.onImageDeactivated()
1547      })
1548      .fromTo(
1549        img.DOM.el,
1550        {
1551          opacity: 1,
1552          scale: 0,
1553          zIndex: this.zIndexVal,
1554          x: this.cacheMousePos.x - img.rect.width / 2,
1555          y: this.cacheMousePos.y - img.rect.height / 2
1556        },
1557        {
1558          duration: 0.4,
1559          ease: 'power1',
1560          scale: 1,
1561          x: this.mousePos.x - img.rect.width / 2,
1562          y: this.mousePos.y - img.rect.height / 2
1563        },
1564        0
1565      )
1566      .fromTo(
1567        img.DOM.inner,
1568        {
1569          scale: 2.8,
1570          filter: 'brightness(250%)'
1571        },
1572        {
1573          duration: 0.4,
1574          ease: 'power1',
1575          scale: 1,
1576          filter: 'brightness(100%)'
1577        },
1578        0
1579      )
1580      .to(
1581        img.DOM.el,
1582        {
1583          duration: 0.4,
1584          ease: 'power2',
1585          opacity: 0,
1586          scale: 0.2
1587        },
1588        0.45
1589      );
1590  }
1591
1592  destroy() {
1593    this.destroyed = true;
1594    if (this.rafId !== null) {
1595      cancelAnimationFrame(this.rafId);
1596      this.rafId = null;
1597    }
1598    this.container.removeEventListener('mousemove', this.handlePointerMove);
1599    this.container.removeEventListener('touchmove', this.handlePointerMove);
1600    this.container.removeEventListener('mousemove', this.initRender);
1601    this.container.removeEventListener('touchmove', this.initRender);
1602    this.images.forEach(img => {
1603      gsap.killTweensOf(img.DOM.el);
1604      img.destroy();
1605    });
1606  }
1607
1608  onImageActivated() {
1609    this.activeImagesCount++;
1610    this.isIdle = false;
1611  }
1612  onImageDeactivated() {
1613    this.activeImagesCount--;
1614    if (this.activeImagesCount === 0) this.isIdle = true;
1615  }
1616}
1617
1618class ImageTrailVariant3 {
1619  constructor(container) {
1620    this.container = container;
1621    this.rafId = null;
1622    this.destroyed = false;
1623    this.DOM = { el: container };
1624    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
1625    this.imagesTotal = this.images.length;
1626    this.imgPosition = 0;
1627    this.zIndexVal = 1;
1628    this.activeImagesCount = 0;
1629    this.isIdle = true;
1630    this.threshold = 80;
1631
1632    this.mousePos = { x: 0, y: 0 };
1633    this.lastMousePos = { x: 0, y: 0 };
1634    this.cacheMousePos = { x: 0, y: 0 };
1635
1636    const handlePointerMove = ev => {
1637      const rect = container.getBoundingClientRect();
1638      this.mousePos = getLocalPointerPos(ev, rect);
1639    };
1640    container.addEventListener('mousemove', handlePointerMove);
1641    container.addEventListener('touchmove', handlePointerMove);
1642
1643    const initRender = ev => {
1644      const rect = container.getBoundingClientRect();
1645      this.mousePos = getLocalPointerPos(ev, rect);
1646      this.cacheMousePos = { ...this.mousePos };
1647
1648      this.rafId = requestAnimationFrame(() => this.render());
1649      container.removeEventListener('mousemove', initRender);
1650      container.removeEventListener('touchmove', initRender);
1651    };
1652    container.addEventListener('mousemove', initRender);
1653    container.addEventListener('touchmove', initRender);
1654    this.handlePointerMove = handlePointerMove;
1655    this.initRender = initRender;
1656  }
1657
1658  render() {
1659    if (this.destroyed) return;
1660
1661    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
1662    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
1663    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
1664
1665    if (distance > this.threshold) {
1666      this.showNextImage();
1667      this.lastMousePos = { ...this.mousePos };
1668    }
1669    if (this.isIdle && this.zIndexVal !== 1) {
1670      this.zIndexVal = 1;
1671    }
1672    this.rafId = requestAnimationFrame(() => this.render());
1673  }
1674
1675  showNextImage() {
1676    ++this.zIndexVal;
1677    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
1678    const img = this.images[this.imgPosition];
1679    gsap.killTweensOf(img.DOM.el);
1680
1681    gsap
1682      .timeline({
1683        onStart: () => this.onImageActivated(),
1684        onComplete: () => this.onImageDeactivated()
1685      })
1686      .fromTo(
1687        img.DOM.el,
1688        {
1689          opacity: 1,
1690          scale: 0,
1691          zIndex: this.zIndexVal,
1692          xPercent: 0,
1693          yPercent: 0,
1694          x: this.cacheMousePos.x - img.rect.width / 2,
1695          y: this.cacheMousePos.y - img.rect.height / 2
1696        },
1697        {
1698          duration: 0.4,
1699          ease: 'power1',
1700          scale: 1,
1701          x: this.mousePos.x - img.rect.width / 2,
1702          y: this.mousePos.y - img.rect.height / 2
1703        },
1704        0
1705      )
1706      .fromTo(
1707        img.DOM.inner,
1708        {
1709          scale: 1.2
1710        },
1711        {
1712          duration: 0.4,
1713          ease: 'power1',
1714          scale: 1
1715        },
1716        0
1717      )
1718      .to(
1719        img.DOM.el,
1720        {
1721          duration: 0.6,
1722          ease: 'power2',
1723          opacity: 0,
1724          scale: 0.2,
1725          xPercent: () => gsap.utils.random(-30, 30),
1726          yPercent: -200
1727        },
1728        0.6
1729      );
1730  }
1731
1732  destroy() {
1733    this.destroyed = true;
1734    if (this.rafId !== null) {
1735      cancelAnimationFrame(this.rafId);
1736      this.rafId = null;
1737    }
1738    this.container.removeEventListener('mousemove', this.handlePointerMove);
1739    this.container.removeEventListener('touchmove', this.handlePointerMove);
1740    this.container.removeEventListener('mousemove', this.initRender);
1741    this.container.removeEventListener('touchmove', this.initRender);
1742    this.images.forEach(img => {
1743      gsap.killTweensOf(img.DOM.el);
1744      img.destroy();
1745    });
1746  }
1747
1748  onImageActivated() {
1749    this.activeImagesCount++;
1750    this.isIdle = false;
1751  }
1752  onImageDeactivated() {
1753    this.activeImagesCount--;
1754    if (this.activeImagesCount === 0) this.isIdle = true;
1755  }
1756}
1757
1758class ImageTrailVariant4 {
1759  constructor(container) {
1760    this.container = container;
1761    this.rafId = null;
1762    this.destroyed = false;
1763    this.DOM = { el: container };
1764    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
1765    this.imagesTotal = this.images.length;
1766    this.imgPosition = 0;
1767    this.zIndexVal = 1;
1768    this.activeImagesCount = 0;
1769    this.isIdle = true;
1770    this.threshold = 80;
1771
1772    this.mousePos = { x: 0, y: 0 };
1773    this.lastMousePos = { x: 0, y: 0 };
1774    this.cacheMousePos = { x: 0, y: 0 };
1775
1776    const handlePointerMove = ev => {
1777      const rect = container.getBoundingClientRect();
1778      this.mousePos = getLocalPointerPos(ev, rect);
1779    };
1780    container.addEventListener('mousemove', handlePointerMove);
1781    container.addEventListener('touchmove', handlePointerMove);
1782
1783    const initRender = ev => {
1784      const rect = container.getBoundingClientRect();
1785      this.mousePos = getLocalPointerPos(ev, rect);
1786      this.cacheMousePos = { ...this.mousePos };
1787      this.rafId = requestAnimationFrame(() => this.render());
1788      container.removeEventListener('mousemove', initRender);
1789      container.removeEventListener('touchmove', initRender);
1790    };
1791    container.addEventListener('mousemove', initRender);
1792    container.addEventListener('touchmove', initRender);
1793    this.handlePointerMove = handlePointerMove;
1794    this.initRender = initRender;
1795  }
1796
1797  render() {
1798    if (this.destroyed) return;
1799
1800    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
1801    if (distance > this.threshold) {
1802      this.showNextImage();
1803      this.lastMousePos = { ...this.mousePos };
1804    }
1805    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
1806    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
1807
1808    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
1809    this.rafId = requestAnimationFrame(() => this.render());
1810  }
1811
1812  showNextImage() {
1813    ++this.zIndexVal;
1814    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
1815    const img = this.images[this.imgPosition];
1816    gsap.killTweensOf(img.DOM.el);
1817
1818    let dx = this.mousePos.x - this.cacheMousePos.x;
1819    let dy = this.mousePos.y - this.cacheMousePos.y;
1820    let distance = Math.sqrt(dx * dx + dy * dy);
1821    if (distance !== 0) {
1822      dx /= distance;
1823      dy /= distance;
1824    }
1825    dx *= distance / 100;
1826    dy *= distance / 100;
1827
1828    gsap
1829      .timeline({
1830        onStart: () => this.onImageActivated(),
1831        onComplete: () => this.onImageDeactivated()
1832      })
1833      .fromTo(
1834        img.DOM.el,
1835        {
1836          opacity: 1,
1837          scale: 0,
1838          zIndex: this.zIndexVal,
1839          x: this.cacheMousePos.x - img.rect.width / 2,
1840          y: this.cacheMousePos.y - img.rect.height / 2
1841        },
1842        {
1843          duration: 0.4,
1844          ease: 'power1',
1845          scale: 1,
1846          x: this.mousePos.x - img.rect.width / 2,
1847          y: this.mousePos.y - img.rect.height / 2
1848        },
1849        0
1850      )
1851      .fromTo(
1852        img.DOM.inner,
1853        {
1854          scale: 2,
1855          filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max((400 * distance) / 100, 100)}%)\`
1856        },
1857        {
1858          duration: 0.4,
1859          ease: 'power1',
1860          scale: 1,
1861          filter: 'brightness(100%) contrast(100%)'
1862        },
1863        0
1864      )
1865      .to(
1866        img.DOM.el,
1867        {
1868          duration: 0.4,
1869          ease: 'power3',
1870          opacity: 0
1871        },
1872        0.4
1873      )
1874      .to(
1875        img.DOM.el,
1876        {
1877          duration: 1.5,
1878          ease: 'power4',
1879          x: \`+=\${dx * 110}\`,
1880          y: \`+=\${dy * 110}\`
1881        },
1882        0.05
1883      );
1884  }
1885
1886  destroy() {
1887    this.destroyed = true;
1888    if (this.rafId !== null) {
1889      cancelAnimationFrame(this.rafId);
1890      this.rafId = null;
1891    }
1892    this.container.removeEventListener('mousemove', this.handlePointerMove);
1893    this.container.removeEventListener('touchmove', this.handlePointerMove);
1894    this.container.removeEventListener('mousemove', this.initRender);
1895    this.container.removeEventListener('touchmove', this.initRender);
1896    this.images.forEach(img => {
1897      gsap.killTweensOf(img.DOM.el);
1898      img.destroy();
1899    });
1900  }
1901
1902  onImageActivated() {
1903    this.activeImagesCount++;
1904    this.isIdle = false;
1905  }
1906  onImageDeactivated() {
1907    this.activeImagesCount--;
1908    if (this.activeImagesCount === 0) this.isIdle = true;
1909  }
1910}
1911
1912class ImageTrailVariant5 {
1913  constructor(container) {
1914    this.container = container;
1915    this.rafId = null;
1916    this.destroyed = false;
1917    this.DOM = { el: container };
1918    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
1919    this.imagesTotal = this.images.length;
1920    this.imgPosition = 0;
1921    this.zIndexVal = 1;
1922    this.activeImagesCount = 0;
1923    this.isIdle = true;
1924    this.threshold = 80;
1925
1926    this.mousePos = { x: 0, y: 0 };
1927    this.lastMousePos = { x: 0, y: 0 };
1928    this.cacheMousePos = { x: 0, y: 0 };
1929    this.lastAngle = 0;
1930
1931    const handlePointerMove = ev => {
1932      const rect = container.getBoundingClientRect();
1933      this.mousePos = getLocalPointerPos(ev, rect);
1934    };
1935    container.addEventListener('mousemove', handlePointerMove);
1936    container.addEventListener('touchmove', handlePointerMove);
1937
1938    const initRender = ev => {
1939      const rect = container.getBoundingClientRect();
1940      this.mousePos = getLocalPointerPos(ev, rect);
1941      this.cacheMousePos = { ...this.mousePos };
1942      this.rafId = requestAnimationFrame(() => this.render());
1943      container.removeEventListener('mousemove', initRender);
1944      container.removeEventListener('touchmove', initRender);
1945    };
1946    container.addEventListener('mousemove', initRender);
1947    container.addEventListener('touchmove', initRender);
1948    this.handlePointerMove = handlePointerMove;
1949    this.initRender = initRender;
1950  }
1951
1952  render() {
1953    if (this.destroyed) return;
1954
1955    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
1956    if (distance > this.threshold) {
1957      this.showNextImage();
1958      this.lastMousePos = { ...this.mousePos };
1959    }
1960    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
1961    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
1962    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
1963    this.rafId = requestAnimationFrame(() => this.render());
1964  }
1965
1966  showNextImage() {
1967    let dx = this.mousePos.x - this.cacheMousePos.x;
1968    let dy = this.mousePos.y - this.cacheMousePos.y;
1969    let angle = Math.atan2(dy, dx) * (180 / Math.PI);
1970    if (angle < 0) angle += 360;
1971    if (angle > 90 && angle <= 270) angle += 180;
1972    const isMovingClockwise = angle >= this.lastAngle;
1973    this.lastAngle = angle;
1974    let startAngle = isMovingClockwise ? angle - 10 : angle + 10;
1975    let distance = Math.sqrt(dx * dx + dy * dy);
1976    if (distance !== 0) {
1977      dx /= distance;
1978      dy /= distance;
1979    }
1980    dx *= distance / 150;
1981    dy *= distance / 150;
1982
1983    ++this.zIndexVal;
1984    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
1985    const img = this.images[this.imgPosition];
1986    gsap.killTweensOf(img.DOM.el);
1987
1988    gsap
1989      .timeline({
1990        onStart: () => this.onImageActivated(),
1991        onComplete: () => this.onImageDeactivated()
1992      })
1993      .fromTo(
1994        img.DOM.el,
1995        {
1996          opacity: 1,
1997          filter: 'brightness(80%)',
1998          scale: 0.1,
1999          zIndex: this.zIndexVal,
2000          x: this.cacheMousePos.x - img.rect.width / 2,
2001          y: this.cacheMousePos.y - img.rect.height / 2,
2002          rotation: startAngle
2003        },
2004        {
2005          duration: 1,
2006          ease: 'power2',
2007          scale: 1,
2008          filter: 'brightness(100%)',
2009          x: this.mousePos.x - img.rect.width / 2 + dx * 70,
2010          y: this.mousePos.y - img.rect.height / 2 + dy * 70,
2011          rotation: this.lastAngle
2012        },
2013        0
2014      )
2015      .to(
2016        img.DOM.el,
2017        {
2018          duration: 0.4,
2019          ease: 'expo',
2020          opacity: 0
2021        },
2022        0.5
2023      )
2024      .to(
2025        img.DOM.el,
2026        {
2027          duration: 1.5,
2028          ease: 'power4',
2029          x: \`+=\${dx * 120}\`,
2030          y: \`+=\${dy * 120}\`
2031        },
2032        0.05
2033      );
2034  }
2035
2036  destroy() {
2037    this.destroyed = true;
2038    if (this.rafId !== null) {
2039      cancelAnimationFrame(this.rafId);
2040      this.rafId = null;
2041    }
2042    this.container.removeEventListener('mousemove', this.handlePointerMove);
2043    this.container.removeEventListener('touchmove', this.handlePointerMove);
2044    this.container.removeEventListener('mousemove', this.initRender);
2045    this.container.removeEventListener('touchmove', this.initRender);
2046    this.images.forEach(img => {
2047      gsap.killTweensOf(img.DOM.el);
2048      img.destroy();
2049    });
2050  }
2051
2052  onImageActivated() {
2053    this.activeImagesCount++;
2054    this.isIdle = false;
2055  }
2056  onImageDeactivated() {
2057    this.activeImagesCount--;
2058    if (this.activeImagesCount === 0) this.isIdle = true;
2059  }
2060}
2061
2062class ImageTrailVariant6 {
2063  constructor(container) {
2064    this.container = container;
2065    this.rafId = null;
2066    this.destroyed = false;
2067    this.DOM = { el: container };
2068    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
2069    this.imagesTotal = this.images.length;
2070    this.imgPosition = 0;
2071    this.zIndexVal = 1;
2072    this.activeImagesCount = 0;
2073    this.isIdle = true;
2074    this.threshold = 80;
2075
2076    this.mousePos = { x: 0, y: 0 };
2077    this.lastMousePos = { x: 0, y: 0 };
2078    this.cacheMousePos = { x: 0, y: 0 };
2079
2080    const handlePointerMove = ev => {
2081      const rect = container.getBoundingClientRect();
2082      this.mousePos = getLocalPointerPos(ev, rect);
2083    };
2084    container.addEventListener('mousemove', handlePointerMove);
2085    container.addEventListener('touchmove', handlePointerMove);
2086
2087    const initRender = ev => {
2088      const rect = container.getBoundingClientRect();
2089      this.mousePos = getLocalPointerPos(ev, rect);
2090      this.cacheMousePos = { ...this.mousePos };
2091      this.rafId = requestAnimationFrame(() => this.render());
2092      container.removeEventListener('mousemove', initRender);
2093      container.removeEventListener('touchmove', initRender);
2094    };
2095    container.addEventListener('mousemove', initRender);
2096    container.addEventListener('touchmove', initRender);
2097    this.handlePointerMove = handlePointerMove;
2098    this.initRender = initRender;
2099  }
2100
2101  render() {
2102    if (this.destroyed) return;
2103
2104    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
2105    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
2106    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
2107
2108    if (distance > this.threshold) {
2109      this.showNextImage();
2110      this.lastMousePos = { ...this.mousePos };
2111    }
2112    if (this.isIdle && this.zIndexVal !== 1) {
2113      this.zIndexVal = 1;
2114    }
2115    this.rafId = requestAnimationFrame(() => this.render());
2116  }
2117
2118  mapSpeedToSize(speed, minSize, maxSize) {
2119    const maxSpeed = 200;
2120    return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1);
2121  }
2122  mapSpeedToBrightness(speed, minB, maxB) {
2123    const maxSpeed = 70;
2124    return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1);
2125  }
2126  mapSpeedToBlur(speed, minBlur, maxBlur) {
2127    const maxSpeed = 90;
2128    return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1);
2129  }
2130  mapSpeedToGrayscale(speed, minG, maxG) {
2131    const maxSpeed = 90;
2132    return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1);
2133  }
2134
2135  showNextImage() {
2136    let dx = this.mousePos.x - this.cacheMousePos.x;
2137    let dy = this.mousePos.y - this.cacheMousePos.y;
2138    let speed = Math.sqrt(dx * dx + dy * dy);
2139
2140    ++this.zIndexVal;
2141    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
2142    const img = this.images[this.imgPosition];
2143
2144    let scaleFactor = this.mapSpeedToSize(speed, 0.3, 2);
2145    let brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3);
2146    let blurValue = this.mapSpeedToBlur(speed, 20, 0);
2147    let grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0);
2148
2149    gsap.killTweensOf(img.DOM.el);
2150    gsap
2151      .timeline({
2152        onStart: () => this.onImageActivated(),
2153        onComplete: () => this.onImageDeactivated()
2154      })
2155      .fromTo(
2156        img.DOM.el,
2157        {
2158          opacity: 1,
2159          scale: 0,
2160          zIndex: this.zIndexVal,
2161          x: this.cacheMousePos.x - img.rect.width / 2,
2162          y: this.cacheMousePos.y - img.rect.height / 2
2163        },
2164        {
2165          duration: 0.8,
2166          ease: 'power3',
2167          scale: scaleFactor,
2168          filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`,
2169          x: this.mousePos.x - img.rect.width / 2,
2170          y: this.mousePos.y - img.rect.height / 2
2171        },
2172        0
2173      )
2174      .fromTo(
2175        img.DOM.inner,
2176        {
2177          scale: 2
2178        },
2179        {
2180          duration: 0.8,
2181          ease: 'power3',
2182          scale: 1
2183        },
2184        0
2185      )
2186      .to(
2187        img.DOM.el,
2188        {
2189          duration: 0.4,
2190          ease: 'power3.in',
2191          opacity: 0,
2192          scale: 0.2
2193        },
2194        0.45
2195      );
2196  }
2197
2198  destroy() {
2199    this.destroyed = true;
2200    if (this.rafId !== null) {
2201      cancelAnimationFrame(this.rafId);
2202      this.rafId = null;
2203    }
2204    this.container.removeEventListener('mousemove', this.handlePointerMove);
2205    this.container.removeEventListener('touchmove', this.handlePointerMove);
2206    this.container.removeEventListener('mousemove', this.initRender);
2207    this.container.removeEventListener('touchmove', this.initRender);
2208    this.images.forEach(img => {
2209      gsap.killTweensOf(img.DOM.el);
2210      img.destroy();
2211    });
2212  }
2213
2214  onImageActivated() {
2215    this.activeImagesCount++;
2216    this.isIdle = false;
2217  }
2218  onImageDeactivated() {
2219    this.activeImagesCount--;
2220    if (this.activeImagesCount === 0) {
2221      this.isIdle = true;
2222    }
2223  }
2224}
2225
2226function getNewPosition(position, offset, arr) {
2227  const realOffset = Math.abs(offset) % arr.length;
2228  if (position - realOffset >= 0) {
2229    return position - realOffset;
2230  } else {
2231    return arr.length - (realOffset - position);
2232  }
2233}
2234class ImageTrailVariant7 {
2235  constructor(container) {
2236    this.container = container;
2237    this.rafId = null;
2238    this.destroyed = false;
2239    this.DOM = { el: container };
2240    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
2241    this.imagesTotal = this.images.length;
2242    this.imgPosition = 0;
2243    this.zIndexVal = 1;
2244    this.activeImagesCount = 0;
2245    this.isIdle = true;
2246    this.threshold = 80;
2247
2248    this.mousePos = { x: 0, y: 0 };
2249    this.lastMousePos = { x: 0, y: 0 };
2250    this.cacheMousePos = { x: 0, y: 0 };
2251
2252    this.visibleImagesCount = 0;
2253    this.visibleImagesTotal = 9;
2254    this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1);
2255
2256    const handlePointerMove = ev => {
2257      const rect = container.getBoundingClientRect();
2258      this.mousePos = getLocalPointerPos(ev, rect);
2259    };
2260    container.addEventListener('mousemove', handlePointerMove);
2261    container.addEventListener('touchmove', handlePointerMove);
2262
2263    const initRender = ev => {
2264      const rect = container.getBoundingClientRect();
2265      this.mousePos = getLocalPointerPos(ev, rect);
2266      this.cacheMousePos = { ...this.mousePos };
2267      this.rafId = requestAnimationFrame(() => this.render());
2268      container.removeEventListener('mousemove', initRender);
2269      container.removeEventListener('touchmove', initRender);
2270    };
2271    container.addEventListener('mousemove', initRender);
2272    container.addEventListener('touchmove', initRender);
2273    this.handlePointerMove = handlePointerMove;
2274    this.initRender = initRender;
2275  }
2276
2277  render() {
2278    if (this.destroyed) return;
2279
2280    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
2281    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
2282    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
2283
2284    if (distance > this.threshold) {
2285      this.showNextImage();
2286      this.lastMousePos = { ...this.mousePos };
2287    }
2288    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
2289
2290    this.rafId = requestAnimationFrame(() => this.render());
2291  }
2292
2293  showNextImage() {
2294    ++this.zIndexVal;
2295    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
2296    const img = this.images[this.imgPosition];
2297    ++this.visibleImagesCount;
2298
2299    gsap.killTweensOf(img.DOM.el);
2300    const scaleValue = gsap.utils.random(0.5, 1.6);
2301
2302    gsap
2303      .timeline({
2304        onStart: () => this.onImageActivated(),
2305        onComplete: () => this.onImageDeactivated()
2306      })
2307      .fromTo(
2308        img.DOM.el,
2309        {
2310          scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0),
2311          rotationZ: 0,
2312          opacity: 1,
2313          zIndex: this.zIndexVal,
2314          x: this.cacheMousePos.x - img.rect.width / 2,
2315          y: this.cacheMousePos.y - img.rect.height / 2
2316        },
2317        {
2318          duration: 0.4,
2319          ease: 'power3',
2320          scale: scaleValue,
2321          rotationZ: gsap.utils.random(-3, 3),
2322          x: this.mousePos.x - img.rect.width / 2,
2323          y: this.mousePos.y - img.rect.height / 2
2324        },
2325        0
2326      );
2327
2328    if (this.visibleImagesCount >= this.visibleImagesTotal) {
2329      const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images);
2330      const oldImg = this.images[lastInQueue];
2331      gsap.to(oldImg.DOM.el, {
2332        duration: 0.4,
2333        ease: 'power4',
2334        opacity: 0,
2335        scale: 1.3,
2336        onComplete: () => {
2337          if (this.activeImagesCount === 0) {
2338            this.isIdle = true;
2339          }
2340        }
2341      });
2342    }
2343  }
2344
2345  destroy() {
2346    this.destroyed = true;
2347    if (this.rafId !== null) {
2348      cancelAnimationFrame(this.rafId);
2349      this.rafId = null;
2350    }
2351    this.container.removeEventListener('mousemove', this.handlePointerMove);
2352    this.container.removeEventListener('touchmove', this.handlePointerMove);
2353    this.container.removeEventListener('mousemove', this.initRender);
2354    this.container.removeEventListener('touchmove', this.initRender);
2355    this.images.forEach(img => {
2356      gsap.killTweensOf(img.DOM.el);
2357      img.destroy();
2358    });
2359  }
2360
2361  onImageActivated() {
2362    this.activeImagesCount++;
2363    this.isIdle = false;
2364  }
2365  onImageDeactivated() {
2366    this.activeImagesCount--;
2367  }
2368}
2369
2370class ImageTrailVariant8 {
2371  constructor(container) {
2372    this.container = container;
2373    this.rafId = null;
2374    this.destroyed = false;
2375    this.DOM = { el: container };
2376    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img));
2377    this.imagesTotal = this.images.length;
2378    this.imgPosition = 0;
2379    this.zIndexVal = 1;
2380    this.activeImagesCount = 0;
2381    this.isIdle = true;
2382    this.threshold = 80;
2383
2384    this.mousePos = { x: 0, y: 0 };
2385    this.lastMousePos = { x: 0, y: 0 };
2386    this.cacheMousePos = { x: 0, y: 0 };
2387
2388    this.rotation = { x: 0, y: 0 };
2389    this.cachedRotation = { x: 0, y: 0 };
2390    this.zValue = 0;
2391    this.cachedZValue = 0;
2392
2393    const handlePointerMove = ev => {
2394      const rect = container.getBoundingClientRect();
2395      this.mousePos = getLocalPointerPos(ev, rect);
2396    };
2397    container.addEventListener('mousemove', handlePointerMove);
2398    container.addEventListener('touchmove', handlePointerMove);
2399
2400    const initRender = ev => {
2401      const rect = container.getBoundingClientRect();
2402      this.mousePos = getLocalPointerPos(ev, rect);
2403      this.cacheMousePos = { ...this.mousePos };
2404      this.rafId = requestAnimationFrame(() => this.render());
2405      container.removeEventListener('mousemove', initRender);
2406      container.removeEventListener('touchmove', initRender);
2407    };
2408    container.addEventListener('mousemove', initRender);
2409    container.addEventListener('touchmove', initRender);
2410    this.handlePointerMove = handlePointerMove;
2411    this.initRender = initRender;
2412  }
2413
2414  render() {
2415    if (this.destroyed) return;
2416
2417    let distance = getMouseDistance(this.mousePos, this.lastMousePos);
2418    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
2419    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
2420
2421    if (distance > this.threshold) {
2422      this.showNextImage();
2423      this.lastMousePos = { ...this.mousePos };
2424    }
2425    if (this.isIdle && this.zIndexVal !== 1) {
2426      this.zIndexVal = 1;
2427    }
2428    this.rafId = requestAnimationFrame(() => this.render());
2429  }
2430
2431  showNextImage() {
2432    const rect = this.container.getBoundingClientRect();
2433    const centerX = rect.width / 2;
2434    const centerY = rect.height / 2;
2435    const relX = this.mousePos.x - centerX;
2436    const relY = this.mousePos.y - centerY;
2437
2438    this.rotation.x = -(relY / centerY) * 30;
2439    this.rotation.y = (relX / centerX) * 30;
2440    this.cachedRotation = { ...this.rotation };
2441
2442    const distanceFromCenter = Math.sqrt(relX * relX + relY * relY);
2443    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
2444    const proportion = distanceFromCenter / maxDistance;
2445    this.zValue = proportion * 1200 - 600;
2446    this.cachedZValue = this.zValue;
2447    const normalizedZ = (this.zValue + 600) / 1200;
2448    const brightness = 0.2 + normalizedZ * 2.3;
2449
2450    ++this.zIndexVal;
2451    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
2452    const img = this.images[this.imgPosition];
2453    gsap.killTweensOf(img.DOM.el);
2454
2455    gsap
2456      .timeline({
2457        onStart: () => this.onImageActivated(),
2458        onComplete: () => this.onImageDeactivated()
2459      })
2460      .set(this.DOM.el, { perspective: 1000 }, 0)
2461      .fromTo(
2462        img.DOM.el,
2463        {
2464          opacity: 1,
2465          z: 0,
2466          scale: 1 + this.cachedZValue / 1000,
2467          zIndex: this.zIndexVal,
2468          x: this.cacheMousePos.x - img.rect.width / 2,
2469          y: this.cacheMousePos.y - img.rect.height / 2,
2470          rotationX: this.cachedRotation.x,
2471          rotationY: this.cachedRotation.y,
2472          filter: \`brightness(\${brightness})\`
2473        },
2474        {
2475          duration: 1,
2476          ease: 'expo',
2477          scale: 1 + this.zValue / 1000,
2478          x: this.mousePos.x - img.rect.width / 2,
2479          y: this.mousePos.y - img.rect.height / 2,
2480          rotationX: this.rotation.x,
2481          rotationY: this.rotation.y
2482        },
2483        0
2484      )
2485      .to(
2486        img.DOM.el,
2487        {
2488          duration: 0.4,
2489          ease: 'power2',
2490          opacity: 0,
2491          z: -800
2492        },
2493        0.3
2494      );
2495  }
2496
2497  destroy() {
2498    this.destroyed = true;
2499    if (this.rafId !== null) {
2500      cancelAnimationFrame(this.rafId);
2501      this.rafId = null;
2502    }
2503    this.container.removeEventListener('mousemove', this.handlePointerMove);
2504    this.container.removeEventListener('touchmove', this.handlePointerMove);
2505    this.container.removeEventListener('mousemove', this.initRender);
2506    this.container.removeEventListener('touchmove', this.initRender);
2507    this.images.forEach(img => {
2508      gsap.killTweensOf(img.DOM.el);
2509      img.destroy();
2510    });
2511  }
2512
2513  onImageActivated() {
2514    this.activeImagesCount++;
2515    this.isIdle = false;
2516  }
2517  onImageDeactivated() {
2518    this.activeImagesCount--;
2519    if (this.activeImagesCount === 0) this.isIdle = true;
2520  }
2521}
2522
2523const variantMap = {
2524  1: ImageTrailVariant1,
2525  2: ImageTrailVariant2,
2526  3: ImageTrailVariant3,
2527  4: ImageTrailVariant4,
2528  5: ImageTrailVariant5,
2529  6: ImageTrailVariant6,
2530  7: ImageTrailVariant7,
2531  8: ImageTrailVariant8
2532};
2533
2534export default function ImageTrail({ items = [], variant = 1 }) {
2535  const containerRef = useRef(null);
2536
2537  useEffect(() => {
2538    if (!containerRef.current) return;
2539
2540    const Cls = variantMap[variant] || variantMap[1];
2541    const instance = new Cls(containerRef.current);
2542
2543    return () => {
2544      instance.destroy();
2545    };
2546  }, [variant, items]);
2547
2548  return (
2549    <div className="w-full h-full relative z-[100] rounded-lg bg-transparent overflow-visible" ref={containerRef}>
2550      {items.map((url, i) => (
2551        <div
2552          className="content__img w-[190px] aspect-[1.1] rounded-[15px] absolute top-0 left-0 opacity-0 overflow-hidden [will-change:transform,filter]"
2553          key={i}
2554        >
2555          <div
2556            className="content__img-inner bg-center bg-cover w-[calc(100%+20px)] h-[calc(100%+20px)] absolute top-[-10px] left-[-10px]"
2557            style={{ backgroundImage: \`url(\${url})\` }}
2558          />
2559        </div>
2560      ))}
2561    </div>
2562  );
2563}
2564`,O=`'use client';
2565
2566import { gsap } from 'gsap';
2567import { type JSX, useEffect, useRef } from 'react';
2568import './ImageTrail.css';
2569
2570function lerp(a: number, b: number, n: number): number {
2571  return (1 - n) * a + n * b;
2572}
2573
2574function getLocalPointerPos(e: MouseEvent | TouchEvent, rect: DOMRect): { x: number; y: number } {
2575  let clientX = 0,
2576    clientY = 0;
2577  if ('touches' in e && e.touches.length > 0) {
2578    clientX = e.touches[0].clientX;
2579    clientY = e.touches[0].clientY;
2580  } else if ('clientX' in e) {
2581    clientX = e.clientX;
2582    clientY = e.clientY;
2583  }
2584  return {
2585    x: clientX - rect.left,
2586    y: clientY - rect.top
2587  };
2588}
2589
2590function getMouseDistance(p1: { x: number; y: number }, p2: { x: number; y: number }): number {
2591  const dx = p1.x - p2.x;
2592  const dy = p1.y - p2.y;
2593  return Math.hypot(dx, dy);
2594}
2595
2596class ImageItem {
2597  public DOM: { el: HTMLDivElement; inner: HTMLDivElement | null } = {
2598    el: null as unknown as HTMLDivElement,
2599    inner: null
2600  };
2601  public defaultStyle: gsap.TweenVars = { scale: 1, x: 0, y: 0, opacity: 0 };
2602  public rect: DOMRect | null = null;
2603  private resize!: () => void;
2604
2605  constructor(DOM_el: HTMLDivElement) {
2606    this.DOM.el = DOM_el;
2607    this.DOM.inner = this.DOM.el.querySelector('.content__img-inner');
2608    this.getRect();
2609    this.initEvents();
2610  }
2611
2612  private initEvents() {
2613    this.resize = () => {
2614      gsap.set(this.DOM.el, this.defaultStyle);
2615      this.getRect();
2616    };
2617    window.addEventListener('resize', this.resize);
2618  }
2619
2620  private getRect() {
2621    this.rect = this.DOM.el.getBoundingClientRect();
2622  }
2623
2624  public destroy() {
2625    window.removeEventListener('resize', this.resize);
2626  }
2627}
2628
2629class ImageTrailVariant1 {
2630  private container: HTMLDivElement;
2631  private DOM: { el: HTMLDivElement };
2632  private images: ImageItem[];
2633  private imagesTotal: number;
2634  private imgPosition: number;
2635  private zIndexVal: number;
2636  private activeImagesCount: number;
2637  private isIdle: boolean;
2638  private threshold: number;
2639  private mousePos: { x: number; y: number };
2640  private lastMousePos: { x: number; y: number };
2641  private cacheMousePos: { x: number; y: number };
2642  private rafId: number | null = null;
2643  private destroyed = false;
2644  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
2645  private initRender!: (ev: MouseEvent | TouchEvent) => void;
2646
2647  constructor(container: HTMLDivElement) {
2648    this.container = container;
2649    this.DOM = { el: container };
2650    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
2651    this.imagesTotal = this.images.length;
2652    this.imgPosition = 0;
2653    this.zIndexVal = 1;
2654    this.activeImagesCount = 0;
2655    this.isIdle = true;
2656    this.threshold = 80;
2657    this.mousePos = { x: 0, y: 0 };
2658    this.lastMousePos = { x: 0, y: 0 };
2659    this.cacheMousePos = { x: 0, y: 0 };
2660
2661    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
2662      const rect = this.container.getBoundingClientRect();
2663      this.mousePos = getLocalPointerPos(ev, rect);
2664    };
2665    container.addEventListener('mousemove', handlePointerMove);
2666    container.addEventListener('touchmove', handlePointerMove);
2667
2668    const initRender = (ev: MouseEvent | TouchEvent) => {
2669      const rect = this.container.getBoundingClientRect();
2670      this.mousePos = getLocalPointerPos(ev, rect);
2671      this.cacheMousePos = { ...this.mousePos };
2672      this.rafId = requestAnimationFrame(() => this.render());
2673      container.removeEventListener('mousemove', initRender as EventListener);
2674      container.removeEventListener('touchmove', initRender as EventListener);
2675    };
2676    container.addEventListener('mousemove', initRender as EventListener);
2677    container.addEventListener('touchmove', initRender as EventListener);
2678    this.handlePointerMove = handlePointerMove;
2679    this.initRender = initRender;
2680  }
2681
2682  private render() {
2683    if (this.destroyed) return;
2684
2685    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
2686    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
2687    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
2688
2689    if (distance > this.threshold) {
2690      this.showNextImage();
2691      this.lastMousePos = { ...this.mousePos };
2692    }
2693    if (this.isIdle && this.zIndexVal !== 1) {
2694      this.zIndexVal = 1;
2695    }
2696    this.rafId = requestAnimationFrame(() => this.render());
2697  }
2698
2699  private showNextImage() {
2700    ++this.zIndexVal;
2701    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
2702    const img = this.images[this.imgPosition];
2703
2704    gsap.killTweensOf(img.DOM.el);
2705    gsap
2706      .timeline({
2707        onStart: () => this.onImageActivated(),
2708        onComplete: () => this.onImageDeactivated()
2709      })
2710      .fromTo(
2711        img.DOM.el,
2712        {
2713          opacity: 1,
2714          scale: 1,
2715          zIndex: this.zIndexVal,
2716          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
2717          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
2718        },
2719        {
2720          duration: 0.4,
2721          ease: 'power1',
2722          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
2723          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
2724        },
2725        0
2726      )
2727      .to(
2728        img.DOM.el,
2729        {
2730          duration: 0.4,
2731          ease: 'power3',
2732          opacity: 0,
2733          scale: 0.2
2734        },
2735        0.4
2736      );
2737  }
2738
2739  public destroy() {
2740    this.destroyed = true;
2741    if (this.rafId !== null) {
2742      cancelAnimationFrame(this.rafId);
2743      this.rafId = null;
2744    }
2745    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
2746    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
2747    this.container.removeEventListener('mousemove', this.initRender as EventListener);
2748    this.container.removeEventListener('touchmove', this.initRender as EventListener);
2749    this.images.forEach(img => {
2750      gsap.killTweensOf(img.DOM.el);
2751      img.destroy();
2752    });
2753  }
2754
2755  private onImageActivated() {
2756    this.activeImagesCount++;
2757    this.isIdle = false;
2758  }
2759
2760  private onImageDeactivated() {
2761    this.activeImagesCount--;
2762    if (this.activeImagesCount === 0) {
2763      this.isIdle = true;
2764    }
2765  }
2766}
2767
2768class ImageTrailVariant2 {
2769  private container: HTMLDivElement;
2770  private DOM: { el: HTMLDivElement };
2771  private images: ImageItem[];
2772  private imagesTotal: number;
2773  private imgPosition: number;
2774  private zIndexVal: number;
2775  private activeImagesCount: number;
2776  private isIdle: boolean;
2777  private threshold: number;
2778  private mousePos: { x: number; y: number };
2779  private lastMousePos: { x: number; y: number };
2780  private cacheMousePos: { x: number; y: number };
2781  private rafId: number | null = null;
2782  private destroyed = false;
2783  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
2784  private initRender!: (ev: MouseEvent | TouchEvent) => void;
2785
2786  constructor(container: HTMLDivElement) {
2787    this.container = container;
2788    this.DOM = { el: container };
2789    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
2790    this.imagesTotal = this.images.length;
2791    this.imgPosition = 0;
2792    this.zIndexVal = 1;
2793    this.activeImagesCount = 0;
2794    this.isIdle = true;
2795    this.threshold = 80;
2796    this.mousePos = { x: 0, y: 0 };
2797    this.lastMousePos = { x: 0, y: 0 };
2798    this.cacheMousePos = { x: 0, y: 0 };
2799
2800    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
2801      const rect = container.getBoundingClientRect();
2802      this.mousePos = getLocalPointerPos(ev, rect);
2803    };
2804    container.addEventListener('mousemove', handlePointerMove);
2805    container.addEventListener('touchmove', handlePointerMove);
2806
2807    const initRender = (ev: MouseEvent | TouchEvent) => {
2808      const rect = container.getBoundingClientRect();
2809      this.mousePos = getLocalPointerPos(ev, rect);
2810      this.cacheMousePos = { ...this.mousePos };
2811      this.rafId = requestAnimationFrame(() => this.render());
2812      container.removeEventListener('mousemove', initRender as EventListener);
2813      container.removeEventListener('touchmove', initRender as EventListener);
2814    };
2815    container.addEventListener('mousemove', initRender as EventListener);
2816    container.addEventListener('touchmove', initRender as EventListener);
2817    this.handlePointerMove = handlePointerMove;
2818    this.initRender = initRender;
2819  }
2820
2821  private render() {
2822    if (this.destroyed) return;
2823
2824    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
2825    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
2826    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
2827
2828    if (distance > this.threshold) {
2829      this.showNextImage();
2830      this.lastMousePos = { ...this.mousePos };
2831    }
2832    if (this.isIdle && this.zIndexVal !== 1) {
2833      this.zIndexVal = 1;
2834    }
2835    this.rafId = requestAnimationFrame(() => this.render());
2836  }
2837
2838  private showNextImage() {
2839    ++this.zIndexVal;
2840    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
2841    const img = this.images[this.imgPosition];
2842
2843    gsap.killTweensOf(img.DOM.el);
2844    gsap
2845      .timeline({
2846        onStart: () => this.onImageActivated(),
2847        onComplete: () => this.onImageDeactivated()
2848      })
2849      .fromTo(
2850        img.DOM.el,
2851        {
2852          opacity: 1,
2853          scale: 0,
2854          zIndex: this.zIndexVal,
2855          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
2856          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
2857        },
2858        {
2859          duration: 0.4,
2860          ease: 'power1',
2861          scale: 1,
2862          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
2863          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
2864        },
2865        0
2866      )
2867      .fromTo(
2868        img.DOM.inner,
2869        { scale: 2.8, filter: 'brightness(250%)' },
2870        {
2871          duration: 0.4,
2872          ease: 'power1',
2873          scale: 1,
2874          filter: 'brightness(100%)'
2875        },
2876        0
2877      )
2878      .to(
2879        img.DOM.el,
2880        {
2881          duration: 0.4,
2882          ease: 'power2',
2883          opacity: 0,
2884          scale: 0.2
2885        },
2886        0.45
2887      );
2888  }
2889
2890  public destroy() {
2891    this.destroyed = true;
2892    if (this.rafId !== null) {
2893      cancelAnimationFrame(this.rafId);
2894      this.rafId = null;
2895    }
2896    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
2897    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
2898    this.container.removeEventListener('mousemove', this.initRender as EventListener);
2899    this.container.removeEventListener('touchmove', this.initRender as EventListener);
2900    this.images.forEach(img => {
2901      gsap.killTweensOf(img.DOM.el);
2902      img.destroy();
2903    });
2904  }
2905
2906  private onImageActivated() {
2907    this.activeImagesCount++;
2908    this.isIdle = false;
2909  }
2910
2911  private onImageDeactivated() {
2912    this.activeImagesCount--;
2913    if (this.activeImagesCount === 0) {
2914      this.isIdle = true;
2915    }
2916  }
2917}
2918
2919class ImageTrailVariant3 {
2920  private container: HTMLDivElement;
2921  private DOM: { el: HTMLDivElement };
2922  private images: ImageItem[];
2923  private imagesTotal: number;
2924  private imgPosition: number;
2925  private zIndexVal: number;
2926  private activeImagesCount: number;
2927  private isIdle: boolean;
2928  private threshold: number;
2929  private mousePos: { x: number; y: number };
2930  private lastMousePos: { x: number; y: number };
2931  private cacheMousePos: { x: number; y: number };
2932  private rafId: number | null = null;
2933  private destroyed = false;
2934  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
2935  private initRender!: (ev: MouseEvent | TouchEvent) => void;
2936
2937  constructor(container: HTMLDivElement) {
2938    this.container = container;
2939    this.DOM = { el: container };
2940    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
2941    this.imagesTotal = this.images.length;
2942    this.imgPosition = 0;
2943    this.zIndexVal = 1;
2944    this.activeImagesCount = 0;
2945    this.isIdle = true;
2946    this.threshold = 80;
2947    this.mousePos = { x: 0, y: 0 };
2948    this.lastMousePos = { x: 0, y: 0 };
2949    this.cacheMousePos = { x: 0, y: 0 };
2950
2951    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
2952      const rect = container.getBoundingClientRect();
2953      this.mousePos = getLocalPointerPos(ev, rect);
2954    };
2955    container.addEventListener('mousemove', handlePointerMove);
2956    container.addEventListener('touchmove', handlePointerMove);
2957
2958    const initRender = (ev: MouseEvent | TouchEvent) => {
2959      const rect = container.getBoundingClientRect();
2960      this.mousePos = getLocalPointerPos(ev, rect);
2961      this.cacheMousePos = { ...this.mousePos };
2962      this.rafId = requestAnimationFrame(() => this.render());
2963      container.removeEventListener('mousemove', initRender as EventListener);
2964      container.removeEventListener('touchmove', initRender as EventListener);
2965    };
2966    container.addEventListener('mousemove', initRender as EventListener);
2967    container.addEventListener('touchmove', initRender as EventListener);
2968    this.handlePointerMove = handlePointerMove;
2969    this.initRender = initRender;
2970  }
2971
2972  private render() {
2973    if (this.destroyed) return;
2974
2975    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
2976    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
2977    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
2978
2979    if (distance > this.threshold) {
2980      this.showNextImage();
2981      this.lastMousePos = { ...this.mousePos };
2982    }
2983    if (this.isIdle && this.zIndexVal !== 1) {
2984      this.zIndexVal = 1;
2985    }
2986    this.rafId = requestAnimationFrame(() => this.render());
2987  }
2988
2989  private showNextImage() {
2990    ++this.zIndexVal;
2991    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
2992    const img = this.images[this.imgPosition];
2993
2994    gsap.killTweensOf(img.DOM.el);
2995    gsap
2996      .timeline({
2997        onStart: () => this.onImageActivated(),
2998        onComplete: () => this.onImageDeactivated()
2999      })
3000      .fromTo(
3001        img.DOM.el,
3002        {
3003          opacity: 1,
3004          scale: 0,
3005          zIndex: this.zIndexVal,
3006          xPercent: 0,
3007          yPercent: 0,
3008          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
3009          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
3010        },
3011        {
3012          duration: 0.4,
3013          ease: 'power1',
3014          scale: 1,
3015          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
3016          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
3017        },
3018        0
3019      )
3020      .fromTo(
3021        img.DOM.inner,
3022        { scale: 1.2 },
3023        {
3024          duration: 0.4,
3025          ease: 'power1',
3026          scale: 1
3027        },
3028        0
3029      )
3030      .to(
3031        img.DOM.el,
3032        {
3033          duration: 0.6,
3034          ease: 'power2',
3035          opacity: 0,
3036          scale: 0.2,
3037          xPercent: () => gsap.utils.random(-30, 30),
3038          yPercent: -200
3039        },
3040        0.6
3041      );
3042  }
3043
3044  public destroy() {
3045    this.destroyed = true;
3046    if (this.rafId !== null) {
3047      cancelAnimationFrame(this.rafId);
3048      this.rafId = null;
3049    }
3050    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
3051    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
3052    this.container.removeEventListener('mousemove', this.initRender as EventListener);
3053    this.container.removeEventListener('touchmove', this.initRender as EventListener);
3054    this.images.forEach(img => {
3055      gsap.killTweensOf(img.DOM.el);
3056      img.destroy();
3057    });
3058  }
3059
3060  private onImageActivated() {
3061    this.activeImagesCount++;
3062    this.isIdle = false;
3063  }
3064
3065  private onImageDeactivated() {
3066    this.activeImagesCount--;
3067    if (this.activeImagesCount === 0) {
3068      this.isIdle = true;
3069    }
3070  }
3071}
3072
3073class ImageTrailVariant4 {
3074  private container: HTMLDivElement;
3075  private DOM: { el: HTMLDivElement };
3076  private images: ImageItem[];
3077  private imagesTotal: number;
3078  private imgPosition: number;
3079  private zIndexVal: number;
3080  private activeImagesCount: number;
3081  private isIdle: boolean;
3082  private threshold: number;
3083  private mousePos: { x: number; y: number };
3084  private lastMousePos: { x: number; y: number };
3085  private cacheMousePos: { x: number; y: number };
3086  private rafId: number | null = null;
3087  private destroyed = false;
3088  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
3089  private initRender!: (ev: MouseEvent | TouchEvent) => void;
3090
3091  constructor(container: HTMLDivElement) {
3092    this.container = container;
3093    this.DOM = { el: container };
3094    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
3095    this.imagesTotal = this.images.length;
3096    this.imgPosition = 0;
3097    this.zIndexVal = 1;
3098    this.activeImagesCount = 0;
3099    this.isIdle = true;
3100    this.threshold = 80;
3101    this.mousePos = { x: 0, y: 0 };
3102    this.lastMousePos = { x: 0, y: 0 };
3103    this.cacheMousePos = { x: 0, y: 0 };
3104
3105    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
3106      const rect = container.getBoundingClientRect();
3107      this.mousePos = getLocalPointerPos(ev, rect);
3108    };
3109    container.addEventListener('mousemove', handlePointerMove);
3110    container.addEventListener('touchmove', handlePointerMove);
3111
3112    const initRender = (ev: MouseEvent | TouchEvent) => {
3113      const rect = container.getBoundingClientRect();
3114      this.mousePos = getLocalPointerPos(ev, rect);
3115      this.cacheMousePos = { ...this.mousePos };
3116      this.rafId = requestAnimationFrame(() => this.render());
3117      container.removeEventListener('mousemove', initRender as EventListener);
3118      container.removeEventListener('touchmove', initRender as EventListener);
3119    };
3120    container.addEventListener('mousemove', initRender as EventListener);
3121    container.addEventListener('touchmove', initRender as EventListener);
3122    this.handlePointerMove = handlePointerMove;
3123    this.initRender = initRender;
3124  }
3125
3126  private render() {
3127    if (this.destroyed) return;
3128
3129    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
3130    if (distance > this.threshold) {
3131      this.showNextImage();
3132      this.lastMousePos = { ...this.mousePos };
3133    }
3134    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
3135    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
3136
3137    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
3138    this.rafId = requestAnimationFrame(() => this.render());
3139  }
3140
3141  private showNextImage() {
3142    ++this.zIndexVal;
3143    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
3144    const img = this.images[this.imgPosition];
3145    gsap.killTweensOf(img.DOM.el);
3146
3147    let dx = this.mousePos.x - this.cacheMousePos.x;
3148    let dy = this.mousePos.y - this.cacheMousePos.y;
3149    let distance = Math.sqrt(dx * dx + dy * dy);
3150    if (distance !== 0) {
3151      dx /= distance;
3152      dy /= distance;
3153    }
3154    dx *= distance / 100;
3155    dy *= distance / 100;
3156
3157    gsap
3158      .timeline({
3159        onStart: () => this.onImageActivated(),
3160        onComplete: () => this.onImageDeactivated()
3161      })
3162      .fromTo(
3163        img.DOM.el,
3164        {
3165          opacity: 1,
3166          scale: 0,
3167          zIndex: this.zIndexVal,
3168          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
3169          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
3170        },
3171        {
3172          duration: 0.4,
3173          ease: 'power1',
3174          scale: 1,
3175          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
3176          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
3177        },
3178        0
3179      )
3180      .fromTo(
3181        img.DOM.inner,
3182        {
3183          scale: 2,
3184          filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max(
3185            (400 * distance) / 100,
3186            100
3187          )}%)\`
3188        },
3189        {
3190          duration: 0.4,
3191          ease: 'power1',
3192          scale: 1,
3193          filter: 'brightness(100%) contrast(100%)'
3194        },
3195        0
3196      )
3197      .to(
3198        img.DOM.el,
3199        {
3200          duration: 0.4,
3201          ease: 'power3',
3202          opacity: 0
3203        },
3204        0.4
3205      )
3206      .to(
3207        img.DOM.el,
3208        {
3209          duration: 1.5,
3210          ease: 'power4',
3211          x: \`+=\${dx * 110}\`,
3212          y: \`+=\${dy * 110}\`
3213        },
3214        0.05
3215      );
3216  }
3217
3218  public destroy() {
3219    this.destroyed = true;
3220    if (this.rafId !== null) {
3221      cancelAnimationFrame(this.rafId);
3222      this.rafId = null;
3223    }
3224    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
3225    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
3226    this.container.removeEventListener('mousemove', this.initRender as EventListener);
3227    this.container.removeEventListener('touchmove', this.initRender as EventListener);
3228    this.images.forEach(img => {
3229      gsap.killTweensOf(img.DOM.el);
3230      img.destroy();
3231    });
3232  }
3233
3234  private onImageActivated() {
3235    this.activeImagesCount++;
3236    this.isIdle = false;
3237  }
3238
3239  private onImageDeactivated() {
3240    this.activeImagesCount--;
3241    if (this.activeImagesCount === 0) {
3242      this.isIdle = true;
3243    }
3244  }
3245}
3246
3247class ImageTrailVariant5 {
3248  private container: HTMLDivElement;
3249  private DOM: { el: HTMLDivElement };
3250  private images: ImageItem[];
3251  private imagesTotal: number;
3252  private imgPosition: number;
3253  private zIndexVal: number;
3254  private activeImagesCount: number;
3255  private isIdle: boolean;
3256  private threshold: number;
3257  private mousePos: { x: number; y: number };
3258  private lastMousePos: { x: number; y: number };
3259  private cacheMousePos: { x: number; y: number };
3260  private rafId: number | null = null;
3261  private destroyed = false;
3262  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
3263  private initRender!: (ev: MouseEvent | TouchEvent) => void;
3264  private lastAngle: number;
3265
3266  constructor(container: HTMLDivElement) {
3267    this.container = container;
3268    this.DOM = { el: container };
3269    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
3270    this.imagesTotal = this.images.length;
3271    this.imgPosition = 0;
3272    this.zIndexVal = 1;
3273    this.activeImagesCount = 0;
3274    this.isIdle = true;
3275    this.threshold = 80;
3276    this.mousePos = { x: 0, y: 0 };
3277    this.lastMousePos = { x: 0, y: 0 };
3278    this.cacheMousePos = { x: 0, y: 0 };
3279    this.lastAngle = 0;
3280
3281    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
3282      const rect = container.getBoundingClientRect();
3283      this.mousePos = getLocalPointerPos(ev, rect);
3284    };
3285    container.addEventListener('mousemove', handlePointerMove);
3286    container.addEventListener('touchmove', handlePointerMove);
3287
3288    const initRender = (ev: MouseEvent | TouchEvent) => {
3289      const rect = container.getBoundingClientRect();
3290      this.mousePos = getLocalPointerPos(ev, rect);
3291      this.cacheMousePos = { ...this.mousePos };
3292      this.rafId = requestAnimationFrame(() => this.render());
3293      container.removeEventListener('mousemove', initRender as EventListener);
3294      container.removeEventListener('touchmove', initRender as EventListener);
3295    };
3296    container.addEventListener('mousemove', initRender as EventListener);
3297    container.addEventListener('touchmove', initRender as EventListener);
3298    this.handlePointerMove = handlePointerMove;
3299    this.initRender = initRender;
3300  }
3301
3302  private render() {
3303    if (this.destroyed) return;
3304
3305    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
3306    if (distance > this.threshold) {
3307      this.showNextImage();
3308      this.lastMousePos = { ...this.mousePos };
3309    }
3310    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
3311    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
3312    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
3313    this.rafId = requestAnimationFrame(() => this.render());
3314  }
3315
3316  private showNextImage() {
3317    let dx = this.mousePos.x - this.cacheMousePos.x;
3318    let dy = this.mousePos.y - this.cacheMousePos.y;
3319    let angle = Math.atan2(dy, dx) * (180 / Math.PI);
3320    if (angle < 0) angle += 360;
3321    if (angle > 90 && angle <= 270) angle += 180;
3322    const isMovingClockwise = angle >= this.lastAngle;
3323    this.lastAngle = angle;
3324    let startAngle = isMovingClockwise ? angle - 10 : angle + 10;
3325    const distance = Math.sqrt(dx * dx + dy * dy);
3326    if (distance !== 0) {
3327      dx /= distance;
3328      dy /= distance;
3329    }
3330    dx *= distance / 150;
3331    dy *= distance / 150;
3332
3333    ++this.zIndexVal;
3334    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
3335    const img = this.images[this.imgPosition];
3336    gsap.killTweensOf(img.DOM.el);
3337
3338    gsap
3339      .timeline({
3340        onStart: () => this.onImageActivated(),
3341        onComplete: () => this.onImageDeactivated()
3342      })
3343      .fromTo(
3344        img.DOM.el,
3345        {
3346          opacity: 1,
3347          filter: 'brightness(80%)',
3348          scale: 0.1,
3349          zIndex: this.zIndexVal,
3350          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
3351          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2,
3352          rotation: startAngle
3353        },
3354        {
3355          duration: 1,
3356          ease: 'power2',
3357          scale: 1,
3358          filter: 'brightness(100%)',
3359          x: this.mousePos.x - (img.rect?.width ?? 0) / 2 + dx * 70,
3360          y: this.mousePos.y - (img.rect?.height ?? 0) / 2 + dy * 70,
3361          rotation: this.lastAngle
3362        },
3363        0
3364      )
3365      .to(
3366        img.DOM.el,
3367        {
3368          duration: 0.4,
3369          ease: 'expo',
3370          opacity: 0
3371        },
3372        0.5
3373      )
3374      .to(
3375        img.DOM.el,
3376        {
3377          duration: 1.5,
3378          ease: 'power4',
3379          x: \`+=\${dx * 120}\`,
3380          y: \`+=\${dy * 120}\`
3381        },
3382        0.05
3383      );
3384  }
3385
3386  public destroy() {
3387    this.destroyed = true;
3388    if (this.rafId !== null) {
3389      cancelAnimationFrame(this.rafId);
3390      this.rafId = null;
3391    }
3392    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
3393    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
3394    this.container.removeEventListener('mousemove', this.initRender as EventListener);
3395    this.container.removeEventListener('touchmove', this.initRender as EventListener);
3396    this.images.forEach(img => {
3397      gsap.killTweensOf(img.DOM.el);
3398      img.destroy();
3399    });
3400  }
3401
3402  private onImageActivated() {
3403    this.activeImagesCount++;
3404    this.isIdle = false;
3405  }
3406
3407  private onImageDeactivated() {
3408    this.activeImagesCount--;
3409    if (this.activeImagesCount === 0) this.isIdle = true;
3410  }
3411}
3412
3413class ImageTrailVariant6 {
3414  private container: HTMLDivElement;
3415  private DOM: { el: HTMLDivElement };
3416  private images: ImageItem[];
3417  private imagesTotal: number;
3418  private imgPosition: number;
3419  private zIndexVal: number;
3420  private activeImagesCount: number;
3421  private isIdle: boolean;
3422  private threshold: number;
3423  private mousePos: { x: number; y: number };
3424  private lastMousePos: { x: number; y: number };
3425  private cacheMousePos: { x: number; y: number };
3426  private rafId: number | null = null;
3427  private destroyed = false;
3428  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
3429  private initRender!: (ev: MouseEvent | TouchEvent) => void;
3430
3431  constructor(container: HTMLDivElement) {
3432    this.container = container;
3433    this.DOM = { el: container };
3434    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
3435    this.imagesTotal = this.images.length;
3436    this.imgPosition = 0;
3437    this.zIndexVal = 1;
3438    this.activeImagesCount = 0;
3439    this.isIdle = true;
3440    this.threshold = 80;
3441    this.mousePos = { x: 0, y: 0 };
3442    this.lastMousePos = { x: 0, y: 0 };
3443    this.cacheMousePos = { x: 0, y: 0 };
3444
3445    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
3446      const rect = container.getBoundingClientRect();
3447      this.mousePos = getLocalPointerPos(ev, rect);
3448    };
3449    container.addEventListener('mousemove', handlePointerMove);
3450    container.addEventListener('touchmove', handlePointerMove);
3451
3452    const initRender = (ev: MouseEvent | TouchEvent) => {
3453      const rect = container.getBoundingClientRect();
3454      this.mousePos = getLocalPointerPos(ev, rect);
3455      this.cacheMousePos = { ...this.mousePos };
3456      this.rafId = requestAnimationFrame(() => this.render());
3457      container.removeEventListener('mousemove', initRender as EventListener);
3458      container.removeEventListener('touchmove', initRender as EventListener);
3459    };
3460    container.addEventListener('mousemove', initRender as EventListener);
3461    container.addEventListener('touchmove', initRender as EventListener);
3462    this.handlePointerMove = handlePointerMove;
3463    this.initRender = initRender;
3464  }
3465
3466  private render() {
3467    if (this.destroyed) return;
3468
3469    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
3470    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
3471    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
3472
3473    if (distance > this.threshold) {
3474      this.showNextImage();
3475      this.lastMousePos = { ...this.mousePos };
3476    }
3477    if (this.isIdle && this.zIndexVal !== 1) {
3478      this.zIndexVal = 1;
3479    }
3480    this.rafId = requestAnimationFrame(() => this.render());
3481  }
3482
3483  private mapSpeedToSize(speed: number, minSize: number, maxSize: number) {
3484    const maxSpeed = 200;
3485    return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1);
3486  }
3487
3488  private mapSpeedToBrightness(speed: number, minB: number, maxB: number) {
3489    const maxSpeed = 70;
3490    return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1);
3491  }
3492
3493  private mapSpeedToBlur(speed: number, minBlur: number, maxBlur: number) {
3494    const maxSpeed = 90;
3495    return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1);
3496  }
3497
3498  private mapSpeedToGrayscale(speed: number, minG: number, maxG: number) {
3499    const maxSpeed = 90;
3500    return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1);
3501  }
3502
3503  private showNextImage() {
3504    const dx = this.mousePos.x - this.cacheMousePos.x;
3505    const dy = this.mousePos.y - this.cacheMousePos.y;
3506    const speed = Math.sqrt(dx * dx + dy * dy);
3507
3508    ++this.zIndexVal;
3509    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
3510    const img = this.images[this.imgPosition];
3511
3512    const scaleFactor = this.mapSpeedToSize(speed, 0.3, 2);
3513    const brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3);
3514    const blurValue = this.mapSpeedToBlur(speed, 20, 0);
3515    const grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0);
3516
3517    gsap.killTweensOf(img.DOM.el);
3518    gsap
3519      .timeline({
3520        onStart: () => this.onImageActivated(),
3521        onComplete: () => this.onImageDeactivated()
3522      })
3523      .fromTo(
3524        img.DOM.el,
3525        {
3526          opacity: 1,
3527          scale: 0,
3528          zIndex: this.zIndexVal,
3529          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
3530          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
3531        },
3532        {
3533          duration: 0.8,
3534          ease: 'power3',
3535          scale: scaleFactor,
3536          filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`,
3537          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
3538          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
3539        },
3540        0
3541      )
3542      .fromTo(
3543        img.DOM.inner,
3544        { scale: 2 },
3545        {
3546          duration: 0.8,
3547          ease: 'power3',
3548          scale: 1
3549        },
3550        0
3551      )
3552      .to(
3553        img.DOM.el,
3554        {
3555          duration: 0.4,
3556          ease: 'power3.in',
3557          opacity: 0,
3558          scale: 0.2
3559        },
3560        0.45
3561      );
3562  }
3563
3564  public destroy() {
3565    this.destroyed = true;
3566    if (this.rafId !== null) {
3567      cancelAnimationFrame(this.rafId);
3568      this.rafId = null;
3569    }
3570    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
3571    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
3572    this.container.removeEventListener('mousemove', this.initRender as EventListener);
3573    this.container.removeEventListener('touchmove', this.initRender as EventListener);
3574    this.images.forEach(img => {
3575      gsap.killTweensOf(img.DOM.el);
3576      img.destroy();
3577    });
3578  }
3579
3580  private onImageActivated() {
3581    this.activeImagesCount++;
3582    this.isIdle = false;
3583  }
3584
3585  private onImageDeactivated() {
3586    this.activeImagesCount--;
3587    if (this.activeImagesCount === 0) {
3588      this.isIdle = true;
3589    }
3590  }
3591}
3592
3593function getNewPosition(position: number, offset: number, arr: ImageItem[]) {
3594  const realOffset = Math.abs(offset) % arr.length;
3595  if (position - realOffset >= 0) {
3596    return position - realOffset;
3597  } else {
3598    return arr.length - (realOffset - position);
3599  }
3600}
3601
3602class ImageTrailVariant7 {
3603  private container: HTMLDivElement;
3604  private DOM: { el: HTMLDivElement };
3605  private images: ImageItem[];
3606  private imagesTotal: number;
3607  private imgPosition: number;
3608  private zIndexVal: number;
3609  private activeImagesCount: number;
3610  private isIdle: boolean;
3611  private threshold: number;
3612  private mousePos: { x: number; y: number };
3613  private lastMousePos: { x: number; y: number };
3614  private cacheMousePos: { x: number; y: number };
3615  private rafId: number | null = null;
3616  private destroyed = false;
3617  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
3618  private initRender!: (ev: MouseEvent | TouchEvent) => void;
3619  private visibleImagesCount: number;
3620  private visibleImagesTotal: number;
3621
3622  constructor(container: HTMLDivElement) {
3623    this.container = container;
3624    this.DOM = { el: container };
3625    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
3626    this.imagesTotal = this.images.length;
3627    this.imgPosition = 0;
3628    this.zIndexVal = 1;
3629    this.activeImagesCount = 0;
3630    this.isIdle = true;
3631    this.threshold = 80;
3632    this.mousePos = { x: 0, y: 0 };
3633    this.lastMousePos = { x: 0, y: 0 };
3634    this.cacheMousePos = { x: 0, y: 0 };
3635    this.visibleImagesCount = 0;
3636    this.visibleImagesTotal = 9;
3637    this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1);
3638
3639    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
3640      const rect = container.getBoundingClientRect();
3641      this.mousePos = getLocalPointerPos(ev, rect);
3642    };
3643    container.addEventListener('mousemove', handlePointerMove);
3644    container.addEventListener('touchmove', handlePointerMove);
3645
3646    const initRender = (ev: MouseEvent | TouchEvent) => {
3647      const rect = container.getBoundingClientRect();
3648      this.mousePos = getLocalPointerPos(ev, rect);
3649      this.cacheMousePos = { ...this.mousePos };
3650      this.rafId = requestAnimationFrame(() => this.render());
3651      container.removeEventListener('mousemove', initRender as EventListener);
3652      container.removeEventListener('touchmove', initRender as EventListener);
3653    };
3654    container.addEventListener('mousemove', initRender as EventListener);
3655    container.addEventListener('touchmove', initRender as EventListener);
3656    this.handlePointerMove = handlePointerMove;
3657    this.initRender = initRender;
3658  }
3659
3660  private render() {
3661    if (this.destroyed) return;
3662
3663    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
3664    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
3665    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
3666
3667    if (distance > this.threshold) {
3668      this.showNextImage();
3669      this.lastMousePos = { ...this.mousePos };
3670    }
3671    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
3672
3673    this.rafId = requestAnimationFrame(() => this.render());
3674  }
3675
3676  private showNextImage() {
3677    ++this.zIndexVal;
3678    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
3679    const img = this.images[this.imgPosition];
3680    ++this.visibleImagesCount;
3681
3682    gsap.killTweensOf(img.DOM.el);
3683    const scaleValue = gsap.utils.random(0.5, 1.6);
3684
3685    gsap
3686      .timeline({
3687        onStart: () => this.onImageActivated(),
3688        onComplete: () => this.onImageDeactivated()
3689      })
3690      .fromTo(
3691        img.DOM.el,
3692        {
3693          scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0),
3694          rotationZ: 0,
3695          opacity: 1,
3696          zIndex: this.zIndexVal,
3697          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
3698          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
3699        },
3700        {
3701          duration: 0.4,
3702          ease: 'power3',
3703          scale: scaleValue,
3704          rotationZ: gsap.utils.random(-3, 3),
3705          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
3706          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
3707        },
3708        0
3709      );
3710
3711    if (this.visibleImagesCount >= this.visibleImagesTotal) {
3712      const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images);
3713      const oldImg = this.images[lastInQueue];
3714      gsap.to(oldImg.DOM.el, {
3715        duration: 0.4,
3716        ease: 'power4',
3717        opacity: 0,
3718        scale: 1.3,
3719        onComplete: () => {
3720          if (this.activeImagesCount === 0) {
3721            this.isIdle = true;
3722          }
3723        }
3724      });
3725    }
3726  }
3727
3728  public destroy() {
3729    this.destroyed = true;
3730    if (this.rafId !== null) {
3731      cancelAnimationFrame(this.rafId);
3732      this.rafId = null;
3733    }
3734    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
3735    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
3736    this.container.removeEventListener('mousemove', this.initRender as EventListener);
3737    this.container.removeEventListener('touchmove', this.initRender as EventListener);
3738    this.images.forEach(img => {
3739      gsap.killTweensOf(img.DOM.el);
3740      img.destroy();
3741    });
3742  }
3743
3744  private onImageActivated() {
3745    this.activeImagesCount++;
3746    this.isIdle = false;
3747  }
3748
3749  private onImageDeactivated() {
3750    this.activeImagesCount--;
3751  }
3752}
3753
3754class ImageTrailVariant8 {
3755  private container: HTMLDivElement;
3756  private DOM: { el: HTMLDivElement };
3757  private images: ImageItem[];
3758  private imagesTotal: number;
3759  private imgPosition: number;
3760  private zIndexVal: number;
3761  private activeImagesCount: number;
3762  private isIdle: boolean;
3763  private threshold: number;
3764  private mousePos: { x: number; y: number };
3765  private lastMousePos: { x: number; y: number };
3766  private cacheMousePos: { x: number; y: number };
3767  private rafId: number | null = null;
3768  private destroyed = false;
3769  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
3770  private initRender!: (ev: MouseEvent | TouchEvent) => void;
3771  private rotation: { x: number; y: number };
3772  private cachedRotation: { x: number; y: number };
3773  private zValue: number;
3774  private cachedZValue: number;
3775
3776  constructor(container: HTMLDivElement) {
3777    this.container = container;
3778    this.DOM = { el: container };
3779    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
3780    this.imagesTotal = this.images.length;
3781    this.imgPosition = 0;
3782    this.zIndexVal = 1;
3783    this.activeImagesCount = 0;
3784    this.isIdle = true;
3785    this.threshold = 80;
3786    this.mousePos = { x: 0, y: 0 };
3787    this.lastMousePos = { x: 0, y: 0 };
3788    this.cacheMousePos = { x: 0, y: 0 };
3789    this.rotation = { x: 0, y: 0 };
3790    this.cachedRotation = { x: 0, y: 0 };
3791    this.zValue = 0;
3792    this.cachedZValue = 0;
3793
3794    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
3795      const rect = container.getBoundingClientRect();
3796      this.mousePos = getLocalPointerPos(ev, rect);
3797    };
3798    container.addEventListener('mousemove', handlePointerMove);
3799    container.addEventListener('touchmove', handlePointerMove);
3800
3801    const initRender = (ev: MouseEvent | TouchEvent) => {
3802      const rect = container.getBoundingClientRect();
3803      this.mousePos = getLocalPointerPos(ev, rect);
3804      this.cacheMousePos = { ...this.mousePos };
3805      this.rafId = requestAnimationFrame(() => this.render());
3806      container.removeEventListener('mousemove', initRender as EventListener);
3807      container.removeEventListener('touchmove', initRender as EventListener);
3808    };
3809    container.addEventListener('mousemove', initRender as EventListener);
3810    container.addEventListener('touchmove', initRender as EventListener);
3811    this.handlePointerMove = handlePointerMove;
3812    this.initRender = initRender;
3813  }
3814
3815  private render() {
3816    if (this.destroyed) return;
3817
3818    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
3819    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
3820    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
3821
3822    if (distance > this.threshold) {
3823      this.showNextImage();
3824      this.lastMousePos = { ...this.mousePos };
3825    }
3826    if (this.isIdle && this.zIndexVal !== 1) {
3827      this.zIndexVal = 1;
3828    }
3829    this.rafId = requestAnimationFrame(() => this.render());
3830  }
3831
3832  private showNextImage() {
3833    const rect = this.container.getBoundingClientRect();
3834    const centerX = rect.width / 2;
3835    const centerY = rect.height / 2;
3836    const relX = this.mousePos.x - centerX;
3837    const relY = this.mousePos.y - centerY;
3838
3839    this.rotation.x = -(relY / centerY) * 30;
3840    this.rotation.y = (relX / centerX) * 30;
3841    this.cachedRotation = { ...this.rotation };
3842
3843    const distanceFromCenter = Math.sqrt(relX * relX + relY * relY);
3844    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
3845    const proportion = distanceFromCenter / maxDistance;
3846    this.zValue = proportion * 1200 - 600;
3847    this.cachedZValue = this.zValue;
3848    const normalizedZ = (this.zValue + 600) / 1200;
3849    const brightness = 0.2 + normalizedZ * 2.3;
3850
3851    ++this.zIndexVal;
3852    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
3853    const img = this.images[this.imgPosition];
3854    gsap.killTweensOf(img.DOM.el);
3855
3856    gsap
3857      .timeline({
3858        onStart: () => this.onImageActivated(),
3859        onComplete: () => this.onImageDeactivated()
3860      })
3861      .set(this.DOM.el, { perspective: 1000 }, 0)
3862      .fromTo(
3863        img.DOM.el,
3864        {
3865          opacity: 1,
3866          z: 0,
3867          scale: 1 + this.cachedZValue / 1000,
3868          zIndex: this.zIndexVal,
3869          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
3870          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2,
3871          rotationX: this.cachedRotation.x,
3872          rotationY: this.cachedRotation.y,
3873          filter: \`brightness(\${brightness})\`
3874        },
3875        {
3876          duration: 1,
3877          ease: 'expo',
3878          scale: 1 + this.zValue / 1000,
3879          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
3880          y: this.mousePos.y - (img.rect?.height ?? 0) / 2,
3881          rotationX: this.rotation.x,
3882          rotationY: this.rotation.y
3883        },
3884        0
3885      )
3886      .to(
3887        img.DOM.el,
3888        {
3889          duration: 0.4,
3890          ease: 'power2',
3891          opacity: 0,
3892          z: -800
3893        },
3894        0.3
3895      );
3896  }
3897
3898  public destroy() {
3899    this.destroyed = true;
3900    if (this.rafId !== null) {
3901      cancelAnimationFrame(this.rafId);
3902      this.rafId = null;
3903    }
3904    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
3905    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
3906    this.container.removeEventListener('mousemove', this.initRender as EventListener);
3907    this.container.removeEventListener('touchmove', this.initRender as EventListener);
3908    this.images.forEach(img => {
3909      gsap.killTweensOf(img.DOM.el);
3910      img.destroy();
3911    });
3912  }
3913
3914  private onImageActivated() {
3915    this.activeImagesCount++;
3916    this.isIdle = false;
3917  }
3918
3919  private onImageDeactivated() {
3920    this.activeImagesCount--;
3921    if (this.activeImagesCount === 0) {
3922      this.isIdle = true;
3923    }
3924  }
3925}
3926
3927type ImageTrailConstructor =
3928  | typeof ImageTrailVariant1
3929  | typeof ImageTrailVariant2
3930  | typeof ImageTrailVariant3
3931  | typeof ImageTrailVariant4
3932  | typeof ImageTrailVariant5
3933  | typeof ImageTrailVariant6
3934  | typeof ImageTrailVariant7
3935  | typeof ImageTrailVariant8;
3936
3937const variantMap: Record<number, ImageTrailConstructor> = {
3938  1: ImageTrailVariant1,
3939  2: ImageTrailVariant2,
3940  3: ImageTrailVariant3,
3941  4: ImageTrailVariant4,
3942  5: ImageTrailVariant5,
3943  6: ImageTrailVariant6,
3944  7: ImageTrailVariant7,
3945  8: ImageTrailVariant8
3946};
3947
3948interface ImageTrailProps {
3949  items?: string[];
3950  variant?: number;
3951}
3952
3953export default function ImageTrail({ items = [], variant = 1 }: ImageTrailProps): JSX.Element {
3954  const containerRef = useRef<HTMLDivElement>(null);
3955
3956  useEffect(() => {
3957    if (!containerRef.current) return;
3958    const Cls = variantMap[variant] || variantMap[1];
3959    const instance = new Cls(containerRef.current);
3960
3961    return () => {
3962      instance.destroy();
3963    };
3964    // eslint-disable-next-line react-hooks/exhaustive-deps
3965  }, [variant, items]);
3966
3967  return (
3968    <div className="content" ref={containerRef}>
3969      {items.map((url, i) => (
3970        <div className="content__img" key={i}>
3971          <div className="content__img-inner" style={{ backgroundImage: \`url(\${url})\` }} />
3972        </div>
3973      ))}
3974    </div>
3975  );
3976}
3977`,C=`'use client';
3978
3979import { gsap } from 'gsap';
3980import { type JSX, useEffect, useRef } from 'react';
3981
3982function lerp(a: number, b: number, n: number): number {
3983  return (1 - n) * a + n * b;
3984}
3985
3986function getLocalPointerPos(e: MouseEvent | TouchEvent, rect: DOMRect): { x: number; y: number } {
3987  let clientX = 0,
3988    clientY = 0;
3989  if ('touches' in e && e.touches.length > 0) {
3990    clientX = e.touches[0].clientX;
3991    clientY = e.touches[0].clientY;
3992  } else if ('clientX' in e) {
3993    clientX = e.clientX;
3994    clientY = e.clientY;
3995  }
3996  return {
3997    x: clientX - rect.left,
3998    y: clientY - rect.top
3999  };
4000}
4001
4002function getMouseDistance(p1: { x: number; y: number }, p2: { x: number; y: number }): number {
4003  const dx = p1.x - p2.x;
4004  const dy = p1.y - p2.y;
4005  return Math.hypot(dx, dy);
4006}
4007
4008class ImageItem {
4009  public DOM: { el: HTMLDivElement; inner: HTMLDivElement | null } = {
4010    el: null as unknown as HTMLDivElement,
4011    inner: null
4012  };
4013  public defaultStyle: gsap.TweenVars = { scale: 1, x: 0, y: 0, opacity: 0 };
4014  public rect: DOMRect | null = null;
4015  private resize!: () => void;
4016
4017  constructor(DOM_el: HTMLDivElement) {
4018    this.DOM.el = DOM_el;
4019    this.DOM.inner = this.DOM.el.querySelector('.content__img-inner');
4020    this.getRect();
4021    this.initEvents();
4022  }
4023
4024  private initEvents() {
4025    this.resize = () => {
4026      gsap.set(this.DOM.el, this.defaultStyle);
4027      this.getRect();
4028    };
4029    window.addEventListener('resize', this.resize);
4030  }
4031
4032  private getRect() {
4033    this.rect = this.DOM.el.getBoundingClientRect();
4034  }
4035
4036  public destroy() {
4037    window.removeEventListener('resize', this.resize);
4038  }
4039}
4040
4041class ImageTrailVariant1 {
4042  private container: HTMLDivElement;
4043  private DOM: { el: HTMLDivElement };
4044  private images: ImageItem[];
4045  private imagesTotal: number;
4046  private imgPosition: number;
4047  private zIndexVal: number;
4048  private activeImagesCount: number;
4049  private isIdle: boolean;
4050  private threshold: number;
4051  private mousePos: { x: number; y: number };
4052  private lastMousePos: { x: number; y: number };
4053  private cacheMousePos: { x: number; y: number };
4054  private rafId: number | null = null;
4055  private destroyed = false;
4056  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
4057  private initRender!: (ev: MouseEvent | TouchEvent) => void;
4058
4059  constructor(container: HTMLDivElement) {
4060    this.container = container;
4061    this.DOM = { el: container };
4062    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
4063    this.imagesTotal = this.images.length;
4064    this.imgPosition = 0;
4065    this.zIndexVal = 1;
4066    this.activeImagesCount = 0;
4067    this.isIdle = true;
4068    this.threshold = 80;
4069    this.mousePos = { x: 0, y: 0 };
4070    this.lastMousePos = { x: 0, y: 0 };
4071    this.cacheMousePos = { x: 0, y: 0 };
4072
4073    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
4074      const rect = this.container.getBoundingClientRect();
4075      this.mousePos = getLocalPointerPos(ev, rect);
4076    };
4077    container.addEventListener('mousemove', handlePointerMove);
4078    container.addEventListener('touchmove', handlePointerMove);
4079
4080    const initRender = (ev: MouseEvent | TouchEvent) => {
4081      const rect = this.container.getBoundingClientRect();
4082      this.mousePos = getLocalPointerPos(ev, rect);
4083      this.cacheMousePos = { ...this.mousePos };
4084      this.rafId = requestAnimationFrame(() => this.render());
4085      container.removeEventListener('mousemove', initRender as EventListener);
4086      container.removeEventListener('touchmove', initRender as EventListener);
4087    };
4088    container.addEventListener('mousemove', initRender as EventListener);
4089    container.addEventListener('touchmove', initRender as EventListener);
4090    this.handlePointerMove = handlePointerMove;
4091    this.initRender = initRender;
4092  }
4093
4094  private render() {
4095    if (this.destroyed) return;
4096
4097    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
4098    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
4099    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
4100
4101    if (distance > this.threshold) {
4102      this.showNextImage();
4103      this.lastMousePos = { ...this.mousePos };
4104    }
4105    if (this.isIdle && this.zIndexVal !== 1) {
4106      this.zIndexVal = 1;
4107    }
4108    this.rafId = requestAnimationFrame(() => this.render());
4109  }
4110
4111  private showNextImage() {
4112    ++this.zIndexVal;
4113    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
4114    const img = this.images[this.imgPosition];
4115
4116    gsap.killTweensOf(img.DOM.el);
4117    gsap
4118      .timeline({
4119        onStart: () => this.onImageActivated(),
4120        onComplete: () => this.onImageDeactivated()
4121      })
4122      .fromTo(
4123        img.DOM.el,
4124        {
4125          opacity: 1,
4126          scale: 1,
4127          zIndex: this.zIndexVal,
4128          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
4129          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
4130        },
4131        {
4132          duration: 0.4,
4133          ease: 'power1',
4134          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
4135          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
4136        },
4137        0
4138      )
4139      .to(
4140        img.DOM.el,
4141        {
4142          duration: 0.4,
4143          ease: 'power3',
4144          opacity: 0,
4145          scale: 0.2
4146        },
4147        0.4
4148      );
4149  }
4150
4151  public destroy() {
4152    this.destroyed = true;
4153    if (this.rafId !== null) {
4154      cancelAnimationFrame(this.rafId);
4155      this.rafId = null;
4156    }
4157    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
4158    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
4159    this.container.removeEventListener('mousemove', this.initRender as EventListener);
4160    this.container.removeEventListener('touchmove', this.initRender as EventListener);
4161    this.images.forEach(img => {
4162      gsap.killTweensOf(img.DOM.el);
4163      img.destroy();
4164    });
4165  }
4166
4167  private onImageActivated() {
4168    this.activeImagesCount++;
4169    this.isIdle = false;
4170  }
4171
4172  private onImageDeactivated() {
4173    this.activeImagesCount--;
4174    if (this.activeImagesCount === 0) {
4175      this.isIdle = true;
4176    }
4177  }
4178}
4179
4180class ImageTrailVariant2 {
4181  private container: HTMLDivElement;
4182  private DOM: { el: HTMLDivElement };
4183  private images: ImageItem[];
4184  private imagesTotal: number;
4185  private imgPosition: number;
4186  private zIndexVal: number;
4187  private activeImagesCount: number;
4188  private isIdle: boolean;
4189  private threshold: number;
4190  private mousePos: { x: number; y: number };
4191  private lastMousePos: { x: number; y: number };
4192  private cacheMousePos: { x: number; y: number };
4193  private rafId: number | null = null;
4194  private destroyed = false;
4195  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
4196  private initRender!: (ev: MouseEvent | TouchEvent) => void;
4197
4198  constructor(container: HTMLDivElement) {
4199    this.container = container;
4200    this.DOM = { el: container };
4201    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
4202    this.imagesTotal = this.images.length;
4203    this.imgPosition = 0;
4204    this.zIndexVal = 1;
4205    this.activeImagesCount = 0;
4206    this.isIdle = true;
4207    this.threshold = 80;
4208    this.mousePos = { x: 0, y: 0 };
4209    this.lastMousePos = { x: 0, y: 0 };
4210    this.cacheMousePos = { x: 0, y: 0 };
4211
4212    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
4213      const rect = container.getBoundingClientRect();
4214      this.mousePos = getLocalPointerPos(ev, rect);
4215    };
4216    container.addEventListener('mousemove', handlePointerMove);
4217    container.addEventListener('touchmove', handlePointerMove);
4218
4219    const initRender = (ev: MouseEvent | TouchEvent) => {
4220      const rect = container.getBoundingClientRect();
4221      this.mousePos = getLocalPointerPos(ev, rect);
4222      this.cacheMousePos = { ...this.mousePos };
4223      this.rafId = requestAnimationFrame(() => this.render());
4224      container.removeEventListener('mousemove', initRender as EventListener);
4225      container.removeEventListener('touchmove', initRender as EventListener);
4226    };
4227    container.addEventListener('mousemove', initRender as EventListener);
4228    container.addEventListener('touchmove', initRender as EventListener);
4229    this.handlePointerMove = handlePointerMove;
4230    this.initRender = initRender;
4231  }
4232
4233  private render() {
4234    if (this.destroyed) return;
4235
4236    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
4237    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
4238    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
4239
4240    if (distance > this.threshold) {
4241      this.showNextImage();
4242      this.lastMousePos = { ...this.mousePos };
4243    }
4244    if (this.isIdle && this.zIndexVal !== 1) {
4245      this.zIndexVal = 1;
4246    }
4247    this.rafId = requestAnimationFrame(() => this.render());
4248  }
4249
4250  private showNextImage() {
4251    ++this.zIndexVal;
4252    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
4253    const img = this.images[this.imgPosition];
4254
4255    gsap.killTweensOf(img.DOM.el);
4256    gsap
4257      .timeline({
4258        onStart: () => this.onImageActivated(),
4259        onComplete: () => this.onImageDeactivated()
4260      })
4261      .fromTo(
4262        img.DOM.el,
4263        {
4264          opacity: 1,
4265          scale: 0,
4266          zIndex: this.zIndexVal,
4267          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
4268          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
4269        },
4270        {
4271          duration: 0.4,
4272          ease: 'power1',
4273          scale: 1,
4274          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
4275          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
4276        },
4277        0
4278      )
4279      .fromTo(
4280        img.DOM.inner,
4281        { scale: 2.8, filter: 'brightness(250%)' },
4282        {
4283          duration: 0.4,
4284          ease: 'power1',
4285          scale: 1,
4286          filter: 'brightness(100%)'
4287        },
4288        0
4289      )
4290      .to(
4291        img.DOM.el,
4292        {
4293          duration: 0.4,
4294          ease: 'power2',
4295          opacity: 0,
4296          scale: 0.2
4297        },
4298        0.45
4299      );
4300  }
4301
4302  public destroy() {
4303    this.destroyed = true;
4304    if (this.rafId !== null) {
4305      cancelAnimationFrame(this.rafId);
4306      this.rafId = null;
4307    }
4308    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
4309    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
4310    this.container.removeEventListener('mousemove', this.initRender as EventListener);
4311    this.container.removeEventListener('touchmove', this.initRender as EventListener);
4312    this.images.forEach(img => {
4313      gsap.killTweensOf(img.DOM.el);
4314      img.destroy();
4315    });
4316  }
4317
4318  private onImageActivated() {
4319    this.activeImagesCount++;
4320    this.isIdle = false;
4321  }
4322
4323  private onImageDeactivated() {
4324    this.activeImagesCount--;
4325    if (this.activeImagesCount === 0) {
4326      this.isIdle = true;
4327    }
4328  }
4329}
4330
4331class ImageTrailVariant3 {
4332  private container: HTMLDivElement;
4333  private DOM: { el: HTMLDivElement };
4334  private images: ImageItem[];
4335  private imagesTotal: number;
4336  private imgPosition: number;
4337  private zIndexVal: number;
4338  private activeImagesCount: number;
4339  private isIdle: boolean;
4340  private threshold: number;
4341  private mousePos: { x: number; y: number };
4342  private lastMousePos: { x: number; y: number };
4343  private cacheMousePos: { x: number; y: number };
4344  private rafId: number | null = null;
4345  private destroyed = false;
4346  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
4347  private initRender!: (ev: MouseEvent | TouchEvent) => void;
4348
4349  constructor(container: HTMLDivElement) {
4350    this.container = container;
4351    this.DOM = { el: container };
4352    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
4353    this.imagesTotal = this.images.length;
4354    this.imgPosition = 0;
4355    this.zIndexVal = 1;
4356    this.activeImagesCount = 0;
4357    this.isIdle = true;
4358    this.threshold = 80;
4359    this.mousePos = { x: 0, y: 0 };
4360    this.lastMousePos = { x: 0, y: 0 };
4361    this.cacheMousePos = { x: 0, y: 0 };
4362
4363    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
4364      const rect = container.getBoundingClientRect();
4365      this.mousePos = getLocalPointerPos(ev, rect);
4366    };
4367    container.addEventListener('mousemove', handlePointerMove);
4368    container.addEventListener('touchmove', handlePointerMove);
4369
4370    const initRender = (ev: MouseEvent | TouchEvent) => {
4371      const rect = container.getBoundingClientRect();
4372      this.mousePos = getLocalPointerPos(ev, rect);
4373      this.cacheMousePos = { ...this.mousePos };
4374      this.rafId = requestAnimationFrame(() => this.render());
4375      container.removeEventListener('mousemove', initRender as EventListener);
4376      container.removeEventListener('touchmove', initRender as EventListener);
4377    };
4378    container.addEventListener('mousemove', initRender as EventListener);
4379    container.addEventListener('touchmove', initRender as EventListener);
4380    this.handlePointerMove = handlePointerMove;
4381    this.initRender = initRender;
4382  }
4383
4384  private render() {
4385    if (this.destroyed) return;
4386
4387    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
4388    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
4389    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
4390
4391    if (distance > this.threshold) {
4392      this.showNextImage();
4393      this.lastMousePos = { ...this.mousePos };
4394    }
4395    if (this.isIdle && this.zIndexVal !== 1) {
4396      this.zIndexVal = 1;
4397    }
4398    this.rafId = requestAnimationFrame(() => this.render());
4399  }
4400
4401  private showNextImage() {
4402    ++this.zIndexVal;
4403    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
4404    const img = this.images[this.imgPosition];
4405
4406    gsap.killTweensOf(img.DOM.el);
4407    gsap
4408      .timeline({
4409        onStart: () => this.onImageActivated(),
4410        onComplete: () => this.onImageDeactivated()
4411      })
4412      .fromTo(
4413        img.DOM.el,
4414        {
4415          opacity: 1,
4416          scale: 0,
4417          zIndex: this.zIndexVal,
4418          xPercent: 0,
4419          yPercent: 0,
4420          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
4421          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
4422        },
4423        {
4424          duration: 0.4,
4425          ease: 'power1',
4426          scale: 1,
4427          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
4428          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
4429        },
4430        0
4431      )
4432      .fromTo(
4433        img.DOM.inner,
4434        { scale: 1.2 },
4435        {
4436          duration: 0.4,
4437          ease: 'power1',
4438          scale: 1
4439        },
4440        0
4441      )
4442      .to(
4443        img.DOM.el,
4444        {
4445          duration: 0.6,
4446          ease: 'power2',
4447          opacity: 0,
4448          scale: 0.2,
4449          xPercent: () => gsap.utils.random(-30, 30),
4450          yPercent: -200
4451        },
4452        0.6
4453      );
4454  }
4455
4456  public destroy() {
4457    this.destroyed = true;
4458    if (this.rafId !== null) {
4459      cancelAnimationFrame(this.rafId);
4460      this.rafId = null;
4461    }
4462    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
4463    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
4464    this.container.removeEventListener('mousemove', this.initRender as EventListener);
4465    this.container.removeEventListener('touchmove', this.initRender as EventListener);
4466    this.images.forEach(img => {
4467      gsap.killTweensOf(img.DOM.el);
4468      img.destroy();
4469    });
4470  }
4471
4472  private onImageActivated() {
4473    this.activeImagesCount++;
4474    this.isIdle = false;
4475  }
4476
4477  private onImageDeactivated() {
4478    this.activeImagesCount--;
4479    if (this.activeImagesCount === 0) {
4480      this.isIdle = true;
4481    }
4482  }
4483}
4484
4485class ImageTrailVariant4 {
4486  private container: HTMLDivElement;
4487  private DOM: { el: HTMLDivElement };
4488  private images: ImageItem[];
4489  private imagesTotal: number;
4490  private imgPosition: number;
4491  private zIndexVal: number;
4492  private activeImagesCount: number;
4493  private isIdle: boolean;
4494  private threshold: number;
4495  private mousePos: { x: number; y: number };
4496  private lastMousePos: { x: number; y: number };
4497  private cacheMousePos: { x: number; y: number };
4498  private rafId: number | null = null;
4499  private destroyed = false;
4500  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
4501  private initRender!: (ev: MouseEvent | TouchEvent) => void;
4502
4503  constructor(container: HTMLDivElement) {
4504    this.container = container;
4505    this.DOM = { el: container };
4506    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
4507    this.imagesTotal = this.images.length;
4508    this.imgPosition = 0;
4509    this.zIndexVal = 1;
4510    this.activeImagesCount = 0;
4511    this.isIdle = true;
4512    this.threshold = 80;
4513    this.mousePos = { x: 0, y: 0 };
4514    this.lastMousePos = { x: 0, y: 0 };
4515    this.cacheMousePos = { x: 0, y: 0 };
4516
4517    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
4518      const rect = container.getBoundingClientRect();
4519      this.mousePos = getLocalPointerPos(ev, rect);
4520    };
4521    container.addEventListener('mousemove', handlePointerMove);
4522    container.addEventListener('touchmove', handlePointerMove);
4523
4524    const initRender = (ev: MouseEvent | TouchEvent) => {
4525      const rect = container.getBoundingClientRect();
4526      this.mousePos = getLocalPointerPos(ev, rect);
4527      this.cacheMousePos = { ...this.mousePos };
4528      this.rafId = requestAnimationFrame(() => this.render());
4529      container.removeEventListener('mousemove', initRender as EventListener);
4530      container.removeEventListener('touchmove', initRender as EventListener);
4531    };
4532    container.addEventListener('mousemove', initRender as EventListener);
4533    container.addEventListener('touchmove', initRender as EventListener);
4534    this.handlePointerMove = handlePointerMove;
4535    this.initRender = initRender;
4536  }
4537
4538  private render() {
4539    if (this.destroyed) return;
4540
4541    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
4542    if (distance > this.threshold) {
4543      this.showNextImage();
4544      this.lastMousePos = { ...this.mousePos };
4545    }
4546    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
4547    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
4548
4549    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
4550    this.rafId = requestAnimationFrame(() => this.render());
4551  }
4552
4553  private showNextImage() {
4554    ++this.zIndexVal;
4555    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
4556    const img = this.images[this.imgPosition];
4557    gsap.killTweensOf(img.DOM.el);
4558
4559    let dx = this.mousePos.x - this.cacheMousePos.x;
4560    let dy = this.mousePos.y - this.cacheMousePos.y;
4561    let distance = Math.sqrt(dx * dx + dy * dy);
4562    if (distance !== 0) {
4563      dx /= distance;
4564      dy /= distance;
4565    }
4566    dx *= distance / 100;
4567    dy *= distance / 100;
4568
4569    gsap
4570      .timeline({
4571        onStart: () => this.onImageActivated(),
4572        onComplete: () => this.onImageDeactivated()
4573      })
4574      .fromTo(
4575        img.DOM.el,
4576        {
4577          opacity: 1,
4578          scale: 0,
4579          zIndex: this.zIndexVal,
4580          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
4581          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
4582        },
4583        {
4584          duration: 0.4,
4585          ease: 'power1',
4586          scale: 1,
4587          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
4588          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
4589        },
4590        0
4591      )
4592      .fromTo(
4593        img.DOM.inner,
4594        {
4595          scale: 2,
4596          filter: \`brightness(\${Math.max((400 * distance) / 100, 100)}%) contrast(\${Math.max(
4597            (400 * distance) / 100,
4598            100
4599          )}%)\`
4600        },
4601        {
4602          duration: 0.4,
4603          ease: 'power1',
4604          scale: 1,
4605          filter: 'brightness(100%) contrast(100%)'
4606        },
4607        0
4608      )
4609      .to(
4610        img.DOM.el,
4611        {
4612          duration: 0.4,
4613          ease: 'power3',
4614          opacity: 0
4615        },
4616        0.4
4617      )
4618      .to(
4619        img.DOM.el,
4620        {
4621          duration: 1.5,
4622          ease: 'power4',
4623          x: \`+=\${dx * 110}\`,
4624          y: \`+=\${dy * 110}\`
4625        },
4626        0.05
4627      );
4628  }
4629
4630  public destroy() {
4631    this.destroyed = true;
4632    if (this.rafId !== null) {
4633      cancelAnimationFrame(this.rafId);
4634      this.rafId = null;
4635    }
4636    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
4637    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
4638    this.container.removeEventListener('mousemove', this.initRender as EventListener);
4639    this.container.removeEventListener('touchmove', this.initRender as EventListener);
4640    this.images.forEach(img => {
4641      gsap.killTweensOf(img.DOM.el);
4642      img.destroy();
4643    });
4644  }
4645
4646  private onImageActivated() {
4647    this.activeImagesCount++;
4648    this.isIdle = false;
4649  }
4650
4651  private onImageDeactivated() {
4652    this.activeImagesCount--;
4653    if (this.activeImagesCount === 0) {
4654      this.isIdle = true;
4655    }
4656  }
4657}
4658
4659class ImageTrailVariant5 {
4660  private container: HTMLDivElement;
4661  private DOM: { el: HTMLDivElement };
4662  private images: ImageItem[];
4663  private imagesTotal: number;
4664  private imgPosition: number;
4665  private zIndexVal: number;
4666  private activeImagesCount: number;
4667  private isIdle: boolean;
4668  private threshold: number;
4669  private mousePos: { x: number; y: number };
4670  private lastMousePos: { x: number; y: number };
4671  private cacheMousePos: { x: number; y: number };
4672  private rafId: number | null = null;
4673  private destroyed = false;
4674  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
4675  private initRender!: (ev: MouseEvent | TouchEvent) => void;
4676  private lastAngle: number;
4677
4678  constructor(container: HTMLDivElement) {
4679    this.container = container;
4680    this.DOM = { el: container };
4681    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
4682    this.imagesTotal = this.images.length;
4683    this.imgPosition = 0;
4684    this.zIndexVal = 1;
4685    this.activeImagesCount = 0;
4686    this.isIdle = true;
4687    this.threshold = 80;
4688    this.mousePos = { x: 0, y: 0 };
4689    this.lastMousePos = { x: 0, y: 0 };
4690    this.cacheMousePos = { x: 0, y: 0 };
4691    this.lastAngle = 0;
4692
4693    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
4694      const rect = container.getBoundingClientRect();
4695      this.mousePos = getLocalPointerPos(ev, rect);
4696    };
4697    container.addEventListener('mousemove', handlePointerMove);
4698    container.addEventListener('touchmove', handlePointerMove);
4699
4700    const initRender = (ev: MouseEvent | TouchEvent) => {
4701      const rect = container.getBoundingClientRect();
4702      this.mousePos = getLocalPointerPos(ev, rect);
4703      this.cacheMousePos = { ...this.mousePos };
4704      this.rafId = requestAnimationFrame(() => this.render());
4705      container.removeEventListener('mousemove', initRender as EventListener);
4706      container.removeEventListener('touchmove', initRender as EventListener);
4707    };
4708    container.addEventListener('mousemove', initRender as EventListener);
4709    container.addEventListener('touchmove', initRender as EventListener);
4710    this.handlePointerMove = handlePointerMove;
4711    this.initRender = initRender;
4712  }
4713
4714  private render() {
4715    if (this.destroyed) return;
4716
4717    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
4718    if (distance > this.threshold) {
4719      this.showNextImage();
4720      this.lastMousePos = { ...this.mousePos };
4721    }
4722    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
4723    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
4724    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
4725    this.rafId = requestAnimationFrame(() => this.render());
4726  }
4727
4728  private showNextImage() {
4729    let dx = this.mousePos.x - this.cacheMousePos.x;
4730    let dy = this.mousePos.y - this.cacheMousePos.y;
4731    let angle = Math.atan2(dy, dx) * (180 / Math.PI);
4732    if (angle < 0) angle += 360;
4733    if (angle > 90 && angle <= 270) angle += 180;
4734    const isMovingClockwise = angle >= this.lastAngle;
4735    this.lastAngle = angle;
4736    let startAngle = isMovingClockwise ? angle - 10 : angle + 10;
4737    const distance = Math.sqrt(dx * dx + dy * dy);
4738    if (distance !== 0) {
4739      dx /= distance;
4740      dy /= distance;
4741    }
4742    dx *= distance / 150;
4743    dy *= distance / 150;
4744
4745    ++this.zIndexVal;
4746    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
4747    const img = this.images[this.imgPosition];
4748    gsap.killTweensOf(img.DOM.el);
4749
4750    gsap
4751      .timeline({
4752        onStart: () => this.onImageActivated(),
4753        onComplete: () => this.onImageDeactivated()
4754      })
4755      .fromTo(
4756        img.DOM.el,
4757        {
4758          opacity: 1,
4759          filter: 'brightness(80%)',
4760          scale: 0.1,
4761          zIndex: this.zIndexVal,
4762          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
4763          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2,
4764          rotation: startAngle
4765        },
4766        {
4767          duration: 1,
4768          ease: 'power2',
4769          scale: 1,
4770          filter: 'brightness(100%)',
4771          x: this.mousePos.x - (img.rect?.width ?? 0) / 2 + dx * 70,
4772          y: this.mousePos.y - (img.rect?.height ?? 0) / 2 + dy * 70,
4773          rotation: this.lastAngle
4774        },
4775        0
4776      )
4777      .to(
4778        img.DOM.el,
4779        {
4780          duration: 0.4,
4781          ease: 'expo',
4782          opacity: 0
4783        },
4784        0.5
4785      )
4786      .to(
4787        img.DOM.el,
4788        {
4789          duration: 1.5,
4790          ease: 'power4',
4791          x: \`+=\${dx * 120}\`,
4792          y: \`+=\${dy * 120}\`
4793        },
4794        0.05
4795      );
4796  }
4797
4798  public destroy() {
4799    this.destroyed = true;
4800    if (this.rafId !== null) {
4801      cancelAnimationFrame(this.rafId);
4802      this.rafId = null;
4803    }
4804    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
4805    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
4806    this.container.removeEventListener('mousemove', this.initRender as EventListener);
4807    this.container.removeEventListener('touchmove', this.initRender as EventListener);
4808    this.images.forEach(img => {
4809      gsap.killTweensOf(img.DOM.el);
4810      img.destroy();
4811    });
4812  }
4813
4814  private onImageActivated() {
4815    this.activeImagesCount++;
4816    this.isIdle = false;
4817  }
4818
4819  private onImageDeactivated() {
4820    this.activeImagesCount--;
4821    if (this.activeImagesCount === 0) this.isIdle = true;
4822  }
4823}
4824
4825class ImageTrailVariant6 {
4826  private container: HTMLDivElement;
4827  private DOM: { el: HTMLDivElement };
4828  private images: ImageItem[];
4829  private imagesTotal: number;
4830  private imgPosition: number;
4831  private zIndexVal: number;
4832  private activeImagesCount: number;
4833  private isIdle: boolean;
4834  private threshold: number;
4835  private mousePos: { x: number; y: number };
4836  private lastMousePos: { x: number; y: number };
4837  private cacheMousePos: { x: number; y: number };
4838  private rafId: number | null = null;
4839  private destroyed = false;
4840  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
4841  private initRender!: (ev: MouseEvent | TouchEvent) => void;
4842
4843  constructor(container: HTMLDivElement) {
4844    this.container = container;
4845    this.DOM = { el: container };
4846    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
4847    this.imagesTotal = this.images.length;
4848    this.imgPosition = 0;
4849    this.zIndexVal = 1;
4850    this.activeImagesCount = 0;
4851    this.isIdle = true;
4852    this.threshold = 80;
4853    this.mousePos = { x: 0, y: 0 };
4854    this.lastMousePos = { x: 0, y: 0 };
4855    this.cacheMousePos = { x: 0, y: 0 };
4856
4857    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
4858      const rect = container.getBoundingClientRect();
4859      this.mousePos = getLocalPointerPos(ev, rect);
4860    };
4861    container.addEventListener('mousemove', handlePointerMove);
4862    container.addEventListener('touchmove', handlePointerMove);
4863
4864    const initRender = (ev: MouseEvent | TouchEvent) => {
4865      const rect = container.getBoundingClientRect();
4866      this.mousePos = getLocalPointerPos(ev, rect);
4867      this.cacheMousePos = { ...this.mousePos };
4868      this.rafId = requestAnimationFrame(() => this.render());
4869      container.removeEventListener('mousemove', initRender as EventListener);
4870      container.removeEventListener('touchmove', initRender as EventListener);
4871    };
4872    container.addEventListener('mousemove', initRender as EventListener);
4873    container.addEventListener('touchmove', initRender as EventListener);
4874    this.handlePointerMove = handlePointerMove;
4875    this.initRender = initRender;
4876  }
4877
4878  private render() {
4879    if (this.destroyed) return;
4880
4881    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
4882    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
4883    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
4884
4885    if (distance > this.threshold) {
4886      this.showNextImage();
4887      this.lastMousePos = { ...this.mousePos };
4888    }
4889    if (this.isIdle && this.zIndexVal !== 1) {
4890      this.zIndexVal = 1;
4891    }
4892    this.rafId = requestAnimationFrame(() => this.render());
4893  }
4894
4895  private mapSpeedToSize(speed: number, minSize: number, maxSize: number) {
4896    const maxSpeed = 200;
4897    return minSize + (maxSize - minSize) * Math.min(speed / maxSpeed, 1);
4898  }
4899
4900  private mapSpeedToBrightness(speed: number, minB: number, maxB: number) {
4901    const maxSpeed = 70;
4902    return minB + (maxB - minB) * Math.min(speed / maxSpeed, 1);
4903  }
4904
4905  private mapSpeedToBlur(speed: number, minBlur: number, maxBlur: number) {
4906    const maxSpeed = 90;
4907    return minBlur + (maxBlur - minBlur) * Math.min(speed / maxSpeed, 1);
4908  }
4909
4910  private mapSpeedToGrayscale(speed: number, minG: number, maxG: number) {
4911    const maxSpeed = 90;
4912    return minG + (maxG - minG) * Math.min(speed / maxSpeed, 1);
4913  }
4914
4915  private showNextImage() {
4916    const dx = this.mousePos.x - this.cacheMousePos.x;
4917    const dy = this.mousePos.y - this.cacheMousePos.y;
4918    const speed = Math.sqrt(dx * dx + dy * dy);
4919
4920    ++this.zIndexVal;
4921    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
4922    const img = this.images[this.imgPosition];
4923
4924    const scaleFactor = this.mapSpeedToSize(speed, 0.3, 2);
4925    const brightnessValue = this.mapSpeedToBrightness(speed, 0, 1.3);
4926    const blurValue = this.mapSpeedToBlur(speed, 20, 0);
4927    const grayscaleValue = this.mapSpeedToGrayscale(speed, 600, 0);
4928
4929    gsap.killTweensOf(img.DOM.el);
4930    gsap
4931      .timeline({
4932        onStart: () => this.onImageActivated(),
4933        onComplete: () => this.onImageDeactivated()
4934      })
4935      .fromTo(
4936        img.DOM.el,
4937        {
4938          opacity: 1,
4939          scale: 0,
4940          zIndex: this.zIndexVal,
4941          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
4942          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
4943        },
4944        {
4945          duration: 0.8,
4946          ease: 'power3',
4947          scale: scaleFactor,
4948          filter: \`grayscale(\${grayscaleValue * 100}%) brightness(\${brightnessValue * 100}%) blur(\${blurValue}px)\`,
4949          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
4950          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
4951        },
4952        0
4953      )
4954      .fromTo(
4955        img.DOM.inner,
4956        { scale: 2 },
4957        {
4958          duration: 0.8,
4959          ease: 'power3',
4960          scale: 1
4961        },
4962        0
4963      )
4964      .to(
4965        img.DOM.el,
4966        {
4967          duration: 0.4,
4968          ease: 'power3.in',
4969          opacity: 0,
4970          scale: 0.2
4971        },
4972        0.45
4973      );
4974  }
4975
4976  public destroy() {
4977    this.destroyed = true;
4978    if (this.rafId !== null) {
4979      cancelAnimationFrame(this.rafId);
4980      this.rafId = null;
4981    }
4982    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
4983    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
4984    this.container.removeEventListener('mousemove', this.initRender as EventListener);
4985    this.container.removeEventListener('touchmove', this.initRender as EventListener);
4986    this.images.forEach(img => {
4987      gsap.killTweensOf(img.DOM.el);
4988      img.destroy();
4989    });
4990  }
4991
4992  private onImageActivated() {
4993    this.activeImagesCount++;
4994    this.isIdle = false;
4995  }
4996
4997  private onImageDeactivated() {
4998    this.activeImagesCount--;
4999    if (this.activeImagesCount === 0) {
5000      this.isIdle = true;
5001    }
5002  }
5003}
5004
5005function getNewPosition(position: number, offset: number, arr: ImageItem[]) {
5006  const realOffset = Math.abs(offset) % arr.length;
5007  if (position - realOffset >= 0) {
5008    return position - realOffset;
5009  } else {
5010    return arr.length - (realOffset - position);
5011  }
5012}
5013
5014class ImageTrailVariant7 {
5015  private container: HTMLDivElement;
5016  private DOM: { el: HTMLDivElement };
5017  private images: ImageItem[];
5018  private imagesTotal: number;
5019  private imgPosition: number;
5020  private zIndexVal: number;
5021  private activeImagesCount: number;
5022  private isIdle: boolean;
5023  private threshold: number;
5024  private mousePos: { x: number; y: number };
5025  private lastMousePos: { x: number; y: number };
5026  private cacheMousePos: { x: number; y: number };
5027  private rafId: number | null = null;
5028  private destroyed = false;
5029  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
5030  private initRender!: (ev: MouseEvent | TouchEvent) => void;
5031  private visibleImagesCount: number;
5032  private visibleImagesTotal: number;
5033
5034  constructor(container: HTMLDivElement) {
5035    this.container = container;
5036    this.DOM = { el: container };
5037    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
5038    this.imagesTotal = this.images.length;
5039    this.imgPosition = 0;
5040    this.zIndexVal = 1;
5041    this.activeImagesCount = 0;
5042    this.isIdle = true;
5043    this.threshold = 80;
5044    this.mousePos = { x: 0, y: 0 };
5045    this.lastMousePos = { x: 0, y: 0 };
5046    this.cacheMousePos = { x: 0, y: 0 };
5047    this.visibleImagesCount = 0;
5048    this.visibleImagesTotal = 9;
5049    this.visibleImagesTotal = Math.min(this.visibleImagesTotal, this.imagesTotal - 1);
5050
5051    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
5052      const rect = container.getBoundingClientRect();
5053      this.mousePos = getLocalPointerPos(ev, rect);
5054    };
5055    container.addEventListener('mousemove', handlePointerMove);
5056    container.addEventListener('touchmove', handlePointerMove);
5057
5058    const initRender = (ev: MouseEvent | TouchEvent) => {
5059      const rect = container.getBoundingClientRect();
5060      this.mousePos = getLocalPointerPos(ev, rect);
5061      this.cacheMousePos = { ...this.mousePos };
5062      this.rafId = requestAnimationFrame(() => this.render());
5063      container.removeEventListener('mousemove', initRender as EventListener);
5064      container.removeEventListener('touchmove', initRender as EventListener);
5065    };
5066    container.addEventListener('mousemove', initRender as EventListener);
5067    container.addEventListener('touchmove', initRender as EventListener);
5068    this.handlePointerMove = handlePointerMove;
5069    this.initRender = initRender;
5070  }
5071
5072  private render() {
5073    if (this.destroyed) return;
5074
5075    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
5076    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.3);
5077    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.3);
5078
5079    if (distance > this.threshold) {
5080      this.showNextImage();
5081      this.lastMousePos = { ...this.mousePos };
5082    }
5083    if (this.isIdle && this.zIndexVal !== 1) this.zIndexVal = 1;
5084
5085    this.rafId = requestAnimationFrame(() => this.render());
5086  }
5087
5088  private showNextImage() {
5089    ++this.zIndexVal;
5090    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
5091    const img = this.images[this.imgPosition];
5092    ++this.visibleImagesCount;
5093
5094    gsap.killTweensOf(img.DOM.el);
5095    const scaleValue = gsap.utils.random(0.5, 1.6);
5096
5097    gsap
5098      .timeline({
5099        onStart: () => this.onImageActivated(),
5100        onComplete: () => this.onImageDeactivated()
5101      })
5102      .fromTo(
5103        img.DOM.el,
5104        {
5105          scale: scaleValue - Math.max(gsap.utils.random(0.2, 0.6), 0),
5106          rotationZ: 0,
5107          opacity: 1,
5108          zIndex: this.zIndexVal,
5109          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
5110          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2
5111        },
5112        {
5113          duration: 0.4,
5114          ease: 'power3',
5115          scale: scaleValue,
5116          rotationZ: gsap.utils.random(-3, 3),
5117          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
5118          y: this.mousePos.y - (img.rect?.height ?? 0) / 2
5119        },
5120        0
5121      );
5122
5123    if (this.visibleImagesCount >= this.visibleImagesTotal) {
5124      const lastInQueue = getNewPosition(this.imgPosition, this.visibleImagesTotal, this.images);
5125      const oldImg = this.images[lastInQueue];
5126      gsap.to(oldImg.DOM.el, {
5127        duration: 0.4,
5128        ease: 'power4',
5129        opacity: 0,
5130        scale: 1.3,
5131        onComplete: () => {
5132          if (this.activeImagesCount === 0) {
5133            this.isIdle = true;
5134          }
5135        }
5136      });
5137    }
5138  }
5139
5140  public destroy() {
5141    this.destroyed = true;
5142    if (this.rafId !== null) {
5143      cancelAnimationFrame(this.rafId);
5144      this.rafId = null;
5145    }
5146    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
5147    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
5148    this.container.removeEventListener('mousemove', this.initRender as EventListener);
5149    this.container.removeEventListener('touchmove', this.initRender as EventListener);
5150    this.images.forEach(img => {
5151      gsap.killTweensOf(img.DOM.el);
5152      img.destroy();
5153    });
5154  }
5155
5156  private onImageActivated() {
5157    this.activeImagesCount++;
5158    this.isIdle = false;
5159  }
5160
5161  private onImageDeactivated() {
5162    this.activeImagesCount--;
5163  }
5164}
5165
5166class ImageTrailVariant8 {
5167  private container: HTMLDivElement;
5168  private DOM: { el: HTMLDivElement };
5169  private images: ImageItem[];
5170  private imagesTotal: number;
5171  private imgPosition: number;
5172  private zIndexVal: number;
5173  private activeImagesCount: number;
5174  private isIdle: boolean;
5175  private threshold: number;
5176  private mousePos: { x: number; y: number };
5177  private lastMousePos: { x: number; y: number };
5178  private cacheMousePos: { x: number; y: number };
5179  private rafId: number | null = null;
5180  private destroyed = false;
5181  private handlePointerMove!: (ev: MouseEvent | TouchEvent) => void;
5182  private initRender!: (ev: MouseEvent | TouchEvent) => void;
5183  private rotation: { x: number; y: number };
5184  private cachedRotation: { x: number; y: number };
5185  private zValue: number;
5186  private cachedZValue: number;
5187
5188  constructor(container: HTMLDivElement) {
5189    this.container = container;
5190    this.DOM = { el: container };
5191    this.images = [...container.querySelectorAll('.content__img')].map(img => new ImageItem(img as HTMLDivElement));
5192    this.imagesTotal = this.images.length;
5193    this.imgPosition = 0;
5194    this.zIndexVal = 1;
5195    this.activeImagesCount = 0;
5196    this.isIdle = true;
5197    this.threshold = 80;
5198    this.mousePos = { x: 0, y: 0 };
5199    this.lastMousePos = { x: 0, y: 0 };
5200    this.cacheMousePos = { x: 0, y: 0 };
5201    this.rotation = { x: 0, y: 0 };
5202    this.cachedRotation = { x: 0, y: 0 };
5203    this.zValue = 0;
5204    this.cachedZValue = 0;
5205
5206    const handlePointerMove = (ev: MouseEvent | TouchEvent) => {
5207      const rect = container.getBoundingClientRect();
5208      this.mousePos = getLocalPointerPos(ev, rect);
5209    };
5210    container.addEventListener('mousemove', handlePointerMove);
5211    container.addEventListener('touchmove', handlePointerMove);
5212
5213    const initRender = (ev: MouseEvent | TouchEvent) => {
5214      const rect = container.getBoundingClientRect();
5215      this.mousePos = getLocalPointerPos(ev, rect);
5216      this.cacheMousePos = { ...this.mousePos };
5217      this.rafId = requestAnimationFrame(() => this.render());
5218      container.removeEventListener('mousemove', initRender as EventListener);
5219      container.removeEventListener('touchmove', initRender as EventListener);
5220    };
5221    container.addEventListener('mousemove', initRender as EventListener);
5222    container.addEventListener('touchmove', initRender as EventListener);
5223    this.handlePointerMove = handlePointerMove;
5224    this.initRender = initRender;
5225  }
5226
5227  private render() {
5228    if (this.destroyed) return;
5229
5230    const distance = getMouseDistance(this.mousePos, this.lastMousePos);
5231    this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
5232    this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
5233
5234    if (distance > this.threshold) {
5235      this.showNextImage();
5236      this.lastMousePos = { ...this.mousePos };
5237    }
5238    if (this.isIdle && this.zIndexVal !== 1) {
5239      this.zIndexVal = 1;
5240    }
5241    this.rafId = requestAnimationFrame(() => this.render());
5242  }
5243
5244  private showNextImage() {
5245    const rect = this.container.getBoundingClientRect();
5246    const centerX = rect.width / 2;
5247    const centerY = rect.height / 2;
5248    const relX = this.mousePos.x - centerX;
5249    const relY = this.mousePos.y - centerY;
5250
5251    this.rotation.x = -(relY / centerY) * 30;
5252    this.rotation.y = (relX / centerX) * 30;
5253    this.cachedRotation = { ...this.rotation };
5254
5255    const distanceFromCenter = Math.sqrt(relX * relX + relY * relY);
5256    const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
5257    const proportion = distanceFromCenter / maxDistance;
5258    this.zValue = proportion * 1200 - 600;
5259    this.cachedZValue = this.zValue;
5260    const normalizedZ = (this.zValue + 600) / 1200;
5261    const brightness = 0.2 + normalizedZ * 2.3;
5262
5263    ++this.zIndexVal;
5264    this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
5265    const img = this.images[this.imgPosition];
5266    gsap.killTweensOf(img.DOM.el);
5267
5268    gsap
5269      .timeline({
5270        onStart: () => this.onImageActivated(),
5271        onComplete: () => this.onImageDeactivated()
5272      })
5273      .set(this.DOM.el, { perspective: 1000 }, 0)
5274      .fromTo(
5275        img.DOM.el,
5276        {
5277          opacity: 1,
5278          z: 0,
5279          scale: 1 + this.cachedZValue / 1000,
5280          zIndex: this.zIndexVal,
5281          x: this.cacheMousePos.x - (img.rect?.width ?? 0) / 2,
5282          y: this.cacheMousePos.y - (img.rect?.height ?? 0) / 2,
5283          rotationX: this.cachedRotation.x,
5284          rotationY: this.cachedRotation.y,
5285          filter: \`brightness(\${brightness})\`
5286        },
5287        {
5288          duration: 1,
5289          ease: 'expo',
5290          scale: 1 + this.zValue / 1000,
5291          x: this.mousePos.x - (img.rect?.width ?? 0) / 2,
5292          y: this.mousePos.y - (img.rect?.height ?? 0) / 2,
5293          rotationX: this.rotation.x,
5294          rotationY: this.rotation.y
5295        },
5296        0
5297      )
5298      .to(
5299        img.DOM.el,
5300        {
5301          duration: 0.4,
5302          ease: 'power2',
5303          opacity: 0,
5304          z: -800
5305        },
5306        0.3
5307      );
5308  }
5309
5310  public destroy() {
5311    this.destroyed = true;
5312    if (this.rafId !== null) {
5313      cancelAnimationFrame(this.rafId);
5314      this.rafId = null;
5315    }
5316    this.container.removeEventListener('mousemove', this.handlePointerMove as EventListener);
5317    this.container.removeEventListener('touchmove', this.handlePointerMove as EventListener);
5318    this.container.removeEventListener('mousemove', this.initRender as EventListener);
5319    this.container.removeEventListener('touchmove', this.initRender as EventListener);
5320    this.images.forEach(img => {
5321      gsap.killTweensOf(img.DOM.el);
5322      img.destroy();
5323    });
5324  }
5325
5326  private onImageActivated() {
5327    this.activeImagesCount++;
5328    this.isIdle = false;
5329  }
5330
5331  private onImageDeactivated() {
5332    this.activeImagesCount--;
5333    if (this.activeImagesCount === 0) {
5334      this.isIdle = true;
5335    }
5336  }
5337}
5338
5339type ImageTrailConstructor =
5340  | typeof ImageTrailVariant1
5341  | typeof ImageTrailVariant2
5342  | typeof ImageTrailVariant3
5343  | typeof ImageTrailVariant4
5344  | typeof ImageTrailVariant5
5345  | typeof ImageTrailVariant6
5346  | typeof ImageTrailVariant7
5347  | typeof ImageTrailVariant8;
5348
5349const variantMap: Record<number, ImageTrailConstructor> = {
5350  1: ImageTrailVariant1,
5351  2: ImageTrailVariant2,
5352  3: ImageTrailVariant3,
5353  4: ImageTrailVariant4,
5354  5: ImageTrailVariant5,
5355  6: ImageTrailVariant6,
5356  7: ImageTrailVariant7,
5357  8: ImageTrailVariant8
5358};
5359
5360interface ImageTrailProps {
5361  items?: string[];
5362  variant?: number;
5363}
5364
5365export default function ImageTrail({ items = [], variant = 1 }: ImageTrailProps): JSX.Element {
5366  const containerRef = useRef<HTMLDivElement>(null);
5367
5368  useEffect(() => {
5369    if (!containerRef.current) return;
5370    const Cls = variantMap[variant] || variantMap[1];
5371    const instance = new Cls(containerRef.current);
5372
5373    return () => {
5374      instance.destroy();
5375    };
5376    // eslint-disable-next-line react-hooks/exhaustive-deps
5377  }, [variant, items]);
5378
5379  return (
5380    <div className="w-full h-full relative z-[100] rounded-lg bg-transparent overflow-visible" ref={containerRef}>
5381      {items.map((url, i) => (
5382        <div
5383          className="content__img w-[190px] aspect-[1.1] rounded-[15px] absolute top-0 left-0 opacity-0 overflow-hidden [will-change:transform,filter]"
5384          key={i}
5385        >
5386          <div
5387            className="content__img-inner bg-center bg-cover w-[calc(100%+20px)] h-[calc(100%+20px)] absolute top-[-10px] left-[-10px]"
5388            style={{ backgroundImage: \`url(\${url})\` }}
5389          />
5390        </div>
5391      ))}
5392    </div>
5393  );
5394}
5395`,z={dependencies:"gsap",usage:`import ImageTrail from './ImageTrail;'
5396
5397<div style={{ height: '500px', position: 'relative', overflow: 'hidden'}}>
5398  <ImageTrail
5399    key={key}
5400    items={[
5401      'https://picsum.photos/id/287/300/300',
5402      'https://picsum.photos/id/1001/300/300',
5403      'https://picsum.photos/id/1025/300/300',
5404      'https://picsum.photos/id/1026/300/300',
5405      'https://picsum.photos/id/1027/300/300',
5406      'https://picsum.photos/id/1028/300/300',
5407      'https://picsum.photos/id/1029/300/300',
5408      'https://picsum.photos/id/1030/300/300',
5409      // ...
5410    ]}
5411    variant={1}
5412  />
5413</div>`,code:D,css:w,tailwind:V,tsCode:O,tsTailwind:C},t={variant:"1"},J=()=>{const{props:i,updateProp:a,resetProps:r,hasChanges:h}=g(t),{variant:n}=i,[m,c]=L(),l=u.useMemo(()=>[{name:"items",type:"string[]",default:"[]",description:"An array of image URLs which will be animated in the trail."},{name:"variant",type:"number",default:"1",description:"A number from 1 to 8 - all different animation styles."}],[]);return e.jsx(P,{props:i,defaultProps:t,resetProps:r,hasChanges:h,children:e.jsxs(p,{children:[e.jsxs(M,{children:[e.jsxs(v,{position:"relative",className:"demo-container",h:4
541300,overflow:"hidden",children:[e.jsx(R,{items:["https://picsum.photos/id/287/300/300","https://picsum.photos/id/1001/300/300","https://picsum.photos/id/1025/300/300","https://picsum.photos/id/1026/300/300","https://picsum.photos/id/1027/300/300","https://picsum.photos/id/1028/300/300","https://picsum.photos/id/1029/300/300","https://picsum.photos/id/1030/300/300"],variant:n},m),e.jsxs(s,{position:"absolute",justifyContent:"center",flexDirection:"column",alignItems:"center",children:[e.jsx(o,{className:"demo-instruction",fontSize:"clamp(2rem, 6vw, 6rem)",fontWeight:600,mb:0,children:"Hover Me."}),e.jsxs(o,{fontSize:"18px",fontWeight:600,color:"#a6a6a6",mt:0,children:["Variant ",n]})]})]}),e.jsx(s,{justify:"flex-end",mt:2,mb:-2,children:e.jsx(I,{backgroundId:"image-trail",currentProps:{variant:n},defaultProps:t})}),e.jsx(T,{children:e.jsx(b,{title:"Variant",options:[{value:"1",label:"1"},{value:"2",label:"2"},{value:"3",label:"3"},{value:"4",label:"4"},{value:"5",label:"5"},{value:"6",label:"6"},{value:"7",label:"7"},{value:"8",label:"8"}],value:n,onChange:d=>{a("variant",d),c()}})}),e.jsx(x,{data:l}),e.jsx(E,{dependencyList:["gsap"]})]}),e.jsx(y,{children:e.jsx(f,{codeObject:z,componentName:"ImageTrail"})})]})})};export{J as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.