Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 6 additions & 9 deletions pcd-website/src/components/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
24 changes: 24 additions & 0 deletions pcd-website/src/components/PartnerCards.astro
Original file line number Diff line number Diff line change
@@ -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;
---

<div class="partner-grid">
{partners.map((partner) => {
const card = partner.data.partnerCard;
if (!card) return null;
return (
<section class="partner-card">
<p><Image src={card.logo} alt={partner.data.title} /></p>
<p>{card.summary}</p>
<p><a href={kitHref(partner.id)}>Learn more about {partner.data.title} →</a></p>
</section>
);
})}
</div>
6 changes: 4 additions & 2 deletions pcd-website/src/config/organizer-kit-nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const TOP_LEVEL: ReadonlyArray<string | { page: string }> = [
{ page: 'peer-support-sessions' },
{ page: 'code-of-conduct' },
{ page: 'faq' },
{ page: 'partners' },
{ page: 'about-processing-foundation' },
{ page: 'contact' },
{ page: 'documents' },
Expand All @@ -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<KitNavNode[]> {
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]));
Expand Down
8 changes: 7 additions & 1 deletion pcd-website/src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,23 @@ 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
// top level's own order comes from TOP_LEVEL in config/organizer-kit-nav.ts.
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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions pcd-website/src/content/organizer-kit/partners.md
Original file line number Diff line number Diff line change
@@ -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.
37 changes: 32 additions & 5 deletions pcd-website/src/layouts/DocsLayout.astro
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -53,9 +59,13 @@ const permalink = new URL(Astro.url.pathname, Astro.site ?? Astro.url.origin).hr
<main id="main" tabindex="-1" class="docs-shell__main">
<article class:list={['docs-prose', { 'docs-article--with-toc': hasToc }]}>
<div class="docs-article__header">
{eyebrow && <p class="prose__eyebrow">{eyebrow}</p>}
<div class="docs-title-row">
<h1>{heading ?? title}</h1>
{backLink
? <BackButton href={backLink.href} label={backLink.label} />
: eyebrow && <p class="prose__eyebrow">{eyebrow}</p>}
<div class:list={['docs-title-row', { 'docs-title-row--logo': headingLogo }]}>
<h1>{headingLogo
? <Image class="docs-heading-logo" src={headingLogo} alt={heading ?? title} />
: heading ?? title}</h1>
{markdown && editHref && (
<DocsPageActions markdown={markdown} permalink={permalink} editHref={editHref} />
)}
Expand All @@ -69,6 +79,23 @@ const permalink = new URL(Astro.url.pathname, Astro.site ?? Astro.url.origin).hr
<Footer />
</BaseLayout>

<style>
.docs-title-row--logo {
row-gap: var(--sp-6);
margin-bottom: var(--sp-6);
}

.docs-heading-logo {
display: block;
width: auto;
height: 48px;
max-width: 100%;
object-fit: contain;
object-position: left;
margin: 0;
}
</style>

<script is:inline>
(() => {
const toggle = document.querySelector('.docs-nav-toggle');
Expand Down
17 changes: 15 additions & 2 deletions pcd-website/src/pages/organize/[...slug].astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import Icon from '../../components/Icon.astro';
import { getCollection, render } from 'astro:content';
import type { GetStaticPaths } from 'astro';
import DocsLayout from '../../layouts/DocsLayout.astro';
import { flattenKitNav, getKitNav } from '../../config/organizer-kit-nav';
import { flattenKitNav, getKitNav, kitHref } from '../../config/organizer-kit-nav';
import { ZINE_SUBMIT_URL } from '../../config';
import ZineGrid from '../../components/ZineGrid.astro';
import { ogImagePath } from '../../lib/og-image.mjs';
import PartnerCards from '../../components/PartnerCards.astro';

export const getStaticPaths: GetStaticPaths = async () => {
const entries = await getCollection('organizerKit', (entry) => !entry.data.draft);
Expand All @@ -17,6 +18,11 @@ export const getStaticPaths: GetStaticPaths = async () => {
};

const { entry } = Astro.props;
const headingLogo = entry.data.partnerCard?.logo;
const partners = entry.id === 'partners'
? (await getCollection('organizerKit', (partner) => !partner.data.draft && !!partner.data.partnerCard))
.sort((a, b) => a.data.order - b.data.order || a.data.title.localeCompare(b.data.title))
: [];
const { Content, headings } = await render(entry);

// Prev/next follow the sidebar's reading order rather than the raw collection
Expand All @@ -32,12 +38,18 @@ const SUBMIT_PAGES = new Set(['zines/zine-library']);
const markdownSuffix = SUBMIT_PAGES.has(entry.id)
? `\n\n[Submit a zine](${ZINE_SUBMIT_URL})\n`
: '';
const markdown = `# ${entry.data.title}\n\n${entry.body?.trim() ?? ''}\n${markdownSuffix}`;
const partnerMarkdown = partners.map((partner) => {
const card = partner.data.partnerCard!;
return `\n\n## ${partner.data.title}\n\n${card.summary}\n\n[Learn more about ${partner.data.title} →](${kitHref(partner.id)})`;
}).join('');
const markdown = `# ${entry.data.title}\n\n${entry.body?.trim() ?? ''}\n${markdownSuffix}${partnerMarkdown}`;
---

<DocsLayout
title={entry.data.title}
ogImage={new URL(ogImagePath('organizer-kit', entry.id, import.meta.env.BASE_URL), Astro.site ?? Astro.url.origin).href}
headingLogo={headingLogo}
backLink={headingLogo ? { href: '/organize/partners/', label: 'Back to Partners' } : undefined}
description={entry.data.description ?? `${entry.data.title} — the Processing Community Day Organizer's Kit.`}
eyebrow={entry.data.section ?? "Organizer's Kit"}
headings={headings}
Expand All @@ -47,6 +59,7 @@ const markdown = `# ${entry.data.title}\n\n${entry.body?.trim() ?? ''}\n${markdo
>
<Content />
{entry.id === 'zines/zine-library' && <ZineGrid />}
{entry.id === 'partners' && <PartnerCards partners={partners} />}

<a class="docs-back-to-top" href="#main">
<Icon name="arrow-up" class="docs-back-to-top__icon" />
Expand Down
53 changes: 53 additions & 0 deletions pcd-website/src/styles/prose.css
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,59 @@
color: var(--color-text);
}

.docs-prose .partner-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
gap: var(--spacing-md);
margin: var(--spacing-lg) 0;
}

.docs-prose .partner-card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
padding: var(--spacing-lg);
border: 1px solid var(--color-border);
border-radius: 6px;
}

.docs-prose .partner-card > p:first-child {
margin-top: 0;
}

.docs-prose .partner-card > p:first-child img {
display: block;
width: auto;
height: 30px;
max-width: 100%;
object-fit: contain;
object-position: left;
margin: 0;
}

.docs-prose .partner-card p:last-child {
margin-top: auto;
margin-bottom: 0;
padding-top: var(--spacing-sm);
}

.docs-prose .partner-card p:last-child a::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
}

.docs-prose .partner-card:hover {
border-color: var(--color-primary);
}

.docs-prose .partner-card:has(a:focus-visible) {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
}

.placeholder-note {
border: 1px dashed var(--color-border);
border-radius: 6px;
Expand Down