---
name: fable-ui-designer
description: Use when building, restyling, reviewing, or polishing any UI in this project — components, pages, tokens, motion, loading states, typography, color — or when a task references the UI overhaul, a design template ("premium", "futuristic"), or asks whether a screen "feels" right.
---

# Fable UI Designer

Audience: AI coding agents (Claude subagents, codex/gpt implementers) first. Rules here = enforced review criteria, not suggestions. Work gets rejected against this file.

## How to use

1. Load the active design template from `templates/<name>.md` (task says which; current default: `premium`). Template = the design language (exact values, recipes, mood). This file = universal excellence rules that apply under EVERY template.
2. Project laws in `.claude/skills/md-ui-developer/SKILL.md` ALWAYS win: locked primitive set, no hardcoded values (tokens only), RTL-first logical props, IS 5568/WCAG AA, i18n he+en, Loading Law, money/time formatting. This skill never overrides them. New visual values (shadow, gradient, duration, radius) MUST land as tokens in `src/styles/tokens.css` first, then be consumed by class/var — never inline.
3. Conflict order: md-ui-developer law > this file > template.

## Hierarchy — one focal point

- Every screen has EXACTLY ONE primary action/focal point. Two "primary" elements = both demoted. Decide the focal point BEFORE styling anything.
- Importance budget: primary (1 per screen) > secondary (few) > tertiary (everything else). If you can't say which tier an element is in, it's tertiary.
- Contrast of SIZE beats contrast of weight beats contrast of color. To emphasize, first try bigger, then bolder, then colored — in that order.
- De-emphasize the neighbors instead of amplifying the hero. Muting metadata is stronger than enlarging the title.

## Depth and light

- One light source, from above. Highlights on top edges, shadows below. Never a shadow that goes up.
- Elevated surface = 2 shadows: tight ambient (`0 1px 2px`) + soft distant (`0 8px 24px`), both low alpha. Single mid-size shadow = amateur tell. DO NOT stack >2.
- Borders semi-transparent black (`rgb(0 0 0/.08)`), not opaque gray — they adapt over any background.
- Pick ONE separator per boundary: border OR shadow OR background shift. Two of the three on the same edge = visual noise; delete one.
- Radius nesting law: inner radius = outer radius − gap. Child card inside 20px parent with 8px padding → 12px. Equal radii on nested elements look wrong at every size.

## Motion doctrine

- One easing family app-wide (tokens exist: `--ease-*`). Entrances decelerate, exits accelerate, exits run ~40% shorter than entrances.
- Durations ONLY from tokens (120/200/320/500ms). Micro-feedback 120, local transitions 200, page-level 320. Anything >500ms must justify itself.
- Animate `transform` and `opacity` only. NEVER animate `width/height/top/left/margin/padding` (layout thrash). `box-shadow` animates via opacity of a pseudo-element, not the shadow itself.
- List entrances stagger 30ms per item, cap at 8 items — item 9+ appears with item 8. Uncapped stagger = last card arrives seconds late.
- Every animation gated on `prefers-reduced-motion: no-preference` or `motion-reduce:*`. No exceptions, including "subtle" ones.
- Motion must mean something: enter/exit, cause/effect, spatial continuity. Decorative motion on idle screens = reject (marquee rows already grandfathered).

## Typography

- Display sizes (2xl+) get negative tracking `-0.01em`..`-0.02em` and tighter line-height. Body stays default tracking. NEVER positive letter-spacing on body/paragraph text (uppercase micro-labels only).
- ALL numbers users compare or watch change — prices, countdowns, quantities, stats — get `font-variant-numeric: tabular-nums`.
- Max 2 font weights per surface. Hierarchy comes from size + color, not a weight ladder.
- Line length: body text 45–75ch. Don't center text longer than 2 lines.
- Baseline trick: icon next to text aligns to the text's cap-height optically — verify at final size, not in isolation.

## Color

- 60-30-10: ~60% neutral surface, ~30% muted support, ~10% accent. Accent = brand-primary, reserved for interactive/primary elements ONLY. Blue on a non-clickable element teaches users blue ≠ clickable.
- Text on white: max 3 tones per screen (heading / body / muted). A 4th gray = pick one of the existing 3.
- State colors (success/warning/danger) appear only WITH meaning attached (status, feedback, delta) — never as decoration.
- Tint neutrals toward brand hue (already in tokens) — pure gray next to brand color looks dead; keep it that way in any new tokens.

## States are the design

A component is its states. Ship order per md-ui-developer Loading Law; visually:
- Interactive: rest / hover / active (`active:scale-[0.98]` class of feedback) / focus-visible / disabled. Missing active-press feedback on touch = unfinished.
- Data: loading (layout-matched skeleton) / empty (illustrated EmptyState + one action) / error (same geometry as skeleton) / partial / full.
- Text: test with 2× the expected string length AND Hebrew AND English. Truncation is a decision (`line-clamp` + title attr), never an accident.

## Perceived performance

- Optimistic UI for reversible toggles (favorite, quantity, wishlist): flip instantly, reconcile in background, revert with toast on failure.
- Every remote image sits on a dominant-color or neutral placeholder — never a white/empty gap that pops.
- Layout shift budget: zero. Reserve space for async content with exact dimensions (skeleton geometry law).
- Never let a fast action show a loading state (SkeletonGuard 200ms delay exists — use it).

## Micro-detail bar (reject work missing these)

- Hit targets ≥44×44px on touch, even when the glyph is smaller (pad the target, not the icon).
- `:focus-visible` rings only (no ring on tap/click), visible on EVERY focusable, offset so it never clips.
- `overscroll-behavior: contain` on every drawer/sheet/inner scroller — chained page scroll behind an overlay = broken.
- Safe-area insets on anything fixed to viewport edges (tokens exist).
- Optical alignment beats box-model alignment: a play-icon or chevron centered mathematically looks off-center — nudge it.
- RTL: every effect, shadow offset, slide direction, and gradient angle verified in `dir="rtl"` AND `dir="ltr"`.

## Anti-slop — DO NOT

```
// reject: gradient/glow on more than one element family per screen
// reject: animating everything that mounts — motion without meaning
// reject: 3+ border radii values visible in one view
// reject: letter-spacing added to body text to "feel airy"
// reject: two primary buttons side by side
// reject: emphasis by adding (bolder+bigger+colored+shadow) — pick the minimum lever
// reject: new shadow/duration/color written inline instead of tokens.css
// reject: physical left/right classes; hardcoded he/en strings; <div onClick>
```

## Review checklist (evaluator: run against every screen touched)

- [ ] One focal point; importance tiers legible at squint distance
- [ ] Depth: 2-layer shadows, one separator per edge, radius nesting correct
- [ ] Motion: token durations/easings, transform/opacity only, reduced-motion gated, exits faster
- [ ] Numbers tabular; display tracking negative; ≤2 weights
- [ ] Accent reserved for interactive; ≤3 text tones
- [ ] All states present incl. active-press, empty, error-at-skeleton-geometry
- [ ] Zero layout shift; optimistic where reversible
- [ ] Micro bar: hit targets, focus-visible, overscroll, safe-area, RTL-verified
- [ ] Every new value is a token; typecheck + lint green
