---
name: frontend-designer
description: >-
  Design and build UI for the Balinyaar client (Next.js 16 + MUI v9). Use when
  creating or restyling any screen, page, component, layout, or visual in
  client/ — turning a feature, mockup, or Figma design into branded, RTL-aware,
  dark-mode-ready, i18n-complete React/MUI code. Covers the brand palette,
  design tokens, typography, the App* component library, layout shells, icons,
  and the hard rules every Balinyaar UI must follow.
---

# Balinyaar Frontend Designer

Build UI that looks like Balinyaar and behaves correctly in both locales and both
color schemes on the first try. This skill is the design contract; the engineering
contract (providers, fetch, cookies, routing) lives in [client/CLAUDE.md](../../../client/CLAUDE.md) — read it
before touching layout/provider/data code, **don't restate it**, and never violate it.

**Stack:** Next.js 16 (App Router, Turbopack) · React 19 · MUI v9 (`@mui/material`) ·
Emotion (RTL via `stylis-plugin-rtl`) · next-intl v4 · notistack. Everything below
lives under `client/src/`.

---

## 1. Brand identity

Balinyaar is a **trust-first home-nursing marketplace in Iran**. The visual tone is
calm, warm, clinical-but-human — not a cold medical dashboard. Default audience is
Persian (RTL); English is secondary.

**Logo mark** — this skill is the construction source of truth (the original
`product/balinyaar.html` seed deck no longer exists in the repo): a deep-teal
rounded-square ground (`var(--bal-primary)`), a cream lowercase "b" glyph built
from a stem + a ring bowl (`var(--bal-primary-contrast)`), and a single
terracotta dot accent (`var(--bal-secondary)`). That trio — **teal ground, cream
glyph, terracotta accent** — is the whole identity. Use terracotta sparingly as
the single accent; teal carries everything else. Implemented as two SVGs under
`components/common/AppIcon/icons/`:
- `LogoMark.tsx` — a monochrome `currentColor` version of just the glyph (no
  ground square), registered as `ICONS.logo`. Use via `<AppIcon icon="logo">`
  anywhere an inline, recolorable brand glyph is needed.
- `LogoLockup.tsx` — the full-color mark (ground + glyph + dot, token-driven so
  it tracks the color scheme) for `BrandMark` (auth splash). The wordmark next
  to it stays real, translated `<Typography>` — never bake locale text into an SVG.
- The favicon (`src/app/favicon.ico`) and `public/img/favicon/*.png` are
  rasterized from the same construction (fixed brand hex, not CSS vars — static
  binary assets are the one place a literal hex is correct). Regenerate with a
  `sharp`-based script if the mark ever changes; don't hand-edit the PNGs/ICO.

| Role | Light | Dark |
|------|-------|------|
| Primary — deep teal | `#1d4a40` | `#6fc0ac` (lifted, readable on dark) |
| Secondary — terracotta | `#d98c6a` | `#e6a98a` |
| Page surface | `#faf9f5` cream | `#0f1c19` deep teal |
| Paper / card | `#ffffff` | `#16302a` teal surface |
| Text primary | `#1b2521` ink | `#f3efe9` cream |

---

## 2. Design tokens — the two-layer system (read this before styling anything)

Colors exist in **two mirrored places** that must stay in sync. Pick the right one:

1. **MUI palette** — `src/theme/colors.ts` (`BRAND`, `LIGHT_PALETTE`, `DARK_PALETTE`).
   Drives `--mui-palette-*` and all MUI component coloring. Reach it through MUI APIs:
   `color="primary"`, `sx={{ color: 'text.secondary', bgcolor: 'background.paper' }}`.
   **This is the default for component styling.** It auto-switches with the color scheme.

2. **`--bal-*` CSS variables** — `src/theme/tokens.css`, defined under
   `[data-mui-color-scheme='light'|'dark']`. The source of truth for **custom CSS
   outside MUI's palette** and for **semantic feedback colors MUI doesn't define**:
   `--bal-success`, `--bal-error`, `--bal-warning`, `--bal-info` (each `+ -contrast`).
   Reference as `var(--bal-primary)`, `var(--bal-success-contrast)`, etc.

