/home/eraldo
Eraldo Hasanaj
Father, Husband, Founder, Software Developer and Gamer (when possible)
eraldo@hasanaj:~$
⌘K / Ctrl K
← back to blog

Reading Mode Torture Test

Jan 14, 2026·7 min read·series:ui-lab

This post is intentionally long.

It exists for two reasons:

  1. To make it obvious when a layout is too narrow.
  2. To test that reading mode truly feels like a full-window experience (including long scrolling).

If you’re reading this inside the site:

  • Press ⌘F / Ctrl F to enter reading mode.
  • Press Esc (or click the “exit” button) to leave.
  • Try the command bar: open "reading mode" (fuzzy match) or cd blog.

This is a sample post. Delete it when you’re done testing.


A quick terminal cheat sheet

# Navigation
./home
./blog
./about
./contact
 
# Move "up" contextually
cd..
 
# Go to a page
cd blog
cd about
 
# Open a post by fuzzy title
open "reading mode"
open reading
 
# Jump to a tag or series
cd "#react"
cd "series:stack-notes"

Readability: short lines vs long lines

A good reading layout should handle both:

  • Short, punchy lines that read like a terminal log.
  • Longer paragraphs that wrap comfortably without looking cramped.

Here’s a longer paragraph to test measure and rhythm. The goal is to avoid a “column inside a column” feeling, while still keeping the text legible. Reading mode should feel like a distraction-free page: no header chrome, no nav, just content. If this paragraph feels squeezed, the container is too narrow.

And here’s another paragraph with slightly different cadence. When you scroll, the header should stay out of the way, and the content should carry the page. If the background bleeds through or you can see underlying UI glow, the overlay isn’t truly opaque.


Code highlighting (small)

type CommandResult =
  | { type: 'navigate'; to: string }
  | { type: 'back' }
  | { type: 'error'; message: string }
 
export function resolve(cmd: string): CommandResult {
  const cleaned = cmd.trim()
  if (!cleaned) return { type: 'error', message: 'empty command' }
 
  if (cleaned === 'cd..') return { type: 'back' }
  if (cleaned === './blog') return { type: 'navigate', to: '/blog' }
 
  return { type: 'error', message: `unknown: ${cleaned}` }
}

Code highlighting (large)

This block is intentionally big to test scroll performance and readability.

import { useEffect, useMemo, useRef, useState } from 'react'
 
type SearchHit = {
  slug: string
  title: string
  summary: string
}
 
function normalize(input: string) {
  return input.toLowerCase().trim().replace(/\s+/g, ' ')
}
 
function score(title: string, query: string) {
  const t = normalize(title)
  const q = normalize(query)
 
  if (t === q) return 1000
  if (t.startsWith(q)) return 800
 
  const index = t.indexOf(q)
  if (index !== -1) return 650 - Math.min(index, 200)
 
  const tokens = q.split(' ').filter(Boolean)
  if (!tokens.length) return -Infinity
 
  for (const token of tokens) {
    if (!t.includes(token)) return -Infinity
  }
 
  return 450 + tokens.length * 25
}
 
export function FakeFuzzyFinder({ hits, onPick }: { hits: SearchHit[]; onPick: (slug: string) => void }) {
  const [query, setQuery] = useState('')
  const inputRef = useRef<HTMLInputElement>(null)
 
  useEffect(() => {
    inputRef.current?.focus()
  }, [])
 
  const results = useMemo(() => {
    if (!query.trim()) return hits.slice(0, 10)
 
    return hits
      .map((hit) => ({ hit, score: score(hit.title, query) }))
      .filter((entry) => entry.score > 0)
      .sort((a, b) => b.score - a.score)
      .slice(0, 10)
      .map((entry) => entry.hit)
  }, [hits, query])
 
  return (
    <div className="space-y-3">
      <input
        ref={inputRef}
        value={query}
        onChange={(event) => setQuery(event.target.value)}
        className="w-full rounded-md border px-3 py-2"
        placeholder="type to filter"
      />
 
      <ul className="space-y-2">
        {results.map((hit) => (
          <li key={hit.slug}>
            <button type="button" onClick={() => onPick(hit.slug)}>
              {hit.title}
            </button>
          </li>
        ))}
      </ul>
    </div>
  )
}

