1/** 2 * Custom Cursor Trail Effects 3 * Themed particle trails: binary, chess, ethereum 4 * Native cursor stays visible. Trails are purely decorative. 5 */ 6 7(function() { 8 'use strict'; 9 10 var isTouchDevice = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0); 11 if (isTouchDevice) return; 12 13 var prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; 14 if (prefersReducedMotion) return; 15 16 var trailTimer; 17 18 // Build an Ethereum diamond SVG element (the actual logo shape) 19 function createEthSVG(color) { 20 var ns = 'http://www.w3.org/2000/svg'; 21 var svg = document.createElementNS(ns, 'svg'); 22 svg.setAttribute('viewBox', '0 0 16 26'); 23 svg.setAttribute('width', '14'); 24 svg.setAttribute('height', '22'); 25 26 var paths = [ 27 { d: 'M8 0L0 13.25L8 10.1L16 13.25L8 0Z', opacity: '0.8' }, 28 { d: 'M8 10.1L0 13.25L8 17.65L16 13.25L8 10.1Z', opacity: '0.6' }, 29 { d: 'M0 14.75L8 26L16 14.75L8 19.15L0 14.75Z', opacity: '0.8' } 30 ]; 31 32 paths.forEach(function(p) { 33 var path = document.createElementNS(ns, 'path'); 34 path.setAttribute('d', p.d); 35 path.setAttribute('fill', color); 36 path.setAttribute('opacity', p.opacity); 37 svg.appendChild(path); 38 }); 39 40 return svg; 41 } 42 43 // Build a small ETH icon for the picker button 44 function createEthIcon() { 45 var ns = 'http://www.w3.org/2000/svg'; 46 var svg = document.createElementNS(ns, 'svg'); 47 svg.setAttribute('viewBox', '0 0 16 26'); 48 svg.setAttribute('width', '12'); 49 svg.setAttribute('height', '19'); 50 51 var paths = [ 52 { d: 'M8 0L0 13.25L8 10.1L16 13.25L8 0Z', opacity: '0.8' }, 53 { d: 'M8 10.1L0 13.25L8 17.65L16 13.25L8 10.1Z', opacity: '0.6' }, 54 { d: 'M0 14.75L8 26L16 14.75L8 19.15L0 14.75Z', opacity: '0.8' } 55 ]; 56 57 paths.forEach(function(p) { 58 var path = document.createElementNS(ns, 'path'); 59 path.setAttribute('d', p.d); 60 path.setAttribute('fill', 'currentColor'); 61 path.setAttribute('opacity', p.opacity); 62 svg.appendChild(path); 63 }); 64 65 return svg; 66 } 67 68 // Trail themes 69 var themes = { 70 binary: { 71 type: 'text', 72 chars: ['0', '1'], 73 label: '01', 74 color: '#2563eb', 75 size: 'normal' 76 }, 77 chess: { 78 type: 'text', 79 chars: ['\u2659', '\u2658', '\u2657', '\u2656', '\u2655', '\u2654'], 80 label: '\u265F', 81 color: '#2563eb', 82 size: 'large' 83 }, 84 ethereum: { 85 type: 'svg', 86 colors: ['#627EEA', '#8C9EEF', '#4A6CF7'] 87 } 88 }; 89 90 var themeKeys = ['binary', 'chess', 'ethereum']; 91 var currentTheme = localStorage.getItem('cursorTheme') || 'binary'; 92 93 function createTrailParticle(x, y) { 94 if (trailTimer) return; 95 96 trailTimer = setTimeout(function() { 97 var theme = themes[currentTheme]; 98 99 if (theme.type === 'svg') { 100 var colors = theme.colors; 101 var color = colors[Math.floor(Math.random() * colors.length)]; 102 var wrapper = document.createElement('div'); 103 wrapper.className = 'mouse-trail-svg'; 104 wrapper.appendChild(createEthSVG(color)); 105 wrapper.style.left = x + 'px'; 106 wrapper.style.top = y + 'px'; 107 document.body.appendChild(wrapper); 108 109 setTimeout(function() { wrapper.remove(); }, 1000); 110 } else { 111 var chars = theme.chars; 112 var char = chars[Math.floor(Math.random() * chars.length)]; 113 var trail = document.createElement('span'); 114 trail.className = 'mouse-trail' + (theme.size === 'large' ? ' mouse-trail--large' : ''); 115 trail.textContent = char; 116 trail.style.left = x + 'px'; 117 trail.style.top = y + 'px'; 118 trail.style.color = theme.color; 119 document.body.appendChild(trail); 120 121 setTimeout(function() { trail.remove(); }, 1000); 122 } 123 124 trailTimer = null; 125 }, 40); 126 } 127 128 function initThemePicker() { 129 var picker = document.createElement('div'); 130 picker.className = 'cursor-theme-picker'; 131 132 themeKeys.forEach(function(key) { 133 var btn = document.createElement('button'); 134 btn.className = 'cursor-theme-btn'; 135 btn.setAttribute('data-theme', key);
136 btn.title = key.charAt(0).toUpperCase() + key.slice(1) + ' trail'; 137 138 if (key === 'ethereum') { 139 btn.appendChild(createEthIcon()); 140 } else { 141 btn.textContent = themes[key].label; 142 } 143 144 if (key === currentTheme) btn.classList.add('active'); 145 146 btn.addEventListener('click', function(e) { 147 e.stopPropagation(); 148 currentTheme = key; 149 localStorage.setItem('cursorTheme', key); 150 var allBtns = picker.querySelectorAll('.cursor-theme-btn'); 151 for (var i = 0; i < allBtns.length; i++) { 152 allBtns[i].classList.remove('active'); 153 } 154 btn.classList.add('active'); 155 }); 156 157 picker.appendChild(btn); 158 }); 159 160 document.body.appendChild(picker); 161 } 162 163 function init() { 164 document.addEventListener('mousemove', function(e) { 165 createTrailParticle(e.clientX, e.clientY); 166 }); 167 initThemePicker(); 168 } 169 170 if (document.readyState === 'loading') { 171 document.addEventListener('DOMContentLoaded', init); 172 } else { 173 init(); 174 } 175 176})();
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.