# speed

Adopt this skill: https://proskills.md/skills/other/seanzor-claude-speed-reader-claude-skills-speed

Page: https://proskills.md/skills/other/seanzor-claude-speed-reader-claude-skills-speed
This document is the public skill bundle for that page. It contains SKILL.md and every helper file the catalog carries. No login is required.

## File: .claude/skills/speed/SKILL.md
size: 1028
sha: 68c17961abeae2f02cdfbd71a68ab01dab707ae0
upstream: https://github.com/seanzor/claude-speed-reader/blob/main/.claude/skills/speed/SKILL.md

````text
---
name: speed
description: Launch RSVP speed reader for text
trigger: command
tools: Write, Bash, Read
---

# Speed Reader

Launch the RSVP speed reader to display text one word at a time with Spritz-style ORP (Optimal Recognition Point) highlighting.

## Instructions

1. **Get the text:**
   - If `$ARGUMENTS` is provided, use that text
   - Otherwise, extract the main content from your **previous response** in this conversation

2. **Prepare the content:**
   - Strip markdown formatting (headers, bold, links, code blocks)
   - Keep clean, readable prose
   - Escape quotes and backslashes for JavaScript

3. **Write and launch:**
   - Read `~/.claude/skills/speed/data/reader.html`
   - Replace `<!-- CONTENT_PLACEHOLDER -->` with:
     ```html
     <script>window.SPEED_READER_CONTENT = "your escaped text";</script>
     <!-- CONTENT_PLACEHOLDER -->
     ```
   - Run: `open ~/.claude/skills/speed/data/reader.html`

4. **Confirm:** Tell the user it's opening. Mention `Space` to play/pause.

## Arguments
$ARGUMENTS
````

## File: .claude/skills/speed/data/reader.html
size: 14386
sha: d9a97e59da2f40ed93493e7ed4434697da4278f8
upstream: https://github.com/seanzor/claude-speed-reader/blob/main/.claude/skills/speed/data/reader.html

