Component library
Back to catalog

Social proof closer

A dark closing section stacking a pull quote beside review artwork, a row of headline figures, and a call to action over a soft glow.

sectiontestimonialstatsctadark

Open preview in a new tab

Use this component

Three ways to get Social proof closer into one of your own projects. The first two carry the component's files with them, so nothing has to fetch this page.

Start here

Placement prompt

The whole thing: what to do, where it goes, what must not change, and every one of the component's 2 files appended underneath with the path each belongs at. Paste it into Claude Code inside the destination project — it fetches nothing and needs nothing else.

Read it first — the files are added underneath when you copy
Add Social proof closer from my Astro Component Library to this Astro project.

Library page, for reference only:
https://astro.baysixmedia.com/components/social-proof-closer/

Place it in:
[ENTER PAGE, FILE, OR ROUTE]

Position:
[ENTER WHERE IT SHOULD APPEAR]

Every file you need is appended below, each fenced with the path it belongs at. Write them there, relative to the project root. Do not import anything from the deployed library at runtime, and do not fetch the library page — the files below are the source of truth.

Adapt its demo content, links, colors, typography, and images to this project through documented props where possible. Preserve its responsive behavior, accessibility, interaction logic, reduced-motion behavior, and support for multiple instances. Avoid changing unrelated project code.

Test it at desktop, tablet, and mobile widths. Run the project's available check and build commands. Report the files changed and any differences from the library version. Do not commit or deploy.

Install kit

The 2 files this component needs and nothing else, each fenced with the path it belongs at, followed by what the library's own verification covered and what it did not. No instructions attached — for placing it yourself, or handing it to something that has its own.

Read it first
Install kit: "Social proof closer"
Library page: https://astro.baysixmedia.com/components/social-proof-closer/
Files: 2 required, 1 supporting

Write each block below to the path named in its header, relative to the project root. A block marked (required) is one the component does not work without; (supporting) is documentation. Nothing here needs downloading except a block that says so in as many words.

===== FILE (required): src/components/SocialProofCloser.astro =====
---
/**
 * Social proof closer
 *
 * A dark full-bleed section that stacks three proof devices and then closes:
 * a pull quote beside review artwork, a row of headline figures, and a tinted
 * band carrying the final heading and call to action over a soft glow.
 *
 * No JavaScript. The reference section this was measured from has no buttons,
 * tabs, accordions, carousels, scroll triggers or entrance animations — its
 * only transition is a 0.15s colour change on the button — so none of that is
 * invented here either.
 *
 * Self-contained: no imports, no packages, no global stylesheet. Every colour
 * and measurement is a `--pc-*` custom property on the root.
 */

export interface ProofStat {
	/** The figure itself, e.g. "40k+". */
	value: string;
	/** The line under it. */
	label: string;
}

interface Props {
	/**
	 * Small masthead above the quote, e.g. a publication name. Omit it and
	 * there is none: a real quote is never attributed to the demo's fictional
	 * publication.
	 */
	source?: string;
	/**
	 * The pull quote. Rendered inside a `<blockquote>`. Omit it and there is
	 * none: the component never supplies a quote of its own.
	 */
	quote?: string;
	/** Attribution under the quote. Omit it (or `""`) and there is none. */
	attribution?: string;
	/** Link under the quote. Needs `quoteHref` to become a link. */
	quoteLinkLabel?: string;
	/** Destination for the quote link. Without one the label is plain text. */
	quoteHref?: string;
	/** Artwork beside the quote. Omit for a drawn placeholder. */
	image?: string;
	/** Alternative text for the artwork. */
	imageAlt?: string;
	/** Aspect ratio of the artwork frame. */
	imageRatio?: string;
	/** The headline figures. None by default: `[]` hides the row. */
	stats?: ProofStat[];
	/** Small print under the figures. Omit it (or `""`) and there is none. */
	disclaimer?: string;
	/** Heading in the closing band. Omit it and there is none. */
	closingTitle?: string;
	/** Call to action label. Omit it (or `""`) and there is no button. */
	ctaLabel?: string;
	/** Destination. Without one the label renders as text, not a link. */
	ctaHref?: string;
	/** For pages where `h2` is the wrong level for the closing heading. */
	headingLevel?: "h1" | "h2" | "h3";
	/** Ground behind the whole section. */
	background?: string;
	/** Tint over the closing band. */
	closingTint?: string;
	/** The glow behind the closing band. `transparent` removes it. */
	glow?: string;
	/** Primary text colour. */
	ink?: string;
	/** Secondary text: attribution, labels, small print. */
	muted?: string;
	/** Button fill and link colour. */
	accent?: string;
	/** Text on the button. */
	accentInk?: string;
	/** Height of the closing band once there is room. */
	closingHeight?: string;
	/** Caps the figures row and centres it. */
	statsMaxWidth?: string;
	/** Caps the whole content column. */
	maxWidth?: string;
	/** Added to the root `<section>`. */
	class?: string;
	/** Root element id. */
	id?: string;
}

const {
	source = "",
	quote = "",
	attribution = "",
	quoteLinkLabel = "",
	quoteHref = "",
	image = undefined,
	imageAlt = "",
	imageRatio = "15 / 8",
	stats = [],
	disclaimer = "",
	closingTitle = "",
	ctaLabel = "",
	ctaHref = "",
	headingLevel = "h2",
	background = "#3b2b7d",
	closingTint = "rgb(35 11 89 / 60%)",
	glow = "rgb(150 130 255 / 45%)",
	ink = "#f0f0f2",
	muted = "rgb(255 255 255 / 50%)",
	accent = "#4840bb",
	accentInk = "#ffffff",
	closingHeight = "300px",
	statsMaxWidth = "1000px",
	maxWidth = "1233px",
	class: className,
	id,
} = Astro.props;

const Heading = headingLevel;

/*
 * Each block renders only from what it was given, and with nothing at all the
 * section renders nothing. The drawn artwork placeholder still stands in for a
 * missing image beside a real quote, but never on its own.
 */
const hasSplit = Boolean(quote || source || attribution || quoteLinkLabel || image);
const hasStats = stats.length > 0 || Boolean(disclaimer);
const hasTop = hasSplit || hasStats;
const hasClosing = Boolean(closingTitle || ctaLabel);

