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.
Last updated
---
/**
* 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>
Show codeHide 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.