RSVP Reader
A Svelte-based Rapid Serial Visual Presentation (RSVP) reader for speed reading with PDF and EPUB support.
Online version / DEMO
Link: https://rsvp.n0name.eu/
What is RSVP?
Rapid Serial Visual Presentation (RSVP) is a technique where text is displayed one word at a time at a fixed focal point. This eliminates the need for eye movements (saccades) during reading, potentially allowing for significantly faster reading speeds.
The app uses Optimal Recognition Point (ORP) highlighting - the red letter in each word indicates the point where your eye naturally focuses for fastest word recognition. This is calculated based on word length:
- 1-3 letter words: 1st letter
- 4-5 letter words: 2nd letter
- 6-9 letter words: 3rd letter
- 10+ letter words: 4th letter
Features
- PDF & EPUB Support: Upload PDF documents or EPUB e-books directly
- Adjustable reading speed: 50-1000 words per minute (WPM)
- ORP highlighting: Red-highlighted focal letter for faster recognition
- Monospace display: Fixed-width font keeps the focal point stable
- Focus mode: Minimal UI during reading for distraction-free experience
- Fade effect: Optional smooth transitions between words
- Punctuation pauses: Configurable extra pause on sentence-ending punctuation
- Periodic pauses: Optional pause every N words for comprehension
- Progress tracking: Visual progress bar and time remaining
- Save progress: Save your reading session and resume later
- Jump to position: Skip to any word number or percentage in the text
- Clickable progress bar: Click anywhere on the progress bar to jump to that position
- Keyboard shortcuts: Full keyboard control for hands-free reading
- Dark theme: Easy on the eyes with black background
Installation
# Clone the repository git clone https://github.com/yourusername/rsvp.git cd rsvp # Install dependencies npm install # Start development server npm run dev
Docker
Run the app using Docker Compose:
cd docker
docker compose up -dThe app will be available at http://localhost:8080
To rebuild after changes:
docker compose up -d --build
Usage
Running the App
# Development mode with hot reload npm run dev # Build for production npm run build # Preview production build npm run preview
Loading Content
From Files:
- Click the document icon in the header
- Click "Upload PDF or EPUB"
- Select your PDF or EPUB file
- The text will be extracted and loaded automatically
From Text:
- Click the document icon in the header
- Paste or type your text in the textarea
- Click "Load Text"
Controls
Buttons:
- Play: Start reading from the beginning or current position
- Pause: Pause reading (UI enters focus mode with minimal controls)
- Resume: Continue from where you paused
- Stop: Stop and reset to beginning
- Restart: Stop and immediately start from beginning
Keyboard Shortcuts:
| Key | Action |
|---|---|
Space |
Play/Pause/Resume |
Escape |
Exit focus mode (or close dialogs) |
Arrow Up |
Increase speed (+25 WPM) |
Arrow Down |
Decrease speed (-25 WPM) |
Arrow Left |
Go back one word |
Arrow Right |
Skip forward one word |
G |
Open jump to position dialog |
Ctrl+S / Cmd+S |
Save current progress |
Saving and Resuming Progress
Save Progress:
- Click the save icon in the header (floppy disk icon)
- Or press
Ctrl+S(Windows/Linux) orCmd+S(Mac) - Your current position, text, and all settings are saved to browser storage
Resume Reading:
- When you return to the app, you'll be prompted to resume your saved session
- Click "Resume" to continue from where you left off
- Click "Start Fresh" to begin with the default text
Jump to Position
Using the Jump Dialog:
- Click the code bracket icon in the header, or press
G - Enter a word number (e.g.,
150) or percentage (e.g.,50%) - Click "Go" or press Enter
Quick Jump Buttons:
- Use the preset buttons (Start, 25%, 50%, 75%) for quick navigation
Clickable Progress Bar:
- When not playing, click anywhere on the progress bar to jump directly to that position
- The progress bar expands on hover to make clicking easier
Settings
Click the gear icon to access settings:
- Words Per Minute: Reading speed (50-1000 WPM)
- Enable Fade Effect: Smooth fade transition between words
- Fade Duration: Duration of fade effect (50-300ms)
- Pause on Punctuation: Extra pause at sentence endings
- Punctuation Pause Multiplier: How much longer to pause (1-4x)
- Pause Every N Words: Take a break every N words (0 = disabled)
- Pause Duration: Length of periodic pauses (100-2000ms)
Project Structure
rsvp/
├── src/
│ ├── App.svelte # Main application component
│ ├── app.css # Global styles
│ ├── main.js # Application entry point
│ ├── lib/
│ │ ├── rsvp-utils.js # Core RSVP utility functions
│ │ ├── file-parsers.js # PDF and EPUB parsing utilities
│ │ ├── progress-storage.js # Session save/load utilities
│ │ └── components/
│ │ ├── RSVPDisplay.svelte # Word display component
│ │ ├── Controls.svelte # Playback controls
│ │ ├── Settings.svelte # Settings panel
│ │ ├── TextInput.svelte # Text/file input panel
│ │ └── ProgressBar.svelte # Progress indicator (clickable)
│ └── tests/
│ ├── setup.js # Test setup
│ ├── rsvp-utils.test.js # RSVP utility tests
│ ├── file-parsers.test.js # File parser tests
│ └── progress-storage.test.js # Progress storage tests
├── index.html
├── package.json
├── vite.config.js
└── README.md
Testing
# Run tests in watch mode npm test # Run tests once npm run test:run # Run tests with coverage npm run test:coverage
API Reference
Utility Functions (src/lib/rsvp-utils.js)
parseText(text)
Parses input text into an array of words.
parseText('Hello world') // ['Hello', 'world']
getORPIndex(word)
Calculates the Optimal Recognition Point index for a word.
getORPIndex('hello') // 1 (second letter 'e')
getActualORPIndex(word)
Gets the actual character index for ORP, accounting for leading punctuation.
getActualORPIndex('"hello') // 2 (skips the quote)
getWordDelay(word, wpm, pauseOnPunctuation, multiplier)
Calculates the display delay for a word based on WPM and punctuation.
getWordDelay('hello', 300) // 200 (ms) getWordDelay('end.', 300, true, 2) // 400 (ms)
formatTimeRemaining(remainingWords, wpm)
Formats remaining time as MM:SS.
formatTimeRemaining(300, 300) // '1:00'
splitWordForDisplay(word)
Splits a word into parts for ORP display.
splitWordForDisplay('hello') // { before: 'h', orp: 'e', after: 'llo' }
shouldPauseAtWord(wordIndex, pauseAfterWords)
Checks if reading should pause at the current word.
shouldPauseAtWord(10, 10) // true shouldPauseAtWord(5, 10) // false
File Parsers (src/lib/file-parsers.js)
parsePDF(file)
Extracts text content from a PDF file.
parseEPUB(file)
Extracts text content from an EPUB e-book.
parseFile(file)
Auto-detects file type and parses accordingly.
getSupportedExtensions()
Returns supported file extensions (.pdf,.epub).
Progress Storage (src/lib/progress-storage.js)
saveSession(session)
Saves the current reading session to localStorage.
saveSession({ text: 'Your text content...', currentWordIndex: 150, totalWords: 500, settings: { wordsPerMinute: 300 } }) // true
loadSession()
Loads a saved reading session from localStorage.
const session = loadSession() // { text: '...', currentWordIndex: 150, totalWords: 500, settings: {...}, savedAt: 1234567890 }
hasSession()
Checks if a saved session exists.
hasSession() // true or false
clearSession()
Removes the saved session from localStorage.
getSessionSummary()
Gets session info without loading the full text.
getSessionSummary() // { currentWordIndex: 150, totalWords: 500, savedAt: 1234567890, hasText: true }
percentageToWordIndex(percentage, totalWords)
Converts a percentage to a word index.
percentageToWordIndex(50, 100) // 50 percentageToWordIndex(25, 200) // 50
wordIndexToPercentage(wordIndex, totalWords)
Converts a word index to a percentage.
wordIndexToPercentage(50, 100) // 50 wordIndexToPercentage(25, 50) // 50
Browser Support
Works in all modern browsers:
- Chrome (recommended)
- Firefox
- Safari
- Edge
Dependencies
- pdfjs-dist: PDF parsing
- epubjs: EPUB e-book parsing
- Svelte 5: UI framework
- Vite: Build tool
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
This project is open source and available under the MIT License.