const style = [
	`--pc-bg:${background}`,
	`--pc-tint:${closingTint}`,
	`--pc-glow:${glow}`,
	`--pc-ink:${ink}`,
	`--pc-muted:${muted}`,
	`--pc-accent:${accent}`,
	`--pc-accent-ink:${accentInk}`,
	`--pc-closing-h:${closingHeight}`,
	`--pc-stats-max:${statsMaxWidth}`,
	`--pc-max:${maxWidth}`,
	`--pc-ratio:${imageRatio}`,
].join(";");
---

{(hasTop || hasClosing) && (
<section class:list={["proof", className]} id={id} style={style}>
	{hasTop && (
	<div class="top">
		<div class="inner">
			{/* Quote left, artwork right — one column until there is genuinely
			    room for two, which the reference puts at 1280. */}
			{hasSplit && (
			<div class="split">
				<div class="quote-col">
					{source && <p class="source">{source}</p>}
					{quote && (
						<blockquote class="quote">
							<span class="mark" aria-hidden="true">&ldquo;</span>
							<p class="quote-text">{quote}</p>
						</blockquote>
					)}
					{
						quoteLinkLabel &&
							(quoteHref ? (
								<a class="quote-link" href={quoteHref}>
									{quoteLinkLabel}
								</a>
							) : (
								/* No destination, so no link: a dead anchor that looks
								   clickable is worse than plain text. */
								<span class="quote-link is-static">{quoteLinkLabel}</span>
							))
					}
					{attribution && <p class="note">{attribution}</p>}
				</div>

				<div class="media-col">
					<figure class="shot">
						{
							image ? (
								<img src={image} alt={imageAlt} loading="lazy" decoding="async" />
							) : (
								/* Drawn, not loaded: with no artwork yet the block still has
								   to look deliberate rather than broken. */
								<span class="placeholder" aria-hidden="true">
									<span class="ph-card"></span>
									<span class="ph-card"></span>
									<span class="ph-card"></span>
								</span>
							)
						}
					</figure>
				</div>
			</div>
			)}

			{
				hasStats && (
					<div class="stats-wrap">
						{stats.length > 0 && (
							<ul class="stats">
								{stats.map((stat) => (
									<li class="stat">
										<span class="stat-value">{stat.value}</span>
										<span class="stat-label">{stat.label}</span>
									</li>
								))}
							</ul>
						)}
						{disclaimer && <p class="note disclaimer">{disclaimer}</p>}
					</div>
				)
			}
		</div>
	</div>
	)}

	{hasClosing && (
	<div class="closing">
		{/* The reference bleeds an oversized artwork off the corner behind this
		    band and clips it. A radial gradient does the same job with no file
		    to ship and no resolution to pick. */}
		<span class="glow" aria-hidden="true"></span>
		<div class="closing-inner">
			{closingTitle && <Heading class="closing-title">{closingTitle}</Heading>}
			{
				ctaLabel &&
					(ctaHref ? (
						<a class="cta" href={ctaHref}>
							{ctaLabel}
						</a>
					) : (
						<span class="cta is-static">{ctaLabel}</span>
					))
			}
		</div>
	</div>
	)}
</section>
)}

