---
name: astry-design-system
description: Astry brand contract + queryable registry. Activates whenever you build, design, or write copy for any Astry surface (astry-agency repo, marketing, dashboard, brand work) or whenever the user mentions Astry, brand.astry.agency, the design system, or any Astry primitive. The full system is exposed as an HTTP registry at brand.astry.agency/r — fetch from it instead of guessing.
metadata:
  priority: 10
  sessionStart: false
  pathPatterns:
    - "components/astry-ui.tsx"
    - "app/design-system/**"
    - "data/mascots.json"
    - "public/r/**"
    - "AGENTS.md"
    - "public/design-system.json"
    - "public/brand/**"
  promptSignals:
    phrases:
      - "astry"
      - "design system"
      - "brand.astry.agency"
      - "astry-ui"
      - "AstryLogo"
      - "AstryMascot"
      - "AngleCorner"
      - "astry registry"
---

# Astry Design System · Agent Contract

The design system is queryable. Don't guess primitive APIs, mascot names, or token values — fetch them from the registry. The registry is regenerated on every build from the same source files that drive `brand.astry.agency`, so it never drifts.

**Brand color**: `#0051FF`
**Live preview**: `https://brand.astry.agency`
**Registry index**: `https://brand.astry.agency/r/index.json`
**Repo**: `https://github.com/martinbon39/astry`

---

## How to query the registry

Every primitive, mascot, logo, token block, and contract is a separate JSON entry under `https://brand.astry.agency/r/<name>.json`.

```bash
# 1. List everything available
curl https://brand.astry.agency/r/index.json

# 2. Fetch a specific item
curl https://brand.astry.agency/r/astry-ui.json          # full primitive pack (one big file)
curl https://brand.astry.agency/r/tokens.json            # CSS vars + Tailwind config
curl https://brand.astry.agency/r/fonts.json             # Neue Haas .ttf URLs + next/font + @font-face snippets
curl https://brand.astry.agency/r/voice.json             # voice contract (do/don't)
curl https://brand.astry.agency/r/accessibility.json     # a11y contract
curl https://brand.astry.agency/r/mascot-happy.json      # any mascot — see meta.url for the SVG
curl https://brand.astry.agency/r/logo-icon-on-blue.json # any logo asset
curl https://brand.astry.agency/r/team-martin.json       # any team member — see meta.founder
curl https://brand.astry.agency/r/client-<slug>.json     # client logo (drag-drop into public/brand/clients/)
curl https://brand.astry.agency/r/ref-<slug>.json        # past post used as visual style ref
curl https://brand.astry.agency/r/skill.json             # this skill's metadata
curl https://brand.astry.agency/r/skill.md               # this skill itself, raw markdown

# 3. Install the primitive pack into another project (shadcn-compatible)
npx shadcn@latest add https://brand.astry.agency/r/astry-ui.json

# 4. Install this skill into Claude Code (one-liner)
mkdir -p ~/.claude/skills/astry-design-system && \
  curl -L https://brand.astry.agency/r/skill.md \
  -o ~/.claude/skills/astry-design-system/SKILL.md
```

The format is **shadcn registry v2**. Items live at `r/<name>.json`. The index lists every item with `name`, `type`, `title`, `description`, `url`, and a small `meta` block.

### Item categories

| Type             | Examples                                          | What's inside                                |
|------------------|---------------------------------------------------|-----------------------------------------------|
| `registry:ui`    | `astry-ui`                                        | Full primitive source · install with shadcn  |
| `registry:style` | `tokens`                                          | CSS vars (light + dark) · Tailwind extension |
| `registry:lib`   | `voice`, `accessibility`, `fonts`, `skill`        | Contracts and bundles exposed via `meta`     |
| `registry:file`  | `mascot-*`, `logo-*`, `team-*`, `angle`           | Individual assets · URL in `meta.url`        |

### Filtering by `meta.kind`

Every binary item carries a `meta.kind` so agents can filter the index without fetching each entry:

