mdfocus
Я скімлю (читаю по діагоналі, вихоплюючи тільки «цікаве»). Довгий технічний текст відкриваю з наміром прочитати, а очі вже летять униз — «де тут цікаве», — і оцінне судження вимикається десь на другому екрані. Для статей це терпимо. Для першоджерел і документації, де половина сенсу в абзаці, який не кричить, — ні. mdfocus я зробив під рівно цю діру: рідер сирого markdown, який не дає тексту проскочити повз.
Три входи: вставити текст, завантажити .md, або кинути лінк на зовнішній .md через ?url=. Далі marked.js рендерить, а зверху лягає шар, який я назвав «токенами» — не LLM-токени і не auth-токени, а одиниці утримання уваги. Назва збігається з двома іншими «токенами», тож одразу уточнюю.
Що всередині
Line focus підсвічує вікно з п'яти блоків навколо рядка читання (по два з кожного боку). Решта тексту лишається з повним контрастом: підсвітка показує, де я зараз, а не карає все поза цією точкою. Progress rail і лічильник секцій по h1/h2 дають масштаб документа; спільний mandrock0-ambient.js reseed-иться лише на зміні секції, не на таймері.
Тепер ембіент реагує і на швидкість скролу. Коли я пролітаю текст, canvas приглушується аж до 25% opacity; після паузи починає повертатись. Швидкість — лише proxy для скіму, не детектор того, що я зрозумів абзац (було б зручно, але браузер такого не знає). При prefers-reduced-motion цю реакцію вимкнено.
Bionic reading теж дійшов до рідера: перемикач робить жирним початок слів у звичайному тексті, не чіпаючи код, заголовки й лінки. Він off за замовчуванням. Незалежного заміру, що такий прийом справді прискорює моє читання, у мене нема — тому не записую його в «доведені покращення», а лишаю ручкою, яку можна вимкнути за секунду. Вузька колонка на 58ch — друга така ручка: коли очі стрибають по рядку, прибираю зайву ширину, не переписуючи сам markdown.
Ще один токен майже невидимий: після скролу позиція зберігається локально для цього тексту. При наступному відкритті з'являється «продовжити читання», але рідер не перекидає мене туди сам — іноді я відкриваю документ саме з початку. Brown/pink noise лишився процедурним через Web Audio API, без аудіофайлів і без автозапуску. Налаштування й позиція живуть у localStorage; бекенд бачить тільки URL, якщо я завантажую зовнішній .md через проксі.
Що я викинув
Було мікро-поле рефлексії після секції — «запиши, що зрозумів». Воно щоразу розривало потік. Викинув 11.09, і ось правило, що з цього випало: токен не має переривати того, кого він тримає. Bionic reading у старій версії був у беклозі саме тому, що я не хотів вмикати спірний прийом усім підряд; тепер він повернувся як opt-in, а поле рефлексії — ні.
Бекенд: проксі й гард, який ніхто не просив
Браузер не вміє fetch-ити чужий .md без CORS, тож ?url= ходить через тонкий проксі на stdlib Python, 0 залежностей, 127.0.0.1:3466 за nginx. Сам проксі — одна функція, але SSRF-гард я туди вклав як окрему роботу: «сходи на будь-який URL від імені сервера» — класична діра у внутрішню мережу й cloud metadata.
Що закриває кожен пункт:
- тільки
http/https— відсікаєfile://,gopher://і решту схем, якими дістають локальні ресурси; - резолв хоста і відмова, якщо хоч одна адреса private/loopback/link-local/reserved/multicast/unspecified — стіна перед
169.254.169.254і внутрішніми IP; - конект на вже перевірений IP, не на ім'я — закриває DNS-rebind, те вікно між перевіркою і запитом, за яке домен встигає перепризначитись на localhost;
- редіректи не слідуються (3xx → 400) — інакше валідний публічний хост редіректить на внутрішній, і вся перевірка нічого не варта;
- 2 MB cap і 10 с таймаут — щоб проксі не став помпою.
Живе: tools.mandrock.me/mdfocus/. Репо, MIT: github.com/0mandrock1/mdfocus.