Photo stamps 01
A full-width photo with a centred eyebrow, heading, line and round button, a column of round stamps down one side and a rounded panel rising over the bottom edge.
Last updated
---
/**
* Photo stamps 01: A full-width photo with a centred eyebrow, heading, line and round button, a column of round stamps down one side and a rounded panel rising over the bottom edge.
*
* @block hero
* @order 54
*/
import { ArrowRight } 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 { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
/** @composable */
interface Props extends HeroContent {
/** The heading on the panel under the photo. @category content */
proofHeading?: string | null
/** The figures under that heading. @category content */
proofStats?: string | null
}
const {
eyebrow = 'Picked ripe · Packed the same day',
heading = 'The freshest fruit you can order online',
description = 'Orchard fruit, berries and greens from small farms. Shipped cold, anywhere in the country.',
primaryAction = { label: 'Shop all', href: '#' },
media = { src: 'https://picsum.photos/id/1018/2000/1300', alt: '', width: 2000, height: 1300 },
proofHeading = 'Trusted by families across the country',
proofStats = '120,000 orders shipped · 4.9 average rating · 300 partner farms',
} = Astro.props
// Stamps: words that run around the rim, and a short mark in the middle.
const stamps = [
{ ring: 'Grown slow · Picked ripe ·', mark: 'GS', tone: 'bg-primary text-on-primary' },
{ ring: 'Family farms · Since 2012 ·', mark: '12', tone: 'bg-page text-primary' },
{ ring: 'No sprays · No shortcuts ·', mark: '100%', tone: 'bg-primary-faded text-primary' },
{ ring: 'Shipped cold · Same week ·', mark: '48h', tone: 'bg-neutral text-on-neutral' },
]
---
<Section spacing="none" container={false} class="overflow-hidden">
<div class="relative isolate flex min-h-[44rem] flex-col items-center justify-center px-gutter pb-40 pt-24 text-center md:min-h-[52rem]">
{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" />}
<!-- Page colour washes down from the top so the text reads in either theme. -->
<div class="absolute inset-0 -z-10 bg-[linear-gradient(to_bottom,var(--background-page)_10%,color-mix(in_oklab,var(--background-page)_70%,transparent)_45%,transparent_80%)]" aria-hidden="true"></div>
<div class="flex max-w-4xl flex-col items-center gap-6">
{eyebrow && <Eyebrow color="neutral" class="tracking-widest">{eyebrow}</Eyebrow>}
<Heading as="h1" size="display" class="text-balance">{plain(heading)}</Heading>
{description && <p class="text-lg md:text-xl">{description}</p>}
{primaryAction && <Button href={primaryAction.href} size="large" rounded class="px-10 uppercase tracking-widest">{primaryAction.label}<ArrowRight slot="end" class="rtl:-scale-x-100" aria-hidden="true" /></Button>}
</div>
<ul class="absolute end-6 top-1/2 hidden -translate-y-1/2 flex-col gap-8 lg:flex" aria-hidden="true">
{stamps.map((s, i) => (
<li class:list={['grid size-28 place-items-center rounded-full shadow-md', s.tone]}>
<svg viewBox="0 0 100 100" class="size-full">
<defs><path id={`photo-stamp-${i}`} d="M50 50m-36 0a36 36 0 1 1 72 0a36 36 0 1 1-72 0" /></defs>
<circle cx="50" cy="50" r="46" fill="none" stroke="currentColor" stroke-width="1.5" />
<circle cx="50" cy="50" r="26" fill="none" stroke="currentColor" stroke-width="1" />
<text font-size="9" font-weight="600" letter-spacing="1.5" fill="currentColor"><textPath href={`#photo-stamp-${i}`}>{s.ring.toUpperCase()}</textPath></text>
<text x="50" y="55" text-anchor="middle" font-size="15" font-weight="700" fill="currentColor">{s.mark}</text>
</svg>
</li>
))}
</ul>
</div>
<div class="relative z-10 -mt-16 rounded-t-[2.5rem] px-gutter py-12 text-center" style="background: linear-gradient(var(--background-neutral-faded), var(--background-neutral-faded)), var(--background-page)">
{proofHeading && <p class="text-2xl font-semibold md:text-4xl">{proofHeading}</p>}
{proofStats && <p class="mt-3 text-neutral-faded tabular-nums">{proofStats}</p>}
</div>
</Section>
Show codeHide code
---
/**
* Photo stamps 01: A full-width photo with a centred eyebrow, heading, line and round button, a column of round stamps down one side and a rounded panel rising over the bottom edge.
*
* @block hero
* @order 54
*/
import { ArrowRight } 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 { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
/** @composable */
interface Props extends HeroContent {
/** The heading on the panel under the photo. @category content */
proofHeading?: string | null
/** The figures under that heading. @category content */
proofStats?: string | null
}
const {
eyebrow = 'Picked ripe · Packed the same day',
heading = 'The freshest fruit you can order online',
description = 'Orchard fruit, berries and greens from small farms. Shipped cold, anywhere in the country.',
primaryAction = { label: 'Shop all', href: '#' },
media = { src: 'https://picsum.photos/id/1018/2000/1300', alt: '', width: 2000, height: 1300 },
proofHeading = 'Trusted by families across the country',
proofStats = '120,000 orders shipped · 4.9 average rating · 300 partner farms',
} = Astro.props
// Stamps: words that run around the rim, and a short mark in the middle.
const stamps = [
{ ring: 'Grown slow · Picked ripe ·', mark: 'GS', tone: 'bg-primary text-on-primary' },
{ ring: 'Family farms · Since 2012 ·', mark: '12', tone: 'bg-page text-primary' },
{ ring: 'No sprays · No shortcuts ·', mark: '100%', tone: 'bg-primary-faded text-primary' },
{ ring: 'Shipped cold · Same week ·', mark: '48h', tone: 'bg-neutral text-on-neutral' },
]
---
<Section spacing="none" container={false} class="overflow-hidden">
<div class="relative isolate flex min-h-[44rem] flex-col items-center justify-center px-gutter pb-40 pt-24 text-center md:min-h-[52rem]">
{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" />}
<!-- Page colour washes down from the top so the text reads in either theme. -->
<div class="absolute inset-0 -z-10 bg-[linear-gradient(to_bottom,var(--background-page)_10%,color-mix(in_oklab,var(--background-page)_70%,transparent)_45%,transparent_80%)]" aria-hidden="true"></div>
<div class="flex max-w-4xl flex-col items-center gap-6">
{eyebrow && <Eyebrow color="neutral" class="tracking-widest">{eyebrow}</Eyebrow>}
<Heading as="h1" size="display" class="text-balance">{plain(heading)}</Heading>
{description && <p class="text-lg md:text-xl">{description}</p>}
{primaryAction && <Button href={primaryAction.href} size="large" rounded class="px-10 uppercase tracking-widest">{primaryAction.label}<ArrowRight slot="end" class="rtl:-scale-x-100" aria-hidden="true" /></Button>}
</div>
<ul class="absolute end-6 top-1/2 hidden -translate-y-1/2 flex-col gap-8 lg:flex" aria-hidden="true">
{stamps.map((s, i) => (
<li class:list={['grid size-28 place-items-center rounded-full shadow-md', s.tone]}>
<svg viewBox="0 0 100 100" class="size-full">
<defs><path id={`photo-stamp-${i}`} d="M50 50m-36 0a36 36 0 1 1 72 0a36 36 0 1 1-72 0" /></defs>
<circle cx="50" cy="50" r="46" fill="none" stroke="currentColor" stroke-width="1.5" />
<circle cx="50" cy="50" r="26" fill="none" stroke="currentColor" stroke-width="1" />
<text font-size="9" font-weight="600" letter-spacing="1.5" fill="currentColor"><textPath href={`#photo-stamp-${i}`}>{s.ring.toUpperCase()}</textPath></text>
<text x="50" y="55" text-anchor="middle" font-size="15" font-weight="700" fill="currentColor">{s.mark}</text>
</svg>
</li>
))}
</ul>
</div>
<div class="relative z-10 -mt-16 rounded-t-[2.5rem] px-gutter py-12 text-center" style="background: linear-gradient(var(--background-neutral-faded), var(--background-neutral-faded)), var(--background-page)">
{proofHeading && <p class="text-2xl font-semibold md:text-4xl">{proofHeading}</p>}
{proofStats && <p class="mt-3 text-neutral-faded tabular-nums">{proofStats}</p>}
</div>
</Section>Add
npx @ava-ui/apogee add hero/photo-stamps-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.