diff --git a/apps/site/.cspell.json b/apps/site/.cspell.json
index c1ed7d7f..01d8a88f 100644
--- a/apps/site/.cspell.json
+++ b/apps/site/.cspell.json
@@ -103,6 +103,8 @@
"wordmark",
"Wordmark",
"WORDMARK",
- "writeups"
+ "writeups",
+ "Tous",
+ "Jours"
]
}
diff --git a/apps/site/eslint-suppressions.json b/apps/site/eslint-suppressions.json
index de6f5dce..b9bb4868 100644
--- a/apps/site/eslint-suppressions.json
+++ b/apps/site/eslint-suppressions.json
@@ -381,14 +381,11 @@
}
},
"src/lib/events-loader.ts": {
- "@typescript-eslint/consistent-type-definitions": {
- "count": 1
- },
"@typescript-eslint/no-unnecessary-condition": {
"count": 15
},
"@typescript-eslint/no-unnecessary-type-assertion": {
- "count": 2
+ "count": 1
},
"@typescript-eslint/no-unsafe-argument": {
"count": 1
@@ -465,9 +462,6 @@
}
},
"src/pages/llms-full.txt.ts": {
- "complexity": {
- "count": 1
- },
"max-lines-per-function": {
"count": 1
},
diff --git a/apps/site/playwright.config.ts b/apps/site/playwright.config.ts
index 3098394a..ef349486 100644
--- a/apps/site/playwright.config.ts
+++ b/apps/site/playwright.config.ts
@@ -37,7 +37,7 @@ export default defineConfig({
{
name: 'ui-contracts',
testMatch:
- /(campaign-events|body-links|color-contracts|event-metadata|footer-colophon|header-current|not-found-search|print-layout|typography-contracts)\.spec\.ts$/,
+ /(campaign-events|body-links|color-contracts|event-metadata|footer-colophon|header-current|not-found-search|print-layout|program-scroll|typography-contracts)\.spec\.ts$/,
use: { ...devices['Desktop Chrome'] },
},
{
diff --git a/apps/site/public/programs/transit-mapper.webp b/apps/site/public/programs/transit-mapper.webp
new file mode 100644
index 00000000..50f5f7b7
Binary files /dev/null and b/apps/site/public/programs/transit-mapper.webp differ
diff --git a/apps/site/public/programs/week-without-driving.webp b/apps/site/public/programs/week-without-driving.webp
new file mode 100644
index 00000000..b620fb34
Binary files /dev/null and b/apps/site/public/programs/week-without-driving.webp differ
diff --git a/apps/site/src/components/programs/ProgramEventFeature.astro b/apps/site/src/components/programs/ProgramEventFeature.astro
new file mode 100644
index 00000000..a4cd6992
--- /dev/null
+++ b/apps/site/src/components/programs/ProgramEventFeature.astro
@@ -0,0 +1,78 @@
+---
+import {
+ programEventFeature,
+ programEventPayload,
+ selectProgramEvent,
+ type ProgramEvent,
+ type ProgramParticipation,
+} from '../../lib/program-events';
+
+interface Props {
+ program: ProgramParticipation;
+ events: ProgramEvent[];
+ headingId: string;
+}
+const { program, events, headingId } = Astro.props;
+const now = new Date();
+const panel = programEventFeature(
+ program,
+ selectProgramEvent(events, program.calendarSeriesUid, now),
+ now,
+);
+---
+
+
+
+
diff --git a/apps/site/src/components/programs/ProgramEventPanel.astro b/apps/site/src/components/programs/ProgramEventPanel.astro
new file mode 100644
index 00000000..cf62c8cc
--- /dev/null
+++ b/apps/site/src/components/programs/ProgramEventPanel.astro
@@ -0,0 +1,95 @@
+---
+import { Icon } from 'astro-icon/components';
+import {
+ programEventPanel,
+ programEventPayload,
+ selectProgramEvent,
+ type ProgramEvent,
+ type ProgramParticipation,
+} from '../../lib/program-events';
+
+interface Props {
+ program: ProgramParticipation;
+ events: ProgramEvent[];
+}
+
+const { program, events } = Astro.props;
+const now = new Date();
+const panel = programEventPanel(
+ program,
+ selectProgramEvent(events, program.calendarSeriesUid, now),
+ now,
+);
+const payload = programEventPayload(program, events);
+const venueAddress = [panel.venue?.address, panel.venue?.tentative ? '(tentative)' : undefined]
+ .filter(Boolean)
+ .join(' ');
+---
+
+
+
+
diff --git a/apps/site/src/components/programs/ProgramFeature.astro b/apps/site/src/components/programs/ProgramFeature.astro
new file mode 100644
index 00000000..b4219db0
--- /dev/null
+++ b/apps/site/src/components/programs/ProgramFeature.astro
@@ -0,0 +1,57 @@
+---
+import { Icon } from 'astro-icon/components';
+import type { CollectionEntry } from 'astro:content';
+import ProgramEventFeature from './ProgramEventFeature.astro';
+import { paths } from '../../lib/paths';
+import type { ProgramEvent } from '../../lib/program-events';
+
+interface Props {
+ program: CollectionEntry<'programs'>;
+ events?: ProgramEvent[];
+ next: { title: string; href: string };
+}
+
+const { program, events = [], next } = Astro.props;
+const { title, icon, summary, image, calendarSeriesUid } = program.data;
+---
+
+ {summary}{title}
+
+