<style>
	.proof {
		box-sizing: border-box;
		background: var(--pc-bg);
		color: var(--pc-ink);
		font-family:
			system-ui,
			-apple-system,
			"Segoe UI",
			Roboto,
			sans-serif;
		font-size: 16px;
		line-height: 1.5;
		text-align: start;
	}

	/* Set rather than inherited, so a destination site's global rules cannot
	   change the geometry this section is built on. */
	.proof *,
	.proof *::before,
	.proof *::after {
		box-sizing: border-box;
	}

	.proof p,
	.proof ul,
	.proof li,
	.proof figure,
	.proof blockquote,
	.proof h1,
	.proof h2,
	.proof h3 {
		margin: 0;
		padding: 0;
	}

	.proof ul {
		list-style: none;
	}

	.proof img {
		display: block;
		max-width: 100%;
	}

	/* --- upper block ------------------------------------------------------- */

	.top {
		padding: 64px 16px;
	}

	.inner {
		display: flex;
		flex-direction: column;
		gap: 48px;
		max-width: var(--pc-max);
		margin-inline: auto;
	}

	.split {
		display: grid;
		grid-template-columns: 1fr;
		gap: 32px;
		align-items: center;
	}

	.quote-col {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 16px;
		min-width: 0;
		text-align: center;
	}

	.source {
		color: var(--pc-ink);
		font-size: 0.8125rem;
		font-weight: 700;
		letter-spacing: 0.16em;
		text-transform: uppercase;
		opacity: 0.85;
	}

	.quote {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 4px;
	}

	.mark {
		display: block;
		margin-bottom: -0.15em;
		color: var(--pc-ink);
		font-family: Georgia, "Times New Roman", serif;
		font-size: 3.5rem;
		font-weight: 700;
		line-height: 0.7;
		opacity: 0.6;
	}

	.quote-text {
		max-width: 44ch;
		font-size: 1.125rem;
		line-height: 1.2;
	}

	.quote-link {
		color: var(--pc-muted);
		font-size: 0.875rem;
		text-decoration: underline;
		text-underline-offset: 3px;
		transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);
	}

	.quote-link:hover {
		color: var(--pc-ink);
	}

	.quote-link.is-static {
		text-decoration: none;
		cursor: default;
	}

	.note {
		max-width: 62ch;
		color: var(--pc-muted);
		font-size: 0.875rem;
		line-height: 1.15;
	}

	/* --- artwork ----------------------------------------------------------- */

	.media-col {
		min-width: 0;
	}

	.shot {
		overflow: hidden;
		aspect-ratio: var(--pc-ratio);
		border-radius: 14px;
		background: rgb(255 255 255 / 6%);
	}

	.shot img {
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.placeholder {
		display: flex;
		flex-direction: column;
		justify-content: center;
		gap: 10px;
		width: 100%;
		height: 100%;
		padding: 8%;
		background: linear-gradient(
			160deg,
			rgb(255 255 255 / 10%),
			rgb(255 255 255 / 3%)
		);
	}

	.ph-card {
		display: block;
		height: 18%;
		border-radius: 8px;
		background: rgb(255 255 255 / 16%);
	}

	.ph-card:first-child {
		width: 82%;
	}

	.ph-card:nth-child(2) {
		width: 96%;
		background: rgb(255 255 255 / 24%);
	}

	.ph-card:last-child {
		width: 68%;
	}

	/* --- figures ------------------------------------------------------------ */

	.stats-wrap {
		display: flex;
		flex-direction: column;
		gap: 12px;
		align-items: center;
		width: 100%;
		max-width: var(--pc-stats-max);
		margin-inline: auto;
	}

	/*
	 * One line, always — the row never wraps. Below the wide breakpoint it
	 * carries three figures and the rest are hidden outright, which is the
	 * reference's behaviour and the only way a single row stays legible on a
	 * phone.
	 */
	.stats {
		display: flex;
		flex-wrap: nowrap;
		justify-content: center;
		gap: 12px;
		width: 100%;
	}

	/*
	 * `1 1 0` rather than a pixel basis: every figure takes an equal share of
	 * whatever width there is and shrinks with it. A basis in pixels is what
	 * makes a `nowrap` row overflow, and what made the wrapping version break
	 * into two rows at 390px and four at 320px.
	 */
	.stat {
		display: flex;
		flex: 1 1 0;
		flex-direction: column;
		align-items: center;
		gap: 6px;
		min-width: 0;
		text-align: center;
	}

	.stat-value,
	.stat-label {
		overflow-wrap: break-word;
	}

	/*
	 * Below the breakpoint only the first three are shown. `display: none`, so
	 * the figure leaves the layout and the accessibility tree together and a
	 * screen-reader user is told exactly what a sighted user is shown — rather
	 * than being clipped to nothing while still being announced.
	 *
	 * Pass more than three figures and the extras only appear on wide screens.
	 */
	@media (max-width: 1139.98px) {
		.stat:nth-child(n + 4) {
			display: none;
		}
	}

	.stat-value {
		font-size: 1.5rem;
		font-weight: 700;
		line-height: 0.9;
		letter-spacing: -0.03em;
	}

	.stat-label {
		color: var(--pc-muted);
		font-size: 0.875rem;
		line-height: 1.15;
	}

	.disclaimer {
		text-align: center;
	}

	/* --- closing band -------------------------------------------------------- */

	.closing {
		position: relative;
		display: flex;
		align-items: center;
		justify-content: center;
		overflow: hidden;
		min-height: var(--pc-closing-h);
		padding: 48px 16px;
		background: var(--pc-tint);
	}

	.glow {
		position: absolute;
		/* Oversized and pushed off the corner, then clipped — the same trick the
		   reference uses with a bitmap, done with a gradient. */
		top: -70%;
		right: -35%;
		width: 110%;
		aspect-ratio: 2 / 1;
		border-radius: 50%;
		background: radial-gradient(circle, var(--pc-glow), transparent 70%);
		pointer-events: none;
	}

	.closing-inner {
		position: relative;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 24px;
		width: 100%;
		max-width: var(--pc-max);
		text-align: center;
	}

	.closing-title {
		font-size: clamp(2rem, 1.2rem + 3.6vw, 3.875rem);
		font-weight: 700;
		line-height: 0.9;
		letter-spacing: -0.03em;
	}

	.cta {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		padding: 12px 20px;
		border-radius: 6px;
		background: var(--pc-accent);
		color: var(--pc-accent-ink);
		font-size: 1.125rem;
		font-weight: 500;
		text-decoration: none;
		transition: filter 150ms cubic-bezier(0.4, 0, 0.2, 1);
	}

	.cta:hover {
		filter: brightness(1.12);
	}

	.cta.is-static {
		background: rgb(255 255 255 / 12%);
		color: var(--pc-ink);
		cursor: default;
	}

	/* One ring for every control. The reference leaves the browser default. */
	.quote-link:focus-visible,
	.cta:focus-visible {
		outline: 3px solid var(--pc-ink);
		outline-offset: 3px;
		border-radius: 3px;
	}

	/* --- wider ---------------------------------------------------------------- */

	@media (min-width: 768px) {
		.stats-wrap {
			padding-inline: 40px;
		}
	}

	/*
	 * 1140px, measured: the reference shows three figures at 1139 and four at
	 * 1140. It is a custom breakpoint, not the 1280 the four test widths might
	 * suggest — bisected across 1100–1200 to find it.
	 */
	@media (min-width: 1140px) {
		.stat-value {
			font-size: 2rem;
		}
	}

	@media (min-width: 1280px) {
		.top {
			padding-block: 64px;
		}

		.split {
			grid-template-columns: 1fr 1fr;
			gap: 64px;
		}
	}

	/* --- reduced motion --------------------------------------------------------- */

	/*
	 * Only colour transitions exist here, and they go. The reference keeps its
	 * 0.15s button transition running regardless of the preference.
	 */
	@media (prefers-reduced-motion: reduce) {
		.quote-link,
		.cta {
			transition: none;
		}
	}
</style>

===== END src/components/SocialProofCloser.astro =====

===== FILE (supporting): src/components/SocialProofCloser.md =====
# Social proof closer

A dark, full-bleed closing section that stacks three proof devices and then
asks for the click: a pull quote beside review artwork, a row of headline
figures, and a tinted band carrying the final heading and call to action over a
soft glow.

**It ships no JavaScript.** The reference section has no buttons, tabs,
accordions, carousels, scroll triggers or entrance animations — its only
transition is a 0.15s colour change on its button — so none of that is invented
here either.

Catalog page: `/components/social-proof-closer`

## What the selector matched

A single `<section>` on a live marketing homepage, matched by a five-level
child selector that returned **exactly one element at all four inspection
widths** — 1280, 834, 390 and 320. There was no responsive markup swap: the
same section is restyled at every width.

At 1280 it measured **1265 × 984** on a deep violet ground, and contained:

```
section
├─ div  (padding 64px 16px)
│  └─ div
│     ├─ div.grid          ← 1 column, 2 at ≥1280 (584.33px each, gap 64px)
│     │  ├─ div            ← masthead logo, pull quote, link, footnote
│     │  └─ div            ← review artwork
│     └─ div               ← max-width 1000px, padding 0 40px
│        └─ div            ← four figures + legal small print
└─ div  (height 300px, overflow hidden, tinted)
   ├─ img                  ← oversized decorative artwork, clipped
   └─ div                  ← closing heading + button
```

## Component boundary

**Exactly the matched `<section>` and nothing else.** No navigation,
announcement bar, adjacent section, footer, disclosure overlay or cookie
control was inspected or reproduced. Both of the section's own direct children —
the upper block and the closing band — are inside the selector, so no boundary
expansion was needed.

## Copying it into another Astro project

Copy **`SocialProofCloser.astro`**. That is the whole component.

| What it needs | Status |
| :--- | :--- |
| Imports from this catalog | none |
| npm packages | none |
| Astro built-ins (`astro:assets`, …) | none |
| Global stylesheet | none — every value is a `--pc-*` property on the root |
| Webfonts | none — system stack, plus a serif fallback stack for the quote mark |
| **JavaScript** | **none at all** |
| Bundled images | **none in the component** — the demo ships one; see below |
| Network requests | none. Verified: zero external requests from the preview |
| Host requirement | Astro compiles `.astro` files |

### Exact steps

1. Copy `SocialProofCloser.astro` into your components folder.
2. Render it at the end of a page:

```astro
---
import SocialProofCloser from "../components/SocialProofCloser.astro";
---

<SocialProofCloser
  source="Trade Weekly"
  quote="One sentence of praise, short enough to read in a glance."
  stats={[
    { value: "40k+", label: "Teams onboarded" },
    { value: "120k", label: "Records processed" },
  ]}
  closingTitle="Ready when you are."
  ctaLabel="Create an account"
  ctaHref="/signup"
/>
```

3. There is no step three. Give it the full page width — it paints its own
   padding and ground edge to edge.

## The demo's image dependency

```
public/social-proof-closer/review-cards-1200.webp   (15 KB, 1200 × 640)
```

Nothing in the `.astro` file imports it — it is passed by URL — so it will not
follow the component on its own. The catalog's source panel lists it under
**assets**, and the copy button includes it.

**Omit `image` entirely and the component draws its own placeholder**: three
translucent rounded bars in the same frame, at the same aspect ratio. It looks
deliberate rather than broken, so the artwork can be wired up later.

### Where the demo image came from

It is **original**, generated for this catalog. Three overlapping rounded cards
with amber five-star rows, text bars and coloured avatar dots, on a violet
radial ground with a bloom and a noise overlay — built as one HTML page of CSS
and captured headless at 1200 × 640, then encoded as WebP at quality 82.

Nothing was downloaded, traced or recreated from the reference. The reference's
own artwork is a screenshot of real client reviews; this is an abstract stand-in
that could not be mistaken for one.

## Props

| Prop | Type | Default | What it does |
| :--- | :--- | :--- | :--- |
| `source` | `string` | none | Small masthead above the quote. Omit it and there is none — a real quote is never attributed to the demo's fictional publication. The catalog demo passes `"The Ledger Review"` explicitly. |
| `quote` | `string` | `""` | The pull quote, inside a `<blockquote>`. Omitted, there is none. |
| `attribution` | `string` | `""` | Footnote under the quote. Omitted, there is none. |
| `quoteLinkLabel` | `string` | `""` | Link under the quote. `""` hides it. |
| `quoteHref` | `string` | `""` | Destination. **Without one the label is plain text.** |
| `image` | `string` | — | Artwork beside the quote. Omit for the placeholder. |
| `imageAlt` | `string` | `""` | Alternative text. Empty means decorative. |
| `imageRatio` | `string` | `"15 / 8"` | Aspect ratio of the artwork frame. |
| `stats` | `ProofStat[]` | `[]` | The headline figures. Empty hides the row. |
| `disclaimer` | `string` | `""` | Small print under the figures. Omitted, there is none. |
| `closingTitle` | `string` | `""` | Heading in the closing band. Omitted, there is none. |
| `ctaLabel` | `string` | `""` | Button label. Omitted, there is no button. |
| `ctaHref` | `string` | `""` | Destination. **Without one the label is plain text.** |
| `headingLevel` | `"h1" \| "h2" \| "h3"` | `"h2"` | Level of the closing heading. |
| `background` | `string` | `"#3b2b7d"` | Ground behind the whole section. |
| `closingTint` | `string` | `"rgb(35 11 89 / 60%)"` | Tint over the closing band. |
| `glow` | `string` | `"rgb(150 130 255 / 45%)"` | The glow behind the band. `transparent` removes it. |
| `ink` | `string` | `"#f0f0f2"` | Primary text. |
| `muted` | `string` | `"rgb(255 255 255 / 50%)"` | Labels, footnotes, small print. |
| `accent` | `string` | `"#4840bb"` | Button fill. |
| `accentInk` | `string` | `"#ffffff"` | Text on the button. |
| `closingHeight` | `string` | `"300px"` | Minimum height of the closing band. |
| `statsMaxWidth` | `string` | `"1000px"` | Caps the figures row. |
| `maxWidth` | `string` | `"1233px"` | Caps the content column. |
| `class` | `string` | — | Added to the root `<section>`. |
| `id` | `string` | — | Root element id. |

`ProofStat` is `{ value: string; label: string }`.

**Nothing is supplied for you.** Every content prop defaults to empty, and each
block renders only from what it is given: no quote, source, link, attribution
or image means no quote block; no figures and no disclaimer means no figures
row; no closing title and no call to action means no closing band. Pass none of
them and the component renders nothing at all. The drawn artwork placeholder
still stands in for a missing `image` beside a real quote, but never on its own.
The catalog demo passes all of its copy, figures and link explicitly through
the registry's demo `props`.

## Reference measurements

| | 1280 | 834 | 390 | 320 |
| :--- | :--- | :--- | :--- | :--- |
| Quote / artwork | 2 columns, 584.33px each, gap 64px | 1 column | 1 column | 1 column |
| Figure size | 32px / 700 | 24px | 24px | 24px |
| Figures shown | 4 | **3** | **3** | **3** |
| Figure cell width (this component) | 221px | 233px | 111px | 88px |
| Figures wrapper | max-width 1000px, padding 0 40px | padding 0 40px | no padding | no padding |
| Closing band | 300px tall | 300px | 300px | 300px |
| Closing heading | 62px / 700, line-height 90%, tracking −0.03em | 42px | 42px | 42px |
| Button | 139 × 46, padding 12px 20px, radius 6px | same | same | same |

Section ground `rgb(59, 43, 125)`; closing band tint `rgba(35, 11, 89, 0.6)`;
button fill `rgb(72, 64, 187)`.

**The reference drops a figure below 1140px.** Its row is `flex-nowrap` and one
cell is wrapped in `hidden xl:contents` — a real `display: none` — so at 834, 390
and 320 only three figures are rendered at all. Bisecting 1100–1200 put the
threshold exactly at **1140px**: three figures at 1139, four at 1140. It is a
custom breakpoint, not the 1280 the four common test widths might suggest.

This component reproduces that: **the row never wraps, and below 1140px only the
first three figures are shown.**

One deliberate generalisation: the reference hides its *third* figure and keeps
its fourth, which only makes sense for that particular copy. This component
hides from the **fourth onward** (`.stat:nth-child(n + 4)`), which is the rule
that holds however many figures you pass.

| Width | Figures | Cell width | Rows |
| :--- | :--- | :--- | :--- |
| 1280 | 4 | 221px | 1 |
| 1279 | 4 | 221px | 1 |
| 1140 | 4 | 221px | 1 |
| 1139 | 3 | 299px | 1 |
| 1024 | 3 | 296px | 1 |
| 834 | 3 | 233px | 1 |
| 390 | 3 | 111px | 1 |
| 320 | 3 | 88px | 1 |

The hidden figures use `display: none`, so they leave the layout and the
accessibility tree together — a screen-reader user hears exactly the figures a
sighted user sees. They are not clipped, not zero-sized and not merely
transparent.

**Pass at most three figures if every one of them matters on a phone.** Anything
past the third is desktop-only.

### How the row stays on one line

Each cell is `flex: 1 1 0` — an equal share of whatever width exists, shrinking
with it — rather than a pixel basis. A pixel basis is what makes a `nowrap` row
overflow its container, and what made an earlier wrapping version of this row
break into two rows at 390px and four at 320px.

## Interactions and animation

There are none to speak of, and that is a measured finding rather than an
omission. In the reference:

| Checked | Result |
| :--- | :--- |
| `<button>`, `<details>`, `<input>`, `role="tab"` | **0 of each** |
| Elements with a CSS animation | **0** |
| Elements with a transform | **0** |
| Elements below full opacity | **0** |
| Running animations | **0** |
| Scroll-triggered entrance | **none** — opacity and transform counts identical before and after the section entered view |
| Transitions | **1** — the button, `0.15s cubic-bezier(0.4, 0, 0.2, 1)` |
| Button hover | no measurable change in background, colour or transform |

This component keeps the one real transition, applies the same duration and
curve to the quote link, and gives the button a `brightness(1.12)` hover so the
control actually acknowledges the pointer.

## Accessibility

- **The quote is a `<blockquote>`** with the decorative quote glyph marked
  `aria-hidden`, so a screen reader gets the sentence and not a stray character.
- **The figures are a list** — `<ul>` of `<li>`, because that is what a row of
  parallel figures is.
- **Visible focus**: a 3px `ink` outline at a 3px offset on `:focus-visible`,
  on both the quote link and the button. *The reference leaves the browser
  default — a white `auto` outline at roughly 2.67px.*
- **A missing destination never produces a dead link.** `quoteHref` or `ctaHref`
  left empty renders a `<span>`, dimmed and out of the tab order, rather than an
  `<a href="#">`.
- **`headingLevel`** keeps the document outline correct for the closing heading.
- **Everything is keyboard reachable**: **two** anchors in this component, both
  focusable and both activated by Enter natively. There is nothing else to
  operate.

  For the record, because the two counts are easy to confuse: **the reference
  section has three anchors, this component has two.** The reference's are
  "Read the article" (an external PDF), "investor relations page" (an inline
  link inside its legal small print) and "Open account". This component
  reproduces the first and the third as `quoteHref` and `ctaHref`. The middle
  one is absent because the disclosure paragraph it sits in is a financial
  legal notice, which this component deliberately does not reproduce — its
  `disclaimer` prop is plain text with no link.
- **Contrast** on the defaults: heading and quote are `#f0f0f2` on `#3b2b7d`
  (10.9:1); the muted tone is white at 50% over the same ground; the button is
  white on `#4840bb` (8.0:1).

### Reduced motion

Under `prefers-reduced-motion: reduce` both colour transitions compute to `0s`
and no animation runs. All content stays — nothing here depends on motion to
become readable. *The reference ignores the preference; its 0.15s button
transition keeps running.*

### Without JavaScript

There is no JavaScript, so there is nothing to degrade. Verified with scripting
disabled: all four figures visible, quote visible, button visible, both links
present, 543 characters of text, no horizontal overflow.

## Multiple instances

**Verified with two on one page** — one with the demo artwork and defaults, one
with a teal palette, two figures, an `h3` closing heading, no source, no
attribution, no quote link, no disclaimer, a 220px band and an empty `ctaHref`:

- Both rendered independently with their own grounds, accents, figure counts,
  heading levels and band heights.
- The second correctly rendered its call to action as a non-focusable
  `<span class="cta is-static">` rather than a link.
- The second correctly fell back to the drawn placeholder with no `image`.
- **No ids are emitted unless you pass one**, so duplicates are impossible.
- No shared state exists — there is no script.

## Customisation

Everything visual is a custom property on the root:

```css
.my-closer {
  --pc-bg: #0f172a;
  --pc-tint: rgb(2 6 23 / 70%);
  --pc-glow: rgb(56 189 248 / 40%);
  --pc-accent: #0ea5e9;
  --pc-ink: #f8fafc;
}
```

Pass `class="my-closer"` to attach it, or use the equivalent props inline.

The section is written to survive a hostile host stylesheet: `box-sizing` is set
on every descendant rather than inherited, margins and padding are zeroed on the
headings, paragraphs, lists, figures and blockquote it owns, `list-style` is
removed explicitly, and `font-size`, `line-height` and `text-align` are set on
the root so a host's `body { line-height: 2 }` cannot stretch the band past its
`closingHeight`.

## Original content

The component has no copy of its own. Every word of the catalog demo's copy
(passed from the registry, not built in) is fictional and non-financial. There
are no real publication names, company names, product names, ratings, awards,
testimonials, client counts, asset totals, yields, performance figures or legal
disclosures.

The demo figures — `40k+`, `120k`, `9 yrs`, `NBM` — are labelled as sample,
demo, made-up and placeholder in their own captions, and the demo's disclaimer
says so in plain words. The masthead reads "The Ledger Review", which is
invented. None of it reaches a page that does not pass it. If your real content
involves regulated claims, that is your compliance review to run, not this
component's.

## Differences from the reference

| | Reference | Here |
| :--- | :--- | :--- |
| Which figure is dropped | its **third** | its **fourth and beyond** |
| Reduced motion | ignored | both transitions off |
| Focus ring | browser default | 3px ink at 3px offset |
| Button hover | no measurable change | `brightness(1.12)` |
| Missing link destination | n/a | renders as text, never a dead anchor |
| Glow behind the band | an oversized bitmap, clipped | a radial gradient, clipped |
| Masthead | a real publication's logo image | a fictional wordmark set in type |
| Quote glyph | an image | a text character, `aria-hidden` |
| Inline link in the small print | one, to an investor-relations page | none — that disclosure is not reproduced |

The glow is worth a note: the reference bleeds a 2024 × 827 bitmap off the
corner at `-210px / -1000px` and clips it with `overflow: hidden`. A radial
gradient reproduces the same soft corner wash with no file to ship, no
resolution to choose and no second request.

## Originality

Every line of markup and CSS here was written for this catalog. The reference
was measured in a browser — computed styles, bounding boxes, interaction probes
and animation counts — not copied. No source, class names, utility framework,
wording, brand or product names, logos, fonts, screenshots, illustrations,
external links, analytics or third-party scripts came from it. The one demo
image is an original CSS scene generated here, and the component makes no
network requests of its own.

===== END src/components/SocialProofCloser.md =====

===== FILE (required): public/social-proof-closer/review-cards-1200.webp =====
[binary asset — download https://astro.baysixmedia.com/social-proof-closer/review-cards-1200.webp and save it to public/social-proof-closer/review-cards-1200.webp]

--- VERIFICATION ---
This component is not enrolled in the library's verification system, so there is no
recorded evidence about it either way. Treat it as unverified and test it yourself.

--- TEST IN THIS PROJECT AFTER INSTALLING ---
- Horizontal overflow at 1440, 1280, 1024, 834, 390 and 320px.
- Keyboard operation end to end, including visible focus.
- The no-JavaScript render.
- prefers-reduced-motion: reduce.
- Two instances on one page, if this project renders more than one.
- If this project uses <ClientRouter />: teardown and reinitialisation across a real navigation.

Component reference

A fragment for a request you write yourself, e.g. “I want to add [paste] to Section 3 of my homepage.” It names the component, links its page, and fixes what must not change — the design build and its effects — while leaving branding and content open. It does not carry the code.

Read it first
“Social proof closer” from my Astro Component Library:
https://astro.baysixmedia.com/components/social-proof-closer/

Use the existing library component as the implementation source. Preserve its design structure, layout, proportions, spacing system, responsive behavior, visual effects, animations, transitions, hover states, interactions, and accessibility.

The copy, colors, typography, images, and other content may be adapted to match the destination website. Do not redesign, simplify, or reinterpret the component’s underlying design build or effects.

src/components/library/SocialProofCloser.astro

This component is not one file on its own

  • assetsCopy these across too — they are referenced by URL, so nothing in the source above points at them: public/social-proof-closer/review-cards-1200.webp. Leave them behind and those URLs resolve to nothing on the other end. What each component does without them is in its README.
---
/**
 * Social proof closer
 *
 * A dark full-bleed section that stacks three proof devices and then closes:
 * a pull quote beside review artwork, a row of headline figures, and a tinted
 * band carrying the final heading and call to action over a soft glow.
 *
 * No JavaScript. The reference section this was measured from has no buttons,
 * tabs, accordions, carousels, scroll triggers or entrance animations — its
 * only transition is a 0.15s colour change on the button — so none of that is
 * invented here either.
 *
 * Self-contained: no imports, no packages, no global stylesheet. Every colour
 * and measurement is a `--pc-*` custom property on the root.
 */

export interface ProofStat {
	/** The figure itself, e.g. "40k+". */
	value: string;
	/** The line under it. */
	label: string;
}

interface Props {
	/**
	 * Small masthead above the quote, e.g. a publication name. Omit it and
	 * there is none: a real quote is never attributed to the demo's fictional
	 * publication.
	 */
	source?: string;
	/**
	 * The pull quote. Rendered inside a `<blockquote>`. Omit it and there is
	 * none: the component never supplies a quote of its own.
	 */
	quote?: string;
	/** Attribution under the quote. Omit it (or `""`) and there is none. */
	attribution?: string;
	/** Link under the quote. Needs `quoteHref` to become a link. */
	quoteLinkLabel?: string;
	/** Destination for the quote link. Without one the label is plain text. */
	quoteHref?: string;
	/** Artwork beside the quote. Omit for a drawn placeholder. */
	image?: string;
	/** Alternative text for the artwork. */
	imageAlt?: string;
	/** Aspect ratio of the artwork frame. */
	imageRatio?: string;
	/** The headline figures. None by default: `[]` hides the row. */
	stats?: ProofStat[];
	/** Small print under the figures. Omit it (or `""`) and there is none. */
	disclaimer?: string;
	/** Heading in the closing band. Omit it and there is none. */
	closingTitle?: string;
	/** Call to action label. Omit it (or `""`) and there is no button. */
	ctaLabel?: string;
	/** Destination. Without one the label renders as text, not a link. */
	ctaHref?: string;
	/** For pages where `h2` is the wrong level for the closing heading. */
	headingLevel?: "h1" | "h2" | "h3";
	/** Ground behind the whole section. */
	background?: string;
	/** Tint over the closing band. */
	closingTint?: string;
	/** The glow behind the closing band. `transparent` removes it. */
	glow?: string;
	/** Primary text colour. */
	ink?: string;
	/** Secondary text: attribution, labels, small print. */
	muted?: string;
	/** Button fill and link colour. */
	accent?: string;
	/** Text on the button. */
	accentInk?: string;
	/** Height of the closing band once there is room. */
	closingHeight?: string;
	/** Caps the figures row and centres it. */
	statsMaxWidth?: string;
	/** Caps the whole content column. */
	maxWidth?: string;
	/** Added to the root `<section>`. */
	class?: string;
	/** Root element id. */
	id?: string;
}

const {
	source = "",
	quote = "",
	attribution = "",
	quoteLinkLabel = "",
	quoteHref = "",
	image = undefined,
	imageAlt = "",
	imageRatio = "15 / 8",
	stats = [],
	disclaimer = "",
	closingTitle = "",
	ctaLabel = "",
	ctaHref = "",
	headingLevel = "h2",
	background = "#3b2b7d",
	closingTint = "rgb(35 11 89 / 60%)",
	glow = "rgb(150 130 255 / 45%)",
	ink = "#f0f0f2",
	muted = "rgb(255 255 255 / 50%)",
	accent = "#4840bb",
	accentInk = "#ffffff",
	closingHeight = "300px",
	statsMaxWidth = "1000px",
	maxWidth = "1233px",
	class: className,
	id,
} = Astro.props;

const Heading = headingLevel;

/*
 * Each block renders only from what it was given, and with nothing at all the
 * section renders nothing. The drawn artwork placeholder still stands in for a
 * missing image beside a real quote, but never on its own.
 */
const hasSplit = Boolean(quote || source || attribution || quoteLinkLabel || image);
const hasStats = stats.length > 0 || Boolean(disclaimer);
const hasTop = hasSplit || hasStats;
const hasClosing = Boolean(closingTitle || ctaLabel);

const style = [
	`--pc-bg:${background}`,
	`--pc-tint:${closingTint}`,
	`--pc-glow:${glow}`,
	`--pc-ink:${ink}`,
	`--pc-muted:${muted}`,
	`--pc-accent:${accent}`,
	`--pc-accent-ink:${accentInk}`,
	`--pc-closing-h:${closingHeight}`,
	`--pc-stats-max:${statsMaxWidth}`,
	`--pc-max:${maxWidth}`,
	`--pc-ratio:${imageRatio}`,
].join(";");
---

{(hasTop || hasClosing) && (
<section class:list={["proof", className]} id={id} style={style}>
	{hasTop && (
	<div class="top">
		<div class="inner">
			{/* Quote left, artwork right — one column until there is genuinely
			    room for two, which the reference puts at 1280. */}
			{hasSplit && (
			<div class="split">
				<div class="quote-col">
					{source && <p class="source">{source}</p>}
					{quote && (
						<blockquote class="quote">
							<span class="mark" aria-hidden="true">&ldquo;</span>
							<p class="quote-text">{quote}</p>
						</blockquote>
					)}
					{
						quoteLinkLabel &&
							(quoteHref ? (
								<a class="quote-link" href={quoteHref}>
									{quoteLinkLabel}
								</a>
							) : (
								/* No destination, so no link: a dead anchor that looks
								   clickable is worse than plain text. */
								<span class="quote-link is-static">{quoteLinkLabel}</span>
							))
					}
					{attribution && <p class="note">{attribution}</p>}
				</div>

				<div class="media-col">
					<figure class="shot">
						{
							image ? (
								<img src={image} alt={imageAlt} loading="lazy" decoding="async" />
							) : (
								/* Drawn, not loaded: with no artwork yet the block still has
								   to look deliberate rather than broken. */
								<span class="placeholder" aria-hidden="true">
									<span class="ph-card"></span>
									<span class="ph-card"></span>
									<span class="ph-card"></span>
								</span>
							)
						}
					</figure>
				</div>
			</div>
			)}

			{
				hasStats && (
					<div class="stats-wrap">
						{stats.length > 0 && (
							<ul class="stats">
								{stats.map((stat) => (
									<li class="stat">
										<span class="stat-value">{stat.value}</span>
										<span class="stat-label">{stat.label}</span>
									</li>
								))}
							</ul>
						)}
						{disclaimer && <p class="note disclaimer">{disclaimer}</p>}
					</div>
				)
			}
		</div>
	</div>
	)}

	{hasClosing && (
	<div class="closing">
		{/* The reference bleeds an oversized artwork off the corner behind this
		    band and clips it. A radial gradient does the same job with no file
		    to ship and no resolution to pick. */}
		<span class="glow" aria-hidden="true"></span>
		<div class="closing-inner">
			{closingTitle && <Heading class="closing-title">{closingTitle}</Heading>}
			{
				ctaLabel &&
					(ctaHref ? (
						<a class="cta" href={ctaHref}>
							{ctaLabel}
						</a>
					) : (
						<span class="cta is-static">{ctaLabel}</span>
					))
			}
		</div>
	</div>
	)}
</section>
)}

<style>
	.proof {
		box-sizing: border-box;
		background: var(--pc-bg);
		color: var(--pc-ink);
		font-family:
			system-ui,
			-apple-system,
			"Segoe UI",
			Roboto,
			sans-serif;
		font-size: 16px;
		line-height: 1.5;
		text-align: start;
	}

	/* Set rather than inherited, so a destination site's global rules cannot
	   change the geometry this section is built on. */
	.proof *,
	.proof *::before,
	.proof *::after {
		box-sizing: border-box;
	}

	.proof p,
	.proof ul,
	.proof li,
	.proof figure,
	.proof blockquote,
	.proof h1,
	.proof h2,
	.proof h3 {
		margin: 0;
		padding: 0;
	}

	.proof ul {
		list-style: none;
	}

	.proof img {
		display: block;
		max-width: 100%;
	}

	/* --- upper block ------------------------------------------------------- */

	.top {
		padding: 64px 16px;
	}

	.inner {
		display: flex;
		flex-direction: column;
		gap: 48px;
		max-width: var(--pc-max);
		margin-inline: auto;
	}

	.split {
		display: grid;
		grid-template-columns: 1fr;
		gap: 32px;
		align-items: center;
	}

	.quote-col {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 16px;
		min-width: 0;
		text-align: center;
	}

	.source {
		color: var(--pc-ink);
		font-size: 0.8125rem;
		font-weight: 700;
		letter-spacing: 0.16em;
		text-transform: uppercase;
		opacity: 0.85;
	}

	.quote {
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 4px;
	}

	.mark {
		display: block;
		margin-bottom: -0.15em;
		color: var(--pc-ink);
		font-family: Georgia, "Times New Roman", serif;
		font-size: 3.5rem;
		font-weight: 700;
		line-height: 0.7;
		opacity: 0.6;
	}

	.quote-text {
		max-width: 44ch;
		font-size: 1.125rem;
		line-height: 1.2;
	}

	.quote-link {
		color: var(--pc-muted);
		font-size: 0.875rem;
		text-decoration: underline;
		text-underline-offset: 3px;
		transition: color 150ms cubic-bezier(0.4, 0, 0.2, 1);
	}

	.quote-link:hover {
		color: var(--pc-ink);
	}

	.quote-link.is-static {
		text-decoration: none;
		cursor: default;
	}

	.note {
		max-width: 62ch;
		color: var(--pc-muted);
		font-size: 0.875rem;
		line-height: 1.15;
	}

	/* --- artwork ----------------------------------------------------------- */

	.media-col {
		min-width: 0;
	}

	.shot {
		overflow: hidden;
		aspect-ratio: var(--pc-ratio);
		border-radius: 14px;
		background: rgb(255 255 255 / 6%);
	}

	.shot img {
		width: 100%;
		height: 100%;
		object-fit: cover;
	}

	.placeholder {
		display: flex;
		flex-direction: column;
		justify-content: center;
		gap: 10px;
		width: 100%;
		height: 100%;
		padding: 8%;
		background: linear-gradient(
			160deg,
			rgb(255 255 255 / 10%),
			rgb(255 255 255 / 3%)
		);
	}

	.ph-card {
		display: block;
		height: 18%;
		border-radius: 8px;
		background: rgb(255 255 255 / 16%);
	}

	.ph-card:first-child {
		width: 82%;
	}

	.ph-card:nth-child(2) {
		width: 96%;
		background: rgb(255 255 255 / 24%);
	}

	.ph-card:last-child {
		width: 68%;
	}

	/* --- figures ------------------------------------------------------------ */

	.stats-wrap {
		display: flex;
		flex-direction: column;
		gap: 12px;
		align-items: center;
		width: 100%;
		max-width: var(--pc-stats-max);
		margin-inline: auto;
	}

	/*
	 * One line, always — the row never wraps. Below the wide breakpoint it
	 * carries three figures and the rest are hidden outright, which is the
	 * reference's behaviour and the only way a single row stays legible on a
	 * phone.
	 */
	.stats {
		display: flex;
		flex-wrap: nowrap;
		justify-content: center;
		gap: 12px;
		width: 100%;
	}

	/*
	 * `1 1 0` rather than a pixel basis: every figure takes an equal share of
	 * whatever width there is and shrinks with it. A basis in pixels is what
	 * makes a `nowrap` row overflow, and what made the wrapping version break
	 * into two rows at 390px and four at 320px.
	 */
	.stat {
		display: flex;
		flex: 1 1 0;
		flex-direction: column;
		align-items: center;
		gap: 6px;
		min-width: 0;
		text-align: center;
	}

	.stat-value,
	.stat-label {
		overflow-wrap: break-word;
	}

	/*
	 * Below the breakpoint only the first three are shown. `display: none`, so
	 * the figure leaves the layout and the accessibility tree together and a
	 * screen-reader user is told exactly what a sighted user is shown — rather
	 * than being clipped to nothing while still being announced.
	 *
	 * Pass more than three figures and the extras only appear on wide screens.
	 */
	@media (max-width: 1139.98px) {
		.stat:nth-child(n + 4) {
			display: none;
		}
	}

	.stat-value {
		font-size: 1.5rem;
		font-weight: 700;
		line-height: 0.9;
		letter-spacing: -0.03em;
	}

	.stat-label {
		color: var(--pc-muted);
		font-size: 0.875rem;
		line-height: 1.15;
	}

	.disclaimer {
		text-align: center;
	}

	/* --- closing band -------------------------------------------------------- */

	.closing {
		position: relative;
		display: flex;
		align-items: center;
		justify-content: center;
		overflow: hidden;
		min-height: var(--pc-closing-h);
		padding: 48px 16px;
		background: var(--pc-tint);
	}

	.glow {
		position: absolute;
		/* Oversized and pushed off the corner, then clipped — the same trick the
		   reference uses with a bitmap, done with a gradient. */
		top: -70%;
		right: -35%;
		width: 110%;
		aspect-ratio: 2 / 1;
		border-radius: 50%;
		background: radial-gradient(circle, var(--pc-glow), transparent 70%);
		pointer-events: none;
	}

	.closing-inner {
		position: relative;
		display: flex;
		flex-direction: column;
		align-items: center;
		gap: 24px;
		width: 100%;
		max-width: var(--pc-max);
		text-align: center;
	}

	.closing-title {
		font-size: clamp(2rem, 1.2rem + 3.6vw, 3.875rem);
		font-weight: 700;
		line-height: 0.9;
		letter-spacing: -0.03em;
	}

	.cta {
		display: inline-flex;
		align-items: center;
		justify-content: center;
		padding: 12px 20px;
		border-radius: 6px;
		background: var(--pc-accent);
		color: var(--pc-accent-ink);
		font-size: 1.125rem;
		font-weight: 500;
		text-decoration: none;
		transition: filter 150ms cubic-bezier(0.4, 0, 0.2, 1);
	}

	.cta:hover {
		filter: brightness(1.12);
	}

	.cta.is-static {
		background: rgb(255 255 255 / 12%);
		color: var(--pc-ink);
		cursor: default;
	}

	/* One ring for every control. The reference leaves the browser default. */
	.quote-link:focus-visible,
	.cta:focus-visible {
		outline: 3px solid var(--pc-ink);
		outline-offset: 3px;
		border-radius: 3px;
	}

	/* --- wider ---------------------------------------------------------------- */

	@media (min-width: 768px) {
		.stats-wrap {
			padding-inline: 40px;
		}
	}

	/*
	 * 1140px, measured: the reference shows three figures at 1139 and four at
	 * 1140. It is a custom breakpoint, not the 1280 the four test widths might
	 * suggest — bisected across 1100–1200 to find it.
	 */
	@media (min-width: 1140px) {
		.stat-value {
			font-size: 2rem;
		}
	}

	@media (min-width: 1280px) {
		.top {
			padding-block: 64px;
		}

		.split {
			grid-template-columns: 1fr 1fr;
			gap: 64px;
		}
	}

	/* --- reduced motion --------------------------------------------------------- */

	/*
	 * Only colour transitions exist here, and they go. The reference keeps its
	 * 0.15s button transition running regardless of the preference.
	 */
	@media (prefers-reduced-motion: reduce) {
		.quote-link,
		.cta {
			transition: none;
		}
	}
</style>