<!– GSAP core –>
<script src=”https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js”></script>
<!– Physics2DPlugin –>
<script src=”https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/Physics2DPlugin.min.js”></script>
<!– Canvas –>
<div class=”trail-container”></div>
<script>
document.addEventListener(“DOMContentLoaded”, () => {
gsap.registerPlugin(Physics2DPlugin);
const container = document.querySelector(“.trail-container”);
// ─── ICON LIBRARY ─────────────────────────────────────────────────────────────
const images = [
“http://junkerr.dk/wp-content/uploads/2025/05/music.png”,
“http://junkerr.dk/wp-content/uploads/2025/05/star.png”,
“http://junkerr.dk/wp-content/uploads/2025/05/like-button.png”,
“http://junkerr.dk/wp-content/uploads/2025/05/love.png”,
“http://junkerr.dk/wp-content/uploads/2025/05/crown.png”,
“http://junkerr.dk/wp-content/uploads/2025/05/smile.png”,
“http://junkerr.dk/wp-content/uploads/2025/05/dancing.png”,
“http://junkerr.dk/wp-content/uploads/2025/05/camera.png”
];
function shuffle(a){ for(let i=a.length-1;i>0;i–){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];}return a; }
let pool = shuffle(images.slice()), ptr = 0;
function nextImage(){
const src = pool[ptr++];
if(ptr>=pool.length){ pool=shuffle(images.slice()); ptr=0; }
return src;
}
// ────────────────────────────────────────────────────────────────────────────────
const cfg = {
spawnInterval: 0.12, // secs between spawns while moving
imageLifespan: 0.8, // secs drifting
removalDelay: 0.05, // small extra delay
inDuration: 0.3, // scale-in
outDuration: 0.8, // drop-out speed
driftMinVel: 30, // px/s
driftMaxVel: 80, // px/s
gravity: 15, // px/s²
bounceEase: “bounce.out”
};
let mouse = { x:0, y:0, moving:false, idleTO:null },
lastSpawn = 0;
document.addEventListener(“mousemove”, e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
mouse.moving = true;
clearTimeout(mouse.idleTO);
mouse.idleTO = setTimeout(()=>mouse.moving=false,100);
});
function spawn(){
const now = performance.now()/800;
if(!mouse.moving || now – lastSpawn < cfg.spawnInterval) return;
lastSpawn = now;
const rect = container.getBoundingClientRect();
if( mouse.x<rect.left||mouse.x>rect.right||mouse.y<rect.top||mouse.y>rect.bottom ) return;
// create icon
const img = document.createElement(“img”);
img.className = “trail-img”;
img.src = nextImage();
container.append(img);
// center under cursor
const { width, height } = img.getBoundingClientRect();
const x0 = mouse.x – rect.left – width/2;
const y0 = mouse.y – rect.top – height/2;
const rot = (Math.random()-0.5)*30;
// scale-in
gsap.set(img, { x:x0, y:y0, rotation:rot, scale:0 });
gsap.to(img, { duration: cfg.inDuration, scale:1, ease:”power3.out” });
// physics drift
const velocity = cfg.driftMinVel + Math.random()*(cfg.driftMaxVel – cfg.driftMinVel);
const angle = Math.random()*360;
gsap.to(img, {
duration: cfg.imageLifespan + cfg.removalDelay,
physics2D: { velocity, angle, gravity: cfg.gravity },
ease: “none”
});
// after drift, bounce once then drop
const floorY = container.clientHeight – height/2;
gsap.delayedCall(cfg.imageLifespan + cfg.removalDelay, () => {
gsap.to(img, {
duration: 0.6,
y: floorY,
ease: cfg.bounceEase,
onComplete() {
// immediately drop out of view
gsap.to(img, {
duration: cfg.outDuration,
y: container.clientHeight + height,
ease: “power1.in”,
onComplete: () => img.remove()
});
}
});
});
}
// main loop
(function loop(){
spawn();
requestAnimationFrame(loop);
})();
});
</script>
.trail-container {
position: relative;
width: 100%;
height: 100%;
overflow: visible;
}
.trail-img {
position: absolute;
width: 200px;
height: 200px;
object-fit: cover;
border-radius: 4px;
pointer-events: none;
transform-origin: center center;
}
/* all trail images are 150×150px, cover mode, centered */
.trail-container .trail-img {
width: 150px;
height: 150px;
object-fit: cover;
object-position: center center;
}
/* optional: if you want to tweak size easily, use a CSS variable */
:root {
–trail-size: 120px;
}
.trail-container .trail-img {
width: var(–trail-size);
height: var(–trail-size);
object-fit: cover;
object-position: center center;
}