Commit 431518eb authored by Mahmoud Aglan's avatar Mahmoud Aglan

kokowawas

parent 6d88756f
<?php
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
require_once __DIR__ . '/../includes/supabase.php';
require_once __DIR__ . '/../includes/auth.php';
$token = requireAuth();
$userId = getUserId($token);
$method = $_SERVER['REQUEST_METHOD'];
$sdb = supabaseService();
if ($method === 'GET') {
$action = $_GET['action'] ?? 'history';
if ($action === 'history') {
$friendId = $_GET['friend_id'] ?? '';
if (!$friendId) jsonError('friend_id required');
// Find the friendship between these two users
$friendship = findFriendship($sdb, $userId, $friendId);
if (!$friendship) jsonError('Not friends');
$before = $_GET['before'] ?? null;
$limit = min(intval($_GET['limit'] ?? 50), 100);
$params = [
'friendship_id' => 'eq.' . $friendship['id'],
'select' => 'id,sender_id,content,message_type,metadata,created_at,read_at',
'order' => 'created_at.desc',
'limit' => $limit
];
if ($before) {
$params['created_at'] = 'lt.' . $before;
}
$messages = $sdb->get('friend_messages', $params);
if (!is_array($messages) || isset($messages['error'])) {
jsonResponse(['messages' => []]);
}
// Mark unread messages from friend as read
$sdb->update('friend_messages', ['read_at' => gmdate('c')], [
'friendship_id' => 'eq.' . $friendship['id'],
'sender_id' => 'eq.' . $friendId,
'read_at' => 'is.null'
]);
jsonResponse(['messages' => array_reverse($messages), 'friendship_id' => $friendship['id']]);
}
if ($action === 'unread') {
// Get unread counts per friendship
$friendships = $sdb->get('friendships', [
'or' => "(requester_id.eq.{$userId},addressee_id.eq.{$userId})",
'status' => 'eq.accepted',
'select' => 'id,requester_id,addressee_id'
]);
if (!is_array($friendships) || isset($friendships['error']) || empty($friendships)) {
jsonResponse(['unread' => []]);
}
$unreadCounts = [];
foreach ($friendships as $f) {
$friendId = $f['requester_id'] === $userId ? $f['addressee_id'] : $f['requester_id'];
$unread = $sdb->get('friend_messages', [
'friendship_id' => 'eq.' . $f['id'],
'sender_id' => 'eq.' . $friendId,
'read_at' => 'is.null',
'select' => 'id'
]);
$count = (is_array($unread) && !isset($unread['error'])) ? count($unread) : 0;
if ($count > 0) {
$unreadCounts[$friendId] = $count;
}
}
jsonResponse(['unread' => $unreadCounts]);
}
if ($action === 'recent') {
// Get recent conversations (last message per friend)
$friendships = $sdb->get('friendships', [
'or' => "(requester_id.eq.{$userId},addressee_id.eq.{$userId})",
'status' => 'eq.accepted',
'select' => 'id,requester_id,addressee_id'
]);
if (!is_array($friendships) || isset($friendships['error']) || empty($friendships)) {
jsonResponse(['conversations' => []]);
}
$conversations = [];
foreach ($friendships as $f) {
$friendId = $f['requester_id'] === $userId ? $f['addressee_id'] : $f['requester_id'];
$lastMsg = $sdb->get('friend_messages', [
'friendship_id' => 'eq.' . $f['id'],
'select' => 'id,sender_id,content,message_type,created_at,read_at',
'order' => 'created_at.desc',
'limit' => 1
]);
if (is_array($lastMsg) && !isset($lastMsg['error']) && !empty($lastMsg)) {
$conversations[] = [
'friend_id' => $friendId,
'friendship_id' => $f['id'],
'last_message' => $lastMsg[0]
];
}
}
// Sort by most recent message
usort($conversations, fn($a, $b) => strcmp($b['last_message']['created_at'], $a['last_message']['created_at']));
jsonResponse(['conversations' => $conversations]);
}
jsonError('Invalid action');
}
if ($method === 'POST') {
$input = getInput();
$action = $input['action'] ?? 'send';
if ($action === 'send') {
$friendId = $input['friend_id'] ?? '';
$content = trim($input['content'] ?? '');
$messageType = $input['message_type'] ?? 'text';
$metadata = $input['metadata'] ?? [];
if (!$friendId) jsonError('friend_id required');
if (!$content) jsonError('content required');
if (mb_strlen($content) > 500) jsonError('Message too long (max 500 chars)');
$friendship = findFriendship($sdb, $userId, $friendId);
if (!$friendship) jsonError('Not friends');
$msg = $sdb->insert('friend_messages', [
'friendship_id' => $friendship['id'],
'sender_id' => $userId,
'content' => $content,
'message_type' => $messageType,
'metadata' => !empty($metadata) ? $metadata : new \stdClass()
]);
if (isset($msg['error'])) jsonError($msg['error']);
jsonResponse(['message' => $msg[0] ?? $msg, 'success' => true]);
}
if ($action === 'mark-read') {
$friendId = $input['friend_id'] ?? '';
if (!$friendId) jsonError('friend_id required');
$friendship = findFriendship($sdb, $userId, $friendId);
if (!$friendship) jsonError('Not friends');
$sdb->update('friend_messages', ['read_at' => gmdate('c')], [
'friendship_id' => 'eq.' . $friendship['id'],
'sender_id' => 'eq.' . $friendId,
'read_at' => 'is.null'
]);
jsonResponse(['success' => true]);
}
jsonError('Invalid action');
}
jsonError('Method not allowed', 405);
function findFriendship($sdb, string $userId, string $friendId): ?array {
$rows = $sdb->get('friendships', [
'or' => "(and(requester_id.eq.{$userId},addressee_id.eq.{$friendId}),and(requester_id.eq.{$friendId},addressee_id.eq.{$userId}))",
'status' => 'eq.accepted',
'select' => 'id,requester_id,addressee_id',
'limit' => 1
]);
if (is_array($rows) && !isset($rows['error']) && !empty($rows)) {
return $rows[0];
}
return null;
}
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
header('Content-Type: application/json'); header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization'); header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; } if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
......
# سكريبت — العب | 3 دقائق
---
## COLD OPEN (0:00 – 0:08)
**[VISUAL]:** أسود. صوت زهر بيتقلب على خشب. بعدها صوت قطعة شطرنج بتتحط. بعدها صوت domino tile بيخبط.
**VO:**
*(صمت. الأصوات بتتكلم لوحدها.)*
---
## HOOK (0:08 – 0:30)
**[VISUAL]:** كلوز أب على إيدين — طفل بيحرك حصان شطرنج على تابلت. قطع بتزحلق smooth. ساعة بتعد. الطفل بيبتسم ابتسامة واحد عرف إنه كسب.
**VO:**
> في ناس شايفة إن الألعاب الذهنية حاجة قديمة.
> طب ما هي القديمة دي — هي اللي صنعت عقول العالم من 5000 سنة.
> الجديد بقى... إنك تحطها في إيد كل طفل في مصر.
> على الموبايل. بشكل يحببه فيها. ويخليه يرجعلها كل يوم.
---
## المنصة — أول مرة تشوفها (0:30 – 1:00)
**[VISUAL]:** screen recording حقيقي من المنصة — الـcarousel بيتقلب بين الألعاب — الطفل بيدوس "العب" — matchmaking بيشتغل — ماتش بيبدأ في 3 ثواني. بعدها split screen: شطرنج / دومينو / لودو — كلهم شغالين في نفس الوقت.
**VO:**
> ده العب.
> ثلاث ألعاب شغالين دلوقتي — شطرنج، دومينو، لودو — وفيه كمان في الطريق.
> من أول ما تفتح لحد ما تبدأ تلعب — ثواني. مفيش تسجيل معقد. مفيش إعلان بيقطعك.
> اللعب هنا شكله حلو — 60 فريم، سلس، يحسسك إنك بتلعب لعبة console مش website.
> وكل ماتش — سواء ضد صاحبك أو ضد AI بمستويات — بيتسجل. بيأثر في تصنيفك. بيحركك لقدام.
---
## مش لعبة وخلاص (1:00 – 1:30)
**[VISUAL]:** شاشة puzzles — طفل بيحل 3 ألغاز ورا بعض — rating بيطلع. بعدها: شاشة "خطط الشطرنج" (تعليم) — animation بتوضح fork. بعدها: XP bar بيتملي — level up — coins بتنزل — الطفل بيفتح achievement.
**VO:**
> جوا العب فيه نظام تعليم شطرنج مدمج.
> ألغاز يومية. خطط. تكتيكات. بتبدأ من الصفر ومفيش سقف.
> الطفل بيلعب وهو بيتعلم — ومش حاسس إنه في حصة.
> وبنجهز نفس الكلام لبقية الألعاب.
> فيه تصنيف. فيه مستويات. فيه achievements.
> يعني الطفل عنده سبب يرجع كل يوم — وكل يوم بيبقى أحسن من امبارح.
---
## البطولات — وده الموضوع الكبير (1:30 – 2:10)
**[VISUAL]:** bracket بطولة بيتملي live — شعار مدرسة على header البطولة — بعدها شعار جامعة — بعدها شعار شركة. أطفال في "نهائي" — split screen بيبيّن اللاعبين. كأس بيظهر. خريطة مصر بتنور نقطة نقطة.]
**VO:**
> العب فيها نظام بطولات كامل — مبني ومتجرب.
> يعني إيه؟ يعني أي مدرسة، أي جامعة، أي نادي، أي شركة، أي مركز شباب، أي حي سكني —
> يقدر يعمل بطولة رسمية على المنصة.
> القرعة أوتوماتيك. الجدول أوتوماتيك. النتايج أوتوماتيك.
> Swiss، knockout، arena — كل الأنظمة موجودة.
> ده مش plan. ده شغال. البنية التحتية جاهزة.
> ومصر — فيها آلاف المدارس والجامعات والأندية اللي محتاجة بالظبط حاجة زي كده.
> ومفيش حد تاني بيعملها.
---
## الأمان — سطرين وخلاص (2:10 – 2:25)
**[VISUAL]:** أيقونة درع — شاشة emotes (عبارات جاهزة مش chat مفتوح) — أيقونة report.
**VO:**
> آه وبالمناسبة — مفيش chat مفتوح مع غرباء.
> التواصل بعبارات جاهزة. فيه نظام إبلاغ. فيه مراقبة.
> بيئة نضيفة. الأهل يطمنوا.
---
## الصورة الكبيرة (2:25 – 2:50)
**[VISUAL]:** الأرقام بتظهر واحدة واحدة بأنيميشن — "4 ألعاب" — "38 module إدارة" — "نظام بطولات كامل" — "تطوير مصري 100%". بعدها: خريطة الوطن العربي بتنور. Text: "400 مليون. مفيش منافس محلي."]
**VO:**
> العب تطوير مصري بالكامل. مفيش سطر كود من بره.
> فيها نظام إدارة بـ38 module — بطولات، اقتصاد، محتوى، moderation — كل حاجة.
> دي مش MVP. دي منصة ناضجة.
> والمنطقة العربية — 400 مليون إنسان — مفيهاش منتج واحد محلي بيعمل اللي إحنا بنعمله.
> السوق مفتوح. والمنتج جاهز.
---
## CLOSER (2:50 – 3:00)
**[VISUAL]:** الشاشة بتغمق تدريجي. لوجو EL3AB بيفضل. تحته: "منصة الألعاب الذهنية المصرية." Particles ذهبية. ثم black.
**VO:**
> العب.
> الأطفال بتوعنا يستاهلوا أحسن من كده. وده — أحسن من كده.
**[SFX]:** قطعة شطرنج. خلاص.
---
## ملاحظات
| | |
|---|---|
| **Tone** | واحد بيعرض مشروعه — واثق، مش بيبيع. بيوريك الحاجة وخلاص. |
| **VO** | صوت راجل مصري 30s-40s. مش announcer. مش YouTuber. واحد عادي بيتكلم بثقة. |
| **Music** | Lo-fi ambient في الأول → builds subtle percussion في البطولات → drops quiet في الختام |
| **Pacing** | يسيب فراغات. مش كل ثانية عليها كلام. الصورة بتشتغل لوحدها أوقات. |
| **مفيش** | كلمة "استثمار"، "عائد"، "فرصة"، "نمو" — الأرقام بتتكلم لوحدها |
| **الرسالة اللي واصلة** | منتج كامل + سوق فاضي + scale جاهز + فريق بيعرف يعمل إيه = واحد هيكلمكم بعد الفيديو |
# سكريبت فيديو إعلاني — EL3AB (4 دقائق)
---
## SCENE 1 — المشكلة (0:00 – 0:25)
**[VISUAL]:** طفل قاعد على كنبة، عينيه على الموبايل، وشه ملوش تعبير. الأم بتبصله بقلق. مشهد تاني: مجموعة أطفال في مدرسة كل واحد في موبايله. مشهد تالت: أب بيحاول يكلم ابنه والابن مش سامعه.]
**VO (صوت راجل، مصري، هادي في الأول):**
> ولادنا بيقضوا ساعات كل يوم على الشاشة.
> ألعاب عنيفة. محتوى ملوش معنى. وقت بيضيع.
> وإحنا كأهالي... قلقانين. بس مش عارفين البديل إيه.
> لأن الحقيقة — مش هنقدر ناخد الموبايل من إيدهم.
> بس نقدر نغيّر اللي جواه.
---
## SCENE 2 — الحل (0:25 – 0:55)
**[VISUAL]:** الشاشة بتتحول من غامقة لذهبي. لوجو EL3AB بيظهر بأنيميشن — particles ذهبية. بعدها الكاميرا بتدخل جوا المنصة: رقعة شطرنج بتتكوّن قطعة قطعة — دومينو بيصطف — زهر لودو بيتقلب في الهوا.]
**VO (النبرة بتتحوّل — فيها حماس):**
> العب.
> أول منصة مصرية 100% للألعاب الذهنية.
> شطرنج. دومينو. لودو. وألعاب تانية في الطريق.
> منصة اتبنت من الصفر — بإيدين مصرية — عشان تدّي ولادنا مكان يفكّروا فيه.
> مش لعبة واحدة... ده عالم كامل.
---
## SCENE 3 — تجربة اللعب (0:55 – 1:25)
**[VISUAL]:** شاشة المنصة الحقيقية: carousel الألعاب — طفل بيختار شطرنج — بيدوس Play — الماتشميكنج بيلاقيله خصم — اللعبة بتبدأ. القطع بتتحرك بسلاسة 60fps. ساعة بتعد. الطفل بيفكر وبياخد قرار.]
**VO:**
> التجربة جوا العب مختلفة عن أي حاجة تانية.
> اللعبة بتبدأ في ثواني — مفيش تعقيد. مفيش إعلانات مزعجة.
> كل ماتش هو تحدّي حقيقي — ضد لاعب حقيقي أو ضد ذكاء اصطناعي بمستويات.
> الطفل بيتعلّم يركّز. يخطط. يستنى. ياخد قرار تحت ضغط.
> مهارات الحياة... من خلال لعبة.
---
## SCENE 4 — التعليم والتطوير (1:25 – 1:55)
**[VISUAL]:** شاشة puzzle — الطفل بيحل لغز شطرنج — أنيميشن "Correct!" مع نجوم. بعدها شاشة rating بيطلع. بعدها شاشة تعليمية فيها شرح خطة (fork مثلاً). XP bar بيتملي. Level up بأنيميشن كبيرة.]
**VO:**
> جوا العب، الطفل مش بس بيلعب... بيتعلّم ويتطوّر.
> نظام ألغاز شطرنج — كل يوم تحدّي جديد بمستواه.
> منهج تعليم مدمج — من أول القواعد الأساسية لحد الخطط المتقدمة.
> كل خطوة بتتقاس. كل تقدّم بيتسجّل.
> والطفل بيشوف نفسه بيتحسّن — بالأرقام.
> وبنجهّز محتوى تعليمي أكتر لكل الألعاب — مش بس شطرنج.
---
## SCENE 5 — البيئة الآمنة (1:55 – 2:20)
**[VISUAL]:** أيقونة درع حماية — شاشة emotes (بس عبارات مهذبة: "حلو!" "لعب جميل" "GG") — مفيش chat مفتوح — أب بيراجع profile ابنه — شاشة report — إعدادات parental.]
**VO:**
> والأهم من كل ده — العب بيئة آمنة.
> مفيش شات مفتوح مع غرباء. مفيش محتوى غير مناسب.
> التواصل بين اللاعبين بيكون بعبارات محددة ومهذبة.
> نظام إبلاغ فوري. مراقبة دايمة.
> الأهل يقدروا يطمنوا — ولادهم بيلعبوا في مكان نضيف.
> لأن إحنا بنبني ده لولادنا إحنا كمان.
---
## SCENE 6 — البطولات (2:20 – 3:00)
**[VISUAL]:** brackets بطولة بتتملي — شعار مدرسة (تخيلي) على بطولة — شعار جامعة على بطولة تانية — شعار شركة — كأس بيلمع — خريطة مصر عليها نقط مضيئة في محافظات مختلفة — أطفال في ماتش نهائي والجمهور بيشجع (split screen)]
**VO:**
> بس العب مش بس لعبة فردية.
> العب مجهّزة بنظام بطولات كامل — جاهز يستضيف أي بطولة رسمية.
> مدارس عايزة تعمل بطولة شطرنج بين طلابها؟ جاهز.
> جامعة عايزة كأس ألعاب ذهنية بين كلياتها؟ جاهز.
> شركة عايزة team building بطريقة مختلفة؟ جاهز.
> أندية. مراكز شباب. أحياء سكنية.
> القرعة. الجدول. الـbrackets. النتايج. كل حاجة أوتوماتيك.
> منصة واحدة لكل بطولات الألعاب التقليدية في مصر.
---
## SCENE 7 — الحجم والرؤية (3:00 – 3:30)
**[VISUAL]:** أرقام بتظهر بأنيميشن (عداد): "106 table في الداتابيز" — "4 ألعاب" — "38 module إدارة" — "كل المحافظات". بعدها timeline مستقبلي: ألعاب جديدة بتتضاف — أيقونة trivia — أيقونة backgammon. خريطة المنطقة العربية بتنور.]
**VO:**
> العب مش فكرة على ورق.
> ده نظام متكامل — شغّال دلوقتي.
> نظام إدارة كامل. بنية تحتية جاهزة للتوسع.
> النهاردة — مصر. بكرة — كل طفل في الوطن العربي يلاقي مكان يفكّر فيه.
> الألعاب الذهنية سوق بيكبر كل يوم.
> والمنطقة العربية — 400 مليون إنسان — مفيش حد بيخدمهم بمنتج من عندهم.
> لحد دلوقتي.
---
## SCENE 8 — الأثر (3:30 – 3:50)
**[VISUAL]:** مونتاج عاطفي: طفل كسب أول بطولة مدرسة — أب فخور بابنه — مدرّس بيشرح شطرنج على العب — أطفال من محافظات مختلفة بيلعبوا ضد بعض — ضحك — تركيز — لحظة فوز.]
**VO:**
> إحنا مش بنبني app.
> إحنا بنبني جيل بيفكّر.
> جيل اتعلّم إن الذكاء ممتع. إن التحدي حلو. إن المنافسة الشريفة بتبني الشخصية.
> تطوير مصري. لأولاد مصر. والمنطقة كلها.
---
## SCENE 9 — الختام (3:50 – 4:00)
**[VISUAL]:** لوجو EL3AB كبير في النص — تحته "منصة الألعاب الذهنية المصرية" — particles ذهبية بتتحرك — الشاشة حية. ثم fade to black مع صوت قطعة شطرنج بتتحط.]
**VO:**
> **العب.**
> عقول بتتبني. أبطال بتتصنع. ومستقبل بيتكتب.
**[SUPER على الشاشة]:** el3ab.com | العب — نمّي ذكاءك
**[SFX]:** صوت قطعة شطرنج بتتحط. Beat. صمت.]
---
## ملاحظات إنتاج
| عنصر | توصية |
|------|--------|
| **المدة** | 4:00 بالظبط |
| **الـ VO** | صوت راجل مصري — هادي في الأول، واثق وحماسي في النص، عاطفي في الآخر |
| **الموسيقى** | تبدأ piano خفيف (المشكلة) → orchestral بتتصاعد (الحل/البطولات) → emotional في الختام |
| **الإيقاع** | Scene 1 بطيء (مشكلة) — Scene 3-6 سريع (إثارة) — Scene 8-9 بطيء (عاطفة) |
| **اللون** | يبدأ رمادي/بارد (المشكلة) → يتحول لـ dark + gold (المنصة) |
| **مفيش** | كلمة "استثمار" أو "عائد" أو "revenue" أو أي لغة بيزنس مباشرة |
| **الرسالة الضمنية** | سوق ضخم + منتج شغال + بنية جاهزة + مفيش منافس عربي = فرصة واضحة |
---
## تقسيم الوقت
| الجزء | المدة | الهدف |
|-------|-------|-------|
| المشكلة | 25 ثانية | يحس المشاهد بالألم |
| الحل (العب) | 30 ثانية | الأمل — فيه بديل |
| تجربة اللعب | 30 ثانية | يشوف المنتج شغال |
| التعليم | 30 ثانية | القيمة التربوية |
| البيئة الآمنة | 25 ثانية | طمأنة الأهل |
| البطولات | 40 ثانية | الـscale والمؤسسات |
| الحجم والرؤية | 30 ثانية | الفرصة (ضمنياً) |
| الأثر + ختام | 30 ثانية | العاطفة والـcall to action |
# سكريبت فيديو إعلاني — EL3AB (دقيقتين)
---
## SCENE 1 — الافتتاحية (0:00 – 0:15)
**[VISUAL]:** شاشة سودا. صوت نوتيفيكيشن موبايل. أيد طفل بتمسك تابلت. الشاشة بتنور بلون دهبي. لوجو EL3AB بيظهر بأنيميشن.]
**VO (صوت راجل، مصري، حماسي بس مش مبالغ):**
> كل يوم، ملايين الأطفال والشباب بيقضوا ساعات على الموبايل...
> بس لو الساعات دي بتشغّل دماغهم؟ بتنمّي ذكاهم؟ بتعلّمهم يفكّروا؟
---
## SCENE 2 — المنصة (0:15 – 0:40)
**[VISUAL]:** مونتاج سريع: رقعة شطرنج بتتحرك عليها قطع بسلاسة — دومينو بيتحط على الطاولة — زهر لودو بيتقلب — أطفال مبتسمين بيلعبوا على تابلت. كل ده بأنيميشن الـ60fps بتاعت المنصة.]
**VO:**
> العب... أول منصة مصرية 100% للألعاب الذهنية.
> شطرنج. دومينو. لودو. وألعاب جاية كمان.
> مش مجرد لعبة — ده عالم كامل. تصنيف. بطولات. جوايز. تطوّر.
> كل ده في بيئة آمنة تماماً للأطفال والعيلة.
---
## SCENE 3 — التعليم (0:40 – 1:00)
**[VISUAL]:** شاشة puzzle شطرنج — طفل بيحل لغز — أنيميشن level up وXP bar بيتملي — شاشة تعليم خطط شطرنج.]
**VO:**
> جوا العب، الطفل مش بس بيلعب... بيتعلّم.
> نظام تعليم شطرنج مدمج — من أول القواعد لحد الخطط المتقدمة.
> كل خطوة محسوبة إنها تخلّي الطفل يحب يفكّر أكتر.
> وبنجهّز محتوى تعليمي أكبر لكل الألعاب.
---
## SCENE 4 — البطولات (1:00 – 1:25)
**[VISUAL]:** brackets بطولات بتتملي بأسماء — شعارات مدارس وجامعات (تخيلية) — كأس دهبي بيلمع — خريطة مصر عليها نقط مضيئة في كل محافظة.]
**VO:**
> العب مجهّزة إنها تستضيف بطولات رسمية.
> مدارس. جامعات. شركات. أندية. مراكز شباب. أحياء.
> أي جهة عايزة تعمل بطولة ألعاب ذهنية — العب بتوفّرلها كل حاجة:
> التنظيم. القرعة. الجدول. النتايج. البث المباشر.
> منصة واحدة لكل بطولات الألعاب التقليدية في مصر.
---
## SCENE 5 — الرؤية (1:25 – 1:50)
**[VISUAL]:** مونتاج: طفل بيكسب ماتش وبيحتفل — ranking بيطلع — أب وابنه بيلعبوا مع بعض — شاشة leaderboard فيها أسماء عربي — الأنيميشن الدهبية بتاعت rank-up.]
**VO:**
> إحنا بنبني حاجة أكبر من لعبة.
> بنبني المكان اللي كل طفل مصري يقدر فيه يكتشف ذكاءه.
> يتحدّى نفسه. يتحدّى صحابه. يتحدّى أبطال من كل المحافظات.
> تطوير مصري. فكر مصري. لأولادنا.
---
## SCENE 6 — الختام (1:50 – 2:00)
**[VISUAL]:** لوجو EL3AB كبير في النص — تحته "منصة الألعاب الذهنية المصرية" — الخلفية فيها particles دهبية بتتحرك — الشاشة حية مش ساكنة أبداً.]
**VO:**
> **العب.**
> عقول بتتبني. أبطال بتتصنع.
**[SUPER على الشاشة]:** el3ab.com | العب — نمّي ذكاءك
**[SFX]:** صوت قطعة شطرنج بتتحط. ثم صمت.]
---
## ملاحظات إنتاج
| عنصر | توصية |
|------|--------|
| **المدة** | 2:00 بالظبط |
| **الـ VO** | صوت راجل مصري — واثق، دافي، مش loud |
| **الموسيقى** | Epic orchestral خفيفة، بتتصاعد في scene 4 و5 |
| **الإيقاع** | قطعات سريعة في المونتاج، بطيئة في الختام |
| **اللون** | Dark theme مع gold accents (زي الـ design tokens بتوع المنصة) |
| **مفيش** | كلمة "استثمار" أو "عائد" أو أي لغة بيزنس مباشرة |
This diff is collapsed.
logof.png

