Reading the web with half-understood words everywhere A developer built rabbitholes, a browser extension that lets users highlight text on any webpage to get instant explanations from Claude Haiku 4.5 via a shadow-DOM tooltip. Every word in the explanation is clickable, allowing users to dive deeper into topics without opening new tabs, and requests go directly from the browser to Anthropic and Brave Search APIs with no intermediary server. Every long article I read has the same problem: a word or concept I half-know that would make the whole paragraph click if I just looked it up. The tab-switch kills it. I open a new tab, read a bit, come back, and I've lost the thread. So I skip it, and the read stays shallow. I built rabbitholes to fix that for myself. Highlight any text on any page, and a shadow-DOM tooltip renders an explanation from Claude Haiku 4.5 next to your cursor. It doesn't inject anything into the host page — shadow DOM keeps the overlay isolated, so it can't break layouts or leak styles into the site you're reading. The part I use most: every word in the explanation is itself clickable. Drag across a phrase to select it. The tooltip rebuilds around that term, with the original context still in the background. You can go four hops deep into a Wikipedia article about Byzantine tax policy without opening a single new tab. A counter tracks how many hops you've taken; the trail is shareable. If I want more than Claude's training data, a globe icon re-answers the same query enriched with Brave Search results, with source chips I can click through. A pencil icon opens a free-form follow-up that inherits the current context so I don't have to re-explain where I am in a topic. The architecture constraint I cared most about: zero intermediary server. Requests go directly from the browser to api.anthropic.com and api.search.brave.com . Your Anthropic API key lives in chrome.storage.sync , encrypted, never touches my infrastructure. Manifest V3. js // The full round-trip is browser → Anthropic API, nothing in between const response = await fetch 'https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': apiKey, // from chrome.storage.sync 'anthropic-version': '2023-06-01', 'content-type': 'application/json' }, body: JSON.stringify { model: 'claude-haiku-4-5', max tokens: 512, messages } } ; Every answer ends with two suggested follow-on topics — the most interesting threads from the current explanation. One click and you're a hop deeper.