1/** 2 * Two.js Custom Cursor System 3 * Main manager for all Two.js-based cursor implementations 4 */ 5 6(function() { 7 'use strict'; 8 9 function emitDevLog() { 10 if (typeof window !== 'undefined' && typeof window.devLog === 'function') { 11 window.devLog.apply(window, arguments); 12 } 13 } 14 function emitDevWarn() { 15 if (typeof window !== 'undefined' && typeof window.devWarn === 'function') { 16 window.devWarn.apply(window, arguments); 17 } 18 } 19 20 let currentCursor = null; 21 let currentCursorType = 'none'; 22 let twoInstance = null; 23 let cursorContainer = null; 24 let isInitialized = false; 25 let lastFrameTime = 0; 26 let deltaTime = 0; 27 28 // Cursor type definitions 29 const CURSOR_TYPES = { 30 none: null, 31 'twojs-geometric': 'animatedGeometricCursor', 32 'twojs-particles': 'interactiveParticleCursor', 33 'twojs-morphing': 'morphingShapeCursor', 34 'twojs-physics': 'physicsBasedCursor', 35 'twojs-reactive': 'reactiveCursor', 36 'twojs-recursive': 'recursiveCursor' 37 }; 38 39 /** 40 * Initialize the Two.js cursor system 41 */ 42 function init() { 43 if (isInitialized) return; 44 45 // Wait for Two.js to be available 46 if (typeof Two === 'undefined') { 47 emitDevWarn('Two.js not loaded yet, waiting...'); 48 setTimeout(init, 100); 49 return; 50 } 51 52 // Create cursor container 53 cursorContainer = document.createElement('div'); 54 cursorContainer.id = 'twojs-cursor-container'; 55 cursorContainer.style.cssText = ` 56 position: fixed; 57 top: 0; 58 left: 0; 59 width: 100%; 60 height: 100%; 61 pointer-events: none !important; 62 z-index: 101600; 63 cursor: none; 64 `; 65 // Ensure all children also have pointer-events: none 66 cursorContainer.setAttribute('data-pointer-events', 'none'); 67 document.body.appendChild(cursorContainer); 68 69 // Add CSS to ensure all children inherit pointer-events: none 70 const containerStyle = document.createElement('style'); 71 containerStyle.id = 'twojs-cursor-container-style'; 72 containerStyle.textContent = ` 73 #twojs-cursor-container, 74 #twojs-cursor-container *, 75 #twojs-cursor-container svg, 76 #twojs-cursor-container canvas, 77 #twojs-cursor-container > svg, 78 #twojs-cursor-container > div { 79 pointer-events: none !important; 80 touch-action: none !important; 81 } 82 `; 83 document.head.appendChild(containerStyle); 84 85 // Hide default cursor when Two.js cursor is active 86 const style = document.createElement('style'); 87 style.id = 'twojs-cursor-style'; 88 style.textContent = ` 89 body.twojs-cursor-active * { 90 cursor: none !important; 91 } 92 `; 93 document.head.appendChild(style); 94 95 isInitialized = true; 96 emitDevLog('⨠Two.js cursor system initialized'); 97 } 98 99 const TWO_JS_SRC = 'https://cdn.jsdelivr.net/npm/[email protected]/build/two.min.js'; 100 let twoJsLoadPromise = null; 101 102 function ensureTwoJs() { 103 if (typeof Two !== 'undefined') { 104 return Promise.resolve(); 105 } 106 if (twoJsLoadPromise) return twoJsLoadPromise; 107 twoJsLoadPromise = new Promise(function (resolve) { 108 var existing = document.querySelector('script[src="' + TWO_JS_SRC + '"]'); 109 if (existing) { 110 existing.addEventListener('load', function () { resolve(); }, { once: true }); 111 existing.addEventListener('error', function () { resolve(); }, { once: true }); 112 setTimeout(resolve, 3000); 113 return; 114 } 115 var script = document.createElement('script'); 116 script.src = TWO_JS_SRC; 117 script.async = false; 118 script.onload = function () { resolve(); }; 119 script.onerror = function () { 120 emitDevWarn('Two.js failed to load â physics/geometric cursors unavailable.'); 121 resolve(); 122 }; 123 document.head.appendChild(script); 124 }); 125 return twoJsLoadPromise; 126 } 127 128 /** 129 * Load a cursor module dynamically 130 */ 131 function loadCursorModule(cursorName) { 132 return new Promise((resolve, reject) => { 133 const script = document.createElement('script'); 134 script.src = `/scripts/dependencies/cursors/${cursorName}.js`; 135 script.onload = () => resolve(); 136 script.onerror = () => reject(new Error(`Failed to load ${cursorName}`)); 137 document.head.appendChild(script); 138 }); 139 } 140 141 /** 142 * Set the active cursor type 143 */ 144 async function setCursor(cursorType) { 145 // Clean up current cursor 146 if (currentCursor && typeof currentCursor.destroy === 'function') { 147 currentCursor.destroy(); 148 currentCursor = null; 149 } 150 151 if (twoInstance) { 152 twoInstance.pause(); 153 // Unbind update handler 154 twoInstance.unbind('update'); 155 // Clean up resize handler 156 if (twoInstance._resizeHandler) { 157 window.removeEventListener('resize', twoInstance._resizeHandler); 158 } 159 // Clean up observer 160 if (twoInstance._pointerEventsObserver) { 161 twoInstance._pointerEventsObserver.disconnect(); 162 } 163 // Ensure renderer is cleaned up properly 164 if (twoInstance.renderer && twoInstance.renderer.domElement) { 165 twoInstance.renderer.domElement.style.pointerEvents = 'none'; 166 } 167 if (cursorContainer) { 168 cursorContainer.innerHTML = ''; 169 } 170 twoInstance = null; 171 } 172 173 // Remove body class 174 document.body.classList.remove('twojs-cursor-active'); 175 176 currentCursorType = cursorType; 177 178 // If none, we're done 179 if (cursorType === 'none' || !cursorType) { 180 return; 181 } 182 183 // Initialize if needed 184 if (!isInitialized) { 185 init(); 186 // Wait a bit for initialization 187 await new Promise(resolve => setTimeout(resolve, 50)); 188 } 189 190 // Get cursor module name 191 const moduleName = CURSOR_TYPES[cursorType]; 192 if (!moduleName) { 193 emitDevWarn(`Unknown cursor type: ${cursorType}`); 194 return; 195 } 196 197 try { 198 await ensureTwoJs(); 199 if (typeof Two === 'undefined') { 200 emitDevWarn('Two.js is not available â cannot use cursor: ' + cursorType); 201 return; 202 } 203 204 // Load the cursor module if it exists 205 if (typeof window[moduleName] === 'undefined') { 206 await loadCursorModule(moduleName); 207 } 208 209 // Create Two.js instance - use explicit dimensions instead of fullscreen 210 const params = { 211 width: window.innerWidth, 212 height: window.innerHeight, 213 autostart: true, 214 type: Two.Types.svg 215 }; 216 twoInstance = new Two(params).appendTo(cursorContainer); 217 218 // Immediately set pointer-events on the renderer's DOM element 219 if (twoInstance.renderer && twoInstance.renderer.domElement) { 220 const domElement = twoInstance.renderer.domElement; 221 domElement.style.pointerEvents = 'none'; 222 domElement.style.setProperty('pointer-events', 'n
222one', 'important'); 223 224 // Also set on the element itself via setAttribute for maximum compatibility 225 domElement.setAttribute('style', domElement.getAttribute('style') + '; pointer-events: none !important;'); 226 227 // Use MutationObserver to ensure it stays set 228 const observer = new MutationObserver(() => { 229 if (domElement.style.pointerEvents !== 'none') { 230 domElement.style.pointerEvents = 'none'; 231 domElement.style.setProperty('pointer-events', 'none', 'important'); 232 } 233 }); 234 observer.observe(domElement, { attributes: true, attributeFilter: ['style'] }); 235 236 // Store observer for cleanup 237 twoInstance._pointerEventsObserver = observer; 238 } 239 240 // Handle window resize 241 const resizeHandler = () => { 242 if (twoInstance) { 243 twoInstance.width = window.innerWidth; 244 twoInstance.height = window.innerHeight; 245 } 246 }; 247 window.addEventListener('resize', resizeHandler); 248 twoInstance._resizeHandler = resizeHandler; 249 250 // Create cursor instance 251 // Try different naming conventions 252 let CursorClass = window[moduleName]; 253 if (!CursorClass) { 254 // Try with first letter capitalized 255 const capitalized = moduleName.charAt(0).toUpperCase() + moduleName.slice(1); 256 CursorClass = window[capitalized]; 257 } 258 259 if (CursorClass && typeof CursorClass === 'function') { 260 currentCursor = new CursorClass(twoInstance); 261 // Some cursors (e.g. recursive) keep the real OS pointer as the lead 262 if (!currentCursor.keepNativeCursor) { 263 document.body.classList.add('twojs-cursor-active'); 264 } 265 266 // Initialize time tracking 267 lastFrameTime = performance.now(); 268 269 // Hook into Two.js update cycle to calculate deltaTime 270 twoInstance.bind('update', function() { 271 const currentTime = performance.now(); 272 deltaTime = (currentTime - lastFrameTime) / 1000; // Convert to seconds 273 lastFrameTime = currentTime; 274 275 // Clamp deltaTime to prevent large jumps (e.g., when tab becomes visible) 276 deltaTime = Math.min(deltaTime, 0.1); // Max 100ms 277 278 // Call cursor update with deltaTime 279 if (currentCursor && typeof currentCursor.update === 'function') { 280 currentCursor.update(deltaTime); 281 } 282 }); 283 284 emitDevLog(`ð¯ Two.js cursor set to: ${cursorType}`); 285 } else { 286 console.error(`Cursor class ${moduleName} not found. Available:`, Object.keys(window).filter(k => k.includes('Cursor'))); 287 } 288 } catch (error) { 289 console.error(`Failed to load cursor ${cursorType}:`, error); 290 } 291 } 292 293 /** 294 * Get current cursor type 295 */ 296 function getCursor() { 297 return currentCursorType; 298 } 299 300 /** 301 * Cleanup 302 */ 303 function cleanup() { 304 if (currentCursor && typeof currentCursor.destroy === 'function') { 305 currentCursor.destroy(); 306 } 307 if (twoInstance) { 308 twoInstance.pause(); 309 // Clean up resize handler 310 if (twoInstance._resizeHandler) { 311 window.removeEventListener('resize', twoInstance._resizeHandler); 312 } 313 // Clean up observer 314 if (twoInstance._pointerEventsObserver) { 315 twoInstance._pointerEventsObserver.disconnect(); 316 } 317 if (twoInstance.renderer && twoInstance.renderer.domElement) { 318 twoInstance.renderer.domElement.style.pointerEvents = 'none'; 319 } 320 } 321 if (cursorContainer && cursorContainer.parentNode) { 322 cursorContainer.parentNode.removeChild(cursorContainer); 323 }
324 // Remove container style 325 const containerStyle = document.getElementById('twojs-cursor-container-style'); 326 if (containerStyle) { 327 containerStyle.remove(); 328 } 329 document.body.classList.remove('twojs-cursor-active'); 330 isInitialized = false; 331 } 332 333 // Export API 334 window.TwoJSCursor = { 335 init, 336 setCursor, 337 getCursor, 338 cleanup, 339 CURSOR_TYPES 340 }; 341 342 // Auto-initialize when DOM is ready 343 if (document.readyState === 'loading') { 344 document.addEventListener('DOMContentLoaded', init); 345 } else { 346 init(); 347 } 348 349})(); 350
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.