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.