Spaces:
Running
Running
| document.addEventListener('DOMContentLoaded', () => { | |
| const paperBody = document.querySelector('.paper-body'); | |
| if (!paperBody) return; | |
| const cursor = document.createElement('div'); | |
| cursor.className = 'llm-cursor'; | |
| document.body.appendChild(cursor); | |
| function wrapWords(element) { | |
| const nodes = Array.from(element.childNodes); | |
| nodes.forEach(node => { | |
| if (node.nodeType === Node.TEXT_NODE) { | |
| const text = node.textContent; | |
| if (text.trim() === '') return; | |
| const fragment = document.createDocumentFragment(); | |
| const words = text.split(/(\s+)/); | |
| words.forEach(word => { | |
| if (word.length === 0) return; | |
| if (word.trim() === '') { | |
| fragment.appendChild(document.createTextNode(word)); | |
| } else { | |
| const span = document.createElement('span'); | |
| span.className = 'word-wrapper'; | |
| span.textContent = word; | |
| fragment.appendChild(span); | |
| } | |
| }); | |
| element.replaceChild(fragment, node); | |
| } else if (node.nodeType === Node.ELEMENT_NODE && | |
| node.tagName !== 'SCRIPT' && | |
| node.tagName !== 'STYLE' && | |
| !node.classList.contains('proof-script')) { | |
| wrapWords(node); | |
| } | |
| }); | |
| } | |
| wrapWords(paperBody); | |
| const queue = []; | |
| let isProcessing = false; | |
| const containers = Array.from(paperBody.querySelectorAll('p, h1, h2, h3, li, blockquote')); | |
| const revealedContainers = new Set(); | |
| // Track active word for cursor repositioning on scroll | |
| let activeWord = null; | |
| function updateCursorPosition() { | |
| if (!activeWord || !cursor.classList.contains('active')) return; | |
| const rect = activeWord.getBoundingClientRect(); | |
| cursor.style.position = 'fixed'; | |
| cursor.style.left = `${rect.right}px`; | |
| cursor.style.top = `${rect.top}px`; | |
| cursor.style.height = `${rect.height}px`; | |
| cursor.style.width = '2px'; | |
| } | |
| function checkVisibility() { | |
| const viewportHeight = window.innerHeight; | |
| containers.forEach(container => { | |
| if (revealedContainers.has(container)) return; | |
| const rect = container.getBoundingClientRect(); | |
| if (rect.top < viewportHeight * 0.95) { | |
| revealedContainers.add(container); | |
| queue.push(container); | |
| if (!isProcessing) processQueue(); | |
| } | |
| }); | |
| // Ensure cursor follows scroll | |
| updateCursorPosition(); | |
| } | |
| window.addEventListener('scroll', checkVisibility, { passive: true }); | |
| setInterval(checkVisibility, 50); // High frequency check for smoother cursor | |
| checkVisibility(); | |
| async function processQueue() { | |
| if (queue.length === 0) { | |
| isProcessing = false; | |
| cursor.classList.remove('active'); | |
| activeWord = null; | |
| return; | |
| } | |
| isProcessing = true; | |
| const container = queue.shift(); | |
| await revealWordsInContainer(container); | |
| processQueue(); | |
| } | |
| function getDynamicDelay() { | |
| const viewportHeight = window.innerHeight; | |
| const hiddenWordsInView = document.querySelectorAll('.word-wrapper:not(.revealed)'); | |
| let countInView = 0; | |
| hiddenWordsInView.forEach(word => { | |
| const rect = word.getBoundingClientRect(); | |
| if (rect.top < viewportHeight && rect.bottom > 0) countInView++; | |
| }); | |
| const minDelay = 4; | |
| const maxDelay = 60; | |
| const scaleFactor = 150; | |
| const ratio = Math.min(countInView / scaleFactor, 1); | |
| return maxDelay - (ratio * (maxDelay - minDelay)); | |
| } | |
| async function revealWordsInContainer(container) { | |
| const words = Array.from(container.querySelectorAll('.word-wrapper:not(.revealed)')); | |
| if (words.length === 0) return; | |
| cursor.classList.add('active'); | |
| for (const word of words) { | |
| activeWord = word; | |
| word.classList.add('revealing'); | |
| updateCursorPosition(); | |
| const baseDelay = getDynamicDelay(); | |
| const delay = baseDelay + Math.random() * (baseDelay * 0.4); | |
| await new Promise(r => setTimeout(r, delay)); | |
| word.classList.add('revealed'); | |
| word.classList.remove('revealing'); | |
| updateCursorPosition(); | |
| } | |
| } | |
| }); | |