Anatomy

root
The card surface: bg-primary fill, a card hairline, the generic card radius, content clipped. Fluid between 280px and 460px wide (360px is the Figma reference width).
featured-border
A 4px brand rule across the very top, shown only for a featured location.
media
The 16:9 venue photo (360x202.5 at the reference width — an exact ratio, not the rounded 360x203 the frame previously used), cover-cropped, with a centered 70% subject-safe area. Falls back to a clearly-marked image placeholder when no photo is published — never stock or mock photography.
club-logo
A 48x48 radius-xl viewport pinned 16px from the media's bottom-right, holding 40x40 partner artwork scaled to Fit — never Crop, so irregular crests and wordmarks are not clipped. Unmasked. Falls back to a clearly-marked logo placeholder when the partner has no artwork.
content
The padded body stack below the media — 24px inline padding, 20px block padding, 12px between groups.
title
The location name — text-lg bold, the card's only heading.
camp-summary
The quiet secondary line beneath the title: a count and the words 'upcoming camps'. Tertiary text, deliberately NOT a status pill. In State=No upcoming camps the count is hidden and the line reads 'No upcoming camps'.
tags
Descriptive tags for the camp formats offered at this venue, revealed by Show tags. Note this card has NO overflow mechanism: unlike the camp card it carries no Tags/Overflow count and no Show tag overflow boolean (verified against the v5 snapshot), and its description is silent on what happens when the tags exceed the row. Treat that as unspecified rather than as licence to wrap — see notes.md.
essential-details
Location, season and club rows, each a 24x24 Beestera icon (location, calendar, cleat) beside text-xs metadata.
price
Optional Early bird pricing: a 'From' eyebrow with an Early bird badge, then a struck-through regular price followed by the emphasised current from-price and its unit. Hidden by default and in State=No upcoming camps.
early-bird-badge
The promotional Early bird pill in the price eyebrow — brand-toned, never a success/green tone.
action
The View details button: a brand-gradient pill that fills the row's width when no price is shown. Retained in every state, including No upcoming camps.

Token bindings

Each binding's value is resolved live from the built @beestera/tokens output — tier-2 component tokens (--camp-location-card-*) collapse through the foundation tier.

Part Property Token Resolved Applies to
root background --bg-primary #ffffff --bg-primary → --color-base-white
root border-color --card-border #e4e4e7 --card-border → --color-border
root border-radius --card-radius 0.75rem --card-radius → --radius-card
root box-shadow --shadow-md 0px 2px 4px -2px #0000000f, 0px 0px 10px -1px #0000001a state:Hover
root box-shadow --focus-ring #3d3d3d --focus-ring → --color-neutral-700 state:Focused
featured-border background --bg-brand-solid #181818 --bg-brand-solid → --color-neutral-900
media background --bg-secondary #fafafa --bg-secondary → --color-neutral-50
club-logo background --bg-primary #ffffff --bg-primary → --color-base-white
club-logo border-color --card-border #e4e4e7 --card-border → --color-border
club-logo border-radius --radius-xl 0.75rem
content padding-inline --spacing-3xl 1.5rem --spacing-3xl → --spacing-scale-6
content padding-block --spacing-2xl 1.25rem --spacing-2xl → --spacing-scale-5
content gap --spacing-lg 0.75rem --spacing-lg → --spacing-scale-3
title color --text-primary #181818 --text-primary → --color-neutral-900
title font-size --font-size-text-lg 1.125rem
camp-summary color --text-tertiary #52525b --text-tertiary → --color-neutral-600
camp-summary font-size --font-size-text-xs 0.75rem
camp-summary gap --spacing-xs 0.25rem --spacing-xs → --spacing-scale-1
tags background --bg-primary #ffffff --bg-primary → --color-base-white
tags border-color --border-primary #d4d4d8 --border-primary → --color-neutral-300
tags border-radius --radius-sm 0.375rem
tags color --text-secondary #3d3d3d --text-secondary → --color-neutral-700
tags font-size --font-size-text-xs 0.75rem
tags gap --spacing-sm 0.375rem --spacing-sm → --spacing-scale-1-5
essential-details color --text-secondary #3d3d3d --text-secondary → --color-neutral-700
essential-details font-size --font-size-text-xs 0.75rem
essential-details gap --spacing-sm 0.375rem --spacing-sm → --spacing-scale-1-5
price color --text-tertiary #52525b --text-tertiary → --color-neutral-600 slot:eyebrow
price color --text-tertiary #52525b --text-tertiary → --color-neutral-600 slot:regular-price
price color --text-primary #181818 --text-primary → --color-neutral-900 slot:current-price
price font-size --font-size-text-xl 1.25rem slot:current-price
price color --text-secondary #3d3d3d --text-secondary → --color-neutral-700 slot:price-unit
early-bird-badge background --utility-brand-50 #fffbeb --utility-brand-50 → --color-brand-25
early-bird-badge border-color --utility-brand-900 #c27c00 --utility-brand-900 → --color-brand-800
early-bird-badge color --utility-brand-900 #c27c00 --utility-brand-900 → --color-brand-800
early-bird-badge border-radius --radius-full 9999px
action background --gradient-brand-500-600 linear-gradient(180deg, var(--color-brand-500) 0%, var(--color-brand-600) 100%)
action color --text-primary #181818 --text-primary → --color-neutral-900
action font-size --font-size-text-md 1rem
action border-radius --radius-full 9999px
action box-shadow --button-shadow 0px 1px 2px 0px #0000000d, inset 0px -2px 0px 0px #0000000d, inset 0px 0px 0px 1px #0000002e --button-shadow → --shadow-xs-skeuomorphic

