PageSourceSearch

https://www.pimvanhelvoort.nl/rule_30_bg.js

js pimvanhelvoort.nl collected 2026-10-08 20:02:36 UTC 8,043 bytes, 266 lines download raw bytes

1// Check if mobile device
2const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
3    window.innerWidth < 768;
4
5// Configuration based on device type
6const config = isMobile ? {
7    size: 250,
8    cellSize: 8,
9    blockColor: '#404040',
10    fadeSpeed: 4,
11    delay: 60
12} : {
13    size: 300,
14    cellSize: 6,
15    blockColor: '#606060',
16    fadeSpeed: 3,
17    delay: 10
18};
19
20class Rule30Visualizer {
21    constructor(container, options = {}) {
22        this.container = container;
23        this.size = options.size || config.size;
24        this.cellSize = options.cellSize || config.cellSize;
25        this.blockColor = options.blockColor || config.blockColor;
26        this.delay = options.delay || config.delay;
27
28        this.width = (this.size * 2 + 1) * this.cellSize;
29        this.height = this.size * this.cellSize;
30        this.currentGeneration = 0;
31        this.isRunning = false;
32
33        this.initializeCanvas();
34        this.setupData();
35    }
36
37    initializeCanvas() {
38        // Remove existing canvas if any
39        d3.select(this.container).select('canvas').remove();
40
41        // Create canvas with D3
42        this.canvas = d3.select(this.container)
43            .append('canvas')
44            .attr('id', 'canvas')
45            .attr('width', this.width)
46            .attr('height', this.height)
47            .style('position', 'fixed')
48            .style('top', '0')
49            .style('transform', 'translateX(-50%)')
50            .style('z-index', '-1');
51
52        this.context = this.canvas.node().getContext('2d');
53
54        // Optimize canvas for performance
55        this.context.imageSmoothingEnabled = false;
56        this.context.globalCompositeOperation = 'source-over';
57    }
58
59    setupData() {
60        // Initialize the cellular automaton state
61        this.cells = new Array(this.size * 2 + 1).fill(0);
62        this.cells[Math.floor(this.cells.length / 2)] = 1;
63
64        // Pre-allocate arrays for better performance
65        this.nextCells = new Array(this.size * 2 + 1).fill(0);
66        this.imageData = this.context.createImageData(this.width, this.cellSize);
67    }
68
69    applyRule30(left, center, right) {
70        // Rule 30: left XOR (center OR right)
71        return left ^ (center | right);
72    }
73
74    computeNextGeneration() {
75        // Apply Rule 30 to compute next generation
76        for (let i = 0; i < this.cells.length; i++) {
77            const left = this.cells[i - 1] || 0;
78            const center = this.cells[i];
79            const right = this.cells[i + 1] || 0;
80            this.nextCells[i] = this.applyRule30(left, center, right);
81        }
82
83        // Swap arrays instead of copying for better performance
84        [this.cells, this.nextCells] = [this.nextCells, this.cells];
85    }
86
87    renderGeneration() {
88        const y = this.currentGeneration * this.cellSize;
89
90        // Use requestAnimationFrame for smoother rendering
91        if (y < this.height) {
92            // Create image data for the current row
93            const data = this.imageData.data;
94            let dataIndex = 0;
95
96            for (let x = 0; x < this.cells.length; x++) {
97                const color = this.cells[x] === 1 ?
98                    this.hexToRgb(this.blockColor) : { r: 0, g: 0, b: 0 };
99
100                // Fill cellSize pixels horizontally for each cell
101                for (let px = 0; px < this.cellSize; px++) {
102                    data[dataIndex] = color.r;     // Red
103                    data[dataIndex + 1] = color.g; // Green
104                    data[dataIndex + 2] = color.b; // Blue
105                    data[dataIndex + 3] = 255;     // Alpha
106                    dataIndex += 4;
107                }
108            }
109
110            // Draw the row using putImageData for better performance
111            for (let row = 0; row < this.cellSize; row++) {
112                this.context.putImageData(this.imageData, 0, y + row);
113
114            }
115        }
116    }
117
118    hexToRgb(hex) {
119        const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
120        return result ? {
121            r: parseInt(result[1], 16),
122            g: parseInt(result[2], 16),
123            b: parseInt(result[3], 16)
124        } : { r: 128, g: 128, b: 128 };
125    }
126
127    animate() {
128        if (!this.isRunning || this.currentGeneration >= this.size) {
129            return;
130        }
131
132        this.renderGeneration();
133        this.computeNextGeneration();
134        this.currentGeneration++;
135
136        // Use setTimeout with requestAnimationFrame for smooth animation
137        setTimeout(() => {
138            requestAnimationFrame(() => this.animate());
139        }, this.delay);
140    }
141
142    start() {
143        this.isRunning = true;
144        this.animate();
145    }
146
147    stop() {
148        this.isRunning = false;
149    }
150
151    reset() {
152        this.stop();
153        this.currentGeneration = 0;
154        this.context.clearRect(0, 0, this.width, this.height);
155        this.setupData();
156    }
157
158    resize() {
159        // Handle window resize
160        const newConfig = window.innerWidth < 768 ? {
161            size: 100,
162            cellSize: 6,
163            blockColor: '#404040',
164            delay: 60
165        } : {
166            size: 200,
167            cellSize: 4,
168            blockColor: '#606060',
169            delay: 30
170        };
171
172        if (newConfig.size !== this.size) {
173            this.size = newConfig.size;
174            this.cellSize = newConfig.cellSize;
175            this.blockColor = newConfig.blockColor;
176            this.delay = newConfig.delay;
177
178            this.width = (this.size * 2 + 1) * this.cellSize;
179            this.height = this.size * this.cellSize;
180
181            this.canvas
182                .attr('width', this.width)
183                .attr('height', this.height);
184
185            this.reset();
186            this.start();
187        }
188    }
189}
190
191// Initialize the visualizer
192const rule30Viz = new Rule30Visualizer('body', config);
193rule30Viz.start();
194
195// Handle scroll effects with optimized performance
196let ticking = false;
197
198function updateScrollEffects() {
199    const canvas = document.getElementById('canvas');
200    if (!canvas) return;
201
202    const scrollPosition = window.scrollY;
203    const opacity = Math.max(0, 1 - (scrollPosition / window.innerHeight) * config.fadeSpeed);
204    const topPosition = -(scrollPosition * 100 / window.innerHeight / 2) * 5;
205
206    canvas.style.opacity = opacity;
207    canvas.style.top = `${topPosition}%`;
208    // canvas.style.filter = `blur(${scrollPosition / 50}px)`;
209
210    // Handle scroll images
211    const scrollImages = [
212        { id: 'scrollImage1', speed: 15, offset: 0 },
213        { id: 'scrollImage2', speed: 25, offset: 30 }
214    ];
215
216    scrollImages.forEach(({ id, speed, offset }) => {
217        const img = document.getElementById(id);
218        if (!img) return;
219
220        const rect = img.getBoundingClientRect();
221        const scrollPercentage = ((window.innerHeight - rect.top) / window.innerHeight) * speed;
222        img.style.objectPosition = `50% ${scrollPercentage + offset}%`;
223    });
224
225    ticking = false;
226}
227
228function requestScrollUpdate() {
229    if (!ticking) {
230        requestAnimationFrame(updateScrollEffects);
231        ticking = true;
232    }
233}
234
235window.addEventListener('scroll', requestScrollUpdate, { passive: true });
236
237// Handle resize with debouncing
238let resizeTimeout;
239window.addEventListener('resize', () => {
240    clearTimeout(resizeTimeout);
241    resizeTimeout = setTimeout(() => {
242        rule30Viz.resize();
243    }, 250);
244});
245
246// Intersection Observer for fade-in effects
247const observer = new IntersectionObserver((entries) => {
248    entries.forEach((entry) => {
249        entry.target.classList.toggle('show', entry.isIntersecting);
250    });
251}, {
252    // rootMargin: "-210px",
253    // rootMargin: "-0px",
254    threshold: 0.1
255});
256
257// Observe hidden elements when DOM is ready
258document.addEventListener('DOMContentLoaded', () => {
259    const hiddenElements = document.querySelectorAll('.hidden');
260    hiddenElements.forEach((el) => observer.observe(el));
261});
262
263// Clean up on page unload
264window.addEventListener('beforeunload', () => {
265    rule30Viz.stop();
266});

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.