**Rules:**
- Styling a MUI component → use palette keys (`color="primary"`, `sx` palette refs).
- Need success/error/warning/info → use `--bal-*` tokens, **not** MUI defaults — the
  MUI palette has no semantic colors, and these tokens are brand-harmonized.
- Need a custom color in raw CSS → add a `--bal-*` token (in **both** scheme blocks),
  then `var(--…)`. **Never hard-code a hex** in `sx`, `styled`, or a component.
- Adding/changing a color means editing `tokens.css` **and** `colors.ts` together (the
  file headers call out the sync requirement).

**Beyond color** — `tokens.css` also defines non-palette tokens (`colors.ts` never needs
these; they're define-only in CSS):
- **Radius** — `--bal-radius-sm` (4px, controls: buttons/inputs), `--bal-radius-md`
  (10px = `theme.shape.borderRadius`, the house default: cards/paper), `--bal-radius-lg`
  (16px: dialogs). Reference the token/constant, never invent a new radius.
- **Elevation** — `--bal-shadow-1/2/3`, teal-tinted (black-teal in dark mode) shadow
  steps that back `theme.ts`'s `shadows` array — every MUI elevation (Paper, Dialog,
  Menu, Popover, AppBar) resolves through these, never MUI's default grey stack.
- **Motion** — `--bal-motion-fast/base/slow` (120/200/300ms) + `--bal-easing-standard`.
  Consumed by the phase-12 app-wide motion pass; use them for any transition you add now.
- **Focus** — `--bal-focus-ring`, the 2px ring `MuiCssBaseline`'s global `:focus-visible`
  override uses. Don't hand-roll a focus style; it's already uniform everywhere.
- **Rating** — `--bal-rating` / `--bal-rating-empty` (filled/empty star colors) —
  `RatingInput` uses these, not `--bal-warning`.
- **Trust** — `--bal-trust` / `--bal-trust-soft`, a distinct identity (not
  primary/success) for verified marks — `TrustBadge` and any future verification UI.
- **Money emphasis** — `--bal-money-emphasis`, an AA-contrast-safe color for emphasized
  money text. `--bal-secondary` (terracotta) fails AA contrast at small sizes on light
  backgrounds — never use it for money text, use this token instead.

---

## 3. Typography & fonts

- `shape.borderRadius: 10` (set in `src/theme/theme.ts`) — the house corner radius
  (= `--bal-radius-md`). Don't override per-component unless deliberate; the radius
  *scale* is `--bal-radius-sm` (4, controls) / `-md` (10, cards) / `-lg` (16, dialogs).
- **Weight system — never write `fontWeight: 600`.** Mikhak and Space Grotesk both load
  only 400/500/700 (no 600 face), so a requested 600 silently renders full Bold. Use
  **700** for headings (`h1`–`h6`) and buttons/strong emphasis, **500** for lighter
  in-text emphasis (subtitles, row labels, chip text). This is enforced globally in
  `typography.ts`; match it in any new `sx` you write.
- Buttons: `textTransform: 'none'`, weight 700 (set globally in `typography.ts`). Never
  re-uppercase button text.
- Persian type scale (`TYPOGRAPHY_RTL`): `letterSpacing: 0` on every variant (Persian is
  a joined script — tracking breaks glyph connections), body line-height ≥1.7, heading
  line-height ~1.4–1.5 (room for ascenders/descenders), and `responsiveFontSizes()`
  wraps both themes (`theme.ts`) so heading sizes scale down on small viewports —
  don't hand-roll per-breakpoint `fontSize` overrides.
- **Fonts are loaded per-locale in `src/app/[locale]/layout.tsx` only** — Mikhak
  (`--font-mikhak`) for `fa`, **Space Grotesk** (`--font-space-grotesk`, via
  `next/font/google`, self-hosted at build time) for `en`. Both `preload: false` with a
  conditional `.variable` className so neither ships to the other locale.
  **Never load a font in a component or page.**
