# فاز ۹ — گالری واژه‌های حرف · «مرا به خاطر بسپار» · بکاپ والدین

**نسخه: 1.5.0** · تاریخ: 1404/07/03 (2026-09-25)
درخواست کاربر:
1. «یک گام جلوتر بریم. با هر حرف چندین عکس رو با نام هاش نشون بده و یاد بده اون حرف رو.
   مثلاً برای حرف "م" واژه های موز، مرغ، مدرسه، معلم…»
2. «"مرا به خاطر بسپار" + refresh token — الان توکن والد ۲۴ ساعته است»
3. «خروجی گرفتن داده‌ها (بکاپ والدین) — اطمینان خاطر»

---

## ۱. گالری واژه‌های حرف (ایستگاه)

### ۱.۱ داده — هر حرف ۳ تا ۵ مثال (قبلاً همه ۱ بودند)

| پک | واژه‌ها | مثال/حرف | واژه‌های جدید |
|---|---|---|---|
| fa | **77** (از ۳۲) | ۳–۵ | ۴۵ (موز🍌 مرغ🐔 مدرسه🏫 معلم👩‍🏫 آناناس🍍 اژدها🐲 …) |
| en | **67** (از ۵۵) | ۳–۵ | ۱۲ (jet violin window zucchini …) |
| nl | **52** (از ۲۶) | ۳–۵ | ۲۶ (boek dolfijn giraf quiche …) |
| tr | **56** (از ۳۸) | ۳–۵ | ۱۸ (çocuk öğretmen yağmur vişne cep …) |
| ar | **57** (از ۳۲) | ۳–۵ | ۲۵ (ثَلْج حَلِيب شَجَرَة طَبِيب ظَبْي …) |

الگوریتم `examples` (اسکریپت `scripts/build-examples.mjs` — قابل اجرای مجدد):
مثال کنونیکال فعلی (پایداری بازی‌ها) ← واژه‌های پین‌شده (لیست صریح کاربر برای «م»)
← واژه‌های شروع‌شونده با حرف (کوتاه‌تر اول) ← واژه‌های حاوی حرف — سقف ۵.

### ۱.۲ راستی‌آزمایی ساختاری — `client/js/core/wordText.js` (ماژول خالص جدید)

هر واژه با «پیمایش letters» روی متن تطبیق داده می‌شود (نه indexOf):
- **حرکت‌ها/تشدید/سکون/ZWNJ/خط تیره** رد می‌شوند («yo-yo»، «تُفَّاح»)
- **واریانت‌های هم‌خانواده**: آ/أ/إ↔ا · ة↔ت · ئ↔ی · ك↔ک
- **بزرگ/کوچک ترکی‌آگاه**: I→ı و İ→i (ولی ı≠İ — ترکی از `toLocaleLowerCase('tr')`)
- خروجی `letterPositions` = مختصات هر تکرارِ حرف در متن اصلی

**۶ باگ واقعی داده‌ی قدیمی شکار و رفع شد**: طوطی (ت→ط) · گل (و اضافه) ·
بَطَّة و قِطّ (ت→ط) · تُفَّاح (ف تکراری) · زَرافة (ة جاافتاده) · xylofoon (o جاافتاده).

### ۱.۳ UI — مودال ایستگاه

به‌جای یک واژه، **گرید کارت‌های واژه** (auto-fill، minmax 104px): ایموجی بزرگ + نام
واژه با **هایلایت رنگیِ حرف هدف در همه‌ی تکرارها** (مرورگرهای مدرن اتصال RTL را
مرزِ `<span>` با فونت یکسان حفظ می‌کنند). لمس = پخش زنجیره‌ی صدا (والد→ادمین→TTS)
+ انیمیشن پالس + نشان ✓ «شنیدم». حالت reduced-motion: پالس خاموش، رنگ می‌ماند.

## ۲. «مرا به خاطر بسپار» + Refresh Token

### ۲.۱ سرور

