Skip to content

Focus Reader

A Chrome extension that turns any article into a reading room.

2026·personal project·landing ↗github ↗

Reading a long article online means fighting the page that hosts it — newsletter modals, sticky navs, recommendation grids. Reader modes clean the clutter but still hand you one enormous vertical document. You scroll, lose your line, re-find it, scroll again.

Focus Reader takes the article out of the page and re-renders it in its own full-screen tab as a deck of single-thought slides. Step through with a click or a key — or press P and it plays itself like a teleprompter at your chosen words-per-minute. No ads, no servers, zero network requests.

Chrome MV3Vanilla JSchrome.storageZero servers

How it works

Toolbar click
      ↓
content.js walks article / main
  keeps h1–h3, p, li, figures, embeds
  drops nav, footers, comments, widgets
      ↓
Structured items → chrome.storage
      ↓
Sandboxed chrome-extension:// tab
  chunks text on sentence boundaries
      ↓
Slide deck — one thought per slide

Most reader tools inject an overlay into the host page, which leaves them exposed to the site's global styles, iframe blockers, and tracking code. Focus Reader moves the reading into its own chrome-extension:// tab instead. The extractor targets the semantic container, pulls content in document order, and a seen-text set catches the duplicate nodes CMS templates love to emit.

Text is chunked into ~200-character slides on sentence boundaries and each lands dead-center on pure black. Type scale adapts to the slide — short punchy fragments render extra large, dense passages step down. Tweets, blockquotes, and images are never shredded into fragments; each renders as a single full-screen slide.

The controls

  • Click right / → / Space — next thought
  • Click left / ← — previous thought
  • P— autoplay on / off; 150–700 wpm via a slider in the control bar
  • Drag — select text without navigating; a 6px threshold tells selection apart from a click
  • Esc / F — close the room

The live teleprompter

Autoplay computes each slide's duration from its word count and your wpm setting, fills a thin timer bar along the bottom, and turns the deck for you. But a bar alone doesn't tell you wherein the text you should be — so the reader sweeps the text itself: unread words dim to near-black, the current word lights up white with a soft glow, read words stay lit. The sweep is synchronized to finish exactly when the slide advances.

The teleprompter sweep lighting up each word as it reads, with the slide timer filling along the bottom
the sweep in motion — each word lights as it's read, timed to the wpm slider

In the wild

A live article with the focus mode entry link
a live article, one click from silence
A single thought centered on a black screen
inside: one thought, dead-center, nothing else
Autoplay running at 350 words per minute
autoplay at 350 wpm, the deck turning itself

Things that were annoying to figure out

Sentence-aware chunking

Slides split on sentence boundaries with a regex that keeps punctuation, then pack to ~200 characters. Edge case: list numbering like “1.” parses as a full sentence and would become its own slide — the chunker detects bare-number “sentences” and glues them to the sentence that follows.

activeTab permission expiring mid-flight

In Manifest V3 the temporary access granted by activeTab can lapse across async boundaries. Focus Reader declares host_permissions and a statically registered content script instead, with the background worker messaging it — and warning gracefully if the tab predates the install.

Detached-DOM text extraction

Extracting innerText from cloned nodes in a detached fragment returns empty strings in Blink. The extractor reads the live DOM first and only recurses through child nodes for blocks containing embeds — skipping nested composites so a tweet quoted in a paragraph isn’t rendered twice.

Keeping the sweep honest

The word sweep’s interval is duration ÷ word count, so the highlight and the slide timer always land together — including on short slides where duration is clamped to a 1.2s minimum so a three-word slide doesn’t blink past.

Why certain things were built the way they are

New tab over injected overlay

An overlay inherits the host page's global styles, iframe blockers, and popup scripts. A chrome-extension:// tab is a fully sandboxed reading environment the site can't reach into.

Length-based type scaling

A fixed giant size looks great for single statements and terrible for paragraphs. The reader steps the scale down as slide length grows so text never wraps awkwardly.

No framework, no build step

Five files of vanilla JS. The extension loads unpacked straight from the repo, and a fix is always a small diff.

Install

  • 1. Clone or download the repo
  • 2. Open chrome://extensions, switch on Developer mode, click Load unpacked, and select the folder
  • 3. Open any article, click the toolbar icon, press P, and watch the words light up