PageSourceSearch

https://task360.app/assets/js/scroll-animations.js

js task360.app collected 2026-09-24 13:43:28 UTC 3,659 bytes, 105 lines download raw bytes

1/**
2 * Task360 Scroll Animations
3 * - Fade-in/slide-up on viewport entry (IntersectionObserver)
4 * - Stat counter animation (counts from 0 to target)
5 */
6(function() {
7  'use strict';
8
9  // ── Scroll-triggered fade-in ──
10  var animEls = document.querySelectorAll('.animate-on-scroll');
11  if (animEls.length && 'IntersectionObserver' in window) {
12    var observer = new IntersectionObserver(function(entries) {
13      entries.forEach(function(entry) {
14        if (entry.isIntersecting) {
15          entry.target.classList.add('is-visible');
16          observer.unobserve(entry.target);
17        }
18      });
19    }, { threshold: 0.15 });
20
21    animEls.forEach(function(el) { observer.observe(el); });
22  } else {
23    // Fallback: show everything immediately
24    animEls.forEach(function(el) { el.classList.add('is-visible'); });
25  }
26
27  // ── Stat counter animation ──
28  var statEls = document.querySelectorAll('.stat-number[data-target]');
29  if (statEls.length && 'IntersectionObserver' in window) {
30    var counted = false;
31    var statObserver = new IntersectionObserver(function(entries) {
32      entries.forEach(function(entry) {
33        if (entry.isIntersecting && !counted) {
34          counted = true;
35          animateCounters();
36          statObserver.disconnect();
37        }
38      });
39    }, { threshold: 0.3 });
40
41    statEls.forEach(function(el) { statObserver.observe(el); });
42  }
43
44  function animateCounters() {
45    statEls.forEach(function(el) {
46      var target = parseFloat(el.getAttribute('data-target'));
47      var suffix = el.getAttribute('data-suffix') || '';
48      var duration = 1600;
49      var start = 0;
50      var startTime = null;
51
52      function step(timestamp) {
53        if (!startTime) startTime = timestamp;
54        var progress = Math.min((timestamp - startTime) / duration, 1);
55        // Ease-out cubic
56        var eased = 1 - Math.pow(1 - progress, 3);
57        var current = Math.round(eased * target);
58        el.textContent = current + suffix;
59        if (progress < 1) {
60          requestAnimationFrame(step);
61        } else {
62          el.textContent = target + suffix;
63        }
64      }
65
66      requestAnimationFrame(step);
67    });
68  }
69
70  // ── FAQ Accordion ──
71  document.querySelectorAll('.faq-question').forEach(function(btn) {
72    btn.addEventListener('click', function() {
73      var item = btn.closest('.faq-item');
74      var isOpen = item.classList.contains('open');
75      item.classList.toggle('open');
76      btn.setAttribute('aria-expanded', !isOpen);
77    });
78  });
79
80  // ── Download Gates ──
81  document.querySelectorAll('.download-gate-form').forEach(function(form) {
82    form.addEventListener('submit', function(e) {
83      e.preventDefault();
84      var gate = form.closest('.download-gate');
85      var emailInput = form.querySelector('input[type="email"]');
86      if (!emailInput.value || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailInput.value)) {
87        emailInput.style.borderColor = '#e53e3e';
88        return;
89      }
90      var btn = form.querySelector('button[type="submit"]');
91      btn.disabled = true;
92      btn.textContent = '...';
93      var data = new FormData(form);
94      fetch(form.action, { method: 'POST', body: data, headers: { 'Accept': 'application/json' } })
95        .then(function() {
96          gate.querySelector('.download-gate-locked').style.display = 'none';
97          gate.querySelector('.download-gate-unlocked').style.display = 'block';
98        })
99        .catch(function() {
100          gate.querySelector('.download-gate-locked').style.display = 'none';
101          gate.querySelector('.download-gate-unlocked').style.display = 'block';
102        });
103    });
104  });
105})();

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.