← Back to site

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`.