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

Open block
Show 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.