1// Workshop Bento Box Animation - Exact copy from brainbodyfm-workshop 2class WorkshopBentoAnimation { 3 constructor() { 4 this.container = document.querySelector('.workshop-biosignal-background'); 5 if (!this.container) return; 6 7 this.rows = []; 8 this.animationId = null; 9 this.isRunning = false; 10 11 // Configuration - exact same as brainbodyfm-workshop 12 this.config = { 13 rowCount: 5, // Same as original 14 iconSpacing: 140, // Same as original 15 speed: 0.15, // Slow speed for bento box 16 iconSize: 100, // Same as original 17 rowHeight: 140, // Same as original 18 rowSpacing: 22, // Same as original 19 rotation: 8, // Same as original 20 icons: [ 21 'assets/modalities/eeg.png', 22 'assets/modalities/fmri.png', 23 'assets/modalities/emg.png', 24 'assets/modalities/meg.png', 25 'assets/modalities/ecg.png', 26 'assets/modalities/ieeg.png', 27 'assets/modalities/wearable.png', 28 'assets/modalities/calcium.png', 29 'assets/modalities/microarray.png', 30 'assets/modalities/eda.png', 31 'assets/modalities/eog.png', 32 'assets/modalities/ppg.png', 33 'assets/modalities/psg.png', 34 'assets/modalities/seeg.png', 35 'assets/modalities/monkey.png', 36 'assets/modalities/c-elegans.png', 37 ] 38 }; 39 40 // Create shuffled icon sequences for each row 41 this.iconSequences = []; 42 this.init(); 43 } 44 45 // Helper function to shuffle array 46 shuffleArray(array) { 47 const shuffled = [...array]; 48 for (let i = shuffled.length - 1; i > 0; i--) { 49 const j = Math.floor(Math.random() * (i + 1)); 50 [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; 51 } 52 return shuffled; 53 } 54 55 // Create a randomized but distributed sequence of icons 56 createIconSequence(length) { 57 const sequence = []; 58 const iconCount = this.config.icons.length; 59 60 // Create multiple shuffled copies of the icon array 61 const copies = Math.ceil(length / iconCount); 62 for (let i = 0; i < copies; i++) { 63 sequence.push(...this.shuffleArray(this.config.icons)); 64 } 65 66 // Trim to exact length needed 67 return sequence.slice(0, length); 68 } 69 70 init() { 71 // Clear existing content 72 this.container.innerHTML = ''; 73 74 // Create rows 75 for (let i = 0; i < this.config.rowCount; i++) { 76 this.createRow(i); 77 } 78 79 this.start(); 80 } 81 82 createRow(index) { 83 const row = document.createElement('div'); 84 row.className = 'workshop-biosignal-row-js'; 85 row.style.cssText = ` 86 position: absolute; 87 width: 100%; 88 height: ${this.config.rowHeight}px; 89 display: flex; 90 align-items: center; 91 top: ${-25 + (index * this.config.rowSpacing)}%; 92 transform: rotate(${this.config.rotation}deg); 93 pointer-events: none; 94 `; 95 96 // Calculate how many icons we need to fill the screen plus buffer 97 const screenWidth = window.innerWidth; 98 const screenHeight = window.innerHeight; 99 const diagonal = Math.sqrt(screenWidth * screenWidth + screenHeight * screenHeight); 100 const rotationBuffer = diagonal * 0.6; 101 const totalWidth = diagonal + rotationBuffer; 102 const iconsNeeded = Math.ceil(totalWidth / this.config.iconSpacing) + 10; 103 104 // Create a randomized icon sequence for this row 105 const iconSequence = this.createIconSequence(iconsNeeded); 106 this.iconSequences[index] = iconSequence; 107 108 // Create row data with consistent speed 109 const rowData = { 110 element: row, 111 icons: [], 112 speed: this.config.speed, 113 offset: (index % 2) * (this.config.iconSpacing / 2), 114 iconSequence: iconSequence 115 }; 116 117 // Create icons for this row using the randomized sequence 118 for (let i = 0; i < iconsNeeded; i++) { 119 const iconData = this.createIcon(i, rowData.offset, iconSequence[i]); 120 row.appendChild(iconData.element); 121 rowData.icons.push(iconData); 122 } 123 124 this.container.appendChild(row); 125 this.rows.push(rowData); 126 } 127 128 createIcon(index, baseOffset, iconSrc) { 129 const img = document.createElement('img'); 130 131 img.src = iconSrc; 132 img.className = 'workshop-biosignal-icon-js'; 133 img.alt = ''; 134 img.style.cssText = `
135 width: ${this.config.iconSize}px; 136 height: ${this.config.iconSize}px; 137 object-fit: contain; 138 opacity: 0.9; 139 flex-shrink: 0; 140 position: absolute; 141 animation: none; 142 `; 143 144 // Start icons off-screen to the left with proper spacing 145 const startX = -this.config.iconSpacing * 4 + baseOffset + (index * this.config.iconSpacing); 146 147 return { 148 element: img, 149 currentX: startX, 150 originalIndex: index, 151 floatOffset: Math.random() * Math.PI * 2, 152 floatSpeed: 0.015 + Math.random() * 0.005 153 }; 154 } 155 156 updatePositions() { 157 const time = Date.now() * 0.001; 158 const screenWidth = window.innerWidth; 159 const screenHeight = window.innerHeight; 160 161 const diagonal = Math.sqrt(screenWidth * screenWidth + screenHeight * screenHeight); 162 const resetPoint = diagonal * 1.0; 163 const startPoint = -this.config.iconSpacing * 5; 164 165 this.rows.forEach((row, rowIndex) => { 166 row.icons.forEach((icon, iconIndex) => { 167 // Update horizontal position with consistent speed 168 icon.currentX += row.speed; 169 170 // Reset position when icon goes far enough off screen 171 if (icon.currentX > resetPoint) { 172 const leftmostX = Math.min(...row.icons.map(i => i.currentX)); 173 icon.currentX = leftmostX - this.config.iconSpacing; 174 175 // When resetting, assign a new random icon from the sequence 176 const newIconIndex = (iconIndex + row.icons.length) % row.iconSequence.length; 177 icon.element.src = row.iconSequence[newIconIndex]; 178 } 179 180 // Calculate subtle floating animation 181 const floatY = Math.sin(time * icon.floatSpeed + icon.floatOffset) * 6; 182 const floatScale = 1 + Math.sin(time * icon.floatSpeed + icon.floatOffset) * 0.03; 183 184 // Apply transform 185 icon.element.style.transform = ` 186 translateX(${icon.currentX}px) 187 translateY(${floatY}px) 188 scale(${floatScale}) 189 `; 190 }); 191 }); 192 } 193 194 start() { 195 if (this.isRunning) return; 196 this.isRunning = true; 197 198 const animate = () => { 199 if (!this.isRunning) return; 200 201 this.updatePositions(); 202 this.animationId = requestAnimationFrame(animate); 203 }; 204 205 animate(); 206 } 207 208 stop() { 209 this.isRunning = false; 210 if (this.animationId) { 211 cancelAnimationFrame(this.animationId); 212 this.animationId = null; 213 } 214 } 215 216 handleResize() { 217 const screenWidth = window.innerWidth; 218 const screenHeight = window.innerHeight; 219 const diagonal = Math.sqrt(screenWidth * screenWidth + screenHeight * screenHeight); 220 const rotationBuffer = diagonal * 0.6; 221 const totalWidth = diagonal + rotationBuffer; 222 const iconsNeeded = Math.ceil(totalWidth / this.config.iconSpacing) + 10; 223 224 this.rows.forEach((row, rowIndex) => { 225 // Add more icons if we don't have enough for the new screen size 226 while (row.icons.length < iconsNeeded) { 227 // Extend the icon sequence if needed 228 if (row.iconSequence.length <= row.icons.length) { 229 const additionalIcons = this.createIconSequence(iconsNeeded - row.iconSequence.length); 230 row.iconSequence.push(...additionalIcons); 231 } 232 233 const iconSrc = row.iconSequence[row.icons.length]; 234 const iconData = this.createIcon(row.icons.length, row.offset, iconSrc); 235 row.element.appendChild(iconData.element); 236 row.icons.push(iconData); 237 } 238 }); 239 } 240} 241 242// Initialize when DOM is loaded 243document.addEventListener('DOMContentLoaded', () => { 244 const workshopBentoAnimation = new WorkshopBentoAnimation(); 245 246 // Handle window resize 247 let resizeTimeout; 248 window.addEventListener('resize', () => { 249 clearTimeout(resizeTimeout); 250 resizeTimeout = setTimeout(() => { 251 workshopBentoAnimation.handleResize(); 252 }, 250); 253 }); 254 255 // Pause animation when page is not visible (performance optimization) 256 document.addEventListener('visibilitychange', () => { 257 if (document.hidden) { 258 workshopBentoAnimation.stop(); 259 } else { 260 workshopBentoAnimation.start(); 261 } 262 }); 263});
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.