PageSourceSearch

https://popup.games/public/assets/new/script.js

js popup.games collected 2026-10-02 01:01:54 UTC 12,383 bytes, 410 lines download raw bytes

1// ===== Hero Canvas Animation =====
2(function () {
3  const canvas = document.getElementById('heroCanvas');
4  if (!canvas) return;
5  const ctx = canvas.getContext('2d');
6  let w, h, particles, mouse;
7
8  const COLORS = [
9    'rgba(124, 58, 237, ',   // purple
10    'rgba(236, 72, 153, ',   // pink
11    'rgba(14, 165, 233, ',   // blue
12    'rgba(167, 139, 250, ',  // light purple
13    'rgba(244, 114, 182, ',  // light pink
14  ];
15
16  const SHAPES = ['circle', 'diamond', 'triangle', 'square'];
17
18  mouse = { x: -1000, y: -1000 };
19
20  function resize() {
21    const hero = canvas.parentElement;
22    w = canvas.width = hero.offsetWidth;
23    h = canvas.height = hero.offsetHeight;
24  }
25
26  function createParticle() {
27    const color = COLORS[Math.floor(Math.random() * COLORS.length)];
28    return {
29      x: Math.random() * w,
30      y: Math.random() * h,
31      vx: (Math.random() - 0.5) * 0.4,
32      vy: (Math.random() - 0.5) * 0.4,
33      size: Math.random() * 4 + 2,
34      color: color,
35      opacity: Math.random() * 0.5 + 0.1,
36      shape: SHAPES[Math.floor(Math.random() * SHAPES.length)],
37      pulse: Math.random() * Math.PI * 2,
38      pulseSpeed: Math.random() * 0.02 + 0.005,
39    };
40  }
41
42  function init() {
43    resize();
44    const count = Math.min(Math.floor((w * h) / 12000), 80);
45    particles = [];
46    for (let i = 0; i < count; i++) {
47      particles.push(createParticle());
48    }
49  }
50
51  function drawShape(p, size) {
52    ctx.beginPath();
53    switch (p.shape) {
54      case 'circle':
55        ctx.arc(p.x, p.y, size, 0, Math.PI * 2);
56        break;
57      case 'diamond':
58        ctx.moveTo(p.x, p.y - size);
59        ctx.lineTo(p.x + size, p.y);
60        ctx.lineTo(p.x, p.y + size);
61        ctx.lineTo(p.x - size, p.y);
62        ctx.closePath();
63        break;
64      case 'triangle':
65        ctx.moveTo(p.x, p.y - size);
66        ctx.lineTo(p.x + size * 0.866, p.y + size * 0.5);
67        ctx.lineTo(p.x - size * 0.866, p.y + size * 0.5);
68        ctx.closePath();
69        break;
70      case 'square':
71        const half = size * 0.7;
72        ctx.rect(p.x - half, p.y - half, half * 2, half * 2);
73        break;
74    }
75  }
76
77  function animate() {
78    ctx.clearRect(0, 0, w, h);
79
80    // Draw connections
81    for (let i = 0; i < particles.length; i++) {
82      for (let j = i + 1; j < particles.length; j++) {
83        const dx = particles[i].x - particles[j].x;
84        const dy = particles[i].y - particles[j].y;
85        const dist = Math.sqrt(dx * dx + dy * dy);
86        if (dist < 150) {
87          const alpha = (1 - dist / 150) * 0.08;
88          ctx.strokeStyle = `rgba(167, 139, 250, ${alpha})`;
89          ctx.lineWidth = 0.5;
90          ctx.beginPath();
91          ctx.moveTo(particles[i].x, particles[i].y);
92          ctx.lineTo(particles[j].x, particles[j].y);
93          ctx.stroke();
94        }
95      }
96    }
97
98    // Update and draw particles
99    particles.forEach(p => {
100      // Mouse interaction — gentle push away
101      const mdx = p.x - mouse.x;
102      const mdy = p.y - mouse.y;
103      const mDist = Math.sqrt(mdx * mdx + mdy * mdy);
104      if (mDist < 120) {
105        const force = (120 - mDist) / 120 * 0.3;
106        p.vx += (mdx / mDist) * force;
107        p.vy += (mdy / mDist) * force;
108      }
109
110      // Damping
111      p.vx *= 0.99;
112      p.vy *= 0.99;
113
114      p.x += p.vx;
115      p.y += p.vy;
116
117      // Wrap around edges
118      if (p.x < -20) p.x = w + 20;
119      if (p.x > w + 20) p.x = -20;
120      if (p.y < -20) p.y = h + 20;
121      if (p.y > h + 20) p.y = -20;
122
123      // Pulse
124      p.pulse += p.pulseSpeed;
125      const pulseAlpha = p.opacity + Math.sin(p.pulse) * 0.15;
126      const pulseSize = p.size + Math.sin(p.pulse) * 0.8;
127
128      // Draw glow
129      const gradient = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, pulseSize * 4);
130      gradient.addColorStop(0, p.color + (pulseAlpha * 0.3) + ')');
131      gradient.addColorStop(1, p.color + '0)');
132      ctx.fillStyle = gradient;
133      ctx.beginPath();
134      ctx.arc(p.x, p.y, pulseSize * 4, 0, Math.PI * 2);
135      ctx.fill();
136
137      // Draw shape
138      ctx.fillStyle = p.color + pulseAlpha + ')';
139      drawShape(p, pulseSize);
140      ctx.fill();
141    });
142
143    requestAnimationFrame(animate);
144  }
145
146  // Mouse tracking on hero
147  canvas.parentElement.addEventListener('mousemove', (e) => {
148    const rect = canvas.parentElement.getBoundingClientRect();
149    mouse.x = e.clientX - rect.left;
150    mouse.y = e.clientY - rect.top;
151  });
152
153  canvas.parentElement.addEventListener('mouseleave', () => {
154    mouse.x = -1000;
155    mouse.y = -1000;
156  });
157
158  window.addEventListener('resize', init);
159  init();
160  animate();
161})();
162
163// Navbar scroll effect
164const navbar = document.getElementById('navbar');
165window.addEventListener('scroll', () => {
166  navbar.classList.toggle('scrolled', window.scrollY > 50);
167});
168
169// About section app icons (random 4 + randomized sizes)
170(function () {
171  const aboutCardStack = document.getElementById('aboutCardStack');
172  if (!aboutCardStack) return;
173
174  const aboutImagePool = [
175    {
176      src: 'https://play-lh.googleusercontent.com/Cps3lut7md1MgGTMT7kXO05JaskmCebmZJmLaMswdqODf-4zTU20Va2k2d1rnUA72KTn',
177      alt: 'Ludo Buzz',
178    },
179    {
180      src: 'https://play-lh.googleusercontent.com/dRdLdkFaRiowGgt7Nap2v4cIU03M4dfVuZpPoWxHNBf3zFkC_MrN65RFOq8pdTXEojC3cFuNLQ0V9WVz5N7k5Q',
181      alt: 'Ludo Gold',
182    },
183    {
184      src: 'https://play-lh.googleusercontent.com/FPl6546xDukrkhU7VPL4-41wT1wmrTHc6HW9JCCRX01pLWSQ4YfBoO2cBuLzkmxJLN0v2EtWjKdWeih53LYHhg',
185      alt: 'Parchisi Buzz',
186    },
187    {
188      src: 'https://play-lh.googleusercontent.com/3uQTn1-bZx9mFyrcgXotK349r_J6snaPYqexJekl-y6lBGlzIiUuJh9TAcMnCFi4Ke1f',
189      alt: 'DOP: Draw One Part',
190    },
191    {
192      src: 'https://play-lh.googleusercontent.com/lr5ED7GdGAx9OIGBjrbqR0OJBNd1-EhWrRaGwgHtOCi9-fHh4vUCDvD4lje26r_U-1c',
193      alt: 'Parking Jam',
194    },
195    {
196      src: 'https://play-lh.googleusercontent.com/27N9UZRWKC_Ef0e_Z_fRqJW4C9_7Tkjy3OdKe6UlBCuVrih5N5WYXE03tmN4lt4e-jQ',
197      alt: 'Color Block Blast',
198    },
199    {
200      src: 'https://play-lh.googleusercontent.com/f2uLkJDS8Cbk2b6z0gvsSppOWzO9FpxMF92eqAkY61qyEoO6MJRjcd86mBC53oHN7u7S',
201      alt: '3 Tile Match',
202    },
203    {
204      src: 'https://play-lh.googleusercontent.com/N5CVW2ARYQOe085RpWu2ZWQQYrTOV3mYxlETYq-vDq7eeZnM1plxtCmgkRpf64WjQw',
205      alt: '2 Pics 1 Word',
206    },
207    {
208      src: 'https://play-lh.googleusercontent.com/aOxzhfrBomuIzT4eKS07VQI_mS368I2TmA0ydwfxcg6zLV3pl1UHRRt-vo5QM-L6Muc',
209      alt: 'Logo Puzzle',
210    },
211    {
212      src: 'https://play-lh.googleusercontent.com/ttG6kY-qY4M4EreqUXC9WOxm7xQennftJYBpdQy2US_mLpNcvrV-caUOnfKO5nQl09oE',
213      alt: '4 Pics 1 Word',
214    },
215  ];
216
217  const cardSlots = ['card-1', 'card-2', 'card-3', 'card-4'];
218  const swapDelayMinMs = 2600;
219  const swapDelayMaxMs = 8200;
220
221  function shuffle(items) {
222    const arr = [...items];
223    for (let i = arr.length - 1; i > 0; i -= 1) {
224      const j = Math.floor(Math.random() * (i + 1));
225      [arr[i], arr[j]] = [arr[j], arr[i]];
226    }
227    return arr;
228  }
229
230  function getRandomInt(min, max) {
231    return Math.floor(Math.random() * (max - min + 1)) + min;
232  }
233
234  function getRandomDelay() {
235    return getRandomInt(swapDelayMinMs, swapDelayMaxMs);
236  }
237
238  function randomizeCardStyle(card, img) {
239    const cardSize = getRandomInt(104, 136);
240    const iconSize = Math.round(cardSize * 0.68);
241    const cardTilt = (Math.random() * 12 - 6).toFixed(1); // -6deg to +6deg
242
243    card.style.setProperty('--card-size', `${cardSize}px`);
244    card.style.setProperty('--image-size', `${iconSize}px`);
245    card.style.setProperty('--card-tilt', `${cardTilt}deg`);
246    img.width = iconSize;
247    img.height = iconSize;
248  }
249
250  function pickNextImage(excludedAlts, currentAlt) {
251    let candidates = aboutImagePool.filter((item) => !excludedAlts.has(item.alt) && item.alt !== currentAlt);
252    if (candidates.length === 0) {
253      candidates = aboutImagePool.filter((item) => item.alt !== currentAlt);
254    }
255    if (candidates.length === 0) return null;
256    return candidates[getRandomInt(0, candidates.length - 1)];
257  }
258
259  function preloadImage(src, onDone) {
260    const preloader = new Image();
261    preloader.decoding = 'async';
262    preloader.onload = onDone;
263    preloader.onerror = onDone;
264    preloader.src = src;
265  }
266
267  const selectedImages = shuffle(aboutImagePool).slice(0, cardSlots.length);
268  const cardStates = [];
269  aboutCardStack.innerHTML = '';
270
271  selectedImages.forEach((image, index) => {
272    const card = document.createElement('div');
273    card.className = `about-floating-card ${cardSlots[index]}`;
274
275    const img = document.createElement('img');
276    img.src = image.src;
277    img.alt = image.alt;
278    img.loading = 'lazy';
279    img.decoding = 'async';
280    img.className = 'about-card-img';
281
282    randomizeCardStyle(card, img);
283
284    card.appendChild(img);
285    aboutCardStack.appendChild(card);
286    cardStates.push({ card, img, image });
287  });
288
289  function scheduleSwap(index) {
290    window.setTimeout(() => {
291      if (document.hidden) {
292        scheduleSwap(index);
293        return;
294      }
295
296      const state = cardStates[index];
297      if (!state) return;
298
299      const otherAlts = new Set(
300        cardStates
301          .filter((_, i) => i !== index)
302          .map((item) => item.image.alt)
303      );
304      const nextImage = pickNextImage(otherAlts, state.image.alt);
305      if (!nextImage) {
306        scheduleSwap(index);
307        return;
308      }
309
310      preloadImage(nextImage.src, () => {
311        state.img.classList.add('is-hidden');
312        window.setTimeout(() => {
313          state.image = nextImage;
314          state.img.src = nextImage.src;
315          state.img.alt = nextImage.alt;
316          randomizeCardStyle(state.card, state.img);
317          window.requestAnimationFrame(() => {
318            state.img.classList.remove('is-hidden');
319          });
320          scheduleSwap(index);
321        }, 220);
322      });
323    }, getRandomDelay());
324  }
325
326  cardStates.forEach((_, index) => {
327    scheduleSwap(index);
328  });
329})();
330
331// Mobile menu toggle
332const menuToggle = document.getElementById('menuToggle');
333const navLinks = document.getElementById('navLinks');
334const navOverlay = document.getElementById('navOverlay');
335
336function closeMenu() {
337  navLinks.classList.remove('active');
338  menuToggle.classList.remove('active');
339  navOverlay.classList.remove('active');
340}
341
342menuToggle.addEventListener('click', () => {
343  const isOpen = navLinks.classList.toggle('active');
344  menuToggle.classList.toggle('active');
345  navOverlay.classList.toggle('active', isOpen);
346});
347
348navOverlay.addEventListener('click', closeMenu);
349
350// Close mobile menu on link click
351navLinks.querySelectorAll('a').forEach(link => {
352  link.addEventListener('click', closeMenu);
353});
354
355// Scroll animations
356const observerOptions = {
357  threshold: 0.1,
358  rootMargin: '0px 0px -50px 0px'
359};
360
361const observer = new IntersectionObserver((entries) => {
362  entries.forEach(entry => {
363    if (entry.isIntersecting) {
364      entry.target.classList.add('visible');
365    }
366  });
367}, observerOptions);
368
369// Add fade-in class to elements
370document.querySelectorAll('.game-card, .feature-card, .about-content, .about-visual, .contact-form, .contact-info, .section-header, .category-title').forEach(el => {
371  el.classList.add('fade-in');
372  observer.observe(el);
373});
374
375// Contact form handler
376const contactForm = document.getElementById('contactForm');
377if (contactForm) {
378  contactForm.addEventListener('submit', (e) => {
379    e.preventDefault();
380    const btn = contactForm.querySelector('button[type="submit"]');
381    if (!btn) return;
382    const originalText = btn.textContent;
383    btn.textContent = 'Message Sent!';
384    btn.style.background = 'linear-gradient(135deg, #10b981, #059669)';
385    setTimeout(() => {
386      btn.textContent = originalText;
387      btn.style.background = '';
388      contactForm.reset();
389    }, 3000);
390  });
391}
392
393// Active nav link on scroll
394const sections = document.querySelectorAll('section[id]');
395window.addEventListener('scroll', () => {
396  const scrollY = window.scrollY + 100;
397  sections.forEach(section => {
398    const sectionTop = section.offsetTop;
399    const sectionHeight = section.offsetHeight;
400    const sectionId = section.getAttribute('id');
401    const navLink = document.querySelector(`.nav-links a[href="#${sectionId}"]`);
402    if (navLink) {
403      if (scrollY >= sectionTop && scrollY < sectionTop + sectionHeight) {
404        navLink.style.color = 'var(--text)';
405      } else {
406        navLink.style.color = '';
407      }
408    }
409  });
410});

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.