feat(tactical): add tactical line of sight API, elevation engine, dynamic step resolution, weather module and executive showcase
This commit is contained in:
+32
-21
@@ -4,6 +4,7 @@ import maplibregl from 'maplibre-gl'
|
||||
import App from './App.tsx'
|
||||
import CompareView from './pages/CompareView'
|
||||
import IntelligenceDashboard from './pages/IntelligenceDashboard'
|
||||
import { ExecutiveShowcase } from './pages/ExecutiveShowcase'
|
||||
import './index.css'
|
||||
|
||||
maplibregl.setRTLTextPlugin(
|
||||
@@ -12,49 +13,53 @@ maplibregl.setRTLTextPlugin(
|
||||
true
|
||||
);
|
||||
|
||||
// Lightweight hash router (no dependency). '#compare' and '#review' are additive
|
||||
// views; anything else falls back to the existing map app, untouched.
|
||||
// Lightweight hash router
|
||||
const NAV = [
|
||||
{ hash: '#map', label: 'Map', icon: '🗺️' },
|
||||
{ hash: '#review', label: 'Review', icon: '🔍' },
|
||||
{ hash: '#compare', label: 'Compare', icon: '⚖️' },
|
||||
{ hash: '#executive', label: 'العرض الاستراتيجي', icon: '🎖️' },
|
||||
{ hash: '#map', label: 'الخريطة الحية', icon: '🗺️' },
|
||||
{ hash: '#review', label: 'تدقيق الطرق', icon: '🔍' },
|
||||
{ hash: '#compare', label: 'المقارنة', icon: '⚖️' },
|
||||
]
|
||||
|
||||
function ViewNav({ hash }: { hash: string }) {
|
||||
const isMap = hash !== '#compare' && hash !== '#review'
|
||||
const currentHash = hash || '#map'
|
||||
return (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: 10,
|
||||
right: 16,
|
||||
top: 12,
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
zIndex: 9999,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
background: 'rgba(15, 23, 42, 0.88)',
|
||||
backdropFilter: 'blur(12px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.12)',
|
||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.45)',
|
||||
background: 'rgba(15, 23, 42, 0.92)',
|
||||
backdropFilter: 'blur(20px)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.18)',
|
||||
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.55)',
|
||||
borderRadius: 999,
|
||||
padding: '3px 4px',
|
||||
fontFamily: 'Inter, system-ui, sans-serif'
|
||||
padding: '4px 6px',
|
||||
fontFamily: "'IBM Plex Sans Arabic', Inter, system-ui, sans-serif",
|
||||
direction: 'rtl',
|
||||
maxWidth: 'calc(100vw - 24px)',
|
||||
overflowX: 'auto'
|
||||
}}>
|
||||
{NAV.map(n => {
|
||||
const active = n.hash === '#map' ? isMap : hash === n.hash
|
||||
const active = currentHash === n.hash
|
||||
return (
|
||||
<a key={n.hash} href={n.hash}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
fontWeight: 700,
|
||||
color: active ? '#fff' : '#94a3b8',
|
||||
background: active ? 'linear-gradient(135deg, #6366f1, #4f46e5)' : 'transparent',
|
||||
boxShadow: active ? '0 2px 8px rgba(99, 102, 241, 0.4)' : 'none',
|
||||
padding: '5px 13px',
|
||||
padding: '6px 14px',
|
||||
borderRadius: 999,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 5,
|
||||
gap: 6,
|
||||
transition: 'all 0.15s ease'
|
||||
}}>
|
||||
<span>{n.icon}</span>
|
||||
@@ -67,13 +72,19 @@ function ViewNav({ hash }: { hash: string }) {
|
||||
}
|
||||
|
||||
function Root() {
|
||||
const [hash, setHash] = useState(window.location.hash)
|
||||
const [hash, setHash] = useState(window.location.hash || '#map')
|
||||
useEffect(() => {
|
||||
const on = () => setHash(window.location.hash)
|
||||
const on = () => setHash(window.location.hash || '#map')
|
||||
window.addEventListener('hashchange', on)
|
||||
return () => window.removeEventListener('hashchange', on)
|
||||
}, [])
|
||||
const view = hash === '#compare' ? <CompareView /> : hash === '#review' ? <IntelligenceDashboard /> : <App />
|
||||
|
||||
const view =
|
||||
hash === '#executive' || hash === '#pitch' ? <ExecutiveShowcase /> :
|
||||
hash === '#compare' ? <CompareView /> :
|
||||
hash === '#review' ? <IntelligenceDashboard /> :
|
||||
<App />
|
||||
|
||||
return (
|
||||
<>
|
||||
<ViewNav hash={hash} />
|
||||
|
||||
Reference in New Issue
Block a user