- Use `<Typography variant=…>` for text — it inherits the correct direction-aware family
  (`TYPOGRAPHY_RTL` = Mikhak everywhere for full Persian glyph coverage; `TYPOGRAPHY_LTR`
  = Space Grotesk headings + system-stack body). Import neither directly in components;
  let the theme apply them.

---

## 4. The component library — reach for these before raw MUI

Shared primitives live in `src/components/` (barrel: `@/components`). Prefer the `App*`
wrapper over the bare MUI component — the wrappers carry the house defaults.

| Component | Use for | Notes |
|-----------|---------|-------|
| `AppButton` | all buttons & button-links | default `variant="contained"`; pass `to`/`href` to render as a link automatically; `startIcon`/`endIcon` accept an **icon name string** (e.g. `startIcon="search"`) or a node; non-MUI `color` strings become text color |
| `AppIconButton` | icon-only actions | takes an icon `name`, `title`, `to`/`onClick` |
| `AppIcon` | any icon | `icon="home"` by registered name (§6); `size`, `color` props |
| `AppLink` | internal/external links | locale-aware Next navigation; default underline `hover` |
| `AppAlert` | inline alerts | default `severity="error"`, `variant="filled"` |
| `AppImage` | images | wrapper around next/image conventions |
| `AppLoading` | loading state | default circular, `primary`, `3rem` |
| `ErrorBoundary` | wrapping fault-prone subtrees | already wraps page content in the shell |
| `ProfileSummary` | the identity card in chrome | avatar+name+masked phone+role label+optional `TrustBadge`; vertical or `compact` horizontal chip |

Defaults for these live in `src/components/config.ts` (`APP_BUTTON_VARIANT`,
`APP_ICON_SIZE = 24`, `CONTENT_MAX_WIDTH = 800`, `CONTENT_MIN_WIDTH = 320`, alert/link/
loading defaults). Change a default there, not per-call-site.

For layout/spacing use MUI primitives directly: `Box`, `Stack`, `Container`, `Grid`,
`Paper`, `Card`. Use the `spacing`/`sx` system (theme spacing unit = 8px) — never inline
pixel margins for rhythm.

**New shared component?** Put it in `src/components/<Name>/<Name>.tsx` with an
`index.tsx` barrel, follow the `App*` prop-spreading + JSDoc style of `AppButton.tsx`,
and add a co-located `.test.tsx` (mandatory for anything imported in >1 place — see
CLAUDE.md "Unit Testing"; wrap with `<ThemeProvider>`, never mock MUI).

---

## 5. Layout & page shells