```text
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Speed Reader</title>
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }

    :root {
      --bg: #0a0a0f;
      --surface: #12121a;
      --border: #1e1e2a;
      --text: #e4e4e7;
      --muted: #52525b;
      --accent: #ef4444;
      --accent-dim: rgba(239, 68, 68, 0.15);
    }

    body {
      font-family: 'JetBrains Mono', monospace;
      background: var(--bg);
      color: var(--text);
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    /* Progress bar - thin line at top */
    .progress {
      position: fixed;
      top: 0;
      left: 0;
      height: 2px;
      background: var(--accent);
      width: 0%;
      transition: width 0.1s linear;
      z-index: 100;
    }

    /* Main reading area */
    .reader {
      flex: 1;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      user-select: none;
    }

    .word-container {
      position: relative;
      width: 100%;
      display: flex;
      justify-content: center;
    }

    /* Focus line indicator */
    .focus-line {
      position: fixed;
      top: 30%;
      bottom: 40%;
      left: 50%;
      transform: translateX(-50%);
      width: 2px;
      background: linear-gradient(to bottom, transparent, var(--accent), transparent);
      opacity: 0.2;
      pointer-events: none;
    }

    .word {
      font-size: clamp(2.5rem, 8vw, 5rem);
      font-weight: 500;
      letter-spacing: 0.02em;
      white-space: nowrap;
      display: flex;
      align-items: baseline;
    }

    .word-before {
      color: var(--text);
      width: 45vw;
      text-align: right;
    }

    .word-after {
      color: var(--text);
      width: 45vw;
      text-align: left;
    }

    .word-orp {
      color: var(--accent);
      position: relative;
      text-align: center;
      min-width: 0.6em;
    }
    .word-orp::after {
      content: '';
      position: absolute;
      bottom: -4px;
      left: 0;
      right: 0;
      height: 2px;
      background: var(--accent);
      opacity: 0.5;
    }

    /* Paused state */
    .paused .word { opacity: 0.5; }
    .paused .word-orp { color: var(--muted); }
    .paused .word-orp::after { background: var(--muted); }

    /* Controls - appear on hover/pause */
    .controls {
      position: fixed;
      bottom: 0;
      left: 0;
      right: 0;
      padding: 1.5rem 2rem;
      background: linear-gradient(to top, var(--bg), transparent);
      opacity: 0;
      transform: translateY(10px);
      transition: all 0.3s ease;
      pointer-events: none;
    }

    body:hover .controls,
    .paused .controls,
    .idle .controls {
      opacity: 1;
      transform: translateY(0);
      pointer-events: auto;
    }

    .controls-inner {
      max-width: 600px;
      margin: 0 auto;
      display: flex;
      flex-direction: column;
      gap: 1rem;
    }

    .speed-row {
      display: flex;
      align-items: center;
      gap: 1rem;
    }

    .wpm {
      font-size: 0.875rem;
      color: var(--muted);
      min-width: 80px;
    }
    .wpm span { color: var(--accent); font-weight: 600; }

    input[type="range"] {
      flex: 1;
      height: 4px;
      -webkit-appearance: none;
      background: var(--border);
      border-radius: 2px;
      outline: none;
    }

    input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none;
      width: 14px;
      height: 14px;
      background: var(--accent);
      border-radius: 50%;
      cursor: pointer;
      transition: transform 0.15s;
    }

    input[type="range"]::-webkit-slider-thumb:hover {
      transform: scale(1.3);
    }

    .btn-row {
      display: flex;
      gap: 0.5rem;
    }

    button {
      flex: 1;
      padding: 0.625rem 1rem;
      font-family: inherit;
      font-size: 0.75rem;
      text-transform: uppercase;
      letter-spacing: 0.05em;
      border: 1px solid var(--border);
      border-radius: 6px;
      background: var(--surface);
      color: var(--muted);
      cursor: pointer;
      transition: all 0.15s;
    }

    button:hover {
      border-color: var(--accent);
      color: var(--text);
    }

    button.primary {
      background: var(--accent);
      border-color: var(--accent);
      color: white;
    }

    /* Status */
    .status {
      position: fixed;
      top: 1rem;
      left: 50%;
      transform: translateX(-50%);
      font-size: 0.75rem;
      color: var(--muted);
      opacity: 0;
      transition: opacity 0.3s;
    }

    body:hover .status,
    .paused .status,
    .idle .status {
      opacity: 1;
    }

    /* Keyboard hints */
    .hints {
      position: fixed;
      bottom: 1rem;
      right: 1rem;
      font-size: 0.625rem;
      color: var(--muted);
      opacity: 0;
      transition: opacity 0.3s;
    }

    body:hover .hints { opacity: 0.5; }

    kbd {
      display: inline-block;
      padding: 0.15rem 0.4rem;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: 3px;
      margin: 0 0.15rem;
    }

    /* Input modal */
    .modal {
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.8);
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 2rem;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s;
    }

    .modal.open {
      opacity: 1;
      pointer-events: auto;
    }

    .modal-content {
      width: 100%;
      max-width: 500px;
      background: var(--surface);
      border: 1px solid var(--border);
      border-radius: 12px;
      padding: 1.5rem;
    }

    .modal h2 {
      font-size: 0.875rem;
      font-weight: 500;
      margin-bottom: 1rem;
      color: var(--muted);
    }

    textarea {
      width: 100%;
      min-height: 150px;
      padding: 1rem;
      font-family: inherit;
      font-size: 0.875rem;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 8px;
      color: var(--text);
      resize: vertical;
      outline: none;
    }

    textarea:focus { border-color: var(--accent); }

    .modal-buttons {
      display: flex;
      gap: 0.5rem;
      margin-top: 1rem;
    }

    /* Drop zone overlay */
    .drop-overlay {
      position: fixed;
      inset: 0;
      background: rgba(10, 10, 15, 0.95);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.25rem;
      color: var(--accent);
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s;
    }

    .drop-overlay.active {
      opacity: 1;
      pointer-events: auto;
    }
  </style>
</head>
<body class="idle">
  <div class="progress" id="progress"></div>

  <div class="status" id="status">Press Space to start</div>

  <div class="reader" id="reader">
    <div class="word-container">
      <div class="focus-line"></div>
      <div class="word" id="word">
        <span class="word-before"></span><span class="word-orp"></span><span class="word-after"></span>
      </div>
    </div>
  </div>

  <div class="controls">
    <div class="controls-inner">
      <div class="speed-row">
        <div class="wpm"><span id="wpmDisplay">600</span> wpm</div>
        <input type="range" id="speedSlider" min="100" max="1500" value="600" step="50">
      </div>
      <div class="btn-row">
        <button class="primary" id="playBtn">Play</button>
        <button id="restartBtn">Restart</button>
        <button id="pasteBtn">Paste</button>
      </div>
    </div>
  </div>

  <div class="hints">
    <kbd>Space</kbd> play <kbd>&larr;</kbd><kbd>&rarr;</kbd> speed <kbd>R</kbd> restart <kbd>V</kbd> paste
  </div>

  <div class="modal" id="modal">
    <div class="modal-content">
      <h2>Paste or drop text</h2>
      <textarea id="textInput" placeholder="Paste your text here..."></textarea>
      <div class="modal-buttons">
        <button id="loadBtn" class="primary">Load</button>
        <button id="cancelBtn">Cancel</button>
      </div>
    </div>
  </div>

  <div class="drop-overlay" id="dropOverlay">Drop text file here</div>

  <script>window.SPEED_READER_CONTENT = "Memento is a Claude Code command that extracts session memories into CLAUDE.md files. Core idea: After a coding session, /memento analyzes your conversation to find learnings that should persist - things like project conventions, user preferences, or patterns discovered during debugging. Key philosophy: Suggestions must be actionable - they should change Claude's future behavior. Vague observations get filtered out. Structure: .claude/commands/memento.md is the main memory extraction command. .claude/commands/brag.md is for generating social content ideas. docs/ and examples/ contain documentation and example scenarios. Memory types: Project Memory goes in ./CLAUDE.md for codebase-specific conventions. User Memory goes in ~/.claude/CLAUDE.md for cross-project preferences. Planned components include Memento MCP, a TypeScript server to read session history, and Memento Sync for Git-based backup with smart CLAUDE.md merging. The command is designed to be symlinked to ~/.claude/commands/ for global use across all projects.";</script>
  <!-- CONTENT_PLACEHOLDER -->
  <script>
    const $ = id => document.getElementById(id);

    let words = [], index = 0, wpm = 600, playing = false, timer = null;

    // ORP: ~1/3 into word
    const orp = w => w.length > 13 ? 4 : [0,0,1,1,1,1,2,2,2,2,3,3,3,3][w.length];

    // Timing: extra pause for punctuation
    const timing = (w, base) => {
      let t = base;
      if (/[.!?]$/.test(w)) t *= 2.2;
      else if (/[,;:]$/.test(w)) t *= 1.5;
      if (w.length > 10) t *= 1.1;
      return t;
    };

    const display = w => {
      if (!w) return;
      const i = orp(w);
      $('word').innerHTML = `<span class="word-before">${esc(w.slice(0,i))}</span><span class="word-orp">${esc(w[i]||'')}</span><span class="word-after">${esc(w.slice(i+1))}</span>`;
    };

    const esc = s => { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; };

    const updateProgress = () => {
      const pct = words.length ? (index / words.length) * 100 : 0;
      $('progress').style.width = pct + '%';
      const remaining = Math.ceil((words.length - index) / wpm * 60);
      $('status').textContent = playing ? `${Math.floor(remaining/60)}:${(remaining%60).toString().padStart(2,'0')} remaining` :
        words.length ? `${words.length} words` : 'Paste text to begin';
    };

    const next = () => {
      if (!playing || index >= words.length) {
        if (index >= words.length && words.length) stop();
        return;
      }
      display(words[index]);
      updateProgress();
      index++;
      timer = setTimeout(next, timing(words[index-1], 60000/wpm));
    };

    const play = () => {
      if (!words.length) { openModal(); return; }
      playing = true;
      document.body.classList.remove('paused', 'idle');
      $('playBtn').textContent = 'Pause';
      next();
    };

    const pause = () => {
      playing = false;
      clearTimeout(timer);
      document.body.classList.add('paused');
      $('playBtn').textContent = 'Play';
      updateProgress();
    };

    const stop = () => { pause(); $('status').textContent = 'Done'; };
    const toggle = () => playing ? pause() : play();
    const restart = () => { pause(); index = 0; if (words[0]) display(words[0]); updateProgress(); document.body.classList.add('idle'); };

    const load = text => {
      pause();
      words = text.replace(/\s+/g, ' ').trim().split(' ').filter(w => w);
      index = 0;
      if (words.length) {
        display(words[0]);
        document.body.classList.add('idle');
      }
      updateProgress();
      closeModal();
    };

    const openModal = () => { $('modal').classList.add('open'); $('textInput').focus(); };
    const closeModal = () => { $('modal').classList.remove('open'); };

    // Events
    $('reader').onclick = toggle;
    $('playBtn').onclick = toggle;
    $('restartBtn').onclick = restart;
    $('pasteBtn').onclick = openModal;
    $('loadBtn').onclick = () => load($('textInput').value);
    $('cancelBtn').onclick = closeModal;

    $('speedSlider').oninput = e => {
      wpm = +e.target.value;
      $('wpmDisplay').textContent = wpm;
      localStorage.setItem('speed-wpm', wpm);
    };

    document.onkeydown = e => {
      if ($('modal').classList.contains('open')) {
        if (e.key === 'Escape') closeModal();
        return;
      }
      if (e.target.tagName === 'TEXTAREA') return;

      switch(e.code) {
        case 'Space': e.preventDefault(); toggle(); break;
        case 'ArrowLeft': e.preventDefault(); wpm = Math.max(100, wpm-50); $('speedSlider').value = wpm; $('wpmDisplay').textContent = wpm; break;
        case 'ArrowRight': e.preventDefault(); wpm = Math.min(1500, wpm+50); $('speedSlider').value = wpm; $('wpmDisplay').textContent = wpm; break;
        case 'KeyR': e.preventDefault(); restart(); break;
        case 'KeyV': e.preventDefault(); openModal(); break;
      }
    };

    // Drag & drop
    document.ondragover = e => { e.preventDefault(); $('dropOverlay').classList.add('active'); };
    document.ondragleave = e => { if (e.relatedTarget === null) $('dropOverlay').classList.remove('active'); };
    document.ondrop = e => {
      e.preventDefault();
      $('dropOverlay').classList.remove('active');
      const file = e.dataTransfer.files[0];
      if (file) {
        const reader = new FileReader();
        reader.onload = ev => load(ev.target.result);
        reader.readAsText(file);
      }
    };

    // Init
    const saved = localStorage.getItem('speed-wpm');
    if (saved) { wpm = +saved; $('speedSlider').value = wpm; $('wpmDisplay').textContent = wpm; }

    if (typeof window.SPEED_READER_CONTENT !== 'undefined' && window.SPEED_READER_CONTENT.trim()) {
      load(window.SPEED_READER_CONTENT);
    } else {
      updateProgress();
    }
  </script>
</body>
</html>
```

