chrome extension · dark theme engine
Dark mode that beats the site to the paint.
DarkShift applies a dynamic dark theme to any page — without the white flash every other dark-mode extension makes you sit through. Two-layer engine, per-site cache, zero telemetry.
typical dark-mode extension
document_start vs. waiting for the page — measured, not marketing.
how it works
Two layers, in order
Most dark-mode extensions wait for the page to render, then repaint it — that's the flash. DarkShift splits the work into a layer that's cheap enough to run before anything is visible, and a layer that takes its time to get colors right.
Instant Shell
A coarse dark layer applied synchronously at document_start, before first paint.
< 5ms
Precise Theme
Transforms actual CSS colors while preserving hue, replaces the shell once ready.
< 300ms p95
measured, not claimed
What the engine actually costs
Every number below is a budget enforced in CI, not an aspiration. Per-site theme results are cached, so repeat visits skip straight to Instant Shell speed.
Dark paint — cached siterevisit, theme already computed
< 50ms
Dark paint — first visitp95, cold theme computation
< 300ms
Live setting changesslider → repaint
< 100ms
zero telemetry
zero network requests
per-site exclude list
Manifest V3
install
Not on the Web Store yet
DarkShift is distributed directly for now — Chrome won't install a downloaded .zip in one click, so it's a short manual load:
Download the .zip above and unzip it anywhere on disk.
Open chrome://extensions in a new tab.
Turn on Developer mode (top-right toggle).
Click Load unpacked and select the unzipped folder.
changelog
What changed
0.2.0
2026-09-28
current
Fixed
- Dark text stayed dark on design-token sites. A colour kept in a CSS custom property was always darkened, whichever way the page used it — so text set through
var(--text) became dark-on-dark and vanished. The engine now infers what each custom property is for from how the page consumes it: lightened when it feeds color/fill/stroke, darkened when it feeds a background or a border. A property used both ways gets a second, lightened twin, and foreground declarations are rewritten to read that one. Definition chains (--a: var(--b)) inherit the role.
- Enabled / Mode in “All sites” scope only affected the current site. The Scope select steered the sliders alone; the mode always wrote a per-site entry.
- Changing the global settings needed a manual reload. A write from the Options page reached storage but no open tab. The background now broadcasts the change, and tabs apply it themselves — switching to Off strips the injected styles without a reload at all.
- Removing a site in Options left its stale theme cached. The cache entry is invalidated together with the setting.
- Report problem still opened a 404. The 0.1.2 fix pointed the button at a repository that is not public. It now opens a report page on this landing. The site and version travel in the URL fragment, which browsers never send to a server — so composing a report tells the server nothing about where you were.
Perf gate re-run with a design-token fixture (300 custom properties + var() usages): 1.1ms against the 300ms first-visit budget.
0.1.2
2026-08-20
Fixed
- Report problem opened a GitHub 404. The issue URL pointed at a placeholder organisation that does not exist.
- Enabled / Mode changes needed a manual refresh. Two code paths re-registered the content script at the same time; one rejected with a duplicate-ID error and aborted the message router before it could tell the tab to reload. Registration runs are now serialized and can no longer reject into the router.
- Filter mode ignored the sliders until a reload. The mode baked brightness and contrast into its CSS as literals and dropped saturation, warmth and dim entirely. Both modes now drive the same custom properties, so a slider repaints immediately.
- Filter mode left backgroundless pages white. A page that sets no background of its own gave the invert nothing to act on — a no-white-flash violation. The root now gets an explicit ground colour that the site's own background still overrides.
- Dragging a slider exceeded the storage quota. Persisting on every input event blew past Chrome's sync-storage write limit and threw in the popup. Dragging now only previews; the write happens once, debounced, on release.
Added
- Extension icons (16/32/48/128) — earlier builds shipped none, so Chrome fell back to a grey placeholder tile.
0.1.1
2026-07-20
Added
- Site fix for habr.com: darken cross-origin cards, lighten
#333 text.
- External stylesheets that load after
document_start are now themed.
Fixed
- Snapshot self-poisoning: the engine's own stylesheets were included in the style snapshot and could trigger a native-dark false positive.
0.1.0
2026-07-09
Initial release
- Instant dark paint, dynamic theme generation, per-site theme cache, mutation pipeline for SPAs, popup and options UI, bundled site fixes.
для тестировщика
Что проверить в 0.2.0
Сборка прогнана через автотесты (движок, расширение, e2e в реальном Chrome) и перф-гейт. От тебя нужно только поставить и посмотреть глазами. Ничего собирать и запускать в терминале не надо.
Установка — 3 шага, около минуты
- Скачай DarkShift 0.2.0 кнопкой вверху страницы и распакуй архив в любую папку. Внутри должен лежать
manifest.json — это и есть расширение.
- Открой в Chrome адрес
chrome://extensions и включи справа вверху «Режим разработчика».
- Нажми «Загрузить распакованное расширение» и выбери папку, куда распаковал (ту, где лежит
manifest.json).
Иконку DarkShift удобно закрепить на панели: значок пазла → «булавка». В этой версии у расширения впервые своя иконка — тёмная плитка с бирюзовым кольцом. Видишь серый квадрат с буквой «D» — значит поставилась старая сборка.
Прицельно — то, что чинили в этой версии
- Главное. Тёмный текст на тёмном фоне. Раньше цвет, который сайт держит в CSS-переменной, всегда затемнялся — и подписи, заголовки, ссылки пропадали. Пройди ya.ru, dzen.ru, habr.com, google.com, avito.ru, ozon.ru, tbank.ru, wikipedia, youtube и смотри именно на читаемость текста: мелкие подписи, хлебные крошки, цены, текст на кнопках.
- Обратная сторона той же правки — не должно стать слишком светло: фон карточек, шапок и полей ввода обязан остаться тёмным, а не «выбелиться».
- Поставь Scope = All sites и переключи Mode или Enabled. Раньше это молча применялось только к текущему сайту; теперь меняется поведение по умолчанию для всех — проверь на второй, ещё не тронутой вкладке.
- Со Scope = This site тот же переключатель должен менять только этот сайт, соседние вкладки не трогать.
- Выключение (Enabled → off) применяется без перезагрузки страницы: тёмные стили просто снимаются.
- В настройках (правый клик по иконке → «Параметры») удали сайт из списка и вернись на него: тема должна пересчитаться заново, а не подтянуться старая.
- Кнопка Report problem переключает сайт в Filter и открывает страницу отчёта на этом лендинге — не 404, как было раньше. Там уже подставлены сайт и версия; текст надо скопировать кнопкой и прислать мне тем же каналом, которым пришла ссылка на установку.
- Подвигай слайдер долго, туда-сюда, секунд десять. На карточке расширения в
chrome://extensions не должно появиться красной кнопки «Ошибки». Появилась — открой и пришли текст.
Общий проход по сайтам
Дополнительно пройди десяток живых сайтов — например Gmail, GitHub, Wikipedia, YouTube, Amazon, Twitter/X, Reddit, StackOverflow, NYTimes, Notion.
- Страница тёмная сразу, без белой вспышки — особенно при первой загрузке и переходах по ссылкам.
- Нет светлых пятен, нечитаемого текста, странно инвертированных картинок и логотипов.
- Поля форм — поиск, логин — тёмные, а не белые.
- Сайт с собственной тёмной темой не становится «грязным» от двойного затемнения.
- На выключенном (excluded) сайте расширение никак не влияет на страницу.
- Страница настроек — правый клик по иконке → «Параметры» — показывает версию 0.2.0, список исключённых сайтов и кнопку «Remove», глобальные слайдеры сохраняются.
Чего в этой версии ещё нет — не надо репортить
- Картинки и логотипы расширение не разбирает по содержимому: тёмный логотип на прозрачном фоне может остаться плохо различимым.
- Стили из чужих доменов, которые браузер не даёт прочитать, темятся только грубым слоем — на таких блоках возможны светлые пятна.
- Точечных фиксов под конкретные сайты в этой сборке ровно столько же, сколько было: правка была общей, в движке. Сломанный сайт — повод прислать адрес, фикс делается отдельно.
Что писать в отчёте
На каждую проблему: адрес сайта + что не так + скриншот. Сломанные сайты на старте — это нормально, они чинятся точечными фиксами; главное их зафиксировать.