AccelaReader

Paste your text and train with RSVP. (URL & AI are premium)
Words: 0

RSVP Reader

Use ↑/↓ or +/– (desktop).
0 / 0 words • 0%
Ready?
Space = Start/Pause • ←/→ = Back/Next • ↑/↓ or +/- = Speed • [ / ] = Chunk − / +
/* --- AccelaReader containment & spill guards --- */ #accela-reader { max-width: 980px; margin: 0 auto; overflow: visible; } #accela-reader .ar-card { overflow: hidden; } /* Kill auto
/ spill directly after the app */
#accela-reader + pre,
#accela-reader + code,
#accela-reader + .wpb_text_column pre,
#accela-reader + .wpb_text_column code {
  display: none !important;
}

/* Neutralize theme wpautop spacing inside our wrapper */
#accela-reader p:empty { display:none; }
#accela-reader p > code, 
#accela-reader p > pre { white-space: normal; }

/* Prevent long words/URLs from blowing layout */
#accela-reader, #accela-reader * { word-wrap: break-word; overflow-wrap: anywhere; }


		
/* AccelaReader — MVP with Premium Locks */ (function(){ var tries=0; function ready(){ var root=document.getElementById('accela-reader'); if(root) init(root); else if(tries++<200) setTimeout(ready,50) } (document.readyState==='loading')?document.addEventListener('DOMContentLoaded',ready):ready(); function init(root){ var $ = s => root.querySelector(s), $$ = s => Array.from(root.querySelectorAll(s)); // Elements var el = { tabPaste:$('#tabPaste'), tabUrl:$('#tabUrl'), tabAI:$('#tabAI'), panePaste:$('#panePaste'), paneUrl:$('#paneUrl'), paneAI:$('#paneAI'), paste:$('#arPaste'), stats:$('#arStats'), sample:$('#arSample'), useText:$('#arUseText'), wpm:$('#arWpm'), wpmNum:$('#arWpmNum'), chunk:$('#arChunk'), pauseStop:$('#pauseStop'), pauseComma:$('#pauseComma'), pauseDash:$('#pauseDash'), prog:$('#arProg'), hudText:$('#arHudText'), chunkView:$('#arChunkView'), prev:$('#arPrev'), next:$('#arNext'), start:$('#arStart'), pause:$('#arPause'), restart:$('#arRestart'), wpmDown:$('#arWpmDown'), wpmUp:$('#arWpmUp'), themeBtn:$('#arTheme'), backToPasteUrl:$('#backToPasteUrl'), backToPasteAI:$('#backToPasteAI'), ctaPremiumUrl:$('#ctaPremiumUrl'), ctaPremiumAI:$('#ctaPremiumAI') }; // State var state = { words:[], i:0, running:false, timer:null, wpm: parseInt(localStorage.getItem('ar_wpm')||'350',10), chunk: parseInt(localStorage.getItem('ar_chunk')||'2',10), pauses:{ stop:true, comma:true, dash:true } }; // Utils function clamp(n,a,b){ return Math.max(a,Math.min(b,n)) } function esc(s){ return s.replace(/[&<>\"']/g,m=>({'&':'&','<':'>','>':'>','"':'"'}[m]||m)).replace(/'/g,''') } function tokenize(txt){ return (txt||'').trim().replace(/\s+/g,' ').split(' ').filter(Boolean) } // Tabs function setTab(name){ // Only allow 'paste' in MVP if(name!=='paste'){ name='paste'; } el.tabPaste.classList.toggle('active',name==='paste'); el.tabUrl.classList.toggle('active',false); el.tabAI.classList.toggle('active',false); el.panePaste.hidden = (name!=='paste'); el.paneUrl.hidden = true; el.paneAI.hidden = true; } el.tabPaste.addEventListener('click',()=>setTab('paste')); // Premium-locked tabs: show a gentle nudge function lockedToast(msg){ alert(msg || 'This is a premium feature. Paste text to start reading now.'); } el.tabUrl.addEventListener('click',()=>lockedToast('URL import is premium. Paste your text to begin.')); el.tabAI.addEventListener('click',()=>lockedToast('AI generation is premium. Paste your text to begin.')); // Locked pane "Back to Paste" el.backToPasteUrl?.addEventListener('click',()=>setTab('paste')); el.backToPasteAI?.addEventListener('click',()=>setTab('paste')); // Locked CTAs: wire to your pricing or signup page if you have one el.ctaPremiumUrl?.addEventListener('click', (e)=>{ e.preventDefault(); lockedToast('To unlock, contact IRIS Reading.'); }); el.ctaPremiumAI?.addEventListener('click', (e)=>{ e.preventDefault(); lockedToast('To unlock, contact IRIS Reading.'); }); // Paste stats function updateStats(){ el.stats.textContent = 'Words: ' + tokenize(el.paste.value||'').length } var SAMPLE='Speed reading is less about rushing and more about directing attention. In an RSVP reader, words or small phrases appear at the same spot on the screen, reducing eye movements. Start near three hundred words per minute, with chunk size of two. Use punctuation pauses like breath marks—slightly longer at periods and shorter at commas. As accuracy holds, nudge speed upward.'; el.sample.addEventListener('click',()=>{ el.paste.value=SAMPLE; updateStats(); }); el.paste.addEventListener('input',updateStats); updateStats(); // Use in Reader function useText(txt){ var t=(txt||'').trim(); if(!t){ alert('No text found.'); return; } state.words = tokenize(t); state.i=0; el.chunkView.textContent='Ready?'; renderHUD(); localStorage.setItem('ar_last_text', t.slice(0,20000)); } el.useText.addEventListener('click',()=>useText(el.paste.value)); // Controls — WPM & Chunk function syncWpm(fromSlider){ var v = fromSlider ? parseInt(el.wpm.value,10) : parseInt(el.wpmNum.value,10); if(!Number.isFinite(v)) v=350; v = clamp(v,100, (fromSlider?1200:2000)); el.wpm.value = clamp(v,100,1200); el.wpmNum.value = v; state.wpm = v; localStorage.setItem('ar_wpm', String(v)); } el.wpm.addEventListener('input',()=>syncWpm(true)); el.wpmNum.addEventListener('input',()=>syncWpm(false)); el.chunk.value = String(state.chunk); el.wpm.value = clamp(state.wpm,100,1200); el.wpmNum.value = state.wpm; el.chunk.addEventListener('change',()=>{ state.chunk = clamp(parseInt(el.chunk.value,10)||2,1,5); localStorage.setItem('ar_chunk', String(state.chunk)); }); // Pauses function pStop(){ return state.pauses.stop?1.6:1 } function pComma(){ return state.pauses.comma?1.25:1 } function pDash(){ return state.pauses.dash?1.15:1 } el.pauseStop.addEventListener('change',()=>state.pauses.stop=el.pauseStop.checked); el.pauseComma.addEventListener('change',()=>state.pauses.comma=el.pauseComma.checked); el.pauseDash.addEventListener('change',()=>state.pauses.dash=el.pauseDash.checked); // Reader core function nextDelayFor(chunkText){ var last = chunkText.slice(-1); var mult = 1; if(/[.!?]/.test(last)) mult = pStop(); else if(/[,:;]/.test(last)) mult = pComma(); else if(/[–—-]/.test(last)) mult = pDash(); var words = chunkText.trim().split(/\s+/).filter(Boolean).length || 1; var base = (words / state.wpm) * 60000; return Math.max(30, base * mult); } function showChunk(){ if(state.i >= state.words.length){ el.chunkView.textContent='Finished ✓'; stop(); return; } var c = state.words.slice(state.i, state.i + state.chunk).join(' '); state.i += state.chunk; el.chunkView.innerHTML = esc(c); renderHUD(); return nextDelayFor(c); } function tick(){ if(!state.running) return; var wait = showChunk(); if(wait==null) return; state.timer = setTimeout(tick, wait); } function start(){ if(!state.words.length){ var last = localStorage.getItem('ar_last_text') || ''; if(last){ useText(last); } else { alert('Paste your text first.'); return; } } if(state.running) return; state.running = true; tick(); } function stop(){ state.running=false; if(state.timer){ clearTimeout(state.timer); state.timer=null; } } function pause(){ stop(); } function restart(){ stop(); state.i=0; el.chunkView.textContent='Ready?'; renderHUD(); } // HUD function renderHUD(){ var total = state.words.length, done = Math.min(total, state.i); var pct = total? Math.round((done/total)*100):0; el.hudText.textContent = done+' / '+total+' words • '+pct+'%'; el.prog.style.width = pct+'%'; } // Buttons el.start.addEventListener('click', start); el.pause.addEventListener('click', pause); el.restart.addEventListener('click', restart); el.prev.addEventListener('click', ()=>{ pause(); state.i = Math.max(0, state.i - state.chunk*2); showChunk(); }); el.next.addEventListener('click', ()=>{ pause(); showChunk(); }); el.wpmDown.addEventListener('click', ()=>{ el.wpmNum.value = clamp(parseInt(el.wpmNum.value,10)-25,100,2000); syncWpm(false); }); el.wpmUp.addEventListener('click', ()=>{ el.wpmNum.value = clamp(parseInt(el.wpmNum.value,10)+25,100,2000); syncWpm(false); }); // Keyboard controls (desktop) window.addEventListener('keydown', function(e){ const k=e.key; if([' ','ArrowLeft','ArrowRight','ArrowUp','ArrowDown','+','-','=','[',']'].includes(k)){ e.preventDefault(); } if(k===' ') (state.running?pause():start()); if(k==='ArrowLeft') el.prev.click(); if(k==='ArrowRight') el.next.click(); if(k==='ArrowUp' || k==='+') el.wpmUp.click(); if(k==='ArrowDown' || k==='-') el.wpmDown.click(); if(k==='[') { state.chunk = clamp(state.chunk-1,1,5); el.chunk.value=String(state.chunk); } if(k===']') { state.chunk = clamp(state.chunk+1,1,5); el.chunk.value=String(state.chunk); } }, {passive:false}); // Theme toggle var theme = localStorage.getItem('ar_theme') || 'dark'; function applyTheme(){ root.classList.toggle('light', theme==='light'); el.themeBtn.textContent = (theme==='light')?'☀️':'🌙'; localStorage.setItem('ar_theme', theme); } el.themeBtn.addEventListener('click',()=>{ theme = (theme==='light')?'dark':'light'; applyTheme(); }); applyTheme(); // Preload last text if present var last = localStorage.getItem('ar_last_text'); if(last){ el.paste.value = last; updateStats(); } } })(); // --- Mount sanity check --- (function(){ var root = document.getElementById('accela-reader'); if(!root){ console.warn('[AccelaReader] #accela-reader not found. Place HTML block above JS.'); return; } var hasCSS = !!document.getElementById('accela-reader-css'); if(!hasCSS) console.warn('[AccelaReader] CSS style tag missing. Place CSS block (Raw HTML) above JS.'); })();
X