## File: README.md
upstream: https://github.com/seanzor/claude-speed-reader/blob/main/README.md

````text
# claude-speed-reader

Speed read Claude's responses at 600+ WPM using RSVP with Spritz-style ORP highlighting.

![Speed Reader Demo](demo.gif)

## What is this?

A Claude Code skill that lets you speed-read any response. Uses **Rapid Serial Visual Presentation (RSVP)** — displaying one word at a time with the **Optimal Recognition Point (ORP)** highlighted in red. Your eyes stay fixed while your brain processes text at 2-3x normal reading speed.

## Install

```bash
# Clone to your Claude skills directory
git clone https://github.com/SeanZoR/claude-speed-reader.git ~/.claude/skills/speed
```

Or manually copy the `.claude/` folder contents to your `~/.claude/` directory.

## Usage

In Claude Code, after any response:

```
/speed
```

That's it. Opens a minimal dark reader with the last response loaded.

**Custom text:**
```
/speed "Your text here"
```

## Controls

| Key | Action |
|-----|--------|
| `Space` | Play / Pause |
| `←` `→` | Adjust speed (±50 WPM) |
| `R` | Restart |
| `V` | Paste new text |

Click anywhere to toggle play/pause. Drag & drop text files supported.

## How ORP Works

The red highlighted letter is the **Optimal Recognition Point** — positioned ~1/3 into each word where your brain naturally focuses. By keeping this point fixed on screen, you eliminate eye movement (saccades) that consume 80% of reading time.

```
    th[e] quick br[o]wn fox ju[m]ps
       ↑         ↑         ↑
      ORP       ORP       ORP
```

## Customization

Edit `~/.claude/skills/speed/data/reader.html` to customize:
- Default WPM (currently 600)
- Colors and fonts
- Timing multipliers for punctuation

## Requirements

- Claude Code
- macOS (uses `open` command)

## License

MIT

---

Built for humans who want to read Claude's novels faster.
````
