agentleFS
Sign inSign up

eg-design-system

ikan-tech1/eg-design-system/.github/copilot-instructions.md

Drop-in file. Place this CLAUDE.md in a project root (or merge it into an existing one) and Claude Code will load it automatically. It defines the design tokens, rules, and taste to use for every UI you build in this project. The canonical token values live in the CSS block below and in the sibling tokens.css. When building or editing any UI in this project: A cohesive direction beats a pile of safe defaults. Before coding, pick a lane and…

Copilot instructions0 starsChanged 4 months ago
# Design System & Frontend Guidelines

> **Drop-in file.** Place this `CLAUDE.md` in a project root (or merge it into an existing one) and Claude Code will load it automatically. It defines the design tokens, rules, and taste to use for **every** UI you build in this project. The canonical token values live in the CSS block below and in the sibling `tokens.css`.

---

## 0. How Claude should use this file

When building or editing any UI in this project:

1. **Never hardcode design values.** No raw hex, no `#fff`, no `16px`, no `box-shadow: 0 2px 4px rgba(0,0,0,.1)`. Reference tokens: `var(--primary)`, `bg-background`, `text-muted-foreground`, `rounded-lg`, `shadow-md`, `gap-4`, `duration-normal`. If a value you need doesn't exist as a token, add it to the token layer first, then use it.
2. **Commit to the aesthetic in Section 1 before writing markup.** State the direction in one sentence, then build to it. Don't default to "Inter + a purple gradient + three rounded cards."
3. **Light and dark are both first-class.** Every surface must work in `:root` and `.dark`. Use semantic tokens (which flip automatically), never mode-specific colors.
4. **Accessibility is non-negotiable** (Section 9). WCAG 2.2 AA contrast, visible `focus-visible` rings, ≥24px (ideally 44px) targets, semantic HTML, `prefers-reduced-motion`.
5. **Match the surrounding code.** Use the project's component library (shadcn/Radix/etc.), its import style, and its conventions.

---

## 1. Design philosophy (anti-"AI-slop")

A cohesive *direction* beats a pile of safe defaults. Before coding, pick a lane and commit:

| Dimension | Avoid (the slop) | Do this instead |
|---|---|---|
| **Type** | Inter everywhere, all 400 weight | A display face with character (Section 6) + a clear type scale + weight contrast |
| **Color** | Timid grays + a purple gradient | One confident dominant color, sharp accents, intentional neutrals (Section 2) |
| **Depth** | Flat cards, one drop shadow | Layered, *tinted* elevation + atmospheric backgrounds (Section 7) |
| **Layout** | Three equal centered cards | Intentional hierarchy, asymmetry where it earns it, bento composition (Section 10) |
| **Motion** | None, or everything fading at 300ms | Purposeful, fast, spring-like; respects reduced-motion (Section 8) |
| **Detail** | Default borders, no states | Considered hover/press/focus states, micro-interactions, empty/loading/error states |

**The rule of one:** one dominant color, one display typeface, one signature interaction. Everything else supports it.

---

## 2. Color — the token layer (OKLCH, light + dark)

We use **OKLCH** (perceptually uniform — predictable lightness, easy theming: change one hue to rebrand). Semantic names follow the shadcn/Radix convention so any shadcn component works unchanged. **To rebrand: change the `--primary` / `--ring` hue (3rd value) and the chart hues.** Everything else is neutral and will follow.

