# 06 — طراحی فرانت‌اند، i18n و سیستم انیمیشن GSAP

## 1. ساختار پوشه‌ی کلاینت

```
client/
├── index.html                 # تک‌صفحه‌ای (SPA) — فقط shell + <div id="app">
├── assets/
│   ├── css/        theme.scss (متغیرها) · components.scss · reduced-motion.scss
│   ├── img/        mascot/ · badges/ · words/<pack>/
│   └── audio/      <pack>/letters/** · <pack>/words/** · sfx/**
├── data/
│   ├── packs/      fa.json · en.json · nl.json (نسخه‌ی توسعه؛ در prod از CDN)
│   └── i18n/       ui-fa.json · ui-en.json · ui-nl.json (متن‌های رابط)
├── js/
│   ├── core/
│   │   ├── eventBus.js        # pub/sub — ستون فقرات
│   │   ├── router.js          # hash router: #/home #/map #/game/:id #/parents
│   │   ├── store.js           # state مرکزی با observable (الگوی سبک)
│   │   ├── packLoader.js      # fetch + اعتبارسنجی schema + کش نسخه‌دار
│   │   ├── i18n.js            # t(key) + تعویض dir/font بر اساس pack
│   │   └── audioEngine.js     # سند 07
│   ├── engine/
│   │   ├── gameEngine.js      # چرخه‌ی عمر بازی + سندباکس try/catch
│   │   ├── gameRegistry.js    # ثبت/یافتن GameModule ها
│   │   ├── letterSelector.js  # الگوریتم وزن‌دار سند 05
│   │   ├── masteryTracker.js  # ارتقای سطح + Leitner
│   │   ├── adaptive.js        # سختی تطبیقی
│   │   └── scoring.js
│   ├── games/                 # هر فایل = یک GameModule
│   │   ├── letterPop.js · memoryMatch.js · soundHunt.js · traceLetter.js
│   │   ├── catchBasket.js · wordBuilder.js · oddOneOut.js · alphabetSafari.js
│   ├── ui/
│   │   ├── screens/           # homeScreen.js · mapScreen.js · gameScreen.js · parentsScreen.js
│   │   ├── components/        # letterCard.js · starBar.js · mascot.js · rewardModal.js
│   │   └── animationService.js
│   ├── api/                   # لایه‌ی دسترسی به داده (Repo Pattern)
│   │   ├── localRepo.js       # localStorage
│   │   ├── restRepo.js        # AJAX: fetch + timeout + retry (backoff)
│   │   └── syncQueue.js       # FIFO + LRU cap 500
│   └── main.js                # bootstrap: pack → auth → route
├── sw.js                      # Service Worker (سند 08)
└── tests/                     # Jest (engine/*) + Playwright (e2e)
```

## 2. اصول رابط کودک (Kid-UX)

- **لمس-اول:** هدف‌ها ≥ 48×48px؛ فاصله ≥ 8px؛ `touch-action: manipulation` (حذف delay 300ms).
- **بدون متن برای کودک:** همه‌ی راهنماها صوتی (ماسکات) + آیکون؛ متن فقط در بخش والدین.
- **Bootstrap 5 سفارشی‌سازی‌شده:** تم رنگی پاستلی با CSS Variables؛ دکمه‌ها rounded-4، سایز `btn-lg` به بالا.
- **دسترس‌پذیری:** `prefers-reduced-motion` → انیمیشن‌ها به fade ساده تبدیل می‌شوند؛ گزینه‌ی فونت مناسب دیسلکسیا (در تنظیمات والدین)؛ `aria-live="polite"` برای بازخوردها.

## 3. چندزبانه‌ای و RTL/LTR

```
pack.dir = "rtl"  →  <html dir="rtl" lang="fa-IR">
```
- i18n خودکار با بارگذاری پک: جهت، فونت (`Vazirmatn` برای فارسی)، اعداد (`fa` → ۱۲۳)، متن دکمه‌ها.
- کامپوننت‌ها هیچ‌وقت `left/right` منطقی نمی‌بینند — فقط `margin-inline-start`, `padding-inline`, `inset-inline` (CSS Logical Properties). این یک قانون lint است.
- بازی Word-Builder ترتیب چیدن حروف را از `pack.dir` می‌گیرد — همان کد برای فارسی و هلندی.