| بخش | طراحی |
|---|---|
| جدول `parent_refresh_tokens` | فقط **sha256** ذخیره می‌شود (لو رفتن DB ≠ لو رفتن توکن) |
| توکن | `rft_` + ۲۵۶ بیت تصادفی، عمر ۳۰ روز |
| چرخش | **تک‌مصرفی** — هر refresh توکن قبلی را باطل و توکن تازه می‌دهد |
| ضد سرقت | replay توکنِ باطل‌شده → **همه‌ی جلسه‌های آن والد** بسته می‌شود + لاگ |
| کانال انتقال | هدر `X-Refresh-Token` · کوئری `?rt=` · بدنه (درس پروکسی 401) |
| `jti` در JWT | دو صدورِ هم‌ثانیه بایت‌به‌بایت یکسان نمی‌شوند |
| endpoints | `POST /auth/parent/refresh` · `POST /auth/parent/logout` (ابطال) |
| housekeeping | حذف ردیف‌های منقضی در هر صدور |

بدون remember: رفتار قبلی (فقط توکن ۲۴س، مرگ با بستن تب). با remember (پیش‌فرض روشن):
refresh در `localStorage` (SafeLocal — fallback حافظه‌ای در iframe سندباکس).

### ۲.۲ کلاینت

- **RestRepo**: روی 401 (فقط مسیرهای غیر-auth) → یک‌بار refresh + retry خودکار؛
  single-flight (طوفان درخواست‌ها → یک چرخش)؛ آفلاین = توکن‌ها می‌مانند.
- **maybeRestore**: بدون توکن ۲۴س ولی با refresh → اول تازه‌سازی فعالانه (بدون
  رفت‌وبرگشت 401) → ورود بی‌صدا بعد از باز شدن مرورگر.
- **دکمه‌ی خروج** (در کارت ✅ و نمای متصل): ابطال در سرور + پاک‌سازی هر دو لایه.

## ۳. خروجی داده‌ها (بکاپ والدین) — `GET /api/v1/parent/export`

محتوای JSON (فرمت 1):
- `export`: اپ/نسخه/زمان/فرمت
- `parent`: ایمیل + تاریخ ساخت (**بدون رمز — هرگز**)
- `children[]`: پروفایل + `progress` (گروه‌بندی بر اساس پک) + `badges` + `events`
- `audio[]`: صداهای ضبط‌شده‌ی خود والد با **بایت‌های base64** (فایل گم‌شدنی نیست)

UI در نمای متصل: دکمه‌ی «💾 نسخه‌ی پشتیبان» → مودال آمار (کودکان/رویدادها/صداها/حجم)
+ «دانلود JSON» + «نمایش/کپی متن» (fallback برای محیط‌های بدون دانلود).
امنیت: `requireParent` + همه‌ی کوئری‌ها مقید به `parent_id` — تست نشت بین حساب‌ها.

## ۴. تست‌ها — همه سبز

| مجموعه | نتیجه |
|---|---|
| واحد (۹ فایل) | **134/134** (+15: گالری/راستی‌آمایی ساختاری/letterPositions ×6/refresh ×5/export ×3) |
| packs.test.js | 24 — شامل «گالری م = ماهی·موز·مرغ·مدرسه·معلم» |
| api.test.js | 35 — چرخش، replay→بستن کل، logout، ساختار export، جداسازی |
| E2E اپ | **18/18** (+4: چک‌باکس، رفرش کامل با sessionStorage پاک، بکاپ، خروج) |
| E2E پک‌ها | **6/6** (+1: گالری عربی ≥۳ کارت + هایلایت + پالس) |
| E2E ادمین | 8/8 |

راستی‌آزمایی DOM گالری «ا» (کروم بی‌سر): dir=rtl · گرید ۳ستونه · هایلایت «آ» در آب،
سه «ا» در آناناس، رتبه‌بندی شروع‌شونده‌ها قبل از حاوی‌ها · لمس → playing/heard ✓

## ۵. فایل‌های کلیدی

- `client/js/core/wordText.js` — جدید: letterPositions/verifyWord/verifyPackWords
- `scripts/build-examples.mjs` — جدید: تولید مثال‌ها (idempotent)
- `client/js/ui/screens/mapScreen.js` — گالری ایستگاه
- `server/auth.js` + `server/routes/auth.js` — refresh/logout + jti
- `server/routes/parent.js` — جدید: export
- `client/js/api/restRepo.js` — retry خودکار + logout
- `client/js/core/safeSession.js` — SafeLocal
- i18n: هر ۵ زبان ۱۹۵→**206 کلید** غیر badges (هم‌ارزی حفظ شد)