```css
:root {
  /* Surfaces & text (each surface ships a paired -foreground) */
  --background: oklch(0.99 0.002 255);
  --foreground: oklch(0.20 0.010 260);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.20 0.010 260);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.20 0.010 260);
  --muted: oklch(0.970 0.003 255);
  --muted-foreground: oklch(0.500 0.012 260);

  /* Brand & emphasis */
  --primary: oklch(0.55 0.19 256);          /* confident azure — the one dominant color */
  --primary-foreground: oklch(0.99 0.002 255);
  --secondary: oklch(0.965 0.004 255);
  --secondary-foreground: oklch(0.25 0.010 260);
  --accent: oklch(0.955 0.022 256);         /* subtle brand tint for hover/selected */
  --accent-foreground: oklch(0.30 0.060 256);

  /* Status */
  --destructive: oklch(0.585 0.218 26);
  --destructive-foreground: oklch(0.99 0 0);
  --success: oklch(0.620 0.160 150);
  --success-foreground: oklch(0.99 0 0);
  --warning: oklch(0.800 0.150 82);
  --warning-foreground: oklch(0.27 0.050 82);
  --info: oklch(0.620 0.140 240);
  --info-foreground: oklch(0.99 0 0);

  /* Lines & focus */
  --border: oklch(0.920 0.004 260);
  --input: oklch(0.920 0.004 260);
  --ring: oklch(0.55 0.19 256);

  /* Data viz (distinct hues, legible in both modes) */
  --chart-1: oklch(0.55 0.19 256);
  --chart-2: oklch(0.65 0.16 168);
  --chart-3: oklch(0.72 0.16 82);
  --chart-4: oklch(0.60 0.21 26);
  --chart-5: oklch(0.58 0.20 312);

  /* Sidebar / app shell */
  --sidebar: oklch(0.985 0.002 255);
  --sidebar-foreground: oklch(0.25 0.010 260);
  --sidebar-primary: oklch(0.55 0.19 256);
  --sidebar-primary-foreground: oklch(0.99 0 0);
  --sidebar-accent: oklch(0.965 0.004 255);
  --sidebar-accent-foreground: oklch(0.25 0.010 260);
  --sidebar-border: oklch(0.920 0.004 260);
  --sidebar-ring: oklch(0.55 0.19 256);

  /* Shape, type, motion (shared across modes) */
  --radius: 0.625rem;

  --font-sans: "Geist", "Inter", ui-sans-serif, system-ui, sans-serif;
  --font-display: "Clash Display", "General Sans", var(--font-sans);
  --font-serif: "Fraunces", ui-serif, Georgia, serif;
  --font-mono: "Geist Mono", "JetBrains Mono", ui-monospace, monospace;

  /* Tinted elevation — shadows carry the brand/ink hue, never pure black */
  --shadow-color: 260 15% 12%;
  --shadow-xs: 0 1px 2px 0 oklch(0.20 0.03 260 / 0.06);
  --shadow-sm: 0 1px 3px 0 oklch(0.20 0.03 260 / 0.10), 0 1px 2px -1px oklch(0.20 0.03 260 / 0.10);
  --shadow-md: 0 4px 6px -1px oklch(0.20 0.03 260 / 0.10), 0 2px 4px -2px oklch(0.20 0.03 260 / 0.08);
  --shadow-lg: 0 10px 15px -3px oklch(0.20 0.03 260 / 0.10), 0 4px 6px -4px oklch(0.20 0.03 260 / 0.08);
  --shadow-xl: 0 20px 25px -5px oklch(0.20 0.03 260 / 0.12), 0 8px 10px -6px oklch(0.20 0.03 260 / 0.10);
  --shadow-2xl: 0 25px 50px -12px oklch(0.20 0.03 260 / 0.28);
  --shadow-glow: 0 0 0 1px var(--ring), 0 0 28px oklch(0.55 0.19 256 / 0.35);
}

.dark {
  --background: oklch(0.180 0.012 264);     /* deep cool ink, not pure black */
  --foreground: oklch(0.960 0.004 255);
  --card: oklch(0.210 0.014 264);
  --card-foreground: oklch(0.960 0.004 255);
  --popover: oklch(0.210 0.014 264);
  --popover-foreground: oklch(0.960 0.004 255);
  --muted: oklch(0.270 0.014 264);
  --muted-foreground: oklch(0.710 0.012 258);

  --primary: oklch(0.66 0.18 256);          /* brighter so it pops on dark */
  --primary-foreground: oklch(0.180 0.020 264);
  --secondary: oklch(0.275 0.014 264);
  --secondary-foreground: oklch(0.960 0.004 255);
  --accent: oklch(0.310 0.030 260);
  --accent-foreground: oklch(0.960 0.004 255);

  --destructive: oklch(0.640 0.215 26);
  --destructive-foreground: oklch(0.98 0 0);
  --success: oklch(0.690 0.160 150);
  --success-foreground: oklch(0.16 0.02 150);
  --warning: oklch(0.820 0.150 82);
  --warning-foreground: oklch(0.20 0.04 82);
  --info: oklch(0.680 0.140 240);
  --info-foreground: oklch(0.16 0.02 240);

  --border: oklch(1 0 0 / 10%);             /* translucent lines read better on dark */
  --input: oklch(1 0 0 / 14%);
  --ring: oklch(0.66 0.18 256);

  --chart-1: oklch(0.66 0.18 256);
  --chart-2: oklch(0.72 0.15 168);
  --chart-3: oklch(0.78 0.15 82);
  --chart-4: oklch(0.67 0.20 26);
  --chart-5: oklch(0.67 0.18 312);

  --sidebar: oklch(0.205 0.014 264);
  --sidebar-foreground: oklch(0.960 0.004 255);
  --sidebar-primary: oklch(0.66 0.18 256);
  --sidebar-primary-foreground: oklch(0.180 0.020 264);
  --sidebar-accent: oklch(0.270 0.014 264);
  --sidebar-accent-foreground: oklch(0.960 0.004 255);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.66 0.18 256);

  /* Elevation on dark = lighter surfaces do the lifting; keep shadows soft */
  --shadow-xs: 0 1px 2px 0 oklch(0 0 0 / 0.30);
  --shadow-sm: 0 1px 3px 0 oklch(0 0 0 / 0.40), 0 1px 2px -1px oklch(0 0 0 / 0.40);
  --shadow-md: 0 4px 6px -1px oklch(0 0 0 / 0.45), 0 2px 4px -2px oklch(0 0 0 / 0.40);
  --shadow-lg: 0 10px 15px -3px oklch(0 0 0 / 0.50), 0 4px 6px -4px oklch(0 0 0 / 0.45);
  --shadow-xl: 0 20px 25px -5px oklch(0 0 0 / 0.55), 0 8px 10px -6px oklch(0 0 0 / 0.50);
  --shadow-2xl: 0 25px 50px -12px oklch(0 0 0 / 0.65);
  --shadow-glow: 0 0 0 1px var(--ring), 0 0 32px oklch(0.66 0.18 256 / 0.45);
}
```