85.4 KB

This diff is collapsed.
...@@ -3,8 +3,12 @@ import { mountTable } from './scenes/table.js'; ...@@ -3,8 +3,12 @@ import { mountTable } from './scenes/table.js';
import { mountBotSelect } from './scenes/bot-select.js'; import { mountBotSelect } from './scenes/bot-select.js';
import { mountTimeSelect } from './scenes/time-select.js'; import { mountTimeSelect } from './scenes/time-select.js';
import { mountQueue } from './scenes/queue.js'; import { mountQueue } from './scenes/queue.js';
import { mountLobby } from './scenes/lobby.js';
import { mountChallenge } from './scenes/challenge.js';
scene.register('play-table', mountTable); scene.register('play-table', mountTable);
scene.register('play-bot-select', mountBotSelect); scene.register('play-bot-select', mountBotSelect);
scene.register('play-time-select', mountTimeSelect); scene.register('play-time-select', mountTimeSelect);
scene.register('play-queue', mountQueue); scene.register('play-queue', mountQueue);
scene.register('game-lobby', mountLobby);
scene.register('challenge-friend', mountChallenge);
This diff is collapsed.
This diff is collapsed.
...@@ -39,9 +39,9 @@ export function mountTable(el) { ...@@ -39,9 +39,9 @@ export function mountTable(el) {
<!-- Quick actions row --> <!-- Quick actions row -->
<div id="daily-widget" style="display:flex;gap:8px;width:100%;max-width:340px;margin-bottom:16px;"> <div id="daily-widget" style="display:flex;gap:8px;width:100%;max-width:340px;margin-bottom:16px;">
<button class="quick-btn" id="btn-challenges"> <button class="quick-btn" id="btn-challenge-friend">
<span class="qb-icon" style="background:linear-gradient(135deg,#1e40af,#3b82f6);">${emoji('lightning', '⚡', 20)}</span> <span class="qb-icon" style="background:linear-gradient(135deg,#7c3aed,#a855f7);">${emoji('challenge_swords', '⚔️', 20)}</span>
<span class="qb-label">تحديات</span> <span class="qb-label">تحدّي صديق</span>
</button> </button>
<button class="quick-btn" id="btn-achievements"> <button class="quick-btn" id="btn-achievements">
<span class="qb-icon" style="background:linear-gradient(135deg,#854d0e,#ca8a04);">${emoji('trophy', '🏆', 20)}</span> <span class="qb-icon" style="background:linear-gradient(135deg,#854d0e,#ca8a04);">${emoji('trophy', '🏆', 20)}</span>
...@@ -228,9 +228,9 @@ export function mountTable(el) { ...@@ -228,9 +228,9 @@ export function mountTable(el) {
const menu = el.querySelector('#game-menu'); const menu = el.querySelector('#game-menu');
// Daily widget buttons // Daily widget buttons
el.querySelector('#btn-challenges')?.addEventListener('click', () => { el.querySelector('#btn-challenge-friend')?.addEventListener('click', () => {
audio.play('click'); audio.play('click');
scene.push('daily-challenges'); scene.push('challenge-friend');
}); });
el.querySelector('#btn-achievements')?.addEventListener('click', () => { el.querySelector('#btn-achievements')?.addEventListener('click', () => {
audio.play('click'); audio.play('click');
......
...@@ -2,7 +2,9 @@ import * as scene from '../../core/scene.js'; ...@@ -2,7 +2,9 @@ import * as scene from '../../core/scene.js';
import { mountFriends } from './scenes/friends.js'; import { mountFriends } from './scenes/friends.js';
import { mountNotifications } from './scenes/notifications.js'; import { mountNotifications } from './scenes/notifications.js';
import { mountActivity } from './scenes/activity.js'; import { mountActivity } from './scenes/activity.js';
import { mountChat } from './scenes/chat.js';
scene.register('friends', mountFriends); scene.register('friends', mountFriends);
scene.register('notifications', mountNotifications); scene.register('notifications', mountNotifications);
scene.register('activity-feed', mountActivity); scene.register('activity-feed', mountActivity);
scene.register('friend-chat', mountChat);
This diff is collapsed.
...@@ -124,14 +124,15 @@ async function checkInvites(el) { ...@@ -124,14 +124,15 @@ async function checkInvites(el) {
if (res.error) { btn.textContent = 'خطأ'; return; } if (res.error) { btn.textContent = 'خطأ'; return; }
audio.play('reward'); audio.play('reward');
juice.hapticSuccess(); juice.hapticSuccess();
// Navigate to game // Navigate to lobby then game
const inv = invites.find(i => i.match_id === btn.dataset.acceptInvite); const inv = invites.find(i => i.match_id === btn.dataset.acceptInvite);
scene.push('chess-game', { scene.push('game-lobby', {
mode: 'live',
matchId: res.match_id, matchId: res.match_id,
color: res.color, color: res.color,
gameKey: inv?.game_key || 'chess',
timeControl: inv?.time_control || 'rapid_10_0', timeControl: inv?.time_control || 'rapid_10_0',
isFriendly: true friendId: inv?.from_id,
isHost: false
}); });
} catch (e) { } catch (e) {
btn.textContent = 'فشل'; btn.textContent = 'فشل';
...@@ -333,7 +334,7 @@ async function loadOnline(content) { ...@@ -333,7 +334,7 @@ async function loadOnline(content) {
function renderFriendCard(f) { function renderFriendCard(f) {
return ` return `
<div class="friend-card" data-uid="${f.id}"> <div class="friend-card" data-uid="${f.id}" data-profile='${JSON.stringify({id:f.id, display_name:f.display_name, username:f.username, avatar_url:f.avatar_url, level:f.level, is_online:f.is_online}).replace(/'/g, '&#39;')}'>
<div class="friend-avatar"> <div class="friend-avatar">
${f.avatar_url ? `<img src="${f.avatar_url}">` : emoji('person', '👤', 18)} ${f.avatar_url ? `<img src="${f.avatar_url}">` : emoji('person', '👤', 18)}
${f.is_online ? '<div class="online-dot"></div>' : ''} ${f.is_online ? '<div class="online-dot"></div>' : ''}
...@@ -343,6 +344,7 @@ function renderFriendCard(f) { ...@@ -343,6 +344,7 @@ function renderFriendCard(f) {
<div style="font-size:11px;color:${f.is_online ? '#34D399' : '#64748b'};">${f.is_online ? 'متصل الآن' : 'غير متصل'}${f.level ? ` — مستوى ${f.level}` : ''}</div> <div style="font-size:11px;color:${f.is_online ? '#34D399' : '#64748b'};">${f.is_online ? 'متصل الآن' : 'غير متصل'}${f.level ? ` — مستوى ${f.level}` : ''}</div>
</div> </div>
<div class="friend-actions"> <div class="friend-actions">
<div class="friend-action" data-chat="${f.id}" title="محادثة" style="background:rgba(37,99,235,0.15);border-color:rgba(37,99,235,0.3);color:#3B82F6;">💬</div>
${f.is_online ? `<div class="friend-action" data-invite="${f.id}" title="تحدّي" style="background:rgba(228,172,56,0.15);border-color:rgba(228,172,56,0.3);color:#E4AC38;">${emoji('challenge_swords', '⚔️', 14)}</div>` : ''} ${f.is_online ? `<div class="friend-action" data-invite="${f.id}" title="تحدّي" style="background:rgba(228,172,56,0.15);border-color:rgba(228,172,56,0.3);color:#E4AC38;">${emoji('challenge_swords', '⚔️', 14)}</div>` : ''}
<div class="friend-action" data-remove="${f.id}" title="إزالة" style="font-size:11px;color:#64748b;">✕</div> <div class="friend-action" data-remove="${f.id}" title="إزالة" style="font-size:11px;color:#64748b;">✕</div>
</div> </div>
...@@ -351,8 +353,20 @@ function renderFriendCard(f) { ...@@ -351,8 +353,20 @@ function renderFriendCard(f) {
} }
function bindFriendActions(content) { function bindFriendActions(content) {
content.querySelectorAll('[data-chat]').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
audio.play('click');
const card = btn.closest('.friend-card');
let profile = null;
try { profile = JSON.parse(card?.dataset?.profile || '{}'); } catch(e) {}
scene.push('friend-chat', { friendId: btn.dataset.chat, profile });
});
});
content.querySelectorAll('[data-invite]').forEach(btn => { content.querySelectorAll('[data-invite]').forEach(btn => {
btn.addEventListener('click', () => { btn.addEventListener('click', (e) => {
e.stopPropagation();
audio.play('click'); audio.play('click');
juice.hapticLight(); juice.hapticLight();
const uid = btn.dataset.invite; const uid = btn.dataset.invite;
...@@ -473,18 +487,18 @@ function showInviteDialog(content, targetId, targetName) { ...@@ -473,18 +487,18 @@ function showInviteDialog(content, targetId, targetName) {
sendBtn.textContent = '✓ تم إرسال التحدي!'; sendBtn.textContent = '✓ تم إرسال التحدي!';
sendBtn.style.background = '#34D399'; sendBtn.style.background = '#34D399';
// Wait for opponent to accept — navigate to game // Navigate to lobby
setTimeout(() => { setTimeout(() => {
dialog.remove(); dialog.remove();
scene.push('chess-game', { scene.push('game-lobby', {
mode: 'live',
matchId: res.match_id, matchId: res.match_id,
color: res.color, color: res.color,
gameKey: selectedGame,
timeControl: selectedTc, timeControl: selectedTc,
isFriendly: true, friendId: targetId,
waitingForOpponent: true isHost: true
}); });
}, 1000); }, 800);
} catch (e) { } catch (e) {
sendBtn.textContent = 'فشل — حاول مرة أخرى'; sendBtn.textContent = 'فشل — حاول مرة أخرى';
sendBtn.disabled = false; sendBtn.disabled = false;
......
qr-code.png

7.66 KB

This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment