Skip to content
Patternsbeta

Heroes

The first screen of a page: a big Playfair title, a small italic aside, one clear action — on cream, or over a real photo. Every hero ends in the same soft wave.

Three hero types

Pick by what you have, in this order: a real photo of the place → the festival’s own identity → words.

Photo hero

City pages with a landmark image. Title sits bottom-left on a scrim.

Identity hero

Festivals and events (FestivalHero). Logo with sticker shadow, countdown, name, CTAs — tinted by the festival accent.

Text hero

Homepage, listings, cities without a photo. Eyebrow + title with an italic highlight.

Photo hero with scrim

From /cities/[city], with the real Munich image and credit.

vue
<section class="relative">
  <div class="relative w-full h-64 sm:h-96 overflow-hidden">
    <img :src="hero.image" :alt="`${city.name} — landmark`" class="absolute inset-0 w-full h-full object-cover" fetchpriority="high">
    <!-- Scrim: light at the top, dark where the title sits -->
    <div class="absolute inset-0" style="background:linear-gradient(180deg, rgba(59,31,18,0.15) 0%, rgba(59,31,18,0.05) 40%, rgba(59,31,18,0.75) 100%);" />
    <div class="relative h-full max-w-4xl mx-auto px-4 flex flex-col justify-end pb-6">
      <div class="font-display italic text-lg text-wd-amber-100">— {{ city.eventCount }} weekly events</div>
      <h1 class="font-display text-5xl sm:text-7xl leading-[0.98] tracking-tight text-white">{{ city.name }}</h1>
    </div>
    <!-- Attribution is required by the CC licence -->
    <a :href="hero.source" class="absolute bottom-1.5 right-2 text-[10px] …">📷 {{ hero.credit }} · {{ hero.license }}</a>
  </div>
</section>

Scrim recipe

Warm brown (brown-900 as rgba 59,31,18), three stops: 15% → 5% at 40% → 75%. Light at the top so the photo breathes, dark at the bottom where the text is. Plus a soft text-shadow: 0 2px 24px rgba(0,0,0,.35) on the title.

Sizes

Height h-64 sm:h-96. Title text-5xl sm:text-7xl, leading-[0.98], tracking-tight. Content constrained to max-w-4xl so it lines up with the page below.

Identity hero (festival & event)

Live FestivalHero with ¡Menéate Viena! data. The accent tints the sun rays, the countdown, the planning chip and both CTAs.

¡Menéate Viena! 2026
— Past

¡Menéate Viena! 2026

Mar 26 – Mar 29, 2026 · wolke19, Vienna, Austria

200 planning
vue
<FestivalHero :festival="festival" :picked="inPlan" @pick="togglePlan" />
<!-- events reuse it: entity-label="event" review-target-type="event" cta-anchor="#discover" -->

Text hero with eyebrow + italic highlight

The default. Eyebrow in caps amber, then a title where one short phrase turns italic and red. A second, green italic aside is allowed once per page (homepage: “year.”).

The festival year
Pick your next one. Plan the year.

Every festival mapped. See who is going before you book.

vue
<div class="text-sm tracking-widest uppercase text-secondary">The festival year</div>
<h1 class="font-display text-5xl sm:text-6xl leading-[0.98] text-foreground">
  Pick your <em class="italic text-primary">next one.</em>
</h1>
<p class="mt-5 font-sans text-lg text-muted-foreground max-w-xl">Every festival mapped. See who is going before you book.</p>
  • Highlight the feeling word (“night”, “next one”, “dancing”), not the noun the page is about.
  • Italic in text-primary (red) is the default highlight; text-success (green) is the second voice — never both in one phrase.
  • Homepage only: the hand-drawn underline SVG under “best”. Don’t reuse it elsewhere; it’s the signature.
  • One CTA in the hero (.wd-cta gradient pill) plus an italic aside — “— see who’s going before you book”.

Wavy divider

Every hero closes with the same wave: a 40px brown-900 path at 8% opacity (10% on the homepage). It turns a hard edge into a shoreline.

vue
<svg class="block w-full h-10 -mb-px" viewBox="0 0 1440 60" preserveAspectRatio="none" aria-hidden="true">
  <path d="M0,40 Q360,0 720,30 T1440,20 V60 H0 Z" :fill="WD.brown900" opacity="0.08" />
</svg>

Use -mb-px to avoid a hairline gap. Same path everywhere — don’t redraw it per page.

Image rules

Real photos only. Every person shown is a real dancer, teacher or host at a real night. No stock, no AI-generated people, no avatar generators — “Every face — real” is on the homepage.
Cities: landmark, not dancers. City heroes use a Wikipedia landmark photo (scripts/fetch-city-images.mjs → server/data/city-images.json). We don’t have dancers from every city yet, and a generic “people dancing” photo would lie.
No photo? Use the text hero. When there is no landmark, the city falls back to the cream text hero with sun rays in the city accent. Never fill the gap with a placeholder image.
Credit the photographer. Wikimedia images are CC-licensed: the credit + licence pill in the bottom-right corner is required, not decoration.
Faces above the fold, not cropped. Use object-position (e.g. center 30%) so heads survive the 4:5 and wide crops.

Do and don't

Munich

Do — white title on a photo only with the scrim behind it.

Munich

Don't — text straight on a bright photo — the sky eats the title.

Where you’re dancing.

Do — one short italic highlight that carries the feeling.

Where you’re dancing.

Don't — italicise half the title, or stack several colours — the emphasis disappears.

Accessibility

  • One <h1> per page — it lives in the hero. The eyebrow is a <div>, not a heading.
  • Scrim before shadow. White title text over a photo needs the gradient scrim (75% brown at the bottom). The text-shadow alone is not enough on bright skies.
  • Alt text describes the image (“Munich — landmark”, “Alösha on stage at the end of the Charanga Habanera Munich concert he hosted”). Decorative SVGs (sun rays, wave) get aria-hidden="true".
  • Hero image is the LCP. Load it eager with fetchpriority="high"; never lazy-load the hero.
  • Italic highlights are emphasis — use <em> so they are announced, and keep them short (one to three words).