**Semantic color usage**

| Token | Use for | Don't use for |
|---|---|---|
| `background` / `foreground` | Page canvas + default text | — |
| `card` / `popover` | Raised surfaces, menus, dialogs | Page background |
| `muted` / `muted-foreground` | Subtle fills, secondary/help text, placeholders | Primary CTAs |
| `primary` | The **one** main action per view, key emphasis | Every button (creates noise) |
| `secondary` | Neutral secondary buttons/chips | Primary action |
| `accent` | Hover/selected/active background tint | Large filled areas |
| `destructive` / `success` / `warning` / `info` | Status, alerts, validation | Decoration |
| `border` / `input` / `ring` | Dividers, field outlines, focus rings | Text |

---

## 3. Tailwind wiring

**Tailwind v4** (recommended — matches current shadcn). Map tokens once with `@theme inline`, then use `bg-background`, `text-primary`, `rounded-lg`, `shadow-md`, etc. everywhere:

```css
/* globals.css */
@import "tailwindcss";
@import "./tokens.css";              /* the :root + .dark block above */
@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-foreground: var(--card-foreground);
  --color-popover: var(--popover);
  --color-popover-foreground: var(--popover-foreground);
  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);
  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);
  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);
  --color-destructive: var(--destructive);
  --color-success: var(--success);
  --color-warning: var(--warning);
  --color-info: var(--info);
  --color-border: var(--border);
  --color-input: var(--input);
  --color-ring: var(--ring);
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);
  --color-sidebar: var(--sidebar);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-ring: var(--sidebar-ring);

  --font-sans: var(--font-sans);
  --font-mono: var(--font-mono);
  --font-serif: var(--font-serif);

  --radius-sm: calc(var(--radius) - 4px);
  --radius-md: calc(var(--radius) - 2px);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) + 4px);
  --radius-2xl: calc(var(--radius) + 8px);

  --shadow-xs: var(--shadow-xs);
  --shadow-sm: var(--shadow-sm);
  --shadow-md: var(--shadow-md);
  --shadow-lg: var(--shadow-lg);
  --shadow-xl: var(--shadow-xl);
  --shadow-2xl: var(--shadow-2xl);
}
```

**Tailwind v3:** put the same CSS-variable blocks in `globals.css`, then reference them in `tailwind.config.{ts,js}` under `theme.extend.colors` as `primary: "var(--primary)"`, `borderRadius.lg: "var(--radius)"`, etc. (the classic shadcn v3 setup), and enable `darkMode: "class"`.

---

## 4. Spacing scale (4px base)

Use the scale — never arbitrary `px`. Tailwind classes map 1:1 (`p-4` = `1rem`).

