ProductPromo
apps/www/src/sections/ProductPromo.astro · 166 lines · group Models (product catalog)
ProductPromo — a product promo block: a photo, a citation, a pair of buttons and a row of figures. It is neither statistics nor a call to action, so it is a component of its own (Kirill, 2026-08-26). Until the 2026-08-26 refactor it went by the tag name `Stats`, which the shared statistics section also claimed.
In the fleet
Used on 1 site built with this engine.
Samples 1

We swapped eleven stations in one morning and did not open the manual once.
Tomas Berg, Björk & Sons
Introducing the N3 generation
The third generation measures leaf wetness as standard, doubles the radio range and holds a week of readings when the network is away.
- range to the gateway
- 4 km
- readings buffered
- 7 days
MDX of this sample
<ProductPromo
title="Introducing the N3 generation"
text={[
'The third generation measures leaf wetness as standard, doubles the radio range and holds a week of readings when the network is away.',
]}
imageName="photo-04.jpg"
citation="We swapped eleven stations in one morning and did not open the manual once."
citationSource="Tomas Berg, Björk & Sons"
stats={[
{ label: 'range to the gateway', value: '4 km' },
{ label: 'readings buffered', value: '7 days' },
]}
primaryButtonTitle="See the models"
primaryButtonLink="/stations/"
secondaryButtonTitle="Datasheet"
secondaryButtonLink="/downloads/"
/>Props
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
| stats | StatisticItem[] | yes | [] | |
| title | string | yes | ||
| text | string | string[] | yes | ||
| linkTitle | string | yes | ||
| imageName | string | yes | ||
| citation | string | yes | ||
| citationSource | string | |||
| primaryButtonTitle | string | 'Kontakta oss' | ||
| primaryButtonLink | string | '/contacts/' | ||
| secondaryButtonTitle | string | 'Buffertenhet' | ||
| secondaryButtonLink | string | '/models/buffering-unit/' |
Source apps/www/src/sections/ProductPromo.astro @ gitt.one
---
/**
* ProductPromo — a product promo block: a photo, a citation, a pair of
* buttons and a row of figures. It is neither statistics nor a call to
* action, so it is a component of its own (Kirill, 2026-08-26). Until the
* 2026-08-26 refactor it went by the tag name `Stats`, which the shared
* statistics section also claimed.
*/
import { Image } from 'astro:assets';
import { resolveImage } from '../lib/images';
interface StatisticItem {
label: string;
value: string;
}
interface Props {
stats: StatisticItem[];
title: string;
text: string | string[];
linkTitle: string;
imageName: string;
citation: string;
citationSource?: string;
primaryButtonTitle?: string;
primaryButtonLink?: string;
secondaryButtonTitle?: string;
secondaryButtonLink?: string;
}
const {
stats = [],
title,
text,
linkTitle,
imageName,
citation,
citationSource,
primaryButtonTitle = 'Kontakta oss',
primaryButtonLink = '/contacts/',
secondaryButtonTitle = 'Buffertenhet',
secondaryButtonLink = '/models/buffering-unit/',
} = Astro.props;
const image = await resolveImage(imageName);
const textItems = Array.isArray(text) ? text : [text];
---
<section class="relative mt-20">
<div class="items-center lg:mx-auto lg:grid lg:max-w-7xl lg:grid-cols-2 lg:gap-24 lg:px-8">
<div class="relative sm:py-16 lg:py-0">
<div aria-hidden="true" class="hidden sm:block lg:absolute lg:inset-y-0 lg:right-0 lg:w-screen">
<div class="absolute inset-y-0 right-1/2 w-full rounded-r-3xl bg-gray-50 lg:right-72"></div>
<svg
class="absolute left-1/2 top-8 -ml-3 lg:-right-8 lg:left-auto lg:top-12"
width={404}
height={392}
fill="none"
viewBox="0 0 404 392"
>
<defs>
<pattern
id="stats-dots-pattern"
x={0}
y={0}
width={20}
height={20}
patternUnits="userSpaceOnUse"
>
<rect x={0} y={0} width={4} height={4} class="text-gray-200" fill="currentColor"></rect>
</pattern>
</defs>
<rect width={404} height={392} fill="url(#stats-dots-pattern)"></rect>
</svg>
</div>
<div class="relative mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:max-w-none lg:px-0 lg:py-20">
<!-- Testimonial card -->
<div class="relative overflow-hidden rounded-2xl pb-10 pt-64 shadow-xl">
<Image
src={image}
alt={title}
widths={[400, 840]}
class="absolute inset-0 h-full w-full object-cover"
/>
<div class="relative bg-primary-700 px-8 py-4 text-white opacity-90">
<blockquote>
<div class="relative text-lg font-medium md:flex-grow">
<svg
class="absolute left-0 top-0 h-8 w-8 -translate-x-3 -translate-y-2 transform"
fill="currentColor"
viewBox="0 0 32 32"
aria-hidden="true"
>
<path
d="M9.352 4C4.456 7.456 1 13.12 1 19.36c0 5.088 3.072 8.064 6.624 8.064 3.36 0 5.856-2.688 5.856-5.856 0-3.168-2.208-5.472-5.088-5.472-.576 0-1.344.096-1.536.192.48-3.264 3.552-7.104 6.624-9.024L9.352 4zm16.512 0c-4.8 3.456-8.256 9.12-8.256 15.36 0 5.088 3.072 8.064 6.624 8.064 3.264 0 5.856-2.688 5.856-5.856 0-3.168-2.304-5.472-5.184-5.472-.576 0-1.248.096-1.44.192.48-3.264 3.456-7.104 6.528-9.024L25.864 4z"
></path>
</svg>
<p class="relative indent-8">{citation}</p>
</div>
{citationSource && <p class="mt-4 font-semibold">{citationSource}</p>}
</blockquote>
</div>
</div>
</div>
</div>
<div class="relative mx-auto max-w-md px-4 sm:max-w-3xl sm:px-6 lg:px-0">
<div class="pt-12 sm:pt-16 lg:pt-20">
<h2 class="text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl">
{title}
</h2>
<div class="mt-6 space-y-6 text-gray-500">
{
textItems.map((content, index) =>
index === 0 ? (
<p class="text-center text-lg">{content}</p>
) : (
<p class="text-base leading-7">{content}</p>
),
)
}
</div>
</div>
{
stats.length > 0 && (
<div class="mt-10">
<div class="grid grid-cols-2 gap-x-4 gap-y-8">
{stats.map((stat) => (
<dl class="border-t-2 border-gray-100 pt-6">
<dt class="text-base font-medium text-gray-500">{stat.label}</dt>
<dd class="text-3xl font-extrabold tracking-tight text-gray-900">{stat.value}</dd>
</dl>
))}
</div>
<div class="mt-10">
<a href="#" class="text-base font-medium text-primary-500">
{linkTitle} →
</a>
</div>
</div>
)
}
<div class="mt-10 sm:flex sm:justify-center lg:justify-start">
<div class="rounded-md shadow">
<a
href={primaryButtonLink}
class="flex w-full items-center justify-center whitespace-nowrap rounded-md border border-transparent bg-primary-600 px-8 py-3 text-base font-medium text-white hover:bg-primary-700 md:px-10 md:py-4 md:text-lg"
>
{primaryButtonTitle}
</a>
</div>
<div class="mt-3 rounded-md shadow sm:ml-3 sm:mt-0">
<a
href={secondaryButtonLink}
class="flex w-full items-center justify-center whitespace-nowrap rounded-md border border-transparent bg-white px-8 py-3 text-base font-medium text-primary-600 hover:bg-gray-50 md:px-10 md:py-4 md:text-lg"
>
{secondaryButtonTitle}
</a>
</div>
</div>
</div>
</div>
</section>Source links point to engine-astro@38c294d9. Samples are demo content: the components are the real ones, the copy and the pictures are made up.