diff --git a/.claude/skills/frontend-designer/SKILL.md b/.claude/skills/frontend-designer/SKILL.md index 2c02d3a..2a20b12 100644 --- a/.claude/skills/frontend-designer/SKILL.md +++ b/.claude/skills/frontend-designer/SKILL.md @@ -28,10 +28,24 @@ Balinyaar is a **trust-first home-nursing marketplace in Iran**. The visual tone calm, warm, clinical-but-human — not a cold medical dashboard. Default audience is Persian (RTL); English is secondary. -**Logo mark** (`product/balinyaar.html` seed deck): deep-teal square, lowercase -display glyph in cream, a single terracotta dot. That trio — **teal ground, cream -text, terracotta accent** — is the whole identity. Use terracotta sparingly as the -single accent; teal carries everything else. +**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 `` + 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 `` — 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 | |------|-------|------| @@ -67,21 +81,54 @@ Colors exist in **two mirrored places** that must stay in sync. Pick the right o - 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. - Don't override per-component unless deliberate; prefer multiples that read as related. -- Buttons: `textTransform: 'none'`, weight 600 (set globally in `typography.ts`). Never +- `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. -- Headings (`h1`–`h6`) use the display font; `h6` is weight 600, the rest 700. +- 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`, system stack for `en` (Space Grotesk `--font-space-grotesk` - is declared but not yet wired). **Never load a font in a component or page.** + (`--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 `` for text — it inherits the correct direction-aware family - (`TYPOGRAPHY_RTL` = Mikhak everywhere for full Persian glyph coverage; `TYPOGRAPHY_LTR`). - Import neither directly in components; let the theme apply them. + (`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. --- @@ -141,13 +188,30 @@ Icons are a **name registry**, not free imports. `src/components/common/AppIcon/ maps lowercase names → MUI/SVG components. Render with `` or pass the name to `AppButton`/`AppIconButton` (`icon="search"`). -Currently registered: `default, logo, close, menu, settings, visibilityon, -visibilityoff, daynight, night, day, search, info, home, account, signup, login, -logout, notifications, error`. +**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. -**Need a new icon:** import it into `config.ts`, add a **lowercase** key to `ICONS`, then -reference by that name. Custom SVGs go in `AppIcon/icons/`. An unregistered name logs a -warning and falls back to `default` — never pass a raw MUI icon where a name is expected. +**`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. `` 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. --- diff --git a/client/CLAUDE.md b/client/CLAUDE.md index f04908b..9ed8951 100644 --- a/client/CLAUDE.md +++ b/client/CLAUDE.md @@ -319,13 +319,12 @@ client/ │ └── auth/ # AuthContext — AuthProvider (server-seeded) + reducer + useAuth ├── theme/ │ ├── ThemeProvider.tsx # MuiThemeProvider wrapper (RTL cache) + ColorSchemeCookieSync - │ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE - │ ├── light.ts / dark.ts # LIGHT_THEME / DARK_THEME ThemeOptions (consumed by theme.ts) + │ ├── colors.ts # BRAND, LIGHT_PALETTE, DARK_PALETTE (incl. success/error/warning/info) │ ├── direction.ts # getDirection(locale) → 'ltr' | 'rtl' - │ ├── theme.ts # APP_THEME_LTR / APP_THEME_RTL (static, created once) - │ ├── tokens.css # CSS custom properties — [data-mui-color-scheme] selectors - │ ├── typography.ts # TYPOGRAPHY_LTR (Space Grotesk) / TYPOGRAPHY_RTL (Mikhak) - │ └── index.ts # Public re-exports (ThemeProvider, getDirection, APP_THEME_*) — note: no ColorSchemeScript is exported/rendered today (doc drift below) + │ ├── theme.ts # APP_THEME_LTR / APP_THEME_RTL (static, created once) — the `components` brand pass + teal-tinted `shadows` array + responsiveFontSizes() + │ ├── tokens.css # CSS custom properties — [data-mui-color-scheme] selectors + the dark @media fallback (no-flash boot, no script — see "Theme System" below) + │ ├── typography.ts # TYPOGRAPHY_LTR (Space Grotesk) / TYPOGRAPHY_RTL (Mikhak) — shared size scale, 500/700 weight system + │ └── index.ts # Public re-exports (ThemeProvider, getDirection, APP_THEME_*) ├── constants/ # App-wide constants (routes.ts w/ actor paths, roles.ts, headers.ts) ├── hooks/ # incl. auth.ts → useIsAuthenticated / useActorRole (role-aware chrome) ├── utils/ # incl. money.ts (IRR/Toman, integer-safe) + date.ts (Shamsi display) + toEnglishDigits @@ -458,15 +457,42 @@ Rules: ## Theme System -### How it works (end-to-end, no-flash) +### How it works (end-to-end, no-flash — CSS only, no boot script) -1. **Request arrives** → `getThemeMode()` reads `'color-scheme'` cookie → returns `{ colorScheme, defaultMode }` -2. **Root layout** sets `data-mui-color-scheme={colorScheme}` on `` server-side -3. **``** in `` runs before any paint: - - Reads the same cookie, sets `data-mui-color-scheme` (handles edge cases where server attr might differ) - - Patches `Storage.prototype` — routes MUI's `localStorage` writes for key `'mode'` to our cookie; reads return `null` so MUI always trusts the `defaultMode` prop -4. **``** mounts — uses the server-derived mode, not localStorage -5. **`ColorSchemeCookieSync`** in ThemeProvider writes the cookie via `useColorScheme().colorScheme` on mount (safety net for first-visit system mode) +The no-flash mechanism is **pure CSS**, matching how every other color decision in this +app is made — no inline `