From bb45b94039970bd570966266ca65222b1f8dd939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20de=20Courville?= Date: Thu, 1 Oct 2026 13:44:38 +0200 Subject: [PATCH] Add organizer partner pages with OpenProcessing Co-Authored-By: Claude Sonnet 5.5 --- AGENTS.md | 2 + pcd-website/src/components/Footer.astro | 15 +++--- pcd-website/src/components/PartnerCards.astro | 24 +++++++++ pcd-website/src/config/organizer-kit-nav.ts | 6 ++- pcd-website/src/content.config.ts | 8 ++- .../partner-pages/openprocessing.md | 33 ++++++++++++ .../src/content/organizer-kit/partners.md | 7 +++ pcd-website/src/layouts/DocsLayout.astro | 37 +++++++++++-- .../src/pages/organize/[...slug].astro | 17 +++++- pcd-website/src/styles/prose.css | 53 +++++++++++++++++++ 10 files changed, 183 insertions(+), 19 deletions(-) create mode 100644 pcd-website/src/components/PartnerCards.astro create mode 100644 pcd-website/src/content/organizer-kit/partner-pages/openprocessing.md create mode 100644 pcd-website/src/content/organizer-kit/partners.md diff --git a/AGENTS.md b/AGENTS.md index 03990690..212c34e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -63,6 +63,8 @@ No install needed — `open-location-code` is already available at `pcd-website/ - **Astro** owns routing, layouts, metadata, and static content pages. `src/layouts/BaseLayout.astro` provides the document shell; `MapLayout.astro`, `SiteLayout.astro`, and `DocsLayout.astro` provide the map, standard content, and Organizer Kit shells respectively. - The Organizer Kit introduction lives in `src/content/organizer-kit/getting-started/introduction.md`; `/organize/` redirects to its canonical `/organize/getting-started/introduction/` route. +- The Organizer Kit's `partners.md` supplies the “Discover our Partners” overview introduction. `PartnerCards.astro` renders published Organizer Kit entries with `partnerCard` frontmatter, sorted by `order` then title. Each partner detail page in `src/content/organizer-kit/partner-pages/` owns its card's `logo` (relative image path validated by Astro), `summary` (one paragraph leading with the participant or organizer benefit, followed by a short partner description); its title and route supply the card name and link. The same data supplies the overview's shared Markdown and the detail-page logo. Detail pages use `hideFromNav: true` to stay published while being omitted from both shared navigation trees and prev/next links; `draft: true` still excludes a page from build output and cards. Partner card styles live in `src/styles/prose.css`. +- Partner detail routes pass their logo to `DocsLayout` through its optional `headingLogo` prop; the image stays inside the h1 with the page title as alt text. Its optional `backLink` prop renders the shared `BackButton` above the heading in place of the eyebrow; partner pages link back to `/organize/partners/`. - **Vue** handles the interactive map UI as `client:only="vue"` island components. Map-specific interactive features belong in Vue; static site and Organizer Kit pages belong in Astro and Markdown content collections. The map filter drawer in `MapView.vue` filters clustered markers by date visibility (Future, Past, and Other/no date), format, and activity type; checkbox choices are ORed within a group and filter groups are ANDed together. Date categories are independent eye-icon toggle buttons with `aria-pressed`; all three may be hidden. All three start visible, “Show all” restores only date visibility, and clearing filters restores all categories and clears other criteria. Undated events belong to Other, while ongoing events remain Future through their final local calendar day. Whenever the last category is hidden, `EyelidBlink.vue` mounts a standalone CSS animation teleported over the full viewport: concave radial-gradient masks curve the top lid down at the sides and the bottom lid up at the sides; eyelids close for 420 ms, hold for 1500 ms, and reopen for 480 ms. The last hidden category is restored as reopening begins. The restored eye is already dilated as the screen reopens. After the full-screen blink completes, it holds dilation for 180 ms, then plays a 700 ms blink and squint while its pupil shrinks from 1.35× to normal size and its inward-thickened stroke recedes from a filled disk to the normal ring; this follow-up runs only in the visible filter drawer and is disabled for reduced motion. The animation blocks covered-screen interactions, allows Escape to finish early, finishes when the tab is hidden, cleans up timers/listeners, and uses an opacity fade for reduced motion. No canvas or p5.js dependency is used. The map filter drawer slides in and out from the left using the shared panel transition, with reduced-motion support. At 720px and below, the shared panel-opening easing stops without overshoot for both the filter drawer and event detail panel; desktop retains its overshoot. The filter drawer and event detail panel are mutually exclusive: opening either closes the other, with focus moving into the newly opened panel. Opening the filter drawer also closes any open Leaflet popup. While open, it hides the filter and language controls. On desktop, a double left caret closes the filter drawer from the right side of its header; the event detail panel uses a double right caret on the left side of its sticky header, with Share on the right. These replace the former edge tabs. Both close controls use matching inline SVG carets spaced 8 SVG units apart in an 18×16 viewBox. At 640px and below, the filter drawer fills the width and hides its desktop close button. On all screen sizes, the compact “n of m” count floats in a rounded pill at the bottom right above the footer actions. The options scroller has bottom padding so its final options can scroll clear of the pill. On all screen sizes, the footer places secondary “Clear Filters” on the left and primary “Done” on the right; Done closes the drawer and returns focus to the filter button while preserving selections. Zero results replace the desktop/mobile count with a localized, bold purple “No matching events” live status. On mobile, “Filter events” sits on the left of the top row beside the right-aligned “Back to map” button with a right-pointing arrow; desktop omits this text button. The date visibility legend reads “Show/hide events” beside Show all on every screen size. At 720px and below, the event detail panel shows Share on the right and “Back to map” on the left instead of the double-caret close button, matching this purple, semibold, 44px-minimum-height style with a left-pointing arrow. The filter footer and header row stay outside the flexing options scroller, with safe-area footer padding. Map-page root overscroll is suppressed, while the options scroller contains scroll chaining and retains local rubberbanding. Date controls offer Show all and individual visibility toggles. Individual labels describe the next action: “Hide” for visible categories and “Show” for hidden categories, localized in every supported language. diff --git a/pcd-website/src/components/Footer.astro b/pcd-website/src/components/Footer.astro index 6501a781..3310ad44 100644 --- a/pcd-website/src/components/Footer.astro +++ b/pcd-website/src/components/Footer.astro @@ -179,22 +179,19 @@ const UTILITY_LINKS = [ .sponsor-logo { display: inline-flex; align-items: center; - padding: 0.5rem 0.625rem; - border: 1px solid var(--color-border); - border-radius: 4px; - background: #fff; + padding: 0.5rem 0; } .sponsor-logo img { display: block; - width: 200px; + width: auto; max-width: 100%; - height: auto; + height: 30px; + object-fit: contain; } - .sponsor-logo:hover { - border-color: var(--color-primary); - color: var(--color-primary); + .sponsor-logo:hover img { + opacity: 0.8; } .social-row { diff --git a/pcd-website/src/components/PartnerCards.astro b/pcd-website/src/components/PartnerCards.astro new file mode 100644 index 00000000..448ec4c5 --- /dev/null +++ b/pcd-website/src/components/PartnerCards.astro @@ -0,0 +1,24 @@ +--- +import { Image } from 'astro:assets'; +import { kitHref, type KitEntry } from '../config/organizer-kit-nav'; + +interface Props { + partners: KitEntry[]; +} + +const { partners } = Astro.props; +--- + +
+ {partners.map((partner) => { + const card = partner.data.partnerCard; + if (!card) return null; + return ( +
+

{partner.data.title}

+

{card.summary}

+

Learn more about {partner.data.title} →

+
+ ); + })} +
diff --git a/pcd-website/src/config/organizer-kit-nav.ts b/pcd-website/src/config/organizer-kit-nav.ts index 67a633f6..ef0a60e7 100644 --- a/pcd-website/src/config/organizer-kit-nav.ts +++ b/pcd-website/src/config/organizer-kit-nav.ts @@ -32,6 +32,7 @@ const TOP_LEVEL: ReadonlyArray = [ { page: 'peer-support-sessions' }, { page: 'code-of-conduct' }, { page: 'faq' }, + { page: 'partners' }, { page: 'about-processing-foundation' }, { page: 'contact' }, { page: 'documents' }, @@ -49,13 +50,14 @@ function toPage(entry: KitEntry): KitPage { /** * Builds the sidebar tree by walking TOP_LEVEL and pulling matching entries out - * of the collection. Throws if an entry exists that TOP_LEVEL never places, so + * of the collection. Pages with `hideFromNav` remain published but are omitted. + * Throws if any other entry exists that TOP_LEVEL never places, so * adding a Markdown file without listing it here fails the build rather than * silently producing an unreachable page. */ export async function getKitNav(): Promise { const allEntries = await getCollection('organizerKit'); - const entries = allEntries.filter((entry) => !entry.data.draft); + const entries = allEntries.filter((entry) => !entry.data.draft && !entry.data.hideFromNav); const allIds = new Set(allEntries.map((entry) => entry.id)); const byId = new Map(entries.map((entry) => [entry.id, entry])); diff --git a/pcd-website/src/content.config.ts b/pcd-website/src/content.config.ts index a9985619..17f926c8 100644 --- a/pcd-website/src/content.config.ts +++ b/pcd-website/src/content.config.ts @@ -31,7 +31,7 @@ const pages = defineCollection({ const organizerKit = defineCollection({ loader: glob({ base: './src/content/organizer-kit', pattern: '**/*.md' }), - schema: z.object({ + schema: ({ image }) => z.object({ title: z.string(), // Sidebar grouping. `section` is the parent group's title; standalone // top-level pages omit it. `order` sorts a page within its section — the @@ -39,9 +39,15 @@ const organizerKit = defineCollection({ section: z.string().optional(), order: z.number().default(0), description: z.string().optional(), + partnerCard: z.object({ + logo: image(), + summary: z.string().min(1), + }).optional(), // Excludes the page from the sidebar and from build output entirely, for // TBD pages that shouldn't be publicly reachable yet. draft: z.boolean().default(false), + // Keeps a page published while omitting it from navigation and prev/next links. + hideFromNav: z.boolean().default(false), // Suppresses the "On this page" table of contents, for short pages whose // headings aren't worth navigating. hideToc: z.boolean().default(false), diff --git a/pcd-website/src/content/organizer-kit/partner-pages/openprocessing.md b/pcd-website/src/content/organizer-kit/partner-pages/openprocessing.md new file mode 100644 index 00000000..63869974 --- /dev/null +++ b/pcd-website/src/content/organizer-kit/partner-pages/openprocessing.md @@ -0,0 +1,33 @@ +--- +title: OpenProcessing +hideFromNav: true +order: 2 +description: One free month of OpenProcessing ProfessorPlus +draft: false +hideToc: true +partnerCard: + logo: ../../../images/openprocessing_logo.svg + summary: Get one free month of ProfessorPlus membership for your workshop during October 2026. +--- + +[OpenProcessing](https://openprocessing.org) is a social creative coding platform for creating, sharing, and exploring p5.js sketches together with the community. + +## Free ProfessorPlus for PCD Workshops + +OpenProcessing is offering **three months of free ProfessorPlus membership** to PCD workshop organizers to use [the classroom](https://openprocessing.org/teach) and [premium features](https://openprocessing.org/membership). All the participants get Plus+ automatically as well, as soon as they join your workshop on OpenProcessing. + +--- + +**Prepare.** Set up your sketches and tutorials before the workshop and add them to your class page before the workshop. Everything runs in the browser, ready to run when people arrive. + +**Share.** Share the invite URL with your participants to join the workshop. Participants see your prep, follow along step by step, and fork any templates with one click to make it their own. + +**Code together.** With ProfessorPlus, **collaborative code editing** lets you and your participants work on the same sketch in real time. Use it for live coding, pair programming, or group sketches. + +**Review.** Everyone's sketches are collected on the class page. When someone gets stuck, open their sketch and help them directly, and see what the room is making as you go. + +**Keep it as an archive.** After the event, the class page stays as a record of the workshop. It holds your materials and everyone's work, so participants can come back to it, and keep building. + +## Request Access + +Email [Sinan at OpenProcessing](mailto:sinan@openprocessing.org?subject=%5BPCD%5D%20OpenProcessing%20ProfessorPlus%20membership%20for%20my%20Processing%20Community%20Day%20event) to request the three-month ProfessorPlus membership for your PCD workshop. \ No newline at end of file diff --git a/pcd-website/src/content/organizer-kit/partners.md b/pcd-website/src/content/organizer-kit/partners.md new file mode 100644 index 00000000..93d20863 --- /dev/null +++ b/pcd-website/src/content/organizer-kit/partners.md @@ -0,0 +1,7 @@ +--- +title: Discover our Partners +description: Meet our partners and explore what they offer PCD organizers. +hideToc: true +--- + +Meet the platforms supporting our community and discover what they offer PCD organizers. diff --git a/pcd-website/src/layouts/DocsLayout.astro b/pcd-website/src/layouts/DocsLayout.astro index bd3363d0..0ca7f179 100644 --- a/pcd-website/src/layouts/DocsLayout.astro +++ b/pcd-website/src/layouts/DocsLayout.astro @@ -1,11 +1,13 @@ --- -import type { MarkdownHeading } from 'astro'; +import type { ImageMetadata, MarkdownHeading } from 'astro'; +import { Image } from 'astro:assets'; import BaseLayout from './BaseLayout.astro'; import Header from '../components/Header.astro'; import Footer from '../components/Footer.astro'; import DocsSidebar from '../components/DocsSidebar.astro'; import DocsTableOfContents from '../components/DocsTableOfContents.astro'; import DocsPageActions from '../components/DocsPageActions.astro'; +import BackButton from '../components/BackButton.astro'; import { getKitNav } from '../config/organizer-kit-nav'; import '../styles/base.css'; import '../styles/docs.css'; @@ -16,8 +18,12 @@ interface Props { ogImage?: string; /** Rendered above the h1 as the section label. */ eyebrow?: string; + /** Parent navigation shown in place of the eyebrow. */ + backLink?: { href: string; label: string }; /** Page heading. Falls back to `title` when omitted. */ heading?: string; + /** Optional logo displayed within the h1, using the heading as alt text. */ + headingLogo?: ImageMetadata; headings?: MarkdownHeading[]; /** Suppresses the "On this page" table of contents. */ hideToc?: boolean; @@ -27,7 +33,7 @@ interface Props { editHref?: string; } -const { title, description, ogImage, eyebrow, heading, headings = [], hideToc = false, markdown, editHref } = Astro.props; +const { title, description, ogImage, eyebrow, backLink, heading, headingLogo, headings = [], hideToc = false, markdown, editHref } = Astro.props; const hasToc = !hideToc && headings.filter((heading) => heading.depth === 2).length > 1; const nav = await getKitNav(); @@ -53,9 +59,13 @@ const permalink = new URL(Astro.url.pathname, Astro.site ?? Astro.url.origin).hr
- {eyebrow &&

{eyebrow}

} -
-

{heading ?? title}

+ {backLink + ? + : eyebrow &&

{eyebrow}

} +
+

{headingLogo + ? + : heading ?? title}

{markdown && editHref && ( )} @@ -69,6 +79,23 @@ const permalink = new URL(Astro.url.pathname, Astro.site ?? Astro.url.origin).hr