| Token | rem | px | Typical use |
|---|---|---|---|
| `0.5` | 0.125 | 2 | hairline nudge |
| `1` | 0.25 | 4 | icon ↔ label |
| `2` | 0.5 | 8 | tight inner padding |
| `3` | 0.75 | 12 | compact control padding |
| `4` | 1 | 16 | **default** gap / card padding |
| `6` | 1.5 | 24 | section inner padding |
| `8` | 2 | 32 | between components |
| `12` | 3 | 48 | block spacing |
| `16` | 4 | 64 | section padding (mobile) |
| `24` | 6 | 96 | section padding (desktop) |
| `32` | 8 | 128 | hero whitespace |

Rhythm: pick one base unit (`4`) and step in multiples. Whitespace is a feature — when unsure, add more.

---

## 5. Radius, borders, z-index

```css
/* radius derives from --radius: 0.625rem */
sm = calc(--radius - 4px)   md = calc(--radius - 2px)   lg = --radius
xl = calc(--radius + 4px)   2xl = calc(--radius + 8px)  full = 9999px

/* border widths */ 0 · 1px (default) · 2px (emphasis) · 4px (focus accents)

/* z-index layers — use these names, never magic numbers */
--z-base: 0;       --z-docked: 10;     --z-dropdown: 1000;  --z-sticky: 1100;
--z-banner: 1200;  --z-overlay: 1300;  --z-modal: 1400;     --z-popover: 1500;
--z-skiplink: 1600; --z-toast: 1700;   --z-tooltip: 1800;
```

---

## 6. Typography

