Merges overlapping Google Calendar events from multiple calendars into a single event with a gradient background. No more unreadable stacked events.
- Event merging — overlapping events become one block with a gradient showing all calendar colors
- Busy event support — "busy" events from shared calendars are merged by position
- Weekend highlighting — customizable colors for weekends in both the main and mini calendar
- Dark mode — adapts borders and weekend colors automatically
- 21 languages — Arabic, Chinese, English, French, German, Hindi, Italian, Japanese, Korean, Dutch, Polish, Portuguese, Romanian, Russian, Spanish, Thai, Turkish, Ukrainian, Vietnamese
- Zero data collection — everything runs locally, no external requests
macOS (Homebrew):
brew install avelino/calblend/calblend@betaWindows (Scoop):
scoop bucket add calblend https://github.com/avelino/scoop-calblend
scoop install calblend-betaWindows (Winget):
winget install avelino.CalBlend.BetaWindows (Chocolatey):
choco install calblend-betaLinux (AUR):
yay -S calblend-beta-binLinux (Snap):
snap install calblend --edgeLinux (Flatpak):
flatpak install run.avelino.calblendLinux (DEB/RPM) and direct download for all platforms: GitHub Releases
- Chrome Web Store (coming soon)
- Firefox Add-ons (coming soon)
Requirements: Node.js 22+, pnpm
pnpm install
pnpm dev # Chrome with hot reload
pnpm dev:firefox # Firefox with hot reload| Command | What it does |
|---|---|
pnpm dev |
Start dev server (Chrome) |
pnpm dev:firefox |
Start dev server (Firefox) |
pnpm build |
Production build (Chrome) |
pnpm build:firefox |
Production build (Firefox) |
pnpm zip |
Build + zip for Chrome Web Store |
pnpm zip:firefox |
Build + zip for Firefox Add-ons |
pnpm test |
Run tests |
pnpm test:watch |
Run tests in watch mode |
pnpm typecheck |
TypeScript type check |
- Fork the repo
- Create a branch (
git checkout -b my-feature) - Make your changes
- Run
pnpm typecheck && pnpm test— both must pass - Open a PR
- Tests required — if you change behavior, add or update tests
- TypeScript strict — no
any, nots-ignore - Keep it simple — this is a small extension, avoid over-engineering
- One concern per module — detection, grouping, merging, and rendering are separate for a reason
Open an issue with:
- Browser and version
- What you expected vs what happened
- A screenshot if possible
MutationObserver (debounced, filtered to calendar nodes)
-> restoreAll() # clean DOM before re-reading
-> findEvents(container) # detect events, extract metadata
-> groupEvents(events) # group by title, regroup busy by position
-> applyMerge(groups) # gradient on kept event, hide duplicates
-> colorWeekends() # optional weekend highlighting
The observer disconnects before applying DOM changes and reconnects after, preventing infinite loops. A restoreAll() call before each cycle ensures we always read clean DOM state.
Originally forked from @limonkufu.