- `meta.kind === "mascot"` · 57 entries · `meta.available: true` for the 10 with SVGs on disk, `false` for the 47 placeholder slots. Each available mascot also has `meta.pngUrl` (auto-rasterized 512×512 PNG, for image-gen agents).
- `meta.kind === "logo"` · 6 PNG variants (icon, lockup, banner, wordmark)
- `meta.kind === "team"` · 4 portraits · `meta.founder: true` filters to founders only (3) · each entry has `meta.angles[]` listing every photo available (primary + extras dropped into `public/brand/team/<slug>/`). Image-gen agents pass 2-3 angles per named person to lock identity.
- `meta.kind === "fonts"` · 1 bundled entry listing all 16 .ttf files
- `meta.kind === "client"` · client logos (drag-drop into `public/brand/clients/`)
- `meta.kind === "ref"` · past good Astry posts kept as visual style refs (drag-drop into `public/brand/refs/`)
- `meta.kind === "skill"` · 1 entry pointing at the raw markdown of this skill

Mascot/logo/team/client/ref binaries are **not inlined** in their JSON entries. Fetch the URL from `meta.url` (or `meta.pngUrl` for the rasterized mascot variant) directly.

### Bot-friendly assets (image-gen)

Image-gen models like Higgsfield need PNG references, not SVG/text descriptions. The registry exposes:

- **Mascot PNGs** at `meta.pngUrl` for every available mascot — 512×512, transparent bg, ready to pass as a Higgsfield reference image.
- **Team portraits** as PNG (`meta.url` on each `team-<slug>` entry).
- **Client logos** at `r/client-<slug>.json#meta.url`.
- **Reference posts** at `r/ref-<slug>.json#meta.url` — past Astry posts to use as style guides ("in the same style as this").

The browse surface lives at `https://brand.astry.agency/design-system/assets`.

---

## TL;DR — 10 rules you must respect

1. **Tokens > hex.** Components reference `bg-brand`, `text-text`, `border-border-strong`. Never write `#0051FF`, `gray-200`, or `bg-[#FFF]` inside a component.
2. **Sharp corners.** Every radius token resolves to `0`. The only exception is `--radius-pill: 9999px` reserved for switch knobs, slider thumbs, status dots, avatar status indicators. Cards, buttons, inputs, tags, badges → square.
3. **No shadows on cards by default.** Hover state = darken border, no translate, no shadow.
4. **Type**: `font-sans` is **Neue Haas Display Pro** (local). `font-mono` is **Inter** (Google). Display sizes ≥32px get `tracking-[-0.02em]`.
5. **Brand signature**: corner angles (`<AngleCorner />`) and the eyebrow label. The slash has been retired — never use `/` as a visual element.
6. **Mascot is brand voice.** Use `<AstryMascot emotion="..." />`. The valid emotion list is the registry — `curl r/index.json` and filter `meta.kind === "mascot" && meta.available`.
7. **Single import path**: `import { Button, Card, Input, AstryLogo, ... } from "@/components/astry-ui"`. That file is the source of truth. Never duplicate.
8. **Logo lockup**: `<AstryLogo size="xs|sm|md|lg|xl" tone="default|knockout" />`. Never bare `<span>astry</span>`.
9. **Buttons on brand-blue surfaces** use `variant="inverse"` (white bg, brand text) or `variant="inverse-ghost"`. Don't override Tailwind classes manually.
10. **Voice**: direct, concrete verbs, one idea per sentence, period in brand color (`<span className="text-brand">.</span>`). No synergies, leveraging, hedge words, or exclamation marks.

---

## Component cheat-sheet

The registry is the authoritative API. This list is for fast recognition only — when you need exact props, fetch `r/astry-ui.json` and read `meta.details`.

