diff --git a/.env.template b/.env.template
new file mode 100644
index 0000000..5ad5032
--- /dev/null
+++ b/.env.template
@@ -0,0 +1 @@
+CALENDAR_API_KEY=
diff --git a/.prettierrc.mjs b/.prettierrc.mjs
index f084910..a56ce69 100644
--- a/.prettierrc.mjs
+++ b/.prettierrc.mjs
@@ -4,6 +4,14 @@ const config = {
tabWidth: 4,
semi: true,
singleQuote: true,
+ overrides: [
+ {
+ files: '*.md',
+ options: {
+ tabWidth: 2,
+ },
+ },
+ ],
};
export default config;
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..47f0f86
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,263 @@
+# AGENTS.md - Coding Agent Guidelines for VanPOP Website
+
+## Project Overview
+
+Static website for VanPOP (Vancouver) built with **Astro v5**, **TypeScript (strict)**, and **Tailwind CSS v4**. Deployed to Cloudflare Pages. Uses **pnpm** as the package manager.
+
+No client-side framework (React/Vue/Svelte) -- all components are `.astro` single-file components. Blog content is driven by Astro Content Collections using Markdown files with Zod-validated frontmatter.
+
+## Build / Lint / Test Commands
+
+| Command | Description |
+| -------------- | ----------------------------------------------------- |
+| `pnpm dev` | Start dev server (accessible on network via `--host`) |
+| `pnpm start` | Start dev server (localhost only) |
+| `pnpm build` | Type-check (`astro check`) then build static site |
+| `pnpm preview` | Preview the production build locally |
+
+### Type Checking
+
+```sh
+pnpm astro check
+```
+
+This is the primary code validation tool. It runs TypeScript checking for both `.ts` and `.astro` files. It is automatically run as part of `pnpm build`.
+
+### Linting
+
+- **No ESLint** is configured. Do not add ESLint rules or dependencies.
+- **Markdown linting** is handled in CI via `markdownlint-cli2`. Config in `.markdownlint.json` disables line length limits (MD013), inline HTML (MD033), and duplicate headings (MD024).
+
+### Testing
+
+There is **no test framework** configured (no vitest, jest, playwright, etc.). There are no test files in the repository. Do not create test infrastructure unless explicitly asked. Validation is done via `astro check` and CI build verification.
+
+## Code Style
+
+### Formatting (Prettier)
+
+Configured in `.prettierrc.mjs`:
+
+- **Indentation**: 4 spaces (also in `.editorconfig`)
+- **Semicolons**: Always (`semi: true`)
+- **Quotes**: Single quotes (`singleQuote: true`)
+- **Trailing commas**: ES5 style (`trailingComma: 'es5'`)
+
+### TypeScript
+
+- Extends `astro/tsconfigs/strict` with `strictNullChecks: true`
+- No path aliases -- use relative imports (`../components/Foo.astro`)
+- Use `import type` for type-only imports:
+
+ ```ts
+ import type { CollectionEntry } from 'astro:content';
+ ```
+
+- **Avoid `as` type assertions.** They bypass the type checker and can hide bugs. Instead:
+ - Use type guards (`if ('key' in obj)`, `typeof x === 'string'`, etc.)
+ - Use `satisfies` when you want to validate a value matches a type without widening
+ - Narrow types with conditional checks rather than casting
+ - If a cast is truly unavoidable (e.g. working around a third-party library's incomplete types), add a comment explaining why
+
+### Imports
+
+Order imports as follows (no enforced linter rule, but follow existing convention):
+
+1. Astro built-ins (`astro:content`, `astro:transitions`)
+2. Third-party packages (`@astrojs/rss`, etc.)
+3. Local components (relative paths)
+4. Types (using `import type`)
+
+Example:
+
+```ts
+import { getCollection } from 'astro:content';
+import BaseHead from '../components/BaseHead.astro';
+import Header from '../components/Header.astro';
+import type { CollectionEntry } from 'astro:content';
+```
+
+### Naming Conventions
+
+| Element | Convention | Example |
+| ------------------ | --------------------- | ------------------------------------------- |
+| Components | PascalCase `.astro` | `BlogPostsPreviewList.astro` |
+| Layouts | PascalCase `.astro` | `BlogPost.astro` |
+| Pages | kebab-case `.astro` | `get-involved.astro` |
+| Blog posts | `YYYYMMDD-slug.md` | `20260213-finding-your-farm.md` |
+| Exported constants | UPPER_SNAKE_CASE | `SITE_TITLE`, `INDEX_PAGE_BLOG_POSTS_LIMIT` |
+| Variables | camelCase | `postsLimit`, `maxTitleLength` |
+| CSS custom props | `--color-vp-*` prefix | `--color-vp-purple` |
+
+### Astro Component Structure
+
+Follow the standard Astro single-file component pattern:
+
+```astro
+---
+// 1. Imports
+import Footer from '../components/Footer.astro';
+import type { HTMLAttributes } from 'astro/types';
+
+// 2. Props interface
+interface Props {
+ title: string;
+ description?: string;
+}
+
+// 3. Destructure props and component logic
+const { title, description } = Astro.props;
+---
+
+
+
+
{title}
+
+
+
+
+
+```
+
+### Component Size & Refactoring
+
+Keep `.astro` components under **~400 lines**. When a component grows beyond that, refactor by extracting:
+
+- **Server-side logic** (data fetching, heavy computation) into `src/lib/*.ts` utility modules
+- **Client-side helper functions** (pure functions, constants) into `src/scripts/*.ts` modules, imported by the component's `
diff --git a/src/components/EventCalendarNav.astro b/src/components/EventCalendarNav.astro
new file mode 100644
index 0000000..ad2aaca
--- /dev/null
+++ b/src/components/EventCalendarNav.astro
@@ -0,0 +1,36 @@
+---
+// Month navigation header for EventCalendar
+---
+
+
+
+
+ Loading...
+
+
+
diff --git a/src/components/EventCardTemplate.astro b/src/components/EventCardTemplate.astro
new file mode 100644
index 0000000..ea1686d
--- /dev/null
+++ b/src/components/EventCardTemplate.astro
@@ -0,0 +1,31 @@
+---
+// HTML for event cards, cloned by client-side JS in EventCalendar
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/EventDetailModal.astro b/src/components/EventDetailModal.astro
new file mode 100644
index 0000000..bc86d05
--- /dev/null
+++ b/src/components/EventDetailModal.astro
@@ -0,0 +1,53 @@
+---
+// Modal overlay + dialog for displaying full event details.
+// Opened programmatically via openEventModal() from src/scripts/event-modal.ts.
+// Dismissed by clicking the overlay or pressing Escape.
+---
+
+
+