Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab8d007b72 | |||
| a5819f0c72 |
@@ -9,6 +9,58 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versi
|
||||
|
||||
---
|
||||
|
||||
## [1.1.3] — 2026-05-27
|
||||
|
||||
### Added — ORP anchor in Sentence mode
|
||||
|
||||
Sentence mode now displays one red letter near the middle of each sentence — same ORP (Optimal Recognition Point) treatment used in Word mode, scaled up to sentence size. The eye fixates on the red character and processes the rest of the sentence peripherally instead of saccading across the line.
|
||||
|
||||
The anchor is calculated by finding the **middle word of the sentence** (by word index, not character index — keeps the anchor word-aligned), then applying the standard ORP-character formula to that word:
|
||||
|
||||
| Word length | ORP position (0-indexed) |
|
||||
|---|---|
|
||||
| 1 character | char 0 |
|
||||
| 2-4 characters | char 1 |
|
||||
| 5-9 characters | char 2 |
|
||||
| 10-13 characters | char 3 |
|
||||
| 14+ characters | char 4 |
|
||||
|
||||
For a 9-word sentence, the middle word is index 4. If that word is "evaluation" (10 chars), the ORP character is `l` (`eva*l*uation`) — that's the red letter on screen.
|
||||
|
||||
### Why this helps high-WPM Sentence mode
|
||||
|
||||
At 3000 WPM with the v1.1.2 lower floors, sentences flash by in 400-800 ms. Without an eye anchor, the eye starts scanning from the left as if reading normally, and runs out of time before the sentence advances. With the red ORP letter near the middle, the eye fixates on the anchor immediately and absorbs the sentence in a single peripheral-vision read. This is what makes high-WPM RSVP comprehensible rather than just fast.
|
||||
|
||||
### Paragraph mode
|
||||
|
||||
Paragraph mode still renders plain text without an ORP anchor. A single anchor character on a multi-line block doesn't give the eye a useful fixation point (it still needs to saccade between lines). Per-sentence ORP within paragraphs could be added in a future version if the use case emerges.
|
||||
|
||||
---
|
||||
|
||||
## [1.1.2] — 2026-05-27
|
||||
|
||||
### Fixed — High WPM in Sentence / Paragraph modes was clamped by a too-conservative minimum-display floor
|
||||
|
||||
After v1.1.1 raised the WPM cap to 3000 in Sentence and Paragraph modes, David noticed that pushing the slider from 1500 to 3000 didn't actually speed up reading — the chunks were still sitting on screen for the same amount of time.
|
||||
|
||||
Root cause: the v1.1.0 minimum-display floor was **1500 ms per chunk** (set conservatively to prevent flash-frame illegibility). At 3000 WPM, any sentence under ~60 words mathematically wants to display for under 1500 ms — but the floor clamped them all to 1500 ms, so the calculated speed-up never reached the user.
|
||||
|
||||
Lowered floors:
|
||||
- **Sentence mode**: 1500 ms → **400 ms** (eye can absorb a short sentence in well under half a second once it's on screen)
|
||||
- **Paragraph mode**: 1500 ms → **800 ms** (paragraphs are denser; need slightly more time even at high WPM)
|
||||
|
||||
Maximum cap stays at 12 s for very long paragraphs at very low WPM.
|
||||
|
||||
At 3000 WPM with the new floors, a 20-word sentence displays for **500 ms** (was 1500 ms — 3× faster) and a 60-word paragraph displays for **1750 ms** (was 1500 ms — uncapped formula now visible).
|
||||
|
||||
This makes the WPM slider actually behave the way users expect: higher WPM = faster reading, all the way up to 3000.
|
||||
|
||||
### Clarification
|
||||
|
||||
The reader does NOT iterate word-by-word inside a chunk in Sentence or Paragraph modes. The whole sentence or paragraph is rendered in one DOM update; only the auto-advance interval is calculated from word count. The word count is used as a proxy for "how long does a reader need to absorb this chunk," not as a per-word display loop.
|
||||
|
||||
---
|
||||
|
||||
## [1.1.1] — 2026-05-27
|
||||
|
||||
### Changed — Mode-aware WPM ceiling
|
||||
|
||||
+42
-7
@@ -250,7 +250,7 @@ textarea:focus {
|
||||
</div>
|
||||
|
||||
<span class="meta" id="meta">Paste text below, drag a .txt file in, then press Play</span>
|
||||
<span class="version">v1.1.1</span>
|
||||
<span class="version">v1.1.3</span>
|
||||
</div>
|
||||
|
||||
<div class="stage mode-word" id="stage">
|
||||
@@ -419,14 +419,39 @@ textarea:focus {
|
||||
function renderChunk(chunk) {
|
||||
if (!chunk) { display.innerHTML = ''; return; }
|
||||
if (mode === 'word') {
|
||||
// Single word with ORP coloring
|
||||
// Single word with ORP coloring on the focal character
|
||||
const i = Math.min(orpIndex(chunk), chunk.length - 1);
|
||||
const before = chunk.slice(0, i);
|
||||
const orp = chunk[i] || '';
|
||||
const after = chunk.slice(i + 1);
|
||||
display.innerHTML = `${esc(before)}<span class="orp">${esc(orp)}</span>${esc(after)}`;
|
||||
} else if (mode === 'sentence') {
|
||||
// v1.1.3: anchor the eye to ONE red letter near the middle of the
|
||||
// sentence. Find the middle word (by index), then apply the same
|
||||
// ORP-character formula to that word. Other tokens are preserved
|
||||
// verbatim including original spacing.
|
||||
const tokens = chunk.split(/(\s+)/); // keep whitespace tokens for layout
|
||||
const wordTokenIndices = tokens
|
||||
.map((t, i) => (/^\s+$/.test(t) || t.length === 0) ? -1 : i)
|
||||
.filter(i => i !== -1);
|
||||
const anchorTokenIdx = wordTokenIndices.length
|
||||
? wordTokenIndices[Math.floor(wordTokenIndices.length / 2)]
|
||||
: -1;
|
||||
let html = '';
|
||||
tokens.forEach((tok, i) => {
|
||||
if (i === anchorTokenIdx && tok.length > 0) {
|
||||
const j = Math.min(orpIndex(tok), tok.length - 1);
|
||||
html += `${esc(tok.slice(0, j))}<span class="orp">${esc(tok[j])}</span>${esc(tok.slice(j + 1))}`;
|
||||
} else {
|
||||
// Sentence or paragraph — render as plain text (no ORP)
|
||||
html += esc(tok);
|
||||
}
|
||||
});
|
||||
display.innerHTML = html;
|
||||
} else {
|
||||
// Paragraph mode — plain text. (ORP on a multi-line block doesn't
|
||||
// give the eye a single useful anchor; the eye still saccades
|
||||
// across lines. If demand emerges, per-sentence ORP within the
|
||||
// paragraph could be added in a future version.)
|
||||
display.textContent = chunk;
|
||||
}
|
||||
}
|
||||
@@ -448,12 +473,22 @@ textarea:focus {
|
||||
const base = 60000 / wpm;
|
||||
return base * pauseMult(chunk);
|
||||
}
|
||||
// Sentence / paragraph: time = words / WPM, with a comprehension multiplier
|
||||
// Sentence / paragraph: in these modes the whole chunk is displayed
|
||||
// in one DOM update — no per-word iteration. We only need to decide
|
||||
// how long to leave the chunk visible before advancing.
|
||||
//
|
||||
// Time = (words in chunk) / WPM × 60 s × comprehension multiplier.
|
||||
// At low WPM the formula dominates. At high WPM the floor kicks in.
|
||||
// v1.1.2: lowered floors significantly — the previous 1.5 s sentence
|
||||
// floor was clamping any value above ~1500 WPM, making 3000 WPM
|
||||
// feel identical to 1500 WPM. Lower floors let high-WPM skim mode
|
||||
// actually skim. The maximum cap stays at 12 s for very long
|
||||
// paragraphs at very low WPM.
|
||||
const wordCount = splitWords(chunk).length;
|
||||
const baseMs = (wordCount / wpm) * 60000;
|
||||
const multiplier = mode === 'sentence' ? 1.25 : 1.40; // brain needs more time on chunks
|
||||
// Sensible bounds: 1.5s min, 12s max
|
||||
return Math.max(1500, Math.min(12000, baseMs * multiplier));
|
||||
const multiplier = mode === 'sentence' ? 1.25 : 1.40;
|
||||
const floor = mode === 'sentence' ? 400 : 800;
|
||||
return Math.max(floor, Math.min(12000, baseMs * multiplier));
|
||||
}
|
||||
|
||||
function tick() {
|
||||
|
||||
Reference in New Issue
Block a user