A table (for layout)

SignalWhat it meansExpected in reading mode
Background is solidYou can’t see the app behind✅ Yes
Full-window heightContent starts at viewport height✅ Yes
Scroll feels naturalLong content scrolls like a page✅ Yes
Exit is accessibleEscape + visible button works✅ Yes

Long scrolling section

Below is a long list of short “log-like” lines to force a lot of vertical scrolling.

Checkpoints

  • Checkpoint 001 — The CRT overlay shouldn’t leak through.
  • Checkpoint 002 — The exit button should stay reachable.
  • Checkpoint 003 — The text shouldn’t feel cramped.
  • Checkpoint 004 — Code blocks should remain readable.
  • Checkpoint 005 — The scroll should be smooth.
  • Checkpoint 006 — Headings should break the flow nicely.
  • Checkpoint 007 — Lists should have comfortable spacing.
  • Checkpoint 008 — Links should keep the teal accent.
  • Checkpoint 009 — Long words should wrap or overflow gracefully.
  • Checkpoint 010 — The viewport should feel “owned” by the post.

More checkpoints (intentionally repetitive)

  • Checkpoint 011 — Lorem ipsum but with purpose.
  • Checkpoint 012 — The design should remain calm.
  • Checkpoint 013 — The spacing should feel consistent.
  • Checkpoint 014 — The page should not jiggle.
  • Checkpoint 015 — Reading mode is about focus.
  • Checkpoint 016 — Find should not open the browser’s search UI.
  • Checkpoint 017 — Escape should always exit.
  • Checkpoint 018 — The overlay should be truly opaque.
  • Checkpoint 019 — The content should fill the width.
  • Checkpoint 020 — This post keeps going.

A lot of paragraphs

Paragraph 01. Building UI is mostly about removing friction. Every extra decision the reader has to make costs attention.

Paragraph 02. A “reading mode” is an explicit choice: prioritize the content and let everything else disappear.

Paragraph 03. The goal is not maximal minimalism; it’s useful calm.

Paragraph 04. When a layout is too narrow, the reader feels boxed in. When it’s too wide, the eye gets lost. The “right” width depends on the font, line-height, and how the rest of the page frames the content.

Paragraph 05. In a terminal-themed site, you want strong contrast, crisp type, and just enough accent color to guide attention.

Paragraph 06. Code blocks are the hardest part of typography. They have different rhythm, different semantics, and usually require horizontal space.

Paragraph 07. Lists and headings are the easiest part, but they reveal inconsistencies in spacing quickly.

Paragraph 08. If reading mode is full-screen, it must also be full-height. It should feel like its own page.

Paragraph 09. The background must be solid — translucent layers read as “modal on top of an app,” not “reading.”

Paragraph 10. This is the tenth paragraph. There will be more.

Paragraph 11. Scrolling is a physical action. If the page stutters, it’s immediately noticeable.

Paragraph 12. Performance issues often show up only when the content is long.

Paragraph 13. Rehype/Shiki output for large code blocks is a great stress test.

Paragraph 14. The exit affordance should always be visible.

Paragraph 15. Keyboard shortcuts are a power-user feature, but they must be discoverable.

Paragraph 16. Cursor focus should not jump around unexpectedly.

Paragraph 17. Reading mode should not fight other shortcuts (like the command bar’s ⌘K).

Paragraph 18. This is still intentionally verbose.

Paragraph 19. Almost there.

Paragraph 20. Keep scrolling.


Even more content

Subsection A

  • A1. A list item.
  • A2. Another list item.
  • A3. Yet another list item.

Subsection B

Here’s a blockquote to test spacing:

The best UI is the UI you don’t notice. The second best UI is the UI you can reason about.

Subsection C

And a final long-ish paragraph: if everything is working, this post should feel like it takes over the viewport in reading mode, scrolls like a real page, and exits instantly with Escape.


End

If you made it here, the scrolling test worked.