import React, { useState, useEffect, useCallback, useRef } from 'react'; import ReactDOM from 'react-dom/client'; // ================================================================================= // TYPES // ================================================================================= interface GameLevel { id: number; image: string; answer: string; question: string; info?: string; letters: string[]; } interface KeyboardLetter { letter: string; id: number; used: boolean; } interface GuessSlot { letter: string; keyboardId: number; } interface SavedGameState { levelId: number; guess: (GuessSlot | null)[]; keyboardLetters: KeyboardLetter[]; revealUsed: boolean; removedTiles: number[]; } interface PlayerStats { completedLevelIds: number[]; totalLettersRevealed: number; achievements: string[]; totalPlayTimeSeconds: number; achievementUnlockTimes: { [key: string]: number }; hasUnlockedFullGame: boolean; } interface Achievement { id: string; name: string; description: string; check: (stats: PlayerStats) => boolean; icon: string; } // ================================================================================= // CONSTANTS // ================================================================================= const definedLevels: Omit[] = [ { answer: 'الكعبة', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/1.png', letters: ['ك', 'ع', 'ا', 'ل', 'ة', 'ب', 'د', 'ر', 'س', 'ش', 'ص', 'ض'] }, { answer: 'روضة طاهرة', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/2.png', letters: ['ر', 'و', 'ض', 'ة', 'ط', 'ا', 'ه', 'ر', 'ة', 'ك', 'ل', 'م'] }, { answer: 'المعظم', question: 'كئي مسجد؟', image: 'https://zahabapps.store/games/maqamaat_img/3.png', letters: ['ا', 'ل', 'م', 'ع', 'ظ', 'م', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ'] }, { answer: 'غرة المساجد', question: 'كئي مسجد؟', image: 'https://zahabapps.store/games/maqamaat_img/4.png', letters: ['غ', 'ر', 'ة', 'ا', 'ل', 'م', 'س', 'ا', 'ج', 'د', 'ف', 'ق', 'ك', 'ن', 'ه'] }, { answer: 'حطيب مبارك', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/5.png', letters: ['ح', 'ط', 'ي', 'ب', 'م', 'ب', 'ا', 'ر', 'ك', 'س', 'ش', 'ص'] }, { answer: 'جامنگر', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/6.png', letters: ['ج', 'ا', 'م', 'ن', 'گ', 'ر', 'د', 'ذ', 'ز', 'س', 'ش', 'ص'] }, { answer: 'برهانپور', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/7.png', letters: ['ب', 'ر', 'ه', 'ا', 'ن', 'پ', 'و', 'ر', 'س', 'ش', 'ص', 'ض'] }, { answer: 'اجين', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/8.png', letters: ['ا', 'ج', 'ي', 'ن', 'ق', 'ب', 'ت', 'ث', 'ح', 'خ', 'د', 'م'] }, { answer: 'أنور', question: 'كئي جامع؟', image: 'https://zahabapps.store/games/maqamaat_img/9.png', letters: ['أ', 'ن', 'و', 'ر', 'د', 'ذ', 'ز', 'س', 'ش', 'ص', 'ض', 'ط'] }, { answer: 'گلياكوت', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/10.png', letters: ['گ', 'ل', 'ي', 'ا', 'ك', 'و', 'ت', 'ب', 'ث', 'ج', 'ح', 'خ'] }, { answer: 'أحمدآباد', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/11.png', letters: ['أ', 'ح', 'م', 'د', 'آ', 'ب', 'ا', 'د', 'ر', 'ز', 'س', 'ش'] }, { answer: 'أقمر', question: 'كئي جامع؟', image: 'https://zahabapps.store/games/maqamaat_img/12.png', letters: ['أ', 'ق', 'م', 'ر', 'س', 'ش', 'ص', 'ض', 'ط', 'ظ', 'ع', 'غ'] }, { answer: 'ماندوي', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/13.png', letters: ['م', 'ا', 'ن', 'د', 'و', 'ي', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ'] }, { answer: 'سورت', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/14.png', letters: ['س', 'و', 'ر', 'ت', 'ج', 'ح', 'خ', 'د', 'ذ', 'ز', 'ش', 'ص'] }, { answer: 'شبام', question: 'كئي جگه؟', info: 'Syedna Idris Imaduddin RA', image: 'https://zahabapps.store/games/maqamaat_img/15.png', letters: ['ش', 'ب', 'ا', 'م', 'د', 'ذ', 'ر', 'ز', 'س', 'ص', 'ض', 'ط'] }, { answer: 'شارقة', question: 'كئي جگه؟', info: 'Syedna Ali Shamsuddin RA', image: 'https://zahabapps.store/games/maqamaat_img/16.png', letters: ['ش', 'ا', 'ر', 'ق', 'ة', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د'] }, { answer: 'أزهر', question: 'كئي جامع؟', image: 'https://zahabapps.store/games/maqamaat_img/17.png', letters: ['أ', 'ز', 'ه', 'ر', 'س', 'ش', 'ص', 'ض', 'ط', 'ظ', 'ع', 'غ'] }, { answer: 'برودا', question: 'كئي جگه؟', info: 'Syedi Moosanji Bin Taj QR', image: 'https://zahabapps.store/games/maqamaat_img/18.png', letters: ['ب', 'ر', 'و', 'د', 'ا', 'ج', 'ح', 'خ', 'ذ', 'ز', 'س', 'ش'] }, { answer: 'سورت', question: 'كئي جگه؟', info: 'Mulla Waheb bs QR', image: 'https://zahabapps.store/games/maqamaat_img/19.png', letters: ['س', 'و', 'ر', 'ت', 'ا', 'ب', 'ج', 'ح', 'د', 'ذ', 'ز', 'ش'] }, { answer: 'كهمبات', question: 'كئي جگه؟', info: 'Moulai Abdullah Saheb(QS)', image: 'https://zahabapps.store/games/maqamaat_img/20.png', letters: ['ك', 'ه', 'م', 'ب', 'ا', 'ت', 'د', 'ذ', 'ر', 'ز', 'س', 'ش'] }, { answer: 'موربي', question: 'كئي جگه؟', info: 'Mawalaya Raj QR', image: 'https://zahabapps.store/games/maqamaat_img/21.png', letters: ['م', 'و', 'ر', 'ب', 'ي', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ'] }, { answer: 'ذي جبلة', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/22.png', letters: ['ذ', 'ي', 'ج', 'ب', 'ل', 'ة', 'ح', 'خ', 'د', 'ر', 'ز', 'س'] }, { answer: 'DONGAON', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/23.png', letters: ['D', 'O', 'N', 'G', 'A', 'O', 'N', 'B', 'C', 'E', 'F', 'H'] }, { answer: 'جيوشي', question: 'كئي جامع؟', image: 'https://zahabapps.store/games/maqamaat_img/24.png', letters: ['ج', 'ي', 'و', 'ش', 'ي', 'ا', 'ب', 'ت', 'ث', 'ح', 'خ', 'د'] }, { answer: 'دينمال', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/25.png', letters: ['د', 'ي', 'ن', 'م', 'ا', 'ل', 'ذ', 'ر', 'ز', 'س', 'ش', 'ص'] }, { answer: 'سيدهپور', question: 'كئي جگه؟', info: 'Syedi Qazi Khan Saheb(QS)', image: 'https://zahabapps.store/games/maqamaat_img/26.png', letters: ['س', 'ي', 'د', 'ه', 'پ', 'و', 'ر', 'ذ', 'ز', 'ش', 'ص', 'ض'] }, { answer: 'لؤلؤة', question: 'كئي جامع؟', image: 'https://zahabapps.store/games/maqamaat_img/27.png', letters: ['ل', 'ؤ', 'ل', 'ؤ', 'ة', 'ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ'] }, { answer: 'طيبة', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/28.png', letters: ['ط', 'ي', 'ب', 'ة', 'ا', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ'] }, { answer: 'زبيد', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/29.png', letters: ['ز', 'ب', 'ي', 'د', 'ا', 'ت', 'ث', 'ج', 'ح', 'خ', 'ذ', 'ر'] }, { answer: 'كراچي', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/30.png', letters: ['ك', 'ر', 'ا', 'چ', 'ي', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د'] }, { answer: 'نيروبي', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/31.png', letters: ['ن', 'ي', 'ر', 'و', 'ب', 'ي', 'ت', 'ث', 'ج', 'ح', 'خ', 'د'] }, { answer: 'MAISAHEBA', question: 'كئي جگه؟', info: 'Noor Biwi Saheba(QS), Fatema Biwi Saheba(QS)', image: 'https://zahabapps.store/games/maqamaat_img/32.png', letters: ['M', 'A', 'I', 'S', 'H', 'E', 'B', 'A', 'D', 'F', 'G', 'K'] }, { answer: 'نجف', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/33.png', letters: ['ن', 'ج', 'ف', 'ا', 'ب', 'ت', 'ث', 'ح', 'خ', 'د', 'ذ', 'ر'] }, { answer: 'المدينة', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/34.png', letters: ['ا', 'ل', 'م', 'د', 'ي', 'ن', 'ة', 'ر', 'ز', 'س', 'ش', 'ص'] }, { answer: 'اجين', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/35.png', letters: ['ا', 'ج', 'ي', 'ن', 'ف', 'ت', 'ث', 'ح', 'خ', 'د', 'ذ', 'م'] }, { answer: 'كربلاء', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/36.png', letters: ['ك', 'ر', 'ب', 'ل', 'ا', 'ء', 'د', 'ذ', 'ز', 'س', 'ش', 'ص'] }, { answer: 'رامپورا', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/37.png', letters: ['ر', 'ا', 'م', 'پ', 'و', 'ر', 'ا', 'ب', 'ت', 'ث', 'ج', 'ح'] }, { answer: 'GODHRA', question: 'كئي جگه؟', info: 'Syedi Ismailji Shaheed Saheb (QR)', image: 'https://zahabapps.store/games/maqamaat_img/38.png', letters: ['G', 'O', 'D', 'H', 'R', 'A', 'B', 'C', 'E', 'F', 'I', 'J'] }, { answer: 'BANSWARA', question: 'كئي جگه؟', info: 'Moulaya Abdul Rasul Shaheed Saheb(QS)', image: 'https://zahabapps.store/games/maqamaat_img/39.png', letters: ['B', 'A', 'N', 'S', 'W', 'A', 'R', 'A', 'C', 'D', 'E', 'F'] }, { answer: 'AHMEDNAGAR', question: 'كئي جگه؟', info: 'Ganje Shohoda (QS)', image: 'https://zahabapps.store/games/maqamaat_img/40.png', letters: ['A', 'H', 'M', 'E', 'D', 'N', 'A', 'G', 'A', 'R', 'S', 'T'] }, { answer: 'غيل بني حامد', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/41.png', letters: ['غ', 'ي', 'ل', 'ب', 'ن', 'ي', 'ح', 'ا', 'م', 'د', 'ذ', 'ر', 'ز', 'س', 'ش', 'ص', 'ض', 'ط'] }, { answer: 'كهف النعيم', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/42.png', letters: ['ك', 'ه', 'ف', 'ا', 'ل', 'ن', 'ع', 'ي', 'م', 'ب', 'ت', 'ث'] }, { answer: 'LONDON', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/43.png', letters: ['L', 'O', 'N', 'D', 'O', 'N', 'A', 'B', 'C', 'E', 'F', 'G'] }, { answer: 'الشام', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/44.png', letters: ['ا', 'ل', 'ش', 'ا', 'م', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د'] }, { answer: 'الأردن', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/45.png', letters: ['ا', 'ل', 'أ', 'ر', 'د', 'ن', 'ذ', 'ز', 'س', 'ش', 'ص', 'ض'] }, { answer: 'ذي جبلة', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/46.png', letters: ['ذ', 'ي', 'ج', 'ب', 'ل', 'ة', 'ت', 'ث', 'ح', 'خ', 'د', 'ر'] }, { answer: 'ذي مرمر', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/47.png', letters: ['ذ', 'ي', 'م', 'ر', 'م', 'ر', 'ز', 'س', 'ش', 'ص', 'ض', 'ط'] }, { answer: 'PAKHTI', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/48.png', letters: ['P', 'A', 'K', 'H', 'T', 'I', 'B', 'C', 'D', 'E', 'F', 'G'] }, { answer: 'رأس الحسين', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/49.png', letters: ['ر', 'أ', 'س', 'ا', 'ل', 'ح', 'س', 'ي', 'ن', 'ش', 'ص', 'ض'] }, { answer: 'مالك الأشتر', question: 'كوني زيارة؟', image: 'https://zahabapps.store/games/maqamaat_img/50.png', letters: ['م', 'ا', 'ل', 'ك', 'ا', 'ل', 'أ', 'ش', 'ت', 'ر', 'ب', 'ث', 'ج', 'ح', 'خ', 'د', 'ز', 'ز'] }, { answer: 'نفيسة', question: 'كوني زيارة؟', image: 'https://zahabapps.store/games/maqamaat_img/51.png', letters: ['ن', 'ف', 'ي', 'س', 'ة', 'ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ'] }, { answer: 'مخلفات', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/52.png', letters: ['م', 'خ', 'ل', 'ف', 'ا', 'ت', 'ر', 'ب', 'ث', 'ج', 'ح', 'د'] }, { answer: 'رقية', question: 'كوني زيارة؟', image: 'https://zahabapps.store/games/maqamaat_img/53.png', letters: ['ر', 'ق', 'ي', 'ة', 'د', 'ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ'] }, { answer: 'زينب', question: 'كوني زيارة؟', image: 'https://zahabapps.store/games/maqamaat_img/54.png', letters: ['ز', 'ي', 'ن', 'ب', 'ا', 'ت', 'ث', 'ج', 'ح', 'خ', 'د', 'ذ'] }, { answer: 'باب النصر', question: 'باب نو نام؟', image: 'https://zahabapps.store/games/maqamaat_img/55.png', letters: ['ب', 'ا', 'ب', 'ا', 'ل', 'ن', 'ص', 'ر', 'ض', 'ط', 'ظ', 'ع'] }, { answer: 'باب الفتوح', question: 'باب نو نام؟', image: 'https://zahabapps.store/games/maqamaat_img/56.png', letters: ['ب', 'ا', 'ب', 'ا', 'ل', 'ف', 'ت', 'و', 'ح', 'خ', 'د', 'ز'] }, { answer: 'ابن طولون', question: 'كئي جامع؟', image: 'https://zahabapps.store/games/maqamaat_img/57.png', letters: ['ا', 'ب', 'ن', 'ط', 'و', 'ل', 'و', 'ن', 'م', 'ه', 'ي', 'ك'] }, { answer: 'امير الجامعة', question: 'كوني زيارة؟', image: 'https://zahabapps.store/games/maqamaat_img/58.png', letters: ['ا', 'م', 'ي', 'ر', 'ا', 'ل', 'ج', 'ا', 'م', 'ع', 'ة', 'ف'] }, { answer: 'مرول', question: 'كئي جگه؟', image: 'https://zahabapps.store/games/maqamaat_img/59.png', letters: ['م', 'ر', 'و', 'ل', 'ا', 'ب', 'ت', 'ث', 'ج', 'ح', 'خ', 'د'] }, { answer: 'أحمدآباد', question: 'كئي جگه؟', info: 'Maulaya Adam QS', image: 'https://zahabapps.store/games/maqamaat_img/60.png', letters: ['أ', 'ح', 'م', 'د', 'آ', 'ب', 'ا', 'د', 'ر', 'ز', 'س', 'ش'] }, { answer: 'WAKANER', question: 'كئي جگه؟', info: 'Moulai Looqmanji Saheb (QS)', image: 'https://zahabapps.store/games/maqamaat_img/61.png', letters: ['W', 'A', 'K', 'A', 'N', 'E', 'R', 'B', 'C', 'D', 'F', 'G'] }, { answer: 'AMRELI', question: 'كئي جگه؟', info: 'Syedi Jafferjee Jivaji Saheb (QS)', image: 'https://zahabapps.store/games/maqamaat_img/62.png', letters: ['A', 'M', 'R', 'E', 'L', 'I', 'B', 'C', 'D', 'F', 'G', 'H'] }, { answer: 'KALAVAD', question: 'كئي جگه؟', info: 'Moulai Ganipeer', image: 'https://zahabapps.store/games/maqamaat_img/63.png', letters: ['K', 'A', 'L', 'A', 'V', 'A', 'D', 'B', 'C', 'E', 'F', 'G'] }, { answer: 'KAMLAPUR', question: 'كئي جگه؟', info: 'Syedi Aliji Shaheed (QS.)', image: 'https://zahabapps.store/games/maqamaat_img/64.png', letters: ['K', 'A', 'M', 'L', 'A', 'P', 'U', 'R', 'B', 'C', 'D', 'E'] }, { answer: 'UMRETH', question: 'كئي جگه؟', info: 'Syedi Miyaji Bin Taj Saheb (Q.R.)', image: 'https://zahabapps.store/games/maqamaat_img/65.png', letters: ['U', 'M', 'R', 'E', 'T', 'H', 'A', 'B', 'C', 'D', 'F', 'G'] }, { answer: 'SHAJAPUR', question: 'كئي جگه؟', info: 'Syedi Yusufkhan Saheb (QS)', image: 'https://zahabapps.store/games/maqamaat_img/66.png', letters: ['S', 'H', 'A', 'J', 'A', 'P', 'U', 'R', 'B', 'C', 'D', 'E'] }, { answer: 'RANPUR', question: 'كئي جگه؟', info: 'Moulai Sheikhpeer Saheb (QS)', image: 'https://zahabapps.store/games/maqamaat_img/67.png', letters: ['R', 'A', 'N', 'P', 'U', 'R', 'B', 'C', 'D', 'E', 'F', 'G'] }, { answer: 'SELAVI', question: 'كئي جگه؟', info: 'Mullah Nooh bhaisaheb (QS)', image: 'https://zahabapps.store/games/maqamaat_img/68.png', letters: ['S', 'E', 'L', 'A', 'V', 'I', 'B', 'C', 'D', 'F', 'G', 'H'] }, { answer: 'PISAWADA', question: 'كئي جگه؟', info: 'Maulai Burhanuddin Saheb (QS)', image: 'https://zahabapps.store/games/maqamaat_img/69.png', letters: ['P', 'I', 'S', 'A', 'W', 'A', 'D', 'A', 'B', 'C', 'E', 'F'] }, { answer: 'AURANGABAD', question: 'كئي جگه؟', info: 'Maulai Najam Khan RA', image: 'https://zahabapps.store/games/maqamaat_img/70.png', letters: ['A', 'U', 'R', 'A', 'N', 'G', 'A', 'B', 'A', 'D', 'E', 'F'] }, { answer: 'PRATAPGARH', question: 'كئي جگه؟', info: 'Kakaji Saheb Mulla Eesa bhaisaheb(QS)', image: 'https://zahabapps.store/games/maqamaat_img/71.png', letters: ['P', 'R', 'A', 'T', 'A', 'P', 'G', 'A', 'R', 'H', 'C', 'D'] }, { answer: 'MUNDRA', question: 'كئي جگه؟', info: 'SYEDA RAANI BEN SAHEBA BINTE SYEDNA ISMAIL BADRUDDIN (38TH DAI)', image: 'https://zahabapps.store/games/maqamaat_img/72.png', letters: ['M', 'U', 'N', 'D', 'R', 'A', 'B', 'C', 'E', 'F', 'G', 'H'] }, { answer: 'DHROL', question: 'كئي جگه؟', info: 'Ganje Shohada QR', image: 'https://zahabapps.store/games/maqamaat_img/73.png', letters: ['D', 'H', 'R', 'O', 'L', 'A', 'B', 'C', 'E', 'F', 'G', 'I'] }, { answer: 'GAZA', question: 'كئي جگه؟', info: 'Moulana Hashim SA', image: 'https://zahabapps.store/games/maqamaat_img/74.png', letters: ['G', 'A', 'Z', 'A', 'B', 'C', 'D', 'E', 'F', 'H', 'I', 'J'] }, { answer: 'KUFA', question: 'كئي جگه؟', info: 'Muslim Bin Aqeel RA', image: 'https://zahabapps.store/games/maqamaat_img/75.png', letters: ['K', 'U', 'F', 'A', 'B', 'C', 'D', 'E', 'G', 'H', 'I', 'J'] }, { answer: 'KUFA', question: 'كئي جگه؟', info: 'Hani Bin Urwa RA', image: 'https://zahabapps.store/games/maqamaat_img/76.png', letters: ['K', 'U', 'F', 'A', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S'] }, { answer: 'KUFA', question: 'كئي جگه؟', info: 'Maytham al-tammar', image: 'https://zahabapps.store/games/maqamaat_img/77.png', letters: ['K', 'U', 'F', 'A', 'T', 'V', 'W', 'X', 'Y', 'Z', 'B', 'C'] }, ]; const allLevels: GameLevel[] = definedLevels.map((level, index) => ({ ...level, id: index + 1, })); const TOTAL_LEVEL_COUNT = allLevels.length; const LEVELS: GameLevel[] = allLevels; const PAYWALL_LEVEL_ID = 15; const ACHIEVEMENTS: Achievement[] = [ { id: 'talib', name: 'Talib', description: 'Complete your first level.', check: (stats: PlayerStats) => stats.completedLevelIds.length >= 1, icon: '👣' }, { id: 'mujtahid', name: 'Mujtahid', description: 'Complete 5 levels.', check: (stats: PlayerStats) => stats.completedLevelIds.length >= 5, icon: '📜' }, { id: 'samajhnaar', name: 'Samajhnaar', description: 'Complete 15 levels.', check: (stats: PlayerStats) => stats.completedLevelIds.length >= 15, icon: '💡' }, { id: 'aalim', name: 'Aalim', description: 'Complete 25 levels.', check: (stats: PlayerStats) => stats.completedLevelIds.length >= 25, icon: '📖' }, { id: 'aarif', name: 'Aarif', description: 'Complete 53 levels.', check: (stats: PlayerStats) => stats.completedLevelIds.length >= 53, icon: '✒️' }, { id: 'mahir', name: 'Mahir', description: 'Complete 72 levels.', check: (stats: PlayerStats) => stats.completedLevelIds.length >= 72, icon: '🧠' }, { id: 'ustaad', name: 'Ustaad', description: 'Complete all levels.', check: (stats: PlayerStats) => stats.completedLevelIds.length >= TOTAL_LEVEL_COUNT, icon: '🏆' }, ]; // ================================================================================= // AUDIO // ================================================================================= const audioContext = typeof window !== 'undefined' ? new (window.AudioContext || (window as any).webkitAudioContext)() : null; type SoundType = 'click' | 'place' | 'remove' | 'incorrect' | 'correct' | 'hint' | 'achievement'; const playNote = (frequency: number, type: OscillatorType, duration: number, volume: number = 0.2) => { if (!audioContext) return; const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.type = type; oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); gainNode.gain.setValueAtTime(volume, audioContext.currentTime); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + duration); } const sounds: { [key in SoundType]: () => void } = { click: () => playNote(440, 'sine', 0.05, 0.2), place: () => playNote(660, 'sine', 0.05, 0.2), remove: () => playNote(330, 'sine', 0.05, 0.2), hint: () => { if (!audioContext) return; const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.type = 'sine'; oscillator.frequency.setValueAtTime(880, audioContext.currentTime); gainNode.gain.setValueAtTime(0.2, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.0001, audioContext.currentTime + 0.2); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + 0.2); }, incorrect: () => { if (!audioContext) return; playNote(392.00, 'sine', 0.1, 0.25); // G4 setTimeout(() => playNote(329.63, 'sine', 0.15, 0.25), 120); // E4 }, correct: () => { if (!audioContext) return; playNote(523.25, 'sine', 0.08, 0.3); // C5 setTimeout(() => playNote(659.25, 'sine', 0.08, 0.3), 100); // E5 setTimeout(() => playNote(783.99, 'sine', 0.08, 0.3), 200); // G5 setTimeout(() => playNote(1046.50, 'triangle', 0.12, 0.3), 300); // C6 (sparkle) }, achievement: () => { if (!audioContext) return; playNote(523.25, 'triangle', 0.1, 0.25); // C5 setTimeout(() => playNote(783.99, 'triangle', 0.1, 0.25), 100); // G5 setTimeout(() => playNote(1046.50, 'triangle', 0.2, 0.25), 200); // C6 }, }; const playSound = (sound: SoundType) => { if (audioContext && audioContext.state === 'suspended') { audioContext.resume(); } if (sounds[sound]) { sounds[sound](); } }; // ================================================================================= // UTILS // ================================================================================= const formatSecondsToTime = (totalSeconds: number): string => { const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; let timeString = ""; if (hours > 0) timeString += `${hours}h `; if (minutes > 0 || hours > 0) timeString += `${minutes}m `; timeString += `${seconds}s`; return timeString.trim() || "0s"; }; const preloadImages = (urls: string[]): Promise => { const promises = urls.map(src => { return new Promise((resolve) => { const img = new Image(); img.src = src; img.onload = () => resolve(); img.onerror = () => resolve(); }); }); return Promise.all(promises); }; const shuffleArray = (array: T[]): T[] => { const newArray = [...array]; for (let i = newArray.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [newArray[i], newArray[j]] = [newArray[j], newArray[i]]; } return newArray; }; // ================================================================================= // HOOKS // ================================================================================= const useFullScreen = () => { const [isFullScreen, setIsFullScreen] = useState(() => { if (typeof document === 'undefined') return false; return !!document.fullscreenElement; }); const toggleFullScreen = useCallback(() => { if (typeof document === 'undefined') return; if (!document.fullscreenElement) { document.documentElement.requestFullscreen().catch((err) => { console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`); }); } else { if (document.exitFullscreen) { document.exitFullscreen(); } } }, []); useEffect(() => { if (typeof document === 'undefined') return; const handleFullScreenChange = () => { setIsFullScreen(!!document.fullscreenElement); }; document.addEventListener('fullscreenchange', handleFullScreenChange); return () => { document.removeEventListener('fullscreenchange', handleFullScreenChange); }; }, []); return { isFullScreen, toggleFullScreen }; }; const usePlayerStats = () => { const [stats, setStats] = useState(() => { try { const savedStats = localStorage.getItem('maqamaat_playerStats'); if (savedStats) { const parsed = JSON.parse(savedStats); return { completedLevelIds: [], totalLettersRevealed: 0, achievements: [], totalPlayTimeSeconds: 0, achievementUnlockTimes: {}, hasUnlockedFullGame: false, ...parsed, }; } } catch (error) { console.error('Error loading player stats:', error); } return { completedLevelIds: [], totalLettersRevealed: 0, achievements: [], totalPlayTimeSeconds: 0, achievementUnlockTimes: {}, hasUnlockedFullGame: false, }; }); const [newlyUnlocked, setNewlyUnlocked] = useState([]); useEffect(() => { try { localStorage.setItem('maqamaat_playerStats', JSON.stringify(stats)); } catch (error) { console.error('Error saving player stats:', error); } }, [stats]); const getNewAchievements = (currentStats: PlayerStats): Achievement[] => { const newAchievements: Achievement[] = []; ACHIEVEMENTS.forEach(achievement => { const isAlreadyUnlocked = currentStats.achievements.includes(achievement.id); if (!isAlreadyUnlocked && achievement.check(currentStats)) { newAchievements.push(achievement); } }); return newAchievements; }; const completeLevel = useCallback((levelId: number) => { setStats(prevStats => { if (prevStats.completedLevelIds.includes(levelId)) { return prevStats; } const intermediateStats = { ...prevStats, completedLevelIds: [...prevStats.completedLevelIds, levelId].sort((a, b) => a - b), }; const newAchievements = getNewAchievements(intermediateStats); if (newAchievements.length > 0) { setNewlyUnlocked(newAchievements); const newAchievementUnlockTimes = { ...intermediateStats.achievementUnlockTimes }; newAchievements.forEach(a => { newAchievementUnlockTimes[a.id] = intermediateStats.totalPlayTimeSeconds || 0; }); return { ...intermediateStats, achievements: [...intermediateStats.achievements, ...newAchievements.map(a => a.id)], achievementUnlockTimes: newAchievementUnlockTimes, }; } return intermediateStats; }); }, []); const addRevealedLetters = useCallback((count: number) => { if (count === 0) return; setStats(prevStats => { const intermediateStats = { ...prevStats, totalLettersRevealed: (prevStats.totalLettersRevealed || 0) + count }; const newAchievements = getNewAchievements(intermediateStats); if (newAchievements.length > 0) { setNewlyUnlocked(newAchievements); const newAchievementUnlockTimes = { ...intermediateStats.achievementUnlockTimes }; newAchievements.forEach(a => { newAchievementUnlockTimes[a.id] = intermediateStats.totalPlayTimeSeconds || 0; }); return { ...intermediateStats, achievements: [...intermediateStats.achievements, ...newAchievements.map(a => a.id)], achievementUnlockTimes: newAchievementUnlockTimes, }; } return intermediateStats; }); }, []); const clearNewlyUnlocked = useCallback(() => { setNewlyUnlocked([]); }, []); const addPlayTime = useCallback((seconds: number) => { setStats(prevStats => ({ ...prevStats, totalPlayTimeSeconds: (prevStats.totalPlayTimeSeconds || 0) + seconds, })); }, []); const unlockFullGame = useCallback(() => { setStats(prevStats => ({ ...prevStats, hasUnlockedFullGame: true, })); }, []); return { stats, completeLevel, addRevealedLetters, newlyUnlocked, clearNewlyUnlocked, addPlayTime, unlockFullGame }; }; // ================================================================================= // UI COMPONENTS // ================================================================================= const LockIcon: React.FC = () => ( ); const ProfileIcon: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => ( ); const ShareIcon: React.FC = () => ( ); const RefreshIcon: React.FC<{ className?: string }> = ({ className = "h-6 w-6" }) => ( ); const StatCard: React.FC<{ label: string; value: number | string }> = ({ label, value }) => (

