Fanned cards 01
A centred heading over four service cards fanned out like a hand, each in a different brand fill, with a progress line under them; a swipe row on phones.
Last updated
---
/**
* Fanned cards 01: A centred heading over four service cards fanned out like a hand, each in a different brand fill; a swipe row on phones.
*
* @block hero
* @order 71
*/
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
/** @composable */
interface Props extends HeroContent {
/** The fanned cards; fills and tilts follow the card's position. @category content */
cards?: { kicker: string; title: string; text: string }[]
}
const {
heading = 'Helping brands show up as themselves, at the speed of culture',
cards = [
{ kicker: 'The personality', title: 'Brand', text: 'Designers and writers who make the looks your customers fall for, and the next ones they will.' },
{ kicker: 'The connection', title: 'Social', text: 'We turn followers into fans and scrolls into sales. The team is online at odd hours so your brand stays current.' },
{ kicker: 'The look', title: 'Production', text: 'Stylists, photographers, film crews and 3D artists who know the picture is everything.' },
{ kicker: 'The conversion', title: 'Interactive', text: 'Where brand becomes business: sites and apps that turn all that work into sales.' },
],
} = Astro.props
// ponytail: tuned for four cards; a fifth repeats the fills and the outer two tilt.
const tones = ['bg-primary text-on-primary', 'bg-primary-faded text-primary', 'bg-inverted text-on-inverted', 'bg-neutral text-neutral']
const tilt = (i: number) => (i === 0 ? 'md:-rotate-[8deg] md:translate-y-6' : i === cards.length - 1 ? 'md:rotate-[8deg] md:translate-y-6' : '')
---
<Section spacing="large" container={false} class="overflow-hidden">
<Heading as="h1" size="display" class="mx-auto max-w-4xl px-gutter text-center font-normal text-balance">{plain(heading)}</Heading>
<ul class="mt-12 flex snap-x snap-mandatory gap-3 overflow-x-auto px-gutter pb-6 md:mt-16 md:justify-center md:overflow-visible md:pb-10">
{cards.map((c, i) => (
<li class:list={['flex aspect-3/5 w-64 shrink-0 snap-center flex-col gap-3 rounded-2xl p-6 shadow-lg md:w-[22%] md:max-w-72 md:p-8', tones[i % tones.length], tilt(i)]}>
<span class="text-xs uppercase tracking-wide opacity-80">{c.kicker}</span>
<span class="text-3xl">{c.title}</span>
<span class="mt-8 text-lg leading-snug">{c.text}</span>
</li>
))}
</ul>
</Section>
Show codeHide code
---
/**
* Fanned cards 01: A centred heading over four service cards fanned out like a hand, each in a different brand fill; a swipe row on phones.
*
* @block hero
* @order 71
*/
import Section from '@/components/apogee/Section/Section.astro'
import Heading from '@/components/apogee/Heading/Heading.astro'
import { plain, type HeroContent } from '@/components/apogee/sections/_shared/spine.ts'
/** @composable */
interface Props extends HeroContent {
/** The fanned cards; fills and tilts follow the card's position. @category content */
cards?: { kicker: string; title: string; text: string }[]
}
const {
heading = 'Helping brands show up as themselves, at the speed of culture',
cards = [
{ kicker: 'The personality', title: 'Brand', text: 'Designers and writers who make the looks your customers fall for, and the next ones they will.' },
{ kicker: 'The connection', title: 'Social', text: 'We turn followers into fans and scrolls into sales. The team is online at odd hours so your brand stays current.' },
{ kicker: 'The look', title: 'Production', text: 'Stylists, photographers, film crews and 3D artists who know the picture is everything.' },
{ kicker: 'The conversion', title: 'Interactive', text: 'Where brand becomes business: sites and apps that turn all that work into sales.' },
],
} = Astro.props
// ponytail: tuned for four cards; a fifth repeats the fills and the outer two tilt.
const tones = ['bg-primary text-on-primary', 'bg-primary-faded text-primary', 'bg-inverted text-on-inverted', 'bg-neutral text-neutral']
const tilt = (i: number) => (i === 0 ? 'md:-rotate-[8deg] md:translate-y-6' : i === cards.length - 1 ? 'md:rotate-[8deg] md:translate-y-6' : '')
---
<Section spacing="large" container={false} class="overflow-hidden">
<Heading as="h1" size="display" class="mx-auto max-w-4xl px-gutter text-center font-normal text-balance">{plain(heading)}</Heading>
<ul class="mt-12 flex snap-x snap-mandatory gap-3 overflow-x-auto px-gutter pb-6 md:mt-16 md:justify-center md:overflow-visible md:pb-10">
{cards.map((c, i) => (
<li class:list={['flex aspect-3/5 w-64 shrink-0 snap-center flex-col gap-3 rounded-2xl p-6 shadow-lg md:w-[22%] md:max-w-72 md:p-8', tones[i % tones.length], tilt(i)]}>
<span class="text-xs uppercase tracking-wide opacity-80">{c.kicker}</span>
<span class="text-3xl">{c.title}</span>
<span class="mt-8 text-lg leading-snug">{c.text}</span>
</li>
))}
</ul>
</Section>Add
npx @ava-ui/apogee add hero/fanned-cards-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.