Standards

    Events & News pattern

    One module for event and news content, with latest + archive behavior.

    Example — listing (News/Events index page)

    This is what a News/Events index page renders — the future-events banner is deliberately not shown here; see the separate "Home page banner" example below for why.

    Latest entries are shown first in calendar order; older entries move into archive.

    Featured update

    News

    external article

    Community Grant Award Supports Rescue Operations

    March 1, 2026

    A regional grant will support vaccinations, transport, and placement assistance.

    Event

    Status: upcoming • Recurring

    Monthly Basket Raffle

    March 7, 2026 at 7:00 PM • Community Hall, 456 Oak Ave, Example City, ST

    First Saturday of every month — dog-themed gift baskets, tickets $5 each or 5 for $20.

    News

    local article

    Self-Hosted Video Example

    March 5, 2026

    Example of a news entry using videoSrc (a real, self-hosted mp4) instead of videoEmbed — prefer this whenever the source clip can be downloaded, since a self-hosted file always plays.

    News

    local article

    Community Partner Spotlight (Local Article)

    February 24, 2026

    Example of a news entry that links to a local article page hosted inside the site, with the source Facebook video embedded via videoEmbed instead of a static image.

    Archive (5)
    Event

    Status: upcoming

    Community Fundraiser

    Saturday, February 21, 2026 • Example City, ST

    One-day fundraiser supporting ongoing rescue operations.

    News

    external article

    Field Rescue Team Spotlight

    February 10, 2026

    How retrieval specialists and fosters coordinate rescue-to-home outcomes.

    Event

    Status: upcoming • Recurring

    Second Weekend of the Month Foster Fair

    January 10, 2026 at 6:00 PM • Riverside Park, Example City, ST

    Runs on the second weekend each month (Saturday + Sunday).

    Event

    Status: upcoming • Recurring

    Biweekly Supply Drive

    January 10, 2026 at 5:00 PM • Warehouse Pickup Hub, Example City, ST

    Every 2 weeks donation sorting and supply drop-off.

    News

    external article

    Volunteer Recruitment Drive

    January 10, 2026

    New volunteer openings for transport, events, and foster support.

    Follow Us on Social Media

    Stay up to date with new arrivals, events, and rescue stories.

    Home page banner (separate from the listing above)

    showFutureEventsBanner renders EventBanner — a dismissible announcement banner for upcoming events. It's a home-page-only element, shown here in isolation (no listing, no archive) to make that clear — never enable it on the same instance used for a News/Events index or archive page, as shown above. In practice, a real site computes this same event list from its own entries via EventsNewsSection's internal toBannerEvents() helper (by passing showFutureEventsBanner on the home page's own instance); the array below is constructed directly only so this example can render the banner with nothing else underneath it.

    📣 Friday Adoption Meet-and-Greet
    Thu, Aug 206:00 PM8:00 PM123 Main St, Example City, ST
    📣 Monthly Basket Raffle
    Sat, Sep 512:00 PM4:00 PMCommunity Hall, 456 Oak Ave, Example City, ST

    Structured content blocks (cardMode="full")

    The richer contentBlocks renderer used on detail-style cards, instead of the summary-only "index" mode shown above.

    Event

    Status: upcoming

    Community Fundraiser

    Saturday, February 21, 2026 • Example City, ST

    One-day fundraiser supporting ongoing rescue operations.

    Join us for a neighborhood fundraiser supporting emergency foster placements and transport costs.

    What to expect

    • Adoption information booth and volunteer sign-ups
    • Pet supply donation drop-off lane
    • Local sponsor raffle every hour

    Bring donation receipts

    Some partner organizations offer matching gifts when donation receipts are submitted within 7 days.

    Image layout (imageLayout="alternating")

    imageLayout independent of cardMode: entries with an imageSrcrender image-beside-text, flipping sides every other card. An entry without an image still renders as a plain text-only card. imageLayout="side" is also available for image-always-on-the-left, and the default "auto" preserves the existing top-image/text-only behavior shown in the sections above.

    Gift baskets set out for a fundraiser raffle
    Event

    Status: upcoming • Recurring

    Monthly Basket Raffle

    March 7, 2026 at 7:00 PM • Community Hall, 456 Oak Ave, Example City, ST

    First Saturday of every month — dog-themed gift baskets, tickets $5 each or 5 for $20.

    News

    local article

    Community Partner Spotlight (Local Article)

    February 24, 2026

    Example of a news entry that links to a local article page hosted inside the site, with the source Facebook video embedded via videoEmbed instead of a static image.

    Placeholder thumbnail (ignored on the detail page below since videoEmbed is set)
    News

    external article

    Field Rescue Team Spotlight

    February 10, 2026

    How retrieval specialists and fosters coordinate rescue-to-home outcomes.

    Event

    Status: upcoming • Recurring

    Friday Adoption Meet-and-Greet

    January 2, 2026 at 1:00 AM • 123 Main St, Example City, ST

    Recurring weekly event every Friday evening.

    A dog waiting to be adopted at a meet-and-greet event

    Standard

    - Use `EventsNewsSection` with a unified `event | news` entry contract.

    - Always keep full history in data; render latest entries + archive.

    - Keep entries in calendar order (newest first for combined feed).

    - Show event-only links (`Open in Maps`, `Add to Google Calendar`, `Share event`) only for events.

    - Do not duplicate event actions for news-only items.

    - Event-action parity rule: every migrated site news/events implementation must expose these three event actions.

    - News entries may link to external URLs or local article routes (for in-site articles).

    - For event detail pages, use index + detail routing (for example `/events` plus `/events/my-event-slug`).

    - Use lightweight metadata labels for status/type context on cards.

    - `showFutureEventsBanner` is home-page-only — enable it only on the `EventsNewsSection` instance mounted on the home page (see the "Home page banner" example above), never on a News/Events index or archive page instance.

    - Use `eventDetailsBasePath` when events should auto-link to local event detail routes.

    - Recurring event banner cards should resolve to local detail links when `eventDetailsBasePath` is configured.

    - For index pages, use `cardMode="index"` so cards stay summary-only (no body/highlights/images).

    - Recurring events are supported via `event.recurrence` (weekly, biweekly, monthly by day, monthly nth-week weekday).

    - Keep `EnvironmentBanner` and event banner separated (never merged into one banner).

    - If both render on a page, order must be `EnvironmentBanner` first, then event banner.

    - Render event banner only when there are active future events; if none exist, do not render event banner row.

    - Banner placement standard: render both in page flow directly under the header; do not mount globally in shared layout.

    - Content seeding standard: do not add fake/sample events in migrated production-like sites; start with empty entries until real events exist.

    - Optional featured card and archive controls are built in (`showFeatured`, archive props).

    - `socialCta` is a required governance default (not opt-in) for sites on the current generation — populate it from `siteConfig.social.*` so a "follow us" row renders via `SocialFollowCta`; never hand-roll a page-local version.

    - Set `entry.videoSrc` (a real, self-hosted mp4 URL) on any entry to render a native `<video>` inline on the detail page (and full-mode card) instead of a static image — takes priority over `videoEmbed` when both are set. Pair it with `entry.imageSrc`, which doubles as the poster frame, and optional `videoAspectRatio` (`"landscape"` default, `"portrait"` for a vertical Reel-shaped clip). Card thumbnails (news index, homepage highlight) still use `imageSrc`/`imageAlt`. Prefer this over `videoEmbed` whenever the source clip can be downloaded (e.g. via `yt-dlp` from a Facebook Reel) — a self-hosted file always plays, where the Facebook iframe embed below can be blocked by Rights Manager depending on the source post's audio track.

    - Set `entry.videoEmbed` (provider `"facebook"`, plus `url`, optional `aspectRatio`, optional `title`) on a `local` news entry to embed the source Facebook video/Reel inline instead, when a self-hosted `videoSrc` isn't available. `aspectRatio` defaults to `"portrait"` for `/reel/` URLs, `"landscape"` otherwise. Card thumbnails still use `imageSrc`/`imageAlt` — keep both set so the thumbnail looks right even though the embed wins on the full/detail view. A prior fleet attempt at embedding Facebook video was reverted after hitting a rights-block on a post with non-original audio; always visually verify an embed actually plays before shipping it on a real entry.

    - Local article workflow: create a local page route (e.g. `/news/my-article-slug`), add it to `AppRoutes.tsx`, and point the news entry's `href` at it. Build-time route discovery (`tools/app-routes.mjs`) picks it up for prerender + sitemap; for dynamic routes, enumerate concrete URLs via `PRERENDER_EXTRA_ROUTES`. Apply the same rule to event detail routes. Validate with `npm run build` and `npm run qa:smoke:nojs`.

    - `imageLayout` (`"auto" | "none" | "top" | "side" | "alternating"`, default `"auto"`) controls image placement on list cards independent of `cardMode` — `"auto"` preserves the original `cardMode`-driven behavior byte-for-byte, so no existing site is affected by leaving it unset.

    - For a detail page's Previous/Next navigation, compute both neighbors with `getAdjacentEntries(entries, currentId, eventDetailsBasePath, currentOccurrenceStartAt?)` (exported from `EventsNewsSection.tsx`) and pass the result straight to `EventsNewsDetail`'s `previous`/`next` props — do not reimplement chronological-adjacency logic per site. Pass the 4th param (the resolved entry's own `startAt`) whenever the entry is recurring, or Previous/Next matches whichever occurrence of that series sorts first instead of the one actually being viewed. Either side renders nothing when there's no neighbor (start/end of the timeline).

    - For a recurring event's detail page, resolve which occurrence to show via `resolveEventOccurrence(entry, now, requestedDateYmd?)` (exported from `EventsNewsSection.tsx`) — do not reimplement this occurrence-resolution math per site. Read a `?date=YYYY-MM-DD` query param from `useSearchParams()` and pass it through as `requestedDateYmd`; it resolves to that specific occurrence when it's a real generated one, otherwise falls back to the live next occurrence via `getNextOccurrence()`. `expandEventEntry()`/`toEventDetailsHref()` already stamp `?date=<occurrence>` onto every occurrence card's link, so each occurrence in a list/archive is individually addressable without further wiring.

    - `EventsNewsDetail`'s event-action row (`Open in Maps`/`Add to Calendar`/share) is hidden once the event has passed — gated on `getEventStatus(entry) !== "past"` (exported from `EventsNewsSection.tsx`); an "ongoing" event (happening right now) still shows it. Those actions aren't relevant for something already over.

    - Event detail route examples (for sitemap + prerender): Friday Adoption Meet-and-Greet, Biweekly Supply Drive, Second Weekend Foster Fair, Monthly Volunteer Orientation, and Community Fundraiser.

    - Local article route example: News Article Example

    - Component: `template/src/components/patterns/EventsNewsSection.tsx`

    - Localization lib: `template/src/lib/localized-content.ts`

    Localization (bilingual sites)

    - `EventsNewsSection`/`EventBanner` themselves stay plain-`string` and single-language — do not add localized fields to the shared components.

    - For a bilingual (EN/ES-toggle) site's real event/news content, author each translatable field as `LocalizedText` (from `lib/localized-content.ts`): a plain string, or an en/es record.

    - Keep a `resolve*Entry(entry, locale)` mapper in the site's own `data/events.ts`/`data/news-items.ts` that resolves `LocalizedText` fields down to the plain-`string` `EventsNewsEventEntry`/`EventsNewsArticleEntry` shape via `resolveText(value, locale)` — call it from the page component with `toContentLocale(i18n.resolvedLanguage)` before handing `entries` to `EventsNewsSection`.

    - `EventBanner` takes an optional `locale` prop (string, default `"en-US"`) for its `Intl.DateTimeFormat` date/time display. On a bilingual site, pass it `toIntlLocaleTag(toContentLocale(i18n.resolvedLanguage))` — otherwise the banner's date/time stays English-formatted even when the rest of the page is in Spanish.