752 lines
23 KiB
HTML
752 lines
23 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ar" dir="rtl">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Siro Admin – محاكاة لوحة التحكم والعمليات</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;900&display=swap" rel="stylesheet">
|
||
<style>
|
||
:root {
|
||
--bg: #090a0f;
|
||
--surface: #11131c;
|
||
--surface-elevated: #1a1d2b;
|
||
--border: #23273c;
|
||
--primary: #4776e6;
|
||
--primary-glow: rgba(71, 118, 230, 0.3);
|
||
--accent: #8e2de2;
|
||
--success: #10b981;
|
||
--warning: #f59e0b;
|
||
--danger: #ef4444;
|
||
--info: #06b6d4;
|
||
--text-primary: #f3f4f6;
|
||
--text-secondary: #9ca3af;
|
||
--divider: #1f2937;
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
body {
|
||
font-family: 'Tajawal', sans-serif;
|
||
background-color: var(--bg);
|
||
color: var(--text-primary);
|
||
min-height: 100vh;
|
||
overflow-x: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* Header styling with Glassmorphism */
|
||
header {
|
||
background: rgba(17, 19, 28, 0.85);
|
||
backdrop-filter: blur(12px);
|
||
border-bottom: 1px solid var(--border);
|
||
padding: 16px 24px;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
}
|
||
|
||
.logo-container {
|
||
width: 40px;
|
||
height: 40px;
|
||
background: linear-gradient(135deg, var(--primary), var(--accent));
|
||
border-radius: 10px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
font-weight: 900;
|
||
color: #ffffff;
|
||
font-size: 20px;
|
||
box-shadow: 0 0 15px var(--primary-glow);
|
||
}
|
||
|
||
.brand h1 {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
background: linear-gradient(to left, #ffffff, var(--text-secondary));
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
}
|
||
|
||
.header-status {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
}
|
||
|
||
.status-badge {
|
||
background: rgba(16, 185, 129, 0.1);
|
||
border: 1px solid var(--success);
|
||
color: var(--success);
|
||
padding: 6px 12px;
|
||
border-radius: 20px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.pulse {
|
||
width: 8px;
|
||
height: 8px;
|
||
background-color: var(--success);
|
||
border-radius: 50%;
|
||
animation: pulse-animation 2s infinite;
|
||
}
|
||
|
||
@keyframes pulse-animation {
|
||
0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
|
||
70% { transform: scale(1); box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
|
||
100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
|
||
}
|
||
|
||
/* Main layout setup */
|
||
.dashboard-container {
|
||
display: grid;
|
||
grid-template-columns: 280px 1fr;
|
||
flex: 1;
|
||
height: calc(100vh - 73px);
|
||
}
|
||
|
||
/* Sidebar controls */
|
||
.sidebar {
|
||
background-color: var(--surface);
|
||
border-left: 1px solid var(--border);
|
||
padding: 24px;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
|
||
.menu-section-title {
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: var(--text-secondary);
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.sidebar-btn {
|
||
width: 100%;
|
||
background: var(--surface-elevated);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
padding: 12px 16px;
|
||
color: var(--text-primary);
|
||
font-family: inherit;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
transition: all 0.2s ease;
|
||
text-align: right;
|
||
}
|
||
|
||
.sidebar-btn:hover {
|
||
border-color: var(--primary);
|
||
background: rgba(71, 118, 230, 0.05);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.sidebar-btn.active {
|
||
border-color: var(--primary);
|
||
background: linear-gradient(135deg, var(--primary), var(--accent));
|
||
color: #ffffff;
|
||
box-shadow: 0 4px 15px var(--primary-glow);
|
||
}
|
||
|
||
/* Workspace Panel */
|
||
.workspace {
|
||
display: grid;
|
||
grid-template-rows: auto 1fr;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Top stats row */
|
||
.stats-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 16px;
|
||
padding: 24px;
|
||
background-color: rgba(9, 10, 15, 0.5);
|
||
}
|
||
|
||
.stat-card {
|
||
background-color: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 16px;
|
||
padding: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.stat-card::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
right: 0;
|
||
left: 0;
|
||
height: 3px;
|
||
background: linear-gradient(to left, var(--primary), var(--accent));
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
.stat-card:hover::after {
|
||
opacity: 1;
|
||
}
|
||
|
||
.stat-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
color: var(--text-secondary);
|
||
font-size: 13px;
|
||
}
|
||
|
||
.stat-value {
|
||
font-size: 28px;
|
||
font-weight: 800;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.stat-change {
|
||
font-size: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.stat-change.up { color: var(--success); }
|
||
.stat-change.down { color: var(--danger); }
|
||
|
||
/* Action view area */
|
||
.action-view {
|
||
padding: 0 24px 24px 24px;
|
||
overflow-y: auto;
|
||
display: grid;
|
||
grid-template-columns: 2fr 1fr;
|
||
gap: 24px;
|
||
}
|
||
|
||
/* Card Panels */
|
||
.panel-card {
|
||
background-color: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 16px;
|
||
padding: 24px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.panel-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
border-bottom: 1px solid var(--border);
|
||
padding-bottom: 12px;
|
||
}
|
||
|
||
/* Simulation Canvas Map */
|
||
.map-panel {
|
||
position: relative;
|
||
height: 320px;
|
||
background-color: #0b0d19;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
}
|
||
|
||
canvas {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
|
||
.map-controls {
|
||
position: absolute;
|
||
bottom: 12px;
|
||
right: 12px;
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.map-btn {
|
||
background: rgba(17, 19, 28, 0.9);
|
||
border: 1px solid var(--border);
|
||
color: #fff;
|
||
padding: 6px 12px;
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.map-btn:hover {
|
||
background: var(--primary);
|
||
}
|
||
|
||
/* Logs view */
|
||
.logs-container {
|
||
max-height: 280px;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.log-item {
|
||
padding: 10px 14px;
|
||
border-radius: 10px;
|
||
font-size: 13px;
|
||
line-height: 1.4;
|
||
background-color: var(--surface-elevated);
|
||
border-right: 3px solid var(--border);
|
||
}
|
||
|
||
.log-item.success { border-color: var(--success); }
|
||
.log-item.warning { border-color: var(--warning); }
|
||
.log-item.danger { border-color: var(--danger); }
|
||
.log-item.info { border-color: var(--info); }
|
||
|
||
/* Forms */
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
label {
|
||
font-size: 13px;
|
||
color: var(--text-secondary);
|
||
font-weight: 500;
|
||
}
|
||
|
||
input, select {
|
||
background-color: var(--surface-elevated);
|
||
border: 1px solid var(--border);
|
||
border-radius: 10px;
|
||
padding: 12px;
|
||
color: #fff;
|
||
font-family: inherit;
|
||
font-size: 14px;
|
||
outline: none;
|
||
}
|
||
|
||
input:focus, select:focus {
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.submit-btn {
|
||
background: linear-gradient(135deg, var(--primary), var(--accent));
|
||
color: #fff;
|
||
border: none;
|
||
padding: 12px;
|
||
border-radius: 10px;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.submit-btn:hover {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* List queues (Captains documents) */
|
||
.doc-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background-color: var(--surface-elevated);
|
||
padding: 12px 16px;
|
||
border-radius: 12px;
|
||
border: 1px solid var(--border);
|
||
}
|
||
|
||
.doc-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
|
||
.doc-name { font-weight: 600; font-size: 14px; }
|
||
.doc-details { font-size: 12px; color: var(--text-secondary); }
|
||
|
||
.doc-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 6px 12px;
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
border: none;
|
||
font-family: inherit;
|
||
}
|
||
|
||
.action-btn.approve { background-color: rgba(16, 185, 129, 0.15); color: var(--success); }
|
||
.action-btn.reject { background-color: rgba(239, 68, 68, 0.15); color: var(--danger); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<!-- Header -->
|
||
<header>
|
||
<div class="brand">
|
||
<div class="logo-container">S</div>
|
||
<h1>Siro Admin — محاكاة المشرف والعمليات</h1>
|
||
</div>
|
||
<div class="header-status">
|
||
<div class="status-badge">
|
||
<span class="pulse"></span>
|
||
اتصال WebSocket نشط
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Container -->
|
||
<div class="dashboard-container">
|
||
|
||
<!-- Sidebar -->
|
||
<div class="sidebar">
|
||
<div>
|
||
<div class="menu-section-title">إدارة لوحة التحكم</div>
|
||
<button class="sidebar-btn active" onclick="switchTab('dashboard', this)">
|
||
<span>📊</span> لوحة التحكم الرئيسية
|
||
</button>
|
||
</div>
|
||
|
||
<div>
|
||
<div class="menu-section-title">التشغيل المالي والأسعار</div>
|
||
<button class="sidebar-btn" onclick="switchTab('kazan', this)">
|
||
<span>💰</span> عمولة Kazan والأسعار
|
||
</button>
|
||
</div>
|
||
|
||
<div>
|
||
<div class="menu-section-title">التوثيق والجودة</div>
|
||
<button class="sidebar-btn" onclick="switchTab('docs', this)">
|
||
<span>📑</span> وثائق الكباتن الجديدة
|
||
</button>
|
||
</div>
|
||
|
||
<div>
|
||
<div class="menu-section-title">الأمن والخصوصية</div>
|
||
<button class="sidebar-btn" onclick="switchTab('fraud', this)">
|
||
<span>🛡️</span> رادار بصمة الجهاز (الاحتيال)
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Workspace -->
|
||
<div class="workspace">
|
||
|
||
<!-- Stats Row -->
|
||
<div class="stats-row">
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span>إجمالي الركاب</span>
|
||
<span class="stat-change up">▲ 12%</span>
|
||
</div>
|
||
<div class="stat-value" id="countPassengers">18,240</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span>إجمالي الكباتن</span>
|
||
<span class="stat-change up">▲ 8%</span>
|
||
</div>
|
||
<div class="stat-value" id="countDrivers">4,912</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span>رحلات الشهر الحالي</span>
|
||
<span class="stat-change up">▲ 24%</span>
|
||
</div>
|
||
<div class="stat-value" id="countRides">32,490</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="stat-header">
|
||
<span>محفظة النظام (عمولات)</span>
|
||
<span class="stat-change down">▼ 2%</span>
|
||
</div>
|
||
<div class="stat-value" id="walletBalance">145,200 SP</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab Content Area -->
|
||
<div class="action-view">
|
||
|
||
<!-- Tab 1: Dashboard -->
|
||
<div id="tab-dashboard" class="panel-card" style="grid-column: 1 / 3;">
|
||
<div class="panel-title">📡 مراقبة الرحلات المباشرة والعمليات</div>
|
||
|
||
<div class="map-panel">
|
||
<canvas id="liveMapCanvas"></canvas>
|
||
<div class="map-controls">
|
||
<button class="map-btn" onclick="triggerMockRide()">محاكاة رحلة جديدة</button>
|
||
<button class="map-btn" onclick="clearSimulation()">مسح الخريطة</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="panel-title">📝 سجل الأحداث والعمليات الفورية</div>
|
||
<div class="logs-container" id="logsContainer">
|
||
<div class="log-item info">[النظام]: تم تشغيل محاكاة Siro Admin بنجاح.</div>
|
||
<div class="log-item success">[العمليات]: تم الاتصال بخادم الـ Websocket (rides.intaleq.xyz).</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 2: Kazan pricing -->
|
||
<div id="tab-kazan" class="panel-card" style="display:none;">
|
||
<div class="panel-title">💰 تعديل عمولة Kazan ومعدلات التعرفة</div>
|
||
<div class="form-group">
|
||
<label>الدولة والمنطقة</label>
|
||
<select id="countrySelect">
|
||
<option value="Syria">سوريا (دمشق)</option>
|
||
<option value="Jordan">الأردن (عمان)</option>
|
||
<option value="Egypt">مصر (القاهرة)</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>نسبة عمولة Kazan (%)</label>
|
||
<input type="number" id="commissionPct" value="15" min="5" max="30">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>تعرفة الكيلومتر الأساسية (عملة محلية)</label>
|
||
<input type="number" id="baseKmPrice" value="1200">
|
||
</div>
|
||
<button class="submit-btn" onclick="updateKazanCommission()">حفظ وتحديث نظام التسعير</button>
|
||
</div>
|
||
|
||
<!-- Tab 3: Captin Documents (Azure OCR Simulation) -->
|
||
<div id="tab-docs" class="panel-card" style="display:none;">
|
||
<div class="panel-title">📑 وثائق الكباتن بانتظار التدقيق والتحقق</div>
|
||
|
||
<div style="display:flex; flex-direction:column; gap:12px;" id="docsQueue">
|
||
<div class="doc-item" id="doc-c1">
|
||
<div class="doc-info">
|
||
<span class="doc-name">الكابتن: محمد أحمد الحموي</span>
|
||
<span class="doc-details">رقم السيارة: دمشق - 482920 • نوع المستند: رخصة القيادة</span>
|
||
<span class="doc-details" style="color:var(--success);">[تحليل الذكاء الاصطناعي Azure OCR]: الاسم والتواريخ متطابقة بنسبة 98%</span>
|
||
</div>
|
||
<div class="doc-actions">
|
||
<button class="action-btn approve" onclick="verifyDocument('c1', true)">قبول</button>
|
||
<button class="action-btn reject" onclick="verifyDocument('c1', false)">رفض</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="doc-item" id="doc-c2">
|
||
<div class="doc-info">
|
||
<span class="doc-name">الكابتن: رامي طارق المصري</span>
|
||
<span class="doc-details">رقم السيارة: ريف دمشق - 729221 • نوع المستند: تأمين المركبة</span>
|
||
<span class="doc-details" style="color:var(--warning);">[تحليل الذكاء الاصطناعي Azure OCR]: المستند ينتهي خلال 3 أيام</span>
|
||
</div>
|
||
<div class="doc-actions">
|
||
<button class="action-btn approve" onclick="verifyDocument('c2', true)">قبول</button>
|
||
<button class="action-btn reject" onclick="verifyDocument('c2', false)">رفض</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tab 4: Fraud radar device fingerprints -->
|
||
<div id="tab-fraud" class="panel-card" style="display:none;">
|
||
<div class="panel-title">🛡️ رادار كشف الاحتيال وتكرار بصمات الأجهزة</div>
|
||
<div class="log-item danger" style="padding:14px;">
|
||
<strong>تنبيه أمني هام:</strong> تم اكتشاف بصمة جهاز مكررة مرتبطة بـ 3 كباتن مختلفين!
|
||
<br>Device FP: <code>SHA256:d89ef239fbc87a1d...</code>
|
||
</div>
|
||
<div style="display:flex; flex-direction:column; gap:10px;">
|
||
<div class="doc-item">
|
||
<div class="doc-info">
|
||
<span class="doc-name">كابتن 1: علي سليم (نشط)</span>
|
||
<span class="doc-details">رقم الهاتف: 963992019283</span>
|
||
</div>
|
||
<button class="action-btn reject" style="background-color:rgba(239,68,68,0.25)" onclick="blockCaptain('علي سليم')">حظر فوري</button>
|
||
</div>
|
||
<div class="doc-item">
|
||
<div class="doc-info">
|
||
<span class="doc-name">كابتن 2: سامر وحيد (نشط)</span>
|
||
<span class="doc-details">رقم الهاتف: 963942091922</span>
|
||
</div>
|
||
<button class="action-btn reject" style="background-color:rgba(239,68,68,0.25)" onclick="blockCaptain('سامر وحيد')">حظر فوري</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<script>
|
||
// Tab switching logic
|
||
function switchTab(tabId, btn) {
|
||
document.getElementById('tab-dashboard').style.display = 'none';
|
||
document.getElementById('tab-kazan').style.display = 'none';
|
||
document.getElementById('tab-docs').style.display = 'none';
|
||
document.getElementById('tab-fraud').style.display = 'none';
|
||
|
||
document.getElementById('tab-' + tabId).style.display = 'flex';
|
||
|
||
const buttons = document.querySelectorAll('.sidebar-btn');
|
||
buttons.forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
|
||
if (tabId === 'dashboard') {
|
||
initCanvas();
|
||
}
|
||
}
|
||
|
||
// Logging helpers
|
||
function log(message, type = 'info') {
|
||
const container = document.getElementById('logsContainer');
|
||
const time = new Date().toLocaleTimeString('ar-EG');
|
||
const div = document.createElement('div');
|
||
div.className = `log-item ${type}`;
|
||
div.innerText = `[${time}] ${message}`;
|
||
container.prepend(div);
|
||
}
|
||
|
||
// Kazan changes
|
||
function updateKazanCommission() {
|
||
const pct = document.getElementById('commissionPct').value;
|
||
const kmPrice = document.getElementById('baseKmPrice').value;
|
||
const country = document.getElementById('countrySelect').value;
|
||
|
||
// Update stats
|
||
document.getElementById('walletBalance').innerText = `${(pct * 10000).toLocaleString()} SP`;
|
||
log(`[نظام كازان]: تم تحديث العمولة لتصبح ${pct}% لـ ${country} مع سعر كم قدره ${kmPrice}.`, 'success');
|
||
}
|
||
|
||
// Document approvals
|
||
function verifyDocument(id, approved) {
|
||
const element = document.getElementById(`doc-${id}`);
|
||
if (element) {
|
||
element.remove();
|
||
log(`[المستندات]: تم ${approved ? 'قبول' : 'رفض'} الوثائق للكابتن بنجاح.`, approved ? 'success' : 'danger');
|
||
}
|
||
}
|
||
|
||
// Block captain
|
||
function blockCaptain(name) {
|
||
log(`[الأمان والخصوصية]: تم حظر الكابتن (${name}) وتجميد محفظته بسبب مطابقة البصمة الرقمية المكررة.`, 'danger');
|
||
}
|
||
|
||
// Canvas map rendering
|
||
let canvas, ctx, animId;
|
||
let particles = [];
|
||
|
||
function initCanvas() {
|
||
canvas = document.getElementById('liveMapCanvas');
|
||
if (!canvas) return;
|
||
ctx = canvas.getContext('2d');
|
||
|
||
// Resize canvas relative to its container client dimensions
|
||
canvas.width = canvas.parentElement.clientWidth;
|
||
canvas.height = canvas.parentElement.clientHeight || 320;
|
||
|
||
// Seed initial dummy drivers
|
||
particles = [
|
||
{ x: canvas.width * 0.3, y: canvas.height * 0.4, label: '🚗 Comfort', angle: 0.5, speed: 0.3 },
|
||
{ x: canvas.width * 0.6, y: canvas.height * 0.7, label: '🏍️ Bike', angle: 1.2, speed: 0.6 },
|
||
{ x: canvas.width * 0.7, y: canvas.height * 0.3, label: '🚗 Speed', angle: 2.3, speed: 0.4 }
|
||
];
|
||
|
||
if (animId) cancelAnimationFrame(animId);
|
||
draw();
|
||
}
|
||
|
||
function draw() {
|
||
ctx.fillStyle = '#0b0d19';
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||
|
||
// Grid background
|
||
ctx.strokeStyle = '#181b2e';
|
||
ctx.lineWidth = 1;
|
||
for (let x = 0; x < canvas.width; x += 40) {
|
||
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
|
||
}
|
||
for (let y = 0; y < canvas.height; y += 40) {
|
||
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke();
|
||
}
|
||
|
||
// Draw cars
|
||
particles.forEach(p => {
|
||
p.x += Math.cos(p.angle) * p.speed;
|
||
p.y += Math.sin(p.angle) * p.speed;
|
||
|
||
// Boundary checks
|
||
if (p.x < 0 || p.x > canvas.width) p.angle = Math.PI - p.angle;
|
||
if (p.y < 0 || p.y > canvas.height) p.angle = -p.angle;
|
||
|
||
// Draw car indicator
|
||
ctx.fillStyle = '#4776e6';
|
||
ctx.beginPath();
|
||
ctx.arc(p.x, p.y, 8, 0, Math.PI * 2);
|
||
ctx.fill();
|
||
|
||
// Label
|
||
ctx.fillStyle = '#9ca3af';
|
||
ctx.font = '10px Tajawal';
|
||
ctx.fillText(p.label, p.x + 12, p.y + 4);
|
||
});
|
||
|
||
animId = requestAnimationFrame(draw);
|
||
}
|
||
|
||
function triggerMockRide() {
|
||
const newCar = {
|
||
x: canvas.width * 0.1,
|
||
y: canvas.height * 0.1,
|
||
label: '🚗 Speed (Active Ride)',
|
||
angle: 0.8,
|
||
speed: 1.2
|
||
};
|
||
particles.push(newCar);
|
||
log('[رحلة جديدة]: تم بدء رحلة نشطة للراكب #3829 مع الكابتن #4928.', 'info');
|
||
}
|
||
|
||
function clearSimulation() {
|
||
particles = [];
|
||
log('[النظام]: تم إخلاء الخريطة وتصفية كافة المركبات.', 'warning');
|
||
}
|
||
|
||
// Run canvas on page load
|
||
window.onload = initCanvas;
|
||
</script>
|
||
</body>
|
||
</html>
|