A Svelte-based Rapid Serial Visual Presentation (RSVP) reader for speed reading with PDF and EPUB support.
Link: https://rsvp.n0name.eu/
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
- 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
# Clone the repository
git clone https://github.com/yourusername/rsvp.git
cd rsvp
# Install dependencies
npm install
# Start development server
npm run devRun 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# Development mode with hot reload
npm run dev
# Build for production
npm run build
# Preview production build
npm run previewFrom 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"
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 |
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
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
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)
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
# Run tests in watch mode
npm test
# Run tests once
npm run test:run
# Run tests with coverage
npm run test:coverageParses input text into an array of words.
parseText('Hello world') // ['Hello', 'world']Calculates the Optimal Recognition Point index for a word.
getORPIndex('hello') // 1 (second letter 'e')Gets the actual character index for ORP, accounting for leading punctuation.
getActualORPIndex('"hello') // 2 (skips the quote)Calculates the display delay for a word based on WPM and punctuation.
getWordDelay('hello', 300) // 200 (ms)
getWordDelay('end.', 300, true, 2) // 400 (ms)Formats remaining time as MM:SS.
formatTimeRemaining(300, 300) // '1:00'Splits a word into parts for ORP display.
splitWordForDisplay('hello')
// { before: 'h', orp: 'e', after: 'llo' }Checks if reading should pause at the current word.
shouldPauseAtWord(10, 10) // true
shouldPauseAtWord(5, 10) // falseExtracts text content from a PDF file.
Extracts text content from an EPUB e-book.
Auto-detects file type and parses accordingly.
Returns supported file extensions (.pdf,.epub).
Saves the current reading session to localStorage.
saveSession({
text: 'Your text content...',
currentWordIndex: 150,
totalWords: 500,
settings: { wordsPerMinute: 300 }
}) // trueLoads a saved reading session from localStorage.
const session = loadSession()
// { text: '...', currentWordIndex: 150, totalWords: 500, settings: {...}, savedAt: 1234567890 }Checks if a saved session exists.
hasSession() // true or falseRemoves the saved session from localStorage.
clearSession() // trueGets session info without loading the full text.
getSessionSummary()
// { currentWordIndex: 150, totalWords: 500, savedAt: 1234567890, hasText: true }Converts a percentage to a word index.
percentageToWordIndex(50, 100) // 50
percentageToWordIndex(25, 200) // 50Converts a word index to a percentage.
wordIndexToPercentage(50, 100) // 50
wordIndexToPercentage(25, 50) // 50Works in all modern browsers:
- Chrome (recommended)
- Firefox
- Safari
- Edge
- pdfjs-dist: PDF parsing
- epubjs: EPUB e-book parsing
- Svelte 5: UI framework
- Vite: Build tool
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
This project is open source and available under the MIT License.