wasmdashai commited on
Commit
d81caf0
·
verified ·
1 Parent(s): b81a8aa

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1011 -283
index.html CHANGED
@@ -1,342 +1,1070 @@
 
1
  <!DOCTYPE html>
2
- <html lang="en">
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>LAHJA AI - Advanced Text-to-Speech</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
9
  <style>
10
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
11
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  body {
13
- font-family: 'Inter', sans-serif;
14
- background-color: #f8fafc;
15
- }
16
-
17
- .gradient-bg {
18
- background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #d946ef 100%);
19
- }
20
-
21
- .textarea-shadow {
22
- box-shadow: 0 4px 6px -1px rgba(79, 70, 229, 0.1), 0 2px 4px -1px rgba(79, 70, 229, 0.06);
23
- }
24
-
25
- .waveform {
26
- height: 60px;
27
- background: linear-gradient(90deg, #6366f1, #8b5cf6, #d946ef);
28
- opacity: 0.7;
29
- position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  }
32
-
33
- .waveform::before {
34
- content: "";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  position: absolute;
36
- top: 0;
37
- left: 0;
38
- right: 0;
39
- bottom: 0;
40
- background: linear-gradient(
41
- 90deg,
42
- transparent,
43
- rgba(255, 255, 255, 0.2),
44
- transparent
45
- );
46
- animation: wave 1.5s linear infinite;
47
- }
48
-
49
- @keyframes wave {
50
- 0% {
51
- transform: translateX(-100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
- 100% {
54
- transform: translateX(100%);
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  }
56
  }
57
-
58
- .audio-player {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  transition: all 0.3s ease;
 
 
60
  }
61
-
62
- .audio-player:hover {
63
- transform: translateY(-2px);
64
- box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
 
 
 
 
 
65
  }
66
  </style>
67
  </head>
68
  <body>
69
- <div class="min-h-screen flex flex-col">
70
- <!-- Header -->
71
- <header class="gradient-bg text-white py-6 shadow-lg">
72
- <div class="container mx-auto px-4">
73
- <div class="flex justify-between items-center">
74
- <div class="flex items-center space-x-2">
75
- <i class="fas fa-wave-square text-2xl"></i>
76
- <h1 class="text-2xl font-bold">LAHJA AI</h1>
77
- </div>
78
- <div class="hidden md:flex items-center space-x-4">
79
- <span class="text-sm font-medium bg-white/20 px-3 py-1 rounded-full">VITS Architecture</span>
80
- <span class="text-sm font-medium bg-white/20 px-3 py-1 rounded-full">Transformers</span>
81
- </div>
82
  </div>
83
- <p class="mt-2 text-sm opacity-80 max-w-2xl">
84
- Advanced AI-powered text-to-speech with accent-aware synthesis using cutting-edge VITS architecture and transformer models.
85
- </p>
86
  </div>
87
  </header>
88
 
89
- <!-- Main Content -->
90
- <main class="flex-grow container mx-auto px-4 py-8">
91
- <div class="max-w-4xl mx-auto">
92
- <div class="bg-white rounded-xl shadow-lg overflow-hidden">
93
- <!-- Input Section -->
94
- <div class="p-6 border-b border-gray-100">
95
- <h2 class="text-xl font-semibold text-gray-800 mb-4">Text Input</h2>
96
- <div class="relative">
97
- <textarea id="textInput"
98
- class="w-full h-48 px-4 py-3 border border-gray-200 rounded-lg textarea-shadow focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 outline-none transition duration-200 resize-none"
99
- placeholder="Enter the text you want to convert to speech...">السلام عليكم كيف الحال اخبارك علومك وش مسوي بالله وش الجديد </textarea>
100
- <div class="absolute bottom-3 right-3 flex items-center space-x-2">
101
- <span id="charCount" class="text-xs text-gray-500">0 characters</span>
102
- <button id="clearBtn" class="text-gray-400 hover:text-gray-600 transition">
103
- <i class="fas fa-times"></i>
104
- </button>
105
- </div>
106
- </div>
 
 
 
 
107
 
108
- <div class="mt-6 flex flex-col sm:flex-row justify-between items-center space-y-4 sm:space-y-0">
109
- <div class="flex items-center space-x-4">
110
- <div class="flex items-center">
111
- <label for="voiceSelect" class="mr-2 text-sm font-medium text-gray-700">Voice:</label>
112
- <select id="voiceSelect" class="border border-gray-200 rounded-md px-3 py-1 text-sm focus:ring-indigo-500 focus:border-indigo-500 outline-none">
113
- <option value="SA2">Najdi Arabic Haba v2</option>
114
-
115
- <option value="us">American English</option>
116
- <option value="SA1">Najdi Arabic Haba v1</option>
117
-
118
- <option value="SA3">Najdi Arabic AHmmed v1</option>
119
- </select>
120
- </div>
121
- <div class="flex items-center">
122
- <label for="speedSelect" class="mr-2 text-sm font-medium text-gray-700">Speed:</label>
123
- <select id="speedSelect" class="border border-gray-200 rounded-md px-3 py-1 text-sm focus:ring-indigo-500 focus:border-indigo-500 outline-none">
124
- <option value="0.8">Slow</option>
125
- <option value="1.0" selected>Normal</option>
126
- <option value="1.2">Fast</option>
127
- </select>
128
- </div>
129
- </div>
130
-
131
- <button id="generateBtn" class="gradient-bg hover:opacity-90 text-white font-medium py-2 px-6 rounded-lg shadow-md transition duration-200 flex items-center">
132
- <i class="fas fa-play-circle mr-2"></i>
133
- Generate Voice
134
- </button>
135
  </div>
136
  </div>
 
 
 
 
137
 
138
- <!-- Output Section -->
139
- <div class="p-6">
140
- <h2 class="text-xl font-semibold text-gray-800 mb-4">Generated Audio</h2>
141
-
142
- <!-- Loading State -->
143
- <div id="loadingState" class="hidden">
144
- <div class="flex flex-col items-center justify-center py-8">
145
- <div class="waveform w-full rounded-lg mb-4"></div>
146
- <p class="text-gray-600 font-medium">Processing your request with LAHJA AI...</p>
147
- <p class="text-sm text-gray-500 mt-1">This may take a few moments</p>
148
- </div>
149
  </div>
150
-
151
- <!-- Audio Player -->
152
- <div id="audioPlayerContainer" class="hidden">
153
- <div class="audio-player bg-gradient-to-r from-indigo-50 to-purple-50 rounded-xl p-4 border border-gray-200">
154
- <div class="flex items-center justify-between mb-3">
155
- <div class="flex items-center space-x-3">
156
- <i class="fas fa-headphones text-indigo-600 text-xl"></i>
157
- <div>
158
- <h3 class="font-medium text-gray-800">Generated Speech</h3>
159
- <p class="text-xs text-gray-500" id="audioInfo">American English • Normal speed</p>
160
- </div>
161
- </div>
162
- <button id="downloadBtn" class="text-indigo-600 hover:text-indigo-800 transition">
163
- <i class="fas fa-download"></i>
164
- </button>
165
- </div>
166
- <audio id="audioPlayer" controls class="w-full"></audio>
167
- </div>
168
  </div>
169
-
170
- <!-- Empty State -->
171
- <div id="emptyState" class="flex flex-col items-center justify-center py-12 text-center">
172
- <i class="fas fa-comment-dots text-4xl text-gray-300 mb-4"></i>
173
- <h3 class="text-lg font-medium text-gray-700">No audio generated yet</h3>
174
- <p class="text-gray-500 max-w-md mt-1">Enter some text above and click "Generate Voice" to create your speech.</p>
 
 
 
175
  </div>
176
  </div>
177
  </div>
178
 
179
- <!-- Features Section -->
180
- <div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-6">
181
- <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
182
- <div class="text-indigo-600 mb-3">
183
- <i class="fas fa-microchip text-2xl"></i>
184
- </div>
185
- <h3 class="font-semibold text-lg mb-2">VITS Architecture</h3>
186
- <p class="text-gray-600 text-sm">
187
- Our advanced VITS model synthesizes realistic audio waveforms directly from text with exceptional clarity and naturalness.
188
- </p>
 
 
 
 
 
 
 
 
189
  </div>
190
- <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
191
- <div class="text-purple-600 mb-3">
192
- <i class="fas fa-language text-2xl"></i>
 
 
 
 
 
 
193
  </div>
194
- <h3 class="font-semibold text-lg mb-2">Accent-Aware</h3>
195
- <p class="text-gray-600 text-sm">
196
- Captures local vocal characteristics and intonation patterns for authentic regional speech synthesis.
197
- </p>
198
  </div>
199
- <div class="bg-white p-6 rounded-xl shadow-sm border border-gray-100">
200
- <div class="text-pink-600 mb-3">
201
- <i class="fas fa-brain text-2xl"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  </div>
203
- <h3 class="font-semibold text-lg mb-2">Transformer Models</h3>
204
- <p class="text-gray-600 text-sm">
205
- Deep linguistic analysis enables context-aware speech generation that reflects natural human expression.
206
- </p>
207
  </div>
208
  </div>
209
  </div>
210
- </main>
211
-
212
- <!-- Footer -->
213
- <footer class="bg-gray-50 py-6 border-t border-gray-200">
214
- <div class="container mx-auto px-4 text-center">
215
- <p class="text-gray-500 text-sm">
216
- &copy; 2025 LAHJA AI. Advanced text-to-speech powered by VITS architecture and transformer models.
217
- </p>
218
- </div>
219
- </footer>
220
  </div>
221
-
222
- <script type="module">
223
- import {Client} from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
224
-
225
- document.addEventListener('DOMContentLoaded', async function () {
226
- // Connect to Gradio client (must be inside async function)
227
- const client = await Client.connect("wasmdashai/DemoLahja");
228
-
229
- // DOM Elements
230
- const textInput = document.getElementById('textInput');
231
- const charCount = document.getElementById('charCount');
232
- const clearBtn = document.getElementById('clearBtn');
233
- const generateBtn = document.getElementById('generateBtn');
234
- const voiceSelect = document.getElementById('voiceSelect');
235
- const speedSelect = document.getElementById('speedSelect');
236
- const loadingState = document.getElementById('loadingState');
237
- const audioPlayerContainer = document.getElementById('audioPlayerContainer');
238
- const emptyState = document.getElementById('emptyState');
239
- const audioPlayer = document.getElementById('audioPlayer');
240
- const downloadBtn = document.getElementById('downloadBtn');
241
- const audioInfo = document.getElementById('audioInfo');
242
-
243
- const voiceLabels = {
244
- 'us': 'American English',
245
- 'SA1': 'Najdi Arabic Haba v1',
246
- 'SA2': 'Najdi Arabic Haba v2',
247
- 'SA3': 'Najdi Arabic AHmmed v1',
248
-
249
- };
250
-
251
- const voiceModels = {
252
- 'us': 'wasmdashai/vits-en-v1',
253
- 'SA1': 'wasmdashai/vits-ar-sa-huba-v1',
254
- 'SA2': 'wasmdashai/vits-ar-sa-huba-v2',
255
- 'SA3': 'wasmdashai/vits-ar-sa-A',
256
-
257
-
258
- };
259
-
260
- const speedLabels = {
261
- '0.3': 'Slow',
262
- '1.0': 'Normal',
263
- '1.2': 'Fast'
264
- };
265
-
266
- // Update character count
267
- textInput.addEventListener('input', function () {
268
- const count = textInput.value.length;
269
- charCount.textContent = `${count} characters`;
270
- clearBtn.classList.toggle('invisible', count === 0);
271
- });
272
 
273
- // Clear text input
274
- clearBtn.addEventListener('click', function () {
275
- textInput.value = '';
276
- charCount.textContent = '0 characters';
277
- clearBtn.classList.add('invisible');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
  });
 
279
 
280
- // Generate voice
281
- generateBtn.addEventListener('click', async function () {
282
- const text = textInput.value.trim();
283
- if (!text) {
284
- alert('Please enter some text to convert to speech.');
285
- return;
 
 
 
 
 
286
  }
 
 
287
 
288
- const voice = voiceSelect.value;
289
- const speed = parseFloat(speedSelect.value);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
 
291
- loadingState.classList.remove('hidden');
292
- audioPlayerContainer.classList.add('hidden');
293
- emptyState.classList.add('hidden');
294
- console.log("start result:", voice);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
295
 
296
- try {
297
- const result = await client.predict("/predict", {
298
- text: text,
299
- name_model: voiceModels[voice],
300
- speaking_rate: speed
301
- });
302
- console.log("Prediction result:", result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
- const audioUrl = result.data?.[0]?.url;
305
- if (!audioUrl) throw new Error("No audio URL received");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
 
307
- audioPlayer.src = audioUrl;
308
- audioInfo.textContent = `${voiceLabels[voice]} • ${speedLabels[speed.toFixed(1)]} speed`;
309
-
310
- loadingState.classList.add('hidden');
311
- audioPlayerContainer.classList.remove('hidden');
312
-
313
- // Auto play
314
- setTimeout(() => {
315
- audioPlayer.play().catch(e => console.warn('Autoplay failed:', e));
316
- }, 300);
317
- } catch (err) {
318
- console.error("Error during prediction:", err);
319
- loadingState.classList.add('hidden');
320
- emptyState.classList.remove('hidden');
321
- }
 
 
 
 
 
 
 
 
322
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- // Download audio
325
- downloadBtn.addEventListener('click', function () {
326
- if (audioPlayer.src) {
327
- const a = document.createElement('a');
328
- a.href = audioPlayer.src;
329
- a.download = `lahja-ai-voice-${Date.now()}.mp3`;
330
- document.body.appendChild(a);
331
- a.click();
332
- document.body.removeChild(a);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
  }
334
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
- // Initialize char count
337
- textInput.dispatchEvent(new Event('input'));
 
 
 
 
 
 
 
 
 
338
  });
339
- </script>
340
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  </body>
342
  </html>
 
1
+
2
  <!DOCTYPE html>
3
+ <html lang="ar" dir="rtl">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>نظام رسم المسارات البحرية المتقدم</title>
8
+ <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
11
  <style>
12
+ :root {
13
+ --primary: #1e3a8a;
14
+ --secondary: #3b82f6;
15
+ --accent: #60a5fa;
16
+ --success: #059669;
17
+ --warning: #d97706;
18
+ --danger: #dc2626;
19
+ --dark: #0f172a;
20
+ --light: #f8fafc;
21
+ --background: #ffffff;
22
+ --text: #1e293b;
23
+ --border: #e2e8f0;
24
+ --original-route: #1e3a8a;
25
+ --generated-route: #dc2626;
26
+ --original-point: #3b82f6;
27
+ --generated-point: #ef4444;
28
+ }
29
+
30
+ * {
31
+ margin: 0;
32
+ padding: 0;
33
+ box-sizing: border-box;
34
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
35
+ }
36
+
37
  body {
38
+ background: var(--background);
39
+ color: var(--text);
40
+ min-height: 100vh;
41
+ overflow-x: hidden;
42
+ }
43
+
44
+ .container {
45
+ max-width: 1800px;
46
+ margin: 0 auto;
47
+ padding: 20px;
48
+ }
49
+
50
+ header {
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ padding: 20px;
55
+ background: var(--background);
56
+ border-radius: 16px;
57
+ margin-bottom: 25px;
58
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
59
+ border: 1px solid var(--border);
60
+ }
61
+
62
+ .logo {
63
+ display: flex;
64
+ align-items: center;
65
+ gap: 15px;
66
+ }
67
+
68
+ .logo i {
69
+ font-size: 2.5rem;
70
+ color: var(--primary);
71
+ }
72
+
73
+ .logo-text h1 {
74
+ font-size: 2.2rem;
75
+ margin-bottom: 5px;
76
+ color: var(--primary);
77
+ }
78
+
79
+ .logo-text p {
80
+ font-size: 1rem;
81
+ color: var(--dark);
82
+ opacity: 0.8;
83
+ }
84
+
85
+ .app-container {
86
+ display: grid;
87
+ grid-template-columns: 1fr 3fr;
88
+ gap: 25px;
89
+ height: calc(100vh - 180px);
90
+ }
91
+
92
+ .control-panel {
93
+ background: var(--background);
94
+ border-radius: 16px;
95
+ padding: 25px;
96
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
97
+ border: 1px solid var(--border);
98
+ overflow-y: auto;
99
+ }
100
+
101
+ .map-container {
102
+ background: var(--background);
103
+ border-radius: 16px;
104
  overflow: hidden;
105
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
106
+ border: 1px solid var(--border);
107
+ position: relative;
108
+ }
109
+
110
+ #map {
111
+ height: 100%;
112
+ width: 100%;
113
+ background: #e8f4f8;
114
+ }
115
+
116
+ .panel-section {
117
+ margin-bottom: 30px;
118
+ }
119
+
120
+ .section-title {
121
+ display: flex;
122
+ align-items: center;
123
+ gap: 10px;
124
+ margin-bottom: 20px;
125
+ padding-bottom: 10px;
126
+ border-bottom: 2px solid var(--primary);
127
+ font-size: 1.3rem;
128
+ color: var(--primary);
129
+ }
130
+
131
+ .section-title i {
132
+ color: var(--primary);
133
  }
134
+
135
+ .file-upload {
136
+ border: 2px dashed var(--border);
137
+ border-radius: 12px;
138
+ padding: 25px;
139
+ text-align: center;
140
+ cursor: pointer;
141
+ transition: all 0.3s ease;
142
+ margin-bottom: 20px;
143
+ }
144
+
145
+ .file-upload:hover {
146
+ border-color: var(--primary);
147
+ background: var(--light);
148
+ }
149
+
150
+ .file-upload i {
151
+ font-size: 3rem;
152
+ margin-bottom: 15px;
153
+ color: var(--primary);
154
+ }
155
+
156
+ .form-group {
157
+ margin-bottom: 20px;
158
+ }
159
+
160
+ label {
161
+ display: block;
162
+ margin-bottom: 8px;
163
+ font-weight: 600;
164
+ color: var(--dark);
165
+ }
166
+
167
+ input, select, button {
168
+ width: 100%;
169
+ padding: 14px;
170
+ border: 1px solid var(--border);
171
+ border-radius: 10px;
172
+ font-size: 16px;
173
+ background: var(--background);
174
+ color: var(--text);
175
+ }
176
+
177
+ input::placeholder {
178
+ color: var(--dark);
179
+ opacity: 0.6;
180
+ }
181
+
182
+ button {
183
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
184
+ color: white;
185
+ border: none;
186
+ cursor: pointer;
187
+ transition: all 0.3s ease;
188
+ font-weight: 600;
189
+ margin-top: 10px;
190
+ display: flex;
191
+ align-items: center;
192
+ justify-content: center;
193
+ gap: 10px;
194
+ }
195
+
196
+ button:hover {
197
+ transform: translateY(-2px);
198
+ box-shadow: 0 6px 20px rgba(37, 99, 235, 0.3);
199
+ }
200
+
201
+ .btn-secondary {
202
+ background: linear-gradient(135deg, var(--warning) 0%, #e58e0c 100%);
203
+ }
204
+
205
+ .btn-danger {
206
+ background: linear-gradient(135deg, var(--danger) 0%, #dc2626 100%);
207
+ }
208
+
209
+ .stats-grid {
210
+ display: grid;
211
+ grid-template-columns: 1fr 1fr;
212
+ gap: 15px;
213
+ margin-top: 20px;
214
+ }
215
+
216
+ .stat-card {
217
+ background: var(--light);
218
+ border-radius: 12px;
219
+ padding: 15px;
220
+ text-align: center;
221
+ border: 1px solid var(--border);
222
+ transition: all 0.3s ease;
223
+ }
224
+
225
+ .stat-card:hover {
226
+ transform: translateY(-3px);
227
+ box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
228
+ }
229
+
230
+ .stat-value {
231
+ font-size: 1.8rem;
232
+ font-weight: bold;
233
+ margin: 10px 0;
234
+ color: var(--primary);
235
+ }
236
+
237
+ .stat-label {
238
+ font-size: 0.9rem;
239
+ color: var(--dark);
240
+ opacity: 0.8;
241
+ }
242
+
243
+ .route-item {
244
+ background: var(--light);
245
+ border-radius: 12px;
246
+ padding: 15px;
247
+ margin-bottom: 15px;
248
+ cursor: pointer;
249
+ transition: all 0.3s ease;
250
+ border-left: 4px solid var(--primary);
251
+ border: 1px solid var(--border);
252
+ }
253
+
254
+ .route-item:hover {
255
+ background: var(--background);
256
+ transform: translateX(-5px);
257
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
258
+ }
259
+
260
+ .route-header {
261
+ display: flex;
262
+ justify-content: space-between;
263
+ align-items: center;
264
+ margin-bottom: 10px;
265
+ }
266
+
267
+ .route-name {
268
+ font-weight: bold;
269
+ font-size: 1.1rem;
270
+ color: var(--dark);
271
+ }
272
+
273
+ .route-type {
274
+ background: var(--primary);
275
+ color: white;
276
+ padding: 4px 10px;
277
+ border-radius: 20px;
278
+ font-size: 0.8rem;
279
+ }
280
+
281
+ .route-details {
282
+ display: flex;
283
+ justify-content: space-between;
284
+ font-size: 0.9rem;
285
+ color: var(--dark);
286
+ opacity: 0.8;
287
+ }
288
+
289
+ .map-overlay {
290
  position: absolute;
291
+ top: 20px;
292
+ left: 20px;
293
+ right: 20px;
294
+ z-index: 1000;
295
+ display: flex;
296
+ gap: 15px;
297
+ }
298
+
299
+ .search-box {
300
+ flex: 1;
301
+ background: var(--background);
302
+ border-radius: 12px;
303
+ padding: 12px 20px;
304
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
305
+ display: flex;
306
+ align-items: center;
307
+ gap: 10px;
308
+ border: 1px solid var(--border);
309
+ }
310
+
311
+ .search-box input {
312
+ background: transparent;
313
+ border: none;
314
+ flex: 1;
315
+ padding: 0;
316
+ }
317
+
318
+ .map-controls {
319
+ display: flex;
320
+ gap: 10px;
321
+ }
322
+
323
+ .control-btn {
324
+ width: 50px;
325
+ height: 50px;
326
+ border-radius: 12px;
327
+ background: var(--background);
328
+ display: flex;
329
+ align-items: center;
330
+ justify-content: center;
331
+ cursor: pointer;
332
+ transition: all 0.3s ease;
333
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
334
+ border: 1px solid var(--border);
335
+ color: var(--primary);
336
+ }
337
+
338
+ .control-btn:hover {
339
+ background: var(--primary);
340
+ color: white;
341
+ transform: translateY(-3px);
342
+ }
343
+
344
+ .legend {
345
+ position: absolute;
346
+ bottom: 20px;
347
+ left: 20px;
348
+ background: var(--background);
349
+ border-radius: 12px;
350
+ padding: 15px;
351
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
352
+ z-index: 1000;
353
+ border: 1px solid var(--border);
354
+ }
355
+
356
+ .legend-title {
357
+ font-weight: bold;
358
+ margin-bottom: 10px;
359
+ text-align: center;
360
+ color: var(--primary);
361
+ }
362
+
363
+ .legend-items {
364
+ display: grid;
365
+ grid-template-columns: 1fr 1fr;
366
+ gap: 10px;
367
+ }
368
+
369
+ .legend-item {
370
+ display: flex;
371
+ align-items: center;
372
+ gap: 8px;
373
+ font-size: 0.9rem;
374
+ }
375
+
376
+ .legend-color {
377
+ width: 15px;
378
+ height: 15px;
379
+ border-radius: 50%;
380
+ }
381
+
382
+ .ship-marker {
383
+ background: var(--primary);
384
+ border: 3px solid white;
385
+ border-radius: 50%;
386
+ animation: pulse 2s infinite;
387
+ }
388
+
389
+ @keyframes pulse {
390
+ 0% { transform: scale(1); }
391
+ 50% { transform: scale(1.1); }
392
+ 100% { transform: scale(1); }
393
+ }
394
+
395
+ .route-popup {
396
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
397
+ min-width: 250px;
398
+ }
399
+
400
+ .popup-title {
401
+ font-weight: bold;
402
+ margin-bottom: 10px;
403
+ color: var(--primary);
404
+ border-bottom: 1px solid var(--border);
405
+ padding-bottom: 5px;
406
+ }
407
+
408
+ .popup-details {
409
+ display: grid;
410
+ grid-template-columns: 1fr 1fr;
411
+ gap: 10px;
412
+ margin-top: 10px;
413
+ }
414
+
415
+ .popup-detail {
416
+ font-size: 0.9rem;
417
+ }
418
+
419
+ .popup-label {
420
+ font-weight: bold;
421
+ color: var(--primary);
422
+ }
423
+
424
+ /* تحسينات للشريط الجانبي */
425
+ .control-panel::-webkit-scrollbar {
426
+ width: 8px;
427
+ }
428
+
429
+ .control-panel::-webkit-scrollbar-track {
430
+ background: var(--light);
431
+ border-radius: 10px;
432
+ }
433
+
434
+ .control-panel::-webkit-scrollbar-thumb {
435
+ background: var(--primary);
436
+ border-radius: 10px;
437
+ }
438
+
439
+ /* تصميم متجاوب */
440
+ @media (max-width: 1200px) {
441
+ .app-container {
442
+ grid-template-columns: 1fr;
443
+ grid-template-rows: auto 1fr;
444
  }
445
+
446
+ .control-panel {
447
+ max-height: 400px;
448
+ }
449
+ }
450
+
451
+ /* أنماط خاصة للمسارات */
452
+ .original-route {
453
+ stroke-dasharray: 10, 5;
454
+ animation: dash 20s linear infinite;
455
+ }
456
+
457
+ @keyframes dash {
458
+ to {
459
+ stroke-dashoffset: -1000;
460
  }
461
  }
462
+
463
+ .generated-point {
464
+ filter: drop-shadow(0 0 3px rgba(220, 38, 38, 0.5));
465
+ }
466
+
467
+ .original-point {
468
+ filter: drop-shadow(0 0 3px rgba(30, 58, 138, 0.5));
469
+ }
470
+
471
+ .route-comparison {
472
+ display: flex;
473
+ gap: 10px;
474
+ margin-top: 15px;
475
+ }
476
+
477
+ .comparison-btn {
478
+ flex: 1;
479
+ padding: 10px;
480
+ background: var(--light);
481
+ border: 1px solid var(--border);
482
+ border-radius: 8px;
483
+ cursor: pointer;
484
  transition: all 0.3s ease;
485
+ text-align: center;
486
+ font-size: 0.9rem;
487
  }
488
+
489
+ .comparison-btn:hover {
490
+ background: var(--primary);
491
+ color: white;
492
+ }
493
+
494
+ .comparison-btn.active {
495
+ background: var(--primary);
496
+ color: white;
497
  }
498
  </style>
499
  </head>
500
  <body>
501
+ <div class="container">
502
+ <header>
503
+ <div class="logo">
504
+ <i class="fas fa-ship"></i>
505
+ <div class="logo-text">
506
+ <h1>نظام المسارات البحرية المتقدم</h1>
507
+ <p>مقارنة المسارات الأصلية والمتولدة في البحر الأحمر</p>
 
 
 
 
 
 
508
  </div>
509
+ </div>
510
+ <div class="header-controls">
511
+ <button class="btn-secondary" id="exportBtn"><i class="fas fa-download"></i> تصدير التقرير</button>
512
  </div>
513
  </header>
514
 
515
+ <div class="app-container">
516
+ <div class="control-panel">
517
+ <div class="panel-section">
518
+ <div class="section-title">
519
+ <i class="fas fa-file-import"></i>
520
+ <h3>تحميل بيانات المسارات</h3>
521
+ </div>
522
+ <div class="file-upload" id="fileUpload">
523
+ <i class="fas fa-cloud-upload-alt"></i>
524
+ <p>انقر أو اسحب ملف Excel هنا</p>
525
+ <p class="file-format">يدعم ملفات CSV و Excel</p>
526
+ </div>
527
+ <input type="file" id="fileInput" accept=".csv,.xlsx,.xls" style="display: none;">
528
+
529
+ <div class="form-group">
530
+ <label for="routeType"><i class="fas fa-route"></i> نوع التحليل</label>
531
+ <select id="routeType">
532
+ <option value="comparison">مقارنة المسارات</option>
533
+ <option value="prediction">توقع المسارات المستقبلية</option>
534
+ <option value="anomaly">كشف الشذوذ في المسارات</option>
535
+ </select>
536
+ </div>
537
 
538
+ <div class="form-group">
539
+ <label><i class="fas fa-layer-group"></i> عرض المسارات</label>
540
+ <div class="route-comparison">
541
+ <div class="comparison-btn active" data-route="all">الكل</div>
542
+ <div class="comparison-btn" data-route="original">الأصلية فقط</div>
543
+ <div class="comparison-btn" data-route="generated">المتولدة فقط</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
544
  </div>
545
  </div>
546
+
547
+ <button id="analyzeBtn"><i class="fas fa-chart-line"></i> تحليل البيانات</button>
548
+ <button class="btn-secondary" id="generateRoutes"><i class="fas fa-wave-square"></i> توليد مسارات جديدة</button>
549
+ </div>
550
 
551
+ <div class="panel-section">
552
+ <div class="section-title">
553
+ <i class="fas fa-chart-bar"></i>
554
+ <h3>إحصائيات المسارات</h3>
555
+ </div>
556
+ <div class="stats-grid">
557
+ <div class="stat-card">
558
+ <div class="stat-label">المسارات الأصلية</div>
559
+ <div class="stat-value" id="originalRoutes">5</div>
560
+ <div class="stat-label">مسار</div>
 
561
  </div>
562
+ <div class="stat-card">
563
+ <div class="stat-label">المسارات المتولدة</div>
564
+ <div class="stat-value" id="generatedRoutes">8</div>
565
+ <div class="stat-label">مسار</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  </div>
567
+ <div class="stat-card">
568
+ <div class="stat-label">متوسط المسافة</div>
569
+ <div class="stat-value" id="avgDistance">1,845</div>
570
+ <div class="stat-label">كم</div>
571
+ </div>
572
+ <div class="stat-card">
573
+ <div class="stat-label">مطابقة المسارات</div>
574
+ <div class="stat-value" id="routeMatch">87%</div>
575
+ <div class="stat-label">نسبة</div>
576
  </div>
577
  </div>
578
  </div>
579
 
580
+ <div class="panel-section">
581
+ <div class="section-title">
582
+ <i class="fas fa-list"></i>
583
+ <h3>قائمة المسارات</h3>
584
+ </div>
585
+ <div id="routesList">
586
+ <!-- سيتم ملؤها ديناميكياً -->
587
+ </div>
588
+ </div>
589
+ </div>
590
+
591
+ <div class="map-container">
592
+ <div id="map"></div>
593
+
594
+ <div class="map-overlay">
595
+ <div class="search-box">
596
+ <i class="fas fa-search"></i>
597
+ <input type="text" placeholder="ابحث عن موقع أو مسار...">
598
  </div>
599
+ <div class="map-controls">
600
+ <div class="control-btn" id="zoomIn">
601
+ <i class="fas fa-plus"></i>
602
+ </div>
603
+ <div class="control-btn" id="zoomOut">
604
+ <i class="fas fa-minus"></i>
605
+ </div>
606
+ <div class="control-btn" id="locateRedSea">
607
+ <i class="fas fa-crosshairs"></i>
608
  </div>
 
 
 
 
609
  </div>
610
+ </div>
611
+
612
+ <div class="legend">
613
+ <div class="legend-title">مفتاح الخريطة</div>
614
+ <div class="legend-items">
615
+ <div class="legend-item">
616
+ <div class="legend-color" style="background: var(--original-route);"></div>
617
+ <span>مسار أصلي</span>
618
+ </div>
619
+ <div class="legend-item">
620
+ <div class="legend-color" style="background: var(--generated-route);"></div>
621
+ <span>مسار متولد</span>
622
+ </div>
623
+ <div class="legend-item">
624
+ <div class="legend-color" style="background: var(--original-point);"></div>
625
+ <span>نقطة أصلية</span>
626
+ </div>
627
+ <div class="legend-item">
628
+ <div class="legend-color" style="background: var(--generated-point);"></div>
629
+ <span>نقطة متولدة</span>
630
  </div>
 
 
 
 
631
  </div>
632
  </div>
633
  </div>
634
+ </div>
 
 
 
 
 
 
 
 
 
635
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
 
637
+ <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
638
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
639
+ <script>
640
+ // تهيئة الخريطة مع طبقة بحر نظيفة
641
+ const map = L.map('map', {
642
+ zoomControl: false,
643
+ attributionControl: false
644
+ }).setView([20.0, 38.0], 6);
645
+
646
+ // إضافة طبقة خريطة بحرية نظيفة (بدون أسماء الدول)
647
+ L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png', {
648
+ maxZoom: 10
649
+ }).addTo(map);
650
+
651
+ // إضافة طبقة خاصة بالبحر الأحمر
652
+ const redSeaBounds = L.rectangle([[12, 32], [31, 44]], {
653
+ color: "#1e3a8a",
654
+ fillColor: "#1e3a8a",
655
+ fillOpacity: 0.05,
656
+ weight: 2,
657
+ dashArray: '5, 5'
658
+ }).addTo(map);
659
+
660
+ // النقاط الرئيسية
661
+ const babMandeb = [12.6, 43.0]; // باب المندب
662
+ const suezCanal = [30.5, 32.3]; // قناة السويس
663
+
664
+ // إضافة نقاط البداية والنهاية
665
+ L.marker(babMandeb, {
666
+ icon: L.divIcon({
667
+ className: 'ship-marker',
668
+ html: '🚢',
669
+ iconSize: [30, 30]
670
+ })
671
+ }).addTo(map).bindPopup('<div class="route-popup"><div class="popup-title">باب المندب</div><div>نقطة البداية للملاحة في البحر الأحمر</div></div>').openPopup();
672
+
673
+ L.marker(suezCanal, {
674
+ icon: L.divIcon({
675
+ className: 'ship-marker',
676
+ html: '⚓',
677
+ iconSize: [30, 30]
678
+ })
679
+ }).addTo(map).bindPopup('<div class="route-popup"><div class="popup-title">قناة السويس</div><div>نقطة النهاية للملاحة في البحر الأحمر</div></div>');
680
+
681
+ // المتغيرات لتخزين المسارات
682
+ let originalRoutes = [];
683
+ let generatedRoutes = [];
684
+ let routeLayers = [];
685
+
686
+ // عناصر واجهة المستخدم
687
+ const fileUpload = document.getElementById('fileUpload');
688
+ const fileInput = document.getElementById('fileInput');
689
+ const analyzeBtn = document.getElementById('analyzeBtn');
690
+ const generateRoutesBtn = document.getElementById('generateRoutes');
691
+ const routesList = document.getElementById('routesList');
692
+ const originalRoutesElem = document.getElementById('originalRoutes');
693
+ const generatedRoutesElem = document.getElementById('generatedRoutes');
694
+ const avgDistanceElem = document.getElementById('avgDistance');
695
+ const routeMatchElem = document.getElementById('routeMatch');
696
+ const zoomInBtn = document.getElementById('zoomIn');
697
+ const zoomOutBtn = document.getElementById('zoomOut');
698
+ const locateRedSeaBtn = document.getElementById('locateRedSea');
699
+ const comparisonBtns = document.querySelectorAll('.comparison-btn');
700
+
701
+ // معالجة تحميل الملف
702
+ fileUpload.addEventListener('click', () => {
703
+ fileInput.click();
704
+ });
705
+
706
+ fileInput.addEventListener('change', (e) => {
707
+ const file = e.target.files[0];
708
+ if (file) {
709
+ processFile(file);
710
+ }
711
+ });
712
+
713
+ // معالجة أزرار المقارنة
714
+ comparisonBtns.forEach(btn => {
715
+ btn.addEventListener('click', () => {
716
+ comparisonBtns.forEach(b => b.classList.remove('active'));
717
+ btn.classList.add('active');
718
+
719
+ const routeType = btn.getAttribute('data-route');
720
+ filterRoutes(routeType);
721
  });
722
+ });
723
 
724
+ // دالة تصفية المسارات المعروضة
725
+ function filterRoutes(type) {
726
+ routeLayers.forEach(layer => {
727
+ if (type === 'all') {
728
+ map.addLayer(layer);
729
+ } else if (type === 'original' && layer.options.routeType === 'original') {
730
+ map.addLayer(layer);
731
+ } else if (type === 'generated' && layer.options.routeType === 'generated') {
732
+ map.addLayer(layer);
733
+ } else {
734
+ map.removeLayer(layer);
735
  }
736
+ });
737
+ }
738
 
739
+ // محاكاة بيانات المسارات (للتجربة)
740
+ function simulateRouteData() {
741
+ originalRoutes = [];
742
+ generatedRoutes = [];
743
+
744
+ // إنشاء مسارات أصلية
745
+ for (let i = 0; i < 5; i++) {
746
+ const route = generateOriginalRoute(babMandeb, suezCanal);
747
+ const distance = calculateRouteLength(route);
748
+
749
+ originalRoutes.push({
750
+ id: `original_${i + 1}`,
751
+ name: `المسار الأصلي ${i + 1}`,
752
+ type: 'original',
753
+ distance: distance,
754
+ route: route,
755
+ startTime: new Date(Date.now() - Math.random() * 86400000).toLocaleString('ar-SA'),
756
+ status: 'مكتمل'
757
+ });
758
+ }
759
+
760
+ // إنشاء مسارات متولدة
761
+ for (let i = 0; i < 8; i++) {
762
+ const originalRoute = originalRoutes[i % originalRoutes.length].route;
763
+ const route = generateRouteFromOriginal(originalRoute);
764
+ const distance = calculateRouteLength(route);
765
+
766
+ generatedRoutes.push({
767
+ id: `generated_${i + 1}`,
768
+ name: `المسار المتولد ${i + 1}`,
769
+ type: 'generated',
770
+ distance: distance,
771
+ route: route,
772
+ startTime: new Date(Date.now() - Math.random() * 86400000).toLocaleString('ar-SA'),
773
+ status: 'متوقع'
774
+ });
775
+ }
776
+ }
777
 
778
+ // دالة لتوليد مسار أصلي
779
+ function generateOriginalRoute(start, end, numPoints = 12) {
780
+ const points = [];
781
+
782
+ // إضافة نقطة البداية
783
+ points.push([start[0], start[1]]);
784
+
785
+ // توليد نقاط وسيطة بمسار مباشر
786
+ for (let i = 1; i < numPoints - 1; i++) {
787
+ const progress = i / (numPoints - 1);
788
+ const baseLat = start[0] + (end[0] - start[0]) * progress;
789
+ const baseLon = start[1] + (end[1] - start[1]) * progress;
790
+
791
+ // انحراف بسيط جداً للمسار الأصلي
792
+ const lat = baseLat + (Math.random() - 0.5) * 0.1;
793
+ const lon = baseLon + (Math.random() - 0.5) * 0.1;
794
+
795
+ points.push([lat, lon]);
796
+ }
797
+
798
+ // إضافة نقطة النهاية
799
+ points.push([end[0], end[1]]);
800
+
801
+ return points;
802
+ }
803
 
804
+ // دالة لتوليد مسار من المسار الأصلي
805
+ function generateRouteFromOriginal(originalRoute, numPoints = 15) {
806
+ const points = [];
807
+
808
+ // إضافة نقطة البداية
809
+ points.push([originalRoute[0][0], originalRoute[0][1]]);
810
+
811
+ // توليد نقاط وسيطة مع انحراف أكبر
812
+ for (let i = 1; i < numPoints - 1; i++) {
813
+ const progress = i / (numPoints - 1);
814
+ const originalIndex = Math.floor(progress * (originalRoute.length - 1));
815
+ const originalPoint = originalRoute[originalIndex];
816
+
817
+ // انحراف أكبر للمسار المتولد
818
+ const lat = originalPoint[0] + (Math.random() - 0.5) * 0.5;
819
+ const lon = originalPoint[1] + (Math.random() - 0.5) * 0.8;
820
+
821
+ points.push([lat, lon]);
822
+ }
823
+
824
+ // إضافة نقطة النهاية
825
+ points.push([originalRoute[originalRoute.length - 1][0], originalRoute[originalRoute.length - 1][1]]);
826
+
827
+ return points;
828
+ }
829
 
830
+ // دالة لحساب المسافة بين نقطتين
831
+ function calculateDistance(lat1, lon1, lat2, lon2) {
832
+ const R = 6371; // نصف قطر الأرض بالكيلومترات
833
+ const dLat = (lat2 - lat1) * Math.PI / 180;
834
+ const dLon = (lon2 - lon1) * Math.PI / 180;
835
+ const a =
836
+ Math.sin(dLat/2) * Math.sin(dLat/2) +
837
+ Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
838
+ Math.sin(dLon/2) * Math.sin(dLon/2);
839
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
840
+ return R * c;
841
+ }
842
+
843
+ // دالة لحساب طول المسار
844
+ function calculateRouteLength(route) {
845
+ let totalDistance = 0;
846
+ for (let i = 0; i < route.length - 1; i++) {
847
+ totalDistance += calculateDistance(
848
+ route[i][0], route[i][1],
849
+ route[i+1][0], route[i+1][1]
850
+ );
851
+ }
852
+ return Math.round(totalDistance);
853
+ }
854
+
855
+ // دالة لتحديث قائمة المسارات
856
+ function updateRoutesList() {
857
+ routesList.innerHTML = '';
858
 
859
+ const allRoutes = [...originalRoutes, ...generatedRoutes];
860
+
861
+ allRoutes.forEach(route => {
862
+ const routeItem = document.createElement('div');
863
+ routeItem.className = 'route-item';
864
+ routeItem.innerHTML = `
865
+ <div class="route-header">
866
+ <div class="route-name">${route.name}</div>
867
+ <div class="route-type" style="background: ${route.type === 'original' ? 'var(--original-route)' : 'var(--generated-route)'}">
868
+ ${route.type === 'original' ? 'أصلي' : 'متولد'}
869
+ </div>
870
+ </div>
871
+ <div class="route-details">
872
+ <span>المسافة: ${route.distance} كم</span>
873
+ <span>الحالة: ${route.status}</span>
874
+ </div>
875
+ `;
876
+
877
+ routeItem.addEventListener('click', () => {
878
+ drawRouteComparison(route);
879
+ });
880
+
881
+ routesList.appendChild(routeItem);
882
  });
883
+ }
884
+
885
+ // دالة لتحديث الإحصائيات
886
+ function updateStats() {
887
+ originalRoutesElem.textContent = originalRoutes.length;
888
+ generatedRoutesElem.textContent = generatedRoutes.length;
889
+
890
+ if (originalRoutes.length > 0 && generatedRoutes.length > 0) {
891
+ const totalDistance = [...originalRoutes, ...generatedRoutes].reduce((sum, route) => sum + route.distance, 0);
892
+ const avgDistance = Math.round(totalDistance / (originalRoutes.length + generatedRoutes.length));
893
+
894
+ avgDistanceElem.textContent = avgDistance.toLocaleString();
895
+ }
896
+ }
897
 
898
+ // دالة لرسم مقارنة المسارات
899
+ function drawRouteComparison(selectedRoute) {
900
+ // مسح المسارات السابقة
901
+ clearRoutes();
902
+
903
+ // تحديد المسار الأصلي المقابل إن وجد
904
+ let originalRouteToShow = null;
905
+ let generatedRouteToShow = null;
906
+
907
+ if (selectedRoute.type === 'original') {
908
+ originalRouteToShow = selectedRoute;
909
+ // إيجاد المسار المتولد المقابل
910
+ const correspondingGenerated = generatedRoutes.find(r =>
911
+ r.id === `generated_${selectedRoute.id.split('_')[1]}`
912
+ );
913
+ if (correspondingGenerated) {
914
+ generatedRouteToShow = correspondingGenerated;
915
+ }
916
+ } else {
917
+ generatedRouteToShow = selectedRoute;
918
+ // إيجاد المسار الأصلي المقابل
919
+ const correspondingOriginal = originalRoutes.find(r =>
920
+ r.id === `original_${selectedRoute.id.split('_')[1]}`
921
+ );
922
+ if (correspondingOriginal) {
923
+ originalRouteToShow = correspondingOriginal;
924
+ }
925
+ }
926
+
927
+ // رسم المسار الأصلي
928
+ if (originalRouteToShow) {
929
+ drawRoute(originalRouteToShow, 'original');
930
+ }
931
+
932
+ // رسم المسار المتولد
933
+ if (generatedRouteToShow) {
934
+ drawRoute(generatedRouteToShow, 'generated');
935
+ }
936
+
937
+ // إذا لم يكن هناك مسار مقابل، رسم المسار المحدد فقط
938
+ if (!originalRouteToShow && !generatedRouteToShow) {
939
+ drawRoute(selectedRoute, selectedRoute.type);
940
+ }
941
+ }
942
+
943
+ // دالة لرسم مسار على الخريطة
944
+ function drawRoute(route, routeType) {
945
+ const isOriginal = routeType === 'original';
946
+ const lineColor = isOriginal ? 'var(--original-route)' : 'var(--generated-route)';
947
+ const pointColor = isOriginal ? 'var(--original-point)' : 'var(--generated-point)';
948
+ const pointClass = isOriginal ? 'original-point' : 'generated-point';
949
+
950
+ // رسم الخط
951
+ const polyline = L.polyline(route.route, {
952
+ color: lineColor,
953
+ weight: isOriginal ? 4 : 3,
954
+ opacity: 0.9,
955
+ lineJoin: 'round',
956
+ className: isOriginal ? 'original-route' : '',
957
+ routeType: routeType
958
+ }).addTo(map);
959
+
960
+ // إضافة نقاط على طول المسار
961
+ route.route.forEach((point, index) => {
962
+ if (index % (isOriginal ? 2 : 1) === 0) { // نقاط أقل للمسار الأصلي
963
+ const pointMarker = L.circleMarker(point, {
964
+ color: pointColor,
965
+ fillColor: pointColor,
966
+ fillOpacity: 0.9,
967
+ radius: isOriginal ? 5 : 4,
968
+ className: pointClass
969
+ }).addTo(map);
970
+
971
+ pointMarker.bindPopup(`<div class="route-popup">
972
+ <div class="popup-title">${route.name}</div>
973
+ <div>${isOriginal ? 'نقطة أصلية' : 'نقطة متولدة'} ${index + 1}</div>
974
+ <div class="popup-details">
975
+ <div class="popup-detail"><span class="popup-label">خط العرض:</span> ${point[0].toFixed(4)}</div>
976
+ <div class="popup-detail"><span class="popup-label">خط الطول:</span> ${point[1].toFixed(4)}</div>
977
+ <div class="popup-detail"><span class="popup-label">المسافة:</span> ${route.distance} كم</div>
978
+ <div class="popup-detail"><span class="popup-label">النوع:</span> ${isOriginal ? 'أصلي' : 'متولد'}</div>
979
+ </div>
980
+ </div>`);
981
+
982
+ routeLayers.push(pointMarker);
983
  }
984
  });
985
+
986
+ routeLayers.push(polyline);
987
+
988
+ // ضبط حدود الخريطة لتناسب المسار
989
+ const group = new L.featureGroup([polyline]);
990
+ map.fitBounds(group.getBounds().pad(0.1));
991
+ }
992
+
993
+ // دالة لمسح جميع المسارات
994
+ function clearRoutes() {
995
+ routeLayers.forEach(layer => map.removeLayer(layer));
996
+ routeLayers = [];
997
+ }
998
 
999
+ // إضافة عناصر التحكم في الخريطة
1000
+ zoomInBtn.addEventListener('click', () => {
1001
+ map.zoomIn();
1002
+ });
1003
+
1004
+ zoomOutBtn.addEventListener('click', () => {
1005
+ map.zoomOut();
1006
+ });
1007
+
1008
+ locateRedSeaBtn.addEventListener('click', () => {
1009
+ map.setView([20.0, 38.0], 6);
1010
  });
 
1011
 
1012
+ // معالجة زر التحليل
1013
+ analyzeBtn.addEventListener('click', () => {
1014
+ if (originalRoutes.length === 0) {
1015
+ simulateRouteData();
1016
+ updateRoutesList();
1017
+ updateStats();
1018
+ }
1019
+
1020
+ // رسم أول مسار في القائمة
1021
+ if (originalRoutes.length > 0) {
1022
+ drawRouteComparison(originalRoutes[0]);
1023
+ }
1024
+ });
1025
+
1026
+ // معالجة زر توليد مسارات جديدة
1027
+ generateRoutesBtn.addEventListener('click', () => {
1028
+ // توليد مسارات متولدة جديدة
1029
+ generatedRoutes = [];
1030
+
1031
+ for (let i = 0; i < 8; i++) {
1032
+ const originalRoute = originalRoutes[i % originalRoutes.length].route;
1033
+ const route = generateRouteFromOriginal(originalRoute);
1034
+ const distance = calculateRouteLength(route);
1035
+
1036
+ generatedRoutes.push({
1037
+ id: `generated_${i + 1}`,
1038
+ name: `المسار المتولد ${i + 1}`,
1039
+ type: 'generated',
1040
+ distance: distance,
1041
+ route: route,
1042
+ startTime: new Date().toLocaleString('ar-SA'),
1043
+ status: 'متولد حديثاً'
1044
+ });
1045
+ }
1046
+
1047
+ updateRoutesList();
1048
+ updateStats();
1049
+
1050
+ if (generatedRoutes.length > 0) {
1051
+ drawRouteComparison(generatedRoutes[0]);
1052
+ }
1053
+ });
1054
+
1055
+ // تحميل بعض البيانات الافتراضية عند بدء التشغيل
1056
+ window.addEventListener('load', () => {
1057
+ simulateRouteData();
1058
+ updateRoutesList();
1059
+ updateStats();
1060
+
1061
+ // رسم أول مسار تلقائياً
1062
+ if (originalRoutes.length > 0) {
1063
+ setTimeout(() => {
1064
+ drawRouteComparison(originalRoutes[0]);
1065
+ }, 1000);
1066
+ }
1067
+ });
1068
+ </script>
1069
  </body>
1070
  </html>