**Families** (free, high-character — load via [Fontshare](https://fontshare.com), Google Fonts, or `next/font`):

- `--font-display` — **Clash Display** / General Sans → headings, hero, numbers. Personality lives here.
- `--font-sans` — **Geist** / Inter → body, UI. Neutral, legible.
- `--font-serif` — **Fraunces** → editorial pull-quotes, long-form (optional contrast).
- `--font-mono` — **Geist Mono** / JetBrains Mono → code, data, tabular numbers.

**Type scale** (1.250 major-third-ish; line-height tightens as size grows):

| Class | size / line-height | Role |
|---|---|---|
| `text-xs` | 0.75 / 1rem | captions, overlines (often `uppercase tracking-wide`) |
| `text-sm` | 0.875 / 1.25rem | secondary text, labels |
| `text-base` | 1 / 1.5rem | **body default** |
| `text-lg` | 1.125 / 1.75rem | lead paragraph |
| `text-xl` | 1.25 / 1.75rem | card titles |
| `text-2xl` | 1.5 / 2rem | section subhead |
| `text-3xl` | 1.875 / 2.25rem | section head |
| `text-4xl` | 2.25 / 2.5rem | page title |
| `text-5xl`–`text-7xl` | 3 / 4.5rem, lh ~1 | hero (`--font-display`, `tracking-tight`) |

**Rules:** body �le 70–75ch (`max-w-prose`/`65ch`). Headings `tracking-tight` (`-0.02em`); uppercase labels `tracking-wide` (`+0.04em`). Weight contrast (400 body vs 600–800 display) does more than size alone. Use `font-variant-numeric: tabular-nums` for tables/metrics.

---

## 7. Elevation & depth

Don't ship flat. Build atmosphere with **layered, tinted** elevation:

| Level | Token | Use |
|---|---|---|
| 0 | (border only) | flush list rows, inputs |
| 1 | `shadow-sm` | cards at rest |
| 2 | `shadow-md` | hovered cards, dropdowns |
| 3 | `shadow-lg` | popovers, sticky bars |
| 4 | `shadow-xl` | modals, command palette |
| 5 | `shadow-2xl` | full-screen dialogs |
| — | `shadow-glow` | focus/active brand emphasis (sparingly) |

Atmosphere techniques: subtle radial/conic gradient washes behind heroes; a faint noise/grain texture; a 1px top highlight border on dark cards (`border-white/10`); soft colored glows under primary CTAs. Shadows are **tinted with the ink/brand hue**, never pure black in light mode.

---

## 8. Motion

```css
--duration-instant: 75ms;  --duration-fast: 150ms;   --duration-normal: 200ms;
--duration-moderate: 250ms; --duration-slow: 300ms;  --duration-slower: 500ms;

--ease-out:    cubic-bezier(0.16, 1, 0.3, 1);    /* entrances, expand — snappy */
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);   /* moves, reorders */
--ease-in:     cubic-bezier(0.4, 0, 1, 1);        /* exits */
```

**Defaults:** hovers/taps `fast`; enter/leave `normal` with `ease-out`; layout shifts `moderate`. Prefer transform/opacity (GPU) over animating layout. For springs use Framer Motion (`type: "spring", stiffness: 400, damping: 30`). **Always** wrap non-essential motion:

```css
@media (prefers-reduced-motion: reduce) { *, ::before, ::after {
  animation-duration: .01ms !important; transition-duration: .01ms !important; } }
```

---

## 9. Interaction states & accessibility (WCAG 2.2 AA)

**State styling** — map to React Aria / Radix data-attributes (and native pseudo-classes) so states are consistent and accessible:

| State | Hook | Treatment |
|---|---|---|
| Hover | `:hover` / `data-[hovered]` | `bg-accent`, subtle lift |
| Press | `:active` / `data-[pressed]` | scale `0.98`, deepen |
| Focus (keyboard) | `:focus-visible` / `data-[focus-visible]` | **2px `ring` + 2px offset, always visible** |
| Selected | `data-[selected]` / `aria-selected` | `bg-accent text-accent-foreground` |
| Disabled | `:disabled` / `data-[disabled]` | `opacity-50 pointer-events-none` |
| Invalid | `aria-invalid` / `data-[invalid]` | `border-destructive ring-destructive` |
| Loading | `data-[pending]` | spinner + `aria-busy`, keep layout stable |

**Hard requirements:**
- **Contrast:** ≥ 4.5:1 body text, ≥ 3:1 large text (≥24px / 18.66px bold) and UI/icon/border boundaries. Verify both modes.
- **Focus visible:** every interactive element shows a `ring` on keyboard focus. Never `outline: none` without a replacement.
- **Target size (WCAG 2.2):** ≥ 24×24px minimum; aim for 44×44px on touch. Use padding, not just icon size.
- **Semantics:** real `<button>`/`<a>`/`<nav>`/`<main>`/`<h1-6>`, one `<h1>`, logical heading order, `<label>` tied to every input, `alt` on meaningful images, visible text not conveyed by color alone.
- **Keyboard:** everything operable without a mouse; visible focus order matches DOM; Esc closes overlays; focus trapped in modals and restored on close.
- **Respect** `prefers-reduced-motion` and `prefers-color-scheme`.

---

## 10. Layout & composition

- **Containers:** content max `--container: 80rem` (1280px); prose max `65ch`. Generous gutters (`px-4` mobile → `px-8`/`px-12` desktop).
- **Breakpoints:** `sm 640 · md 768 · lg 1024 · xl 1280 · 2xl 1536`. Design mobile-first; enhance upward.
- **Grid:** 12-col on desktop; collapse to 1–2 on mobile. Use `gap-*` tokens, not margins, for grid/flex spacing.
- **Bento layout** (great for dashboards, feature sections, landing "everything" grids): a CSS-grid mosaic of unequal tiles that still aligns to one grid. Vary tile *span*, not tile *style* — keep radius, padding, and elevation consistent so the rhythm reads as one system.

```html
<section class="grid grid-cols-2 lg:grid-cols-4 auto-rows-[12rem] gap-4">
  <article class="col-span-2 row-span-2 rounded-xl border bg-card p-6 shadow-sm">…hero tile…</article>
  <article class="rounded-xl border bg-card p-6 shadow-sm">…</article>
  <article class="rounded-xl border bg-card p-6 shadow-sm">…</article>
  <article class="col-span-2 rounded-xl border bg-card p-6 shadow-sm">…wide tile…</article>
</section>
```

Always design the **empty, loading, and error** states for any data view — not just the happy path.

---

## 11. Component recipes (tokens in practice)

```tsx
// Primary button — the one main action. Note focus-visible ring + press scale.
<button class="inline-flex items-center justify-center gap-2 rounded-lg
  bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground
  shadow-sm transition-all duration-fast
  hover:brightness-110 active:scale-[0.98]
  focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
  focus-visible:ring-offset-background disabled:opacity-50 disabled:pointer-events-none">
  Continue
</button>

// Secondary button
<button class="… bg-secondary text-secondary-foreground hover:bg-accent …">Cancel</button>

// Card
<div class="rounded-xl border border-border bg-card text-card-foreground p-6 shadow-sm
  transition-shadow duration-normal hover:shadow-md">…</div>

// Input with focus ring + invalid state
<input class="w-full rounded-md border border-input bg-background px-3 py-2 text-sm
  placeholder:text-muted-foreground
  focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2
  focus-visible:ring-offset-background
  aria-[invalid=true]:border-destructive aria-[invalid=true]:ring-destructive" />
```

---

## 13. Backgrounds, accents & atmosphere

Flat backgrounds are the #1 tell of "AI slop." Always layer at least one atmosphere effect behind hero sections, app shells, and onboarding screens. Tokens live in `tokens.css`:

| Token | Use for | Recipe |
|---|---|---|
| `--bg-mesh` | Hero washes, sign-in, marketing | 4 OKLCH radial blobs at varied hues; fades to transparent |
| `--bg-aurora` | Mission control top, dark hero | Two large ellipses bleeding off-screen |
| `--bg-conic` | Iconic brand moments, loading screens | Conic gradient through brand wheel |
| `--bg-dot-grid` | Empty states, drag canvases, app shell | 1px dots on a 22px grid |
| `--bg-blueprint` | Editors, mission-control HUD, status pages | Crosshatch 48px brand-tinted grid |
| `--bg-noise` | Sit *on top of* any of the above | Inline SVG turbulence; ~5–8% opacity equivalent |
| `--gradient-brand` | Primary CTAs, hero metric tiles | Brand → adjacent hue 135° |
| `--gradient-success/warning` | Status hero, badges | Hue-matched 135° |
| `--gradient-holo` | Special-mode emphasis (AI, premium) | 4-stop iridescent |

**Composition recipes:**

```css
/* App shell (any archetype) — subtle but never flat */
.app-shell { background: var(--background) var(--bg-dot-grid);
             background-size: var(--bg-dot-grid-size); }

/* Hero with depth — mesh below, noise above, content above noise */
.hero { position: relative; isolation: isolate; }
.hero::before { content:""; position:absolute; inset:0; z-index:-2; background: var(--bg-mesh); }
.hero::after  { content:""; position:absolute; inset:0; z-index:-1;
                background: var(--bg-noise); opacity:.6; mix-blend-mode: overlay; }

/* Glassmorphic sidebar / sheet / command palette */
.glass { background: var(--glass-bg-light); backdrop-filter: var(--glass-blur);
         -webkit-backdrop-filter: var(--glass-blur); border: var(--glass-border-light); }
.dark .glass { background: var(--glass-bg-dark); border: var(--glass-border-dark); }

/* Gradient-border card — perceived depth without a heavy shadow */
.card-gradient-border { position: relative; background: var(--card); border-radius: var(--radius); }
.card-gradient-border::before { content:""; position:absolute; inset:0;
  padding:1px; border-radius: inherit; background: var(--gradient-brand);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude; pointer-events:none; }

/* Spotlight halo behind a primary CTA */
.cta-glow::after { content:""; position:absolute; inset:-40% -20%; z-index:-1;
  background: radial-gradient(closest-side, oklch(0.7 0.21 256 / 0.55), transparent);
  filter: blur(28px); }
```

**Guidance:** one atmosphere effect per surface — mesh **or** dot grid, not both. Noise is the only effect that layers on top. On dark mode, drop blob alpha to ~0.18–0.25 (it reads stronger against ink). Always sit content on a clean card surface — atmosphere is *between* cards, never under text.

---

## 14. App archetypes (the system targets all of these)

This system isn't just for one dashboard. Each archetype below tells you how to *configure* the same tokens for a very different feel. Pick the archetype before building; the rest of the file applies unchanged.

### A. SaaS analytics dashboard *(Vision / Purity / SaaS Selling Figma family)*
- **Shell:** left sidebar `w-60` glassmorphic (`var(--glass-bg-*)`); top bar with search + bell + avatar; main on `--bg-dot-grid`.
- **Hero row:** 3–4 KPI cards (Section 15.1), one *hero metric tile* (15.2) doubling width with `--gradient-brand` background.
- **Density:** medium — 24px card padding, 16px gaps, `text-base` body. Generous whitespace.
- **Color:** restrained — one dominant `--primary`, gradients reserved for hero & CTA. Charts use the `--chart-*` palette.
- **Type:** display face for KPI values (`text-4xl/5xl font-display`), sans for everything else.

### B. ERP / back-office *(SAP-modern, Linear-dense)*
- **Shell:** left icon-rail `w-14` + collapsible label panel `w-56`; persistent top context bar (entity title, breadcrumbs, save state); main on flat `--background`.
- **Density:** **high** — 8–12px row height, 12–16px card padding, `text-sm` body, tabular-nums everywhere. No big gradients on functional surfaces.
- **Color:** neutral-heavy. Save accents (`--primary`, `--destructive`) for actions and validation only. Status dots are first-class.
- **Tables are the hero** — sticky headers, frozen first column, inline edit, bulk-select toolbar, density toggle (compact/normal/comfortable).
- **Forms:** two-column on desktop, label-left, inline help, sticky footer with Save/Discard. Always show unsaved-change banner.

### C. Mission control / operations *(NASA-modern, ops cockpit)*
- **Dark-first.** Force `.dark`; light mode is optional. Background = `--bg-aurora` over deep ink + faint `--bg-blueprint` grid.
- **Layout:** no chrome — full-bleed grid of panels, each its own widget. Bezel = 1px highlight border (`border-white/10`) + a tinted shadow.
- **Type:** display family **or `--font-mono`** for big numerics; tabular-nums mandatory. Labels in `uppercase text-xs tracking-wider text-muted-foreground`.
- **Status taxonomy:** OK / WARN / CRIT / UNKNOWN — paired status dot + label + last-update timestamp on every panel. Color alone is never the signal (icon + label too).
- **Live data:** use **number-pop-in** (transitions-dev) for value updates; **text-states-swap** for status labels; never a fade-in on every tick.
- **Motion:** restrained. Only state changes animate; charts redraw without bouncing.

### D. PWA / mobile-first app shell
- **Shell:** top app bar (h-14), bottom tab nav (h-16, ≥3 tabs ≤5) on mobile; collapses to side-nav at `lg`. Use `safe-area-inset-*` env() padding.
- **Targets:** ≥ **44×44px** everywhere (WCAG 2.2 + iOS HIG). Padding > icon size.
- **Surfaces:** content on `--card`, app shell on `--background`. Bottom sheets > modals on mobile.
- **States:** every screen designs offline (`navigator.onLine === false`), pull-to-refresh, install-prompt, push-permission, low-battery, and skeleton-loading variants.
- **Motion:** **page-side-by-side** for list↔detail, **panel-reveal** for sheets, **modal** only on tablet+.

### E. Marketing landing
- See [`brand-landingpage`](skill) for the brand interview flow. From a tokens standpoint: hero with `--bg-mesh + --bg-noise`, big display type, gradient CTA with `cta-glow`, bento section using §10 grid, testimonial row, pricing as 3-column.

---

## 15. Dashboard block library (efferd + Figma pattern set)

Compose dashboards from these blocks. All read from the existing tokens. **Never invent a new card shape** — pick a block, configure it.

### 15.1 KPI stat card *(every dashboard needs this)*
- **Anatomy:** overline label (uppercase, `text-xs`, `text-muted-foreground`) → big value (`text-4xl font-display tabular-nums`) → delta chip (▲ +12.4% in `text-success` / ▼ in `text-destructive`) → corner sparkline (24px tall, `stroke=currentColor` opacity 0.6).
- **Hover:** `shadow-md`, no scale. Sparkline brightens.
- **States:** loading → skeleton with shimmer; empty → "—" + "No data this period."

### 15.2 Hero metric tile *(efferd Dashboard 8 vibe)*
Doubles or triples the size of a KPI card. Background = `--gradient-brand` or `var(--bg-mesh)` clipped, value in `text-6xl/7xl font-display`, brand-foreground text. Sits at the top-left of the hero grid.

### 15.3 Chart cards
- **Area / line** (revenue, traffic): 240–320px tall, axis labels in `text-xs text-muted-foreground`, faint horizontal gridlines (`border-border/50`), legend top-right as chips. Tooltip uses `--popover` + `shadow-lg`.
- **Donut / pie** (channel mix, category): center label (big value + tiny caption). Stroke widths 12–16px. Use first 4 `--chart-*` tokens.
- **Funnel** (conversion): horizontal stacked bars with percentage drops between stages.
- **Heatmap** (cohort, engagement): grid of cells colored on a single-hue scale derived from `--primary` lightness.
- **Sparkline** (in any card corner): inline `<svg>`, no axes, 24–40px tall.

### 15.4 Data table
- Sticky header (`bg-card/95 backdrop-blur` + `border-b`).
- Row hover: `bg-accent/50`. Selected: `bg-accent`. Striping is optional and ≤ `bg-muted/30`.
- Numeric cols right-aligned, `tabular-nums`.
- Column toolbar: search input, multi-filter chips, density toggle, column-visibility menu, export button (rightmost).
- Bulk-select bar appears with `panel-reveal` motion when ≥1 row selected.

### 15.5 Activity feed
Vertical list, each item: avatar (32px) → text (`<strong>` actor, action, `<a>` object) → timestamp (`text-xs text-muted-foreground`, relative). Group by day with sticky day-header.

### 15.6 Attention queue / ticket list
Each row: priority dot (CRIT/WARN/INFO color) → title → meta (assignee avatar, age, ID). Hover reveals quick-actions on the right (`opacity-0 → 100` on hover).

### 15.7 Team roster / on-duty
Horizontal avatar stack (use **avatar-group-hover** motion). Each: presence dot, name on hover. "+3 more" overflow chip.

### 15.8 Date toolbar
Pills: `Today | 7d | 30d | 90d | YTD | Custom`. Selected pill = `bg-accent text-accent-foreground`. Mounted at top-right of any data view.

### 15.9 Command bar (Cmd-K)
Centered modal w/ `--modal-open-dur` open, `--glass-blur` + `shadow-2xl`. Sectioned (Recent / Navigate / Actions / Help). Arrow-key nav, Enter to invoke. Always reachable.

### 15.10 AI insight callout
Card with `--gradient-holo` 1px border, sparkles icon, terse copy ("Trend: refunds up 14% in last 7d, mostly from `Pro` plan"), an action button. Use sparingly — one per view.

### 15.11 Empty / error / offline states
Centered illustration *or* a simple icon, terse title (`text-xl font-display`), 1-sentence description, primary action. Never just "No data" — say *what's missing* and *how to fix it*.

### 15.12 Onboarding checklist
4–6 items, each: checkbox state + title + 1-line description + action button. Show overall progress as `progress` element using `--primary`. Dismissible.

---

## 16. Motion library *(transitions-dev integrated)*

This project ships **transitions-dev** — 12 named, production-ready transitions with a single `:root` token block (already merged into `tokens.css`). **Prefer these over inventing new motion.**

| Transition | Use when | File |
|---|---|---|
| **card-resize** | A container changes width/height with state | `01-card-resize.md` |
| **number-pop-in** | A number updates (KPIs, counters, mission-control values) | `02-number-pop-in.md` |
| **notification-badge** | Dot/badge appears on a trigger | `03-notification-badge.md` |
| **text-states-swap** | Inline text content changes (status labels, button text) | `04-text-states-swap.md` |
| **menu-dropdown** | Surface grows from a trigger (popovers, menus) | `05-menu-dropdown.md` |
| **modal** | Centered dialog open/close | `06-modal.md` |
| **panel-reveal** | Surface slides into a region (sheets, drawers, bulk-select bars) | `07-panel-reveal.md` |
| **page-side-by-side** | List ↔ detail, step 1 ↔ step 2 | `08-page-side-by-side.md` |
| **icon-swap** | Two icons in the same slot (spinner → check) | `09-icon-swap.md` |
| **success-check** | Confirmation moment | `10-success-check.md` |
| **avatar-group-hover** | Hover on a horizontal stack of items | `11-avatar-group-hover.md` |
| **error-state-shake** | Form validation, "this is wrong" feedback | `12-error-state-shake.md` |

Invoke the skill with `transitions reveal`, `transitions review`, or `transitions apply` for a chosen element. Every snippet ships a `prefers-reduced-motion` guard — never strip it.

**House defaults** for things not covered by transitions-dev: hover/tap `--duration-fast` w/ `--ease-out`; layout shifts `--duration-moderate`. Animate `transform`/`opacity`/`filter`, never `width`/`height`/`top`/`left` directly (use `card-resize` if you need to).

---

## 17. Pre-ship checklist

- [ ] Stated the aesthetic direction before building; it's coherent, not generic.
- [ ] Zero hardcoded colors/sizes/shadows — tokens only.
- [ ] Verified in **light and dark**.
- [ ] Contrast passes AA in both modes; focus-visible rings present everywhere.
- [ ] Targets ≥ 24px (44px touch); fully keyboard-operable; Esc/focus-trap on overlays.
- [ ] Empty / loading / error states designed.
- [ ] Motion is purposeful and respects `prefers-reduced-motion`.
- [ ] Responsive from 360px → 1536px; no horizontal scroll.
- [ ] Type scale + spacing scale used consistently; whitespace is generous.

> When in doubt: fewer elements, more hierarchy, more whitespace, one confident accent.

Discussion

Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.

Posts are public.Sign in to post

No one has posted yet. Be the first.