{"id":48217620,"url":"https://github.com/bmmmm/bitcircus101","last_synced_at":"2026-04-04T19:01:39.951Z","repository":{"id":283718590,"uuid":"952696214","full_name":"bmmmm/bitcircus101","owner":"bmmmm","description":"website repo of bitcircus101","archived":false,"fork":false,"pushed_at":"2026-03-29T12:54:29.000Z","size":1668,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-03-29T13:47:02.273Z","etag":null,"topics":["bonn","commnuity","coworking-space","hackpace"],"latest_commit_sha":null,"homepage":"https://bitcircus101.de","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bmmmm.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":"CONTRIBUTING.md","funding":"funding.json","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{}},"created_at":"2025-03-21T18:06:13.000Z","updated_at":"2026-03-28T01:49:39.000Z","dependencies_parsed_at":"2025-03-21T19:31:50.519Z","dependency_job_id":"f7e51d4a-eed4-4a6b-8159-242b53b58b79","html_url":"https://github.com/bmmmm/bitcircus101","commit_stats":null,"previous_names":["bmmmm/bitcircus101"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bmmmm/bitcircus101","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmmmm%2Fbitcircus101","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmmmm%2Fbitcircus101/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmmmm%2Fbitcircus101/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmmmm%2Fbitcircus101/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bmmmm","download_url":"https://codeload.github.com/bmmmm/bitcircus101/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmmmm%2Fbitcircus101/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31409471,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T10:20:44.708Z","status":"ssl_error","status_checked_at":"2026-04-04T10:20:06.846Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bonn","commnuity","coworking-space","hackpace"],"created_at":"2026-04-04T19:01:39.183Z","updated_at":"2026-04-04T19:01:39.928Z","avatar_url":"https://github.com/bmmmm.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bitcircus101\n\n[bitcircus101](https://bitcircus101.de) is a tech and creative space located in Bonn, Germany.\n\nThis repository contains the website for bitcircus101.\n\n## Branches\n\n| Branch | Purpose |\n|--------|---------|\n| `feat/…` | Feature branches — all changes land here first, then merge to `main` via PR; delete after merge |\n| `main` | Integration branch (no direct pushes for normal work; see [CONTRIBUTING.md](CONTRIBUTING.md)) |\n| `live` | Production branch — served by GitHub Pages. CI actions commit generated files here |\n\n## Project structure\n\n```\nindex.html                  Main landing page\nevents.html                 Events page (loads events-data.json)\ndonations.html              Donation / funding page (loads funding.json)\nraum-nutzen.html            Room usage info\nimpressum-datenschutz.html  Legal / privacy\ndankedankedanke.html        Thank you page\n\nascii/index.html            Internal ASCII playground (/ascii/) — noindex, not in nav; strict system monospace\n\nincludes/\n  site-header.html          Shared \u003cheader\u003e (nav); inlined into pages by build:layout\n  site-footer.html          Shared \u003cfooter\u003e; inlined into pages by build:layout\nscripts/inject-layout.mjs   Writes header/footer into the six layout HTML files\n\nstyle.css                   Global styles (terminal theme, dark bg, green accent)\nmain.js                     Frontend: nav, carousel, map, events preview, footer\n\nevents.js                   Frontend: fetches events-data.json, renders event cards, tag filtering\ncalendars.json              Calendar source definitions (ICS URLs, names, flags)\nfunding.json                Current funding percentage (generated by CI)\n\nscripts/\n  sync-events.mjs           Fetches ICS from Nextcloud, generates events-data.json + feed.xml\n\ntests/\n  site.spec.js              Playwright end-to-end tests (~20 tests × 2 browsers)\n  sync-events.spec.mjs      Unit tests for ICS parser and RRULE expansion (22 tests)\nplaywright.config.js        Playwright config (Chromium, Mobile Chrome)\n\n.github/workflows/\n  ci.yml                    PR checks: layout sync + unit tests (no Playwright)\n  deploy.yml                Test on main → deploy to live\n  sync-events.yml           Calendar sync (every 30 min)\n  release.yml               Create release (manual, calver tags)\n  update-funding.yml        Funding level update (manual)\n  sitemap.yml               Sitemap generation (on push to live)\n\nllms.txt                    LLM-friendly site summary (llms.txt standard)\nchangelog.md                Release history\nrobots.txt                  Crawler rules (allows AI bots, blocks /ascii/)\n```\n\n## Generated files\n\nThese files are created/updated by CI actions on the `live` branch. Seed values are tracked on `main` so local dev and E2E tests work without running CI first:\n\n| File | Generated by | Schedule |\n|------|-------------|----------|\n| `events-data.json` | `sync-events.yml` | Every 30 minutes |\n| `feed.xml` | `sync-events.yml` | Every 30 minutes |\n| `sitemap.xml` | `sitemap.yml` | On push to `live` |\n| `funding.json` | `update-funding.yml` | Manual trigger |\n\n## CI / GitHub Actions\n\n### Deploy pipeline (`deploy.yml`)\n\n```\nmain (push) → Unit tests → Sync script → Playwright E2E → Deploy to live\n```\n\nEvery push to `main` triggers:\n1. **Layout sync check:** `scripts/inject-layout.mjs` must not change any committed `*.html`\n2. **Unit tests:** ICS parser, RRULE expansion, date parsing (node:test)\n3. **Sync script:** Generates `events-data.json` for E2E tests to use\n4. **E2E tests:** Playwright across 2 browsers (~20 tests × Chromium + Mobile Chrome)\n5. **Deploy** (only if all tests pass): Syncs site files from `main` to `live`\n\nPRs get lightweight CI via [`ci.yml`](.github/workflows/ci.yml): same layout check + unit tests (no Playwright). See [CONTRIBUTING.md](CONTRIBUTING.md).\n\nNothing reaches production without passing all tests first.\n\n### Sync calendar events (`sync-events.yml`)\n\n- **Trigger:** Cron every 30 minutes + manual\n- **Branch:** Checks out `live`, pulls scripts/config from `main`\n- **What it does:**\n  1. Runs `scripts/sync-events.mjs` (Node 22, zero dependencies)\n  2. Fetches ICS feeds from all calendars defined in `calendars.json`\n  3. Parses VEVENT entries, expands RRULE recurrences (weekly, monthly with BYDAY/BYSETPOS)\n  4. Filters out internal/blocker events and past events\n  5. Generates `events-data.json` with `lastSync` timestamp (max 40 cards, 120-day horizon)\n  6. Generates `feed.xml` (RSS 2.0, primary calendar only, max 15 items)\n  7. Commits and pushes to `live` with retry logic\n\n### Create release (`release.yml`)\n\n- **Trigger:** Manual (`workflow_dispatch`)\n- **Branch:** `main`\n- **Versioning:** [CalVer](https://calver.org/) — `v2026.03.28` (`.N` suffix for same-day releases)\n- **What it does:**\n  1. Determines next calver tag (checks for existing same-day tags)\n  2. Collects commits since last tag, drops CI bot / merge commits\n  3. Groups by [conventional commit](https://www.conventionalcommits.org/) prefix (Features, Fixes, Styling, Docs, Maintenance, Other)\n  4. Creates git tag and GitHub Release with auto-generated notes\n  5. Prepends release to `changelog.md` and commits to `main`\n\nReleases are **decoupled from deploys** — every merge to `main` deploys automatically, but releases are created manually when enough changes have accumulated.\n\n### Update funding level (`update-funding.yml`)\n\n- **Trigger:** Manual (`workflow_dispatch`) with `percent` input (0-100)\n- **Branch:** `live`\n- **What it does:** Writes `funding.json` with the given percentage, commits to `live`\n\n### Generate sitemap (`sitemap.yml`)\n\n- **Trigger:** On push to `live` + manual\n- **Branch:** `live`\n- **What it does:**\n  1. Uses `cicirello/generate-sitemap` to scan all HTML files\n  2. Strips Google verification files from the sitemap\n  3. Commits `sitemap.xml` to `live`\n\n## Calendar system\n\n### Data sources (`calendars.json`)\n\n| ID | Name | Nextcloud instance | Primary | In RSS |\n|----|------|--------------------|---------|--------|\n| `bitcircus` | bitcircus101 | nc.6bm.de | yes | yes |\n| `datenburg` | Datenburg e.V. | cloud.datenb.org | no | no |\n\nTo add a new calendar, add an entry to `calendars.json` — no code changes needed.\n\n### How events get tagged\n\nTags are resolved in priority order:\n\n1. **Explicit `#hashtags`** in the Nextcloud event description (e.g. `#workshop #hardware`)\n2. **ICS CATEGORIES** field (set in Nextcloud calendar UI)\n3. **Keyword auto-detection** from title + description (fallback)\n\nIf no tags match, the event gets `#community` as default.\n\n### Filtering\n\n- Events with \"blocker\" or \"interne veranstaltung\" in the title are excluded\n- Only future events are shown (up to 120 days ahead)\n- Max 30 events per calendar, 40 total\n\n### Frontend (`events.js`)\n\n- Loads `events-data.json`, falls back to live ICS fetch if JSON unavailable\n- Renders event cards grouped by month\n- Dynamic tag filter bar (OR logic: any matching tag shows event)\n- Source badge for non-primary calendar events\n- Permalink anchors with smooth scroll\n\n## Local development\n\n**Site chrome (nav + footer):** Edit [`includes/site-header.html`](includes/site-header.html) and [`includes/site-footer.html`](includes/site-footer.html), then run `npm run build:layout` and commit the partials plus updated `*.html`. Documented in [CONTRIBUTING.md](CONTRIBUTING.md) and [CLAUDE.md](CLAUDE.md).\n\nStatic site — open HTML files directly or use any local server:\n\n```sh\npython3 -m http.server 8080\n```\n\n### Running tests\n\nContributors only need the quick check — see [CONTRIBUTING.md](CONTRIBUTING.md).\n\n```sh\nnpm install\nnpm run test:quick         # unit tests only (~100ms, no browser needed)\nnpm test                   # full suite (unit + E2E, used by CI)\nnpm run test:e2e           # only Playwright E2E\nnpm run test:headed        # E2E with browser window\nnpm run test:ui            # Playwright UI mode\n```\n\n### Test coverage\n\n**Unit tests** (`tests/sync-events.spec.mjs` — 22 tests, ~100ms):\n\n| Area | What is tested |\n|------|---------------|\n| `parseDate` | All-day dates, datetime with timezone, UTC, null inputs |\n| `nthWeekday` | Nth weekday calculation, month overflow |\n| `expandRRule` | BYSETPOS + classic BYDAY, weekly, COUNT, EXDATE exclusions |\n| `parseICS` | Single events, all-day, TZID params, line folding, recurring BYSETPOS |\n| `clean` | ICS character unescaping (`\\n`, `\\,`, `\\;`) |\n\n**E2E tests** (`tests/site.spec.js` — ~20 tests × 2 browsers):\n\n| Area | Tests | What is tested |\n|------|-------|---------------|\n| Home page | 3 | Title + heading, CTAs, carousel + map interaction |\n| SEO / Meta | 2 | All meta tags on home, subpage descriptions + keywords |\n| Privacy | 1 | No Google Fonts (HTML + network) |\n| Navigation | 2 | Desktop nav + routing, mobile hamburger toggle |\n| Events page | 1 | Title, list, subscribe, RSS, back link |\n| Events content | 2 | Tags + filtering + month groups, sync status |\n| Donations | 1 | Title, consent banner dismiss |\n| Raum nutzen | 1 | Title, CTA, structured data |\n| Impressum | 1 | Title + back link |\n| Danke page | 1 | Title, noindex, content, back link |\n| Terminal theme | 1 | Dark bg, monospace, no inline styles |\n| JS errors | 6 | All 6 pages free of console errors |\n| Internal links | 1 | Crawls all pages, verifies every internal link resolves |\n| Accessibility | 1 | Aria-labels, alt texts, landmark roles |\n\nBrowsers: Chromium, Mobile Chrome (Pixel 5).\n\n### Running calendar sync locally\n\n```sh\nnode scripts/sync-events.mjs\n```\n\nThis fetches live ICS data and writes `events-data.json` + `feed.xml` to the project root.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbmmmm%2Fbitcircus101","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbmmmm%2Fbitcircus101","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbmmmm%2Fbitcircus101/lists"}