stat2025 commited on
Commit
63473f9
·
verified ·
1 Parent(s): e1e0812

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +547 -13
index.html CHANGED
@@ -1,23 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
  <html lang="ar" dir="rtl">
3
  <head>
4
  <meta charset="utf-8"/>
5
- <title>تحت المعالجة</title>
6
  <style>
7
- body {
8
- font-family: Arial, sans-serif;
9
- text-align: center;
10
- margin-top: 100px;
11
- background-color: #f8f9fa;
12
- color: #333;
13
- }
14
- h1 {
15
- color: red;
16
- }
17
  </style>
18
  </head>
19
  <body>
20
- <h1>🚧 جاري تعديل الكود 🚧</h1>
21
- <p>الصفحة تحت المعالجة حالياً، يرجى العودة لاحقاً.</p>
 
22
  </body>
23
  </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =======================
2
+ # 1) المتطلبات والرفع
3
+ # =======================
4
+ import os, zipfile, hashlib, json
5
+ import pandas as pd
6
+ import folium
7
+ from folium.plugins import LocateControl
8
+ from google.colab import files
9
+
10
+ print("↥ ارفع ملف Excel...")
11
+ uploaded = files.upload()
12
+ excel_file = list(uploaded.keys())[0]
13
+ print(f"تم الرفع: {excel_file}")
14
+
15
+ # =======================
16
+ # 2) قراءة الملف وتوحيد الأعمدة
17
+ # =======================
18
+ df = pd.read_excel(excel_file)
19
+ df.columns = df.columns.str.strip()
20
+ print("الأعمدة:", df.columns.tolist())
21
+
22
+ required_cols = ["رقم الباحث", "اسم الباحث", "اسم الحارة"]
23
+ for c in required_cols:
24
+ if c not in df.columns:
25
+ raise ValueError(f"الملف لا يحتوي على العمود: {c}")
26
+
27
+ df["researcher_id"] = df["رقم الباحث"].astype(str).str.strip()
28
+ df["researcher_name"] = df["اسم الباحث"].astype(str).fillna("").str.strip()
29
+
30
+ df["hara"] = df["اسم الحارة"].astype(str).fillna("غير محدد").str.strip()
31
+ df.loc[df["hara"].eq(""), "hara"] = "غير محدد"
32
+
33
+ rename_map = {}
34
+ if "اسم المنشأة" in df.columns: rename_map["اسم المنشأة"] = "est"
35
+ if "x" in df.columns: rename_map["x"] = "lon"
36
+ if "y" in df.columns: rename_map["y"] = "lat"
37
+ if "lon" in df.columns: rename_map["lon"] = "lon"
38
+ if "lat" in df.columns: rename_map["lat"] = "lat"
39
+ df = df.rename(columns=rename_map)
40
+
41
+ if "lon" not in df.columns or "lat" not in df.columns:
42
+ raise ValueError("يجب وجود إحداثيات: (x,y) أو (lon,lat)")
43
+
44
+ df["lon"] = pd.to_numeric(df["lon"], errors="coerce")
45
+ df["lat"] = pd.to_numeric(df["lat"], errors="coerce")
46
+
47
+ df["est"] = df.get("est", "بدون اسم").astype(str).fillna("بدون اسم").str.strip()
48
+ df.loc[df["est"].eq(""), "est"] = "بدون اسم"
49
+
50
+ # =======================
51
+ # 3) مجلد الخرج
52
+ # =======================
53
+ OUT_DIR = "pages"
54
+ os.makedirs(OUT_DIR, exist_ok=True)
55
+ pages = []
56
+
57
+ # =======================
58
+ # 4) أدوات مساعدة
59
+ # =======================
60
+ FOLIUM_COLORS = [
61
+ "red","blue","green","purple","orange","darkred","lightred","beige",
62
+ "darkblue","darkgreen","cadetblue","darkpurple","pink","lightblue",
63
+ "lightgreen","gray","black","lightgray"
64
+ ]
65
+ CSS_COLOR_MAP = {
66
+ "red":"#dc3545","blue":"#0d6efd","green":"#198754","purple":"#6f42c1","orange":"#fd7e14",
67
+ "darkred":"#8b0000","lightred":"#ff6b6b","beige":"#f5f5dc","darkblue":"#003f88",
68
+ "darkgreen":"#0b6623","cadetblue":"#5f9ea0","darkpurple":"#4b0082","pink":"#d63384",
69
+ "lightblue":"#74c0fc","lightgreen":"#8ce99a","gray":"#6c757d","black":"#212529","lightgray":"#ced4da"
70
+ }
71
+
72
+ def stable_folium_color(text: str) -> str:
73
+ t = (text or "غير محدد").strip() or "غير محدد"
74
+ h = hashlib.md5(t.encode("utf-8")).hexdigest()
75
+ return FOLIUM_COLORS[int(h[:8], 16) % len(FOLIUM_COLORS)]
76
+
77
+ def make_footer_html():
78
+ return """
79
+ <div style="
80
+ position: fixed;
81
+ bottom: 8px; left: 8px;
82
+ background: rgba(255,255,255,0.85);
83
+ padding: 6px 10px;
84
+ border-radius: 10px;
85
+ font-size: 13px;
86
+ direction: rtl;
87
+ z-index: 9999;
88
+ box-shadow: 0 1px 8px rgba(0,0,0,0.15);
89
+ font-family: Arial, sans-serif;
90
+ ">
91
+ تصميم وإعداد <b>نوف الناصر</b>
92
+ </div>
93
+ """
94
+
95
+ def make_stats_panel_bottom_right(title, total, hara_counts, css_color_map):
96
+ top3 = " | ".join([f"<b>{h}</b>: {int(c)}" for h, c in hara_counts.head(3).items()]) if len(hara_counts) else "—"
97
+ legend = ""
98
+ for h in hara_counts.index[:10]:
99
+ legend += f"""
100
+ <div style="display:flex;gap:6px;align-items:center;margin:4px 0;">
101
+ <span style="width:12px;height:12px;border-radius:50%;
102
+ background:{css_color_map.get(h,'#6c757d')};display:inline-block;border:1px solid rgba(0,0,0,0.2);"></span>
103
+ <span style="font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:170px;">{h}</span>
104
+ <span style="font-size:12px;color:#666;">({int(hara_counts[h])})</span>
105
+ </div>"""
106
+ more = ""
107
+ if len(hara_counts) > 10:
108
+ more = f"<div style='font-size:12px;color:#666;margin-top:6px;'>+ {len(hara_counts)-10} حارات أخرى</div>"
109
+
110
+ return f"""
111
+ <style>
112
+ .stats-box {{
113
+ position: fixed;
114
+ bottom: 14px;
115
+ right: 14px;
116
+ width: 300px;
117
+ background: rgba(255,255,255,0.92);
118
+ border-radius: 14px;
119
+ box-shadow: 0 2px 14px rgba(0,0,0,0.2);
120
+ z-index: 9999;
121
+ font-family: Arial, sans-serif;
122
+ overflow: hidden;
123
+ }}
124
+ .stats-head {{
125
+ padding:10px 12px;
126
+ font-weight:700;
127
+ font-size:14px;
128
+ border-bottom:1px solid rgba(0,0,0,0.08);
129
+ direction:rtl;
130
+ display:flex;
131
+ justify-content:space-between;
132
+ align-items:center;
133
+ gap:10px;
134
+ }}
135
+ .stats-title {{
136
+ white-space:nowrap;
137
+ overflow:hidden;
138
+ text-overflow:ellipsis;
139
+ max-width: 210px;
140
+ }}
141
+ .toggle {{
142
+ cursor:pointer;
143
+ background:rgba(0,0,0,0.06);
144
+ border-radius:10px;
145
+ padding:4px 8px;
146
+ font-size:12px;
147
+ }}
148
+ .stats-body {{
149
+ padding:10px 12px;
150
+ direction:rtl;
151
+ font-size:13px;
152
+ line-height:1.8;
153
+ }}
154
+ .legend {{
155
+ max-height: 180px;
156
+ overflow:auto;
157
+ padding-right: 2px;
158
+ margin-top: 8px;
159
+ }}
160
+ .stats-fab {{
161
+ position: fixed;
162
+ bottom: 14px;
163
+ right: 14px;
164
+ z-index: 9999;
165
+ display:none;
166
+ cursor:pointer;
167
+ background: rgba(255,255,255,0.92);
168
+ padding: 10px 12px;
169
+ border-radius: 14px;
170
+ box-shadow: 0 2px 14px rgba(0,0,0,0.2);
171
+ font-family: Arial, sans-serif;
172
+ font-size: 13px;
173
+ direction: rtl;
174
+ }}
175
+ hr {{
176
+ border:none;
177
+ border-top:1px solid rgba(0,0,0,0.08);
178
+ margin:10px 0;
179
+ }}
180
+ </style>
181
+
182
+ <div id="statsFab" class="stats-fab">إظهار الإحصاءات</div>
183
+
184
+ <div id="statsBox" class="stats-box">
185
+ <div class="stats-head">
186
+ <span class="stats-title">{title}</span>
187
+ <span class="toggle" id="hideStats">إخفاء</span>
188
+ </div>
189
+ <div class="stats-body" id="statsBody">
190
+ <b>عدد المواقع:</b> {int(total)}<br>
191
+ <b>عدد الحارات:</b> {int(len(hara_counts))}<br>
192
+ <b>الأكثر:</b> {top3}
193
+ <hr>
194
+ <b>تلوين حسب الحارة</b>
195
+ <div class="legend">
196
+ {legend}
197
+ {more}
198
+ </div>
199
+ </div>
200
+ </div>
201
+
202
+ <script>
203
+ (function(){{
204
+ const box = document.getElementById('statsBox');
205
+ const fab = document.getElementById('statsFab');
206
+ const hide = document.getElementById('hideStats');
207
+
208
+ hide.addEventListener('click', function(){{
209
+ box.style.display = 'none';
210
+ fab.style.display = 'block';
211
+ }});
212
+
213
+ fab.addEventListener('click', function(){{
214
+ box.style.display = 'block';
215
+ fab.style.display = 'none';
216
+ }});
217
+ }})();
218
+ </script>
219
+ """
220
+
221
+ def make_search_control_js(map_var_name: str, items_json: str, position: str = "bottomleft"):
222
+ """
223
+ بحث جزئي + قائمة نتائج (Leaflet Control).
224
+ position: topleft / topright / bottomleft / bottomright
225
+ """
226
+ return f"""
227
+ <style>
228
+ .leaflet-control.est-search-control {{
229
+ background: rgba(255,255,255,0.92);
230
+ box-shadow: 0 2px 14px rgba(0,0,0,0.20);
231
+ border-radius: 14px;
232
+ padding: 10px 10px;
233
+ width: 320px;
234
+ direction: rtl;
235
+ font-family: Arial, sans-serif;
236
+ }}
237
+ .est-search-control input {{
238
+ width: 100%;
239
+ border: 1px solid rgba(0,0,0,0.15);
240
+ border-radius: 10px;
241
+ padding: 10px 12px;
242
+ font-size: 13px;
243
+ outline: none;
244
+ }}
245
+ .est-search-meta {{
246
+ margin-top: 6px;
247
+ font-size: 12px;
248
+ color: #666;
249
+ display:flex;
250
+ justify-content:space-between;
251
+ gap:8px;
252
+ }}
253
+ .est-results {{
254
+ margin-top: 8px;
255
+ max-height: 180px;
256
+ overflow: auto;
257
+ border-top: 1px solid rgba(0,0,0,0.08);
258
+ padding-top: 6px;
259
+ }}
260
+ .est-item {{
261
+ cursor: pointer;
262
+ padding: 8px 10px;
263
+ border-radius: 10px;
264
+ font-size: 13px;
265
+ line-height: 1.4;
266
+ }}
267
+ .est-item:hover {{
268
+ background: rgba(0,0,0,0.06);
269
+ }}
270
+ .est-badge {{
271
+ font-size: 12px;
272
+ color: #333;
273
+ }}
274
+ .est-clear {{
275
+ cursor:pointer;
276
+ user-select:none;
277
+ font-size:12px;
278
+ color:#0b6efd;
279
+ }}
280
+ </style>
281
+
282
+ <script>
283
+ (function(){{
284
+ const map = {map_var_name};
285
+ // items: list of objects (title, lat, lon, markerVar)
286
+ const items = {items_json};
287
+
288
+ function norm(s){{
289
+ if(!s) return "";
290
+ s = (""+s).toLowerCase();
291
+
292
+ // إزالة التشكيل
293
+ s = s.replace(/[\\u064B-\\u065F]/g, "");
294
+ // تطبيع عربي
295
+ s = s.replace(/[إأآا]/g, "ا")
296
+ .replace(/ى/g, "ي")
297
+ .replace(/ة/g, "ه")
298
+ .replace(/ؤ/g, "و")
299
+ .replace(/ئ/g, "ي")
300
+ .replace(/ـ/g, "");
301
+ // مسافات
302
+ s = s.replace(/\\s+/g, " ").trim();
303
+ return s;
304
+ }}
305
+
306
+ const SearchControl = L.Control.extend({{
307
+ options: {{ position: '{position}' }},
308
+ onAdd: function(){{
309
+ const div = L.DomUtil.create('div', 'leaflet-control est-search-control');
310
+ div.innerHTML = `
311
+ <input id="estQ" type="text" placeholder="ابحث باسم المنشأة (جزء من الاسم)..." />
312
+ <div class="est-search-meta">
313
+ <span id="estCount" class="est-badge"></span>
314
+ <span id="estClear" class="est-clear">مسح</span>
315
+ </div>
316
+ <div id="estResults" class="est-results"></div>
317
+ `;
318
+ L.DomEvent.disableClickPropagation(div);
319
+ L.DomEvent.disableScrollPropagation(div);
320
+ return div;
321
+ }}
322
+ }});
323
+
324
+ map.addControl(new SearchControl());
325
+
326
+ function renderResults(hits){{
327
+ const results = document.getElementById('estResults');
328
+ const count = document.getElementById('estCount');
329
+ results.innerHTML = "";
330
+
331
+ if(!hits.length){{
332
+ count.textContent = "لا توجد نتائج";
333
+ return;
334
+ }}
335
+
336
+ count.textContent = "النتائج: " + hits.length;
337
+
338
+ for(const h of hits.slice(0, 50)){{
339
+ const el = document.createElement('div');
340
+ el.className = 'est-item';
341
+ el.textContent = h.title;
342
+ el.onclick = function(){{
343
+ try {{
344
+ const mv = window[h.markerVar];
345
+ if(mv && mv.getLatLng){{
346
+ map.setView(mv.getLatLng(), Math.max(map.getZoom(), 17), {{animate:true}});
347
+ if(mv.openPopup) mv.openPopup();
348
+ }} else {{
349
+ map.setView([h.lat, h.lon], Math.max(map.getZoom(), 17), {{animate:true}});
350
+ }}
351
+ }} catch(e) {{}}
352
+ }};
353
+ results.appendChild(el);
354
+ }}
355
+ }}
356
+
357
+ function search(q){{
358
+ const nq = norm(q);
359
+ if(!nq) return [];
360
+ const hits = [];
361
+ for(const it of items){{
362
+ if(norm(it.title).includes(nq)) hits.push(it);
363
+ }}
364
+ return hits;
365
+ }}
366
+
367
+ setTimeout(function(){{
368
+ const input = document.getElementById('estQ');
369
+ const clearBtn = document.getElementById('estClear');
370
+
371
+ input.addEventListener('input', function(){{
372
+ renderResults(search(this.value));
373
+ }});
374
+
375
+ input.addEventListener('keydown', function(e){{
376
+ if(e.key === 'Enter'){{
377
+ const hits = search(this.value);
378
+ renderResults(hits);
379
+ if(hits.length){{
380
+ try {{
381
+ const mv = window[hits[0].markerVar];
382
+ if(mv && mv.getLatLng){{
383
+ map.setView(mv.getLatLng(), Math.max(map.getZoom(), 17), {{animate:true}});
384
+ if(mv.openPopup) mv.openPopup();
385
+ }}
386
+ }} catch(e) {{}}
387
+ }}
388
+ }}
389
+ }});
390
+
391
+ clearBtn.addEventListener('click', function(){{
392
+ input.value = "";
393
+ document.getElementById('estResults').innerHTML = "";
394
+ document.getElementById('estCount').textContent = "";
395
+ }});
396
+ }}, 0);
397
+ }})();
398
+ </script>
399
+ """
400
+
401
+ # =======================
402
+ # 5) إنشاء صفحات الباحثين
403
+ # =======================
404
+ ids = sorted(pd.to_numeric(df["researcher_id"], errors="coerce").dropna().astype(int).unique())
405
+
406
+ for idx, rid in enumerate(ids, 1):
407
+ g = df[df["researcher_id"] == str(rid)].copy()
408
+ g_valid = g.dropna(subset=["lat", "lon"]).copy()
409
+
410
+ name_s = g["researcher_name"].replace("", pd.NA).dropna()
411
+ title = f"{(name_s.iloc[0] if len(name_s) else 'باحث')} - {rid}"
412
+
413
+ if g_valid.empty:
414
+ html_empty = f"""
415
+ <html lang='ar' dir='rtl'>
416
+ <head><meta charset='utf-8'/></head>
417
+ <body style='font-family:Arial;text-align:center;margin-top:50px'>
418
+ <h3>لا توجد إحداثيات صالحة ({title})</h3>
419
+ <div style='margin-top:20px;color:#666;'>تصميم وإعداد نوف الناصر</div>
420
+ </body>
421
+ </html>
422
+ """
423
+ with open(os.path.join(OUT_DIR, f"{idx:02d}.html"), "w", encoding="utf-8") as f:
424
+ f.write(html_empty)
425
+ pages.append((f"{idx:02d}", title))
426
+ continue
427
+
428
+ # خريطة بدون tiles افتراضيًا
429
+ m = folium.Map(
430
+ location=[g_valid["lat"].mean(), g_valid["lon"].mean()],
431
+ zoom_start=13,
432
+ tiles=None,
433
+ control_scale=True
434
+ )
435
+
436
+ # طبقات الخرائط
437
+ folium.TileLayer("OpenStreetMap", name="خريطة الشارع").add_to(m)
438
+ folium.TileLayer(
439
+ tiles="https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
440
+ attr="&copy; Esri",
441
+ name="أقمار صناعية (Esri)",
442
+ overlay=False,
443
+ control=True
444
+ ).add_to(m)
445
+
446
+ LocateControl(auto_start=False, flyTo=True).add_to(m)
447
+
448
+ # إحصاءات الحارات + ألوان
449
+ hara_counts = g_valid["hara"].value_counts()
450
+ hara_to_folium = {h: stable_folium_color(h) for h in hara_counts.index}
451
+ hara_to_css = {h: CSS_COLOR_MAP.get(hara_to_folium[h], "#6c757d") for h in hara_counts.index}
452
+
453
+ m.get_root().html.add_child(
454
+ folium.Element(make_stats_panel_bottom_right(title, len(g_valid), hara_counts, hara_to_css))
455
+ )
456
+
457
+ # رسم النقاط + تجهيز بيانات البحث
458
+ bounds = []
459
+ search_items = [] # [{title, lat, lon, markerVar}]
460
+ for _, r in g_valid.iterrows():
461
+ lat, lon = float(r["lat"]), float(r["lon"])
462
+ bounds.append([lat, lon])
463
+
464
+ hara = (r.get("hara") or "غير محدد").strip() or "غير محدد"
465
+ marker_color = hara_to_folium.get(hara, "gray")
466
+ est_name = str(r.get("est", "بدون اسم")).strip() or "بدون اسم"
467
+
468
+ link = f"https://www.google.com/maps?q={lat},{lon}"
469
+ popup_html = f"""
470
+ <div style="direction:rtl;font-family:Arial;line-height:1.7;">
471
+ <b>{est_name}</b><br>
472
+ السجل التجاري: {r.get('السجل التجاري','-')}<br>
473
+ المنطقة: {r.get('اسم المنطقة','-')}<br>
474
+ الحارة: {r.get('اسم الحارة','-')}<br>
475
+ <a href="{link}" target="_blank">فتح في خرائط جوجل</a>
476
+ </div>
477
+ """
478
+
479
+ marker = folium.Marker(
480
+ location=[lat, lon],
481
+ tooltip=est_name,
482
+ popup=folium.Popup(popup_html, max_width=320),
483
+ icon=folium.Icon(color=marker_color, icon="info-sign")
484
+ ).add_to(m)
485
+
486
+ search_items.append({
487
+ "title": est_name,
488
+ "lat": lat,
489
+ "lon": lon,
490
+ "markerVar": marker.get_name()
491
+ })
492
+
493
+ if bounds:
494
+ m.fit_bounds(bounds, padding=(30, 30))
495
+
496
+ # LayerControl لتبديل نوع الخريطة
497
+ folium.LayerControl(collapsed=False).add_to(m)
498
+
499
+ # إضافة مربع البحث (أسفل اليسار) + بحث جزئي
500
+ items_json = json.dumps(search_items, ensure_ascii=False)
501
+ m.get_root().html.add_child(
502
+ folium.Element(make_search_control_js(m.get_name(), items_json, position="bottomleft"))
503
+ )
504
+
505
+ # فوتر
506
+ m.get_root().html.add_child(folium.Element(make_footer_html()))
507
+
508
+ # حفظ
509
+ fname = f"{idx:02d}.html"
510
+ m.save(os.path.join(OUT_DIR, fname))
511
+ pages.append((f"{idx:02d}", title))
512
+
513
+ print(f"✓ تم إنشاء {len(pages)} صفحة داخل: {OUT_DIR}")
514
+
515
+ # =======================
516
+ # 6) صفحة الفهرس
517
+ # =======================
518
+ list_items = "".join([
519
+ f'<li><a href="{num}.html" target="_blank">{num} — {title}</a></li>'
520
+ for num, title in pages
521
+ ])
522
+
523
+ index_html = f"""
524
  <!DOCTYPE html>
525
  <html lang="ar" dir="rtl">
526
  <head>
527
  <meta charset="utf-8"/>
528
+ <title>روابط الخرائط</title>
529
  <style>
530
+ body {{ font-family: Arial, sans-serif; margin: 24px; }}
531
+ h1 {{ margin-bottom: 10px; }}
532
+ ul {{ line-height: 1.9; }}
533
+ a {{ color: #0b6efd; text-decoration: none; }}
534
+ a:hover {{ text-decoration: underline; }}
535
+ .footer {{ margin-top: 28px; color:#666; }}
 
 
 
 
536
  </style>
537
  </head>
538
  <body>
539
+ <h1>اختر صفحة الباحث</h1>
540
+ <ul>{list_items}</ul>
541
+ <div class="footer">تصميم وإعداد نوف الناصر</div>
542
  </body>
543
  </html>
544
+ """
545
+ with open(os.path.join(OUT_DIR, "index.html"), "w", encoding="utf-8") as f:
546
+ f.write(index_html)
547
+
548
+ # =======================
549
+ # 7) ضغط وتنزيل
550
+ # =======================
551
+ zip_name = "خرائط_الباحثين.zip"
552
+ with zipfile.ZipFile(zip_name, "w", zipfile.ZIP_DEFLATED) as z:
553
+ for fn in os.listdir(OUT_DIR):
554
+ z.write(os.path.join(OUT_DIR, fn), arcname=fn)
555
+
556
+ print(f"✓ تم إنشاء الملف المضغوط: {zip_name}")
557
+ files.download(zip_name)