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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +67 -550
index.html CHANGED
@@ -1,557 +1,74 @@
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)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  <!DOCTYPE html>
2
  <html lang="ar" dir="rtl">
3
  <head>
4
+ <meta charset="utf-8"/>
5
+ <title>اختر الصفحة</title>
6
+ <style>
7
+ body { font-family: Arial, sans-serif; margin: 20px; }
8
+ h1 { margin-bottom: 10px; }
9
+ ul { line-height: 1.9; }
10
+ a { color:#0b6efd; text-decoration:none; }
11
+ a:hover { text-decoration:underline; }
12
+ .btn {
13
+ padding: 8px 12px; background: #28a745; color: #fff; border: 0;
14
+ border-radius: 6px; cursor: pointer; margin-bottom: 12px;
15
+ }
16
+ .mini { font-size: 12px; color:#666; margin-right:8px; }
17
+ </style>
18
+
19
+ <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
20
  </head>
21
  <body>
22
+
23
+ <h1>اختر الصفحة</h1>
24
+
25
+ <button class="btn" onclick="exportAllToExcel()">تحميل قائمة الروابط (Excel)</button>
26
+ <span class="mini">تصميم وإعداد نوف الناصر</span>
27
+
28
+ <ul id="pages-list"></ul>
29
+
30
+ <script>
31
+ const FOLDER = 'BCS';
32
+ const TOTAL_PAGES = 61;
33
+
34
+ function pageUrl(n) {
35
+ const two = String(n).padStart(2, '0');
36
+ const base = location.origin + location.pathname.replace(/[^/]*$/, '');
37
+ return `${base}${FOLDER}/${two}.html`;
38
+ }
39
+
40
+ // إنشاء القائمة على الصفحة
41
+ (function buildList() {
42
+ const ul = document.getElementById('pages-list');
43
+ for (let i = 1; i <= TOTAL_PAGES; i++) {
44
+ const li = document.createElement('li');
45
+ const a = document.createElement('a');
46
+ a.href = `${FOLDER}/${String(i).padStart(2,'0')}.html`;
47
+ a.target = '_blank';
48
+ a.textContent = `صفحة ${i}`;
49
+ li.appendChild(a);
50
+ ul.appendChild(li);
51
+ }
52
+ })();
53
+
54
+ // تصدير للإكسل بحيث كلمة "اضغط هنا" هي الرابط
55
+ function exportAllToExcel() {
56
+ const rows = [];
57
+ for (let i = 1; i <= TOTAL_PAGES; i++) {
58
+ const link = pageUrl(i);
59
+ rows.push({
60
+ 'الصفحة': `صفحة ${i}`,
61
+ 'الرابط': { t: "s", v: "اضغط هنا", l: { Target: link } }
62
+ });
63
+ }
64
+
65
+ const ws = XLSX.utils.json_to_sheet([]);
66
+ XLSX.utils.sheet_add_json(ws, rows, { origin: "A1", skipHeader: false });
67
+
68
+ const wb = XLSX.utils.book_new();
69
+ XLSX.utils.book_append_sheet(wb, ws, 'links');
70
+ XLSX.writeFile(wb, 'روابط_الصفحات.xlsx');
71
+ }
72
+ </script>
73
  </body>
74
  </html>