TODO: Write the privacy policy at
src/content/page-text/{en,de}/datenschutz/datenschutz.mdx(currently both stubs say "Coming soon!"). The following points need to be disclosed:
- Instagram embeds:
embed.jsis loaded from Meta, transmits IP address and browser data to Meta Platforms Ireland Ltd- OpenStreetMap map tiles: IP address is transmitted to
openstreetmap.orgtile servers- YouTube embeds (nocookie): Privacy-enhanced mode, but still connects to Google
- localStorage: Used for docs sidebar UI state (purely functional, no tracking)
- No analytics tools or tracking cookies in use
You need Node.js (v18 or later) and npm (bundled with Node.js).
- Download and install Node.js from nodejs.org (the LTS version is recommended).
- Verify the installation by opening a terminal and running:
Both commands should print a version number.
node -v npm -v
npm install
npm run devDev server runs at localhost:4321. To work on the admin UI locally, use:
npm run dev:adminThen visit localhost:4321/keystatic to open the Keystatic editor.
The repo produces two separate deploys from the same source tree. The public pages are pure static HTML/CSS/JS, which GitHub Pages serves for free — but the admin UI's /keystatic and /api/keystatic/* routes need server-side rendering (OAuth callback + GitHub commit handler), and GitHub Pages doesn't support that. The admin therefore runs on Cloudflare Pages with an SSR adapter, while the public site stays on GitHub Pages.
- Public site —
bears-space.de, deployed to GitHub Pages. Purely static, built withnpm run build. This is what end users see. - Admin UI —
admin.bears-space.de, deployed to Cloudflare Pages. Astrooutput: 'server'with the@astrojs/cloudflareadapter, built withnpm run build:admin(setsADMIN_BUILD=true). Serves/keystaticfor content editors.
Both builds read and write the same files under src/content/. Editors save through admin.bears-space.de/keystatic → Keystatic commits to main via a GitHub App → GitHub Actions rebuilds the public site. The admin site never maintains its own database.
One-time setup for the admin deploy (GitHub App, Cloudflare Pages project, DNS, access control) is documented in CLAUDE.md.
Content editors normally work through the admin UI at admin.bears-space.de/keystatic — Keystatic commits the edits for you. The steps below are for developers making content changes directly in the codebase.
When adding or editing content (events, projects, sponsors, page text, etc.), always work on a new branch — never commit directly to main.
- Create a new branch before making changes:
git checkout -b my-content-update
- Make your changes, then commit them:
git add . git commit -m "Add new event: Spring Workshop 2026"
- Push your branch:
git push -u origin my-content-update
- (Optional) If you're comfortable with GitHub, you can open a Pull Request on GitHub to have your changes reviewed before they go live.
Every save through the admin UI becomes a Git commit, so almost any editing mistake can be undone with one click:
- Go to github.com/bears-space/bears-space.github.io/commits/main.
- Find the bad commit (usually the most recent one).
- Click the
...menu on it → Revert. Merge the PR GitHub opens.
The site rebuilds from the previous state within a few minutes. See the If Something Breaks guide for details and when to escalate.
/
├── public/ # Static assets (favicon, etc.)
├── src/
│ ├── assets/ # Images organized by content type
│ │ ├── about-us/ # About page section images (our-mission/)
│ │ ├── default-images/ # Placeholder/fallback images
│ │ ├── docs/ # Images embedded in /docs MDX content
│ │ ├── events/ # Event cover images
│ │ ├── footer/ # Footer logo variants
│ │ ├── header/ # Header logo variants
│ │ ├── hero/ # Hero images by page (about-us/, events/, etc.)
│ │ ├── people/ # Person portraits (Faces of BEARS + Meet the Team)
│ │ ├── projects/ # Project cover images
│ │ ├── social-icons/ # Social platform icons (per-platform subfolder)
│ │ ├── sponsors/ # Sponsor logos by tier (diamond/, gold/, etc.)
│ │ └── whatIsBears/ # "What is BEARS" section images
│ │
│ ├── components/ # Astro components
│ │ ├── about/ # About page sections
│ │ ├── admin/ # Admin dashboard (admin build only)
│ │ ├── contact/ # Contact page components
│ │ ├── docs/ # Documentation page components
│ │ ├── events/ # Events page components
│ │ ├── landing/ # Homepage sections
│ │ ├── layout/ # Header, Footer, BackToTop
│ │ ├── mdx/ # Components for use in MDX content
│ │ ├── post/ # Event/project detail page components
│ │ ├── posts-catalog/ # Listing pages: cards, filters, pagination
│ │ ├── projects/ # Projects page components
│ │ ├── reusable/ # Generic reusable UI components
│ │ └── sponsors/ # Sponsors page components
│ │
│ ├── content/ # Astro content collections
│ │ ├── about-section-visibility/ # Per-section show/hide toggles (singleton)
│ │ ├── branding/ # Brand logos, favicon, OG default (singleton)
│ │ ├── contact-section-visibility/ # Per-section show/hide toggles (singleton)
│ │ ├── default-images/ # Fallback cover images (singleton)
│ │ ├── docs/ # Documentation pages (guides/, dev/)
│ │ ├── events/ # Event entries (.md/.mdx)
│ │ │ ├── en/ # English (default)
│ │ │ └── de/ # German translations
│ │ ├── hero-slides/ # Landing page hero carousel slides
│ │ ├── instagram/ # Instagram feed entries
│ │ ├── landing-section-visibility/ # Per-section show/hide toggles (singleton)
│ │ ├── media-section-visibility/ # Per-section show/hide toggles (singleton)
│ │ ├── page-text/ # Editable page copy by section
│ │ │ ├── en/ # English (default)
│ │ │ │ ├── 404/
│ │ │ │ ├── about-us/
│ │ │ │ ├── contact/
│ │ │ │ ├── datenschutz/
│ │ │ │ ├── events/
│ │ │ │ ├── imprint/
│ │ │ │ ├── landing/ # Homepage sections
│ │ │ │ ├── nav-links/
│ │ │ │ ├── media/ # Media page (media-title + media-categories)
│ │ │ │ ├── projects/
│ │ │ │ ├── site/
│ │ │ │ ├── sponsors/
│ │ │ │ └── *.mdx # Outlier singletons at locale root:
│ │ │ │ # hero, faq, social, donate, nav-columns
│ │ │ └── de/ # German translations (same structure)
│ │ ├── people/ # People (Faces of BEARS + project leads, locale-agnostic)
│ │ ├── projects/ # Project entries (.md/.mdx)
│ │ │ ├── en/ # English (default)
│ │ │ └── de/ # German translations
│ │ ├── social-platforms/ # Social platforms (Instagram, LinkedIn, YouTube, …)
│ │ ├── sponsors/ # Sponsor entries by tier (diamond/, gold/, etc.)
│ │ ├── sponsors-section-visibility/ # Per-section show/hide toggles (singleton)
│ │ ├── testimonials/ # Single list.mdx; quotes inline as quoteEn / quoteDe
│ │ └── config.ts # Collection schemas (Zod)
│ │
│ ├── keystatic/ # MDX component registry for the Keystatic editor
│ ├── layouts/ # Page layouts (BaseLayout, DocsLayout, PostLayout)
│ ├── pages/ # File-based routing (English, default locale)
│ │ ├── de/ # German locale wrappers (re-render root pages)
│ │ │ ├── index.astro # Each file imports + renders the root page
│ │ │ ├── events/[slug].astro
│ │ │ └── ... # about-us, projects, sponsors, contact, etc.
│ │ ├── docs/[...slug].astro # Documentation pages (English only)
│ │ ├── events/[slug].astro # Dynamic event detail pages
│ │ └── projects/[slug].astro # Dynamic project detail pages
│ ├── styles/ # Global CSS (Tailwind v4)
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Helpers (content queries, image loading, etc.)
│ └── __tests__/ # Vitest unit tests
│
├── astro.config.mjs
├── keystatic.config.ts # Keystatic CMS schema (mirrors src/content/config.ts)
├── wrangler.jsonc # Cloudflare Pages deployment config (admin build)
├── vitest.config.ts
├── CLAUDE.md # AI assistant instructions
├── package.json
└── tsconfig.json
The site supports two languages: English (default) and German.
- English pages live at the root URL (e.g.,
/about-us) - German pages live under
/de/(e.g.,/de/about-us)
Localized content collections (events, projects, page-text) use en/ and de/ subfolders. Collections that are language-neutral (people, sponsors, instagram, hero-slides, testimonials) stay flat — people keeps role translation inline via roleEn / roleDe, and testimonials keeps quote translation inline via quoteEn / quoteDe. If a German translation is missing, the English version is shown as fallback.
The language switcher in the header toggles between locales. Locale utilities live in src/utils/i18n.ts.
All images must be local files in src/assets/ subdirectories — remote URLs are not supported. Accepted formats: .jpg, .jpeg, .png, .webp. Hero slides also accept video formats (.mp4, .webm, .ogg). See the image system docs for details on the loading pipeline and fallback behavior.
All commands are run from the root of the project, from a terminal:
| Command | Action |
|---|---|
npm install |
Installs dependencies |
npm run dev |
Starts local dev server at localhost:4321 |
npm run dev:admin |
Dev server with Keystatic admin UI (visit /keystatic) |
npm run build |
Build public site to ./dist/ |
npm run build:admin |
Build admin variant for the Cloudflare Pages deploy |
npm run preview |
Preview your build locally, before deploying |
npm test |
Run unit tests once |
npm run test:watch |
Run unit tests in watch mode |
npm run typecheck |
TypeScript type check (tsc --noEmit) |
npm run compress-images |
Compress images in a path (also runs in pre-commit) |
npm run astro ... |
Run CLI commands like astro add, astro check |
npm run astro -- --help |
Get help using the Astro CLI |