Commit e79d4439 authored by Mahmoud Aglan's avatar Mahmoud Aglan

feat: Live Tournaments Update — public tournament pages with 40 features

Full public-facing live tournament system:
- Public pages at /live/{slug} with no auth required
- 5 visual themes (default, neon, minimal, royal, arena)
- Live standings, pairings, bracket with auto-refresh polling
- Countdown timer, player roster, results feed, timeline
- Prize display, announcements feed, photo gallery
- QR code sharing, social share, embeddable widget
- SEO meta tags (OG, Twitter Cards, JSON-LD)
- Tournament ad slots with impression/click tracking
- Analytics tracking (views, sections, referrers)
- Admin config panel: visibility toggles, custom CSS, slug management
- Mobile responsive with dark/light mode support
- Print-optimized view

New DB tables: tournament_announcements, tournament_ad_slots,
tournament_page_views, tournament_media
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 853341b5
......@@ -370,6 +370,41 @@ return [
'organizations/{id}/spotlights/{spotId}/toggle' => ['module' => 'org-spotlights', 'action' => 'toggle'],
'organizations/{id}/spotlights/{spotId}/delete' => ['module' => 'org-spotlights', 'action' => 'delete'],
// Live Tournaments - Public (no auth)
'live/{id}' => ['module' => 'live-tournaments', 'action' => 'show'],
'live/{id}/embed' => ['module' => 'live-tournaments', 'action' => 'embed'],
'live/{id}/standings' => ['module' => 'live-tournaments', 'action' => 'standingsPage'],
'live/{id}/bracket' => ['module' => 'live-tournaments', 'action' => 'bracketPage'],
'live/{id}/players' => ['module' => 'live-tournaments', 'action' => 'playersPage'],
'live/{id}/print' => ['module' => 'live-tournaments', 'action' => 'printView'],
// Live Tournaments - Public API (no auth, JSON)
'api/live/{id}/data' => ['module' => 'live-tournaments', 'action' => 'apiData'],
'api/live/{id}/standings' => ['module' => 'live-tournaments', 'action' => 'apiStandings'],
'api/live/{id}/pairings' => ['module' => 'live-tournaments', 'action' => 'apiPairings'],
'api/live/{id}/bracket' => ['module' => 'live-tournaments', 'action' => 'apiBracket'],
'api/live/{id}/arena' => ['module' => 'live-tournaments', 'action' => 'apiArena'],
'api/live/{id}/announcements' => ['module' => 'live-tournaments', 'action' => 'apiAnnouncements'],
'api/live/{id}/results' => ['module' => 'live-tournaments', 'action' => 'apiResults'],
'api/live/{id}/track' => ['module' => 'live-tournaments', 'action' => 'apiTrack'],
// Live Tournaments - Admin (auth required)
'tournaments/{id}/live-settings' => ['module' => 'live-tournaments', 'action' => 'adminSettings'],
'tournaments/{id}/live-settings/update' => ['module' => 'live-tournaments', 'action' => 'adminUpdateSettings'],
'tournaments/{id}/live-settings/slug' => ['module' => 'live-tournaments', 'action' => 'adminUpdateSlug'],
'tournaments/{id}/live-settings/toggle' => ['module' => 'live-tournaments', 'action' => 'adminToggleLive'],
'tournaments/{id}/live-settings/announcements' => ['module' => 'live-tournaments', 'action' => 'adminAnnouncements'],
'tournaments/{id}/live-settings/announcements/store' => ['module' => 'live-tournaments', 'action' => 'adminStoreAnnouncement'],
'tournaments/{id}/live-settings/announcements/{annId}/delete' => ['module' => 'live-tournaments', 'action' => 'adminDeleteAnnouncement'],
'tournaments/{id}/live-settings/ads' => ['module' => 'live-tournaments', 'action' => 'adminAds'],
'tournaments/{id}/live-settings/ads/store' => ['module' => 'live-tournaments', 'action' => 'adminStoreAd'],
'tournaments/{id}/live-settings/ads/{adId}/toggle' => ['module' => 'live-tournaments', 'action' => 'adminToggleAd'],
'tournaments/{id}/live-settings/ads/{adId}/delete' => ['module' => 'live-tournaments', 'action' => 'adminDeleteAd'],
'tournaments/{id}/live-settings/media' => ['module' => 'live-tournaments', 'action' => 'adminMedia'],
'tournaments/{id}/live-settings/media/upload' => ['module' => 'live-tournaments', 'action' => 'adminUploadMedia'],
'tournaments/{id}/live-settings/media/{mediaId}/delete' => ['module' => 'live-tournaments', 'action' => 'adminDeleteMedia'],
'tournaments/{id}/live-settings/analytics' => ['module' => 'live-tournaments', 'action' => 'adminAnalytics'],
// Org Announcements
'organizations/{id}/announcements' => ['module' => 'org-announcements', 'action' => 'list'],
'organizations/{id}/announcements/create' => ['module' => 'org-announcements', 'action' => 'create'],
......
......@@ -34,6 +34,19 @@ class Router
return;
}
if ($module === 'live-tournaments' && !str_starts_with($action, 'admin')) {
$controllerPath = MODULES_PATH . "/live-tournaments/controller.php";
require_once $controllerPath;
$controller = new LiveTournamentsController();
if (!method_exists($controller, $action)) {
http_response_code(404);
View::render('errors/404');
return;
}
$controller->$action($params, $method);
return;
}
Auth::requireAuth();
$controllerPath = MODULES_PATH . "/{$module}/controller.php";
......
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $pageTitle ?? 'بطولة مباشرة' ?> — El3ab</title>
<?php if (!empty($metaTags)): ?>
<meta name="description" content="<?= htmlspecialchars($metaTags['description'] ?? '') ?>">
<meta property="og:title" content="<?= htmlspecialchars($metaTags['title'] ?? $pageTitle) ?>">
<meta property="og:description" content="<?= htmlspecialchars($metaTags['description'] ?? '') ?>">
<meta property="og:image" content="<?= htmlspecialchars($metaTags['image'] ?? '') ?>">
<meta property="og:type" content="website">
<meta property="og:url" content="<?= htmlspecialchars($metaTags['url'] ?? '') ?>">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?= htmlspecialchars($metaTags['title'] ?? $pageTitle) ?>">
<meta name="twitter:description" content="<?= htmlspecialchars($metaTags['description'] ?? '') ?>">
<meta name="twitter:image" content="<?= htmlspecialchars($metaTags['image'] ?? '') ?>">
<?php endif; ?>
<?php if (!empty($jsonLd)): ?>
<script type="application/ld+json"><?= json_encode($jsonLd, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?></script>
<?php endif; ?>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Arabic:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/modules/live-tournaments/assets/live.css">
<?php if (!empty($liveTheme) && $liveTheme !== 'default'): ?>
<link rel="stylesheet" href="/modules/live-tournaments/assets/themes/<?= htmlspecialchars($liveTheme) ?>.css">
<?php endif; ?>
<?php if (!empty($liveCustomCss)): ?>
<style><?= strip_tags($liveCustomCss) ?></style>
<?php endif; ?>
<?php if (!empty($isPrint)): ?>
<link rel="stylesheet" href="/modules/live-tournaments/assets/print.css">
<?php endif; ?>
</head>
<body class="live-page" data-theme="<?= htmlspecialchars($liveTheme ?? 'default') ?>">
<?= $content ?>
<script src="/modules/live-tournaments/assets/live.js"></script>
<?php if (!empty($loadCountdown)): ?>
<script src="/modules/live-tournaments/assets/countdown.js"></script>
<?php endif; ?>
<?php if (!empty($loadBracket)): ?>
<script src="/modules/live-tournaments/assets/bracket-public.js"></script>
<?php endif; ?>
<script src="/modules/live-tournaments/assets/sharing.js"></script>
<script src="/modules/live-tournaments/assets/analytics.js"></script>
</body>
</html>
-- Live Tournaments Update - Public tournament pages
-- Migration 004
ALTER TABLE el3ab_tournaments
ADD COLUMN IF NOT EXISTS slug TEXT UNIQUE,
ADD COLUMN IF NOT EXISTS live_enabled BOOLEAN DEFAULT false,
ADD COLUMN IF NOT EXISTS live_theme TEXT DEFAULT 'default',
ADD COLUMN IF NOT EXISTS live_custom_css TEXT,
ADD COLUMN IF NOT EXISTS live_visibility JSONB DEFAULT '{"standings":true,"pairings":true,"bracket":true,"players":true,"schedule":true,"prizes":true,"rules":true,"stats":true,"announcements":true,"gallery":true,"ticker":true}',
ADD COLUMN IF NOT EXISTS live_branding JSONB DEFAULT '{}',
ADD COLUMN IF NOT EXISTS view_count INT DEFAULT 0,
ADD COLUMN IF NOT EXISTS unique_visitors INT DEFAULT 0;
CREATE TABLE IF NOT EXISTS tournament_announcements (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
tournament_id UUID NOT NULL REFERENCES el3ab_tournaments(id) ON DELETE CASCADE,
title TEXT,
content TEXT NOT NULL,
type TEXT DEFAULT 'info' CHECK (type IN ('info','result','alert','commentary','milestone')),
is_pinned BOOLEAN DEFAULT false,
created_by TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS tournament_ad_slots (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
tournament_id UUID NOT NULL REFERENCES el3ab_tournaments(id) ON DELETE CASCADE,
position TEXT NOT NULL CHECK (position IN ('hero_top','hero_bottom','sidebar_top','sidebar_bottom','between_sections','footer','ticker')),
slot_type TEXT DEFAULT 'image' CHECK (slot_type IN ('image','html','script')),
content TEXT NOT NULL,
link_url TEXT,
alt_text TEXT,
is_active BOOLEAN DEFAULT true,
display_order INT DEFAULT 0,
impressions INT DEFAULT 0,
clicks INT DEFAULT 0,
starts_at TIMESTAMPTZ,
ends_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS tournament_page_views (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
tournament_id UUID NOT NULL REFERENCES el3ab_tournaments(id) ON DELETE CASCADE,
visitor_hash TEXT NOT NULL,
ip_country TEXT,
referrer TEXT,
page_section TEXT DEFAULT 'overview',
viewed_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE IF NOT EXISTS tournament_media (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
tournament_id UUID NOT NULL REFERENCES el3ab_tournaments(id) ON DELETE CASCADE,
media_type TEXT DEFAULT 'image' CHECK (media_type IN ('image','video_url')),
url TEXT NOT NULL,
thumbnail_url TEXT,
caption TEXT,
display_order INT DEFAULT 0,
is_featured BOOLEAN DEFAULT false,
uploaded_by TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_tournaments_slug ON el3ab_tournaments(slug) WHERE slug IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_tournaments_live ON el3ab_tournaments(live_enabled) WHERE live_enabled = true;
CREATE INDEX IF NOT EXISTS idx_announcements_tournament ON tournament_announcements(tournament_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_ad_slots_tournament ON tournament_ad_slots(tournament_id);
CREATE INDEX IF NOT EXISTS idx_page_views_tournament ON tournament_page_views(tournament_id, viewed_at DESC);
CREATE INDEX IF NOT EXISTS idx_tournament_media_tournament ON tournament_media(tournament_id);
-- RLS
ALTER TABLE tournament_announcements ENABLE ROW LEVEL SECURITY;
ALTER TABLE tournament_ad_slots ENABLE ROW LEVEL SECURITY;
ALTER TABLE tournament_page_views ENABLE ROW LEVEL SECURITY;
ALTER TABLE tournament_media ENABLE ROW LEVEL SECURITY;
CREATE POLICY "service_role_all_tournament_announcements" ON tournament_announcements FOR ALL TO service_role USING (true) WITH CHECK (true);
CREATE POLICY "service_role_all_tournament_ad_slots" ON tournament_ad_slots FOR ALL TO service_role USING (true) WITH CHECK (true);
CREATE POLICY "service_role_all_tournament_page_views" ON tournament_page_views FOR ALL TO service_role USING (true) WITH CHECK (true);
CREATE POLICY "service_role_all_tournament_media" ON tournament_media FOR ALL TO service_role USING (true) WITH CHECK (true);
This diff is collapsed.
<div class="content-header">
<h1>الإعلانات المدفوعة</h1>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings" class="btn btn-ghost">← الإعدادات</a>
</div>
<!-- Add Ad -->
<div class="card mb-4">
<div class="card-header"><h3>إضافة إعلان مدفوع</h3></div>
<div class="card-body">
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/ads/store">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<div class="form-row">
<div class="form-group" style="flex:1">
<label>الموقع</label>
<select name="position" class="form-input">
<option value="hero_top">أعلى البانر</option>
<option value="hero_bottom">أسفل البانر</option>
<option value="sidebar_top">أعلى الشريط الجانبي</option>
<option value="sidebar_bottom">أسفل الشريط الجانبي</option>
<option value="between_sections">بين الأقسام</option>
<option value="footer">أسفل الصفحة</option>
<option value="ticker">شريط الأخبار</option>
</select>
</div>
<div class="form-group" style="width:150px">
<label>النوع</label>
<select name="slot_type" class="form-input">
<option value="image">صورة</option>
<option value="html">HTML</option>
</select>
</div>
<div class="form-group" style="width:100px">
<label>الترتيب</label>
<input type="number" name="display_order" value="0" class="form-input">
</div>
</div>
<div class="form-group">
<label>المحتوى (رابط صورة أو كود HTML)</label>
<textarea name="content" class="form-input" rows="3" required placeholder="https://... أو <div>...</div>" dir="ltr"></textarea>
</div>
<div class="form-row">
<div class="form-group" style="flex:1">
<label>رابط الإعلان (اختياري)</label>
<input type="url" name="link_url" class="form-input" placeholder="https://..." dir="ltr">
</div>
<div class="form-group" style="flex:1">
<label>نص بديل</label>
<input type="text" name="alt_text" class="form-input" placeholder="وصف الإعلان">
</div>
</div>
<button type="submit" class="btn btn-primary">إضافة الإعلان</button>
</form>
</div>
</div>
<!-- Existing Ads -->
<div class="card">
<div class="card-header"><h3>الإعلانات الحالية (<?= count($ads) ?>)</h3></div>
<div class="card-body">
<?php if (empty($ads)): ?>
<p class="text-muted text-center">لا توجد إعلانات مدفوعة</p>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>الموقع</th>
<th>النوع</th>
<th>الحالة</th>
<th>مشاهدات</th>
<th>نقرات</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($ads as $ad): ?>
<tr>
<td><?= htmlspecialchars($ad['position']) ?></td>
<td><?= $ad['slot_type'] ?></td>
<td>
<span class="badge <?= $ad['is_active'] ? 'badge-success' : 'badge-ghost' ?>">
<?= $ad['is_active'] ? 'نشط' : 'متوقف' ?>
</span>
</td>
<td><?= number_format($ad['impressions'] ?? 0) ?></td>
<td><?= number_format($ad['clicks'] ?? 0) ?></td>
<td style="display:flex;gap:6px;">
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/ads/<?= $ad['id'] ?>/toggle">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<button class="btn btn-sm btn-ghost"><?= $ad['is_active'] ? 'إيقاف' : 'تفعيل' ?></button>
</form>
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/ads/<?= $ad['id'] ?>/delete">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<button class="btn btn-sm btn-danger" onclick="return confirm('حذف؟')">حذف</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<div class="content-header">
<h1>إحصائيات الزوار</h1>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings" class="btn btn-ghost">← الإعدادات</a>
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;margin-bottom:24px;">
<div class="card" style="text-align:center;padding:24px;">
<div style="font-size:32px;font-weight:700;color:var(--brand-blue);"><?= number_format($stats['total_views'] ?? 0) ?></div>
<div class="text-muted">إجمالي المشاهدات</div>
</div>
<div class="card" style="text-align:center;padding:24px;">
<div style="font-size:32px;font-weight:700;color:var(--brand-blue);"><?= number_format($stats['unique_visitors'] ?? 0) ?></div>
<div class="text-muted">زوار فريدون</div>
</div>
<div class="card" style="text-align:center;padding:24px;">
<?php $avgPages = ($stats['unique_visitors'] ?? 0) > 0 ? round(($stats['total_views'] ?? 0) / $stats['unique_visitors'], 1) : 0; ?>
<div style="font-size:32px;font-weight:700;color:var(--brand-blue);"><?= $avgPages ?></div>
<div class="text-muted">متوسط الصفحات/زائر</div>
</div>
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
<!-- Sections -->
<div class="card">
<div class="card-header"><h3>الأقسام الأكثر زيارة</h3></div>
<div class="card-body">
<?php if (empty($stats['sections'])): ?>
<p class="text-muted">لا توجد بيانات بعد</p>
<?php else: ?>
<?php $maxSect = max($stats['sections']); ?>
<?php foreach ($stats['sections'] as $section => $count): ?>
<div style="margin-bottom:12px;">
<div style="display:flex;justify-content:space-between;font-size:13px;margin-bottom:4px;">
<span><?= htmlspecialchars($section) ?></span>
<span class="text-muted"><?= $count ?></span>
</div>
<div style="height:6px;background:var(--bg-primary);border-radius:3px;overflow:hidden;">
<div style="height:100%;width:<?= ($count / max(1, $maxSect)) * 100 ?>%;background:var(--brand-blue);border-radius:3px;"></div>
</div>
</div>
<?php endforeach; ?>
<?php endif; ?>
</div>
</div>
<!-- Referrers -->
<div class="card">
<div class="card-header"><h3>المصادر</h3></div>
<div class="card-body">
<?php if (empty($stats['referrers'])): ?>
<p class="text-muted">لا توجد بيانات بعد</p>
<?php else: ?>
<table class="table">
<thead><tr><th>المصدر</th><th>الزيارات</th></tr></thead>
<tbody>
<?php foreach ($stats['referrers'] as $ref => $count): ?>
<tr>
<td style="font-size:13px;"><?= htmlspecialchars($ref) ?></td>
<td><?= $count ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</div>
<!-- Recent Views -->
<div class="card mt-4">
<div class="card-header"><h3>آخر الزيارات</h3></div>
<div class="card-body">
<?php if (empty($stats['recent'])): ?>
<p class="text-muted">لا توجد زيارات بعد</p>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead><tr><th>القسم</th><th>الوقت</th><th>البلد</th><th>المصدر</th></tr></thead>
<tbody>
<?php foreach ($stats['recent'] as $view): ?>
<tr>
<td><?= htmlspecialchars($view['page_section'] ?? '') ?></td>
<td style="white-space:nowrap"><?= date('m/d H:i', strtotime($view['viewed_at'] ?? 'now')) ?></td>
<td><?= htmlspecialchars($view['ip_country'] ?? '-') ?></td>
<td style="font-size:12px;max-width:200px;overflow:hidden;text-overflow:ellipsis;"><?= htmlspecialchars($view['referrer'] ?? '-') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<div class="content-header">
<h1>إعلانات البطولة</h1>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings" class="btn btn-ghost">← الإعدادات</a>
</div>
<!-- Add Announcement -->
<div class="card mb-4">
<div class="card-header"><h3>إضافة إعلان جديد</h3></div>
<div class="card-body">
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/announcements/store">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<div class="form-row">
<div class="form-group" style="flex:1">
<label>العنوان (اختياري)</label>
<input type="text" name="title" class="form-input" placeholder="عنوان الإعلان...">
</div>
<div class="form-group" style="width:150px">
<label>النوع</label>
<select name="type" class="form-input">
<option value="info">معلومات</option>
<option value="result">نتيجة</option>
<option value="alert">تنبيه</option>
<option value="commentary">تعليق</option>
<option value="milestone">إنجاز</option>
</select>
</div>
</div>
<div class="form-group">
<label>المحتوى</label>
<textarea name="content" class="form-input" rows="3" required placeholder="نص الإعلان..."></textarea>
</div>
<div style="display:flex;align-items:center;gap:16px;">
<label class="checkbox-label" style="display:flex;align-items:center;gap:8px;">
<input type="checkbox" name="is_pinned">
<span>تثبيت في الأعلى</span>
</label>
<button type="submit" class="btn btn-primary">نشر الإعلان</button>
</div>
</form>
</div>
</div>
<!-- Existing Announcements -->
<div class="card">
<div class="card-header"><h3>الإعلانات الحالية (<?= count($announcements) ?>)</h3></div>
<div class="card-body">
<?php if (empty($announcements)): ?>
<p class="text-muted text-center">لا توجد إعلانات بعد</p>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>النوع</th>
<th>المحتوى</th>
<th>التاريخ</th>
<th>مثبت</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($announcements as $ann): ?>
<tr>
<td><span class="badge badge-<?= $ann['type'] ?? 'info' ?>"><?= $ann['type'] ?? 'info' ?></span></td>
<td>
<?php if (!empty($ann['title'])): ?><strong><?= htmlspecialchars($ann['title']) ?></strong><br><?php endif; ?>
<?= htmlspecialchars(mb_substr($ann['content'], 0, 80)) ?>
</td>
<td style="white-space:nowrap"><?= date('m/d H:i', strtotime($ann['created_at'])) ?></td>
<td><?= !empty($ann['is_pinned']) ? '📌' : '-' ?></td>
<td>
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/announcements/<?= $ann['id'] ?>/delete" style="display:inline">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('حذف؟')">حذف</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<div class="content-header">
<h1>معرض الصور والوسائط</h1>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings" class="btn btn-ghost">← الإعدادات</a>
</div>
<!-- Upload -->
<div class="card mb-4">
<div class="card-header"><h3>رفع وسائط</h3></div>
<div class="card-body">
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/media/upload" enctype="multipart/form-data">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<div class="form-row">
<div class="form-group" style="flex:1">
<label>رفع صورة</label>
<input type="file" name="media_file" class="form-input" accept="image/png,image/jpeg,image/webp,image/gif">
<small class="text-muted">PNG, JPG, WebP, GIF — حد أقصى 10MB</small>
</div>
<div class="form-group" style="flex:1">
<label>أو رابط فيديو</label>
<input type="url" name="video_url" class="form-input" placeholder="https://youtube.com/..." dir="ltr">
</div>
</div>
<div class="form-row">
<div class="form-group" style="flex:1">
<label>وصف (اختياري)</label>
<input type="text" name="caption" class="form-input" placeholder="وصف الصورة...">
</div>
<label class="checkbox-label" style="display:flex;align-items:center;gap:8px;padding-top:24px;">
<input type="checkbox" name="is_featured">
<span>صورة مميزة</span>
</label>
</div>
<button type="submit" class="btn btn-primary">رفع</button>
</form>
</div>
</div>
<!-- Gallery -->
<div class="card">
<div class="card-header"><h3>الوسائط الحالية (<?= count($media) ?>)</h3></div>
<div class="card-body">
<?php if (empty($media)): ?>
<p class="text-muted text-center">لا توجد وسائط</p>
<?php else: ?>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;">
<?php foreach ($media as $item): ?>
<div class="card" style="padding:0;overflow:hidden;position:relative;">
<?php if ($item['media_type'] === 'image'): ?>
<img src="<?= htmlspecialchars($item['url']) ?>" alt="" style="width:100%;height:150px;object-fit:cover;">
<?php else: ?>
<div style="width:100%;height:150px;display:flex;align-items:center;justify-content:center;background:var(--bg-primary);">
🎬 فيديو
</div>
<?php endif; ?>
<div style="padding:8px;">
<p style="font-size:12px;margin:0 0 8px;"><?= htmlspecialchars($item['caption'] ?? 'بدون وصف') ?></p>
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/media/<?= $item['id'] ?>/delete">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<button class="btn btn-danger btn-sm" style="width:100%" onclick="return confirm('حذف؟')">حذف</button>
</form>
</div>
<?php if (!empty($item['is_featured'])): ?>
<span style="position:absolute;top:8px;right:8px;"></span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php
$isLive = !empty($tournament['live_enabled']);
$slug = $tournament['slug'] ?? '';
$currentTheme = $tournament['live_theme'] ?? 'default';
$customCss = $tournament['live_custom_css'] ?? '';
$publicUrl = $slug ? "/live/{$slug}" : "/live/{$tournament['id']}";
?>
<div class="content-header">
<h1>إعدادات الصفحة المباشرة</h1>
<div class="header-actions">
<a href="/tournaments/<?= $tournament['id'] ?>" class="btn btn-ghost">← العودة للبطولة</a>
<?php if ($isLive): ?>
<a href="<?= $publicUrl ?>" target="_blank" class="btn btn-primary">عرض الصفحة المباشرة ↗</a>
<?php endif; ?>
</div>
</div>
<!-- Live Toggle -->
<div class="card mb-4">
<div class="card-body" style="display:flex;align-items:center;justify-content:space-between;">
<div>
<h3 style="margin:0 0 4px">تفعيل الصفحة المباشرة</h3>
<p class="text-muted" style="margin:0;font-size:13px;">عند التفعيل، سيتمكن أي شخص من مشاهدة البطولة عبر رابط عام</p>
</div>
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/toggle">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<button type="submit" class="btn <?= $isLive ? 'btn-danger' : 'btn-success' ?>">
<?= $isLive ? 'إيقاف' : 'تفعيل' ?>
</button>
</form>
</div>
</div>
<!-- Slug -->
<div class="card mb-4">
<div class="card-header"><h3>الرابط المختصر</h3></div>
<div class="card-body">
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/slug">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<div class="form-group">
<label>الرابط الحالي</label>
<div style="display:flex;gap:8px;align-items:center;">
<span class="text-muted">/live/</span>
<input type="text" name="slug" value="<?= htmlspecialchars($slug) ?>" class="form-input" placeholder="tournament-name" dir="ltr" style="flex:1">
</div>
<small class="text-muted">اتركه فارغاً لإنشاء رابط تلقائي من اسم البطولة</small>
</div>
<button type="submit" class="btn btn-primary">حفظ الرابط</button>
</form>
</div>
</div>
<!-- Theme & Visibility -->
<form method="POST" action="/tournaments/<?= $tournament['id'] ?>/live-settings/update">
<input type="hidden" name="_csrf" value="<?= Auth::csrfToken() ?>">
<div class="card mb-4">
<div class="card-header"><h3>المظهر</h3></div>
<div class="card-body">
<div class="form-group">
<label>السمة</label>
<select name="live_theme" class="form-input">
<option value="default" <?= $currentTheme === 'default' ? 'selected' : '' ?>>El3ab الافتراضي (أزرق داكن)</option>
<option value="neon" <?= $currentTheme === 'neon' ? 'selected' : '' ?>>نيون (سايبربانك)</option>
<option value="minimal" <?= $currentTheme === 'minimal' ? 'selected' : '' ?>>بسيط (أبيض نظيف)</option>
<option value="royal" <?= $currentTheme === 'royal' ? 'selected' : '' ?>>ملكي (ذهبي أنيق)</option>
<option value="arena" <?= $currentTheme === 'arena' ? 'selected' : '' ?>>أرينا (أحمر تنافسي)</option>
</select>
</div>
<div class="form-group">
<label>ألوان مخصصة (اختياري)</label>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;">
<div>
<label class="text-muted" style="font-size:12px">لون التمييز</label>
<input type="color" name="accent_color" value="<?= htmlspecialchars($branding['accent_color'] ?? '#2082F0') ?>" class="form-input">
</div>
<div>
<label class="text-muted" style="font-size:12px">لون النص</label>
<input type="color" name="text_color" value="<?= htmlspecialchars($branding['text_color'] ?? '#E8ECF0') ?>" class="form-input">
</div>
<div>
<label class="text-muted" style="font-size:12px">لون الخلفية</label>
<input type="color" name="bg_color" value="<?= htmlspecialchars($branding['bg_color'] ?? '#0B1220') ?>" class="form-input">
</div>
</div>
</div>
<div class="form-group">
<label>CSS مخصص</label>
<textarea name="live_custom_css" class="form-input" rows="4" dir="ltr" placeholder=".live-hero { background: url(...); }"><?= htmlspecialchars($customCss) ?></textarea>
<small class="text-muted">يمكنك تعديل أي عنصر باستخدام CSS مخصص</small>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header"><h3>الأقسام المرئية</h3></div>
<div class="card-body">
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;">
<?php
$sectionLabels = [
'standings' => 'الترتيب',
'pairings' => 'المباريات',
'bracket' => 'الشجرة',
'players' => 'اللاعبون',
'schedule' => 'الجدول',
'prizes' => 'الجوائز',
'rules' => 'القوانين',
'stats' => 'الإحصائيات',
'announcements' => 'الإعلانات',
'gallery' => 'المعرض',
'ticker' => 'شريط الأخبار',
];
foreach ($sectionLabels as $key => $label):
?>
<label class="checkbox-label" style="display:flex;align-items:center;gap:8px;padding:8px;background:var(--bg-primary);border-radius:8px;">
<input type="checkbox" name="visibility_<?= $key ?>" <?= !empty($visibility[$key]) ? 'checked' : '' ?>>
<span><?= $label ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary btn-lg">حفظ الإعدادات</button>
</form>
<!-- Quick Links -->
<div class="card mt-4">
<div class="card-header"><h3>إدارة المحتوى</h3></div>
<div class="card-body">
<div style="display:flex;gap:12px;flex-wrap:wrap;">
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings/announcements" class="btn btn-ghost">📢 الإعلانات (<?= count($announcements) ?>)</a>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings/ads" class="btn btn-ghost">📊 الإعلانات المدفوعة (<?= count($ads) ?>)</a>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings/media" class="btn btn-ghost">🖼️ معرض الصور (<?= count($media) ?>)</a>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings/analytics" class="btn btn-ghost">📈 الإحصائيات</a>
</div>
</div>
</div>
(function() {
'use strict';
const container = document.querySelector('.live-container, .live-embed');
if (!container) return;
const tournamentId = container.dataset.tournamentId;
if (!tournamentId) return;
let trackedSections = new Set();
function trackSection(section) {
if (trackedSections.has(section)) return;
trackedSections.add(section);
if (navigator.sendBeacon) {
navigator.sendBeacon(`/api/live/${tournamentId}/track`, JSON.stringify({ section }));
} else {
fetch(`/api/live/${tournamentId}/track`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ section }),
keepalive: true,
});
}
}
trackSection('overview');
const sections = document.querySelectorAll('.live-section[id]');
if (sections.length) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = entry.target.id.replace('-section', '');
trackSection(id);
}
});
}, { threshold: 0.5 });
sections.forEach(s => observer.observe(s));
}
document.addEventListener('click', (e) => {
const adLink = e.target.closest('.ad-slot [data-track="click"]');
if (!adLink) return;
const adSlot = adLink.closest('.ad-slot');
const adId = adSlot ? adSlot.dataset.adId : '';
if (adId) {
trackSection('ad_click_' + adId);
}
});
})();
(function() {
'use strict';
const container = document.getElementById('bracket-svg-container');
if (!container) return;
const matchesRaw = container.dataset.matches;
if (!matchesRaw) return;
let matches;
try {
matches = JSON.parse(matchesRaw);
} catch (e) { return; }
if (!matches.length) return;
const roundsContainer = document.getElementById('bracket-rounds');
if (!roundsContainer) return;
const roundsMap = {};
matches.forEach(m => {
const rn = m.round_number || 1;
if (!roundsMap[rn]) roundsMap[rn] = [];
roundsMap[rn].push(m);
});
const roundNumbers = Object.keys(roundsMap).map(Number).sort((a, b) => a - b);
const totalRounds = roundNumbers.length;
const roundLabels = {
1: 'الجولة الأولى',
2: 'ربع النهائي',
3: 'نصف النهائي',
4: 'النهائي',
};
roundNumbers.forEach((rn, idx) => {
const roundDiv = document.createElement('div');
roundDiv.className = 'bracket-round';
let label = roundLabels[totalRounds - idx] || `الجولة ${rn}`;
if (idx === totalRounds - 1) label = 'النهائي';
else if (idx === totalRounds - 2) label = 'نصف النهائي';
const titleDiv = document.createElement('div');
titleDiv.className = 'bracket-round-title';
titleDiv.textContent = label;
roundDiv.appendChild(titleDiv);
const roundMatches = roundsMap[rn].sort((a, b) => (a.match_number || 0) - (b.match_number || 0));
roundMatches.forEach(match => {
const matchDiv = document.createElement('div');
matchDiv.className = 'bracket-match';
const p1Name = match.player1_name || match.player_a_name || 'TBD';
const p2Name = match.player2_name || match.player_b_name || 'TBD';
const p1Score = match.player1_score ?? match.score_a ?? '';
const p2Score = match.player2_score ?? match.score_b ?? '';
const winner = match.winner_id || match.winner;
const p1Id = match.player1_id || match.player_a_id || '';
const p2Id = match.player2_id || match.player_b_id || '';
const p1Winner = winner && winner === p1Id;
const p2Winner = winner && winner === p2Id;
matchDiv.innerHTML = `
<div class="bracket-player ${p1Winner ? 'bracket-winner' : ''}">
<span>${escapeHtml(p1Name)}</span>
<span class="bracket-score">${p1Score}</span>
</div>
<div class="bracket-player ${p2Winner ? 'bracket-winner' : ''}">
<span>${escapeHtml(p2Name)}</span>
<span class="bracket-score">${p2Score}</span>
</div>
`;
roundDiv.appendChild(matchDiv);
});
roundsContainer.appendChild(roundDiv);
});
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
})();
(function() {
'use strict';
const section = document.querySelector('.countdown-section');
if (!section) return;
const target = new Date(section.dataset.target).getTime();
if (isNaN(target)) return;
const daysEl = document.getElementById('countdown-days');
const hoursEl = document.getElementById('countdown-hours');
const minutesEl = document.getElementById('countdown-minutes');
const secondsEl = document.getElementById('countdown-seconds');
function update() {
const now = Date.now();
const diff = target - now;
if (diff <= 0) {
daysEl.textContent = '00';
hoursEl.textContent = '00';
minutesEl.textContent = '00';
secondsEl.textContent = '00';
section.classList.add('countdown-ended');
return;
}
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
animateValue(daysEl, days);
animateValue(hoursEl, hours);
animateValue(minutesEl, minutes);
animateValue(secondsEl, seconds);
requestAnimationFrame(() => setTimeout(update, 1000));
}
function animateValue(el, value) {
const formatted = String(value).padStart(2, '0');
if (el.textContent !== formatted) {
el.style.transform = 'rotateX(-90deg)';
setTimeout(() => {
el.textContent = formatted;
el.style.transform = 'rotateX(0)';
}, 150);
}
}
update();
})();
.form-row {
display: flex;
gap: 16px;
align-items: flex-start;
}
@media (max-width: 768px) {
.form-row {
flex-direction: column;
}
}
// Admin JS for live-tournaments settings (minimal)
(function() {
'use strict';
})();
This diff is collapsed.
(function() {
'use strict';
const container = document.querySelector('.live-container, .live-embed');
if (!container) return;
const tournamentId = container.dataset.tournamentId;
const status = container.dataset.status;
const mode = container.dataset.mode;
const POLL_INTERVALS = {
in_progress: 15000,
registration: 60000,
completed: 0,
};
let pollTimer = null;
let isVisible = true;
function init() {
setupPolling();
setupDarkModeToggle();
setupPlayerCards();
setupGalleryLightbox();
setupH2H();
setupSectionObservers();
document.addEventListener('visibilitychange', () => {
isVisible = !document.hidden;
if (isVisible && status === 'in_progress') {
refreshData();
}
});
}
function setupPolling() {
const interval = POLL_INTERVALS[status] || 0;
if (interval <= 0) return;
pollTimer = setInterval(() => {
if (isVisible) refreshData();
}, interval);
}
async function refreshData() {
try {
const resp = await fetch(`/api/live/${tournamentId}/data`);
if (!resp.ok) return;
const data = await resp.json();
updateStandings(data.standings);
updatePairings(data.pairings);
updateAnnouncements(data.announcements);
} catch (e) {}
}
function updateStandings(standings) {
if (!standings || !standings.length) return;
const section = document.getElementById('standings-section');
if (!section) return;
const tbody = section.querySelector('tbody');
if (!tbody) return;
const rows = tbody.querySelectorAll('tr');
standings.forEach((row, i) => {
if (rows[i]) {
const cells = rows[i].querySelectorAll('td');
const pts = row.points ?? row.score ?? 0;
if (cells[2]) cells[2].innerHTML = `<strong>${pts}</strong>`;
if (cells[3]) cells[3].textContent = row.gamesPlayed ?? row.games_played ?? 0;
if (cells[4]) cells[4].textContent = row.wins ?? 0;
if (cells[5]) cells[5].textContent = row.draws ?? 0;
if (cells[6]) cells[6].textContent = row.losses ?? 0;
}
});
}
function updatePairings(pairingsData) {
if (!pairingsData) return;
const section = document.getElementById('pairings-section');
if (!section) return;
const pairings = pairingsData.pairings || [];
const cards = section.querySelectorAll('.pairing-card');
cards.forEach((card, i) => {
if (!pairings[i]) return;
const p = pairings[i];
const result = p.result;
if (result && card.classList.contains('pairing-live')) {
card.classList.remove('pairing-live');
card.classList.add('pairing-done');
const vs = card.querySelector('.pairing-vs');
if (vs) vs.innerHTML = '<span class="vs-text">vs</span>';
card.style.animation = 'fadeInUp 0.3s ease';
}
});
}
function updateAnnouncements(announcements) {
if (!announcements || !announcements.length) return;
const feed = document.getElementById('announcements-feed');
if (!feed) return;
const existingIds = new Set(
[...feed.querySelectorAll('.announcement-bubble')].map(el => el.dataset.id)
);
announcements.forEach(ann => {
if (existingIds.has(ann.id)) return;
const bubble = document.createElement('div');
bubble.className = `announcement-bubble ann-${ann.type || 'info'}`;
bubble.dataset.id = ann.id;
bubble.innerHTML = `
<div class="ann-body">
${ann.title ? `<strong class="ann-title">${escapeHtml(ann.title)}</strong>` : ''}
<p class="ann-content">${escapeHtml(ann.content)}</p>
<span class="ann-time">الآن</span>
</div>
`;
bubble.style.animation = 'fadeInUp 0.4s ease';
feed.prepend(bubble);
});
}
function setupDarkModeToggle() {
const saved = localStorage.getItem('live-mode');
if (saved === 'light') {
document.body.setAttribute('data-mode', 'light');
}
}
function setupPlayerCards() {
document.addEventListener('click', (e) => {
const link = e.target.closest('.player-name-link, .player-card-mini');
if (!link) return;
const playerData = link.dataset.player;
if (!playerData) return;
try {
const player = JSON.parse(playerData);
showPlayerModal(player);
} catch (err) {}
});
const closeBtn = document.getElementById('player-modal-close');
if (closeBtn) {
closeBtn.addEventListener('click', hidePlayerModal);
}
const backdrop = document.querySelector('.player-modal-backdrop');
if (backdrop) {
backdrop.addEventListener('click', hidePlayerModal);
}
}
function showPlayerModal(player) {
const modal = document.getElementById('player-modal');
if (!modal) return;
const name = player.playerName || player.player_name || player.name || '';
const rating = player.rating || player.elo || '';
const wins = player.wins || 0;
const draws = player.draws || 0;
const losses = player.losses || 0;
document.getElementById('modal-player-name').textContent = name;
document.getElementById('modal-player-rating').textContent = rating ? `تقييم: ${rating}` : '';
document.getElementById('modal-avatar').textContent = name.charAt(0);
document.getElementById('modal-wins').textContent = wins;
document.getElementById('modal-draws').textContent = draws;
document.getElementById('modal-losses').textContent = losses;
modal.hidden = false;
}
function hidePlayerModal() {
const modal = document.getElementById('player-modal');
if (modal) modal.hidden = true;
}
function setupGalleryLightbox() {
document.addEventListener('click', (e) => {
const item = e.target.closest('.gallery-item[data-full]');
if (!item) return;
const lightbox = document.getElementById('gallery-lightbox');
const img = document.getElementById('lightbox-img');
if (!lightbox || !img) return;
img.src = item.dataset.full;
lightbox.hidden = false;
});
const closeBtn = document.querySelector('.lightbox-close');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
const lb = document.getElementById('gallery-lightbox');
if (lb) lb.hidden = true;
});
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const lb = document.getElementById('gallery-lightbox');
if (lb) lb.hidden = true;
hidePlayerModal();
}
});
}
function setupH2H() {
const selectA = document.getElementById('h2h-player-a');
const selectB = document.getElementById('h2h-player-b');
if (!selectA || !selectB) return;
const playerCards = document.querySelectorAll('.player-card-mini');
playerCards.forEach(card => {
try {
const data = JSON.parse(card.dataset.player || '{}');
const name = data.playerName || data.player_name || data.name || '';
const id = data.playerId || data.player_id || data.id || '';
if (!name) return;
const optA = new Option(name, id);
const optB = new Option(name, id);
selectA.add(optA);
selectB.add(optB);
} catch (e) {}
});
[selectA, selectB].forEach(sel => {
sel.addEventListener('change', computeH2H);
});
}
function computeH2H() {
const result = document.getElementById('h2h-result');
if (result) result.hidden = true;
}
function setupSectionObservers() {
const sections = document.querySelectorAll('.live-section');
if (!sections.length) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}
});
}, { threshold: 0.1 });
sections.forEach(s => {
s.style.opacity = '0';
s.style.transform = 'translateY(20px)';
s.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
observer.observe(s);
});
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
@media print {
body.live-page {
background: white !important;
color: #000 !important;
}
.live-hero { background: none !important; padding: 20px 0; }
.hero-overlay { display: none; }
.hero-title { color: #000 !important; text-shadow: none !important; -webkit-text-fill-color: #000 !important; }
.live-body { display: block; }
.live-sidebar { display: none; }
.ticker-bar { display: none; }
.live-footer-ads { display: none; }
.ad-slot { display: none; }
.sharing-widget { display: none; }
.countdown-section { display: none; }
.live-section { break-inside: avoid; border: 1px solid #ddd; margin-bottom: 16px; box-shadow: none !important; }
.standings-table th, .standings-table td { border: 1px solid #ddd; }
.pairing-live { border-color: #000; box-shadow: none; }
.pulse-dot { display: none; }
.player-modal { display: none; }
}
@page {
margin: 1cm;
}
(function() {
'use strict';
document.addEventListener('click', (e) => {
const copyBtn = e.target.closest('[data-copy]');
if (!copyBtn) return;
const text = copyBtn.dataset.copy;
navigator.clipboard.writeText(text).then(() => {
const original = copyBtn.innerHTML;
copyBtn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6L9 17l-5-5"/></svg>';
copyBtn.style.background = '#10B981';
setTimeout(() => {
copyBtn.innerHTML = original;
copyBtn.style.background = '';
}, 2000);
});
});
const embedTextareas = document.querySelectorAll('.embed-code');
embedTextareas.forEach(ta => {
ta.addEventListener('click', () => {
ta.select();
navigator.clipboard.writeText(ta.value);
});
});
})();
/* Arena Competitive Red Theme */
body.live-page[data-theme="arena"] {
--live-bg: #0A0A0A;
--live-surface: #141414;
--live-card: #1C1C1C;
--live-border: rgba(220,38,38,0.15);
--live-text: #F5F5F5;
--live-text-secondary: #A0A0A0;
--live-text-muted: #666666;
--live-accent: #DC2626;
--live-accent-glow: rgba(220,38,38,0.3);
--live-success: #22C55E;
--live-warning: #EAB308;
--live-danger: #DC2626;
--live-gold: #F59E0B;
--live-purple: #9333EA;
--live-cyan: #06B6D4;
}
body.live-page[data-theme="arena"] .live-hero {
background: linear-gradient(135deg, #0A0A0A 0%, #1A0000 50%, #0A0A0A 100%);
border-bottom: 2px solid rgba(220,38,38,0.4);
}
body.live-page[data-theme="arena"] .hero-badge.status-live {
background: rgba(220,38,38,0.2);
color: #DC2626;
border-color: #DC2626;
}
body.live-page[data-theme="arena"] .live-section {
border-color: rgba(220,38,38,0.1);
}
body.live-page[data-theme="arena"] .section-title {
text-transform: uppercase;
letter-spacing: 1px;
font-size: 16px;
}
body.live-page[data-theme="arena"] .countdown-value {
border-color: rgba(220,38,38,0.4);
box-shadow: 0 0 20px rgba(220,38,38,0.3);
color: #DC2626;
}
body.live-page[data-theme="arena"] .pairing-card {
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 8px 100%, 0 calc(100% - 8px));
}
body.live-page[data-theme="arena"] .pairing-live {
border-color: #DC2626;
box-shadow: 0 0 15px rgba(220,38,38,0.2);
}
body.live-page[data-theme="arena"] .round-badge {
background: #DC2626;
clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
border-radius: 0;
padding: 4px 20px;
}
body.live-page[data-theme="arena"] .stat-card {
clip-path: polygon(0 0, calc(100% - 6px) 0, 100% 6px, 100% 100%, 6px 100%, 0 calc(100% - 6px));
}
body.live-page[data-theme="arena"] .player-card-mini {
border-radius: 4px;
}
body.live-page[data-theme="arena"] .player-card-mini:hover {
border-color: #DC2626;
box-shadow: 0 0 10px rgba(220,38,38,0.2);
}
body.live-page[data-theme="arena"] .pulse-dot {
background: #DC2626;
box-shadow: 0 0 8px #DC2626;
}
body.live-page[data-theme="arena"] .ticker-bar {
background: linear-gradient(90deg, rgba(220,38,38,0.05), transparent, rgba(220,38,38,0.05));
border-bottom-color: rgba(220,38,38,0.2);
}
body.live-page[data-theme="arena"] .footer-brand {
color: #DC2626;
text-transform: uppercase;
letter-spacing: 2px;
}
/* Default El3ab Theme - Navy Dark Gaming */
body.live-page[data-theme="default"] {
--live-bg: #0B1220;
--live-surface: #111B2E;
--live-card: #152132;
--live-border: rgba(255,255,255,0.08);
--live-text: #E8ECF0;
--live-text-secondary: #8B9BB4;
--live-text-muted: #5A6B80;
--live-accent: #2082F0;
--live-accent-glow: rgba(32,130,240,0.3);
--live-success: #10B981;
--live-warning: #E4AC38;
--live-danger: #E84D1E;
--live-gold: #FFCC66;
--live-purple: #6834BE;
--live-cyan: #00FFFF;
}
/* Minimal Clean White Theme */
body.live-page[data-theme="minimal"] {
--live-bg: #FAFBFC;
--live-surface: #FFFFFF;
--live-card: #F6F8FA;
--live-border: #E1E4E8;
--live-text: #24292E;
--live-text-secondary: #586069;
--live-text-muted: #959DA5;
--live-accent: #2082F0;
--live-accent-glow: rgba(32,130,240,0.15);
--live-success: #28A745;
--live-warning: #F9A825;
--live-danger: #D73A49;
--live-gold: #B08800;
--live-purple: #6F42C1;
--live-cyan: #0598BC;
}
body.live-page[data-theme="minimal"] .live-hero {
background: linear-gradient(135deg, #FFFFFF 0%, #F0F4FF 100%);
border-bottom: 2px solid #E1E4E8;
}
body.live-page[data-theme="minimal"] .hero-overlay {
background: none;
}
body.live-page[data-theme="minimal"] .hero-title {
text-shadow: none;
color: #24292E;
}
body.live-page[data-theme="minimal"] .live-section {
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
border-color: #E1E4E8;
}
body.live-page[data-theme="minimal"] .countdown-value {
box-shadow: 0 2px 8px rgba(32,130,240,0.1);
background: #FFFFFF;
border-color: #E1E4E8;
}
body.live-page[data-theme="minimal"] .pairing-card {
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
body.live-page[data-theme="minimal"] .standings-table tr:hover {
background: #F6F8FA;
}
body.live-page[data-theme="minimal"] .stat-card {
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
body.live-page[data-theme="minimal"] .sharing-widget {
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
body.live-page[data-theme="minimal"] .ticker-bar {
background: #F6F8FA;
}
/* Neon Cyberpunk Theme */
body.live-page[data-theme="neon"] {
--live-bg: #0A0A14;
--live-surface: #0F0F1E;
--live-card: #14142A;
--live-border: rgba(0,255,255,0.12);
--live-text: #E0F0FF;
--live-text-secondary: #7EC8E3;
--live-text-muted: #4A6B80;
--live-accent: #00FFFF;
--live-accent-glow: rgba(0,255,255,0.4);
--live-success: #00FF88;
--live-warning: #FFD700;
--live-danger: #FF3366;
--live-gold: #FFD700;
--live-purple: #BF40FF;
--live-cyan: #00FFFF;
}
body.live-page[data-theme="neon"] .live-hero {
background: linear-gradient(135deg, #0A0A14 0%, #1A0030 50%, #0A0A14 100%);
}
body.live-page[data-theme="neon"] .hero-title {
text-shadow: 0 0 20px rgba(0,255,255,0.5), 0 0 40px rgba(0,255,255,0.2);
}
body.live-page[data-theme="neon"] .live-section {
border-color: rgba(0,255,255,0.1);
box-shadow: 0 0 15px rgba(0,255,255,0.05);
}
body.live-page[data-theme="neon"] .countdown-value {
box-shadow: 0 0 30px rgba(0,255,255,0.4), inset 0 0 10px rgba(0,255,255,0.1);
border-color: rgba(0,255,255,0.3);
}
body.live-page[data-theme="neon"] .pairing-live {
border-color: var(--live-success);
box-shadow: 0 0 20px rgba(0,255,136,0.2);
}
body.live-page[data-theme="neon"] .round-badge {
background: transparent;
border: 1px solid var(--live-accent);
box-shadow: 0 0 10px var(--live-accent-glow);
}
body.live-page[data-theme="neon"] .pulse-dot {
box-shadow: 0 0 10px var(--live-success), 0 0 20px var(--live-success);
}
body.live-page[data-theme="neon"] .stat-card {
border-color: rgba(0,255,255,0.08);
box-shadow: 0 0 8px rgba(0,255,255,0.03);
}
body.live-page[data-theme="neon"] .player-card-mini:hover {
box-shadow: 0 0 12px var(--live-accent-glow);
}
body.live-page[data-theme="neon"] .footer-brand {
text-shadow: 0 0 10px var(--live-accent);
}
/* Royal Elegant Gold Theme */
body.live-page[data-theme="royal"] {
--live-bg: #0C1525;
--live-surface: #101D33;
--live-card: #142340;
--live-border: rgba(212,175,55,0.15);
--live-text: #F5F0E0;
--live-text-secondary: #B8A67A;
--live-text-muted: #7A6B4A;
--live-accent: #D4AF37;
--live-accent-glow: rgba(212,175,55,0.3);
--live-success: #4CAF50;
--live-warning: #FFB300;
--live-danger: #C62828;
--live-gold: #D4AF37;
--live-purple: #7B1FA2;
--live-cyan: #26C6DA;
}
body.live-page[data-theme="royal"] .live-hero {
background: linear-gradient(135deg, #0C1525 0%, #1A2744 50%, #0C1525 100%);
border-bottom: 2px solid rgba(212,175,55,0.3);
}
body.live-page[data-theme="royal"] .hero-title {
background: linear-gradient(135deg, #D4AF37, #F5E6A3, #D4AF37);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
body.live-page[data-theme="royal"] .section-title {
font-family: 'IBM Plex Sans Arabic', serif;
}
body.live-page[data-theme="royal"] .live-section {
border-color: rgba(212,175,55,0.12);
}
body.live-page[data-theme="royal"] .countdown-value {
border-color: rgba(212,175,55,0.3);
box-shadow: 0 0 20px rgba(212,175,55,0.2);
color: var(--live-gold);
}
body.live-page[data-theme="royal"] .round-badge {
background: linear-gradient(135deg, #D4AF37, #A0830A);
}
body.live-page[data-theme="royal"] .rank-1 {
background: rgba(212,175,55,0.1);
border-inline-start: 3px solid var(--live-gold);
}
body.live-page[data-theme="royal"] .prize-total-value {
background: linear-gradient(135deg, #D4AF37, #F5E6A3);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
body.live-page[data-theme="royal"] .sharing-widget {
border-color: rgba(212,175,55,0.15);
}
body.live-page[data-theme="royal"] .footer-brand {
color: var(--live-gold);
}
body.live-page[data-theme="royal"] .player-avatar-small {
background: linear-gradient(135deg, #D4AF37, #A0830A);
}
This diff is collapsed.
<?php
class AnalyticsService
{
private static Database $db;
private static function db(): Database
{
if (!isset(self::$db)) {
self::$db = Database::getInstance();
}
return self::$db;
}
public static function trackView(string $tournamentId, string $section = 'overview'): void
{
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$visitorHash = hash('sha256', $ip . ($_SERVER['HTTP_USER_AGENT'] ?? '') . date('Y-m-d'));
self::db()->insert('tournament_page_views', [
'tournament_id' => $tournamentId,
'visitor_hash' => $visitorHash,
'ip_country' => self::getCountryFromIp($ip),
'referrer' => $_SERVER['HTTP_REFERER'] ?? null,
'page_section' => $section,
]);
self::incrementViewCount($tournamentId);
$existing = self::db()->select('tournament_page_views', [
'tournament_id' => "eq.{$tournamentId}",
'visitor_hash' => "eq.{$visitorHash}",
'select' => 'id',
'limit' => 2,
]);
if (count($existing) <= 1) {
$current = self::db()->selectOne('el3ab_tournaments', [
'id' => "eq.{$tournamentId}",
'select' => 'unique_visitors',
]);
$visitors = ($current['unique_visitors'] ?? 0) + 1;
self::db()->update('el3ab_tournaments', ['id' => "eq.{$tournamentId}"], [
'unique_visitors' => $visitors,
]);
}
}
public static function incrementViewCount(string $tournamentId): void
{
$current = self::db()->selectOne('el3ab_tournaments', [
'id' => "eq.{$tournamentId}",
'select' => 'view_count',
]);
$count = ($current['view_count'] ?? 0) + 1;
self::db()->update('el3ab_tournaments', ['id' => "eq.{$tournamentId}"], [
'view_count' => $count,
]);
}
public static function trackBeacon(string $tournamentId, string $section): void
{
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$visitorHash = hash('sha256', $ip . ($_SERVER['HTTP_USER_AGENT'] ?? '') . date('Y-m-d'));
self::db()->insert('tournament_page_views', [
'tournament_id' => $tournamentId,
'visitor_hash' => $visitorHash,
'ip_country' => self::getCountryFromIp($ip),
'referrer' => $_SERVER['HTTP_REFERER'] ?? null,
'page_section' => $section,
]);
}
public static function getStats(string $tournamentId): array
{
$tournament = self::db()->selectOne('el3ab_tournaments', [
'id' => "eq.{$tournamentId}",
'select' => 'view_count,unique_visitors',
]);
$recentViews = self::db()->select('tournament_page_views', [
'tournament_id' => "eq.{$tournamentId}",
'select' => 'page_section,viewed_at,ip_country,referrer',
'order' => 'viewed_at.desc',
'limit' => 100,
]);
$sectionCounts = [];
$countryCounts = [];
$referrerCounts = [];
foreach ($recentViews as $view) {
$section = $view['page_section'] ?? 'overview';
$sectionCounts[$section] = ($sectionCounts[$section] ?? 0) + 1;
if (!empty($view['ip_country'])) {
$countryCounts[$view['ip_country']] = ($countryCounts[$view['ip_country']] ?? 0) + 1;
}
if (!empty($view['referrer'])) {
$host = parse_url($view['referrer'], PHP_URL_HOST) ?: $view['referrer'];
$referrerCounts[$host] = ($referrerCounts[$host] ?? 0) + 1;
}
}
arsort($sectionCounts);
arsort($countryCounts);
arsort($referrerCounts);
return [
'total_views' => $tournament['view_count'] ?? 0,
'unique_visitors' => $tournament['unique_visitors'] ?? 0,
'sections' => $sectionCounts,
'countries' => array_slice($countryCounts, 0, 10),
'referrers' => array_slice($referrerCounts, 0, 10),
'recent' => array_slice($recentViews, 0, 20),
];
}
private static function getCountryFromIp(string $ip): ?string
{
if (in_array($ip, ['127.0.0.1', '::1', '0.0.0.0'])) {
return 'LOCAL';
}
return null;
}
}
This diff is collapsed.
<?php
class QRCodeService
{
public static function generateSvg(string $data, int $size = 200): string
{
$matrix = self::encode($data);
$moduleCount = count($matrix);
$cellSize = $size / $moduleCount;
$svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ' . $size . ' ' . $size . '" width="' . $size . '" height="' . $size . '">';
$svg .= '<rect width="100%" height="100%" fill="white"/>';
$path = '';
for ($row = 0; $row < $moduleCount; $row++) {
for ($col = 0; $col < $moduleCount; $col++) {
if ($matrix[$row][$col]) {
$x = round($col * $cellSize, 2);
$y = round($row * $cellSize, 2);
$w = round($cellSize, 2);
$path .= "M{$x},{$y}h{$w}v{$w}h-{$w}z";
}
}
}
$svg .= '<path d="' . $path . '" fill="#152132"/>';
$svg .= '</svg>';
return $svg;
}
private static function encode(string $data): array
{
$size = self::getMinSize(strlen($data));
$matrix = array_fill(0, $size, array_fill(0, $size, false));
self::addFinderPatterns($matrix, $size);
self::addTimingPatterns($matrix, $size);
if ($size >= 25) {
self::addAlignmentPattern($matrix, $size);
}
self::encodeData($matrix, $data, $size);
return $matrix;
}
private static function getMinSize(int $dataLen): int
{
$versions = [21, 25, 29, 33, 37, 41, 45, 49, 53, 57];
$capacities = [17, 32, 53, 78, 106, 134, 154, 192, 230, 271];
foreach ($capacities as $i => $cap) {
if ($dataLen <= $cap) {
return $versions[$i];
}
}
return 57;
}
private static function addFinderPatterns(array &$matrix, int $size): void
{
$positions = [[0, 0], [0, $size - 7], [$size - 7, 0]];
foreach ($positions as [$startRow, $startCol]) {
for ($r = 0; $r < 7; $r++) {
for ($c = 0; $c < 7; $c++) {
$isOuter = ($r === 0 || $r === 6 || $c === 0 || $c === 6);
$isInner = ($r >= 2 && $r <= 4 && $c >= 2 && $c <= 4);
$matrix[$startRow + $r][$startCol + $c] = $isOuter || $isInner;
}
}
}
}
private static function addTimingPatterns(array &$matrix, int $size): void
{
for ($i = 8; $i < $size - 8; $i++) {
$matrix[6][$i] = ($i % 2 === 0);
$matrix[$i][6] = ($i % 2 === 0);
}
}
private static function addAlignmentPattern(array &$matrix, int $size): void
{
$pos = $size - 9;
for ($r = -2; $r <= 2; $r++) {
for ($c = -2; $c <= 2; $c++) {
$isOuter = (abs($r) === 2 || abs($c) === 2);
$isCenter = ($r === 0 && $c === 0);
$matrix[$pos + $r][$pos + $c] = $isOuter || $isCenter;
}
}
}
private static function encodeData(array &$matrix, string $data, int $size): void
{
$bits = '';
for ($i = 0; $i < strlen($data); $i++) {
$bits .= str_pad(decbin(ord($data[$i])), 8, '0', STR_PAD_LEFT);
}
$bitIndex = 0;
$upward = true;
for ($col = $size - 1; $col >= 1; $col -= 2) {
if ($col === 6) $col = 5;
$rows = $upward ? range($size - 1, 0, -1) : range(0, $size - 1);
foreach ($rows as $row) {
for ($c = 0; $c < 2; $c++) {
$currentCol = $col - $c;
if ($currentCol < 0 || $currentCol >= $size) continue;
if (self::isReserved($row, $currentCol, $size)) continue;
if ($bitIndex < strlen($bits)) {
$matrix[$row][$currentCol] = $bits[$bitIndex] === '1';
$bitIndex++;
} else {
$matrix[$row][$currentCol] = (($row + $currentCol) % 2 === 0);
}
}
}
$upward = !$upward;
}
}
private static function isReserved(int $row, int $col, int $size): bool
{
if ($row < 9 && $col < 9) return true;
if ($row < 9 && $col >= $size - 8) return true;
if ($row >= $size - 8 && $col < 9) return true;
if ($row === 6 || $col === 6) return true;
return false;
}
}
<?php
class SlugService
{
private static Database $db;
private static function db(): Database
{
if (!isset(self::$db)) {
self::$db = Database::getInstance();
}
return self::$db;
}
public static function generate(string $name): string
{
$slug = self::transliterate($name);
$slug = preg_replace('/[^a-z0-9]+/', '-', strtolower($slug));
$slug = trim($slug, '-');
if (empty($slug)) {
$slug = 'tournament-' . substr(uniqid(), -6);
}
$original = $slug;
$counter = 1;
while (self::exists($slug)) {
$slug = $original . '-' . $counter;
$counter++;
}
return $slug;
}
public static function exists(string $slug, ?string $excludeId = null): bool
{
$params = ['slug' => "eq.{$slug}", 'select' => 'id'];
if ($excludeId) {
$params['id'] = "neq.{$excludeId}";
}
$result = self::db()->select('el3ab_tournaments', $params);
return !empty($result);
}
public static function resolve(string $idOrSlug): ?array
{
$params = ['select' => '*'];
if (preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $idOrSlug)) {
$params['id'] = "eq.{$idOrSlug}";
} else {
$params['slug'] = "eq.{$idOrSlug}";
}
return self::db()->selectOne('el3ab_tournaments', $params);
}
private static function transliterate(string $text): string
{
$map = [
'ا' => 'a', 'ب' => 'b', 'ت' => 't', 'ث' => 'th', 'ج' => 'g',
'ح' => 'h', 'خ' => 'kh', 'د' => 'd', 'ذ' => 'th', 'ر' => 'r',
'ز' => 'z', 'س' => 's', 'ش' => 'sh', 'ص' => 's', 'ض' => 'd',
'ط' => 't', 'ظ' => 'z', 'ع' => 'a', 'غ' => 'gh', 'ف' => 'f',
'ق' => 'q', 'ك' => 'k', 'ل' => 'l', 'م' => 'm', 'ن' => 'n',
'ه' => 'h', 'و' => 'w', 'ي' => 'y', 'ة' => 'a', 'ى' => 'a',
'أ' => 'a', 'إ' => 'i', 'آ' => 'a', 'ؤ' => 'w', 'ئ' => 'y',
];
$result = strtr($text, $map);
if (function_exists('transliterator_transliterate')) {
$result = transliterator_transliterate('Any-Latin; Latin-ASCII', $result);
}
return $result;
}
}
<?php if (empty($announcements)): ?>
<div class="empty-state">لا توجد إعلانات</div>
<?php else: ?>
<div class="announcements-feed" id="announcements-feed">
<?php foreach ($announcements as $ann):
$typeIcons = [
'info' => '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>',
'result' => '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><path d="M22 4L12 14.01l-3-3"/></svg>',
'alert' => '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><path d="M12 9v4M12 17h.01"/></svg>',
'commentary' => '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>',
'milestone' => '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5"/></svg>',
];
$icon = $typeIcons[$ann['type'] ?? 'info'] ?? $typeIcons['info'];
$isPinned = !empty($ann['is_pinned']);
?>
<div class="announcement-bubble ann-<?= $ann['type'] ?? 'info' ?> <?= $isPinned ? 'pinned' : '' ?>">
<?php if ($isPinned): ?><span class="pin-icon">📌</span><?php endif; ?>
<span class="ann-icon"><?= $icon ?></span>
<div class="ann-body">
<?php if (!empty($ann['title'])): ?>
<strong class="ann-title"><?= htmlspecialchars($ann['title']) ?></strong>
<?php endif; ?>
<p class="ann-content"><?= nl2br(htmlspecialchars($ann['content'])) ?></p>
<span class="ann-time"><?= date('H:i - d/m', strtotime($ann['created_at'])) ?></span>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
$arenaData = $data['arena'] ?? ['standings' => [], 'games' => []];
$arenaStandings = $arenaData['standings'] ?? [];
$arenaGames = $arenaData['games'] ?? [];
?>
<?php if (empty($arenaStandings)): ?>
<div class="empty-state">لا توجد بيانات أرينا</div>
<?php else: ?>
<div class="arena-board">
<div class="arena-standings">
<table class="standings-table arena-table">
<thead>
<tr>
<th>#</th>
<th>اللاعب</th>
<th>النقاط</th>
<th>المباريات</th>
<th>الأداء</th>
</tr>
</thead>
<tbody>
<?php foreach ($arenaStandings as $i => $player): ?>
<tr class="<?= $i < 3 ? 'top-rank rank-' . ($i + 1) : '' ?>">
<td><?= $i + 1 ?></td>
<td><?= htmlspecialchars($player['player_name'] ?? 'لاعب') ?></td>
<td><strong><?= $player['score'] ?? 0 ?></strong></td>
<td><?= $player['games_played'] ?? 0 ?></td>
<td>
<div class="performance-bar">
<?php
$total = max(1, $player['games_played'] ?? 1);
$winPct = (($player['wins'] ?? 0) / $total) * 100;
?>
<div class="perf-fill" style="width: <?= $winPct ?>%"></div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php if (!empty($arenaGames)): ?>
<div class="arena-recent">
<h4>آخر المباريات</h4>
<div class="arena-games-feed">
<?php foreach (array_slice($arenaGames, 0, 10) as $game): ?>
<div class="arena-game-item">
<span class="arena-player"><?= htmlspecialchars($game['white_name'] ?? '') ?></span>
<span class="arena-result"><?= $game['result'] ?? '-' ?></span>
<span class="arena-player"><?= htmlspecialchars($game['black_name'] ?? '') ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (empty($bracket)): ?>
<div class="empty-state">لا توجد قرعة حالياً</div>
<?php else: ?>
<div class="bracket-container" id="bracket-svg-container" data-matches="<?= htmlspecialchars(json_encode($bracket)) ?>">
<div class="bracket-scroll">
<div class="bracket-rounds" id="bracket-rounds"></div>
</div>
</div>
<?php endif; ?>
<?php
$startDate = $tournament['start_date'] ?? '';
if (empty($startDate)) return;
$startTimestamp = strtotime($startDate);
if ($startTimestamp <= time()) return;
?>
<div class="countdown-section" data-target="<?= date('c', $startTimestamp) ?>">
<h3 class="countdown-label">تبدأ البطولة خلال</h3>
<div class="countdown-clock">
<div class="countdown-unit">
<span class="countdown-value" id="countdown-days">00</span>
<span class="countdown-text">يوم</span>
</div>
<div class="countdown-sep">:</div>
<div class="countdown-unit">
<span class="countdown-value" id="countdown-hours">00</span>
<span class="countdown-text">ساعة</span>
</div>
<div class="countdown-sep">:</div>
<div class="countdown-unit">
<span class="countdown-value" id="countdown-minutes">00</span>
<span class="countdown-text">دقيقة</span>
</div>
<div class="countdown-sep">:</div>
<div class="countdown-unit">
<span class="countdown-value" id="countdown-seconds">00</span>
<span class="countdown-text">ثانية</span>
</div>
</div>
<?php if (!empty($tournament['max_players'])): ?>
<div class="registration-bar">
<div class="reg-bar-label">
<span><?= count($data['players'] ?? []) ?> / <?= (int)$tournament['max_players'] ?> لاعب مسجل</span>
</div>
<div class="reg-bar-track">
<div class="reg-bar-fill" style="width: <?= min(100, (count($data['players'] ?? []) / max(1, (int)$tournament['max_players'])) * 100) ?>%"></div>
</div>
</div>
<?php endif; ?>
</div>
<?php if (empty($gallery)): ?>
<div class="empty-state">لا توجد صور</div>
<?php else: ?>
<div class="gallery-grid">
<?php foreach ($gallery as $item): ?>
<?php if (($item['media_type'] ?? '') === 'video_url'): ?>
<div class="gallery-item gallery-video">
<a href="<?= htmlspecialchars($item['url']) ?>" target="_blank" rel="noopener" class="gallery-link">
<div class="gallery-video-thumb">
<svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
</div>
<?php if (!empty($item['caption'])): ?>
<span class="gallery-caption"><?= htmlspecialchars($item['caption']) ?></span>
<?php endif; ?>
</a>
</div>
<?php else: ?>
<div class="gallery-item" data-full="<?= htmlspecialchars($item['url']) ?>">
<img src="<?= htmlspecialchars($item['thumbnail_url'] ?? $item['url']) ?>" alt="<?= htmlspecialchars($item['caption'] ?? '') ?>" loading="lazy">
<?php if (!empty($item['caption'])): ?>
<span class="gallery-caption"><?= htmlspecialchars($item['caption']) ?></span>
<?php endif; ?>
<?php if (!empty($item['is_featured'])): ?>
<span class="gallery-featured"></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endforeach; ?>
</div>
<div class="lightbox" id="gallery-lightbox" hidden>
<button class="lightbox-close">&times;</button>
<img src="" alt="" class="lightbox-img" id="lightbox-img">
</div>
<?php endif; ?>
<?php
$groups = $data['groups'] ?? [];
if (empty($groups)): ?>
<div class="empty-state">لا توجد مجموعات</div>
<?php else: ?>
<div class="groups-grid">
<?php foreach ($groups as $groupName => $groupPlayers): ?>
<div class="group-card">
<h4 class="group-name"><?= htmlspecialchars($groupName) ?></h4>
<table class="group-table">
<thead>
<tr>
<th>#</th>
<th>اللاعب</th>
<th>نقاط</th>
<th>م</th>
</tr>
</thead>
<tbody>
<?php foreach ($groupPlayers as $i => $p): ?>
<tr class="<?= $i < 2 ? 'qualifies' : '' ?>">
<td><?= $i + 1 ?></td>
<td><?= htmlspecialchars($p['name'] ?? '') ?></td>
<td><?= $p['points'] ?? 0 ?></td>
<td><?= $p['games'] ?? 0 ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="h2h-tool">
<h4 class="widget-title">مقارنة لاعبين</h4>
<div class="h2h-selectors">
<select id="h2h-player-a" class="h2h-select">
<option value="">اختر لاعب...</option>
</select>
<span class="h2h-vs">VS</span>
<select id="h2h-player-b" class="h2h-select">
<option value="">اختر لاعب...</option>
</select>
</div>
<div class="h2h-result" id="h2h-result" hidden>
<div class="h2h-score">
<span class="h2h-name" id="h2h-name-a"></span>
<span class="h2h-scores" id="h2h-scores"></span>
<span class="h2h-name" id="h2h-name-b"></span>
</div>
<div class="h2h-games" id="h2h-games"></div>
</div>
</div>
<?php
$bannerUrl = $tournament['banner_url'] ?? '';
$orgLogo = $org['logo_url'] ?? '';
$statusLabels = [
'draft' => 'مسودة',
'registration' => 'التسجيل مفتوح',
'in_progress' => 'جارية الآن',
'completed' => 'انتهت',
'cancelled' => 'ملغية',
];
$statusClasses = [
'registration' => 'status-registration',
'in_progress' => 'status-live',
'completed' => 'status-completed',
'cancelled' => 'status-cancelled',
];
$modeLabels = [
'swiss' => 'نظام سويسري',
'elimination' => 'خروج المغلوب',
'round_robin' => 'دوري كامل',
'arena' => 'أرينا',
'group_stage' => 'مجموعات',
'multi_phase' => 'متعدد المراحل',
];
?>
<header class="live-hero" <?php if ($bannerUrl): ?>style="background-image: url('<?= htmlspecialchars($bannerUrl) ?>')"<?php endif; ?>>
<div class="hero-overlay"></div>
<div class="hero-content">
<?php if ($orgLogo): ?>
<img src="<?= htmlspecialchars($orgLogo) ?>" alt="" class="hero-org-logo">
<?php endif; ?>
<div class="hero-badge <?= $statusClasses[$status] ?? '' ?>">
<?php if ($status === 'in_progress'): ?><span class="pulse-dot"></span><?php endif; ?>
<?= $statusLabels[$status] ?? $status ?>
</div>
<h1 class="hero-title"><?= htmlspecialchars($tournament['name']) ?></h1>
<div class="hero-meta">
<span class="hero-meta-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
<?= $modeLabels[$mode] ?? $mode ?>
</span>
<?php if (!empty($tournament['time_control'])): ?>
<span class="hero-meta-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
<?= htmlspecialchars($tournament['time_control']) ?>
</span>
<?php endif; ?>
<?php if (!empty($tournament['max_players'])): ?>
<span class="hero-meta-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></svg>
<?= (int)$tournament['max_players'] ?> لاعب
</span>
<?php endif; ?>
<?php if (!empty($tournament['start_date'])): ?>
<span class="hero-meta-item">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
<?= date('Y/m/d', strtotime($tournament['start_date'])) ?>
</span>
<?php endif; ?>
</div>
<?php if ($org): ?>
<div class="hero-org">
<span>تنظيم: <?= htmlspecialchars($org['name']) ?></span>
</div>
<?php endif; ?>
</div>
</header>
<?php
$description = $tournament['description'] ?? '';
$rules = $tournament['rules'] ?? '';
$modeDescriptions = [
'swiss' => 'نظام سويسري: يتم اقتران اللاعبين ذوي النقاط المتقاربة في كل جولة. لا يُقصى أي لاعب.',
'elimination' => 'خروج المغلوب: الخاسر يخرج من البطولة. الفائز يتقدم للجولة التالية.',
'round_robin' => 'دوري كامل: كل لاعب يلعب ضد جميع اللاعبين الآخرين.',
'arena' => 'أرينا: مباريات سريعة متتالية. كلما لعبت أكثر، زادت فرصتك.',
'group_stage' => 'مجموعات: يتم تقسيم اللاعبين إلى مجموعات، والأوائل يتأهلون.',
'multi_phase' => 'متعدد المراحل: مراحل متتالية بأنظمة مختلفة.',
];
$mode = $tournament['tournament_mode'] ?? 'swiss';
?>
<div class="info-section">
<div class="info-format">
<h4>نظام البطولة</h4>
<p class="format-desc"><?= $modeDescriptions[$mode] ?? '' ?></p>
</div>
<?php if ($description): ?>
<div class="info-description">
<h4>الوصف</h4>
<div class="info-text"><?= nl2br(htmlspecialchars($description)) ?></div>
</div>
<?php endif; ?>
<?php if ($rules): ?>
<div class="info-rules">
<h4>القوانين</h4>
<div class="info-text"><?= nl2br(htmlspecialchars($rules)) ?></div>
</div>
<?php endif; ?>
</div>
<?php
$roundNumber = $pairings['round_number'] ?? 0;
$pairingsList = $pairings['pairings'] ?? [];
if (empty($pairingsList)): ?>
<div class="empty-state">لا توجد مباريات حالياً</div>
<?php else: ?>
<div class="pairings-header">
<span class="round-badge">الجولة <?= $roundNumber ?></span>
</div>
<div class="pairings-grid">
<?php foreach ($pairingsList as $pairing):
$playerA = $pairing['playerAName'] ?? $pairing['player_a_name'] ?? $pairing['white'] ?? 'لاعب 1';
$playerB = $pairing['playerBName'] ?? $pairing['player_b_name'] ?? $pairing['black'] ?? 'لاعب 2';
$result = $pairing['result'] ?? null;
$board = $pairing['boardNumber'] ?? $pairing['board_number'] ?? '';
$isLive = empty($result);
?>
<div class="pairing-card <?= $isLive ? 'pairing-live' : 'pairing-done' ?>">
<?php if ($board): ?>
<span class="board-number">طاولة <?= $board ?></span>
<?php endif; ?>
<div class="pairing-players">
<div class="pairing-player player-a <?= $result === '1-0' || $result === 'white' ? 'winner' : '' ?>">
<span class="player-color white-dot"></span>
<span class="player-name"><?= htmlspecialchars($playerA) ?></span>
<?php if ($result === '1-0' || $result === 'white'): ?><span class="result-icon win">1</span>
<?php elseif ($result === '0-1' || $result === 'black'): ?><span class="result-icon loss">0</span>
<?php elseif ($result === '0.5-0.5' || $result === 'draw'): ?><span class="result-icon draw">½</span>
<?php endif; ?>
</div>
<div class="pairing-vs">
<?php if ($isLive): ?>
<span class="vs-live"><span class="pulse-dot"></span>LIVE</span>
<?php else: ?>
<span class="vs-text">vs</span>
<?php endif; ?>
</div>
<div class="pairing-player player-b <?= $result === '0-1' || $result === 'black' ? 'winner' : '' ?>">
<span class="player-color black-dot"></span>
<span class="player-name"><?= htmlspecialchars($playerB) ?></span>
<?php if ($result === '0-1' || $result === 'black'): ?><span class="result-icon win">1</span>
<?php elseif ($result === '1-0' || $result === 'white'): ?><span class="result-icon loss">0</span>
<?php elseif ($result === '0.5-0.5' || $result === 'draw'): ?><span class="result-icon draw">½</span>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php if (empty($phases)): return; endif; ?>
<div class="phases-progress">
<?php foreach ($phases as $i => $phase):
$phaseStatus = $phase['status'] ?? 'pending';
$isActive = $phaseStatus === 'in_progress';
$isComplete = $phaseStatus === 'completed';
?>
<div class="phase-item <?= $isActive ? 'phase-active' : '' ?> <?= $isComplete ? 'phase-complete' : '' ?>">
<div class="phase-connector <?= $isComplete ? 'filled' : '' ?>"></div>
<div class="phase-dot">
<?php if ($isComplete): ?>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>
<?php elseif ($isActive): ?>
<span class="pulse-dot-small"></span>
<?php endif; ?>
</div>
<div class="phase-info">
<span class="phase-name"><?= htmlspecialchars($phase['name'] ?? 'المرحلة ' . ($i + 1)) ?></span>
<span class="phase-mode"><?= htmlspecialchars($phase['format'] ?? '') ?></span>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="player-modal" id="player-modal" hidden>
<div class="player-modal-backdrop"></div>
<div class="player-modal-content">
<button class="modal-close" id="player-modal-close">&times;</button>
<div class="modal-player-header">
<div class="modal-avatar" id="modal-avatar"></div>
<h3 class="modal-player-name" id="modal-player-name"></h3>
<span class="modal-player-rating" id="modal-player-rating"></span>
</div>
<div class="modal-player-stats" id="modal-player-stats">
<div class="modal-stat">
<span class="modal-stat-value" id="modal-wins">0</span>
<span class="modal-stat-label">انتصارات</span>
</div>
<div class="modal-stat">
<span class="modal-stat-value" id="modal-draws">0</span>
<span class="modal-stat-label">تعادل</span>
</div>
<div class="modal-stat">
<span class="modal-stat-value" id="modal-losses">0</span>
<span class="modal-stat-label">هزائم</span>
</div>
</div>
<div class="modal-player-games" id="modal-player-games"></div>
</div>
</div>
<?php if (empty($players)): ?>
<div class="empty-state">لا يوجد لاعبون مسجلون</div>
<?php else: ?>
<div class="players-grid">
<?php foreach ($players as $player):
$name = $player['player_name'] ?? $player['name'] ?? 'لاعب';
$rating = $player['rating'] ?? $player['elo'] ?? null;
$seed = $player['seed_number'] ?? null;
$avatar = $player['avatar_url'] ?? '';
?>
<div class="player-card-mini" data-player="<?= htmlspecialchars(json_encode($player)) ?>">
<div class="player-avatar-small">
<?php if ($avatar): ?>
<img src="<?= htmlspecialchars($avatar) ?>" alt="">
<?php else: ?>
<span class="avatar-initials"><?= mb_substr($name, 0, 1) ?></span>
<?php endif; ?>
</div>
<div class="player-info-mini">
<span class="player-name-mini"><?= htmlspecialchars($name) ?></span>
<?php if ($rating): ?>
<span class="player-rating-mini"><?= $rating ?></span>
<?php endif; ?>
</div>
<?php if ($seed): ?>
<span class="player-seed">#<?= $seed ?></span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
$prizePool = $tournament['prize_pool'] ?? '';
$distribution = $tournament['prize_distribution'] ?? '';
if (is_string($distribution)) {
$distribution = json_decode($distribution, true) ?: [];
}
?>
<div class="prizes-display">
<?php if ($prizePool): ?>
<div class="prize-total">
<span class="prize-total-label">إجمالي الجوائز</span>
<span class="prize-total-value"><?= htmlspecialchars($prizePool) ?></span>
</div>
<?php endif; ?>
<?php if (!empty($distribution)): ?>
<div class="prize-podium">
<?php
$positions = ['first' => '🥇', 'second' => '🥈', 'third' => '🥉'];
$i = 0;
foreach ($distribution as $place => $prize):
$i++;
$medal = $positions[['first','second','third'][$i-1] ?? ''] ?? "#{$i}";
$maxPrize = max(array_values($distribution));
$pct = $maxPrize > 0 ? ($prize / $maxPrize) * 100 : 0;
?>
<div class="prize-bar-item">
<div class="prize-bar-info">
<span class="prize-medal"><?= $medal ?></span>
<span class="prize-place"><?= htmlspecialchars($place) ?></span>
<span class="prize-amount"><?= htmlspecialchars($prize) ?></span>
</div>
<div class="prize-bar-track">
<div class="prize-bar-fill" style="width: <?= $pct ?>%"></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php
$tickerItems = [];
if (!empty($tickerAds)) {
foreach ($tickerAds as $ta) {
$tickerItems[] = ['type' => 'ad', 'content' => $ta['content'], 'link' => $ta['link_url'] ?? ''];
}
}
$latestResults = $data['latest_results'] ?? [];
foreach ($latestResults as $r) {
$playerA = $r['playerAName'] ?? $r['player_a_name'] ?? $r['white'] ?? '';
$playerB = $r['playerBName'] ?? $r['player_b_name'] ?? $r['black'] ?? '';
$result = $r['result'] ?? '';
$tickerItems[] = ['type' => 'result', 'content' => "{$playerA} {$result} {$playerB}"];
}
if (empty($tickerItems)) return;
?>
<div class="ticker-bar" id="live-ticker">
<div class="ticker-track">
<?php foreach ($tickerItems as $item): ?>
<?php foreach ([1, 2] as $_dup): ?>
<span class="ticker-item ticker-<?= $item['type'] ?>">
<?php if ($item['type'] === 'result'): ?>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="10"/></svg>
<?php endif; ?>
<?php if (!empty($item['link'])): ?>
<a href="<?= htmlspecialchars($item['link']) ?>" target="_blank" rel="noopener"><?= htmlspecialchars($item['content']) ?></a>
<?php else: ?>
<?= htmlspecialchars($item['content']) ?>
<?php endif; ?>
</span>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
</div>
<?php
$slug = $tournament['slug'] ?? $tournament['id'];
$shareUrl = ($_SERVER['REQUEST_SCHEME'] ?? 'https') . '://' . ($_SERVER['HTTP_HOST'] ?? '') . '/live/' . $slug;
$shareText = $tournament['name'] . ' - بطولة مباشرة على El3ab';
$qrSvg = QRCodeService::generateSvg($shareUrl, 150);
?>
<div class="sharing-widget">
<h4 class="widget-title">مشاركة</h4>
<div class="share-url-box">
<input type="text" readonly value="<?= htmlspecialchars($shareUrl) ?>" class="share-url-input" id="share-url">
<button class="share-copy-btn" data-copy="<?= htmlspecialchars($shareUrl) ?>" title="نسخ الرابط">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</button>
</div>
<div class="share-buttons">
<a href="https://twitter.com/intent/tweet?text=<?= urlencode($shareText) ?>&url=<?= urlencode($shareUrl) ?>" target="_blank" rel="noopener" class="share-btn share-twitter" title="Twitter">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a>
<a href="https://wa.me/?text=<?= urlencode($shareText . ' ' . $shareUrl) ?>" target="_blank" rel="noopener" class="share-btn share-whatsapp" title="WhatsApp">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>
</a>
<a href="https://t.me/share/url?url=<?= urlencode($shareUrl) ?>&text=<?= urlencode($shareText) ?>" target="_blank" rel="noopener" class="share-btn share-telegram" title="Telegram">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M11.944 0A12 12 0 000 12a12 12 0 0012 12 12 12 0 0012-12A12 12 0 0012 0a12 12 0 00-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 01.171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.479.33-.913.492-1.302.48-.428-.013-1.252-.242-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>
</a>
</div>
<div class="share-qr">
<details>
<summary>رمز QR</summary>
<div class="qr-container"><?= $qrSvg ?></div>
</details>
</div>
<div class="share-embed">
<details>
<summary>كود التضمين</summary>
<textarea readonly class="embed-code" rows="3"><iframe src="<?= htmlspecialchars($shareUrl) ?>/embed" width="100%" height="600" frameborder="0"></iframe></textarea>
</details>
</div>
</div>
<?php if (empty($ad)) return; ?>
<div class="ad-slot ad-<?= htmlspecialchars($ad['position'] ?? 'between_sections') ?>" data-ad-id="<?= $ad['id'] ?? '' ?>">
<?php if (($ad['slot_type'] ?? '') === 'image'): ?>
<?php if (!empty($ad['link_url'])): ?>
<a href="<?= htmlspecialchars($ad['link_url']) ?>" target="_blank" rel="noopener sponsored" class="ad-link" data-track="click">
<img src="<?= htmlspecialchars($ad['content']) ?>" alt="<?= htmlspecialchars($ad['alt_text'] ?? 'إعلان') ?>" class="ad-image" loading="lazy">
</a>
<?php else: ?>
<img src="<?= htmlspecialchars($ad['content']) ?>" alt="<?= htmlspecialchars($ad['alt_text'] ?? 'إعلان') ?>" class="ad-image" loading="lazy">
<?php endif; ?>
<?php elseif (($ad['slot_type'] ?? '') === 'html'): ?>
<div class="ad-html"><?= $ad['content'] ?></div>
<?php endif; ?>
</div>
<?php if (empty($standings)): ?>
<div class="empty-state">لا يوجد ترتيب حالياً</div>
<?php else: ?>
<div class="standings-table-wrap">
<table class="standings-table">
<thead>
<tr>
<th class="col-rank">#</th>
<th class="col-player">اللاعب</th>
<th class="col-pts">النقاط</th>
<th class="col-games">م</th>
<th class="col-wins">ف</th>
<th class="col-draws">ت</th>
<th class="col-losses">خ</th>
<th class="col-tb">TB</th>
</tr>
</thead>
<tbody>
<?php foreach ($standings as $i => $row): ?>
<?php
$rank = $row['rank'] ?? $row['position'] ?? ($i + 1);
$name = $row['playerName'] ?? $row['player_name'] ?? $row['name'] ?? 'لاعب';
$points = $row['points'] ?? $row['score'] ?? 0;
$games = $row['gamesPlayed'] ?? $row['games_played'] ?? 0;
$wins = $row['wins'] ?? 0;
$draws = $row['draws'] ?? 0;
$losses = $row['losses'] ?? 0;
$tb = $row['tiebreak1'] ?? $row['buchholz'] ?? '-';
$isTop3 = $rank <= 3;
?>
<tr class="<?= $isTop3 ? 'top-rank rank-' . $rank : '' ?>" data-player-id="<?= htmlspecialchars($row['playerId'] ?? $row['player_id'] ?? '') ?>">
<td class="col-rank">
<?php if ($rank === 1): ?><span class="medal gold">🥇</span>
<?php elseif ($rank === 2): ?><span class="medal silver">🥈</span>
<?php elseif ($rank === 3): ?><span class="medal bronze">🥉</span>
<?php else: ?><?= $rank ?>
<?php endif; ?>
</td>
<td class="col-player">
<span class="player-name-link" data-player="<?= htmlspecialchars(json_encode($row)) ?>"><?= htmlspecialchars($name) ?></span>
</td>
<td class="col-pts"><strong><?= $points ?></strong></td>
<td class="col-games"><?= $games ?></td>
<td class="col-wins"><?= $wins ?></td>
<td class="col-draws"><?= $draws ?></td>
<td class="col-losses"><?= $losses ?></td>
<td class="col-tb"><?= $tb ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
<?php if (empty($stats)): ?>
<div class="empty-state">لا توجد إحصائيات</div>
<?php else: ?>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 21v-2a4 4 0 00-4-4H6a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
</div>
<div class="stat-value"><?= $stats['total_players'] ?? 0 ?></div>
<div class="stat-label">لاعب</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
</div>
<div class="stat-value"><?= $stats['completed_rounds'] ?? 0 ?> / <?= $stats['total_rounds'] ?? 0 ?></div>
<div class="stat-label">جولات مكتملة</div>
</div>
<div class="stat-card">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
</div>
<div class="stat-value"><?= ucfirst($stats['format'] ?? 'swiss') ?></div>
<div class="stat-label">النظام</div>
</div>
<?php if (!empty($stats['time_control'])): ?>
<div class="stat-card">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="7" width="20" height="14" rx="2"/><path d="M16 3v4M8 3v4"/></svg>
</div>
<div class="stat-value"><?= htmlspecialchars($stats['time_control']) ?></div>
<div class="stat-label">وقت اللعب</div>
</div>
<?php endif; ?>
<div class="stat-card">
<div class="stat-icon">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
</div>
<div class="stat-value"><?= $stats['active_players'] ?? 0 ?></div>
<div class="stat-label">لاعب نشط</div>
</div>
</div>
<?php endif; ?>
<?php if (empty($allRounds)): ?>
<div class="empty-state">لا توجد جولات</div>
<?php else: ?>
<div class="timeline-container">
<div class="timeline-progress">
<?php
$totalRounds = count($allRounds);
$completedRounds = 0;
foreach ($allRounds as $r) {
$hasResults = false;
foreach ($r['pairings'] ?? [] as $p) {
if (!empty($p['result'])) { $hasResults = true; break; }
}
if ($hasResults) $completedRounds++;
}
$pct = $totalRounds > 0 ? ($completedRounds / $totalRounds) * 100 : 0;
?>
<div class="timeline-bar">
<div class="timeline-bar-fill" style="width: <?= $pct ?>%"></div>
</div>
<span class="timeline-label"><?= $completedRounds ?> / <?= $totalRounds ?> جولات مكتملة</span>
</div>
<div class="timeline-rounds">
<?php foreach ($allRounds as $round):
$roundNum = $round['round_number'];
$roundPairings = $round['pairings'] ?? [];
$hasResults = false;
$allDone = true;
foreach ($roundPairings as $p) {
if (!empty($p['result'])) $hasResults = true;
else $allDone = false;
}
$roundStatus = empty($roundPairings) ? 'upcoming' : ($allDone ? 'completed' : ($hasResults ? 'in_progress' : 'paired'));
?>
<div class="timeline-round <?= $roundStatus ?>">
<div class="timeline-dot"></div>
<div class="timeline-round-info">
<span class="round-num">الجولة <?= $roundNum ?></span>
<span class="round-status-label">
<?php if ($roundStatus === 'completed'): ?>مكتملة
<?php elseif ($roundStatus === 'in_progress'): ?>جارية
<?php elseif ($roundStatus === 'paired'): ?>مقترنة
<?php else: ?>قادمة
<?php endif; ?>
</span>
</div>
<?php if (!empty($roundPairings)): ?>
<details class="round-details">
<summary><?= count($roundPairings) ?> مباراة</summary>
<div class="round-pairings-mini">
<?php foreach ($roundPairings as $p): ?>
<div class="mini-pairing">
<span><?= htmlspecialchars($p['playerAName'] ?? $p['player_a_name'] ?? $p['white'] ?? '?') ?></span>
<span class="mini-result"><?= $p['result'] ?? '-' ?></span>
<span><?= htmlspecialchars($p['playerBName'] ?? $p['player_b_name'] ?? $p['black'] ?? '?') ?></span>
</div>
<?php endforeach; ?>
</div>
</details>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
<?php
$visibility = $data['visibility'] ?? [];
$status = $data['status'] ?? 'draft';
$mode = $data['mode'] ?? 'swiss';
?>
<div class="live-embed" data-tournament-id="<?= htmlspecialchars($tournament['id']) ?>" data-status="<?= $status ?>">
<div class="embed-header">
<h2 class="embed-title"><?= htmlspecialchars($tournament['name']) ?></h2>
<span class="embed-badge status-<?= $status ?>"><?= $status === 'in_progress' ? 'مباشر' : ($status === 'completed' ? 'انتهت' : 'قريباً') ?></span>
</div>
<?php if (!empty($visibility['standings']) && !empty($data['standings'])): ?>
<div class="embed-section">
<?php $standings = $data['standings']; require __DIR__ . '/_standings.php'; ?>
</div>
<?php endif; ?>
<?php if (!empty($visibility['pairings']) && !empty($data['pairings'])): ?>
<div class="embed-section">
<?php $pairings = $data['pairings']; require __DIR__ . '/_pairings.php'; ?>
</div>
<?php endif; ?>
<div class="embed-footer">
<a href="/live/<?= htmlspecialchars($tournament['slug'] ?? $tournament['id']) ?>" target="_blank" class="embed-link">
عرض البطولة الكاملة على El3ab ↗
</a>
</div>
</div>
<?php
$visibility = $data['visibility'] ?? [];
$tournament = $data['tournament'];
$status = $data['status'] ?? 'draft';
$mode = $data['mode'] ?? 'swiss';
$ads = $data['ads'] ?? [];
$org = $data['organization'] ?? null;
$heroAds = array_filter($ads, fn($a) => $a['position'] === 'hero_top');
$footerAds = array_filter($ads, fn($a) => $a['position'] === 'footer');
$sidebarAds = array_filter($ads, fn($a) => in_array($a['position'], ['sidebar_top', 'sidebar_bottom']));
$betweenAds = array_filter($ads, fn($a) => $a['position'] === 'between_sections');
$tickerAds = array_filter($ads, fn($a) => $a['position'] === 'ticker');
?>
<div class="live-container" data-tournament-id="<?= htmlspecialchars($tournament['id']) ?>" data-status="<?= $status ?>" data-mode="<?= $mode ?>">
<?php if (!empty($tickerAds) && !empty($visibility['ticker'])): ?>
<?php require __DIR__ . '/_results_feed.php'; ?>
<?php endif; ?>
<?php if (!empty($heroAds)): ?>
<?php foreach ($heroAds as $ad): ?>
<?php require __DIR__ . '/_sponsors.php'; ?>
<?php endforeach; ?>
<?php endif; ?>
<?php require __DIR__ . '/_hero.php'; ?>
<?php if ($status === 'registration'): ?>
<?php require __DIR__ . '/_countdown.php'; ?>
<?php endif; ?>
<div class="live-body">
<div class="live-main">
<?php if (!empty($visibility['pairings']) && $status === 'in_progress'): ?>
<section class="live-section" id="pairings-section">
<h2 class="section-title"><span class="pulse-dot"></span> المباريات الجارية</h2>
<?php $pairings = $data['pairings'] ?? []; require __DIR__ . '/_pairings.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($betweenAds)): ?>
<?php $ad = reset($betweenAds); require __DIR__ . '/_sponsors.php'; ?>
<?php endif; ?>
<?php if (!empty($visibility['standings'])): ?>
<section class="live-section" id="standings-section">
<h2 class="section-title">الترتيب</h2>
<?php $standings = $data['standings'] ?? []; require __DIR__ . '/_standings.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['bracket']) && $mode === 'elimination'): ?>
<section class="live-section" id="bracket-section">
<h2 class="section-title">شجرة البطولة</h2>
<?php $bracket = $data['bracket'] ?? []; require __DIR__ . '/_bracket.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($phases) && count($phases) > 1): ?>
<section class="live-section" id="phases-section">
<h2 class="section-title">مراحل البطولة</h2>
<?php require __DIR__ . '/_phases.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['schedule']) && !empty($allRounds)): ?>
<section class="live-section" id="timeline-section">
<h2 class="section-title">الجولات</h2>
<?php require __DIR__ . '/_timeline.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['stats'])): ?>
<section class="live-section" id="stats-section">
<h2 class="section-title">إحصائيات</h2>
<?php $stats = $data['stats'] ?? []; require __DIR__ . '/_stats.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['players'])): ?>
<section class="live-section" id="players-section">
<h2 class="section-title">اللاعبون</h2>
<?php $players = $data['players'] ?? []; require __DIR__ . '/_players.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['prizes']) && !empty($tournament['prize_pool'])): ?>
<section class="live-section" id="prizes-section">
<h2 class="section-title">الجوائز</h2>
<?php require __DIR__ . '/_prizes.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['announcements'])): ?>
<section class="live-section" id="announcements-section">
<h2 class="section-title">الإعلانات</h2>
<?php $announcements = $data['announcements'] ?? []; require __DIR__ . '/_announcements.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['gallery']) && !empty($data['gallery'])): ?>
<section class="live-section" id="gallery-section">
<h2 class="section-title">معرض الصور</h2>
<?php $gallery = $data['gallery']; require __DIR__ . '/_gallery.php'; ?>
</section>
<?php endif; ?>
<?php if (!empty($visibility['rules']) && !empty($tournament['description'])): ?>
<section class="live-section" id="info-section">
<h2 class="section-title">القوانين والمعلومات</h2>
<?php require __DIR__ . '/_info.php'; ?>
</section>
<?php endif; ?>
</div>
<aside class="live-sidebar">
<?php if (!empty($sidebarAds)): ?>
<?php foreach ($sidebarAds as $ad): ?>
<?php require __DIR__ . '/_sponsors.php'; ?>
<?php endforeach; ?>
<?php endif; ?>
<?php require __DIR__ . '/_sharing.php'; ?>
<?php if (!empty($visibility['pairings']) && $mode !== 'elimination'): ?>
<div class="sidebar-widget" id="h2h-widget">
<?php require __DIR__ . '/_head2head.php'; ?>
</div>
<?php endif; ?>
</aside>
</div>
<?php if (!empty($footerAds)): ?>
<div class="live-footer-ads">
<?php foreach ($footerAds as $ad): ?>
<?php require __DIR__ . '/_sponsors.php'; ?>
<?php endforeach; ?>
</div>
<?php endif; ?>
<footer class="live-footer">
<div class="live-footer-inner">
<span class="footer-brand">El3ab Tournaments</span>
<span class="footer-sep">|</span>
<span class="footer-views"><?= number_format($tournament['view_count'] ?? 0) ?> مشاهدة</span>
</div>
</footer>
</div>
<?php require __DIR__ . '/_player_card.php'; ?>
......@@ -99,6 +99,17 @@ $tabs['arbiter'] = 'أدوات الحكم';
تعديل
</a>
<?php endif; ?>
<a href="/tournaments/<?= $tournament['id'] ?>/live-settings" class="btn btn-ghost" style="border-color:var(--brand-blue);">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polygon points="10 8 16 12 10 16 10 8"/></svg>
صفحة مباشرة
</a>
<?php if (!empty($tournament['live_enabled'])): ?>
<a href="/live/<?= htmlspecialchars($tournament['slug'] ?? $tournament['id']) ?>" target="_blank" class="btn btn-primary">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
عرض مباشر ↗
</a>
<?php endif; ?>
</div>
</div>
......
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