# Blocks — Bike listing

A **titled grid of Bike cards** (one, two or three columns) with an optional CTA below. The grid is
centred when it holds three or fewer bikes and wraps from the left when it holds more. Used on
model pages ("All Paris bikes"). Rebuilt from production's `BikeListing` Storyblok component. The
Blocks → Bike listing screen emits this exact markup.

## Metadata

- **Category:** Blocks
- **Family:** Listings
- **Status:** Stable (data-source fields are out of scope; see below)
- **Storyblok component:** `BikeListing` (+ `list_of_model_detail_bikes`, `cta` → `LinkItem`)
- **Production source:** `gazelle-int-website/app/storyblok/BikeListing.vue` on
  `origin/staging` (`e1acfea4`) → `app/components/BikeCard.vue`
- **Source:** `docs/blocks-registry.js` (canonical markup) composing
  `docs/molecules-registry.js` (Bike card)

## When to use

- A model page listing that model's bikes, or a hand-picked set of bikes on a content page.

## When not to use

- For a filterable results page, use the **Product listing**.

## Storyblok fields

| Field | Type | Covered | Notes |
| --- | --- | --- | --- |
| `title` | text | Yes | Centred H2 |
| `model_name` / `list_of_model_detail_bikes` | text / bloks (SKU, page link, award, tagline) | Partial | Which bikes show. The preview uses sample products (**Bikes** control) |
| `list_of_model_detail_bikes[].award_image` | asset | Yes | The Bike card's award. Shown on the first bike in the preview |
| `list_of_model_detail_bikes[].custom_tagline` | richtext | No | Live cards hide taglines (GAZELLEB2C-10652) |
| `hide_short_specs` | boolean | Yes | Passed to every Bike card |
| `background_color` | option, datasource `background-colors` (default oyster) | Yes | Oyster, Grey, White |
| `cta` | bloks → `LinkItem` (max 1) | Yes | Button atom, Primary |
| `introduction_year`, `is_ebike`, `parent_is_ebike` | text / boolean | No | Filters for the dynamic fetch |
| `custom_padding`, `padding_top`, `padding_bottom` | boolean | Yes | Applied as margin, off by default |

## Anatomy

```html
<section class="bg-surface-oyster px-gutter py-15" data-gz-compare-max="3">
  <div class="mx-auto max-w-content">
    <h2 class="mb-10 text-center type-h2-sm text-fg-brand tablet:mb-16 tablet:type-h2-lg">All Paris bikes</h2>
    <ul class="-m-2 flex flex-wrap justify-center">  <!-- justify-start with more than 3 bikes -->
      <li class="flex w-full flex-col p-2 tablet:w-1/2 laptop:w-1/3"><!-- Bike card --></li>
      …
    </ul>
    <div class="mt-16 text-center"><!-- Button atom <a>: Primary, Small + lg-tablet --></div>
  </div>
</section>
```

**Centring with a gap.** Production centres a short row with `flex flex-wrap justify-center` and
`calc()` widths. Here every card sits in a list item with `p-2` padding, and the list pulls back
the outer padding with `-m-2`. The result is a 16px gap between cards and fractional widths
(`w-1/2`, `w-1/3`), with no arbitrary values.

**Spacing.** `custom_padding` + `padding_top` / `padding_bottom` add
`mt-section-sm laptop:mt-section-lg` / `mb-section-sm laptop:mb-section-lg`, matching
production's `mt-07 laptop:mt-11` (32 → 80px) exactly.

## Tokens

- Layout: `max-w-content`, `px-gutter py-15`, `-m-2` / `p-2`, `w-1/2`, `w-1/3`, `mb-10 tablet:mb-16`,
  `mt-16`, `*-section-sm` / `*-section-lg`.
- Colour: `bg-surface-oyster` / `bg-surface-muted` / `bg-white`, `text-fg-brand`.
- Type: **H2** (32 → 40px).

## Production mapping

| Part | Production | Design system | Decision |
| --- | --- | --- | --- |
| Section | `p-05 desktop-xl:px-00`, inner `c-container-small my-08` (20 + 40 = 60px vertical) | `px-gutter py-15` + `max-w-content` | Exact 60px rhythm, as one padding |
| Background | `bg-${background_color}` (default `main-oyster-500`) | `bg-surface-oyster` / `bg-surface-muted` / `bg-white` | Same datasource values |
| Title | `h2 tablet:h1-small mb-08 tablet:mb-10 text-main-blue-500 text-center` | `type-h2-sm tablet:type-h2-lg mb-10 tablet:mb-16 text-fg-brand text-center` | Closest roles: the production helpers have no Gazelle type token |
| Grid | `flex flex-wrap gap-04`, cards `w-full tablet:w-[calc((100%-1.5rem)/2)] laptop:w-[calc((100%-3rem)/3)]`, `justify-center` unless more than 3 | `-m-2 flex flex-wrap`, items `p-2 w-full tablet:w-1/2 laptop:w-1/3`, same justify rule | Same layout, no arbitrary values |
| CTA | `text-center mt-10`, `BaseButton !inline-flex` | `mt-16 text-center`, Button atom Primary, Small + `lg-tablet` | Exact (production `10` = 64px) |
| Margin | `mt-07 laptop:mt-11` / `mb-07 laptop:mb-11` when `custom_padding` | `*-section-sm laptop:*-section-lg` | Exact |
| Structured data | `StructuredData` (ItemList) | Not covered | Product code |

## Cross-references

- `specs/molecules/bike-card.md`
- `specs/blocks/product-listing.md`
