feat: add Jordan 3D topographic platform, Siro Map cinematic orbit studio, sun-shadow simulation, and sovereign intelligence security gating
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
|
||||
def main():
|
||||
src = 'apps/dashboard/tourism.html'
|
||||
with open(src, 'r', encoding='utf-8') as f:
|
||||
html = f.read()
|
||||
|
||||
# 1. Add HUD CSS
|
||||
hud_css = """
|
||||
/* Sovereign In-App Navigation HUD */
|
||||
.sovereign-nav-hud {
|
||||
position: absolute;
|
||||
top: 80px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 70;
|
||||
background: rgba(6, 29, 66, 0.96);
|
||||
backdrop-filter: blur(25px);
|
||||
-webkit-backdrop-filter: blur(25px);
|
||||
border: 2px solid #06b6d4;
|
||||
border-radius: 18px;
|
||||
padding: 14px 20px;
|
||||
color: #fff;
|
||||
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.45);
|
||||
width: 480px;
|
||||
max-width: calc(100vw - 32px);
|
||||
animation: fadeInDown 0.3s ease;
|
||||
}
|
||||
@keyframes fadeInDown { from { opacity: 0; transform: translate(-50%, -15px); } to { opacity: 1; transform: translate(-50%, 0); } }
|
||||
.nav-hud-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||
.nav-hud-title { display: flex; align-items: center; gap: 8px; font-weight: 800; font-size: 0.88rem; color: #38bdf8; }
|
||||
.pulse-dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; box-shadow: 0 0 10px #22c55e; display: inline-block; }
|
||||
.nav-hud-close { background: rgba(239, 68, 68, 0.2); border: 1px solid rgba(239, 68, 68, 0.5); color: #fca5a5; padding: 4px 10px; border-radius: 8px; cursor: pointer; font-size: 0.74rem; font-weight: 700; }
|
||||
.nav-hud-close:hover { background: rgba(239, 68, 68, 0.4); }
|
||||
.nav-dest-info { font-size: 0.84rem; color: #f1f5f9; margin-bottom: 8px; }
|
||||
.dest-label { color: var(--gold); font-weight: 700; margin-left: 4px; }
|
||||
.nav-stats { display: flex; gap: 8px; margin-bottom: 10px; }
|
||||
.stat-pill { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.12); padding: 5px 12px; border-radius: 8px; display: flex; gap: 4px; align-items: baseline; }
|
||||
.stat-val { font-weight: 800; font-size: 0.95rem; color: #facc15; }
|
||||
.stat-lbl { font-size: 0.7rem; color: #94a3b8; }
|
||||
.nav-step { font-size: 0.78rem; background: rgba(6, 182, 212, 0.12); border: 1px solid rgba(6, 182, 212, 0.3); color: #a5f3fc; padding: 7px 12px; border-radius: 8px; }
|
||||
"""
|
||||
if '.sovereign-nav-hud' not in html:
|
||||
html = html.replace('</style>', hud_css + '\n </style>', 1)
|
||||
|
||||
# 2. Add HUD HTML
|
||||
hud_html = """
|
||||
<!-- Sovereign In-App Navigation HUD -->
|
||||
<div id="sovereignNavHud" class="sovereign-nav-hud" style="display: none;">
|
||||
<div class="nav-hud-header">
|
||||
<div class="nav-hud-title">
|
||||
<span class="pulse-dot"></span>
|
||||
<span>🚗 مسار التوجيه الملاحي السيادي (محرك أوروك المستقل)</span>
|
||||
</div>
|
||||
<button class="nav-hud-close" id="stopNavBtn">✕ إنهاء التوجيه ومسح المسار</button>
|
||||
</div>
|
||||
<div class="nav-hud-body">
|
||||
<div class="nav-dest-info">
|
||||
<span class="dest-label">نقطة الوصول:</span>
|
||||
<strong id="navDestName">بوابة العبور ومواقف الحافلات</strong>
|
||||
</div>
|
||||
<div class="nav-stats">
|
||||
<div class="stat-pill"><span class="stat-val" id="navDistance">0</span> <span class="stat-lbl">كم</span></div>
|
||||
<div class="stat-pill"><span class="stat-val" id="navEta">0</span> <span class="stat-lbl">دقيقة</span></div>
|
||||
<div class="stat-pill"><span class="stat-val">محرك سيادي</span> <span class="stat-lbl">GraphHopper</span></div>
|
||||
</div>
|
||||
<div class="nav-step" id="navInstruction">🛣️ اسلك المسار المضاء مباشرة نحو بوابة العبور ومواقف الحافلات المخصصة</div>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
if 'id="sovereignNavHud"' not in html:
|
||||
html = html.replace('<script>', hud_html + '\n <script>', 1)
|
||||
|
||||
# 3. Replace the Google Maps redirect with in-app sovereign navigation
|
||||
pattern = r"document\.getElementById\('drawerNavBtn'\)\.onclick\s*=\s*\(\)\s*=>\s*\{[\s\S]*?window\.open\([^\)]*\);[\s\S]*?\};"
|
||||
replacement = "document.getElementById('drawerNavBtn').onclick = () => {\n startSovereignNavigation(lm);\n };"
|
||||
html = re.sub(pattern, replacement, html)
|
||||
|
||||
# 4. Add the sovereign navigation engine function
|
||||
nav_code = """
|
||||
// Sovereign In-App Routing Engine (Zero Google Maps Dependency)
|
||||
function startSovereignNavigation(lm) {
|
||||
document.getElementById('landmarkDrawer').style.display = 'none';
|
||||
const [gateLng, gateLat] = lm.gate;
|
||||
|
||||
const defaultHubs = {
|
||||
'العراق': [44.3855, 33.3283],
|
||||
'الأردن': [35.9106, 31.9539],
|
||||
'سوريا': [36.2765, 33.5138],
|
||||
'مصر': [31.2357, 30.0444]
|
||||
};
|
||||
|
||||
const startCoord = defaultHubs[lm.country] || [44.3855, 33.3283];
|
||||
|
||||
function computeAndDraw(startLng, startLat) {
|
||||
const apiUrl = `/api/maps/route?fromLat=${startLat}&fromLng=${startLng}&toLat=${gateLat}&toLng=${gateLng}&profile=car&steps=true&locale=ar`;
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error('Offline');
|
||||
return r.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data && data.routes && data.routes.length > 0) {
|
||||
const r = data.routes[0];
|
||||
const distKm = (r.distance / 1000).toFixed(1);
|
||||
const etaMins = Math.round(r.duration / 60);
|
||||
drawRouteOnMap(r.geometry.coordinates, distKm, etaMins, lm);
|
||||
} else {
|
||||
throw new Error('No route');
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
const lineCoords = generateSovereignCorridor(startLng, startLat, gateLng, gateLat);
|
||||
const dist = calculateHaversineKm(startLat, startLng, gateLat, gateLng);
|
||||
const eta = Math.round((dist / 75) * 60);
|
||||
drawRouteOnMap(lineCoords, dist.toFixed(1), eta, lm);
|
||||
});
|
||||
}
|
||||
|
||||
if ('geolocation' in navigator) {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
pos => computeAndDraw(pos.coords.longitude, pos.coords.latitude),
|
||||
() => computeAndDraw(startCoord[0], startCoord[1]),
|
||||
{ timeout: 4000 }
|
||||
);
|
||||
} else {
|
||||
computeAndDraw(startCoord[0], startCoord[1]);
|
||||
}
|
||||
}
|
||||
|
||||
function calculateHaversineKm(lat1, lon1, lat2, lon2) {
|
||||
const R = 6371;
|
||||
const dLat = (lat2 - lat1) * Math.PI / 180;
|
||||
const dLon = (lon2 - lon1) * Math.PI / 180;
|
||||
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
|
||||
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
||||
Math.sin(dLon/2) * Math.sin(dLon/2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
|
||||
return R * c;
|
||||
}
|
||||
|
||||
function generateSovereignCorridor(lng1, lat1, lng2, lat2) {
|
||||
const points = [];
|
||||
const steps = 12;
|
||||
for (let i = 0; i <= steps; i++) {
|
||||
const f = i / steps;
|
||||
const curLng = lng1 + (lng2 - lng1) * f;
|
||||
const curLat = lat1 + (lat2 - lat1) * f;
|
||||
points.push([curLng, curLat]);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
function drawRouteOnMap(coords, distKm, etaMins, lm) {
|
||||
const geojson = {
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: 'LineString',
|
||||
coordinates: coords
|
||||
}
|
||||
};
|
||||
|
||||
if (map.getSource('sovereign-route')) {
|
||||
map.getSource('sovereign-route').setData(geojson);
|
||||
} else {
|
||||
map.addSource('sovereign-route', {
|
||||
type: 'geojson',
|
||||
data: geojson
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: 'sovereign-route-casing',
|
||||
type: 'line',
|
||||
source: 'sovereign-route',
|
||||
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
||||
paint: {
|
||||
'line-color': '#0369a1',
|
||||
'line-width': 8,
|
||||
'line-opacity': 0.8
|
||||
}
|
||||
});
|
||||
|
||||
map.addLayer({
|
||||
id: 'sovereign-route-core',
|
||||
type: 'line',
|
||||
source: 'sovereign-route',
|
||||
layout: { 'line-join': 'round', 'line-cap': 'round' },
|
||||
paint: {
|
||||
'line-color': '#38bdf8',
|
||||
'line-width': 4.5
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const bounds = coords.reduce((b, coord) => b.extend(coord), new maplibregl.LngLatBounds(coords[0], coords[0]));
|
||||
map.fitBounds(bounds, { padding: 90, pitch: 45, bearing: 10, duration: 1600 });
|
||||
|
||||
document.getElementById('navDestName').innerText = `بوابة ${lm.name_ar} (نقطة العبور الخدمي ومواقف الحافلات)`;
|
||||
document.getElementById('navDistance').innerText = distKm;
|
||||
document.getElementById('navEta').innerText = etaMins;
|
||||
document.getElementById('sovereignNavHud').style.display = 'block';
|
||||
}
|
||||
|
||||
document.getElementById('stopNavBtn').addEventListener('click', () => {
|
||||
document.getElementById('sovereignNavHud').style.display = 'none';
|
||||
if (map.getSource('sovereign-route')) {
|
||||
map.getSource('sovereign-route').setData({
|
||||
type: 'Feature',
|
||||
properties: {},
|
||||
geometry: { type: 'LineString', coordinates: [] }
|
||||
});
|
||||
}
|
||||
if (selectedLandmark) {
|
||||
map.flyTo({ center: selectedLandmark.centroid, zoom: 14.5, pitch: 40 });
|
||||
}
|
||||
});
|
||||
"""
|
||||
if 'function startSovereignNavigation' not in html:
|
||||
html = html.replace("map.on('load', () => {", nav_code + "\n map.on('load', () => {", 1)
|
||||
|
||||
# Save to all 4 destinations
|
||||
destinations = [
|
||||
'apps/dashboard/tourism.html',
|
||||
'apps/dashboard/tourism/index.html',
|
||||
'apps/web/public/tourism.html',
|
||||
'apps/web/public/tourism/index.html'
|
||||
]
|
||||
for d in destinations:
|
||||
with open(d, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
print(f'✅ Successfully updated {d} with 100% In-App Sovereign Routing!')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user