A WebExtension that detects whether a page is mostly light or dark. Mostly-light pages are darkened automatically by inverting color lightness while preserving hue where practical. The toolbar button persists a per-site override.
Option 1, temporary add-on:
- Open Firefox.
- Go to
about:debugging#/runtime/this-firefox. - Click Load Temporary Add-on....
- Select this repository's
manifest.json.
Option 2, web-ext:
npm install
npm run run:firefox- Prepare the Chrome build directory with
npm run stage:chrome. - Open
chrome://extensions. - Enable Developer mode.
- Click Load unpacked and select
build/chrome.
- Automatic mode samples visible viewport backgrounds to classify a page.
- The extension runs at
document_startand reuses the last same-origin inverted/original result to reduce navigation flicker before full detection completes. - If the page is mostly light, a root
invert(1) hue-rotate(180deg)filter is applied to the page. - Images, videos, canvases, iframes, objects, embeds, and explicit exception elements receive the same filter again so they render close to their original appearance.
- Open shadow roots get a copy of the exception rule so media inside web components is restored too; roots are discovered at activation and watched for as the page changes.
- Click the toolbar button to open a popup menu.
- The popup can disable/enable the extension globally.
- The popup keeps the global enable/disable control separate from Auto Mode and current-site controls.
- Auto Mode chooses an automatic direction: darken mostly-light pages (Dark) or lighten mostly-dark pages (Light).
- Auto Mode includes a threshold slider for choosing how bright a page must be before automatic inversion applies.
- Per-site controls choose whether images/media are restored to original colors or inverted with the page.
- Per-site controls can add a direction-aware shadow around restored images and tune its strength for contrast.
- Per-site controls can enable custom brightness and contrast sliders while pages are inverted.
- Per-site controls can set the current site to Automatic, Always inverted, or Always original.
- Right-click the toolbar button and choose Reset site to Automatic to remove the site override.
- The toolbar badge shows
Awhen auto mode inverted the current page,I/Ofor per-site Always inverted/Always original overrides,OFFwhen globally disabled, and nothing when auto mode left the page unchanged. - The popup shows what auto mode decided for the current page while the site is in Automatic mode.
The root manifest.json is the Firefox development manifest. Browser-specific release manifests live in manifests/:
manifests/manifest.firefox.jsonuses Firefox MV3background.scripts, includes the Gecko IDsmart-dark-mode@alumino.us, and declares no data collection.manifests/manifest.chrome.jsonuses Chrome MV3background.service_worker._locales/provides English and Korean localization.
Install development dependencies first if you want web-ext commands:
npm installStage unpacked browser-specific build directories with:
npm run stage:firefox # build/firefox
npm run stage:chrome # build/chrome
npm run stage # both build directoriesPNG toolbar icons are generated from the SVG sources automatically during staging/builds with npm run icons.
Package release archives with web-ext:
npm run build:firefox # package build/firefox into dist/firefox/
npm run build:chrome # package build/chrome into dist/chrome/
npm run build # package both browser builds
npm run cleanThe package:* scripts are aliases for the corresponding build:* scripts.
web-ext helpers:
npm run lint:firefox
npm run run:firefox
npm run run:chrome
npm run package:firefox
npm run package:chromeweb-ext can build either manifest variant. Firefox packages are written under dist/firefox/; Chrome/Chromium packages are written under dist/chrome/.
GitHub Actions workflows live in .github/workflows/:
ci.ymlruns validation, Firefox linting, and both browser builds on pushes and pull requests.release.ymlis manually triggered. It builds both packages, creates a GitHub release with notes generated from git commits, and attempts to publish to AMO and the Chrome Web Store.
Store publishing is skipped with a workflow warning unless the relevant secrets are configured:
- Firefox:
FIREFOX_JWT_ISSUER,FIREFOX_JWT_SECRET - Chrome:
CHROME_EXTENSION_ID,CHROME_CLIENT_ID,CHROME_CLIENT_SECRET,CHROME_REFRESH_TOKEN
Open pages listen for extension storage changes, so global and per-site settings apply to already-open tabs without requiring the broad tabs permission.
Run validation and Firefox linting with:
npm run validate
npm run lint:firefoxManual fixtures are in test-fixtures/ (described for the default Dark direction):
light.htmlshould darken automatically.dark.htmlshould remain unchanged automatically.dynamic.htmlshould darken new content while active.mixed-media.htmlshould leave media elements visually unchanged.shadow-dom.htmlshould leave media inside open shadow roots visually unchanged, including a shadow root attached after load.
Switch the Auto Mode direction to Light to verify the inverse: dark.html lightens automatically and light.html stays unchanged.
This implementation intentionally uses a simple page-level CSS filter. It is broad and consistent, but exception elements may not always be perfectly restored, especially with nested filtered content, cross-origin iframes, CSS background images, or complex compositing.
MIT. See LICENSE.