# Hero Poster

> Full-bleed image or video with the headline set over it, bottom left, behind a soft scrim.

- **Slug:** `hero-poster`
- **Kind:** section · **Category:** Hero
- **Source:** original to Viewership UI
- **Docs page:** https://viewership.ai/design/components/hero-poster
- **All files in one bundle:** https://viewership.ai/design/components/hero-poster.bundle.txt
- **Structured JSON:** https://viewership.ai/design/components/hero-poster.json

## Install

1. Import the foundation tokens once (download: https://viewership.ai/design/raw/foundation.css.txt):

```astro
---
// src/layouts/Layout.astro
import "../styles/foundation.css";
---
```

2. Copy this file into `src/components/viewership/hero-poster/`:

   - `HeroPoster.astro` (raw: https://viewership.ai/design/raw/hero-poster/HeroPoster.astro.txt)

3. Copy its registry dependencies, each into its own sibling folder under `src/components/viewership/`:

   - `button`: `Button.astro` (docs: https://viewership.ai/design/components/button.md)

npm packages: none.

## Usage

```astro
---
import HeroPoster from "../components/viewership/hero-poster/HeroPoster.astro";
---

<HeroPoster />
```

## Example

The gallery demo, verbatim. Use the import path from Usage instead of `./`.

```astro
---
import HeroPoster from "./HeroPoster.astro";
---

<HeroPoster
  caption="Winter collection. Boundary Waters, Minnesota."
  title="Out past"
  accent="the last road."
  body="Shells and sleep systems rated for minus thirty, tested by people who go there on purpose."
  primary={{ label: "Shop winter", href: "#" }}
  secondary={{ label: "Field notes", href: "#" }}
>
  <img slot="media" src="/ui/demo/ridgeline-night.webp" alt="Mountain ridges under a full moon" width="1600" height="1000" loading="lazy" />
</HeroPoster>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` |  | Headline. |
| `accent` | `string` |  | Closing words of the headline. |
| `body` | `string` |  | Supporting paragraph. |
| `primary` | `{ label: string; href: string }` |  | Main call to action (accent button). |
| `secondary` | `{ label: string; href: string }` |  | Second action. |
| `caption` | `string` |  | Short line in the top corner. |
| `as` | `"h1" \| "h2"` | `"h1"` | Heading element. |
| `slot: media` | `slot` |  | Background image or video, covers the section. |

## Source

### `src/components/viewership/hero-poster/HeroPoster.astro`

```astro
---
/**
 * HeroPoster: full-bleed media with the headline set over it, bottom left.
 *
 * A scrim keeps the type readable on any image. Text is always light here,
 * whatever the theme mode, because it sits on the picture.
 *
 * @example
 * <HeroPoster title="Out past" accent="the last road." primary={{ label: "Shop", href: "/shop" }}>
 *   <img slot="media" src="/night.webp" alt="" />
 * </HeroPoster>
 */
import Button from "../button/Button.astro";

interface Cta {
  label: string;
  href: string;
}
interface Props {
  title: string;
  accent?: string;
  body?: string;
  primary?: Cta;
  secondary?: Cta;
  /** Short caption in the top corner: a place, a season, a credit. */
  caption?: string;
  as?: "h1" | "h2";
  class?: string;
}

const { title, accent, body, primary, secondary, caption, as: Heading = "h1", class: className } = Astro.props;
---

<section class:list={["vs-hero-poster", className]}>
  <div class="vs-hero-poster__media" aria-hidden={Astro.slots.has("media") ? undefined : "true"}>
    <slot name="media" />
  </div>
  <div class="vs-hero-poster__inner">
    {caption && <p class="vs-hero-poster__caption">{caption}</p>}
    <div class="vs-hero-poster__copy">
      <Heading class="vs-display vs-hero-poster__title">{title}{accent && <> <em>{accent}</em></>}</Heading>
      {body && <p class="vs-hero-poster__body">{body}</p>}
      {
        (primary || secondary) && (
          <div class="vs-hero-poster__actions">
            {primary && <Button href={primary.href} variant="accent">{primary.label}</Button>}
            {secondary && <Button href={secondary.href} variant="secondary">{secondary.label}</Button>}
          </div>
        )
      }
    </div>
  </div>
</section>

<style>
  .vs-hero-poster {
    position: relative;
    container-type: inline-size;
    display: grid;
    overflow: hidden;
    background: #0d1118;
    color: #f6f2ea;
    font-family: var(--vs-font-sans);
    font-stretch: var(--vs-font-stretch);
  }
  .vs-hero-poster__media {
    position: absolute;
    inset: 0;
  }
  .vs-hero-poster__media :global(:is(img, video)) {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .vs-hero-poster__media::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgb(5 7 12 / 0.82) 0%, rgb(5 7 12 / 0.35) 45%, rgb(5 7 12 / 0) 75%);
  }
  .vs-hero-poster__inner {
    position: relative;
    display: flex;
    min-height: clamp(460px, 62cqi, 760px);
    flex-direction: column;
    justify-content: space-between;
    gap: 40px;
    width: 100%;
    max-width: var(--vs-container);
    margin-inline: auto;
    padding: clamp(24px, 4cqi, 40px) var(--vs-gutter) var(--vs-section-y);
  }
  .vs-hero-poster__caption {
    margin: 0;
    font-size: 0.8125rem;
    letter-spacing: 0.02em;
    opacity: 0.8;
  }
  .vs-hero-poster__copy {
    max-width: 44rem;
    margin-top: auto;
  }
  .vs-hero-poster__title {
    font-size: clamp(2.5rem, 9cqi, 6.5rem);
  }
  .vs-hero-poster__title :global(em) {
    color: inherit;
    opacity: 0.75;
  }
  .vs-hero-poster__body {
    max-width: 30rem;
    margin: 1.25rem 0 0;
    font-size: 1.0625rem;
    line-height: 1.55;
    opacity: 0.85;
  }
  .vs-hero-poster__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 2rem;
  }
</style>
```
