Ask AI for Help
Need help managing content? Copy the reference below and paste it into ChatGPT, Claude, or any other AI assistant, then ask your question. It contains a full summary of the content system so the AI can give you accurate answers.
AI Context Prompt
BEARS WEBSITE CONTENT SYSTEM REFERENCE
This is the content management system for the BEARS e.V. website, built with Astro.js. All content lives in src/content/ as MDX (.mdx) files. Each file has YAML frontmatter between --- delimiters.
The site supports English (default) and German (/de/ prefix). Content files in page-text are organized under en/ and de/ subfolders. The English version is shown automatically if a German translation is missing.
========================================
CONTENT COLLECTIONS
========================================
EVENTS (src/content/events/{en,de}/)
File naming: "<slug>.mdx" — filename is the slug, sort order comes from the `date` frontmatter field.
Frontmatter:
title: string (required)
description: string (required)
date: YYYY-MM-DD (required)
categoryEvent: one of "trade-fairs-and-conventions", "competitions-and-workshops", "kick-off-events", "other" (required)
coverImage: string (required) — path under src/assets/events/<slug>/, written as "/<slug>/<filename>", valid: .jpg .jpeg .png .webp .svg
coverImageDescription: string (optional) — single caption + credit field shown as a gradient strip below the cover image on /media (multiline ok)
// Cover always appears in the Events category on /media. Inline <Img /> blocks in the body also surface on /media unless `displayInMedia: false`.
isDraft: boolean (optional, default false) — hidden in production, visible in dev
Body: Markdown or MDX content for the detail page.
PROJECTS (src/content/projects/{en,de}/)
File naming: "<slug>.mdx" — filename is the slug, sort order comes from the `date` frontmatter field.
Frontmatter:
title: string (required)
description: string (required)
date: YYYY-MM-DD (required)
categoryProject: one of "experimental-rocketry", "science-and-experiments", "robotics", "other" (required)
coverImage: string (required) — path under src/assets/projects/<slug>/, written as "/<slug>/<filename>", valid: .jpg .jpeg .png .webp .svg
coverImageDescription: string (optional) — single caption + credit field shown as a gradient strip below the cover image on /media (multiline ok)
// Cover always appears in the Projects category on /media. Inline <Img /> blocks in the body also surface on /media unless `displayInMedia: false`.
isProjectCompleted: boolean (required)
isDraft: boolean (optional, default false)
displayMeetTheTeam: boolean (optional) — shows this project in the homepage "Meet the Team" section
person: string (optional) — REQUIRED when displayMeetTheTeam is true; slug of an entry in src/content/people/ (e.g. "jane-doe")
Body: Markdown or MDX content for the detail page.
SPONSORS (src/content/sponsors/)
Organized by tier folders: diamond/, platinum/, gold/, silver/, bronze/
File naming within tier: "<slug>.mdx" — filename is the slug, sort order comes from the `order` frontmatter field.
Frontmatter:
name: string (required)
order: number (required) — display order within the tier (lower = shown first)
logo: string (required) — path under src/assets/sponsors/<tier>/<slug>/, written as "/<slug>/<filename>", valid: .jpg .jpeg .png .webp .svg
alt: string (optional) — falls back to `name` at call sites
url: string (optional) — full website URL
bgColor: string (optional, default #ffffff) — logo card background color
HERO SLIDES (src/content/hero-slides/)
File naming: "<slug>.mdx" — filename is the slug, sort order comes from the `order` frontmatter field.
Frontmatter:
order: number (required) — display order (lower = shown first)
alt: string (required) — also used as the slide's slug in the admin UI
shownText: string (optional) — overlay text shown in the slide corner on the homepage hero; doubles as the caption strip on /media (single field for caption + photographer credit)
displayInMedia: boolean (default true) — when true and the slide is an image, it appears in the Hero category on /media
media: object (required) — conditional field with two branches:
{ discriminant: "image", value: "/<slug>/media.<ext>" } — .jpg .jpeg .png .webp .svg
{ discriminant: "video", value: "/<slug>/media.<ext>" } — .mp4 .webm .ogg
Files live at src/assets/hero/landingpage/<slug>/media.<ext> (one subfolder per slide).
INSTAGRAM POSTS (src/content/instagram/)
File naming: filename is the slug (derived from the post URL). Sort order comes from the `date` frontmatter field.
Frontmatter:
url: string (required) — full Instagram post URL (also used as the slug)
date: YYYY-MM-DD (required)
isDraft: boolean (optional, default false)
Landing page shows the 3 most recent posts sorted by date.
PEOPLE (src/content/people/)
Locale-agnostic — one file per person; roles translate inline.
File naming: "name.mdx" (slug based on the person's name). Powers three surfaces from a single record: (1) Faces of BEARS grid, (2) projects' Meet the Team feature via projects.person reference, (3) landing-page Testimonials carousel (via a referencing entry in the `testimonials` collection).
Frontmatter:
name: string (required)
roleEn: string (required) — English role
roleDe: string (required) — German role
coverImage: string (required) — path under src/assets/people/<slug>/, written as "<slug>/<filename>", valid: .jpg .jpeg .png .webp .svg. The same portrait is reused across all three surfaces.
coverImageDescription: string (optional) — single caption + credit field shown over the portrait on /media (multiline ok; no effect on Faces/Meet/Testimonials cards)
showInFaces: boolean (default false, opt-in) — single consent toggle gating BOTH Faces of BEARS (About Us) and the People accordion on /media; off = appears in neither; on /media only people with a coverImage are shown
order: number (default 0) — sort order wherever the person is shown (Faces of BEARS, Media page); lower = first, ties break on slug
TESTIMONIALS (src/content/testimonials/list.mdx)
Single-entry content collection backing the landing-page Testimonials carousel. The MDX body is empty and not editable — only frontmatter is used. Managed in Keystatic as a drag-reorderable list (a singleton with fields.array); array order in the file IS the display order — no sort key, no per-item filenames.
Frontmatter:
items: array of objects, each with:
person: reference('people') (required) — slug of the person this quote is from
quoteEn: string (required) — English quote
quoteDe: string (required) — German quote
Query via getEntry('testimonials', 'list').
PAGE TEXT (src/content/page-text/)
Editable headings, descriptions, buttons, and structured data for every page section.
Files are organized under en/ and de/ locale subfolders.
Only frontmatter is used — the body is ignored.
Frontmatter fields:
title: string (required) — section heading
subtitle: string (optional) — secondary heading or label
description: string (optional) — paragraph text
seoDescription: string (optional) — meta description for search engines (~150 chars)
buttonText: string (optional) — primary button label (pair with buttonHref)
buttonHref: string (optional) — primary button URL (pair with buttonText)
secondButtonText: string (optional) — secondary button label (pair with secondButtonHref)
secondButtonHref: string (optional) — secondary button URL (pair with secondButtonText)
instagramButtonText: string (optional) — Instagram CTA button label (used in landing/latest-news)
ctas: array (max 4) — each: { title, description, href }
items: array of strings — plain list (used by contact/contact-info, donate, footer)
titledItems: array — each: { title, description? } — used by about-us/whats-in-it for the benefits grid
faqs: array — each: { question, answer } — answers support Markdown (bold, links, lists)
socialLinks: array — each: { platform, url, hoverColor? } — platform is a reference to the social-platforms collection (one content entry per platform, with its own iconFile and defaultHoverColor). Adding a new platform is editor-driven: create an entry in the Social platforms collection in the CMS and upload an icon — no code change needed.
navLinks: array — each: { label, href }
navColumns: array — each: { heading, href, links: [{ label, href }] }
mediaCategories: array — each: { id, label } — controls which categories appear on the Media page and in what order
Available ids: all (combined view), about-us, events, people, hero, projects, what-is-bears
Remove an entry to hide that category; reorder entries to change display order.
address: string — multi-line text block (footer address). Each line renders on its own line.
room: string — "Find Us" room label (large text next to the map on the About Us page, e.g. "Room F11 in ILR").
schedule: string — "Find Us" meeting time, rendered below the room label (e.g. "every Tuesday at 6 PM").
mapLat: number — "Find Us" map pin latitude (-90 to 90). Right-click a spot on google.com/maps to copy it.
mapLng: number — "Find Us" map pin longitude (-180 to 180). Second number in the same Google Maps popup.
tierDescriptions: object — sponsor tier descriptions, keyed by tier name: { diamond?, platinum?, gold?, silver?, bronze? } — each value is a short string shown above the logos for that tier.
accountHolder: string — donation: account holder name (donate card on sponsors page)
bankName: string — donation: bank name
iban: string — donation: IBAN for bank transfers
bic: string — donation: BIC/SWIFT code
reference: string — donation: suggested transfer reference
paypalUrl: string — donation: full PayPal donation URL (must be paired with paypalButtonText)
paypalButtonText: string — donation: PayPal button label (must be paired with paypalUrl)
rememberLabel: string — About Us "Find Us" small uppercase label above the room/schedule (e.g. "remember:")
emailLabel: string — Contact page heading on the email card (e.g. "Email")
addressLabel: string — Contact page heading on the address card (e.g. "Address")
mapLinkText: string — Contact page text for the link below the address that jumps to the map
followLabel: string — Contact page heading on the "Follow Us" / social-links card
showMoreText: string — Landing page Latest News mobile-only button that expands the list (e.g. "Show more")
showLessText: string — Landing page Latest News mobile-only button that collapses the list (e.g. "Show less")
orDividerText: string — Sponsors page small uppercase divider between the sponsor CTA and the donate card (e.g. "Or")
bankToggleText: string — Sponsors page label on the button that expands the bank transfer details
Page text folder structure (inside en/ and de/):
landing/ — homepage sections (what-is-bears, latest-news, meet-the-team, testimonials, become-sponsor)
about-us/ — about page (about-us-title, our-mission, whats-in-it, faq-crosslink, find-us, faces-of-bears — section heading text only; the people themselves live in src/content/people/)
events/ — events page (events-title, events-intro, events-crosslink, events-empty-state)
projects/ — projects page (projects-title, categories-intro, category-experimental-rocketry, category-science-and-experiments, category-robotics, projects-crosslink, projects-empty-state)
sponsors/ — sponsors page (sponsors-title, sponsors-intro, sponsor-tiers, sponsors-crosslink, become-sponsor-cta)
contact/ — contact page (contact-title, contact-info, contact-crosslink)
site/ — site-wide (metadata — default site title and description)
nav-links/ — flat nav-link lists (header — top nav; footer-bottom — copyright + legal links)
404/ — 404 page (not-found)
imprint/ — imprint page (imprint)
datenschutz/ — privacy policy page (datenschutz)
Outlier singletons at the locale root (one .mdx file each, not in a subfolder):
hero.mdx — landing hero (subtitle/seoDescription + ctas array of up to 4 cards)
faq.mdx — About Us FAQ (faqs array; answers support Markdown)
donate.mdx — Sponsors page donate card (description, items, accountHolder, bankName, iban, bic, reference, paypalUrl, paypalButtonText)
social.mdx — site-wide social media links (socialLinks array; each entry references an entry in the social-platforms content collection — icons are owned by that collection, not the mdx)
contact-details.mdx — site-wide email and multi-line address; read by the Contact page and the site footer
nav-columns.mdx — footer multi-column navigation (navColumns array)
Media page (split across two files in the media/ subfolder):
media/media-title.mdx — page header (title + subtitle + seoDescription + hero image + shownText)
media/media-categories.mdx — accordion category list (mediaCategories array)
Naming conventions:
-title suffix — page hero header (e.g. events/events-title)
-crosslink suffix — link banner to another page (e.g. events/events-crosslink)
-empty-state suffix — message when no items to display
category- prefix — project category description (e.g. projects/category-robotics)
Special behaviors:
{year} in nav-links/footer-bottom title is replaced with the current year at build time.
buttonText and buttonHref must both be provided for a button to appear. Same for secondButtonText and secondButtonHref.
paypalUrl and paypalButtonText must both be provided for the PayPal donate button to appear (same pairing rule).
titledItems (about-us/whats-in-it) renders each entry as a numbered card with a bold title and lighter description.
donate.mdx (at the locale root) is the direct-donations card on the Sponsors page (below the sponsor CTA, bridged by an "OR" divider). Bank block renders if any of accountHolder/bankName/iban/bic is set; each row only appears if its field is present. The items array renders as a value-props row with icons (icons hardcoded, labels editable).
sponsors/sponsor-tiers.mdx uses the tierDescriptions object (keyed by tier name: diamond/platinum/gold/silver/bronze) — each value renders above the logos for that tier on the sponsors page.
contact-details.mdx (at the locale root) owns the email and the multi-line address. Both the Contact page (email + address cards) and the site footer read from it, so editing it in one place updates both surfaces.
========================================
MDX COMPONENTS (for .mdx event/project posts)
========================================
Import all components at the top of any .mdx file:
import { Accordion, Button, Callout, Carousel, Center, ImageGrid, Img, Instagram, Marquee, YouTube, SideBySide, Left, Right } from '@mdx';
Unused imports are removed at build time — no performance cost.
ACCORDION
items: array of { title, subtitle? (small label), content (Markdown string) } (required)
mode: "single-closed" | "single-open" | "always-one" | "multi" — preset that controls how items open/close.
- "single-closed": all items start closed; clicking one closes any other (default-style).
- "single-open": first item starts open; only one open at a time; can close all.
- "always-one": first item starts open; only one open at a time; can NOT close all (always exactly one open).
- "multi": all start closed; multiple can be open simultaneously.
width: CSS string (default "100%")
Content field supports full Markdown: bold, links, lists, code.
Note: legacy props defaultOpen/allowCloseAll/allowMultiple still work in the Astro component for backwards compatibility, but `mode` is the preferred API and is the only Accordion control surfaced through the CMS.
BUTTON
content: string — button text (or use a slot)
href: string — if provided renders as <a> link, otherwise <button>
variant: "primary" | "secondary" | "inverse" (default "primary")
size: "standard" | "large" | "xlarge" (default "standard")
disabled: boolean (default false)
CALLOUT
title: string (optional bold heading)
Slot for body content (Markdown, nested components).
Renders as a glass-style card with gradient accent bar.
CAROUSEL
autoScroll: boolean (default false, pauses on hover)
autoScrollInterval: number in ms (default 5000)
showArrows: boolean (default true)
showDots: boolean (default true)
height: "sm" (300px) | "md" (400px) | "lg" (500px) | "xl" (600px) | "auto" (default "md")
Each direct child becomes a slide (typically <Img>). Supports swipe on mobile.
IMPORTANT: <Img> children must keep enableClickToEnlarge enabled (the default) — disabling it breaks the carousel layout.
CENTER
Wraps content in a centered flex container. Accepts a class prop.
IMG
src: imported image (required) — use: import myPhoto from '@assets/events/photo.jpg';
alt: string (required)
width: CSS string (default "100%")
sizes: string — responsive sizes hint
widths: number[] — srcset widths (default [240, 480, 720, 960, 1280, 1920, 2560])
class: string — extra Tailwind/CSS classes (e.g. "rounded-full shadow-lg"). If the class string contains a "rounded*" utility, the default rounded-lg is dropped so your override wins.
enableClickToEnlarge: boolean (default true) — click opens full-size modal
loading: "eager" | "lazy" (default "lazy")
fetchpriority: "high" | "low" | "auto"
fill: boolean — fill parent container (default false)
IMPORTANT: images must be imported from src/assets/, never remote URLs.
IMAGEGRID
images: array of { image, alt } (required) — `image` is an imported ImageMetadata, `alt` is the caption
cols: 2 | 3 | 4 | 5 | 6 (default 4) — desktop column count; mobile is always 2 cols
gap: "sm" | "md" | "lg" (default "md") — spacing between images
aspectRatio: "square" (default) | "native" — "square" crops every tile to 1:1 in a CSS Grid; "native" keeps the original aspect ratios in a CSS-columns masonry layout
enableClickToEnlarge: boolean (default true) — applies to every Img inside the grid
class: string — extra classes for the grid container
Each tile is rendered with the standard <Img> pipeline (responsive srcset, lazy loading, click-to-enlarge), so the same image rules apply.
INSTAGRAM
url: string (required) — full Instagram URL or shortcode
size: "small" (380px) | "medium" (460px) | "large" (540px) | "full" (100%) (default "medium")
MARQUEE
speed: number — pixels per second (default 50)
gap: CSS string (default "2rem")
pauseOnHover: boolean (default true)
height: "sm" (12rem) | "md" (16rem) | "lg" (20rem) | "xl" (24rem) (default "md")
direction: "left" | "right" (default "left")
Infinite horizontal scroll. Children are auto-duplicated to fill screen.
YOUTUBE
id: string (required) — video ID or full URL (youtube.com/watch?v=..., youtu.be/..., youtube.com/embed/..., youtube.com/shorts/..., etc.)
title: string — accessibility label (default "YouTube video")
width: CSS string (default "100%")
start: number — start time in seconds (only sent to YouTube when > 0)
class: string — extra Tailwind/CSS classes for the outer container
SIDEBYSIDE + LEFT + RIGHT
SideBySide: showDivider (boolean, default true) — vertical line between columns on desktop
Left / Right: centerVertical (boolean, default false) — vertically center shorter content
Stacks on mobile, side-by-side on large screens (50/50 split).
========================================
IMAGE RULES
========================================
All images must be local files in src/assets/ subdirectories. Events, projects, sponsors, and people use a per-slug asset layout — each entry gets its own subfolder whose name matches the entry's filename (without extension):
src/assets/events/<slug>/ — per-event cover + inline images
src/assets/projects/<slug>/ — per-project cover + inline images
src/assets/sponsors/<tier>/<slug>/ — per-sponsor logo (organized under the tier folder: diamond/, platinum/, gold/, silver/, bronze/)
src/assets/people/<slug>/ — per-person portrait (used by Faces of BEARS grid, Meet the Team, and Testimonials carousel)
src/assets/about-us/our-mission/ — about page mission section images
src/assets/whatIsBears/ — What is BEARS carousel images
src/assets/hero/landingpage/ — landing page hero slides (images and videos)
src/assets/hero/landingpage/logo/ — landing page hero logo
src/assets/hero/{about-us,contact,events,media,projects,sponsors}/ — sub-page hero images
src/assets/header/ — header logo
src/assets/footer/ — footer logo
src/assets/default-images/ — fallback images when no custom image is provided
Valid image formats: .jpg, .jpeg, .png, .webp, .svg
Valid video formats (hero slides only): .mp4, .webm, .ogg
Never use remote/external image URLs.
========================================
KEY CONVENTIONS
========================================
- isDraft: true hides events, projects, and Instagram posts in production (visible in dev).
- Sort order is controlled differently per collection: events/projects/instagram sort by the `date` frontmatter field; sponsors/hero-slides sort by the `order` frontmatter field; people sort by `order` in the Faces of BEARS grid (when showInFaces is true); testimonials sort by their own `order` field in the landing Testimonials carousel. Lower = shown first; ties fall back to the slug. No filename prefixes are used for ordering — slugs should be human-readable (e.g. `jane-doe.mdx`, not `01-jane-doe.mdx`).
- FAQ answers and Accordion content support Markdown: **bold**, [links](/path), - bullet lists.
- Sponsor tier is determined by folder name (diamond/, platinum/, gold/, silver/, bronze/), not frontmatter. Sponsor logos live under src/assets/sponsors/<tier>/<slug>/ — one folder per sponsor.
- Sponsor tier descriptions are set via the tierDescriptions object in sponsors/sponsor-tiers.mdx (keyed by tier name), not via separate per-tier files.
- The seoDescription field sets a page's <meta name="description"> tag. Keep it ~150 characters.
- For projects, if displayMeetTheTeam is true then the `person` reference must point to an existing entry in src/content/people/ (e.g. `person: "jane-doe"`).
- UI strings for events/projects pages (filter labels, sort options, status badges) are centralized in catalogUiStrings in src/utils/i18n.ts.
- UI strings for the media page ("This is" label, empty state) are in mediaUiStrings in src/utils/i18n.ts.
- Media page categories are configured via the mediaCategories field in the page-text file media/media-categories.mdx (under the media/ subfolder, edited in the CMS as the Media categories singleton). Category labels are set per locale in the content file, not in i18n.ts. The page header (title/subtitle/seoDescription/hero image) is its own file at media/media-title.mdx.
- Header navigation links are configured in the page-text file nav-links/header.mdx (navLinks field, edited as part of the Nav link lists collection in the CMS).
- Footer navigation columns are configured in the page-text file nav-columns.mdx (at the locale root, navColumns field, edited as the Footer navigation singleton in the CMS).
- Footer address is configured in contact-details.mdx (at the locale root) via the multi-line `address` field (each line in the YAML renders as its own line on the page); the same field drives the Contact page address card.
- Slugs for events, projects, docs, and nav-links must be lowercase kebab-case (letters, digits, and hyphens only) — Keystatic enforces this and validation at build time would reject anything else. Use `hackathon-2026.mdx`, not `Hackathon 2026.mdx`.