PageSourceSearch

https://www.canonwars.ai/assets/js/scroll-magic.js?v=BMYCnCgKJQ1xcfKC

js canonwars.ai collected 2026-10-05 15:29:05 UTC 4,231 bytes, 111 lines download raw bytes

1// Canon Wars — Scroll storytelling + custom cursor (v27)
2// No external deps. Vanilla JS, ~3KB minified.
3
4(function() {
5    'use strict';
6
7    // Bail if user prefers reduced motion
8    if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
9        document.documentElement.classList.add('cw-reduced-motion');
10        return;
11    }
12
13    // === Custom brass cursor ===
14    // Only enable on devices with fine pointer (mouse, not touch)
15    if (window.matchMedia('(pointer: fine)').matches && window.innerWidth >= 768) {
16        const cursor = document.createElement('div');
17        cursor.className = 'cw-cursor';
18        cursor.innerHTML = '<svg viewBox="0 0 32 32" width="32" height="32"><path fill="currentColor" d="M16 2l1.5 3 3-1 .5 3 3 1.5-1 3 2.5 2-2 2.5 1 3-3 1.5-.5 3-3-1L16 30l-1.5-3-3 1-.5-3-3-1.5 1-3-2.5-2 2.5-2.5-1-3 3-1.5.5-3 3 1zm0 8a6 6 0 100 12 6 6 0 000-12z"/></svg>';
19        document.body.appendChild(cursor);
20
21        let cursorX = 0, cursorY = 0;
22        let targetX = 0, targetY = 0;
23
24        document.addEventListener('mousemove', (e) => {
25            targetX = e.clientX;
26            targetY = e.clientY;
27        });
28
29        // Smooth follow
30        function animate() {
31            cursorX += (targetX - cursorX) * 0.18;
32            cursorY += (targetY - cursorY) * 0.18;
33            cursor.style.transform = `translate3d(${cursorX - 16}px, ${cursorY - 16}px, 0)`;
34            requestAnimationFrame(animate);
35        }
36        animate();
37
38        // Grow cursor over interactive elements
39        const interactive = 'a, button, [role="button"], input, textarea, [data-portal]';
40        document.addEventListener('mouseover', (e) => {
41            if (e.target.matches && e.target.matches(interactive) ||
42                e.target.closest && e.target.closest(interactive)) {
43                cursor.classList.add('cw-cursor--grow');
44            }
45        });
46        document.addEventListener('mouseout', (e) => {
47            cursor.classList.remove('cw-cursor--grow');
48        });
49
50        // Hide on document leave
51        document.addEventListener('mouseleave', () => cursor.style.opacity = '0');
52        document.addEventListener('mouseenter', () => cursor.style.opacity = '');
53    }
54
55    // === Scroll-triggered reveals ===
56    if ('IntersectionObserver' in window) {
57        const observer = new IntersectionObserver((entries) => {
58            entries.forEach((entry) => {
59                if (entry.isIntersecting) {
60                    entry.target.classList.add('cw-revealed');
61                    observer.unobserve(entry.target);
62                }
63            });
64        }, {
65            threshold: 0.1,
66            rootMargin: '0px 0px -80px 0px'
67        });
68
69        // Observe everything that should reveal
70        const reveals = document.querySelectorAll(
71            '.post-card, .cogs-log-card, .section-intro, .post-subscribe-cta, ' +
72            '.post-full-content > h2, .post-full-content > h3, ' +
73            '.archive-heading'
74        );
75        reveals.forEach((el) => {
76            el.classList.add('cw-reveal');
77            observer.observe(el);
78        });
79    }
80
81    // === Hero parallax on scroll (subtle, performant) ===
82    const heroCog = document.querySelector('.site-hero');
83    if (heroCog) {
84        let ticking = false;
85        window.addEventListener('scroll', () => {
86            if (!ticking) {
87                requestAnimationFrame(() => {
88                    const scrollY = window.scrollY;
89                    if (scrollY < 800) {
90                        heroCog.style.setProperty('--cw-scroll-y', `${scrollY * 0.5}px`);
91                    }
92                    ticking = false;
93                });
94                ticking = true;
95            }
96        });
97    }
98
99    // === Smooth anchor scroll ===
100    document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
101        anchor.addEventListener('click', (e) => {
102            const id = anchor.getAttribute('href');
103            if (id === '#' || id.length < 2) return;
104            const target = document.querySelector(id);
105            if (!target) return;
106            e.preventDefault();
107            target.scrollIntoView({ behavior: 'smooth', block: 'start' });
108        });
109    });
110
111})();

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.