```tsx
// Buttons
<Button variant="primary|secondary|outline|ghost|link|danger|inverse|inverse-ghost"
        size="xs|sm|md|lg" leftIcon={...} rightIcon={...} withAngles />
<IconButton icon={...} aria-label="..." variant size />

// Inputs
<Input label hint error success leadingIcon trailingIcon size="sm|md|lg" />
<Textarea label hint error rows />
<Select label options={[{value, label}]} value onChange placeholder size />
<Checkbox label /> <Radio label name value /> <Switch checked onChange label />
<Slider value onChange min max step label />

// Display
<Card hover><CardHeader><CardTitle /><CardSubtitle /></CardHeader><CardBody /><CardFooter /></Card>
<Badge variant="neutral|brand|success|warning|danger|outline" dot size="sm|md" />
<Tag onRemove removeIcon /> <Skeleton /> <Kbd>⌘K</Kbd>
<Alert variant="info|success|warning|danger" title icon onClose closeIcon />
<Avatar src alt size status="online|offline|busy" /> <AvatarGroup avatars max size />

// Navigation
<Tabs tabs={[{id, label, icon}]} value onChange fullWidth />
<Breadcrumb items={[{label, href}]} />
<Pagination current total onChange />     {/* arrows are baked in */}
<Stepper steps={[{label, description}]} current />
<Progress value label /> <Tooltip content>...</Tooltip>

// Brand
<AstryLogo size="xs|sm|md|lg|xl" tone="default|knockout" href="/" />
<AstryMascot emotion="<see registry>" size />
<AngleCorner position="tl|tr|bl|br" size color />
<BracketFrame size color>...</BracketFrame>
<Eyebrow tone="brand|muted|subtle">SECTION LABEL</Eyebrow>
<Icon icon={HugeIconsExport} size strokeWidth={1.8} />
```

Icons come from `@hugeicons/core-free-icons` — pass the imported icon to the `icon` prop.

---

## Token snapshot (light)

Brand `#0051FF` · canvas `#FFFFFF` · bg `#F4F4F2` · text `#111111` · border `#E2E5EA` · border-strong `#C9CFD8`. Brand lifts to `#5577FF` in dark to clear AA contrast on `bg-bg`.

Type scale: `display 96 / 5xl 72 / 4xl 56 / 3xl 40 / 2xl 32 / xl 24 / lg 20 / md 18 / base 16 / sm 14 / xs 12`. Display ≥32 → `tracking-[-0.02em]`. Eyebrow → 12px, uppercase, brand color, `tracking-[0.08em]`.

Motion: `instant 80ms / fast 140ms / base 200ms (default) / slow 320ms` · easing `cubic-bezier(0.4, 0, 0.2, 1)`. Killed under `prefers-reduced-motion`.

Full values live in `r/tokens.json` (`cssVars.light`, `cssVars.dark`).

---

## Hard rules — what NOT to do

| Don't                                                         | Do                                                       |
|---------------------------------------------------------------|----------------------------------------------------------|
| Write `bg-[#0051FF]` or `text-[#FFF]`                         | Use `bg-brand` / `text-text-on-brand`                    |
| `rounded-md` (or any rounded class) on Card / Button / Input  | Leave it square (tokens already = 0)                    |
| Add `shadow-md` to a Card                                     | Use `border` for delineation; only hover darkens border  |
| `<span>astry</span>` as logo                                  | `<AstryLogo size="md" />`                                |
| Embed `/brand/mascot/happy.svg` directly                      | `<AstryMascot emotion="happy" />`                        |
| Build a custom `<select>` with native `<option>`              | Use `<Select options={[...]} />` from astry-ui           |
| Use `/` as a visual divider or eyebrow prefix                 | Drop it; brand has retired the slash                     |
| Override variant with `!bg-...` `!text-...`                   | Use proper variant (`inverse`, `inverse-ghost`, etc.)    |
| Re-implement Button / Input / Card                            | Import from `@/components/astry-ui`                      |
| Hardcode the mascot list in your own code                     | Query `r/index.json` filtered by `meta.kind === "mascot"`|

---

## Voice & accessibility

Voice and a11y rules are versioned at `r/voice.json` and `r/accessibility.json`. Read them before writing UI copy or interactive primitives. Headlines:

- **Voice**: Direct. Concrete verbs (build, ship, embed, deploy). One idea per sentence. Period in brand color. No synergies, leveraging, hedge words, exclamation marks.
- **A11y**: WCAG 2.2 AA floor. 2px brand focus ring. 44×44 touch targets. All transitions killed under `prefers-reduced-motion`. Status pairs colour with a dot or icon.

---

## Repo layout (relevant bits)

