1// Typewriter query animation 2 const queries = [ 3 [ 4 { text: 'SELECT ', cls: 'keyword' }, 5 { text: '* ', cls: '' }, 6 { text: 'FROM ', cls: 'keyword' }, 7 { text: 'transactions ', cls: '' }, 8 { text: 'JOIN ', cls: 'keyword' }, 9 { text: 'analytics ', cls: '' }, 10 { text: 'USING ', cls: 'keyword' }, 11 { text: '(data)', cls: '' }, 12 ] 13 ]; 14 15 const queryEl = document.getElementById('queryText'); 16 const flat = queries[0]; 17 let charIndex = 0; 18 let segIndex = 0; 19 let built = []; 20 21 function typeNext() { 22 if (segIndex >= flat.length) return; 23 24 const seg = flat[segIndex]; 25 if (charIndex < seg.text.length) { 26 // Build the full HTML from completed segments + partial current 27 let html = ''; 28 for (let i = 0; i < segIndex; i++) { 29 html += flat[i].cls 30 ? `<span class="${flat[i].cls}">${flat[i].text}</span>` 31 : flat[i].text; 32 } 33 const partial = seg.text.slice(0, charIndex + 1); 34 html += seg.cls ? `<span class="${seg.cls}">${partial}</span>` : partial; 35 queryEl.innerHTML = html; 36 charIndex++; 37 setTimeout(typeNext, 38); 38 } else { 39 segIndex++; 40 charIndex = 0; 41 setTimeout(typeNext, 38); 42 } 43 } 44 45 setTimeout(typeNext, 800); 46 47 // Scroll reveal 48 const reveals = document.querySelectorAll('.reveal'); 49 const observer = new IntersectionObserver((entries) => { 50 entries.forEach(e => { 51 if (e.isIntersecting) { 52 e.target.classList.add('visible'); 53 observer.unobserve(e.target); 54 } 55 }); 56 }, { threshold: 0.12 }); 57 58 reveals.forEach(el => observer.observe(el));
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.