bitcircus101 is a tech and creative space located in Bonn, Germany.
This repository contains the website for bitcircus101.
| Branch | Purpose |
|---|---|
main |
Source branch — development and PRs go here |
live |
Production branch — served by GitHub Pages. CI actions commit generated files here |
index.html Main landing page
events.html Events page (loads events-data.json)
donations.html Donation / funding page (loads funding.json)
raum-nutzen.html Room usage info
impressum-datenschutz.html Legal / privacy
dankedankedanke.html Thank you page
style.css Global styles (terminal theme, dark bg, green accent)
events.js Frontend: fetches events-data.json, renders event cards, tag filtering
calendars.json Calendar source definitions (ICS URLs, names, flags)
funding.json Current funding percentage (generated by CI)
scripts/
sync-events.mjs Fetches ICS from Nextcloud, generates events-data.json + feed.xml
tests/
site.spec.js Playwright end-to-end tests (204 tests × 3 browsers)
sync-events.spec.mjs Unit tests for ICS parser and RRULE expansion (22 tests)
playwright.config.js Playwright config (Chromium, Firefox, Mobile Chrome)
.github/workflows/
deploy.yml Test on main → deploy to live
sync-events.yml Calendar sync (every 15 min)
update-funding.yml Funding level update (manual)
sitemap.yml Sitemap generation (on push to live)
These files are created by CI actions and committed to the live branch only:
| File | Generated by | Schedule |
|---|---|---|
events-data.json |
sync-events.yml |
Every 30 minutes |
feed.xml |
sync-events.yml |
Every 30 minutes |
sitemap.xml |
sitemap.yml |
On push to live |
funding.json |
update-funding.yml |
Manual trigger |
main (push) → Unit tests → Sync script → Playwright E2E → Deploy to live
Every push to main triggers:
- Unit tests: ICS parser, RRULE expansion, date parsing (node:test, 22 tests)
- Sync script: Generates
events-data.jsonfor E2E tests to use - E2E tests: Playwright across 3 browsers (204 tests)
- Deploy (only if all tests pass): Syncs site files from
maintolive
Nothing reaches production without passing all tests first.
- Trigger: Cron every 30 minutes + manual
- Branch: Checks out
live, pulls scripts/config frommain - What it does:
- Runs
scripts/sync-events.mjs(Node 22, zero dependencies) - Fetches ICS feeds from all calendars defined in
calendars.json - Parses VEVENT entries, expands RRULE recurrences (weekly, monthly with BYDAY/BYSETPOS)
- Filters out internal/blocker events and past events
- Generates
events-data.jsonwithlastSynctimestamp (max 40 cards, 120-day horizon) - Generates
feed.xml(RSS 2.0, primary calendar only, max 15 items) - Commits and pushes to
livewith retry logic
- Runs
- Trigger: Manual (
workflow_dispatch) withpercentinput (0-100) - Branch:
live - What it does: Writes
funding.jsonwith the given percentage, commits tolive
- Trigger: On push to
live+ manual - Branch:
live - What it does:
- Uses
cicirello/generate-sitemapto scan all HTML files - Strips Google verification files from the sitemap
- Commits
sitemap.xmltolive
- Uses
| ID | Name | Nextcloud instance | Primary | In RSS |
|---|---|---|---|---|
bitcircus |
bitcircus101 | nc.6bm.de | yes | yes |
datenburg |
Datenburg e.V. | cloud.datenb.org | no | no |
To add a new calendar, add an entry to calendars.json — no code changes needed.
Tags are resolved in priority order:
- Explicit
#hashtagsin the Nextcloud event description (e.g.#workshop #hardware) - ICS CATEGORIES field (set in Nextcloud calendar UI)
- Keyword auto-detection from title + description (fallback)
If no tags match, the event gets #community as default.
- Events with "blocker" or "interne veranstaltung" in the title are excluded
- Only future events are shown (up to 120 days ahead)
- Max 30 events per calendar, 40 total
- Loads
events-data.json, falls back to live ICS fetch if JSON unavailable - Renders event cards grouped by month
- Dynamic tag filter bar (OR logic: any matching tag shows event)
- Source badge for non-primary calendar events
- Permalink anchors with smooth scroll
Static site — open HTML files directly or use any local server:
python3 -m http.server 8080npm install
npm test # all tests (unit + E2E headless)
npm run test:unit # only unit tests (ICS parser, fast)
npm run test:e2e # only Playwright E2E
npm run test:headed # E2E with browser window
npm run test:ui # Playwright UI modeUnit tests (tests/sync-events.spec.mjs — 22 tests, ~100ms):
| Area | What is tested |
|---|---|
parseDate |
All-day dates, datetime with timezone, UTC, null inputs |
nthWeekday |
Nth weekday calculation, month overflow |
expandRRule |
BYSETPOS + classic BYDAY, weekly, COUNT, EXDATE exclusions |
parseICS |
Single events, all-day, TZID params, line folding, recurring BYSETPOS |
clean |
ICS character unescaping (\n, \,, \;) |
E2E tests (tests/site.spec.js — 68 tests × 3 browsers = 204):
| Area | Tests | What is tested |
|---|---|---|
| Home page | 10 | Title, h1, ASCII art, carousel, support CTAs, contact, map |
| SEO / Meta | 10 | Description, canonical, OG tags, JSON-LD, keywords per page |
| Privacy | 3 | No Google Fonts (link tags, preconnect, network requests) |
| Navigation | 3 | Desktop links, mobile hamburger toggle, link navigation |
| Events page | 6 | Title, event cards load, subscribe buttons, RSS, linkup info |
| Events content | 7 | Tags present, filter works, reset, lastSync shown, month groups, Datenburg events |
| Donations | 3 | Title, consent banner, banner dismiss |
| Raum mieten | 6 | Title, contact CTA, map, JSON-LD, back link |
| Impressum | 2 | Title, back link |
| Danke page | 4 | Title, content, noindex, back link |
| Terminal theme | 3 | Dark background, monospace font, no inline styles |
| JS errors | 6 | All 6 pages free of console errors |
| Internal links | 1 | Crawls all pages, verifies every internal link resolves |
| Accessibility | 4 | Aria-labels, alt texts, footer role |
Browsers: Chromium, Firefox, Mobile Chrome (Pixel 5).
node scripts/sync-events.mjsThis fetches live ICS data and writes events-data.json + feed.xml to the project root.