```
app/
  globals.css                   ← all tokens (light + dark)
  design-system/                ← /design-system/* surfaces (synced to brand.astry.agency)
    layout.tsx, page.tsx
    foundations|layouts|templates|forms|data|sections|navigation|cards|mascots|api/page.tsx
components/
  astry-ui.tsx                  ← single source of truth for primitives
data/
  mascots.json                  ← canonical mascot catalog (consumed by page + registry)
  team.json                     ← canonical team list (consumed by registry; pages still hardcode)
public/
  brand/{logo,mascot,team}/...  ← brand binaries
  fonts/Neue/*.ttf              ← Neue Haas Display Pro (16 files)
  r/                            ← AUTO-GENERATED registry — do not edit by hand
  r/index.json                  ← catalog of every item
  r/<name>.json                 ← per-item registry entry
  r/skill.md                    ← this skill, served as raw markdown
  design-system.json            ← legacy manifest (now points at /r/index.json)
scripts/
  build-registry.mjs            ← regenerates public/r/* on every `prebuild`
.claude/skills/astry-design-system/SKILL.md ← source of /r/skill.md
middleware.ts                   ← rewrites brand.astry.agency/* → /design-system/*
```

The registry is derived from `astry-ui.tsx`, `globals.css`, `data/mascots.json`, `data/team.json`, the SKILL.md itself, and the contents of `public/brand/` + `public/fonts/Neue/`. **Never edit `public/r/*` by hand** — change the source and re-run `npm run registry:build`.

---

## When asked to add a new primitive

1. Add it to `components/astry-ui.tsx` (alphabetical-ish, grouped by category). Use only semantic tokens — zero hex.
2. If it has props or variants worth advertising, add an entry in `PRIMITIVES` inside `scripts/build-registry.mjs` so it shows in `r/astry-ui.json#meta.details`.
3. Showcase it in the relevant `/app/design-system/*/page.tsx` page.
4. Run `npm run registry:build` to refresh `public/r/*`. The next push regenerates them on Vercel automatically.

## When asked to add a new mascot

1. Drop the SVG at `public/brand/mascot/<name>.svg` (brand-blue fill, no padding).
2. Add a corresponding entry in `data/mascots.json` (`name`, `category`, `usage`, optional `label`).
3. Add the name to the `MASCOTS` tuple in `components/astry-ui.tsx` so the TS union accepts it.
4. Run `npm run registry:build`. The mascot is now fetchable at `r/mascot-<name>.json` and visible in `/design-system/mascots`.

## When asked to add or update a team member

1. Drop the **primary** portrait at `public/brand/team/<slug>.png` (square, brand-aligned crop, transparent or solid bg).
2. Add or update the entry in `data/team.json` (`slug`, `name`, `role`, `founder` boolean, `photo` filename).
3. The showcase pages at `app/design-system/sections/page.tsx` and `app/design-system/cards/page.tsx` currently hardcode the team list — update them too if the change is name- or role-related.
4. Run `npm run registry:build`. The member is now fetchable at `r/team-<slug>.json`.

### Adding more angles to an existing founder

Image-gen agents (Higgsfield, Midjourney) need 2-3 photos per person to lock identity. To enrich a founder's reference set:

1. Drop additional PNGs into `public/brand/team/<slug>/` (folder per slug — already exists for the 4 current founders).
2. Filename = angle name. Recommended set: `front.png`, `3q-left.png`, `3q-right.png`, `side.png`, `casual.png`.
3. Run `npm run registry:build`. Each new file appears in `r/team-<slug>.json#meta.angles[]` automatically — no data file edit needed.

---

## When the user gives you a prompt with annotations

If the user pastes a markdown prompt that looks like:
```
## /design-system/...
### N. `<button>` — "..."
- **Selector**: ...
- **Note**: ...
```

It comes from the annotation tool. Respect every item in order. Reply with a short summary of what you fixed and which file each change touched.

---

## URLs to remember

- Live design system: `https://brand.astry.agency`
- API & install instructions (humans + agents): `https://brand.astry.agency/design-system/api`
- Registry index: `https://brand.astry.agency/r/index.json`
- Primitive pack (installable): `https://brand.astry.agency/r/astry-ui.json`
- Tokens: `https://brand.astry.agency/r/tokens.json`
- Fonts (Neue Haas): `https://brand.astry.agency/r/fonts.json`
- Team (filter `meta.founder`): `https://brand.astry.agency/r/team-<slug>.json`
- This skill (raw): `https://brand.astry.agency/r/skill.md`
- Repo: `https://github.com/martinbon39/astry`

If you have web-fetch capability, **prefer the registry over this skill** when you need exact APIs, mascot names, token values, or team data. The registry is the source of truth; this skill is an orientation map.