- **Four per-actor shells**, each wrapped in `RoleGuard` (don't touch): `CustomerLayout`
  (mobile-first — contextual `TopBar`: brand lockup on the 5 root tabs, title + back
  chevron on pushed routes, an inline desktop top-nav at `≥md` replacing the mobile
  `BottomBar`), `NurseLayout` / `AdminLayout` / `PartnerLayout` (all three share
  `TopBarAndSideBarLayout`, `src/layout/`). All chrome navigation goes through
  `@/i18n/navigation` (`Link`/`usePathname`/`useRouter`) — never a raw `next/link` or a
  manual `` `/${locale}` `` prefix.
- `TopBarAndSideBarLayout`: a fixed `TopBar` (title from `useRouteTitle()`, the
  route→title map in `layout/routeTitle.tsx`) + a `SideBar` rendered as **two Drawers
  sharing one content tree** — mobile `temporary` and desktop `variant="permanent"` —
  switched purely by `sx` breakpoint, not `useIsMobile()`; the permanent Drawer is a
  normal flex sibling of the main column, so desktop reserves its own width with no
  manual offset math and no post-hydration layout jump. Optional `identity` (TopBar
  chip — admin/partner), `sidebarIdentity` (sidebar card — nurse's `ProfileSummary`),
  and `mobileBottomBar` slots.
- Sidebar nav items are `{ title, path, icon, group? }` arrays (`@/utils`'s
  `LinkToPage`) built with `useTranslations('nav')`; a shared `group` string on
  consecutive items renders a `ListSubheader` section (see `NurseLayout`/`AdminLayout`).
  Selection is computed once via the shared `matchActivePath` (longest-prefix,
  winner-takes-all) helper — reuse it for any new nav list, never hand-roll
  `pathname.startsWith`.
- **Public screens** use `PublicLayout` — a minimal corner strip (logo +
  `LocaleSwitcher` + dark toggle), no sidebar/bottom bar; the step content (`AuthCard`)
  carries its own larger `BrandMark`, so don't duplicate a big lockup in the shell.
- Page content is auto-wrapped in `ErrorBoundary` inside every shell.
- Shell dimensions are constants in `src/layout/config.ts` (`SIDE_BAR_WIDTH = 240px`,
  top-bar `56px` mobile / `64px` desktop). Respect them; don't hard-code.
- A page is `src/app/[locale]/(private|public-routes)/…/page.tsx`. Keep page bodies to
  composition + content; push reusable visuals into `src/components/`.
- Constrain reading width with `CONTENT_MAX_WIDTH` (800) for text-heavy views; full-bleed
  is fine for dashboards/tables.
- Prefer MUI breakpoints in `sx` (`{ xs: …, md: … }`) for responsive branching over
  `useIsMobile()` (`@/hooks`) — the latter is JS/post-hydration and is what caused the
  desktop SSR flash `TopBarAndSideBarLayout` now avoids; reach for it only for genuinely
  non-structural, JS-only behavior.

---

## 6. Icons

Icons are a **name registry**, not free imports. `src/components/common/AppIcon/config.ts`
maps lowercase names → MUI/SVG components. Render with `<AppIcon icon="home" />` or pass
the name to `AppButton`/`AppIconButton` (`icon="search"`).

**One visual family: MUI `*Rounded`.** Every registered icon is the `Rounded` variant of
`@mui/icons-material` (warmer, softer strokes than the old filled/outlined mix — fits
"clinical-but-human"). When adding an icon, import the `*Rounded` version; don't mix in a
Filled/Outlined/Sharp/TwoTone icon next to it. ~90 names are registered today, spanning
navigation, catalog, verification, booking, payments, admin, and messaging — read
`AppIcon/config.ts` directly for the full list rather than duplicating it here (it drifts
too fast for a skill doc to track reliably); the two structural rules below don't.

**`size` actually resizes now.** `AppIcon` drives size via `style.fontSize` (the basis for
MUI SvgIcon's internal `1em` sizing) instead of `width`/`height` attributes, which MUI's
own CSS used to beat. `<AppIcon icon="verified" size={48} />` renders 48px — no more
silent 24px flattening.

**Directional icons mirror automatically.** Icons authored for LTR that must flip under
RTL (`back`, `chevron_start`) are registered in `AppIcon/config.ts`'s `DIRECTIONAL_ICONS`
set. `AppIcon` stamps `data-icon-directional` on those, and one CSS rule
(`app/globals.css`) does `[dir='rtl'] [data-icon-directional] { transform: scaleX(-1); }`.
Adding a new directional icon is a one-line registry addition — never hand-roll a
per-component flip.

**Need a new icon:** import the `*Rounded` version into `config.ts`, add a **lowercase**
key to `ICONS`, then reference by that name. Custom SVGs (the brand mark) go in
`AppIcon/icons/`. An unregistered name logs a dev-only warning and falls back to
`default` — never pass a raw MUI icon where a name is expected.

---

## 7. Non-negotiable rules for every Balinyaar UI

Every screen/component you produce must satisfy **all** of these:

1. **i18n — no hard-coded user-facing strings.** Add the key to **both**
   `messages/en.json` and `messages/fa.json` (keep them in sync; top-level keys are
   namespaces). Client: `useTranslations(ns)`. Server: `getTranslations(ns)`.
2. **RTL-safe.** `fa` is the default locale and is **RTL**. Never use directional
   hard-coding (`marginLeft`, `left:`, `textAlign: 'left'`) for layout flow — use
   logical/MUI-flipped props (`ml`→ MUI flips; prefer `marginInlineStart`, `start`/`end`,
   `sx` shorthand that the RTL Emotion cache mirrors). Test the layout visually at `/fa`.
   Do **not** pass `flexWrap`/`useFlexGap` as `Stack` props — use `sx={{ flexWrap: 'wrap' }}`.
3. **Dark-mode correct.** Pull every color from the palette or `--bal-*` tokens so it
   switches automatically. Verify on both schemes — never assume a light background.
4. **Tokens, not hexes.** No raw color literals in `sx`/`styled`/components (§2).
5. **Constants, not magic values.** Cookie names, routes, repeated dimensions, event
   names → named constants (CLAUDE.md "Constants").
6. **Use the wrappers** (§4) and the **icon registry** (§6) before bare MUI.
7. **Shared component ⇒ co-located test** (§4).
8. **MUI v9 API only.** No v5/v6-era props (e.g. `Stack` `useFlexGap`, `storageWindow`).
   Avoid deprecated APIs that throw.

---

## 8. Workflow: turning a design or feature into a screen

1. **Locate & scope.** Decide private vs public route; confirm the layout shell. Identify
   which existing `App*` components and tokens already cover the design.
2. **Tokens first.** If the design needs a color/spacing not in the system, add the
   `--bal-*` token (both schemes) + mirror in `colors.ts` if it's palette-level.
3. **Compose with primitives.** Build with `Box`/`Stack`/`Grid`/`Paper` + `App*`
   wrappers. Keep raw MUI to layout/structural components.
4. **Wire copy through i18n.** Every label/placeholder/aria string → both message files.
5. **Verify the four axes:** `/fa` (RTL) and `/en` (LTR) × light and dark. The default
   route is `/fa` — start there.
6. **Tests** for any new shared component; **never** add a layout above `[locale]`
   (breaks locale/dir — see CLAUDE.md).
7. Data/fetch/auth/cookies/toasts → follow CLAUDE.md (`serverFetch`/`clientFetch`,
   `@/lib/cookies/*`, `dispatchToast`/`useSnackbar`). Don't reinvent these.

---

## 9. Anti-patterns (design-specific — CLAUDE.md has the full engineering list)

- Hard-coded hex/rgb in components → use palette keys or `--bal-*` tokens.
- MUI default success/error colors for feedback → use `--bal-*` semantic tokens.
- `marginLeft`/`left`/`textAlign:'left'` for flow → breaks RTL; use logical props.
- Hard-coded English (or any) UI string → add to both message files.
- Loading a font in a component/page → fonts live only in `src/app/[locale]/layout.tsx`.
- `createTheme()` in a component → use `APP_THEME_LTR`/`APP_THEME_RTL`.
- Raw MUI icon where a registry name is expected → register it in `AppIcon/config.ts`.
- New shared component without a `.test.tsx`, or mocking MUI in tests.
- Re-introducing `src/app/layout.tsx` / any layout above `[locale]`.

---

## 10. Design ↔ Figma (optional)

A Figma MCP is connected. When the user provides a figma.com URL or asks to implement a
Figma frame, use the Figma tools (`get_design_context`, `get_screenshot`, `get_metadata`)
to pull the design, then map it onto **this** system: translate Figma colors to the
nearest `--bal-*`/palette token (don't introduce new hexes unless the design truly adds a
brand color), Figma type to `<Typography>` variants, and Figma components to the `App*`
library. Follow the Figma plugin skills (`/figma-use`, `/figma-generate-design`) when
pushing code back into Figma.

---

## Key files

| Concern | File |
|---------|------|
| Brand color tokens (CSS) | `client/src/theme/tokens.css` |
| MUI palette (mirror) | `client/src/theme/colors.ts` |
| Typography & fonts | `client/src/theme/typography.ts` |
| Theme factory (shape, schemes, dir) | `client/src/theme/theme.ts` |
| Theme provider / RTL cache | `client/src/theme/ThemeProvider.tsx` |
| Component library | `client/src/components/` (`@/components`) |
| Component defaults | `client/src/components/config.ts` |
| Icon registry | `client/src/components/common/AppIcon/config.ts` |
| Layout shells | `client/src/layout/` |
| Layout dimensions | `client/src/layout/config.ts` |
| Messages (i18n) | `client/messages/{en,fa}.json` |
| Engineering contract | `client/CLAUDE.md` |
