1/** 2 * lightning.js v3 â Realistic GSAP + SVG lightning 3 * 4 * Improvements over v2: 5 * - Two-layer rendering: wide diffuse glow + sharp bright core per bolt 6 * - Tree branching: 3â6 branches per strike (not just one fork) 7 * - Multiple return strokes: rapid flicker before long afterglow (real lightning physics) 8 * - More subdivisions + larger displacement = jaggier, more naturalistic paths 9 * - Color: white core (#fff) over cyan-blue glow â matches real cloud-to-ground lightning 10 * - Branches taper: thinner, shorter, dimmer than trunk 11 * 12 * Preserves: 13 * - Easter egg / Keys of the Caribbean (fires at strike 15) 14 * - IntersectionObserver â only animates when hero is in view 15 * - API surface expected by main.js 16 */ 17 18// âââ Configuration ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 19const STRIKE_MIN_DELAY = 4; // seconds min between strikes 20const STRIKE_MAX_DELAY = 11; // seconds max between strikes 21const TRUNK_SUBDIVISIONS = 5; // midpoint passes for main bolt (more = jaggier) 22const BRANCH_SUBDIVISIONS = 4; // midpoint passes for branches 23const TRUNK_DISP = 110; // initial displacement for trunk (px) 24const BRANCH_DISP = 75; // initial displacement for branches (px) 25const MIN_BRANCHES = 2; // min branch bolts per strike 26const MAX_BRANCHES = 5; // max branch bolts per strike 27 28// âââ Easter egg state (preserved) ââââââââââââââââââââââââââââââââââââââââââââ 29let lightningStrikeCount = 0; 30let rabbitHoleRevealed = false; 31 32// âââ DOM setup ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 33const heroSection = document.getElementById('pg-hero'); 34const cvsStorm = document.getElementById('lightning-storm'); 35if (cvsStorm) cvsStorm.style.display = 'none'; 36 37// main.js compatibility stubs 38const stormInterval = 4500; 39const animate = () => {}; 40function startLightning() { startStorm(); } 41const observer = new IntersectionObserver(() => {}, { threshold: 0.05 }); 42 43// âââ SVG setup ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 44if (!heroSection) { 45 console.warn('[lightning] #pg-hero not found â aborting.'); 46} else { 47 const svgNS = 'http://www.w3.org/2000/svg'; 48 49 const svg = document.createElementNS(svgNS, 'svg'); 50 svg.setAttribute('id', 'lightning-svg'); 51 svg.setAttribute('aria-hidden', 'true'); 52 Object.assign(svg.style, { 53 position: 'absolute', 54 top: '0', 55 left: '0', 56 width: '100%', 57 height: '100%', 58 pointerEvents: 'none', 59 zIndex: '1', 60 overflow: 'visible', 61 }); 62 63 // Two filters: 64 // bolt-outer â wide, soft diffuse corona (5px + 12px blur) 65 // bolt-inner â tight, bright core glow (1.5px blur only) 66 const defs = document.createElementNS(svgNS, 'defs'); 67 defs.innerHTML = ` 68 <filter id="bolt-outer" x="-120%" y="-20%" width="340%" height="140%" 69 color-interpolation-filters="sRGB"> 70 <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="b1"/> 71 <feGaussianBlur in="SourceGraphic" stdDeviation="12" result="b2"/> 72 <feMerge> 73 <feMergeNode in="b2"/> 74 <feMergeNode in="b1"/> 75 </feMerge> 76 </filter> 77 <filter id="bolt-inner" x="-60%" y="-10%" width="220%" height="120%" 78 color-interpolation-filters="sRGB"> 79 <feGaussianBlur in="SourceGraphic" stdDeviation="1.5" result="b"/> 80 <feMerge> 81 <feMergeNode in="b"/> 82 <feMergeNode in="SourceGraphic"/> 83 </feMerge> 84 </filter> 85 `; 86 svg.appendChild(defs); 87 heroSection.appendChild(svg); 88 89 // âââ Midpoint displacement path generation ââââââââââââââââââââââââââââââââ 90 function generatePoints(x0, y0, x1, y1, passes, disp) { 91 let pts = [{ x: x0, y: y0 }, { x: x1, y: y1 }]; 92 let d = disp; 93 94 for (let p = 0; p < passes; p++) { 95 const next = []; 96 for (let i = 0; i < pts.length - 1; i++) { 97 const a = pts[i], b = pts[i + 1];
98 // Slight downward bias on midpoint y keeps bolt moving earthward 99 const my = (a.y + b.y) / 2 + (Math.random() - 0.48) * d * 0.18; 100 const mx = (a.x + b.x) / 2 + (Math.random() - 0.5) * d; 101 next.push(a, { x: mx, y: my }); 102 } 103 next.push(pts[pts.length - 1]); 104 pts = next; 105 d *= 0.55; // decay displacement each pass 106 } 107 return pts; 108 } 109 110 function toD(pts) { 111 return pts.map((p, i) => `${i === 0 ? 'M' : 'L'}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' '); 112 } 113 114 // Two-layer bolt: outer glow path + inner core path 115 function makeBolt(d, glowWidth, coreWidth, glowColor, coreColor) { 116 const group = document.createElementNS(svgNS, 'g'); 117 group.style.opacity = '0'; 118 119 const glow = document.createElementNS(svgNS, 'path'); 120 glow.setAttribute('d', d); 121 glow.setAttribute('stroke', glowColor); 122 glow.setAttribute('stroke-width', glowWidth); 123 glow.setAttribute('stroke-linecap','round'); 124 glow.setAttribute('stroke-linejoin','round'); 125 glow.setAttribute('fill', 'none'); 126 glow.setAttribute('filter', 'url(#bolt-outer)'); 127 128 const core = document.createElementNS(svgNS, 'path'); 129 core.setAttribute('d', d); 130 core.setAttribute('stroke', coreColor); 131 core.setAttribute('stroke-width', coreWidth); 132 core.setAttribute('stroke-linecap','round'); 133 core.setAttribute('stroke-linejoin','round'); 134 core.setAttribute('fill', 'none'); 135 core.setAttribute('filter', 'url(#bolt-inner)'); 136 137 group.appendChild(glow); 138 group.appendChild(core); 139 return group; 140 } 141 142 // âââ Strike âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 143 function strike() { 144 const w = heroSection.offsetWidth; 145 const h = heroSection.offsetHeight; 146 147 // Trunk: random top-x â random bottom-x, full height 148 const topX = w * 0.05 + Math.random() * w * 0.9; 149 const botX = w * 0.05 + Math.random() * w * 0.9; 150 const trunk = generatePoints(topX, 0, botX, h, TRUNK_SUBDIVISIONS, TRUNK_DISP); 151 const trunkEl = makeBolt( 152 toD(trunk), 153 '5', '1.2', // glow 5px wide, core 1.2px 154 'rgba(120, 180, 255, 0.55)', // blue glow 155 'rgba(240, 250, 255, 1)' // white-blue core 156 ); 157 svg.appendChild(trunkEl); 158 159 // Branches: pick random attachment points along trunk 160 const numBranches = MIN_BRANCHES + Math.floor(Math.random() * (MAX_BRANCHES - MIN_BRANCHES + 1)); 161 const branchEls = []; 162 163 for (let b = 0; b < numBranches; b++) { 164 // Attach in the lower 70% of the trunk (avoid tiny top stubs) 165 const startIdx = Math.floor(trunk.length * (0.15 + Math.random() * 0.65)); 166 const origin = trunk[startIdx]; 167 168 // Branch end: spread horizontally, terminate before floor 169 const endX = Math.max(5, Math.min(w - 5, origin.x + (Math.random() - 0.5) * w * 0.5)); 170 const endY = origin.y + (h - origin.y) * (0.3 + Math.random() * 0.6); 171 172 const bPts = generatePoints(origin.x, origin.y, endX, endY, BRANCH_SUBDIVISIONS, BRANCH_DISP); 173 174 // Branches get thinner & dimmer than trunk; deep branches dimmer still 175 const depthFade = 0.5 + 0.5 * (1 - startIdx / trunk.length); 176 const bEl = makeBolt( 177 toD(bPts), 178 `${(3 * depthFade).toFixed(1)}`, 179 `${(0.8 * depthFade).toFixed(1)}`, 180 `rgba(100, 160, 255, ${(0.35 * depthFade).toFixed(2)})`, 181 `rgba(220, 240, 255, ${(0.85 * depthFade).toFixed(2)})` 182 ); 183 svg.appendChild(bEl); 184 branchEls.push({ el: bEl, depth: depthFade }); 185 } 186 187 // ââ Return strokes (realistic flicker) ââ 188 // Real lightning: step leader (dim) â 2â3 rapid return strokes â afterglow 189 const returnStrokes = 1 + Math.floor(Math.random() * 2); // 2 or 3 total flashes 190 const tl = gsap.timeline({ 191 onComplete: () => { 192 trunkEl.remove(); 193 branchEls.forEach(b => b.el.remove()); 194 } 195 }); 196 197 // Build flicker sequence 198 let t = 0; 199 for (let s = 0; s < returnStrokes; s++) { 200 const peak = s === 0 ? 1 : 0.6 + Math.random() * 0.3; // first stroke brightest 201 tl.to([trunkEl, ...branchEls.map(b => b.el)], { 202 opacity: peak, duration: 0.025, ease: 'none' 203 }, t); 204 t += 0.025; 205 tl.to([trunkEl, ...branchEls.map(b => b.el)], { 206 opacity: peak * 0.25, duration: 0.04, ease: 'none' 207 }, t); 208 t += 0.04; 209 } 210 211 // Final afterglow fade â long, smooth power curve 212 tl.to([trunkEl, ...branchEls.map(b => b.el)], { 213 opacity: 1, duration: 0.02, ease: 'none' 214 }, t); 215 tl.to([trunkEl, ...branchEls.map(b => b.el)], { 216 opacity: 0, duration: 1.1, ease: 'power3.out' 217 }, t + 0.02); 218 219 // ââ Easter egg ââ 220 lightningStrikeCount++; 221 const logMessages = [ 222 'you are not alone', 'there is no spoon', 223 'wake up, sons and daughters', 'the cake is a lie', 224 'the answer is 42', 'dont panic', 225 'are you lost?', 'you are not lost', 226 'we are all Satoshi', 'the revolution is decentralized',
227 'who the cap fit', 'we gotta chase dem crazy', 228 'you cant run away from yourself', 'the truth is out there', 229 'one one cocoa full basket', 'yes i am a pirate', 230 'i love you too', 231 ]; 232 console.log(logMessages[Math.floor(Math.random() * logMessages.length)]); 233 if (lightningStrikeCount === 15 && !rabbitHoleRevealed) revealRabbitHole(); 234 } 235 236 // âââ Scheduler ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 237 let stormActive = false; 238 239 function scheduleNextStrike() { 240 if (!stormActive) return; 241 const delay = STRIKE_MIN_DELAY + Math.random() * (STRIKE_MAX_DELAY - STRIKE_MIN_DELAY); 242 gsap.delayedCall(delay, () => { 243 if (stormActive) { strike(); scheduleNextStrike(); } 244 }); 245 } 246 247 function startStorm() { 248 if (stormActive) return; 249 stormActive = true; 250 const firstDelay = 1.5 + Math.random() * 2.5; 251 gsap.delayedCall(firstDelay, () => { if (stormActive) { strike(); scheduleNextStrike(); } }); 252 } 253 254 function stopStorm() { 255 stormActive = false; 256 gsap.killDelayedCallsTo(scheduleNextStrike); 257 } 258 259 // IntersectionObserver on hero section 260 const heroObserver = new IntersectionObserver((entries) => { 261 entries.forEach(e => e.isIntersecting ? startStorm() : stopStorm()); 262 }, { threshold: 0.05 }); 263 heroObserver.observe(heroSection); 264 265 // âââ Easter egg: revealRabbitHole âââââââââââââââââââââââââââââââââââââââââ 266 function revealRabbitHole() { 267 rabbitHoleRevealed = true; 268 console.log('%c'); 269 console.log('%câââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ', 'color: #F6C453;'); 270 console.log('%c â¡ SIGNAL DETECTED â¡', 'font-size: 20px; color: #F6C453; font-weight: bold;'); 271 console.log('%câââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ', 'color: #F6C453;'); 272 console.log('%c'); 273 console.log('%c "I\'ve been working on a new electronic cash system', 'font-size: 14px; color: #7C8A92; font-style: italic;'); 274 console.log('%c that\'s fully peer-to-peer, with no trusted third party."', 'font-size: 14px; color: #7C8A92; font-style: italic;'); 275 console.log('%c â Satoshi, 2008', 'font-size: 12px; color: #41AD49;'); 276 console.log('%c'); 277 console.log('%c The hunt sets sail soon â live, in the real world.', 'font-size: 14px; color: #7C8A92;'); 278 console.log('%c â https://kotc.islandbitcoin.com', 'font-size: 14px; color: #41AD49;'); 279 console.log('%c'); 280 console.log('%c "We are all Satoshi."', 'font-size: 14px; color: #41AD49; font-style: italic;'); 281 console.log('%c'); 282 console.log('%câââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ', 'color: #F6C453;'); 283 } 284}
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.