PageSourceSearch

https://isekainextgen.com/scripts/main.js?v=21.0

js isekainextgen.com collected 2026-10-01 12:11:59 UTC 6,210 bytes, 179 lines download raw bytes

1/** Site behavior: navigation, scroll reveal, counters, tech marquee */
2(function () {
3    'use strict';
4
5    const MOBILE = 860;
6
7    /* ---------- Navigation ---------- */
8
9    function normalize(path) {
10        if (!path || path === '/index.html') return '/';
11        return path.replace(/\/+$/, '') || '/';
12    }
13
14    function initNav() {
15        const current = normalize(window.location.pathname);
16        document.querySelectorAll('.nav-link[href]').forEach((link) => {
17            const target = normalize(new URL(link.getAttribute('href'), window.location.origin).pathname);
18            const active = target === current;
19            link.classList.toggle('active', active);
20            if (active) link.setAttribute('aria-current', 'page');
21        });
22
23        const toggle = document.querySelector('.menu-toggle');
24        const list = document.querySelector('.nav-list');
25        const nav = document.querySelector('.navbar');
26        if (!toggle || !list || !nav) return;
27
28        function setOpen(open) {
29            list.classList.toggle('active', open);
30            toggle.classList.toggle('active', open);
31            document.body.style.overflow = open ? 'hidden' : '';
32            toggle.setAttribute('aria-expanded', String(open));
33            toggle.setAttribute('aria-label', open ? 'Close menu' : 'Open menu');
34        }
35
36        toggle.addEventListener('click', () => setOpen(!list.classList.contains('active')));
37
38        document.addEventListener('click', (event) => {
39            if (window.innerWidth > MOBILE) return;
40            if (!nav.contains(event.target)) setOpen(false);
41        });
42
43        document.addEventListener('keydown', (event) => {
44            if (event.key === 'Escape') setOpen(false);
45        });
46
47        list.querySelectorAll('.nav-link').forEach((link) => {
48            link.addEventListener('click', () => {
49                if (window.innerWidth <= MOBILE) setOpen(false);
50            });
51        });
52
53        window.addEventListener('resize', () => {
54            if (window.innerWidth > MOBILE) setOpen(false);
55        });
56    }
57
58    /* ---------- Scroll reveal ---------- */
59
60    function initReveal() {
61        const targets = document.querySelectorAll('.reveal, .reveal-group');
62        if (!targets.length) return;
63
64        if (!('IntersectionObserver' in window)) {
65            targets.forEach((el) => el.classList.add('visible'));
66            return;
67        }
68
69        const observer = new IntersectionObserver((entries) => {
70            entries.forEach((entry) => {
71                if (entry.isIntersecting) {
72                    entry.target.classList.add('visible');
73                    observer.unobserve(entry.target);
74                }
75            });
76        }, { threshold: 0.12, rootMargin: '0px 0px -48px 0px' });
77
78        targets.forEach((el) => observer.observe(el));
79    }
80
81    /* ---------- Count-up stats ---------- */
82
83    function animateCount(el) {
84        const target = parseFloat(el.dataset.count);
85        if (!Number.isFinite(target)) return;
86        const decimals = (el.dataset.count.split('.')[1] || '').length;
87        const duration = 1400;
88        const start = performance.now();
89
90        function frame(now) {
91            const progress = Math.min((now - start) / duration, 1);
92            const eased = 1 - Math.pow(1 - progress, 3);
93            el.textContent = (target * eased).toFixed(decimals);
94            if (progress < 1) requestAnimationFrame(frame);
95        }
96
97        requestAnimationFrame(frame);
98    }
99
100    function initCounters() {
101        const counters = document.querySelectorAll('[data-count]');
102        if (!counters.length) return;
103
104        const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
105        if (reduced || !('IntersectionObserver' in window)) {
106            counters.forEach((el) => { el.textContent = el.dataset.count; });
107            return;
108        }
109
110        const observer = new IntersectionObserver((entries) => {
111            entries.forEach((entry) => {
112                if (entry.isIntersecting) {
113                    animateCount(entry.target);
114                    observer.unobserve(entry.target);
115                }
116            });
117        }, { threshold: 0.4 });
118
119        counters.forEach((el) => observer.observe(el));
120    }
121
122    /* ---------- Tech marquee ---------- */
123
124    const TECH = [
125        'Shopify', 'Next.js', 'React', 'TypeScript', 'WordPress', 'Tailwind CSS',
126        'JavaScript', 'Node.js', 'Klaviyo', 'Bloomreach', 'HTML/CSS', 'Liquid', 'Drupal'
127    ];
128
129    function initMarquee() {
130        document.querySelectorAll('[data-tech-marquee]').forEach((container) => {
131            container.textContent = '';
132            const track = document.createElement('div');
133            track.className = 'marquee';
134            for (let i = 0; i < 2; i += 1) {
135                const group = document.createElement('div');
136                group.className = 'marquee-group';
137                if (i === 1) group.setAttribute('aria-hidden', 'true');
138                TECH.forEach((name) => {
139                    const item = document.createElement('span');
140                    item.className = 'marquee-item';
141                    item.textContent = name;
142                    group.appendChild(item);
143                });
144                track.appendChild(group);
145            }
146            container.appendChild(track);
147        });
148    }
149
150    /* ---------- Tech pills (static grid variant) ---------- */
151
152    function initTechPills() {
153        document.querySelectorAll('[data-tech-pills]').forEach((container) => {
154            container.textContent = '';
155            TECH.forEach((name) => {
156                const chip = document.createElement('span');
157                chip.className = 'pill';
158                chip.textContent = name;
159                container.appendChild(chip);
160            });
161        });
162    }
163
164    /* ---------- Boot ---------- */
165
166    function init() {
167        initNav();
168        initReveal();
169        initCounters();
170        initMarquee();
171        initTechPills();
172    }
173
174    if (document.querySelector('.navbar')) {
175        init();
176    } else {
177        document.addEventListener('shell:ready', init, { once: true });
178    }
179})();

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.