# Hero Split

> Headline and actions beside a media panel; stacks when narrow, flips with one prop.

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

## Install

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

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

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

   - `HeroSplit.astro` (raw: https://viewership.ai/ui/raw/hero-split/HeroSplit.astro.txt)

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

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

npm packages: none.

## Usage

```astro
---
import HeroSplit from "../components/viewership/hero-split/HeroSplit.astro";
---

<HeroSplit />
```

## Example

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

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

<HeroSplit
  title="Built to be mended,"
  accent="not replaced."
  body="Every pack comes with a map of where it was made and a promise to fix it for as long as you own it."
  primary={{ label: "Shop packs", href: "#" }}
  secondary={{ label: "How repairs work", href: "#" }}
  note="Free returns within 60 days, used or not."
>
  <img slot="media" src="/ui/demo/contours.webp" alt="Topographic contour map" width="1600" height="1100" loading="lazy" />
</HeroSplit>
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `title` | `string` |  | Headline. |
| `accent` | `string` |  | Closing words of the headline, drawn in the theme's accent style. |
| `body` | `string` |  | Supporting paragraph. |
| `primary` | `{ label: string; href: string }` |  | Main call to action. |
| `secondary` | `{ label: string; href: string }` |  | Text-style second action. |
| `note` | `string` |  | Small line under the actions. |
| `reverse` | `boolean` | `false` | Media on the left. |
| `as` | `"h1" \| "h2"` | `"h1"` | Heading element. |
| `slot: media` | `slot` |  | Image or video, cropped to 4:3. |

## Source

### `src/components/viewership/hero-split/HeroSplit.astro`

```astro
---
/**
 * HeroSplit: copy on one side, media on the other. Stacks on narrow widths.
 *
 * @example
 * <HeroSplit title="Built to be mended," accent="not replaced." primary={{ label: "Shop", href: "/shop" }}>
 *   <img slot="media" src="/map.webp" alt="" />
 * </HeroSplit>
 */
import Button from "../button/Button.astro";

interface Cta {
  label: string;
  href: string;
}
interface Props {
  title: string;
  accent?: string;
  body?: string;
  primary?: Cta;
  secondary?: Cta;
  /** Small line under the actions: a guarantee, a rating, a note. */
  note?: string;
  /** Put the media on the left instead. */
  reverse?: boolean;
  as?: "h1" | "h2";
  class?: string;
}

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

<section class:list={["vs-hero-split", { "is-reverse": reverse }, className]}>
  <div class="vs-hero-split__inner">
    <div class="vs-hero-split__copy">
      <Heading class="vs-display vs-hero-split__title">{title}{accent && <> <em>{accent}</em></>}</Heading>
      {body && <p class="vs-hero-split__body">{body}</p>}
      {
        (primary || secondary) && (
          <div class="vs-hero-split__actions">
            {primary && <Button href={primary.href} variant="primary">{primary.label}</Button>}
            {secondary && <Button href={secondary.href} variant="quiet">{secondary.label}</Button>}
          </div>
        )
      }
      {note && <p class="vs-hero-split__note">{note}</p>}
    </div>
    {
      Astro.slots.has("media") && (
        <div class="vs-hero-split__media">
          <slot name="media" />
        </div>
      )
    }
  </div>
</section>

<style>
  .vs-hero-split {
    container-type: inline-size;
    background: var(--vs-page);
    color: var(--vs-ink);
    font-family: var(--vs-font-sans);
    font-stretch: var(--vs-font-stretch);
  }
  .vs-hero-split__inner {
    display: grid;
    gap: clamp(32px, 6cqi, 72px);
    max-width: var(--vs-container);
    margin-inline: auto;
    padding: var(--vs-section-y) var(--vs-gutter);
  }
  .vs-hero-split__title {
    font-size: clamp(2.25rem, 7cqi, 4.75rem);
  }
  .vs-hero-split__body {
    max-width: 30rem;
    margin: 1.5rem 0 0;
    font-size: 1.0625rem;
    line-height: 1.55;
    color: var(--vs-ink-2);
  }
  .vs-hero-split__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 2rem;
  }
  .vs-hero-split__note {
    margin: 1.25rem 0 0;
    font-size: 0.8125rem;
    color: var(--vs-ink-3);
  }
  .vs-hero-split__media {
    min-width: 0;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border: var(--vs-border-w) solid var(--vs-line);
    border-radius: var(--vs-radius-card);
    background: var(--vs-canvas);
    box-shadow: var(--vs-pop-shadow, none);
  }
  .vs-hero-split__media :global(:is(img, video)) {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  @container (min-width: 760px) {
    .vs-hero-split__inner {
      grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
      align-items: center;
    }
    .is-reverse .vs-hero-split__media {
      order: -1;
    }
  }
</style>
```