## 4. سیستم انیمیشن GSAP — AnimationService

### 4.1 واژگان مشترک انیمیشن (نه تایم‌لاین پراکنده در کدها)

| id | تایم‌لاین | کاربرد | منبع رویداد |
|---|---|---|---|
| `intro-card` | `scale:0→1, back.out(1.7)`, stagger 60ms | ورود کارت‌ها/گزینه‌ها | `ui:mount` |
| `success-pop` | `scale 1→1.25→1` + `rotate ±8°` yoyo | پاسخ درست | `game:attempt(ok)` |
| `gentle-shake` | `x: ±6px` × 3، مدت 0.4s | پاسخ نادرست — هرگز قرمز/بلند نیست | `game:attempt(!ok)` |
| `hint-pulse` | `scale 1→1.1` repeat 2 | راهنمایی هدف | `adaptive:help` |
| `confetti-burst` | ۲۰ ذره DOM، `stagger 0.02`، gravity با `gsap.to y+` | پایان مرحله | `game:complete` |
| `star-reveal` | ستاره‌ها `stagger 0.15, back.out(2)` | مراسم پاداش | `scoring:finalize` |
| `mascot-cheer` | bounce + چرخش خفیف | تشویق ماسکات | `audio:praise` |
| `screen-out/in` | `opacity + x: ∓40px` (جهت‌آگاه از `dir`) | ترنزیشن صفحات | `router:navigate` |

```js
// js/ui/animationService.js — تنها جایی که gsap.* مستقیم صدا می‌شود
import gsap from 'gsap';
const reduced = matchMedia('(prefers-reduced-motion: reduce)').matches;

export const Anim = {
  play(id, targets, { dir = 'ltr' } = {}) {
    if (reduced) return gsap.to(targets, { opacity: 1, duration: 0.2 });
    return VOCABULARY[id](gsap, targets, dir);   // سازنده‌ی timeline اختصاصی
  }
};
```

### 4.2 قواعد عملکرد انیمیشن

1. فقط `transform` و `opacity` انیمیت می‌شوند (GPU-friendly، بدون layout thrash).
2. `will-change` فقط در لحظه‌ی انیمیشن، سپس حذف.
3. حداکثر ۳۰ عنصر متحرک هم‌زمان؛ ذرات confetti با object-pool.
4. هر `GameModule.destroy()` باید `tl.kill()` کند — نشت timeline ممنوع (چک در تست‌های حافظه).

## 5. مدیریت وضعیت (Store)

```js
// store ساده با الگوی observable
const store = createStore({
  child: null, pack: null, progress: {},
  settings: { sound: true, reducedMotion: false },
  session: { letters: [], score: 0, combo: 0 }
});
store.subscribe('progress', renderMapScreen);   // فقط هنگام تغییر progress دوباره رندر می‌شود
```
- یک منبع حقیقت؛ صفحات تابعیِ state هستند (render(state) → DOM).
- بعد از هر `session` تغییر، `LocalRepo.save` + `SyncQueue.enqueue` از طریق subscription انجام می‌شود — منطق بازی از ذخیره‌سازی بی‌خبر است.

## 6. مسیریابی (Hash Router)

```
#/home            انتخاب/ساخت کودک (آواتارها)
#/map             نقشه ماجراجویی — انتخاب ایستگاه/بازی
#/game/:id        اجرای بازی (gameEngine سندباکس می‌کند)
#/room            اتاق جوایز (نشان‌ها و ستاره‌ها)
#/parents         پنل والدین — محافظت‌شده با PIN (خارج از دسترس لمس کودک)
```
- ترنزیشن صفحات با `screen-out/in` (GSAP) و سپس رندر جدید — هیچ reload کامل وجود ندارد.
