Files
maps-saas/apps/web/public/map-demo.html
T
2026-04-14 03:03:35 +03:00

715 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Intaleq Premium Map V3 - خرائط انطلاقة الذكية</title>
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;600;800&family=Noto+Sans+Arabic:wght@400;700&display=swap');
:root {
--primary: #c0a048;
--primary-dark: #8a6e20;
--accent: #2563eb;
--bg-glass: rgba(255, 255, 255, 0.88);
--shadow: 0 12px 40px rgba(0,0,0,0.12);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Outfit', 'Noto Sans Arabic', sans-serif;
background: #F2EDE4;
overflow: hidden;
color: #342C20;
}
/* ── MAP ── */
#map { width: 100vw; height: 100vh; position: absolute; inset: 0; }
/* ── LOADING OVERLAY ── */
#loading {
display: none; position: fixed; inset: 0;
background: rgba(242,237,228,0.6);
z-index: 2000; align-items: center; justify-content: center;
backdrop-filter: blur(6px);
}
.spinner {
width: 40px; height: 40px;
border: 4px solid rgba(192,160,72,0.15);
border-top: 4px solid var(--primary);
border-radius: 50%;
animation: spin 0.9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* ── CONTROLS PANEL ── */
.controls {
position: absolute; top: 24px; right: 24px; z-index: 100;
background: var(--bg-glass);
backdrop-filter: blur(14px) saturate(180%);
-webkit-backdrop-filter: blur(14px) saturate(180%);
border-radius: 24px; padding: 28px; width: 380px;
box-shadow: var(--shadow);
border: 1px solid rgba(255,255,255,0.35);
}
.badge {
display: inline-block; padding: 4px 10px; border-radius: 6px;
font-size: 11px; font-weight: 800;
background: var(--primary); color: #fff;
margin-bottom: 12px; text-transform: uppercase; letter-spacing: 1px;
}
.panel-title { font-size: 22px; font-weight: 800; margin-bottom: 6px; color: #18100A; }
.panel-desc { font-size: 13px; color: #584840; margin-bottom: 20px; line-height: 1.55; }
/* ── SEARCH ── */
.search-wrap { position: relative; margin-bottom: 20px; }
.search-wrap input {
width: 100%; padding: 13px 60px 13px 16px;
border-radius: 12px; border: 1.5px solid #ddd;
font-family: inherit; font-size: 14px;
outline: none; transition: border-color 0.25s;
background: rgba(255,255,255,0.7);
}
.search-wrap input:focus { border-color: var(--primary); }
.search-wrap .search-btn {
position: absolute; left: 8px; top: 50%; transform: translateY(-50%);
background: var(--primary); color: #fff;
border: none; padding: 6px 13px; border-radius: 8px;
cursor: pointer; font-size: 12px; font-weight: 700;
transition: background 0.2s;
}
.search-wrap .search-btn:hover { background: var(--primary-dark); }
#search-results {
display: none; max-height: 220px; overflow-y: auto;
background: #fff; border-radius: 12px;
border: 1px solid #eee;
box-shadow: 0 4px 14px rgba(0,0,0,0.06);
margin-top: 8px;
}
.result-item {
padding: 11px 16px; border-bottom: 1px solid #f5f5f5;
cursor: pointer; transition: background 0.15s;
}
.result-item:last-child { border-bottom: none; }
.result-item:hover { background: #fdfaf5; }
.result-item .name { font-weight: 700; font-size: 13px; }
.result-item .meta { font-size: 11px; color: #999; margin-top: 2px; }
/* ── ROUTE BUTTONS ── */
.btn-group { display: flex; flex-direction: column; gap: 10px; }
.btn {
padding: 15px 18px; border: none; border-radius: 14px;
font-size: 14px; font-weight: 700; cursor: pointer;
display: flex; align-items: center; justify-content: space-between;
transition: transform 0.25s, box-shadow 0.25s;
font-family: inherit;
}
.btn .label-sub { font-size: 11px; font-weight: 400; opacity: 0.75; margin-bottom: 2px; }
.btn .icon { font-size: 20px; }
.btn-gold {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
color: #fff;
box-shadow: 0 4px 16px rgba(192,160,72,0.28);
}
.btn-gold:hover { transform: translateY(-2px); box-shadow: 0 8px 22px rgba(192,160,72,0.38); }
.btn-white {
background: #fff; color: #342C20;
border: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 3px 8px rgba(0,0,0,0.04);
}
.btn-white:hover { background: #fdfaf5; transform: translateY(-2px); }
/* ── STATUS BOX ── */
#status {
display: none; margin-top: 20px;
background: rgba(253,250,245,0.7);
border: 1.5px dashed var(--primary);
border-radius: 14px; padding: 16px; font-size: 13px;
line-height: 1.65;
animation: fadeUp 0.35s ease-out;
}
.status-row {
display: flex; justify-content: space-between; margin-top: 6px;
}
.status-row span:last-child { font-weight: 800; }
/* ── DB NAMES INDICATOR ── */
#db-status {
margin-top: 14px; padding: 10px 14px;
background: rgba(192,160,72,0.08);
border-radius: 10px; font-size: 12px; color: #7A5C10;
display: flex; align-items: center; gap: 8px;
}
.dot-gold {
width: 8px; height: 8px; border-radius: 50%;
background: var(--primary); flex-shrink: 0;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── BRANDING ── */
.branding {
position: absolute; bottom: 28px; left: 28px; z-index: 100;
background: var(--bg-glass); backdrop-filter: blur(8px);
padding: 10px 18px; border-radius: 12px;
display: flex; align-items: center; gap: 10px;
box-shadow: 0 6px 20px rgba(0,0,0,0.09);
border: 1px solid rgba(255,255,255,0.4);
}
.branding span { font-size: 12px; font-weight: 800; color: #18100A; letter-spacing: 0.5px; }
/* ── CONTEXT MENU ── */
#context-menu {
display: none; position: fixed; z-index: 3000;
background: var(--bg-glass); backdrop-filter: blur(12px);
border-radius: 12px; min-width: 180px;
box-shadow: 0 8px 30px rgba(0,0,0,0.15);
border: 1px solid rgba(255,255,255,0.4);
padding: 6px;
}
.menu-item {
padding: 10px 14px; border-radius: 8px; font-size: 13px; font-weight: 700;
cursor: pointer; display: flex; align-items: center; gap: 10px;
transition: background 0.2s;
}
.menu-item:hover { background: rgba(192,160,72,0.12); color: var(--primary-dark); }
.menu-item .icon { font-size: 16px; }
/* ── ADMIN PANEL ── */
#admin-panel {
position: absolute; bottom: 84px; right: 24px; z-index: 100;
background: var(--bg-glass); backdrop-filter: blur(14px);
border-radius: 20px; padding: 20px; width: 340px;
box-shadow: var(--shadow); border: 1px solid rgba(255,255,255,0.3);
display: none; animation: fadeUp 0.3s;
}
.admin-title { font-size: 16px; font-weight: 800; margin-bottom: 12px; display: flex; align-items: center; gap: 8px; }
.admin-close { cursor: pointer; opacity: 0.5; margin-right: auto; font-size: 18px; }
.delete-group { margin-top: 15px; }
.delete-group label { display: block; font-size: 11px; font-weight: 700; margin-bottom: 6px; opacity: 0.7; }
.delete-group input, .delete-group select {
width: 100%; padding: 10px; border-radius: 8px; border: 1.5px solid #eee;
font-size: 13px; margin-bottom: 10px; outline: none;
}
.delete-group input:focus { border-color: #c0392b; }
.btn-delete {
width: 100%; padding: 10px; background: #c0392b; color: #fff; border: none;
border-radius: 10px; font-weight: 800; cursor: pointer;
}
#toggle-admin {
position: absolute; top: 110px; left: 10px; z-index: 100;
background: var(--bg-glass); border: none; border-radius: 50%;
width: 44px; height: 44px; display: flex; align-items: center; justify-content: center;
box-shadow: var(--shadow); cursor: pointer; font-size: 20px;
}
/* ── SCROLLBAR ── */
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--primary); border-radius: 10px; }
</style>
</head>
<body>
<!-- MAP -->
<div id="map"></div>
<!-- LOADING -->
<div id="loading">
<div style="text-align:center">
<div class="spinner" style="margin: 0 auto 14px;"></div>
<strong style="color:var(--primary-dark)">جاري حساب المسار الذكي...</strong>
</div>
</div>
<!-- BRANDING -->
<div class="branding">
<img src="/intaleq-logo.png" alt="Logo" style="height:22px" onerror="this.style.display='none'">
<span>INTALEQ PREMIUM MAPS</span>
</div>
<!-- CONTROLS -->
<div class="controls">
<div class="badge">v3.1.0</div>
<div class="panel-title">🗺️ خرائط انطلاقة الذكية</div>
<p class="panel-desc">حلول جغرافية متقدمة لمنطقة الشرق الأوسط وشمال أفريقيا — عرض ثلاثي الأبعاد وأسماء مباشرة من قاعدة البيانات.</p>
<!-- SEARCH -->
<div class="search-wrap">
<input id="search-input" type="text" placeholder="ابحث عن مكان... (مسجد، مستشفى، ...)" />
<button class="search-btn" onclick="performSearch()">بحث</button>
<div id="search-results"></div>
</div>
<!-- ROUTE BUTTONS -->
<div class="btn-group">
<button class="btn btn-gold" onclick="handleRoute('LEVANT')">
<div>
<div class="label-sub">مسار مقترح — سوريا</div>
<span>عمّان ← دمشق</span>
</div>
<span class="icon">🚀</span>
</button>
<button class="btn btn-white" onclick="handleRoute('EGYPT')">
<div>
<div class="label-sub">مسار مقترح — مصر</div>
<span>شبرا ← الزمالك</span>
</div>
<span class="icon">🇪🇬</span>
</button>
</div>
<!-- STATUS -->
<div id="status"></div>
<!-- OPTIONS -->
<div style="margin-top: 24px; border-top: 1px solid rgba(0,0,0,0.06); padding-top: 20px;">
<div style="font-size: 14px; font-weight: 800; margin-bottom: 15px; color: #18100A;">🛡️ خيارات / Options</div>
<label style="display: flex; align-items: center; gap: 10px; margin-bottom: 12px; cursor: pointer; font-size: 13px;">
<input type="checkbox" id="toggle-3d" checked onchange="updateLayers()" style="accent-color: var(--primary); width: 16px; height: 16px;">
<span>مباني ثلاثية الأبعاد / 3D Buildings</span>
</label>
<label style="display: flex; align-items: center; gap: 10px; cursor: pointer; font-size: 13px;">
<input type="checkbox" id="toggle-pois" checked onchange="updateLayers()" style="accent-color: var(--primary); width: 16px; height: 16px;">
<span>عرض المعالم / Show POIs</span>
</label>
<label style="display: flex; align-items: center; gap: 10px; margin-top: 12px; cursor: pointer; font-size: 13px;">
<input type="checkbox" id="toggle-arrows" checked onchange="updateLayers()" style="accent-color: var(--primary); width: 16px; height: 16px;">
<span>اتجاهات الشوارع / Street Directions</span>
</label>
</div>
<!-- DB NAMES INDICATOR -->
<div id="db-status">
<div class="dot-gold"></div>
<span id="db-status-text">جاري تحميل الأسماء من قاعدة البيانات...</span>
</div>
</div>
<!-- TOGGLE ADMIN -->
<button id="toggle-admin" onclick="toggleAdminPanel()" title="إدارة البيانات">⚙️</button>
<!-- CONTEXT MENU -->
<div id="context-menu">
<div class="menu-item" onclick="copyCoords()">
<span class="icon">📋</span> <span>نسخ الإحداثيات</span>
</div>
<div class="menu-item" id="menu-delete-btn" style="color:#c0392b" onclick="deleteFromContext()">
<span class="icon">🗑️</span> <span>حذف هذا الموقع</span>
</div>
</div>
<!-- ADMIN PANEL -->
<div id="admin-panel">
<div class="admin-title">
<span>🛡️ إدارة البيانات</span>
<span class="admin-close" onclick="toggleAdminPanel()">×</span>
</div>
<div class="delete-group">
<label>حذف موقع بواسطة الاسم</label>
<input type="text" id="admin-delete-name" placeholder="أدخل اسم الموقع..." />
<label>الدولة</label>
<select id="admin-delete-country">
<option value="syria">سوريا</option>
<option value="jordan">الأردن</option>
<option value="egypt">مصر</option>
</select>
<button class="btn-delete" onclick="handleAdminDelete()">حذف الموقع نهائياً</button>
</div>
<div id="admin-info" style="font-size:11px; margin-top:10px; opacity:0.6; text-align:center"></div>
</div>
<script>
/* ───────────────────────────────────────────
CONFIG
─────────────────────────────────────────── */
const API_BASE = window.location.origin; // Dynamically use the current host
const API_KEY = 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX';
const HEADERS = { 'x-api-key': API_KEY };
const ROUTES = {
LEVANT: {
from: [35.9106, 31.9539], // Amman
to: [36.2765, 33.5138], // Damascus
center: [36.09, 32.73],
zoom: 8,
color: '#c0a048'
},
EGYPT: {
from: [31.2427, 30.0931], // Shoubra
to: [31.2201, 30.0619], // Zamalek
center: [31.23, 30.07],
zoom: 13,
color: '#2563eb'
}
};
/* ───────────────────────────────────────────
MAP INIT
─────────────────────────────────────────── */
maplibregl.setRTLTextPlugin(
'https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.js',
null, true
);
const map = new maplibregl.Map({
container: 'map',
center: ROUTES.LEVANT.center,
zoom: ROUTES.LEVANT.zoom,
style: './style.json', // ← style.json fixed (no duplicate IDs)
pitch: 0,
bearing: 0,
attributionControl: false
});
map.addControl(new maplibregl.NavigationControl(), 'bottom-right');
/* ───────────────────────────────────────────
MAP LOAD — count DB names shown on map
─────────────────────────────────────────── */
map.on('load', () => {
console.log('[Intaleq] Map loaded — style v3.1.0');
loadDBNamesStatus();
});
/*
* Fetch the same GeoJSON the style uses for intaleq_dynamic_pois
* and report how many names were loaded on the map.
*/
async function loadDBNamesStatus() {
const statusEl = document.getElementById('db-status-text');
try {
const res = await fetch(`${API_BASE}/api/geocoding/geojson`, { headers: HEADERS });
const data = await res.json();
const count = data?.features?.length ?? 0;
statusEl.textContent = count > 0
? `✅ ${count} اسم مُحمَّل من قاعدة البيانات على الخريطة`
: '⚠️ لا توجد أسماء في قاعدة البيانات حتى الآن';
// Refresh the source in case MapLibre cached an empty response
if (count > 0 && map.getSource('intaleq_dynamic_pois')) {
map.getSource('intaleq_dynamic_pois').setData(data);
}
} catch (err) {
console.warn('[Intaleq] DB names fetch failed:', err);
statusEl.textContent = '⚠️ تعذّر الاتصال بقاعدة البيانات';
}
}
/* ───────────────────────────────────────────
POLYLINE DECODER
─────────────────────────────────────────── */
function decodePoly(str) {
let i = 0, lat = 0, lng = 0, out = [];
while (i < str.length) {
let byte, shift = 0, result = 0;
do { byte = str.charCodeAt(i++) - 63; result |= (byte & 0x1f) << shift; shift += 5; } while (byte >= 0x20);
lat += (result & 1) ? ~(result >> 1) : (result >> 1);
shift = 0; result = 0;
do { byte = str.charCodeAt(i++) - 63; result |= (byte & 0x1f) << shift; shift += 5; } while (byte >= 0x20);
lng += (result & 1) ? ~(result >> 1) : (result >> 1);
out.push([lng / 1e5, lat / 1e5]);
}
return out;
}
/* ───────────────────────────────────────────
ROUTING
─────────────────────────────────────────── */
async function handleRoute(region) {
const cfg = ROUTES[region];
const loading = document.getElementById('loading');
const status = document.getElementById('status');
loading.style.display = 'flex';
status.style.display = 'none';
try {
const url = `${API_BASE}/api/maps/route`
+ `?fromLat=${cfg.from[1]}&fromLng=${cfg.from[0]}`
+ `&toLat=${cfg.to[1]}&toLng=${cfg.to[0]}`;
const res = await fetch(url, { headers: HEADERS });
const data = await res.json();
if (data.statusCode >= 400) throw new Error(data.message || 'Routing error');
const coords = typeof data.points === 'string'
? decodePoly(data.points)
: data.points;
if (!coords?.length) throw new Error('No route points returned');
// Add or update route layer
const geojson = { type: 'Feature', geometry: { type: 'LineString', coordinates: coords } };
if (map.getSource('route')) {
map.getSource('route').setData(geojson);
map.setPaintProperty('route-line', 'line-color', cfg.color);
} else {
map.addSource('route', { type: 'geojson', data: geojson });
map.addLayer({
id: 'route-line', type: 'line', source: 'route',
layout: { 'line-join': 'round', 'line-cap': 'round' },
paint: { 'line-color': cfg.color, 'line-width': 7, 'line-opacity': 0.88 }
});
}
// Fit map to route
const bounds = coords.reduce(
(b, c) => b.extend(c),
new maplibregl.LngLatBounds(coords[0], coords[0])
);
map.fitBounds(bounds, { padding: 110, duration: 1800 });
// Show summary
setTimeout(() => {
status.style.display = 'block';
status.innerHTML = `
<div style="font-weight:700;color:var(--primary-dark);margin-bottom:8px">🏁 ملخص الرحلة</div>
<div class="status-row"><span>المسافة:</span><span>${(data.distance / 1000).toFixed(1)} كم</span></div>
<div class="status-row"><span>الوقت المتوقع:</span><span>${Math.round(data.duration / 60)} دقيقة</span></div>
`;
}, 600);
} catch (err) {
console.error('[Intaleq] Route error:', err);
status.style.display = 'block';
status.innerHTML = `<span style="color:#c0392b">⚠️ تعذّر تحميل المسار: ${err.message}</span>`;
} finally {
loading.style.display = 'none';
}
}
/* ───────────────────────────────────────────
SEARCH
─────────────────────────────────────────── */
let searchMarkers = [];
async function performSearch() {
const q = document.getElementById('search-input').value.trim();
const resultsEl = document.getElementById('search-results');
if (!q) return;
const center = map.getCenter();
const url = `${API_BASE}/api/geocoding/search`
+ `?q=${encodeURIComponent(q)}`
+ `&lat=${center.lat}&lng=${center.lng}&radius=20000`;
resultsEl.style.display = 'block';
resultsEl.innerHTML = '<div class="result-item"><span class="meta">جاري البحث...</span></div>';
// Clear old markers
searchMarkers.forEach(m => m.remove());
searchMarkers = [];
try {
const res = await fetch(url, { headers: HEADERS });
const data = await res.json();
if (!data.results?.length) {
resultsEl.innerHTML = '<div class="result-item"><span class="meta">لا توجد نتائج.</span></div>';
return;
}
resultsEl.innerHTML = '';
data.results.forEach(place => {
const lng = parseFloat(place.longitude);
const lat = parseFloat(place.latitude);
const name = place.name_ar || place.name;
const dist = place.distance ? `${(place.distance / 1000).toFixed(2)} كم` : '';
// Result row
const item = document.createElement('div');
item.className = 'result-item';
item.innerHTML = `
<div class="name">${name}</div>
<div class="meta">${place.category || ''} ${dist ? '• ' + dist : ''}</div>
`;
item.onclick = () => map.flyTo({ center: [lng, lat], zoom: 16 });
resultsEl.appendChild(item);
// Map marker — gold for DB entries, blue for OSM
const isDB = place.source === 'user_submitted' || place.source === 'intaleq_db';
const color = isDB ? '#c0a048' : '#2563eb';
let popupHTML = `<strong>${name}</strong><br><span style="font-size:12px;color:#888">${place.category || ''}</span>`;
if (isDB) {
popupHTML += `<hr style="margin:8px 0; border:0; border-top:1px solid #eee">
<button onclick="directDelete(${place.id})" style="background:#c0392b; color:#fff; border:0; padding:4px 8px; border-radius:4px; font-size:10px; cursor:pointer; width:100%">حذف القيد</button>`;
}
const marker = new maplibregl.Marker({ color })
.setLngLat([lng, lat])
.setPopup(new maplibregl.Popup({ offset: 25 })
.setHTML(popupHTML))
.addTo(map);
searchMarkers.push(marker);
});
} catch (err) {
console.error('[Intaleq] Search error:', err);
resultsEl.innerHTML = '<div class="result-item" style="color:#c0392b">⚠️ فشل الاتصال بخدمة البحث.</div>';
}
}
// Enter key triggers search
document.getElementById('search-input').addEventListener('keydown', e => {
if (e.key === 'Enter') performSearch();
});
/* ───────────────────────────────────────────
INTERACTIVE MANAGEMENT
─────────────────────────────────────────── */
let lastRightClick = null;
map.on('contextmenu', (e) => {
lastRightClick = e.lngLat;
const menu = document.getElementById('context-menu');
menu.style.display = 'block';
menu.style.left = e.point.x + 'px';
menu.style.top = e.point.y + 'px';
});
document.addEventListener('click', () => {
document.getElementById('context-menu').style.display = 'none';
});
function copyCoords() {
if (!lastRightClick) return;
const txt = `${lastRightClick.lat.toFixed(7)}, ${lastRightClick.lng.toFixed(7)}`;
navigator.clipboard.writeText(txt);
alert('تم نسخ الإحداثيات: ' + txt);
}
async function deleteFromContext() {
if (!lastRightClick) return;
const country = prompt('يرجى كتابة الدولة للتأكيد (syria, jordan, egypt):', 'syria');
if (!country) return;
const name = prompt('يرجى كتابة اسم الموقع لحذفه نهائياً:');
if (!name) return;
await apiDelete(name, country);
}
function toggleAdminPanel() {
const p = document.getElementById('admin-panel');
p.style.display = p.style.display === 'block' ? 'none' : 'block';
}
async function handleAdminDelete() {
const name = document.getElementById('admin-delete-name').value.trim();
const country = document.getElementById('admin-delete-country').value;
if (!name) return alert('يرجى إدخال الاسم');
if (!confirm(`هل أنت متأكد من حذف 모든 المواقع المسمى "${name}" في ${country}؟`)) return;
await apiDelete(name, country);
}
async function directDelete(id) {
const country = prompt('يرجى تحديد الدولة (syria, jordan, egypt):', 'syria');
if (!country) return;
if (!confirm('هل أنت متأكد من حذف هذا القيد؟')) return;
try {
const res = await fetch(`${API_BASE}/api/geocoding/places?id=${id}&country=${country}`, {
method: 'DELETE',
headers: HEADERS
});
const data = await res.json();
if (data.success) {
alert('تم الحذف بنجاح');
location.reload();
}
} catch (err) {
alert('فشل الحذف');
}
}
async function apiDelete(name, country) {
const info = document.getElementById('admin-info');
info.textContent = 'جاري الحذف...';
try {
const res = await fetch(`${API_BASE}/api/geocoding/places?name=${encodeURIComponent(name)}&country=${country}`, {
method: 'DELETE',
headers: HEADERS
});
const data = await res.json();
if (data.success) {
info.textContent = `✅ تم حذف ${data.affected} قيد بنجاح.`;
setTimeout(() => location.reload(), 2000);
} else {
info.textContent = '❌ فشل الحذف.';
}
} catch (err) {
info.textContent = '❌ خطأ في الاتصال بالسيرفر.';
}
}
/* ───────────────────────────────────────────
LAYER CONTROLS
─────────────────────────────────────────── */
function updateLayers() {
const show3D = document.getElementById('toggle-3d').checked;
const showPOIs = document.getElementById('toggle-pois').checked;
const showArrows = document.getElementById('toggle-arrows').checked;
// Toggle 3D Overture buildings
if (map.getLayer('building-3d')) {
map.setLayoutProperty('building-3d', 'visibility', show3D ? 'visible' : 'none');
}
// Show flat footprints when 3D is off
if (map.getLayer('overture-building-footprint')) {
map.setLayoutProperty('overture-building-footprint', 'visibility', show3D ? 'none' : 'visible');
}
if (map.getLayer('intaleq-db-label')) {
map.setLayoutProperty('intaleq-db-label', 'visibility', showPOIs ? 'visible' : 'none');
}
// Also toggle the dynamic POIs icons layer if exists
if (map.getLayer('intaleq_pois')) {
map.setLayoutProperty('intaleq_pois', 'visibility', showPOIs ? 'visible' : 'none');
}
// Toggle places label layers + Overture name layers
['places-syria-labels', 'places-jordan-labels', 'places-egypt-labels', 'overture-building-names', 'overture-street-names'].forEach(id => {
if (map.getLayer(id)) {
map.setLayoutProperty(id, 'visibility', showPOIs ? 'visible' : 'none');
}
});
// Toggle street direction arrows
if (map.getLayer('road-direction-arrows')) {
map.setLayoutProperty('road-direction-arrows', 'visibility', showArrows ? 'visible' : 'none');
}
}
</script>
</body>
</html>