sc1m/design
ComponentsAI Summary

AI Summary

The banner that marks AI-authored copy: one sentence of plain language on an animated painting, in one of four palettes. No runtime dependencies.

Stable

Live preview

What mattered

You made 42 purchases in August. Twelve were the same café at the same hour — a habit, not a decision. Groceries fell 31% in the same weeks.

The painting above is live — the colour masses drift and the noise warp breathes on separate cycles, so the field never visibly loops. Only the background moves: the highlighted phrases are static, because a highlight is a reading aid rather than an event.

Each field is one gradient plus a handful of colour masses pushed through a fractal-noise displacement warp, with a grain pass over the top — so a palette is a few hex values and three durations, and nothing is re-rendered or fetched at runtime. The preview shows ember; the ink default is deliberately the still one.

Installation

pnpm dlx shadcn@latest add @sc1m/ai-summary

This also merges the token layer and installs src/lib/cn.ts. For the manual route — prerequisites, components.json, and app-level setup — see Installation.

Usage

import {
  AiSummary,
  AiSummaryLabel,
  AiSummaryText,
  AiSummaryMark,
} from "@/components/ui/ai-summary";

Composition

Every part is a separate export. AiSummary owns the painting; the rest are presentational and must appear in this order.

AiSummary├── AiSummaryLabel└── AiSummaryText    └── AiSummaryMark

Example

<AiSummary palette="ember">
  <AiSummaryLabel>What mattered</AiSummaryLabel>
  <AiSummaryText>
    You made 42 purchases in August.{" "}
    <AiSummaryMark>Twelve were the same café at the same hour</AiSummaryMark>{" "}
    — a habit, not a decision.
  </AiSummaryText>
</AiSummary>
Note:

Writing this in MDX

Keep the children of AiSummaryText on one line, or render them from a .tsx. MDX parses multi-line JSX children as a markdown block and wraps them in its own <p>, nesting a paragraph inside this one — which the HTML parser cannot do, so it auto-closes the outer one and hydration fails.

Palettes

Four ship. ink is the default — the restrained one, brand accent on near-black with three masses and almost no movement. ember is warm with a high warp and a slow pan; tide is cool and deep, low warp with high octaves; moss is green and sand on a lazy 52s pan.

ember

Groceries fell 31% while eating out held flat.

tide

Groceries fell 31% while eating out held flat.

moss

Groceries fell 31% while eating out held flat.

ink

Groceries fell 31% while eating out held flat.

API reference

PropTypeDefaultDescription
palette"ember" | "tide" | "moss" | "ink""ink"Which painting to hang behind the copy.

AiSummaryMark takes no props of its own — the highlight is static, and box-decoration-break: clone keeps the pill whole when a phrase wraps.

Accessibility

  • The painting is aria-hidden — it carries no information, and the summary reads the same with it removed.
  • The veil between painting and copy is what makes the text legible, so it is per-palette rather than constant: a hot field needs a flat 72%, a deep one can be graded and let the top of the card keep its colour.
  • The highlight on AiSummaryMark is static. It is a reading aid, not an event: animating it in would pull the eye to the mark before the reader has reached it, and repeat on every remount.
  • SMIL ignores prefers-reduced-motion — no CSS rule can stop <animate> — so the component leaves the painting's timelines out of the DOM entirely rather than trying to suppress them. The server render and first paint are always the still painting; motion is added on hydration only if the reader allows it.

Design tokens

The palettes are deliberately raw hex. They are paint, not semantics — there is no brand meaning to inherit, and pinning them to tokens would give every brand the same picture. Everything structural around them is tokens.

TokenApplied to
--fgThe veil over the painting, and the card's base
--bgEyebrow and body copy
--accentThe highlight sweep on AiSummaryMark
--brand-radius-lgCard corners
--font-monoEyebrow

Source

src/components/ui/ai-summary/ai-summary.tsx — no next/* imports, so it drops into any React 19 + Tailwind v4 app.