# Hallmarks — Animated Section Block for React

> A trusted-by logo cloud section in three variants: a marquee belt composed from Frieze, a grid whose logos ignite from ghost to full contrast in a seeded stamp-landing scatter on scroll-in, and a relay row where one slot at a time plate-flips to a logo not currently shown. Marks sit grayscale and dimmed at rest, lift to full contrast on hover, and are height-normalized; eyebrow/heading framing included. Renders as a static full-contrast grid under reduced motion.

- Page: https://crucibleui.com/components/sections/hallmarks
- Category: [sections](https://crucibleui.com/components/sections)
- Tier: pro (one-time purchase, see https://crucibleui.com/pricing)
- Engine: motion
- npm dependencies: motion

## Install

```bash
npx shadcn@latest add @crucible/hallmarks
```

Pro install requires adding the token-bearing `@crucible` registry to components.json (issued at https://crucibleui.com/account after purchase).

Import from `@/components/crucible/hallmarks`.

## Props (`HallmarksProps`)

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `logos?` | `HallmarkLogo[]` | `eight brand-free placeholder wordmarks` | Marks on the wall. |
| `variant?` | `"grid" \| "marquee" \| "relay"` | `"grid"` | Display mode. `grid` ignites marks from ghost to full contrast in a seeded scatter on scroll-in; `marquee` composes the Frieze belt inside the section frame; `relay` shows a fixed row where one slot at a time plate-flips to a different logo. |
| `eyebrow?` | `React.ReactNode` | `"Trusted by"` | Small uppercase kicker above the heading. Pass null/empty to omit. |
| `heading?` | `React.ReactNode` | — | Optional heading rendered as an `h2` beneath the eyebrow. Omitted by default. |
| `frieze?` | `Omit<FriezeProps, "children">` | — | Passthrough props for the composed Frieze belt (marquee variant only). |
| `pattern?` | `"scatter" \| "sweep"` | `"scatter"` | Grid ignition order: seeded scatter or a plain left-to-right sweep. |
| `stagger?` | `number` | `0.06` | Seconds between consecutive grid ignitions. |
| `threshold?` | `number` | `0.35` | IntersectionObserver threshold that triggers the grid ignition. |
| `interval?` | `number` | `2400` | Milliseconds between relay plate-flips (clamped to ≥ 600). |
| `slots?` | `number` | `5` | Visible slots in the relay row (clamped to the logo count). |
| `restOpacity?` | `number` | `0.55` | Resting opacity of each mark before hover lifts it to full. |
| `grayscale?` | `number` | `1` | Grayscale amount applied to marks at rest, 0–1. |
| `gap?` | `number` | `48` | Gap between marks, in pixels (also forwarded to Frieze in marquee mode). |
| `logoHeight?` | `number` | `28` | Normalized mark height in pixels — SVGs and images scale to this. |
| `seed?` | `number` | `7` | Seed for the scatter ignition order and the relay slot rotation. |
| `colors?` | `string[]` | `["rgba(255,255,255,0.5)"]` | Accent palette; `colors[0]` tints the eyebrow kicker. |
| `paused?` | `boolean` | `false` | Freeze all choreography: settled grid, halted belt, suspended relay. |

Also accepts: `Omit<React.ComponentPropsWithoutRef<"section">, "children">`.

## Usage example

```tsx
"use client";

import * as React from "react";

import { Hallmarks, type HallmarksProps } from "@/components/crucible/hallmarks";

type Variant = NonNullable<HallmarksProps["variant"]>;

const VARIANTS: Variant[] = ["grid", "marquee", "relay"];

export default function HallmarksDemo() {
  const [variant, setVariant] = React.useState<Variant>("grid");
  const [replay, setReplay] = React.useState(0);

  return (
    <div className="relative isolate flex h-full min-h-[420px] w-full flex-col items-center justify-center gap-4 overflow-hidden rounded-xl bg-neutral-950 py-8">
      <div className="w-full">
        <Hallmarks
          key={`${variant}-${replay}`}
          variant={variant}
          heading="The quiet standard"
        />
      </div>

      <div className="relative z-10 flex flex-wrap items-center justify-center gap-2">
        {VARIANTS.map((v) => (
          <Toggle key={v} on={variant === v} onClick={() => setVariant(v)}>
            {v}
          </Toggle>
        ))}
        {variant === "grid" && (
          <Toggle on={false} onClick={() => setReplay((r) => r + 1)}>
            Replay ignition
          </Toggle>
        )}
      </div>

      <p className="relative z-10 text-xs text-neutral-500">
        Hover a mark to lift it to full contrast.
      </p>
    </div>
  );
}

function Toggle({
  on,
  onClick,
  children,
}: {
  on: boolean;
  onClick: () => void;
  children: React.ReactNode;
}) {
  return (
    <button
      type="button"
      aria-pressed={on}
      onClick={onClick}
      className={
        "rounded-md border px-3 py-1.5 text-xs font-medium capitalize transition-colors " +
        (on
          ? "border-white/25 bg-white/10 text-white"
          : "border-white/10 text-neutral-400 hover:bg-white/5 hover:text-neutral-200")
      }
    >
      {children}
    </button>
  );
}
```

## Accessibility & performance

Honors `prefers-reduced-motion` with a designed static frame or fade (never a blank box) and passes `className` through.
