A full-screen product photo with a mono eyebrow, heading, line and two round buttons at the bottom start, a scroll cue in the middle and a video thumbnail at the bottom end.

Last updated

Open block
Show code
---
/**
 * Product photo 01: A full-screen product photo with a mono eyebrow, heading, line and two round buttons at the bottom start, a scroll cue in the middle and a video thumbnail at the bottom end.
 *
 * @block hero
 * @order 56
 */
import { ArrowRight, ChevronDown, Play } from '@lucide/astro'
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import Eyebrow from '@/components/apogee/Eyebrow/Eyebrow.astro'
import Button from '@/components/apogee/Button/Button.astro'
import Image from '@/components/apogee/Image/Image.astro'
import Link from '@/components/apogee/Link/Link.astro'
import { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'

/** @composable */
interface Props extends HeroContent {}

const {
  eyebrow = 'The Ridgeline X1',
  heading = 'A new kind of trail bike starts here',
  description = 'Made for riders who keep going past the end of the map.',
  secondaryAction = { label: 'Explore the bike', href: '#' },
  primaryAction = { label: 'Configure yours', href: '#' },
  media = { src: 'https://picsum.photos/id/1018/2400/1600', alt: '', width: 2400, height: 1600 },
} = Astro.props
---

<!-- Always dark: the text sits on a photo, so it keeps light ink in both themes. -->
<Section spacing="none" container={false} theme="dark" class="overflow-hidden">
  <div class="relative isolate flex min-h-svh flex-col justify-end gap-10 px-gutter pb-10 pt-32 md:flex-row md:items-end md:justify-between md:pb-14">
    {media && <Image src={media.src} alt={media.alt} width={media.width} height={media.height} fit="cover" loading="eager" class="absolute inset-0 -z-10 size-full" />}
    <div class="absolute inset-0 -z-10 bg-[linear-gradient(to_top,color-mix(in_oklab,var(--background-page)_85%,transparent),transparent_60%)]" aria-hidden="true"></div>

    <div class="flex max-w-2xl flex-col gap-4">
      {eyebrow && <Eyebrow color="neutral" class="font-mono">{eyebrow}</Eyebrow>}
      <Heading as="h1" size="display" class="font-normal text-balance">{plain(heading)}</Heading>
      {description && <p class="text-lg md:text-xl text-neutral-faded">{description}</p>}
      {(primaryAction || secondaryAction) && (
      <div class="mt-4 flex flex-wrap gap-3">
        {secondaryAction && <Button href={secondaryAction.href} size="large" rounded variant="outline" color="neutral">{secondaryAction.label}<ArrowRight slot="end" class="rtl:-scale-x-100" aria-hidden="true" /></Button>}
        {primaryAction && <Button href={primaryAction.href} size="large" rounded>{primaryAction.label}<ArrowRight slot="end" class="rtl:-scale-x-100" aria-hidden="true" /></Button>}
      </div>
      )}
    </div>

    <p class="absolute bottom-6 start-1/2 hidden -translate-x-1/2 flex-col items-center text-sm text-neutral-faded md:flex rtl:translate-x-1/2" aria-hidden="true">Scroll<ChevronDown class="size-4" /></p>

    <Link href="#" color="inherit" underline="none" class="group flex w-full max-w-xs flex-col gap-3 text-sm">
      <span class="relative block overflow-hidden rounded-md">
        <Image src="https://picsum.photos/id/1072/640/400" alt="" width={640} height={400} fit="cover" class="aspect-16/10 w-full transition-transform duration-(--duration-medium) group-hover:scale-105 motion-reduce:transition-none" />
        <span class="absolute inset-0 grid place-items-center"><span class="grid size-12 place-items-center rounded-full bg-page/80 backdrop-blur"><Play class="size-5" aria-hidden="true" /></span></span>
      </span>
      <span>Play the launch film <span class="tabular-nums text-neutral-faded">02:56</span></span>
    </Link>
  </div>
</Section>

Add

npx @ava-ui/apogee add hero/product-photo-01

Every piece of copy is a prop, with the sample copy as its default. Pass yours, or null to hide a part; see copy is props.