# Threat Shield

> Inbound requests converging on a shield that scores every one, then fanning out to a card with the requests scored, a column chart of the last minutes stacked by verdict, and each verdict (allowed, challenged, blocked) counting up as pulses arrive.

Source: https://ui.cortexcn.dev/docs/cortex/threat-shield

## Installation

```bash
npx shadcn@latest add @cortexcn/threat-shield
```

## Usage

```tsx
import { ThreatShield } from "@/components/threat-shield";
```

```tsx
<ThreatShield
  title="Last 15 minutes"
  verdicts={[
    { name: "Allowed", detail: "Passed every rule", count: 41870, tone: "accent" },
    { name: "Challenged", detail: "Asked to prove they're human", count: 1284, tone: "warning" },
    { name: "Blocked", detail: "Matched a rule", count: 362, tone: "destructive" },
  ]}
/>
```

Each verdict gets a row with its count and share, and a band in every column of the chart. Use it on the page for a firewall, bot protection, rate limiting, or sign-in security, to show what reaches the app and what is stopped.

## Examples

### Sign-in checks

The same shield checking sign-in attempts over the last hour.

### Static

With `animated={false}`, nothing moves and the counts stay as given.

## Behavior

- While the flow is on screen, pulses run from the left into the shield, then on to each verdict. The counts count up from zero, then each verdict adds its `perPulse` every time a pulse reaches it, and the newest column of the chart grows as they arrive.
- `tone` colors a verdict's marker, its band in the chart, and, for `"destructive"`, its count. `"warning"` and `"destructive"` use the theme's warning and destructive colors, so they follow light and dark mode.
- The chart splits `title`'s window into `columns`. Each column's height is a steady variation on the verdicts' average, the same on every render.
- With reduced motion enabled, or off screen, nothing moves and every count shows its given value.
- The whole flow is one SVG that scales to its container. It lays out at `minWidth` or wider (560px by default); in a narrower container it scrolls sideways and takes keyboard focus.
- Screen readers hear the total, then each verdict's count and share.

## Props

### ThreatShield

| Prop            | Type                    | Default                                   | Description                                         |
| --------------- | ----------------------- | ----------------------------------------- | --------------------------------------------------- |
| `verdicts`      | `ThreatShieldVerdict[]` | Three sample verdicts                     | The verdicts, in list and stacking order            |
| `title`         | `string`                | `"Last 15 minutes"`                       | The card's title, the window the numbers cover      |
| `totalLabel`    | `string`                | `"Requests scored"`                       | The label over the total                            |
| `columns`       | `number`                | `15`                                      | How many columns the chart splits `title` into      |
| `shieldName`    | `string`                | `"Shield"`                                | The name in the middle node                         |
| `shieldCaption` | `string`                | `"Scores every request"`                  | A line under the middle node                        |
| `sourceLabel`   | `string`                | `"Inbound requests"`                      | The label over the incoming lines                   |
| `rateLabel`     | `string`                | `"requests a minute"`                     | The words after the rate under the source label     |
| `apiLabel`      | `string`                | `"Rules"`                                 | The label on the lines into the card                |
| `note`          | `string`                | `"Blocked requests never reach your app."` | A note at the foot of the card                     |
| `caption`       | `string`                | `"Illustrative numbers"`                  | A small label in the bottom-left corner             |
| `locale`        | `string`                | `"en-US"`                                 | The locale numbers are written in                   |
| `lines`         | `number`                | `22`                                      | How many lines of traffic come in from the left     |
| `accent`        | `string`                | `"var(--foreground)"`                     | Color of the pulses and of `"accent"` verdicts      |
| `animated`      | `boolean`               | `true`                                    | Run the pulses and count up while on screen         |
| `minWidth`      | `number`                | `560`                                     | Narrowest layout width in pixels; below it, scroll  |
| `ariaLabel`     | `string`                | `"Threat shield"`                         | Names the flow for screen readers                   |
| `className`     | `string`                | -                                         | Extra classes on the frame                          |

### ThreatShieldVerdict

| Prop       | Type                                      | Default    | Description                                            |
| ---------- | ----------------------------------------- | ---------- | ------------------------------------------------------ |
| `name`     | `string`                                  | -          | The verdict, such as `"Blocked"`                       |
| `detail`   | `string`                                  | -          | Why requests get it                                    |
| `count`    | `number`                                  | -          | Requests that got it so far                            |
| `tone`     | `"accent" \| "warning" \| "destructive"` | `"accent"` | Its color                                              |
| `perPulse` | `number`                                  | `1`        | Requests added each time a pulse reaches it while live |