Variants

state DefaultHoverFocusedNo upcoming camps
The Figma State axis. Default/Hover/Focused are the card details link's interaction states. 'No upcoming camps' is the single source of truth for a venue with no published future sessions: the location and all of its details stay accessible, the camp-summary line goes quiet ('No upcoming camps'), pricing is hidden, and View details is retained. Never model it as a separate overlay or boolean. There is deliberately no Disabled state — a location stays discoverable even with no future sessions. Neither card system has one.

State matrix

default
Resting card. The whole surface links to the location-detail page; View details repeats that destination as an explicit control.
hover
The card details link lifts to the medium elevation (--shadow-md).
focus
Keyboard focus on the card details link paints the shared focus ring — a 4px ring offset from the card by a 2px bg-primary halo.

Accessibility

Role
The root is the link to the location-detail page (a real <a>, or a card-wide link overlay) — decided 2026-08-06. View details points at the same destination and is therefore NOT a second control: render it as a visual affordance inside the card link (aria-hidden, or a plain <span> styled as a button), never as its own <a>/<button>. Exactly one tab stop per card.
Contrast
The camp-summary line is text-tertiary on bg-primary (>= 4.5:1). The Early bird badge keeps brand-900 on brand-50. Nothing on this card conveys status through colour alone.
Focus order
One tab stop per card — the card link itself. View details is inside it and takes no focus of its own, so a list of venues tabs one card at a time.
Target size
View details is 44px tall in the Figma reference and must stay >= 44px, even though it is not separately focusable — it is still a pointer target within the card link.

Usage

Do

  • Use this card on the browse page, where each entry is a venue that may host many camps. The location-detail page uses the camp card instead.
  • Keep the camp count as quiet secondary text beneath the title.
  • Use State=No upcoming camps when a venue has no published future sessions, and keep View details reachable.
  • Keep the tag row to one line. The camp card's overflow pattern is NOT part of this component in Figma, so if this card needs one, raise it with the designer rather than importing the camp card's silently.
  • Pair the struck-through regular price with the emphasised current from-price when Early bird pricing applies.

Don't

  • Don't promote the camp count into a status pill — it is not a badge.
  • Don't put exact ages, session dates or inventory counts on this card; those belong on the camp card or the details screen.
  • Don't add a separate 'no camps' overlay or boolean alongside the state axis.
  • Don't make View details its own <a>/<button> — the card is the link, and a second control would be a duplicate tab stop to the same destination on every card in the list.
  • Don't mask or crop partner logos — the 48x48 viewport is deliberately unmasked around 40x40 Fit artwork.
  • Don't let a tag wrap to a second line, truncate mid-word, or extend past the card edge.
  • Don't assume this card has the camp card's Tags/Overflow count — it doesn't, and inventing one here would put a component in the build that the design file has never drawn.

Implementations

Not implemented in any product repo yet. This repo owns the spec; implementations land in each product repo on their own cadence.

Figma source: Cards/Camp location card