{value}

{label}

); declare global { interface Window { adsbygoogle: any[]; } } const AdsenseAd: React.FC = () => { useEffect(() => { try { (window.adsbygoogle = window.adsbygoogle || []).push({}); } catch (e) { console.error('Adsense error:', e); } }, []); return ( ); }; // ================================================================================= // MODAL COMPONENTS // ================================================================================= const CorrectAnswerModal: React.FC<{ isOpen: boolean; level: GameLevel; onContinue: () => void; }> = ({ isOpen, level, onContinue }) => { if (!isOpen) return null; const answerLength = level.answer.length; let answerFontSize = 'text-4xl'; if (answerLength > 22) { answerFontSize = 'text-xl'; } else if (answerLength > 18) { answerFontSize = 'text-2xl'; } else if (answerLength > 14) { answerFontSize = 'text-3xl'; } const isRTL = /[\u0600-\u06FF]/.test(level.answer); return (

Well Done!

{level.answer}
{level.answer.split(' ').map((word, wordIndex) => { if (word === '') return null; const animationDelay = `${wordIndex * 0.15}s`; return ( {word} ); })}
{level.info && (

{level.info}

)}
); }; const AchievementModal: React.FC<{ achievement: Achievement; stats: PlayerStats; onDismiss: () => void; }> = ({ achievement, stats, onDismiss }) => { const [isSharing, setIsSharing] = useState(false); const handleShare = async () => { playSound('click'); if (isSharing) return; setIsSharing(true); const solvedCount = stats.completedLevelIds.length; const timeString = formatSecondsToTime(stats.totalPlayTimeSeconds || 0); const link = window.location.href; const message = `I unlocked the "${achievement.name}"\nI have solved ${solvedCount} Maqamaat in ${timeString}. Join me in this challenge ${link}`; try { if (navigator.share) { await navigator.share({ title: 'Maqamaat Muqaddasah Achievement!', text: message }); } else if (navigator.clipboard) { await navigator.clipboard.writeText(message); alert('Achievement message copied to clipboard!'); } else { alert("Sharing is not supported on your device."); } } catch (error) { console.error("Failed to share achievement:", error); } finally { setIsSharing(false); } }; const handleContinue = () => { playSound('click'); onDismiss(); }; return (
{achievement.icon}

{achievement.name}

{achievement.description}

); }; const OnboardingModal: React.FC<{ isOpen: boolean; onClose: () => void; title: string; message: string; }> = ({ isOpen, onClose, title, message }) => { if (!isOpen) return null; const handleClose = () => { playSound('click'); onClose(); }; return (
e.stopPropagation()}>

{title}

{message}

); }; // ================================================================================= // GAME COMPONENTS // ================================================================================= const TiledImage: React.FC<{ imageUrl: string; onTileRemove: (index: number) => void; removedTiles: Set; gridSize?: number; maxRemoved?: number; }> = ({ imageUrl, onTileRemove, removedTiles, gridSize = 4, maxRemoved = 6 }) => { const [isImageLoaded, setIsImageLoaded] = useState(false); useEffect(() => { const img = new Image(); img.src = imageUrl; img.onload = () => setIsImageLoaded(true); }, [imageUrl]); const handleTileClick = (index: number) => { if (removedTiles.has(index) || removedTiles.size >= maxRemoved) { return; } onTileRemove(index); }; const totalTiles = gridSize * gridSize; const tiles = Array.from({ length: totalTiles }, (_, i) => i); const canRemoveMore = removedTiles.size < maxRemoved; return (
{!isImageLoaded &&
}
{tiles.map(index => { const isRemoved = removedTiles.has(index); const isDisabled = isRemoved || !canRemoveMore; let tileClass = 'transition-opacity duration-300 ease-in-out '; if (isRemoved) { tileClass += 'opacity-0 pointer-events-none'; } else { tileClass += 'bg-[#008080] '; if (isDisabled) { tileClass += 'cursor-not-allowed'; } else { tileClass += 'hover:bg-[#00695C] cursor-pointer'; } } return (
{removedTiles.size} / {maxRemoved}
); }; const GameBoard: React.FC<{ level: GameLevel, onLevelComplete: () => void, onLettersRevealed: (count: number) => void }> = ({ level, onLevelComplete, onLettersRevealed }) => { const isRTL = /[\u0600-\u06FF]/.test(level.answer); const processedAnswer = isRTL ? level.answer : level.answer.toUpperCase(); const answerForLogic = processedAnswer.replace(/\s/g, '').replace(/[\u064B-\u0652\u0670]/g, ''); const loadState = (): SavedGameState | null => { const savedStateRaw = localStorage.getItem('maqamaat_gameState'); if (!savedStateRaw) return null; try { const savedState: SavedGameState = JSON.parse(savedStateRaw); if (savedState.levelId === level.id) return savedState; } catch (e) { console.error("Failed to parse saved game state:", e); } return null; }; const [guess, setGuess] = useState<(GuessSlot | null)[]>(() => loadState()?.guess || Array(answerForLogic.length).fill(null)); const [keyboardLetters, setKeyboardLetters] = useState(() => { const savedKeyboard = loadState()?.keyboardLetters; if (savedKeyboard) { return savedKeyboard; } const shuffled = shuffleArray(level.letters); return shuffled.map((letter, index) => ({ letter, id: index, used: false })); }); const [revealUsed, setRevealUsed] = useState(() => loadState()?.revealUsed || false); const [removedTiles, setRemovedTiles] = useState>(() => new Set(loadState()?.removedTiles || [])); const [status, setStatus] = useState<'playing' | 'correct' | 'incorrect'>('playing'); const [isSharing, setIsSharing] = useState(false); const [showOnboardingHint, setShowOnboardingHint] = useState(false); const [timeOnLevel, setTimeOnLevel] = useState(0); const maxTilesToRemove = 7; useEffect(() => { if (level.id === 1) { const hasSeenHint = localStorage.getItem('maqamaat_hasSeenFirstLevelHint'); if (!hasSeenHint) { setShowOnboardingHint(true); } } }, [level.id]); useEffect(() => { if (status === 'playing') { const interval = setInterval(() => { setTimeOnLevel(prev => prev + 1); }, 1000); return () => clearInterval(interval); } }, [status]); useEffect(() => { if (status === 'playing') { const stateToSave: SavedGameState = { levelId: level.id, guess, keyboardLetters, revealUsed, removedTiles: Array.from(removedTiles) }; localStorage.setItem('maqamaat_gameState', JSON.stringify(stateToSave)); } }, [guess, keyboardLetters, revealUsed, removedTiles, level.id, status]); useEffect(() => { if (status === 'playing' && guess.every(g => g !== null)) { if (guess.map(g => g!.letter).join('') === answerForLogic) { playSound('correct'); setStatus('correct'); } else { playSound('incorrect'); setStatus('incorrect'); } } }, [guess, answerForLogic, status]); useEffect(() => { if (status === 'incorrect') { const timer = setTimeout(() => { setGuess(Array(answerForLogic.length).fill(null)); setKeyboardLetters(prev => prev.map(key => ({ ...key, used: false }))); setStatus('playing'); }, 1000); return () => clearTimeout(timer); } }, [status, answerForLogic.length]); const handleKeyboardClick = (clickedLetter: string, clickedId: number) => { const letterState = keyboardLetters.find(l => l.id === clickedId); if (status !== 'playing' || letterState?.used) return; const firstEmptyIndex = guess.findIndex(g => g === null); if (firstEmptyIndex !== -1) { playSound('place'); const newGuess = [...guess]; newGuess[firstEmptyIndex] = { letter: clickedLetter, keyboardId: clickedId }; setGuess(newGuess); setKeyboardLetters(prev => prev.map(key => (key.id === clickedId ? { ...key, used: true } : key))); } }; const handleAnswerClick = (guessIndex: number) => { if (status !== 'playing') return; const guessSlot = guess[guessIndex]; if (guessSlot) { playSound('remove'); const newGuess = [...guess]; newGuess[guessIndex] = null; setGuess(newGuess); setKeyboardLetters(prev => prev.map(key => (key.id === guessSlot.keyboardId ? { ...key, used: false } : key))); } }; const handleRevealClick = () => { if (revealUsed || status !== 'playing') return; // 1. Find all potential reveals by matching empty slots to available keyboard letters const availableKeyboard = keyboardLetters.filter(k => !k.used); const emptyGuessIndices = guess.map((g, i) => (g === null ? i : -1)).filter(i => i !== -1); const possibleReveals: { guessIndex: number; keyboardId: number; letter: string }[] = []; for (const guessIndex of emptyGuessIndices) { const correctLetter = answerForLogic[guessIndex]; const keyboardMatchIndex = availableKeyboard.findIndex(k => k.letter === correctLetter); if (keyboardMatchIndex !== -1) { const keyboardLetter = availableKeyboard[keyboardMatchIndex]; possibleReveals.push({ guessIndex, keyboardId: keyboardLetter.id, letter: correctLetter }); // Temporarily remove from availableKeyboard to prevent using the same keyboard letter twice in one hint availableKeyboard.splice(keyboardMatchIndex, 1); } } if (possibleReveals.length === 0) return; // 2. Shuffle possibilities and pick up to 3 to apply const shuffledReveals = shuffleArray(possibleReveals); const revealsToApply = shuffledReveals.slice(0, 3); // 3. Apply the reveals to temporary state copies let tempGuess = [...guess]; const revealedKeyboardIds = new Set(revealsToApply.map(r => r.keyboardId)); for (const reveal of revealsToApply) { tempGuess[reveal.guessIndex] = { letter: reveal.letter, keyboardId: reveal.keyboardId }; } const tempKeyboard = keyboardLetters.map(k => revealedKeyboardIds.has(k.id) ? { ...k, used: true } : k ); // 4. Update the actual state playSound('hint'); onLettersRevealed(revealsToApply.length); setGuess(tempGuess); setKeyboardLetters(tempKeyboard); setRevealUsed(true); }; const handleAskFriend = async () => { if (isSharing) return; setIsSharing(true); playSound('click'); try { const shareText = `Mane aa Maqamaat solve karwama madad kari sako cho?\n\nQuestion: ${level.question || 'No question available'}\nLetters: ${level.letters.join(', ')}\nAnswer length: ${answerForLogic.length} letters\n\nAap bhi shamil thaiye aa activity ma: ${window.location.href}`; if (navigator.share) await navigator.share({ title: 'Help me solve this Maqamaat!', text: shareText }); else window.open(`https://wa.me/?text=${encodeURIComponent(shareText)}`, '_blank'); } catch (error) { if (error instanceof Error && error.name !== 'AbortError') alert("Could not share. Please try again."); } finally { setIsSharing(false); } }; const handleRevealAnswerClick = () => { if (timeOnLevel < 120 || status !== 'playing') return; playSound('correct'); setStatus('correct'); }; const answerLength = answerForLogic.length; let sizing = { box: 'w-10 h-10 text-xl', letterGap: 'gap-x-1', wordContainer: 'gap-y-2 gap-x-2', }; if (answerLength > 15) { // 16+ letters: Very small sizing = { box: 'w-6 h-6 text-sm', letterGap: 'gap-x-0.5', wordContainer: 'gap-y-1 gap-x-1', }; } else if (answerLength > 11) { // 12-15 letters: Small sizing = { box: 'w-7 h-7 text-base', letterGap: 'gap-x-1', wordContainer: 'gap-y-1 gap-x-1.5', }; } else if (answerLength > 8) { // 9-11 letters: Medium sizing = { box: 'w-8 h-8 text-lg', letterGap: 'gap-x-1', wordContainer: 'gap-y-2 gap-x-2', }; } const words = processedAnswer.split(' '); let cumulativeLetterIndex = 0; return (
{ setShowOnboardingHint(false); localStorage.setItem('maqamaat_hasSeenFirstLevelHint', 'true'); }} title="How to Play" message="Click and remove tiles to reveal the picture!" />

{level.question}

{ playSound('remove'); const newTiles = new Set(removedTiles); newTiles.add(index); setRemovedTiles(newTiles); }} maxRemoved={maxTilesToRemove} />
{words.map((word, wordIndex) => { const sanitizedWord = word.replace(/[\u064B-\u0652\u0670]/g, ''); if (sanitizedWord.length === 0) return null; const wordStartLetterIndex = cumulativeLetterIndex; const wordElement = (
{sanitizedWord.split('').map((_, letterInWordIndex) => { const guessIndex = wordStartLetterIndex + letterInWordIndex; const guessSlot = guess[guessIndex]; const borderColor = status === 'incorrect' ? 'border-red-500' : status === 'correct' ? 'border-green-500' : 'border-gray-500'; return ( ); })}
); cumulativeLetterIndex += sanitizedWord.length; return wordElement; })}
{timeOnLevel < 120 ? (
Reveal in
{120 - timeOnLevel}s
) : ( )}
{keyboardLetters.map(({ letter, id, used }) => ( ))}
); }; const LevelSelect: React.FC<{ levels: GameLevel[]; onSelectLevel: (level: GameLevel) => void; playerStats: PlayerStats; onShowProfile: () => void; }> = ({ levels, onSelectLevel, playerStats, onShowProfile }) => { const [currentPage, setCurrentPage] = useState(0); const levelsPerPage = 24; const pageCount = Math.ceil(levels.length / levelsPerPage); const highestUnlockedLevel = playerStats.completedLevelIds.length + 1; const handleNextPage = () => { playSound('click'); setCurrentPage(prev => Math.min(prev + 1, pageCount - 1)); }; const handlePrevPage = () => { playSound('click'); setCurrentPage(prev => Math.max(prev - 1, 0)); }; const handleRefresh = () => { playSound('click'); localStorage.removeItem('maqamaat_gameState'); window.location.reload(); }; const startIndex = currentPage * levelsPerPage; const currentLevels = levels.slice(startIndex, startIndex + levelsPerPage); const totalPlayableLevels = levels.filter(l => l.answer !== "قادم").length; const completionPercentage = totalPlayableLevels > 0 ? Math.round((playerStats.completedLevelIds.length / totalPlayableLevels) * 100) : 0; return (

Select a Level

Game Progress {completionPercentage}%
{currentLevels.map((level) => { const isBeyondPaywall = level.id > PAYWALL_LEVEL_ID && !playerStats.hasUnlockedFullGame; const isLockedByProgress = level.id > highestUnlockedLevel && level.id !== 1; const isLocked = isBeyondPaywall || isLockedByProgress; const isCompleted = playerStats.completedLevelIds.includes(level.id); let buttonClass = 'bg-[#008080] text-white shadow-lg border-b-4 border-[#004D40] hover:bg-[#00695C] active:scale-95'; if (isLocked) buttonClass = 'bg-[#F0F0F0] cursor-not-allowed'; else if (isCompleted) buttonClass = 'bg-[#2E7D32] text-white shadow-lg border-b-4 border-[#1B5E20] hover:bg-[#1B5E20] active:scale-95'; return ( ); })}
{pageCount > 1 && (
Page {currentPage + 1} of {pageCount}
)}
); }; const ProfileScreen: React.FC<{ stats: PlayerStats; onClose: () => void; }> = ({ stats, onClose }) => { const [sharingId, setSharingId] = useState(null); const handleShareAchievement = async (achievement: Achievement) => { playSound('click'); if (sharingId) return; setSharingId(achievement.id); const solvedCount = stats.completedLevelIds.length; const timeString = formatSecondsToTime(stats.totalPlayTimeSeconds || 0); const link = window.location.href; const message = `I unlocked the "${achievement.name}"\nI have solved ${solvedCount} Maqamaat in ${timeString}. Join me in this challenge ${link}`; try { if (navigator.share) { await navigator.share({ title: 'Maqamaat Muqaddasah Achievement!', text: message }); } else if (navigator.clipboard) { await navigator.clipboard.writeText(message); alert('Achievement message copied to clipboard!'); } else { alert("Sharing is not supported, but you're awesome!"); } } catch (error) { console.error('Sharing failed:', error); } finally { setSharingId(null); } }; const formattedPlayTime = () => { return formatSecondsToTime(stats.totalPlayTimeSeconds || 0); }; return (

My Profile

Statistics

Achievements

    {ACHIEVEMENTS.map(achievement => { const isUnlocked = stats.achievements.includes(achievement.id); const unlockMetric = stats.achievementUnlockTimes?.[achievement.id]; return (
  • {achievement.icon}

    {achievement.name}

    {achievement.description}

    {isUnlocked && unlockMetric !== undefined && (

    {unlockMetric > 1000000000000 ? `Unlocked on ${new Date(unlockMetric).toLocaleDateString()}` : `Unlocked at: ${formatSecondsToTime(unlockMetric)}` }

    )}
    {isUnlocked && ( )}
  • ); })}
); }; const MubarakMohannaScreen: React.FC<{ onBack: () => void; }> = ({ onBack }) => { return (

Mubarak Mohanna

Aap 15 levels paas kari chuka cho

); }; const GameEndScreen: React.FC<{ onBack: () => void; onShare: () => void; }> = ({ onBack, onShare }) => { return (
🎉

Mubarak Mohanna

You have completed all the levels successfully. We request you to visit again and do a refresh levels to get new levels when available.

Shukran and please share! Dua ma yaad

); }; const UncoverIcon = () => ( ); const SpellIcon = () => ( ); const WelcomeScreen: React.FC<{ onStart: () => void; }> = ({ onStart }) => { return (

اهلا وسهلا

Su aap ye Sagla Maqamaat Muqaddasah ni ziyarat kidi che?

Khuda Taala Aqa Moula TUS na saathe jaawu naseeb kare

Maqamaat Muqaddasah

How to Play

Uncover the Image

Tap tiles to reveal the hidden picture of a Maqamaat Muqaddasah.

Spell the Name

Use the letters provided to guess the name of the Maqam.

); }; // ================================================================================= // APP ROOT COMPONENT // ================================================================================= type Screen = 'welcome' | 'level_select' | 'playing' | 'profile' | 'unlock' | 'game_end'; const App: React.FC = () => { const [screen, setScreen] = useState(() => { if (typeof window !== 'undefined' && localStorage.getItem('maqamaat_hasSeenWelcomeScreen') === 'true') { return 'level_select'; } return 'welcome'; }); const [currentLevel, setCurrentLevel] = useState(null); const { stats, completeLevel, addRevealedLetters, newlyUnlocked, clearNewlyUnlocked, addPlayTime, unlockFullGame } = usePlayerStats(); const { isFullScreen, toggleFullScreen } = useFullScreen(); const [achievementQueue, setAchievementQueue] = useState([]); const playTimeIntervalRef = useRef(null); useEffect(() => { const shouldUnlock = new URLSearchParams(window.location.search).get('unlock') === 'true'; if (shouldUnlock && !stats.hasUnlockedFullGame) { unlockFullGame(); window.history.replaceState(null, '', window.location.pathname); } }, [stats.hasUnlockedFullGame, unlockFullGame]); const startPlayTimeTracking = useCallback(() => { if (playTimeIntervalRef.current) clearInterval(playTimeIntervalRef.current); playTimeIntervalRef.current = window.setInterval(() => addPlayTime(1), 1000); }, [addPlayTime]); const stopPlayTimeTracking = useCallback(() => { if (playTimeIntervalRef.current) { clearInterval(playTimeIntervalRef.current); playTimeIntervalRef.current = null; } }, []); useEffect(() => { if (screen === 'playing' && currentLevel) startPlayTimeTracking(); else stopPlayTimeTracking(); return () => stopPlayTimeTracking(); }, [screen, currentLevel, startPlayTimeTracking, stopPlayTimeTracking]); useEffect(() => { if (currentLevel) { const nextLevel = LEVELS.find(l => l.id === currentLevel.id + 1); if (nextLevel && nextLevel.answer !== "قادم") { const isBeyondPaywall = nextLevel.id > PAYWALL_LEVEL_ID && !stats.hasUnlockedFullGame; if (!isBeyondPaywall) { preloadImages([nextLevel.image]); } } } }, [currentLevel, stats.hasUnlockedFullGame]); useEffect(() => { if (newlyUnlocked.length > 0) { playSound('achievement'); setAchievementQueue(prev => [...prev, ...newlyUnlocked]); clearNewlyUnlocked(); } }, [newlyUnlocked, clearNewlyUnlocked]); useEffect(() => { if (screen === 'welcome' || screen === 'unlock' || screen === 'playing') return; const savedStateRaw = localStorage.getItem('maqamaat_gameState'); if (savedStateRaw) { try { const { levelId } = JSON.parse(savedStateRaw); const levelToLoad = LEVELS.find(l => l.id === levelId); if (levelToLoad) { const highestUnlockedLevel = stats.completedLevelIds.length + 1; const isPastPaywall = levelToLoad.id > PAYWALL_LEVEL_ID && !stats.hasUnlockedFullGame; const isLockedByProgress = levelToLoad.id > highestUnlockedLevel; if (!isPastPaywall && !isLockedByProgress) { setCurrentLevel(levelToLoad); setScreen('playing'); } else { localStorage.removeItem('maqamaat_gameState'); } } else { localStorage.removeItem('maqamaat_gameState'); } } catch (e) { localStorage.removeItem('maqamaat_gameState'); } } }, [screen, stats.completedLevelIds.length, stats.hasUnlockedFullGame]); const handleStartGame = () => { playSound('click'); localStorage.setItem('maqamaat_hasSeenWelcomeScreen', 'true'); setScreen('level_select'); }; const handleLevelSelect = (level: GameLevel) => { playSound('click'); setCurrentLevel(level); setScreen('playing'); }; const handleBackToMenu = () => { playSound('click'); setCurrentLevel(null); setScreen('level_select'); localStorage.removeItem('maqamaat_gameState'); }; const handleLevelComplete = () => { if (currentLevel) { completeLevel(currentLevel.id); localStorage.removeItem('maqamaat_gameState'); const nextLevel = LEVELS.find(level => level.id === currentLevel.id + 1); if (!nextLevel || nextLevel.answer === "قادم") { setScreen('game_end'); return; } const isNextLevelBeyondPaywall = nextLevel.id > PAYWALL_LEVEL_ID && !stats.hasUnlockedFullGame; if (isNextLevelBeyondPaywall) { setScreen('unlock'); } else { setCurrentLevel(nextLevel); } } }; const handleShareGame = async (isGameEnd = false) => { playSound('click'); const shareText = isGameEnd ? `Alhamdulillah, I've completed all the levels in Maqamaat Muqaddasah! Join me in this challenge. Dua ma yaad. ${window.location.href}` : `Salam, Maqamaat Muqaddasah ni activity zaroor try karjo. Ekdum maza aavse. aa link par jaiye ${window.location.href}`; try { if (navigator.share) await navigator.share({ title: "Maqamaat Muqaddasah", text: shareText }); else window.open(`https://wa.me/?text=${encodeURIComponent(shareText)}`, '_blank'); } catch (error) { console.log('Sharing failed:', error); } }; const handleToggleFullScreen = () => { playSound('click'); toggleFullScreen(); }; const renderScreen = () => { switch(screen) { case 'welcome': return ; case 'unlock': return ; case 'game_end': return handleShareGame(true)} />; case 'profile': return setScreen('level_select')} />; case 'playing': return currentLevel ? (

Level {currentLevel.id}

) : null; case 'level_select': default: return setScreen('profile')} />; } } const paddingClass = screen === 'welcome' || screen === 'game_end' ? '' : 'px-3 pt-2 pb-3'; const bgClass = screen === 'welcome' || screen === 'game_end' ? 'bg-[#FAF8F7]' : 'bg-[#FFFFFF]'; return (
{renderScreen()} {achievementQueue.length > 0 && setAchievementQueue(q => q.slice(1))} />}
); }; // ================================================================================= // RENDER APP // ================================================================================= const rootElement = document.getElementById('root'); if (rootElement) { const root = ReactDOM.createRoot(rootElement); root.render(); }