feat: redesign landing page with brand system and section blocks
Build & Push Docker Image / build (push) Has been cancelled

Refresh the homepage around new hero/services/roadmap sections, shared design tokens, and updated brand assets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ar.badiei
2026-07-23 17:00:55 +03:30
co-authored by Cursor
parent f0c290786e
commit c2376b3b9e
352 changed files with 34442 additions and 2760 deletions
+138
View File
@@ -0,0 +1,138 @@
# Amin Rayan Design System
Single source of truth for layout, components, tokens, motion, responsive behavior, accessibility, and coding conventions across the Amin Rayan (aminrayan) Next.js site.
This documentation does **not** redesign the product. It codifies what already exists in code so every future UI change stays visually and technically consistent.
---
## Purpose
- Prevent ad-hoc containers, colors, spacing, and typography.
- Make Shadcn / Tailark imports adapt to project tokens before shipping.
- Give developers and Cursor Agents one place to look before writing UI.
- Keep light/dark themes, RTL Persian readability, and accessibility as non-negotiable defaults.
---
## Source of truth (code)
| Concern | Canonical location |
| --- | --- |
| CSS tokens (color, radius, button metrics) | `app/globals.css` |
| Layout container & section rhythm | `lib/layout.ts`, `components/container.tsx` |
| Typography scale | `lib/typography.ts`, `components/ui/typography.tsx` |
| Button system | `components/ui/button.tsx` |
| Card system | `components/ui/card.tsx` |
| Scroll motion | `lib/block-motion.ts`, `components/motion-reveal.tsx` |
| Theme | `components/theme-provider.tsx` (`next-themes`, class strategy) |
| Cursor enforcement | `.cursor/rules/*.mdc` |
When docs and code disagree, **update the docs to match code** (or fix code via an intentional design change)—never invent a second system.
---
## Documentation hierarchy
```
docs/design-system/
├── README.md ← you are here (entry point)
├── layout.md ← page structure
├── grid.md
├── container.md
├── spacing.md
├── colors.md
├── typography.md
├── buttons.md
├── forms.md
├── cards.md
├── navbar.md
├── hero.md
├── sections.md
├── footer.md
├── icons.md
├── images.md
├── motion.md
├── gradients.md
├── glassmorphism.md
├── dark-mode.md
├── responsive.md
├── accessibility.md
├── shadcn.md
├── coding-rules.md
└── quality-checklist.md ← gate before every UI PR
```
---
## Priority order
When two guidelines conflict, follow this order:
1. **Accessibility** (`accessibility.md`) — contrast, focus, keyboard, reduced motion, touch targets.
2. **Layout & container** (`layout.md`, `container.md`, `grid.md`) — never invent a second content width.
3. **Semantic tokens** (`colors.md`, `spacing.md`, `typography.md`) — never hardcode hex or arbitrary spacing.
4. **Component systems** (`buttons.md`, `cards.md`, `forms.md`, …) — reuse existing primitives.
5. **Visual effects** (`gradients.md`, `glassmorphism.md`, `motion.md`) — decorative only; never break layout.
6. **Imported blocks** (`shadcn.md`) — adapt third-party UI to the above; never ship vendor styles as-is.
---
## How developers should use this
1. Before any UI task, open this README and the relevant topic docs.
2. Reuse `Container`, `layout.*`, `type.*` / `<Typography>`, `Button`, and `Card`.
3. Prefer semantic Tailwind classes (`bg-primary`, `text-muted-foreground`, `border-border`) over raw values.
4. For new sections, copy the section shell pattern from `sections.md`.
5. Run through `quality-checklist.md` before opening a PR.
### Quick imports
```tsx
import { Container } from "@/components/container";
import { layout } from "@/lib/layout";
import { type, typeSpacing } from "@/lib/typography";
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { cn } from "@/lib/utils";
```
---
## How Cursor should follow this
Cursor Rules under `.cursor/rules/` enforce the same standards automatically:
| Rule file | Scope |
| --- | --- |
| `layout-container.mdc` | Containers & section width |
| `frontend.mdc` | Tokens, reuse, dark mode, coding conventions |
| `ui-design.mdc` | Visual consistency, spacing, motion, components |
| `accessibility.mdc` | A11y non-negotiables |
| `shadcn.mdc` | Adapting imported Shadcn/Tailark blocks |
Agents must:
- Read these docs when building or changing UI.
- Never invent a second container, color palette, spacing scale, or button style.
- Always support light and dark themes via semantic tokens.
- Always preserve RTL-friendly typography and responsive behavior.
---
## Brand snapshot
| Token | Value | Role |
| --- | --- | --- |
| Primary | `#29a19c` | CTAs, focus ring, links |
| Primary dark / secondary | `#044343` | Deep brand panels, secondary surfaces |
| Neutral | `#222222` | Body text (light) |
| Gray | `#999999` | Muted accents |
| Light | `#e4e4e4` | Borders / muted fills (light) |
| Font | IRANSansX | Site-wide Persian UI |
---
## Related skills (optional)
Project skills under `.cursor/skills/` (design-system, ui-styling, brand) can assist generation, but **this folder wins** for Amin Rayan product UI.
+87
View File
@@ -0,0 +1,87 @@
# Accessibility
Accessibility is priority #1 in the design system hierarchy. Visual polish never overrides perceivable, operable UI.
## Color contrast
- Body text (`foreground` on `background`) and muted text must remain readable in both themes.
- Do not place `muted-foreground` on muted backgrounds without checking contrast.
- Primary buttons use white label on `#29a19c`—do not lower primary opacity on labels.
- Never convey meaning by color alone (pair with text/icon).
## Focus ring
Visible focus is mandatory for keyboard users.
| Control | Pattern |
| --- | --- |
| Button | `focus-visible:ring-[3px] focus-visible:ring-ring/40 focus-visible:border-ring` |
| Input | `focus-visible:ring-[3px] focus-visible:ring-primary/25 focus-visible:border-primary` |
| Generic | Prefer ring tokens; never `outline-none` without replacement |
Do not remove focus styles from cards/links that are interactive.
## Keyboard navigation
- All buttons, links, and controls reachable via Tab.
- Menus / sheets / dialogs use Radix primitives (focus trap + Escape).
- Maintain logical RTL tab order; avoid positive `tabIndex` except when necessary.
- Icon-only controls need accessible names.
## ARIA expectations
| Case | Requirement |
| --- | --- |
| Icon button | `aria-label` |
| Decorative icon/image | `aria-hidden` / empty `alt` |
| Expandable | `aria-expanded` + controls id |
| Dialogs / sheets | Titled (`SheetTitle`, `DialogTitle`) — `sr-only` if visually hidden |
| Invalid fields | `aria-invalid` + error text |
| Landmark nav | `aria-label` on primary `<nav>` |
| Decorative sections | `aria-hidden` on `.section-decor` |
Prefer native semantics (`button`, `a`, `label`) before ARIA.
## Reduced motion
- Honor `prefers-reduced-motion`.
- `MotionReveal` already disables motion.
- New CSS animations need a reduced-motion override.
- Do not autoplay motion-critical content.
## Touch targets
| Control | Minimum |
| --- | --- |
| Default button | 40×40 (`h-10`) |
| Large button | 48×48 |
| Icon button | 40×40 |
| Compact button | 32×32 — avoid for primary mobile actions |
| Input | ≥ 36px height (`h-9`); 48px when paired with lg submit |
## Forms
- Visible labels (not placeholder-only).
- Error text associated with fields.
- Preserve browser autofill / zoom-safe 16px text on mobile inputs.
## Images & media
- Meaningful `alt`.
- Do not flash high-contrast animations.
## Checklist snippet
- [ ] Keyboard only can complete the flow
- [ ] Focus visible on all interactive elements
- [ ] Contrast OK in light and dark
- [ ] Reduced motion OK
- [ ] SR labels on icon buttons
- [ ] No keyboard trap outside modals
## Related
- [forms.md](./forms.md)
- [motion.md](./motion.md)
- [dark-mode.md](./dark-mode.md)
- Cursor rule: `.cursor/rules/accessibility.mdc`
+114
View File
@@ -0,0 +1,114 @@
# Buttons
Every interactive CTA uses `@/components/ui/button`. Do not create parallel button styles, raw `<button className="bg-…">` marketing CTAs, or one-off rounded pills.
Canonical implementation: `components/ui/button.tsx` + CSS tokens in `app/globals.css`.
## Tokens
| CSS variable | Value | Role |
| --- | --- | --- |
| `--btn-radius` | `var(--radius)` → `0.625rem` | Corner radius |
| `--btn-height-sm` | `2rem` (32px) | Compact / cards |
| `--btn-height-md` | `2.5rem` (40px) | Default |
| `--btn-height-lg` | `3rem` (48px) | Hero / marketing |
| `--btn-px-sm` | `0.75rem` | Horizontal padding sm |
| `--btn-px-md` | `1.25rem` | Horizontal padding md |
| `--btn-px-lg` | `1.5rem` | Horizontal padding lg |
| `--btn-gap-sm/md/lg` | `0.375–0.5rem` | Icon ↔ label gap |
## Variants
| Variant | Prop | Visual | When to use |
| --- | --- | --- | --- |
| Primary | `default` | Solid `primary`, white label, soft shadow | Main conversion CTA |
| Secondary | `secondary` | Transparent + border | Supporting action |
| Outline | `outline` | Alias of secondary (Shadcn compat) | Imported blocks |
| Ghost | `ghost` | No border, accent wash on hover | Nav / tertiary |
| Destructive | `destructive` | Danger fill | Delete / irreversible |
| Link | `link` | Text link, underline on hover | Inline actions |
## Surfaces
| Surface | Prop | Use |
| --- | --- | --- |
| Default | `surface="default"` | Normal page canvas |
| Brand | `surface="brand"` | Teal / dark marketing panels (inverse contrast) |
On `brand` surfaces:
- Primary → white fill, dark text
- Secondary/outline → white border, white text
- Ghost → white text, white/10 hover
## Sizes
| Size | Prop | Height | Text | Icon size |
| --- | --- | --- | --- | --- |
| Small | `sm` | 32px | `text-sm` | 0.875rem |
| Medium | `default` | 40px | `text-sm` | 1rem |
| Large | `lg` | 48px | `text-base` | 1.25rem |
| Icon sm | `icon-sm` | 32×32 | — | 0.875rem |
| Icon | `icon` | 40×40 | — | 1rem |
| Icon lg | `icon-lg` | 48×48 | — | 1.25rem |
## States
| State | Behavior |
| --- | --- |
| Hover | Primary → `primary-hover` + `shadow-glow-primary`; secondary → border primary tint + accent fill + `shadow-glow-sm` |
| Active | `active:scale-[0.98]` + darker fill |
| Focus | `focus-visible:ring-[3px] ring-ring/40` + `border-ring` |
| Disabled | `opacity-50`, no pointer, no shadow |
| Invalid | Destructive ring/border via `aria-invalid` |
## Motion
- Transition: `duration-200 ease-out`
- Properties: color, background, border, box-shadow, opacity, transform
- Optional micro-lift on header CTA: `hover:-translate-y-px` (sparingly)
## Usage
```tsx
import { Button } from "@/components/ui/button";
import Link from "next/link";
import { ChevronLeft } from "lucide-react";
<Button size="lg">درخواست مشاوره</Button>
<Button variant="secondary" size="default">مشاهده نمونه کارها</Button>
<Button variant="ghost" size="icon" aria-label="باز کردن منو">
<Menu className="size-5" />
</Button>
<Button asChild size="lg">
<Link href="/contact-us">
ادامه
<ChevronLeft className="size-4" />
</Link>
</Button>
{/* On dark teal CTA band */}
<Button surface="brand" size="lg">شروع کنید</Button>
```
## Hierarchy in a section
1. At most **one** primary button per viewport section.
2. Secondary/outline for the alternate path.
3. Ghost for low-emphasis actions.
4. Do not place two equal primary buttons side by side.
## Forbidden
- Custom `rounded-full` CTA capsules that ignore `--btn-radius`.
- Hardcoded `bg-[#29a19c]` buttons.
- Re-implementing button styles in WordPress/HTML blocks without mapping to tokens (WP buttons are themed in `globals.css` to approximate primary).
## Related
- [colors.md](./colors.md)
- [motion.md](./motion.md)
- [forms.md](./forms.md)
+84
View File
@@ -0,0 +1,84 @@
# Cards
Canonical card: `@/components/ui/card`.
Use cards for **interactive or grouped content units** (settings panels, post previews, pricing cells). Do not wrap every section in a card. Do not put cards in the hero.
## Anatomy
```tsx
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
CardAction,
} from "@/components/ui/card";
<Card>
<CardHeader>
<CardTitle>عنوان</CardTitle>
<CardDescription>توضیح کوتاه</CardDescription>
<CardAction>{/* optional */}</CardAction>
</CardHeader>
<CardContent>…</CardContent>
<CardFooter>…</CardFooter>
</Card>
```
## Spec
| Property | Value |
| --- | --- |
| Background | `bg-card` |
| Text | `text-card-foreground` |
| Radius | `rounded-xl` (≈ `--radius-xl`) |
| Border | `border border-border` |
| Shadow | `shadow-glow-sm` → hover `shadow-glow-primary` (dark hover `shadow-glow-lg`) |
| Padding | Vertical `py-6`; header/content/footer `px-6` |
| Internal stack | `flex flex-col gap-6` |
| Title type | `type["card-title"]` |
| Description type | `type["card-desc"]` |
| Transition | `duration-200` on border + shadow |
## Hover
| Mode | Behavior |
| --- | --- |
| Light | `hover:border-primary/30 hover:shadow-glow-primary` |
| Dark | `dark:hover:border-primary/40 dark:hover:shadow-glow-lg` |
Hover is subtle—cards should not jump (`translate`) or scale aggressively. Timeline roadmap cards are a special case with their own CSS in `globals.css`.
## Dark mode
Cards use semantic `card` tokens (`#1c2424` in dark). Do not force `bg-white` on cards.
## Spacing with grids
```tsx
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{items.map((item) => (
<Card key={item.id}>…</Card>
))}
</div>
```
## Blog / media cards
Blog surfaces may use `rounded-blog` (`--radius-blog: 0.9375rem`) on images/figures. Prefer shared `Card` for structured panels; media-heavy tiles may compose `Card` + `Image` with `object-cover` and consistent aspect ratios ([images.md](./images.md)).
## Forbidden
- Nested cards inside cards for decoration.
- Heavy multi-layer drop shadows.
- Random `rounded-2xl` / `rounded-md` overrides without reason.
- Cards as the only way to create section background (use `bg-section-*` instead).
## Related
- [colors.md](./colors.md)
- [spacing.md](./spacing.md)
- [glassmorphism.md](./glassmorphism.md)
+93
View File
@@ -0,0 +1,93 @@
# Coding Rules
Conventions for UI code in this Next.js (App Router) + Tailwind v4 + Shadcn codebase.
## Stack assumptions
- React 19 + Next.js 16 App Router
- Tailwind CSS v4 (`@import "tailwindcss"` in `globals.css`)
- CVA + `cn()` (`clsx` + `tailwind-merge`)
- Radix-based Shadcn primitives in `components/ui/`
- Motion library: `motion/react`
- Themes: `next-themes` class strategy
- Locale: Persian RTL by default
## Always
1. Follow `docs/design-system/*` and `.cursor/rules/*`.
2. Reuse design tokens (CSS variables + Tailwind semantic colors).
3. Use `<Container>` / `layout.*` for page sections.
4. Use `type.*` or `<Typography>` for text styles.
5. Use shared `Button` and `Card` components.
6. Support light and dark themes without hardcoding surfaces.
7. Preserve responsive behavior (mobile first).
8. Preserve accessibility (focus, labels, reduced motion).
9. Prefer semantic HTML and Radix patterns already in the repo.
10. Keep components focused; extract when a pattern repeats twice+.
## Never
1. Create another content container / max-width system.
2. Hardcode colors (`#29a19c`, `rgb(…)`, `text-gray-700`) in UI.
3. Invent spacing outside the scale.
4. Duplicate Button/Card/Input styles.
5. Ship unadapted Shadcn/Tailark blocks.
6. Introduce inconsistent radii/shadows/hover timings.
7. Animate layout properties when transform suffices.
8. Commit secrets or environment files.
9. Add heavy dependencies for trivial UI.
10. Bypass ESLint without cause.
## File organization
| Kind | Location |
| --- | --- |
| Page routes | `app/**` |
| Marketing sections | `components/*.tsx` |
| Primitives | `components/ui/*` |
| Layout tokens | `lib/layout.ts` |
| Typography tokens | `lib/typography.ts` |
| Global CSS / theme | `app/globals.css` |
Name section files descriptively (`services-14.tsx`) or by domain (`footer.tsx`). Avoid `NewSection2.tsx`.
## Class composition
```tsx
import { cn } from "@/lib/utils";
className={cn(
"section-shell bg-section-canvas",
layout.sectionY,
className
)}
```
- Put conditional classes in `cn`.
- Do not string-concatenate Tailwind classes manually.
- Prefer extending variants via CVA for reusable primitives.
## Imports
Use `@/` alias. Keep import groups tidy: external → `@/components` → `@/lib` → local.
## Client vs server
- Default Server Components.
- Add `"use client"` only for hooks, browser APIs, or motion/theme interactivity.
- Keep presentational children server-friendly when possible.
## State & data
- Follow existing form patterns (`react-hook-form` + zod).
- Do not fetch in deeply nested presentational components if a parent already owns data.
## Comments
- Comment only non-obvious intent (animation thresholds, RTL quirks, token constraints).
- Do not narrate obvious JSX.
## Related
- [shadcn.md](./shadcn.md)
- [quality-checklist.md](./quality-checklist.md)
+79
View File
@@ -0,0 +1,79 @@
# Colors
Never hardcode hex/rgb in components. Use semantic CSS variables exposed as Tailwind theme colors.
Canonical definitions: `app/globals.css` (`:root` and `.dark`).
## Brand primitives
| CSS variable | Light value | Role |
| --- | --- | --- |
| `--brand-primary` | `#29a19c` | Brand teal |
| `--brand-primary-dark` | `#044343` | Deep teal |
| `--brand-neutral` | `#222222` | Near-black neutral |
| `--brand-gray` | `#999999` | Mid gray |
| `--brand-light` | `#e4e4e4` | Light gray |
| `--brand-primary-hover` | `#32b5af` | Primary hover |
| `--brand-primary-active` | `#218a86` | Primary active |
| `--brand-glow` | `rgb(41 161 156 / 0.12)` | Soft glow |
| `--brand-highlight` | mix of primary + white | Timeline / highlight accents |
Tailwind aliases: `brand`, `brand-dark`, `brand-neutral`, `brand-gray`, `brand-light`, `glow`.
## Semantic palette (prefer these in UI)
| Token | Tailwind | Light | Dark | Use |
| --- | --- | --- | --- | --- |
| Background | `bg-background` | `#ffffff` | `#141818` | Page canvas |
| Foreground | `text-foreground` | `#222222` | `#f4f4f4` | Primary text |
| Card | `bg-card` / `text-card-foreground` | white / `#222` | `#1c2424` / `#f4f4f4` | Cards, elevated surfaces |
| Surface | `bg-surface` / `text-surface-foreground` | `#f7f7f7` | `#1a2222` | Soft bands, inputs (dark) |
| Primary | `bg-primary` / `text-primary-foreground` | `#29a19c` / white | same | Main CTA |
| Primary hover | `bg-primary-hover` | `#32b5af` | same | Hover |
| Primary active | `bg-primary-active` | `#218a86` | same | Pressed |
| Secondary | `bg-secondary` / `text-secondary-foreground` | `#044343` / white | `#243535` / `#e4e4e4` | Deep brand / secondary panels |
| Muted | `bg-muted` / `text-muted-foreground` | `#e4e4e4` / `#5c5c5c` | `#243030` / `#999999` | Subtle fills, secondary text |
| Accent | `bg-accent` / `text-accent-foreground` | teal-tinted white / `#044343` | teal mix / `#e4e4e4` | Hover washes, chips |
| Destructive | `bg-destructive` / `text-destructive-foreground` | `#c44747` / white | `#e06a6a` / white | Errors / danger |
| Border | `border-border` | `#e4e4e4` | white @ 12% | Dividers, outlines |
| Input | `border-input` | `#e4e4e4` | white @ 14% | Form controls |
| Ring | `ring-ring` | `#29a19c` | `#29a19c` | Focus rings |
| Popover | `bg-popover` | white | `#1c2424` | Menus, dropdowns |
## Semantic intent map
| Intent | Tokens |
| --- | --- |
| Page background | `background` |
| Primary text | `foreground` |
| Secondary text | `muted-foreground` |
| Interactive primary | `primary` → `primary-hover` → `primary-active` |
| Borders | `border` |
| Focus | `ring` + `border-ring` / `border-primary` |
| Disabled | lower opacity on control (`opacity-50`) — do not invent a gray hex |
| Danger | `destructive` |
| Charts | `chart-1` … `chart-5` |
## Hover / focus / disabled
| State | Pattern |
| --- | --- |
| Hover (primary button) | `hover:bg-primary-hover` |
| Active (primary button) | `active:bg-primary-active` + slight `scale-[0.98]` |
| Focus visible | `focus-visible:ring-[3px] focus-visible:ring-ring/40` (buttons) or `ring-primary/25` (inputs) |
| Disabled | `disabled:opacity-50 disabled:pointer-events-none` |
| Invalid | `aria-invalid:border-destructive` + destructive ring |
## Rules
1. **Never** use raw `#29a19c`, `#044343`, etc. in JSX/className. Use `primary`, `brand`, or CSS variables.
2. Prefer semantic names (`primary`, `muted-foreground`) over brand primitives in app UI.
3. Dark mode must work by swapping CSS variables—not by separate hard-coded dark hex in components.
4. Selection color is already themed in `globals.css` (`::selection`).
5. Glow utilities: `bg-glow-primary`, `shadow-glow-primary` — see [gradients.md](./gradients.md).
## Related
- [dark-mode.md](./dark-mode.md)
- [buttons.md](./buttons.md)
- [accessibility.md](./accessibility.md)
+64
View File
@@ -0,0 +1,64 @@
# Container
The site has **one** content container. All sections, imported blocks, and future features must use it.
## API
```tsx
import { Container } from "@/components/container";
import { layout, containerClassName } from "@/lib/layout";
```
| Export | What it does |
| --- | --- |
| `<Container>` | Renders `div` with `layout.container` |
| `layout.container` | Class string for rare cases without the component |
| `containerClassName(extra?)` | Merges extras via `cn` |
## Spec
| Property | Value |
| --- | --- |
| Display | Block, full width of parent |
| Max width | `76.8rem` (1228.8px) — 20% wider than former `max-w-5xl` |
| Horizontal padding | `1.5rem` (`px-6`) at all breakpoints |
| Centering | `mx-auto` |
| Vertical padding | **None** — vertical rhythm belongs on the `<section>` |
## Full-bleed vs constrained
```tsx
{/* Background spans viewport; content stays aligned */}
<section className={cn("section-shell bg-gradient-cta", layout.sectionY)}>
<Container className="section-content">
…
</Container>
</section>
```
Rules:
- Backgrounds, borders, and decorative washes may be full viewport width.
- Text, forms, card grids, and CTAs stay inside `<Container>`.
- Edge-to-edge imagery (hero photos, marquees) may break out **only** when the design intentionally requires it; any overlaid copy still uses `Container`.
## Replacing imported containers
When adapting Shadcn / Tailark / marketing blocks:
1. Delete their `container`, `mx-auto max-w-*`, and conflicting `px-*`.
2. Wrap content with `<Container>` (or apply `layout.container`).
3. Move vertical padding to `layout.sectionY` on the outer `<section>`.
4. Keep inner `max-w-*` only on prose columns.
## Do not
- Create `ContainerSm`, `ContainerWide`, or page-specific max-widths.
- Use `px-4` / `px-8` as section gutters (always `px-6` via the shared token).
- Apply `layout.container` twice (nested containers).
## Related
- [layout.md](./layout.md)
- [shadcn.md](./shadcn.md)
- Cursor rule: `.cursor/rules/layout-container.mdc`
+52
View File
@@ -0,0 +1,52 @@
# Dark Mode
Dark mode is a first-class theme, not an invert filter. It is a deep teal-neutral palette defined under `.dark` in `app/globals.css`.
## Mechanism
| Piece | Detail |
| --- | --- |
| Strategy | `class` on `html` via `next-themes` |
| Provider | `components/theme-provider.tsx` |
| Default | `light` |
| System | `enableSystem` |
| Variant | `@custom-variant dark (&:is(.dark *));` |
| Toggle | `components/theme-toggle.tsx` |
## Palette intent
| Role | Light | Dark |
| --- | --- | --- |
| Canvas | `#ffffff` | `#141818` |
| Card / popover | `#ffffff` | `#1c2424` |
| Surface | `#f7f7f7` | `#1a2222` |
| Primary | `#29a19c` (unchanged) | `#29a19c` |
| Secondary | `#044343` | `#243535` |
| Borders | `#e4e4e4` | white @ ~12% alpha |
| Muted text | `#5c5c5c` | `#999999` |
Primary brand teal stays constant for recognition; surrounding neutrals shift.
## Implementation rules
1. Style with semantic tokens only (`bg-background`, `text-muted-foreground`, `border-border`, …).
2. Use `dark:` variants only when semantic tokens are insufficient (e.g. `dark:hover:shadow-glow-primary`, header glass tweaks).
3. Never gate entire components behind duplicated light/dark markup trees unless media differs (`next/image` theme backgrounds).
4. Test both themes before shipping UI.
5. Charts and decorative strokes should use `chart-*` or primary mixes that remain visible on dark surfaces.
## Transitions
Body background/color transitions use `0.3s ease`. Do not disable theme transitions globally (`disableTransitionOnChange={false}`).
## Forbidden
- `bg-white text-black` hardcodes.
- `dark:bg-black` overrides that fight the teal-neutral system.
- Assuming pure inversion of light colors will look correct.
## Related
- [colors.md](./colors.md)
- [accessibility.md](./accessibility.md)
- [gradients.md](./gradients.md)
+58
View File
@@ -0,0 +1,58 @@
# Footer
Canonical footer: `components/footer.tsx` (`FooterSection`).
## Structure
1. **Newsletter band** — headline + short copy + email form.
2. **Link columns** — site maps (pages, services, contact, legal).
3. **Brand / social / legal meta** — logo mark, social icon buttons, copyright.
## Layout
```tsx
<footer className={cn("section-shell bg-section-muted", layout.sectionY)}>
<div className="section-decor" aria-hidden>…</div>
<Container className="section-content">
{/* newsletter */}
{/* link grid */}
{/* bottom bar */}
</Container>
</footer>
```
| Region | Grid / spacing |
| --- | --- |
| Newsletter | `grid-cols-1 lg:grid-cols-2 gap-10`, bottom border, `mb-14 pb-14` |
| Links | `grid-cols-2 sm:grid-cols-4 gap-x-6 gap-y-10`, `py-10 lg:py-16` |
| Column titles | `type.h4` + `mb-5` |
| Links | `type.link` + `space-y-4` |
## Form
- `Input` height aligned to `Button size="lg"` via `h-[var(--btn-height-lg)]`.
- Stack vertically on mobile (`flex-col`), horizontal from `sm`.
## Social icons
- Use `Button variant="ghost" size="icon"` with accessible `aria-label`.
- Icons from `lucide-react`.
## Visual
- Background: `bg-section-muted` (not a hard gray box).
- Optional top wash gradient in `.section-decor` using primary at ≤ 4% opacity.
- Borders: semantic `border-border`.
## Rules
1. Stay on the shared `Container`—footer columns must align with header/content.
2. Do not introduce a second footer component for marketing pages.
3. Keep link lists scannable; avoid paragraph walls.
4. Dark mode comes from tokens automatically.
## Related
- [sections.md](./sections.md)
- [forms.md](./forms.md)
- [icons.md](./icons.md)
+83
View File
@@ -0,0 +1,83 @@
# Forms
Form UI must use shared primitives so controls match buttons, cards, and focus rings.
## Building blocks
| Piece | Import |
| --- | --- |
| Input | `@/components/ui/input` |
| Textarea | `@/components/ui/textarea` |
| Label | `@/components/ui/label` |
| Checkbox / Radio / Switch / Select | `@/components/ui/*` |
| Field layout | `@/components/ui/field` (`Field`, `FieldGroup`, `FieldSet`, …) |
| Form (RHF) | `@/components/ui/form` + `react-hook-form` + zod resolvers |
| Button submit | `@/components/ui/button` |
## Input spec
From `components/ui/input.tsx`:
| Property | Value |
| --- | --- |
| Height | `h-9` (36px) default; align to buttons with `h-[var(--btn-height-lg)]` when paired with `size="lg"` submit |
| Radius | `rounded-md` (`--radius-md`) |
| Border | `border-input` |
| Background | `bg-background`; dark: `dark:bg-surface/60` |
| Text | `text-base md:text-sm` (16px on mobile avoids iOS zoom) |
| Padding | `px-3 py-1` |
| Shadow | `shadow-xs` |
| Transition | `duration-200` on color/border/shadow |
| Focus | `border-primary` + `ring-[3px] ring-primary/25` |
| Disabled | `opacity-50`, no pointer |
| Invalid | `aria-invalid` → destructive border/ring |
## Field layout
- Vertical (default): label above control, `gap-3` within field; groups use `gap-7`.
- Label → control spacing: prefer `typeSpacing.labelToControl` (`gap-1.5`) or Field defaults.
- Checkbox/radio groups: tighter `gap-3`.
- Errors: use `text-destructive` and wire `aria-invalid` on the control.
```tsx
import { Field, FieldLabel, FieldDescription, FieldError } from "@/components/ui/field";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
<Field>
<FieldLabel htmlFor="email">ایمیل</FieldLabel>
<Input id="email" type="email" autoComplete="email" />
<FieldDescription>برای پیگیری درخواست با شما تماس می‌گیریم.</FieldDescription>
<FieldError>ایمیل معتبر نیست.</FieldError>
</Field>
<div className="flex flex-col gap-3 sm:flex-row">
<Input type="email" className="h-[var(--btn-height-lg)]" placeholder="آدرس ایمیل شما" />
<Button type="submit" size="lg">عضویت</Button>
</div>
```
## Validation & a11y
1. Associate every control with a visible `<Label>` / `FieldLabel` via `htmlFor` + `id`.
2. Announce errors with `aria-invalid` and descriptive error text (not color alone).
3. Required fields: use `aria-required` or native `required` plus clear copy.
4. Keep touch targets ≥ 36px height (inputs `h-9`; prefer `lg` button pairing for marketing forms).
5. Do not disable paste or obscure password managers.
## Newsletter / compact patterns
Footer pattern: email input height matches `Button size="lg"` via `--btn-height-lg`.
## Forbidden
- Unstyled native inputs next to themed buttons.
- `outline-none` without a visible focus replacement.
- Hardcoded border colors (`border-gray-300`).
- Mixing different input heights in the same form row without intent.
## Related
- [buttons.md](./buttons.md)
- [accessibility.md](./accessibility.md)
- [colors.md](./colors.md)
+78
View File
@@ -0,0 +1,78 @@
# Glassmorphism
Glass is used sparingly for **floating chrome** (header, menus, sheets, blog tables)—not as a general card style.
## When to use
| Allowed | Not allowed |
| --- | --- |
| Scrolled header shell | Every content card |
| Nav dropdown / mobile sheet | Full page backgrounds |
| Blog table header treatments | Text-heavy panels that hurt readability |
## Canonical recipe (header scrolled)
```txt
border-border/50
bg-background/75
supports-[backdrop-filter]:bg-background/65
backdrop-blur-xl
backdrop-saturate-150
shadow-sm
rounded-2xl
```
Mobile sheet:
```txt
bg-background/95
backdrop-blur-xl
backdrop-saturate-150
shadow-xl
border-border/50
```
Dropdown:
```txt
bg-popover/95
backdrop-blur-xl
shadow-md
border-border/50
rounded-xl
```
## Spec ranges
| Property | Range |
| --- | --- |
| Blur | `backdrop-blur-md` (12px) to `backdrop-blur-xl` (24px) |
| Fill opacity | 65%–95% of `background` / `popover` |
| Border | `border-border/40`–`/50` or translucent mixes |
| Saturation | `backdrop-saturate-150` for chrome |
## Readability rules
1. Text on glass must still meet contrast—prefer `foreground` / `muted-foreground`, not low-alpha white text on light glass.
2. Provide opaque fallbacks when `backdrop-filter` is unsupported (`supports-[backdrop-filter]:…` pattern).
3. Do not place long paragraphs on heavy blur without a solid scrim.
## Dark mode
Glass uses the same semantic tokens; dark backgrounds already sit on `#141818` / `#1c2424`. Do not switch to pure `bg-white/10` hacks unless matching an existing pattern (timeline idle cards are a special dark UI).
## Motion
Blur/background changes on the header animate with `duration-300 ease-out`. Avoid animating blur on scroll for large content regions (expensive).
## Forbidden
- Frosted cards stacked across an entire landing page.
- Extra noise textures + glass + glow simultaneously.
- Glass without border or shadow that disappears on light backgrounds.
## Related
- [navbar.md](./navbar.md)
- [gradients.md](./gradients.md)
- [cards.md](./cards.md)
+67
View File
@@ -0,0 +1,67 @@
# Gradients
Gradients create continuous atmosphere across the page. They must feel like one canvas—not disconnected colorful patches.
Canonical utilities live in `app/globals.css` `@layer utilities`.
## Allowed utilities
| Class | Role |
| --- | --- |
| `bg-section-canvas` | Default section continuity (very soft primary tint) |
| `bg-section-muted` | Alternate muted band |
| `bg-gradient-hero` | Hero radial + vertical wash |
| `bg-gradient-featured` | Featured band (corner radials + wash) |
| `bg-gradient-cta` | Strong 135° brand CTA panel (`brand-primary-dark` → `brand-primary`) |
| `bg-glow-primary` | Soft radial glow using `--brand-glow` |
| `shadow-glow-sm` | Neon rim for primary buttons / resting cards |
| `shadow-glow-primary` | Mid neon for card hover / button hover |
| `shadow-glow-lg` | Stronger neon (dark hover / media hover) |
| `shadow-glow-media` | Neon + depth for image frames |
| `media-frame` | Shared image wrapper: surface border + media neon |
## Opacity limits
| Effect | Light | Dark |
| --- | --- | --- |
| Section tint washes | ~3–6% primary mixed into background | ~4–6% |
| Hero radial glow | ~0.12 alpha | ~0.10 alpha |
| Featured radials | ~0.08–0.10 | ~0.08 + dark brand mix |
| Footer top wash | ~0.03–0.04 | ~0.03 |
| Brand glow token | `0.12` | `0.10` |
If a glow is obvious as a “blob,” it is too strong.
## Layer ordering
Inside a section:
1. Section background utility (`bg-section-*` / gradient).
2. `.section-decor` absolute layer (glows, grids).
3. `.section-content` real UI.
Never place glows above content (`z-index`) or with pointer events.
## Glow usage
- Spotlights behind headings or cards: `bg-glow-primary` on a blurred circle in `.section-decor`.
- Interactive emphasis: `shadow-glow-sm` → `shadow-glow-primary` / `shadow-glow-lg` on buttons and cards.
- Image frames: prefer `media-frame` (or `shadow-glow-media`) over plain `shadow-surface-*`.
- Timeline active nodes may use highlight mixes—keep confined to that component.
## CTA panels
Use `bg-gradient-cta` for conversion bands. Pair with `Button surface="brand"` for correct contrast.
## Forbidden
- One-off `bg-gradient-to-r from-purple-500 to-pink-500` (or any non-brand palette).
- Hard rectangular gradient blocks that do not blend into neighbors.
- Multiple competing radials in one small card.
- Fixed-position gradient overlays spanning the whole site.
## Related
- [sections.md](./sections.md)
- [glassmorphism.md](./glassmorphism.md)
- [dark-mode.md](./dark-mode.md)
+89
View File
@@ -0,0 +1,89 @@
# Grid
Grid rules for marketing sections, card layouts, and footers. Grids always live **inside** `<Container>`.
## Defaults
| Concern | Standard |
| --- | --- |
| Framework | Tailwind CSS Grid / Flexbox |
| Gap scale | Spacing tokens only (`gap-4`, `gap-6`, `gap-8`, `gap-10` …) — see [spacing.md](./spacing.md) |
| Columns | Mobile-first: 1 → 2 → 3/4 as viewport grows |
| Alignment | Start-aligned in RTL; use logical properties (`ms-`, `me-`, `ps-`, `pe-`) when offsetting |
## Common patterns
### Two-column feature / split
```tsx
<div className="grid grid-cols-1 gap-10 lg:grid-cols-2 lg:gap-16">
<div>…copy…</div>
<div>…media…</div>
</div>
```
### Card grid (services / posts)
```tsx
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{items.map(…)}
</div>
```
### Footer link columns
```tsx
<nav className="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-4">
…
</nav>
```
### Newsletter / CTA split
```tsx
<div className="grid grid-cols-1 gap-10 lg:grid-cols-2">
…
</div>
```
## Breakpoint guidance
| Viewport | Typical columns |
| --- | --- |
| &lt; 640px (`default`) | 1 |
| `sm` ≥ 640px | 2 for dense cards |
| `md` ≥ 768px | 2–3 |
| `lg` ≥ 1024px | 2–4 marketing grids |
| `xl` ≥ 1280px | Same as `lg`; prefer more whitespace over more columns |
Exact breakpoints: [responsive.md](./responsive.md).
## Gap rules
Prefer these gaps:
| Context | Gap |
| --- | --- |
| Dense UI (forms, chips) | `gap-2` / `gap-3` (8 / 12) |
| Card grids | `gap-6` (24) |
| Section split layouts | `gap-10` / `gap-16` (40 / 64) |
| Footer columns | `gap-x-6 gap-y-10` |
Do not use arbitrary gaps (`gap-[13px]`, `gap-[1.1rem]`).
## Alignment & RTL
- Site is Persian / RTL. Prefer `start` / `end` over `left` / `right`.
- For asymmetric hero layouts, mirror with logical classes (`lg:ms-auto`, not `lg:ml-auto` unless intentional LTR islands).
## Forbidden
- CSS Grid that sets `width` wider than the container without clipping.
- Mixing Bootstrap / custom 12-column frameworks.
- Absolute-positioned “fake grids” for primary content.
## Related
- [layout.md](./layout.md)
- [spacing.md](./spacing.md)
- [cards.md](./cards.md)
+85
View File
@@ -0,0 +1,85 @@
# Hero
Hero guidelines for marketing landings. Reference implementations: `components/hero-section.tsx`, `hero-50.tsx`, `hero-213.tsx`.
This design system does **not** invent a new hero—it constrains future heroes to the same layout, token, and motion language.
## First-viewport budget
Prefer:
1. Brand / logo presence (via global header).
2. One headline (`type.display` or `type.h1`).
3. One short supporting sentence.
4. One CTA group (primary + optional secondary).
5. One dominant visual (background or product image).
Avoid packing stats, schedules, address blocks, or promo chips into the first viewport.
## Layout
```tsx
<section className="bg-background"> {/* or bg-gradient-hero */}
<div className="relative pt-24 md:pt-36">
<Container>
<div className={typeSpacing.heroStack}>
<h1 className={type.display}>…</h1>
<p className={cn(type["body-lg"], "text-muted-foreground")}>…</p>
<div className="flex flex-wrap items-center gap-3">
<Button size="lg">…</Button>
<Button size="lg" variant="secondary">…</Button>
</div>
</div>
</Container>
</div>
</section>
```
Rules:
- Clearance under fixed header: at least `pt-24` / `md:pt-36` (or `layout.sectionTopWithHeader` on simpler pages).
- Content width: site `<Container>` — do not invent `max-w-7xl` shells for new work (legacy heroes being migrated should move to `Container`).
- Full-bleed **background** images are encouraged; foreground copy stays constrained.
- No detached floating badges/stickers on top of hero media.
## Backgrounds
Allowed:
- `bg-gradient-hero` utility.
- Theme-aware `next/image` backgrounds (`dark:block` / `hidden dark:…`).
- Soft radial washes inside a clipped absolute layer.
See [gradients.md](./gradients.md). Decorative layers must not cause horizontal scroll.
## Motion
- Entrance: opacity + translateY (+ optional blur) via `AnimatedGroup` / `TextEffect` / `MotionReveal`.
- Prefer spring or `blockEase` from `lib/block-motion.ts`.
- Respect `prefers-reduced-motion` (MotionReveal already does).
- Do not animate layout width/height for entrance—use transform/opacity.
## CTA rules
- Primary = `Button` default `size="lg"`.
- Secondary = `variant="secondary"` or `outline`.
- On dark brand panels use `surface="brand"`.
## Imagery
- Provide explicit `width` / `height` or `fill` + sized parent to avoid CLS.
- Use `sizes` appropriate to viewport (`100vw` for full-bleed backgrounds).
- `object-cover` for atmospheric photos; keep focal point sensible for RTL crops.
## Forbidden
- Card-wrapped heroes.
- Inset rounded media as the only hero treatment when a full-bleed treatment is the site norm.
- Extra containers that misalign with the header.
## Related
- [navbar.md](./navbar.md)
- [buttons.md](./buttons.md)
- [images.md](./images.md)
- [motion.md](./motion.md)
+55
View File
@@ -0,0 +1,55 @@
# Icons
## Libraries
| Library | Use |
| --- | --- |
| `lucide-react` | Default UI icons (nav, actions, social) |
| `@iconify/react` | Occasional extended sets when Lucide lacks a glyph |
Do not mix many icon libraries in one view. Prefer Lucide for consistency.
## Sizing
| Context | Size |
| --- | --- |
| Inline with `text-sm` | `size-4` (16px) |
| Buttons (default) | Controlled by `--btn-icon-size` (0.875–1.25rem) |
| Icon buttons | Match button box; glyph ~20px (`size-5`) for menu |
| Cards / features | `size-5` / `size-6` |
| Decorative empty states | `size-8`–`size-10` max |
Use Tailwind `size-*` from the spacing scale. Avoid arbitrary `w-[18px]`.
## Color
- Inherit from parent text (`currentColor`) whenever possible.
- Semantic: `text-muted-foreground`, `text-primary`, `text-destructive`.
- Never hardcode icon hex fills in JSX.
## Alignment
- Pair icons with labels using flex + `gap-2` / button gaps.
- Decorative icons: `aria-hidden`.
- Standalone icon buttons: mandatory `aria-label` (or visible text).
```tsx
<Button variant="ghost" size="icon" aria-label="باز کردن منو">
<Menu aria-hidden className="size-5" />
</Button>
```
## Stroke
Lucide defaults are fine. Do not globally remount icons with custom stroke widths that fight the set.
## Forbidden
- Emoji as UI icons.
- PNG icons for simple glyphs.
- Different visual weights in the same toolbar.
## Related
- [buttons.md](./buttons.md)
- [accessibility.md](./accessibility.md)
+71
View File
@@ -0,0 +1,71 @@
# Images
## Principles
1. Prefer `next/image` for all meaningful raster images.
2. Reserve space (width/height or sized `fill` parent) to prevent layout shift.
3. Lazy-load below-the-fold images; prioritize true LCP hero assets.
4. Stay within brand radius tokens.
## Aspect ratios
| Use | Ratio | Notes |
| --- | --- | --- |
| Hero atmosphere | fluid / full bleed | `object-cover`, tall crops OK |
| Blog / content figures | intrinsic | `w-full h-auto` in `.blog-content` |
| Cards / thumbnails | `aspect-video` or `aspect-[4/3]` | Keep consistent within a grid |
| Avatars | `1:1` | `rounded-full` |
| Logos (marquee) | intrinsic height | Use `LogoItem` patterns |
Use `AspectRatio` from `@/components/ui/aspect-ratio` when the box must stay stable before load.
## Object-fit
| Value | When |
| --- | --- |
| `object-cover` | Photos filling a fixed frame |
| `object-contain` | Logos, diagrams that must not crop |
| `object-center` / logical positions | Adjust focal point; prefer `object-center` unless art-directed |
## Radius
| Token | Class | Use |
| --- | --- | --- |
| Default | `rounded-md` / `rounded-xl` | UI media |
| Blog | `rounded-blog` | WP images / blockquotes |
| Pill | `rounded-full` | Avatars only |
Marketing image frames use `media-frame` (surface border + brand neon via `--elevation-glow-media`). Prefer that over ad-hoc `border` + `shadow-surface-*` on homepage media.
`.wp-block-image` already applies `rounded-blog overflow-hidden shadow-sm`.
## Lazy loading & placeholders
- Default `next/image` lazy loading for non-priority images.
- Set `priority` only on the primary LCP image.
- Always pass meaningful `sizes` (e.g. `100vw`, `(max-width: 768px) 100vw, 33vw`).
- Blur placeholder: use when a `blurDataURL` exists; otherwise rely on solid surface behind the image.
## Alt text
- Informative images: concise Persian (or English in `/en`) alt describing content.
- Decorative images: `alt=""` and/or `aria-hidden` on wrapper.
- Never put SEO keyword stuffing in alt.
## Floating / decorative images
- Absolute decorative photos must live inside a clipped section (`section-shell` / `overflow-hidden`).
- Do not float images that overlap interactive controls.
- Avoid random rotated polaroid stacks unless an existing block already defines that pattern.
## Forbidden
- Unsized `<img>` causing CLS.
- Inconsistent radii in the same card grid.
- Full-quality 4K backgrounds without `sizes` / quality tuning.
## Related
- [hero.md](./hero.md)
- [cards.md](./cards.md)
- [responsive.md](./responsive.md)
+95
View File
@@ -0,0 +1,95 @@
# Layout
Page structure rules for Amin Rayan. Every marketing page, blog surface, and product block must share the same content column, gutters, and vertical rhythm.
## Principles
1. One content width for the whole site.
2. Full-bleed backgrounds are allowed; content is never full-bleed unless intentionally edge-to-edge media.
3. Section vertical rhythm comes from `layout.sectionY`—not one-off `py-*` values.
4. Decorative layers never participate in document flow or cause horizontal overflow.
5. The fixed header defines safe top offsets and scroll margins.
## Canonical tokens
Defined in `lib/layout.ts`:
| Token | Class / value | Use |
| --- | --- | --- |
| `layout.gutter` | `px-6` (24px) | Horizontal padding |
| `layout.maxWidth` | `max-w-[76.8rem]` (1228.8px) | Content cap |
| `layout.container` | `mx-auto w-full max-w-[76.8rem] px-6` | Standard shell |
| `layout.sectionY` | `py-16 md:py-20` | Section vertical padding (adjacent gap = 8rem / md:10rem) |
| `layout.sectionTopWithHeader` | `pt-28 pb-16 md:pt-32 md:pb-20` | First section under fixed header |
| `layout.scrollMargin` | `scroll-mt-24` | In-page anchors |
Component wrapper: `<Container>` from `@/components/container`.
## Section anatomy
```tsx
<section className={cn("section-shell bg-section-canvas", layout.sectionY)}>
<div className="section-decor" aria-hidden>
{/* optional absolute glow / wash — never in flow */}
</div>
<Container className="section-content">
{/* real content */}
</Container>
</section>
```
Utility classes (from `app/globals.css`):
| Class | Role |
| --- | --- |
| `.section-shell` | `relative isolation-isolate overflow-x-clip` |
| `.section-decor` | Absolute decorative layer, `pointer-events-none`, `z-0` |
| `.section-content` | Content above decor, `z-1` |
## Alignment rules
- Left and right edges of text/CTA columns must align with header content and footer content (same `Container`).
- Do not nest another `max-w-*` shell around an entire section. Inner measures (`max-w-xl`, `max-w-2xl`, `max-w-3xl`) are allowed only on **text blocks** for readability.
- Grids live **inside** `Container`. Never place a grid that escapes the container unless it is intentionally full-bleed media.
## Safe areas
- Header uses `pt-[env(safe-area-inset-top,0px)]` for notched devices.
- First content under the fixed header: use `layout.sectionTopWithHeader` or explicit `pt-24 md:pt-36` (hero) so titles are not obscured.
- Anchor targets (`id="…"`) must include `layout.scrollMargin` (`scroll-mt-24`).
## Overflow rules
- `html` and `body` use `overflow-x: clip`.
- Sections use `section-shell` / `overflow-x: clip` so glows cannot create horizontal scroll.
- Never use `position: fixed` decorative blobs for section atmosphere—keep them inside `.section-decor`.
- Prefer `transform` / absolute positioning for decorations; do not widen the document with large negative margins without clipping.
## Forbidden patterns
```tsx
// ❌ Second container width
<div className="mx-auto max-w-5xl px-4">…
// ❌ Section without shared rhythm
<section className="py-12 px-8">…
// ❌ Full-bleed text without Container
<section className="w-full px-4">
<h2>…</h2>
</section>
// ✅ Correct
<section className={cn("section-shell bg-section-muted", layout.sectionY)}>
<Container>
<h2 className={type.h2}>…</h2>
</Container>
</section>
```
## Related docs
- [container.md](./container.md)
- [grid.md](./grid.md)
- [sections.md](./sections.md)
- [responsive.md](./responsive.md)
+85
View File
@@ -0,0 +1,85 @@
# Motion
Motion should clarify hierarchy and presence—not decorate every pixel.
Canonical scroll motion: `lib/block-motion.ts` + `components/motion-reveal.tsx`.
Header / micro-interactions use CSS transitions documented below.
## Duration scale
| ms | Tailwind / CSS | Use |
| --- | --- | --- |
| 150 | `duration-150` | Micro feedback (rarely needed) |
| 200 | `duration-200` | Buttons, inputs, cards, links (default UI) |
| 250 | `duration-250` | Base heading/paragraph color transitions |
| 300 | `duration-300` | Header chrome, nav, sheets, larger reveals |
| 500 | `duration-500` | Timeline / emphasis state changes, section reveals |
Do not invent `duration-[350ms]` or multi-second loops for marketing UI.
## Easing
| Token | Value | Use |
| --- | --- | --- |
| UI default | `ease-out` | Hovers, chrome |
| Block reveal | `blockEase = [0.22, 1, 0.36, 1]` | Scroll entrances |
| Spring (hero) | `type: "spring", bounce: ~0.3` | Occasional hero text only |
## Hover transitions
Prefer transitioning:
- `color`, `background-color`, `border-color`
- `box-shadow`, `opacity`
- `transform` (`translate`, `scale`)
**Never animate layout** (`top`/`left`/`width`/`height`/`margin`) when `transform` or opacity can achieve the effect.
Buttons already include `active:scale-[0.98]` and `duration-200`.
## Scroll animations
```tsx
import { MotionReveal, MotionRevealItem } from "@/components/motion-reveal";
<MotionReveal stagger>
<MotionRevealItem>…</MotionRevealItem>
<MotionRevealItem>…</MotionRevealItem>
</MotionReveal>
```
Defaults from `block-motion.ts`:
| Option | Value |
| --- | --- |
| Duration | 0.5s |
| Offset | `y: 24` → `0` |
| Stagger | 0.08s children |
| Viewport | `once: true`, `amount: 0.2`, bottom margin `-10%` |
Keep stagger counts small (≤ ~8).
## Reduced motion
- `MotionReveal` short-circuits to static markup when `useReducedMotion()` is true.
- Timeline and header animations disable under `@media (prefers-reduced-motion: reduce)` in `globals.css`.
- New animations **must** honor reduced motion.
## Performance rules
1. Prefer `transform` + `opacity`.
2. Avoid animating `blur` on large surfaces in loops (hero entrance blur is OK once).
3. Do not use `will-change` everywhere—only hot paths (e.g. timeline progress).
4. No infinite attention-grabbing pulses on primary CTAs.
## Forbidden
- Layout thrashing animations.
- Autoplaying large Lottie stacks on every section.
- Ignoring `prefers-reduced-motion`.
## Related
- [navbar.md](./navbar.md)
- [accessibility.md](./accessibility.md)
- [buttons.md](./buttons.md)
+87
View File
@@ -0,0 +1,87 @@
# Navbar
Canonical header: `HeroHeader` in `components/header.tsx`.
Do not create a second site header. Extend the existing component when navigation changes.
## Structure
- Fixed to the top (`fixed inset-x-0 top-0 z-50`).
- Content constrained with `<Container>`.
- Safe area: `pt-[env(safe-area-inset-top,0px)]`.
- RTL reading order: Logo → primary links → utilities (English, theme, CTA, menu).
## Heights
| State | Height |
| --- | --- |
| Default (top) | `h-14` mobile / `h-16` desktop |
| Scrolled glass | `h-12` |
Logo scales slightly on scroll (`scale-[0.97]`).
## States
### Default (scrollY ≤ 24px)
- Transparent background, no border, no blur.
- Full height bar flush with top (`mt-0`).
### Sticky / scrolled (scrollY > 24px)
Threshold: `SCROLL_THRESHOLD = 24`.
- Same vertical position as default — **no** top margin / vertical shift of chrome.
- Glass: `bg-background/75` (or `/65` when backdrop-filter supported).
- Blur: `backdrop-blur-xl backdrop-saturate-150`.
- Border: `border-border/50`.
- Shadow: `shadow-sm`.
- Radius: `rounded-2xl`.
- Width: identical to unscrolled — never a narrower/wider max-width.
- Horizontal inset only: `px-4 md:px-6` so logo / nav / CTA move inward and the glass rim shows.
Transition: `duration-300 ease-out` across padding, background, border, shadow, backdrop-filter, radius.
## Desktop (≥ xl)
- Horizontal `NavigationMenu` with underline affordance on links.
- Dropdown panels: `rounded-xl`, `bg-popover/95`, `backdrop-blur-xl`, `shadow-md`.
- Primary CTA: `Button size="sm"`.
- English link + `ThemeToggle` visible.
## Tablet / mobile (&lt; xl)
- Hamburger `Button variant="ghost" size="icon"`.
- `Sheet` from the right (`side="right"`), width `min(100%, 20rem)`.
- Sheet glass: `bg-background/95 backdrop-blur-xl shadow-xl`.
- Accordion for grouped links (e.g. خدمات).
- Staggered enter animation (`header-nav-in`, ~320ms) — disabled under `prefers-reduced-motion`.
## Padding & alignment
- Always inside site `Container` (`layout.container`: `max-w-[76.8rem]` + `px-6`).
- **Top of page:** inner padding `px-0` — logo and CTA align with section content edges.
- **Scrolled:** glass fills the container; inner padding `px-4 md:px-6` insets chrome toward the center so the glass rim shows.
- Do not use a different `max-w-*` for the sticky state.
- Nav link padding ≈ `px-2 py-1.5`; mobile rows `px-3 py-2.5`.
- Keep CTA and icon buttons on the trailing side; do not center-float disconnected controls.
## Accessibility
- `nav` has `aria-label="ناوبری اصلی"`.
- Mobile toggle: `aria-expanded`, `aria-controls="mobile-nav"`, `aria-label`.
- Sheet title available to SR (`sr-only`).
- Active routes reflected visually (underline / foreground) without relying on color alone when possible.
## Forbidden
- Second fixed header on inner pages.
- Opaque solid bar that ignores glass tokens when scrolled.
- Breaking out of `Container` alignment (e.g. `max-w-[68rem]` or `max-w-[90rem]` shells).
- Animating layout width with JS reflow—use transform/opacity/height transitions already defined.
## Related
- [glassmorphism.md](./glassmorphism.md)
- [motion.md](./motion.md)
- [container.md](./container.md)
+64
View File
@@ -0,0 +1,64 @@
# Quality Checklist
Run this checklist before finishing any UI task or opening a PR that touches presentation.
## Layout
- [ ] ✓ Container consistency — uses `<Container>` / `layout.container`
- [ ] ✓ Grid consistency — grids sit inside the container; gaps from the spacing scale
- [ ] ✓ Section rhythm — `layout.sectionY` (or documented hero top padding)
- [ ] ✓ Alignment — matches header/footer edges
- [ ] ✓ No overflow — no accidental horizontal scroll; decor clipped
## Tokens
- [ ] ✓ Same spacing scale — no arbitrary `px-[N]` / `gap-[N]`
- [ ] ✓ Same typography — `type.*` / `<Typography>`
- [ ] ✓ Semantic colors only — no hardcoded hex
- [ ] ✓ Same radius — `--radius` scale / `rounded-blog` where intended
- [ ] ✓ Same shadows — `shadow-xs/sm/md` or `shadow-glow-primary`
## Components
- [ ] ✓ Same button system — `@/components/ui/button`
- [ ] ✓ Same cards — `@/components/ui/card` (when a card is warranted)
- [ ] ✓ No duplicated primitives — no second Button/Card/Input
- [ ] ✓ Forms use shared Input/Field/Label patterns
## Interaction
- [ ] ✓ Same hover behavior — matches neighboring components
- [ ] ✓ Same transition timing — 200ms UI / 300ms chrome / 500ms reveals
- [ ] ✓ Transform/opacity for motion — no layout animation abuse
- [ ] ✓ Reduced motion honored
## Theme & responsive
- [ ] ✓ Same dark mode — verified with theme toggle
- [ ] ✓ Responsive — mobile / tablet / desktop checked
- [ ] ✓ Touch targets adequate
## Accessibility
- [ ] ✓ Focus rings visible
- [ ] ✓ Keyboard operable
- [ ] ✓ Icon buttons labeled
- [ ] ✓ Contrast acceptable in light and dark
- [ ] ✓ Images have correct alt / decorative handling
## Stability
- [ ] ✓ No layout shift — images sized; fonts not causing CLS spikes
- [ ] ✓ No duplicated styles — no copy-pasted one-off CSS competing with tokens
- [ ] ✓ Imported Shadcn/Tailark blocks fully adapted ([shadcn.md](./shadcn.md))
## Final gate
If any item fails, fix before merge. When unsure, re-read [README.md](./README.md) priority order:
1. Accessibility
2. Layout & container
3. Semantic tokens
4. Component systems
5. Visual effects
6. Imported blocks
+67
View File
@@ -0,0 +1,67 @@
# Responsive
Mobile-first layout using Tailwind default breakpoints.
## Breakpoints
| Name | Min width | Typical device |
| --- | --- | --- |
| default | 0 | Mobile phones |
| `sm` | 640px | Large phones / small tablets |
| `md` | 768px | Tablets |
| `lg` | 1024px | Laptops |
| `xl` | 1280px | Desktops |
| `2xl` | 1536px | Large desktops |
Project usage notes:
- **Header desktop nav** appears at `xl` (not `lg`) to protect crowded Persian labels.
- **Section padding** steps at `md` (`py-16` → `md:py-20`).
- **Hero top padding** steps at `md` (`pt-24` → `md:pt-36`).
## Layout adaptations
| Concern | Mobile | Desktop |
| --- | --- | --- |
| Container gutter | `px-6` | same |
| Max width | `76.8rem` | same |
| Section Y | 64px | 80px |
| Grids | 1 col | 2–4 cols |
| Nav | Sheet menu | Inline `NavigationMenu` |
| Forms (newsletter) | Stacked | Horizontal `sm:flex-row` |
## Typography scaling
Scale variants already encode responsive steps (e.g. `h2`: `text-2xl md:text-3xl lg:text-4xl`). Prefer those over manual overrides.
Inputs use `text-base md:text-sm` to prevent iOS focus zoom.
## Spacing adaptations
- Increase gaps at larger breakpoints (`gap-10 lg:gap-16`) using the spacing scale.
- Do not shrink container padding on mobile below `px-6`.
- Collapse multi-column footers to `grid-cols-2` before `sm:grid-cols-4`.
## Touch & pointer
- Minimum target ~40px for primary icon buttons (`size="icon"` → 40px).
- Keep tap areas separated (`gap-1.5`–`gap-2` in toolbars).
## Overflow
- Test at 320–360px widths for Persian word wrapping.
- No horizontal page scroll; clip section decorations.
- Tables: wrap in horizontal scroll regions (blog tables already do).
## QA matrix (minimum)
- 375×812 (mobile)
- 768×1024 (tablet)
- 1280×800 (desktop)
- Light + dark on each
## Related
- [layout.md](./layout.md)
- [typography.md](./typography.md)
- [navbar.md](./navbar.md)
+92
View File
@@ -0,0 +1,92 @@
# Sections
Every major page band is a **section** with shared shell, rhythm, and background language.
## Required pattern
```tsx
import { Container } from "@/components/container";
import { layout } from "@/lib/layout";
import { type, typeSpacing } from "@/lib/typography";
import { cn } from "@/lib/utils";
<section
id="services"
className={cn("section-shell bg-section-canvas", layout.sectionY, layout.scrollMargin)}
>
<div className="section-decor" aria-hidden>
{/* optional ambient glow */}
</div>
<Container className="section-content">
<div className={cn("mx-auto max-w-2xl text-center", typeSpacing.sectionStack)}>
<p className={type.eyebrow}>…</p>
<h2 className={type.h2}>…</h2>
<p className={type["section-intro"]}>…</p>
</div>
{/* section body */}
</Container>
</section>
```
## One job per section
- One purpose, one headline, usually one supporting sentence.
- Do not combine unrelated marketing goals in a single band.
- Secondary actions belong in the CTA group, not as parallel headline systems.
## Background variants
| Utility | Intent |
| --- | --- |
| `bg-section-canvas` | Default continuous wash (subtle primary tint) |
| `bg-section-muted` | Soft muted band (replaces hard gray patches) |
| `bg-gradient-hero` | Hero atmosphere |
| `bg-gradient-featured` | Featured / highlight band |
| `bg-gradient-cta` | Strong brand CTA panel |
| `bg-background` | Flat canvas when wash is unnecessary |
Alternate canvas ↔ muted between adjacent sections to create rhythm without hard blocks.
## Vertical rhythm
- Use `layout.sectionY` (`py-16 md:py-20`) so every block-to-block gap is the same (8rem / md:10rem).
- First section under header: `layout.sectionTopWithHeader` (homepage hero uses this too).
- Anchors: always add `layout.scrollMargin`.
## Intro block
Prefer centered or start-aligned intro using `typeSpacing.sectionStack`:
- Eyebrow → Heading → Section intro
- Constrain measure with `max-w-2xl` / `max-w-3xl` on the **intro only**
## Decorative layers
```tsx
<div className="section-decor" aria-hidden>
<div className="absolute start-1/2 top-0 size-[min(32rem,80vw)] -translate-x-1/2 rounded-full bg-glow-primary" />
</div>
```
Rules:
- Must sit in `.section-decor`.
- `pointer-events-none`, no layout participation.
- Never `position: fixed` for section atmosphere.
- Opacity kept low (see [gradients.md](./gradients.md)).
## Motion
Wrap section bodies with `MotionReveal` / `MotionRevealItem` for scroll entrance. Keep stagger ≤ ~8 children.
## Forbidden
- Random `py-12` / `py-24` section padding.
- Hard `bg-gray-100` patches disconnected from `bg-section-*`.
- Multiple competing H2s in one section.
## Related
- [layout.md](./layout.md)
- [gradients.md](./gradients.md)
- [footer.md](./footer.md)
+82
View File
@@ -0,0 +1,82 @@
# Shadcn / Tailark Adaptation
Imported blocks are starting points—not finished UI. Every Shadcn UI or Tailark block must be adapted to Amin Rayan tokens before merge.
## Goals
- Visual continuity with existing sections.
- One container, one spacing scale, one button/card system.
- Dark mode and RTL friendliness preserved.
## Adaptation checklist (mandatory)
When adding or regenerating a block:
1. **Container** — Replace `container`, `mx-auto max-w-*`, and foreign `px-*` with `<Container>` / `layout.container`.
2. **Section rhythm** — Outer `<section>` uses `layout.sectionY` (+ `section-shell` / `bg-section-*` as appropriate).
3. **Colors** — Remap to semantic tokens (`bg-background`, `text-foreground`, `primary`, `muted-foreground`, `border`). Delete purple/zinc hardcodes.
4. **Typography** — Swap ad-hoc heading classes for `type.*` / `<Typography>`.
5. **Buttons** — Use `@/components/ui/button` only; map sizes to `sm | default | lg`.
6. **Cards** — Prefer `@/components/ui/card`; remove stacked shadow recipes that fight `shadow-sm`.
7. **Radius** — Align to `--radius` scale (`rounded-md`, `rounded-xl`, `rounded-blog` where relevant).
8. **Shadows** — Prefer `shadow-xs` / `shadow-sm` / `shadow-md` / `shadow-glow-primary`.
9. **Motion** — Prefer `MotionReveal` or existing duration scale; strip flashy infinite animations unless product-required.
10. **Dark mode** — Verify `.dark` with semantic tokens; remove `dark:` hacks that reintroduce foreign palettes.
11. **A11y** — Keep Radix behaviors; ensure labels, focus, and reduced motion.
12. **Alignment QA** — Compare left/right edges with header/footer.
## Never allow after import
| Banned | Replace with |
| --- | --- |
| Random containers / `max-w-5xl` shells | `<Container>` |
| Random hex colors | Semantic tokens |
| Random typography stacks | `type.*` |
| Parallel button CSS | `Button` |
| Arbitrary spacing (`p-[1.125rem]`) | Spacing scale |
| Purple / indigo marketing gradients | Brand gradients |
| Extra global CSS for one block | Local Tailwind + existing utilities |
## Workflow
```bash
npx shadcn@latest add button
# or paste a Tailark section into components/
```
Then immediately:
1. Open [container.md](./container.md) + [quality-checklist.md](./quality-checklist.md).
2. Diff the block against a known-good section (`services-14`, `cta-16`, `footer`).
3. Delete unused vendor CSS variables.
4. Wire copy to Persian where the surface is fa-IR.
## Example transform
```tsx
// ❌ Vendor
<section className="py-24">
<div className="mx-auto max-w-5xl px-4">
<h2 className="text-4xl font-bold tracking-tight">…</h2>
<button className="rounded-full bg-indigo-600 px-6 py-3 text-white">…</button>
</div>
</section>
// ✅ Adapted
<section className={cn("section-shell bg-section-canvas", layout.sectionY)}>
<Container>
<h2 className={type.h2}>…</h2>
<Button size="lg">…</Button>
</Container>
</section>
```
## Existing UI primitives
Prefer composing from `components/ui/*` already in the repo (59+ primitives) instead of re-adding duplicates.
## Related
- [coding-rules.md](./coding-rules.md)
- [quality-checklist.md](./quality-checklist.md)
- Cursor rule: `.cursor/rules/shadcn.mdc`
+70
View File
@@ -0,0 +1,70 @@
# Spacing
All spacing must come from the scale below. Arbitrary values (`p-[13px]`, `mt-[22px]`, `gap-[0.9rem]`) are forbidden unless fixing a temporary third-party bug—and must be replaced before merge.
## Scale (px)
| Token | px | Rem | Tailwind |
| --- | --- | --- | --- |
| 1 | 4 | 0.25 | `1` |
| 2 | 8 | 0.5 | `2` |
| 3 | 12 | 0.75 | `3` |
| 4 | 16 | 1 | `4` |
| 5 | 20 | 1.25 | `5` |
| 6 | 24 | 1.5 | `6` |
| 8 | 32 | 2 | `8` |
| 10 | 40 | 2.5 | `10` |
| 12 | 48 | 3 | `12` |
| 16 | 64 | 4 | `16` |
| 20 | 80 | 5 | `20` |
| 24 | 96 | 6 | `24` |
Use as `p-4`, `gap-6`, `mt-8`, `space-y-4`, etc.
## Layout-level spacing
| Role | Token / class | Notes |
| --- | --- | --- |
| Container gutter | `px-6` (24) | Via `layout.container` only |
| Section Y (mobile) | `py-16` (64) | `layout.sectionY` — adjacent gap = 8rem |
| Section Y (md+) | `md:py-20` (80) | `layout.sectionY` — adjacent gap = 10rem |
| Header clearance | `pt-28` / `md:pt-32` | `layout.sectionTopWithHeader` |
| Scroll margin | `scroll-mt-24` (96) | Anchors under fixed header |
## Typography-linked spacing
From `lib/typography.ts` → `typeSpacing`:
| Token | Class | Use |
| --- | --- | --- |
| `eyebrowToHeading` | `mt-2` | Eyebrow → title |
| `headingToBody` | `mt-3` | Title → body |
| `sectionStack` | `space-y-4` | Section intro stack |
| `heroStack` | `space-y-6` | Hero copy stack |
| `cardStack` | `gap-1.5` | Card title → description |
| `paragraphStack` | `space-y-4` | Stacked paragraphs |
| `labelToControl` | `gap-1.5` | Label → input |
Prefer `typeSpacing.*` over reinventing the same stacks.
## Component spacing norms
| Component | Internal spacing |
| --- | --- |
| Card | `gap-6`, horizontal `px-6`, vertical `py-6` |
| Button | Tokenized via `--btn-px-*` / `--btn-gap-*` — do not override casually |
| Field groups | `gap-7` between fields; `gap-3` for checkbox/radio groups |
| Footer blocks | `mb-14`, `pb-14`, column `gap-10` |
## Rules
1. Never invent spacing outside the scale.
2. Prefer consistency with nearest sibling sections over “optical” one-off tweaks.
3. Vertical rhythm between major sections comes from `layout.sectionY` alone — do not add extra section margins that change the shared gap.
4. Icon-to-label gaps inside buttons use button tokens; elsewhere prefer `gap-2` / `gap-3`.
## Related
- [layout.md](./layout.md)
- [typography.md](./typography.md)
- [coding-rules.md](./coding-rules.md)
+119
View File
@@ -0,0 +1,119 @@
# Typography
Persian-first typography system using **IRANSansX** (FaNum — Persian digits baked into the font). Canonical scale: `lib/typography.ts`. Prefer `type.*` classes or `<Typography variant="…">` over ad-hoc `text-*` stacks.
## Font family
| Role | Stack |
| --- | --- |
| Sans (UI) | `IRANSansX`, system-ui, sans-serif |
| Mono | `ui-monospace`, SF Mono, Menlo, Consolas, monospace |
Loaded via `@font-face` in `app/globals.css`. Body applies `font-family: "IRANSansX"` with `text-align: start` (right in RTL).
## Global base
| Element | Line height | Letter spacing | Alignment |
| --- | --- | --- | --- |
| `body` | `1.75` | `normal` | `start` |
| `h1–h6` | `1.45` | `normal` | `start` |
| `p` | `1.8` | `normal` | `start` |
Tracking is almost always `tracking-normal` (Persian). Avoid `tracking-tight` except decorative mono display (`display-mono`).
**Prefer** `text-start` (right in RTL). Section captions (`section-intro`) use `text-justify` with `text-align-last: start` so multi-line captions justify while the last line stays right-aligned. Avoid justify on body paragraphs and titles.
## Scale
Use via:
```tsx
import { type, typeSpacing, typeMeasure } from "@/lib/typography";
import { Typography, SectionTitle } from "@/components/ui/typography";
import { toPersianDigits } from "@/lib/persian-digits";
<h2 className={type.h2}>عنوان بخش</h2>
<Typography variant="section-intro" className={typeMeasure.prose}>
توضیح کوتاه
</Typography>
<span>{toPersianDigits(24)}</span>
```
| Requested level | Variant | Size / weight | Default element | Use |
| --- | --- | --- | --- | --- |
| Heading 1 | `h1` / `display` | 3xl→5xl, extrabold / bold | `h1` | Page / hero titles |
| Heading 2 | `h2` / `cta-title` | 2xl→4xl, bold | `h2` | Major sections |
| Heading 3 | `h3` | lg→xl, bold | `h3` | Subsections / features |
| Body Large | `body-lg` | lg, leading 1.8 | `p` | Prominent intros (same size as captions) |
| Body | `body` | base, leading 1.8 | `p` | Standard copy |
| Small Text | `body-sm` | sm, leading 1.75 | `p` | Dense / secondary |
| Caption | `caption` | xs, muted | `span` | Meta, footnotes |
| Micro | `micro` | 11px (0.6875rem) | `span` | Dense chrome / mockups / step badges |
| Button Text | `button` / `button-lg` | sm / base, medium, leading-none | `span` | Wired into `Button` sizes |
| Navigation | `nav` / `nav-mobile` | sm / base, medium | `span` | Header / mobile menu |
| — | `eyebrow` | sm medium, muted | `p` | Section labels |
| — | `label` | sm medium | `span` | Form labels |
| — | `card-title` / `card-desc` | base bold / sm | `h3` / `p` | Cards |
| — | `section-intro` | lg muted, justify | `p` | Section captions under headings |
| — | `stat` | 4xl→5xl bold primary | `span` | KPI numbers |
## Reading measure
| Token | Class | Use |
| --- | --- | --- |
| `typeMeasure.narrow` | `max-w-md` | Short support under a heading |
| `typeMeasure.prose` | `max-w-2xl` | Standard paragraph / intro |
| `typeMeasure.wide` | `max-w-3xl` | Longer multi-paragraph blocks |
## Alignment
- Titles: **right-aligned** (`text-start` in RTL).
- Section captions (`section-intro`): justify with last line start (right in RTL) when 2+ lines.
- Body paragraphs: right-aligned; do not justify.
- Hero marketing headlines may use `text-center` when the composition requires it — override with `className="text-center"`.
- Do not inherit `text-center` onto multi-line section captions.
## Numbers
- Prefer explicit Persian digits in copy (`۲۴`) or `toPersianDigits()` / `formatPersianNumber()`.
- IRANSansXFaNum also shapes Latin digits visually; still prefer explicit Persian for dates, counters, stats, and steps.
- Keep Latin only for technical IDs, versions, emails, and code.
## Button text
`Button` sizes pull from `type.button` (sm/default) and `type["button-lg"]` (lg). Horizontal padding tokens in `globals.css` are slightly wider for Persian glyphs. Do not wrap button labels in `<Typography>` unless building a non-`Button` control.
## Hierarchy rules
1. One `h1` per page (or `display` acting as the page title).
2. Section titles use `h2` / `cta-title`; subsections use `h3`/`h4`.
3. Pair titles with at most one short supporting sentence (`section-intro` or `body` + `text-muted-foreground`).
4. Use `text-balance` on large headings (already in scale) for better wrap in Persian.
5. Blog article prose uses `.blog-content` rules in `globals.css`—do not override casually.
## Stacking
```tsx
<div className={typeSpacing.sectionStack}>
<p className={type.eyebrow}>خدمات</p>
<h2 className={cn(type.h2, typeSpacing.eyebrowToHeading)}>…</h2>
<p className={cn(type["section-intro"], typeSpacing.headingToBody, typeMeasure.prose)}>…</p>
</div>
```
## Forbidden
- Inter / Roboto / Arial as primary UI fonts.
- Random `text-[15px]` / `text-[1.05rem]` (use `type.micro` for dense chrome).
- `tracking-widest` on Persian body copy.
- `text-justify` on body paragraphs / titles (section captions are the exception).
- Using `font-bold` everywhere—follow the scale weights.
- Physical `text-left` / `mr-` / `ml-` / `pr-` / `pl-` when logical `text-start` / `ms-` / `me-` / `ps-` / `pe-` work.
- Overriding scale sizes with `sm:text-5xl` / `lg:text-6xl` on `type.display` / `type.h*`.
- Using `type.h2` on an `h3` (or vice versa) — keep element level and scale token aligned.
## Related
- [buttons.md](./buttons.md)
- [responsive.md](./responsive.md)
- [accessibility.md](./accessibility.md)