- Production: https://cc.bexelbie.com/ — latest stable release of the Compact Calendar
- Beta: https://beta-cc.bexelbie.com/ — tracks the
betabranch when it differs frommain
Compact Calendar is a web-based, year-at-a-glance planner inspired by DSri Seah's Compact Calendar. It's designed for planning questions that regular calendars handle poorly — which weeks are completely free, how holidays and trips overlap, or whether you can turn two public holidays into a long stretch away from work.
Instead of maintaining a separate spreadsheet, Compact Calendar reads your existing calendars (ICS files or URLs) and renders them into a single-page grid of continuous Monday–Sunday weeks. Up to six calendars are color-coded with a colorblind-safe palette, making it easy to see committed versus possible time at a glance and print or share the result.
For more background on why this exists, see the blog post.
- ISO 8601 week numbers, Monday start
- Two event bands: Committed (green) and Possible (yellow)
- Load events from ICS files or webcal/HTTPS URLs (e.g., iCloud published calendars)
- Built-in demo data to explore the calendar without your own files
- Country-selectable public holidays via Nager.Date API
- Share your calendar view via a URL — settings are encoded in the URL fragment and never sent to any server
- Font size controls and print-friendly layout
- All data stays in the browser (localStorage for preferences and caching)
- Server-side CORS proxy for fetching remote ICS URLs
- Frontend: Vanilla JavaScript, built with Vite
- API: Azure Function (Node.js) providing a CORS proxy at
/api/ics-proxy - Hosting: Azure Static Web Apps
- Tests: Vitest
npm install
npx vite # Dev server at http://localhost:5173
npx vitest run # Run tests
npx vite build # Build to dist/The Vite dev server includes a proxy at /api/ics-proxy for local development, mirroring the Azure Function in production.
src/
calendar-grid.js # Year grid generation (ISO 8601 weeks)
holidays.js # Holiday fetching, caching, country selection
ics-parser.js # ICS file parsing (VEVENT extraction)
renderer.js # DOM rendering, color precedence, event placement
main.js # App orchestration, UI controls, state management
share.js # URL-hash-based sharing (encode/decode config)
styles.css # Layout, colors, print styles
api/
src/functions/
ics-proxy.js # Azure Function: CORS proxy for ICS URLs
public/
green-sample.ics # Demo data: Committed events
yellow-sample.ics # Demo data: Possible events
staticwebapp.config.json # Azure SWA routing and auth config
test/ # Vitest test suites
index.html # Single-page app entry point
Hosted on Azure Static Web Apps with GitHub Actions CI/CD. Pushes to main trigger automatic builds and deployments. The site is available at cc.bexelbie.com.
The Azure infrastructure was created with the Azure CLI. To recreate from scratch:
# Create resource group
az group create \
--name rg-compact-calendar \
--location westeurope
# Create Static Web App (free tier)
az staticwebapp create \
--name compact-calendar \
--resource-group rg-compact-calendar \
--location westeurope
# Get the deployment token (store as AZURE_STATIC_WEB_APPS_API_TOKEN secret in GitHub)
az staticwebapp secrets list \
--name compact-calendar \
--resource-group rg-compact-calendar \
--query "properties.apiKey" -o tsv
# Configure custom domain
az staticwebapp hostname set \
--name compact-calendar \
--resource-group rg-compact-calendar \
--hostname cc.bexelbie.comThe custom domain requires a CNAME record pointing cc.bexelbie.com to the Static Web App's default hostname:
az staticwebapp show \
--name compact-calendar \
--resource-group rg-compact-calendar \
--query "defaultHostname" -o tsvThe GitHub Actions workflow (.github/workflows/deploy.yml) uses the AZURE_STATIC_WEB_APPS_API_TOKEN secret to authenticate deployments.
All preferences (country, calendar URLs, filter settings) are stored in your browser's localStorage. Nothing is sent to third parties or used for tracking.
Calendar URLs necessarily go through the server-side proxy because browsers won't fetch them directly (CORS). The proxy is a stateless pass-through — it does not persist calendar data, in the function or in your browser. Calendar URLs are sent via POST request body rather than query parameters so they are not captured in platform-level request logs. Error logging includes only the target hostname, never the full URL or authentication tokens. If your calendar URL contains authentication tokens (iCloud URLs do), understand that the proxy briefly sees them in transit.
Holiday data is fetched directly from Nager.Date and cached in your browser for 30 days.
