Template:UI/upcoming events featured/card
Card-rendering sub-template for Template:UI/upcoming_events_featured and Template:UI/workshops_library. Renders one workshop card — image, status badge, eyebrow date, title, tagline, and CTA buttons.
Normally invoked indirectly via a parent template's
format=template auto-query, which passes the matched
workshop's full title in as Page. Can also be called
directly for one-off main-page features that need to override the
auto-pick (e.g. promoting a specific workshop ahead of its
application-close timing):

July 2026
Hands-on workshop covering the complete open-source Miniscope imaging pipeline! From experimental design and hardware setup to data analysis and interpretation.
All other fields are derived from the page's SMW data via per-call
#show against {{{Page}}}:
- Eyebrow —
?Has start date#ISO, rendered via
Template:UI/datetime (format=month_year) → "July 2026". CSS uppercases the visual rendering. Date-only format means no TZ shift and no<time>wrapper — the eyebrow is a workshop-month kicker, not a precise meeting time, so visitor- local conversion would be misleading (a workshop "in July" is in July regardless of where the visitor is). Suppressed when Has start date is empty (the empty{{{iso}}}arm in UI/datetime returns nothing, which keeps the eyebrow from collapsing to today's month).
- Title —
Page, wrapped in a wikilink
to the workshop page.
- Tagline —
?Has descriptionon the page.
Suppressed when empty.
- Image —
?Has card image, falling back to
?Has banner. 4:3 aspect locked by CSS; uploads of a
different ratio center-crop. When both properties are empty the
image slot is suppressed and the card renders text-only.
Status badge
A small badge in the card's top-right corner reflects where in its lifecycle the workshop sits. The state machine is a cascade over four dates (Has start date, Has end date, Has application open date, Has application close date) against today:
past — Has end date is in the past (workshop is over).
Falls back to "Has start date in the past" when
Has end date isn't set.
in_progress — Has start date ≤ today ≤ Has end date.
apps_open — workshop is upcoming AND application window is
currently open (open ≤ today ≤ close).
apps_closed — workshop is upcoming AND application close date
is in the past (close < today < start).
upcoming — workshop is upcoming AND no application info is
set, OR applications haven't opened yet
(today < open).
none — Has start date isn't set; no badge rendered.
The cascade runs twice — once to emit the badge text, once to drive the CTA logic — because the Variables extension isn't enabled on the target wikis and wikitext has no other way to cache the result of an
- ifexpr chain. The repeated #show calls within a parser pass are
served from SMW's per-property cache, so the cost is roughly one SMW lookup per (workshop × property), not per (workshop × property × cascade-branch).
CTA buttons
The cascade also gates which CTAs render. Each only appears when its data is set AND the workshop is in the right state for it:
- Primary "Apply now" →
?Has registration url, but
only when status = apps_open. On a past or
applications-closed workshop, the apply CTA would be misleading
even if a registration URL is still on the page.
- Secondary "View recording" →
?Has recording url,
only when status = past. Surfaces the archival
artifact on past-workshop cards in the workshops library.
- Secondary "Learn more" → the workshop page itself
({{{Page}}}). Always renders when Page is non-empty —
the universal fallback CTA so every card has at least one action.
Why not project these via the outer #ask in Template:UI/upcoming_events_featured: keeping them as #show calls here means the direct-invocation path (passing Page explicitly) works without changing the call site. The cost is a handful of extra #show per render; even in a workshops library with 30 workshops the parser budget is comfortable.
Named args
Page— bare full title of the workshop (required from
#ask via mainlabel=Page; required from direct callers).
When empty the template renders nothing — the empty-state placeholder
lives on the dispatcher side so it shows up only when the auto-query
returned zero matches, not when this sub-template happens to receive
an empty Page (which would only happen via a malformed call).