revert(ui): remove app-style dashboard redesign #44
46
README.md
46
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Intelligence Terminal
|
# Intelligence Terminal
|
||||||
|
|
||||||
**Modrinth-app-inspired operator dashboard. 27 open sources. Docker-first. No telemetry.**
|
**Self-hosted intelligence dashboard. 27 open sources. Docker-first. No telemetry.**
|
||||||
|
|
||||||
[](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
|
[](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
|
||||||
[](https://git.wilkensxl.de/MrSphay/-/packages/container/intelligence-terminal/latest)
|
[](https://git.wilkensxl.de/MrSphay/-/packages/container/intelligence-terminal/latest)
|
||||||
@@ -34,10 +34,8 @@
|
|||||||
> Runtime data stays in your configured `runs/` volume and API keys are operator-owned.
|
> Runtime data stays in your configured `runs/` volume and API keys are operator-owned.
|
||||||
> **Source:** [git.wilkensxl.de/MrSphay/intelligence-terminal](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
|
> **Source:** [git.wilkensxl.de/MrSphay/intelligence-terminal](https://git.wilkensxl.de/MrSphay/intelligence-terminal)
|
||||||
> Pull the image or clone the repository to run Intelligence Terminal on your own infrastructure.
|
> Pull the image or clone the repository to run Intelligence Terminal on your own infrastructure.
|
||||||
>
|
|
||||||
> **Design transparency:** the dashboard is inspired by app-style marketplace UX patterns, especially dark desktop app shells with a strong left navigation. It does not use Modrinth branding, logos, or assets and is not affiliated with Modrinth.
|
|
||||||
|
|
||||||
Intelligence Terminal pulls satellite fire detection, flight tracking, radiation monitoring, satellite constellation tracking, economic indicators, live market prices, conflict data, sanctions lists, and social sentiment from 27 open-source intelligence feeds in parallel, every 15 minutes, and renders everything in a dark, app-style operator workspace.
|
Intelligence Terminal pulls satellite fire detection, flight tracking, radiation monitoring, satellite constellation tracking, economic indicators, live market prices, conflict data, sanctions lists, and social sentiment from 27 open-source intelligence feeds in parallel, every 15 minutes, and renders everything on a single self-contained dashboard.
|
||||||
|
|
||||||
Hook it up to an LLM and it becomes a **two-way intelligence assistant**: pushing multi-tier alerts to Telegram and Discord when something meaningful changes, responding to commands like `/brief` and `/sweep` from your phone, and generating trade ideas grounded in real cross-domain data.
|
Hook it up to an LLM and it becomes a **two-way intelligence assistant**: pushing multi-tier alerts to Telegram and Discord when something meaningful changes, responding to commands like `/brief` and `/sweep` from your phone, and generating trade ideas grounded in real cross-domain data.
|
||||||
|
|
||||||
@@ -303,21 +301,26 @@ Gitea Actions publishes the same image automatically when the repository secret
|
|||||||
## What You Get
|
## What You Get
|
||||||
|
|
||||||
### Live Dashboard
|
### Live Dashboard
|
||||||
A self-contained app-style operator dashboard with dark mode by default, a large left navigation rail, rounded content surfaces, and view-specific panels:
|
A self-contained Jarvis-style HUD with:
|
||||||
- **Home** - sweep status, alert posture, latest feed, source count, macro summary, and high-level signal state
|
- **3D WebGL globe** (Globe.gl) with atmosphere glow, star field, and smooth rotation — plus a classic flat map toggle
|
||||||
- **Worldview** - Globe.gl 3D globe or flat map, regional focus controls, 9 marker types, flight corridors, and layer focus/hide controls
|
- **9 marker types** across both views: fire detections, air traffic, radiation sites, maritime chokepoints, SDR receivers, OSINT events, health alerts, geolocated news, conflict events
|
||||||
- **Sources** - sensor grid, source health, API-key degradation signals, and transparent partial-data states
|
- **Animated 3D flight corridor arcs** between air traffic hotspots and global hubs
|
||||||
- **Signals** - cross-source signals, sweep delta, scenario watchlist, OSINT feed, and escalation/de-escalation context
|
- **Region filters** (World, Americas, Europe, Middle East, Asia Pacific, Africa) — rotates the globe or zooms the flat map
|
||||||
- **Markets** - live indexes, crypto, energy, commodities, VIX, high-yield spread, supply-chain pressure, and LLM-assisted ideas
|
- **Live market data** — indexes, crypto, energy, commodities via Yahoo Finance (no API key needed)
|
||||||
- **Ops** - browser-triggered terminal actions, performance mode, Telegram/Discord operator workflows, and system status
|
- **Risk gauges** — VIX, high-yield spread, supply chain pressure index
|
||||||
|
- **OSINT feed** — English-language posts from 17 Telegram intelligence channels (expandable)
|
||||||
The UI keeps the existing operational features: `/api/data`, SSE live refresh, globe/flat map mode, layer focus/hide, terminal actions, low-performance mode, LLM output, Telegram and Discord alerting, and scenario watchlist data.
|
- **News ticker** — merged RSS + GDELT headlines + Telegram posts, auto-scrolling
|
||||||
|
- **Sweep delta** — live panel showing what changed since last sweep (new signals, escalations, de-escalations with severity)
|
||||||
|
- **Cross-source signals** — correlated intelligence across satellite, economic, conflict, and social domains
|
||||||
|
- **Nuclear watch** — real-time radiation readings from Safecast + EPA RadNet
|
||||||
|
- **Space watch** — CelesTrak satellite tracking: recent launches, ISS, military constellations, Starlink/OneWeb counts
|
||||||
|
- **Leverageable ideas** — AI-generated trade ideas (with LLM) or signal-correlated ideas (without)
|
||||||
|
|
||||||
### Performance Modes
|
### Performance Modes
|
||||||
The `VISUALS FULL` / `VISUALS LITE` button in the top bar only changes rendering behavior - it does **not** remove data sources or reduce sweep coverage.
|
The `VISUALS FULL` / `VISUALS LITE` button in the top bar only changes rendering behavior - it does **not** remove data sources or reduce sweep coverage.
|
||||||
|
|
||||||
When you switch to **VISUALS LITE**, the dashboard:
|
When you switch to **VISUALS LITE**, the dashboard:
|
||||||
- Disables decorative background effects such as radial and grid overlays
|
- Disables decorative background effects such as the radial/grid overlays and scanlines
|
||||||
- Removes expensive blur/backdrop-filter effects on panels and overlays
|
- Removes expensive blur/backdrop-filter effects on panels and overlays
|
||||||
- Stops non-essential animations like the logo ring blink, conflict rings, and corridor flow effects
|
- Stops non-essential animations like the logo ring blink, conflict rings, and corridor flow effects
|
||||||
- Disables globe auto-rotation and turns off animated flight-arc dashes
|
- Disables globe auto-rotation and turns off animated flight-arc dashes
|
||||||
@@ -505,7 +508,7 @@ intelligence-terminal/
|
|||||||
├── dashboard/
|
├── dashboard/
|
||||||
│ ├── inject.mjs # Data synthesis + standalone HTML injection
|
│ ├── inject.mjs # Data synthesis + standalone HTML injection
|
||||||
│ └── public/
|
│ └── public/
|
||||||
│ └── jarvis.html # Self-contained app-style operator dashboard
|
│ └── jarvis.html # Self-contained Jarvis HUD
|
||||||
│
|
│
|
||||||
├── lib/
|
├── lib/
|
||||||
│ ├── llm/ # LLM abstraction (8 providers, raw fetch, no SDKs)
|
│ ├── llm/ # LLM abstraction (8 providers, raw fetch, no SDKs)
|
||||||
@@ -644,7 +647,7 @@ When running `npm run dev`:
|
|||||||
|
|
||||||
| Endpoint | Description |
|
| Endpoint | Description |
|
||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| `GET /` | App-style operator dashboard |
|
| `GET /` | Jarvis HUD dashboard |
|
||||||
| `GET /api/data` | Current synthesized intelligence data (JSON) |
|
| `GET /api/data` | Current synthesized intelligence data (JSON) |
|
||||||
| `GET /api/health` | Server status, uptime, source count, LLM status |
|
| `GET /api/health` | Server status, uptime, source count, LLM status |
|
||||||
| `GET /events` | SSE stream for live push updates |
|
| `GET /events` | SSE stream for live push updates |
|
||||||
@@ -723,17 +726,16 @@ Check these in order:
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
The `docs/` folder contains dashboard screenshots referenced by this README. The hero screenshot has been refreshed for the app-style shell; regenerate the supporting map/globe images from a running instance when those views materially change.
|
The `docs/` folder contains dashboard screenshots referenced by this README:
|
||||||
|
|
||||||
| File | Description |
|
| File | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `docs/dashboard.png` | Full operator dashboard - hero image at the top of this README |
|
| `docs/dashboard.png` | Full dashboard — hero image at the top of this README |
|
||||||
| `docs/design/modrinth-app-final-concept.png` | Final app-style design reference used for the current shell |
|
| `docs/boot.png` | Cinematic boot sequence animation |
|
||||||
| `docs/boot.png` | Boot sequence animation |
|
| `docs/map.png` | D3 world map with marker types and flight arcs |
|
||||||
| `docs/map.png` | Worldview map with marker types and flight arcs |
|
|
||||||
| `docs/globe.png` | 3D WebGL globe view with atmosphere glow and markers |
|
| `docs/globe.png` | 3D WebGL globe view with atmosphere glow and markers |
|
||||||
|
|
||||||
For a fresh capture, run the dashboard, wait for a sweep to complete, then use your browser's DevTools (`F12` -> `Ctrl+Shift+P` -> "Capture full size screenshot") or a tool like [LICEcap](https://www.cockos.com/licecap/) for GIFs.
|
To update them: run the dashboard, wait for a sweep to complete, then use your browser's DevTools (`F12` → `Ctrl+Shift+P` → "Capture full size screenshot") or a tool like [LICEcap](https://www.cockos.com/licecap/) for GIFs.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 MiB |
@@ -3,9 +3,9 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Intelligence Terminal</title>
|
<title>CRUCIX — Intelligence Terminal</title>
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
|
||||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||||
<script src="https://d3js.org/topojson.v3.min.js"></script>
|
<script src="https://d3js.org/topojson.v3.min.js"></script>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
--bg:#020408;--panel:rgba(6,14,22,0.82);--glass:rgba(10,20,32,0.55);
|
--bg:#020408;--panel:rgba(6,14,22,0.82);--glass:rgba(10,20,32,0.55);
|
||||||
--border:rgba(100,240,200,0.12);--border-bright:rgba(100,240,200,0.3);
|
--border:rgba(100,240,200,0.12);--border-bright:rgba(100,240,200,0.3);
|
||||||
--text:#e8f4f0;--dim:#6a8a82;--accent:#64f0c8;--accent2:#44ccff;
|
--text:#e8f4f0;--dim:#6a8a82;--accent:#64f0c8;--accent2:#44ccff;
|
||||||
--warn:#ffb84c;--danger:#ff5f63;--mono:'IBM Plex Mono',monospace;--sans:'Inter',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
|
--warn:#ffb84c;--danger:#ff5f63;--mono:'IBM Plex Mono',monospace;--sans:'Space Grotesk',sans-serif;
|
||||||
}
|
}
|
||||||
html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--sans);overflow-x:hidden}
|
html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--sans);overflow-x:hidden}
|
||||||
.bg-grid{position:fixed;inset:0;pointer-events:none;opacity:0;
|
.bg-grid{position:fixed;inset:0;pointer-events:none;opacity:0;
|
||||||
@@ -25,6 +25,10 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|||||||
background-size:60px 60px;mask-image:radial-gradient(ellipse at 50% 30%,black 20%,transparent 70%)}
|
background-size:60px 60px;mask-image:radial-gradient(ellipse at 50% 30%,black 20%,transparent 70%)}
|
||||||
.bg-radial{position:fixed;inset:0;pointer-events:none;opacity:0;
|
.bg-radial{position:fixed;inset:0;pointer-events:none;opacity:0;
|
||||||
background:radial-gradient(ellipse at 50% 0%,rgba(40,120,100,0.15),transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(40,100,180,0.08),transparent 40%)}
|
background:radial-gradient(ellipse at 50% 0%,rgba(40,120,100,0.15),transparent 50%),radial-gradient(ellipse at 80% 20%,rgba(40,100,180,0.08),transparent 40%)}
|
||||||
|
.scanline{position:fixed;inset:0;pointer-events:none;overflow:hidden;opacity:0}
|
||||||
|
.scanline::after{content:'';position:absolute;left:0;width:100%;height:2px;background:linear-gradient(90deg,transparent,rgba(100,240,200,0.12),transparent);animation:scanMove 4s linear infinite}
|
||||||
|
@keyframes scanMove{0%{top:-2px}100%{top:100%}}
|
||||||
|
|
||||||
/* BOOT */
|
/* BOOT */
|
||||||
#boot{position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;align-items:center;justify-content:center;background:var(--bg)}
|
#boot{position:fixed;inset:0;z-index:1000;display:flex;flex-direction:column;align-items:center;justify-content:center;background:var(--bg)}
|
||||||
.logo-ring{width:120px;height:120px;border:2px solid var(--border);border-radius:50%;display:flex;align-items:center;justify-content:center;position:relative;opacity:0}
|
.logo-ring{width:120px;height:120px;border:2px solid var(--border);border-radius:50%;display:flex;align-items:center;justify-content:center;position:relative;opacity:0}
|
||||||
@@ -270,7 +274,7 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
|||||||
.ideas-src.static{color:var(--dim);border-color:rgba(255,255,255,0.1);background:rgba(255,255,255,0.03)}
|
.ideas-src.static{color:var(--dim);border-color:rgba(255,255,255,0.1);background:rgba(255,255,255,0.03)}
|
||||||
|
|
||||||
/* LOW PERFORMANCE MODE */
|
/* LOW PERFORMANCE MODE */
|
||||||
body.low-perf .bg-grid,body.low-perf .bg-radial{display:none!important}
|
body.low-perf .bg-grid,body.low-perf .bg-radial,body.low-perf .scanline{display:none!important}
|
||||||
body.low-perf .topbar,body.low-perf .g-panel,body.low-perf .map-popup,body.low-perf .map-loading{backdrop-filter:none!important}
|
body.low-perf .topbar,body.low-perf .g-panel,body.low-perf .map-popup,body.low-perf .map-loading{backdrop-filter:none!important}
|
||||||
body.low-perf .logo-ring::before,body.low-perf .logo-ring::after,body.low-perf .regime-chip .blink,body.low-perf .conflict-ring,body.low-perf .corridor-flow{animation:none!important}
|
body.low-perf .logo-ring::before,body.low-perf .logo-ring::after,body.low-perf .regime-chip .blink,body.low-perf .conflict-ring,body.low-perf .corridor-flow{animation:none!important}
|
||||||
body.low-perf .ticker-wrap{overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(100,240,200,0.2) transparent}
|
body.low-perf .ticker-wrap{overflow-y:auto;scrollbar-width:thin;scrollbar-color:rgba(100,240,200,0.2) transparent}
|
||||||
@@ -331,298 +335,20 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200,
|
|||||||
|
|
||||||
/* IDEA HORIZON BADGE */
|
/* IDEA HORIZON BADGE */
|
||||||
.idea-horizon{font-family:var(--mono);font-size:8px;letter-spacing:0.08em;text-transform:uppercase;padding:1px 5px;border:1px solid rgba(100,240,200,0.15);color:var(--dim);margin-left:6px}
|
.idea-horizon{font-family:var(--mono);font-size:8px;letter-spacing:0.08em;text-transform:uppercase;padding:1px 5px;border:1px solid rgba(100,240,200,0.15);color:var(--dim);margin-left:6px}
|
||||||
|
|
||||||
/* APP-STYLE REDESIGN */
|
|
||||||
:root{
|
|
||||||
--app-bg:#17191f;
|
|
||||||
--app-surface:#22252d;
|
|
||||||
--app-surface-2:#2b2f38;
|
|
||||||
--app-card:#2a2d35;
|
|
||||||
--app-card-soft:#242830;
|
|
||||||
--app-border:#3a3f49;
|
|
||||||
--app-border-soft:#343942;
|
|
||||||
--app-text:#f3f5f7;
|
|
||||||
--app-muted:#aeb6c2;
|
|
||||||
--app-dim:#77818f;
|
|
||||||
--app-green:#1bd96a;
|
|
||||||
--app-green-soft:rgba(27,217,106,0.17);
|
|
||||||
--app-blue:#5fb4ff;
|
|
||||||
--app-red:#ff5f6f;
|
|
||||||
--app-yellow:#ffcc66;
|
|
||||||
}
|
|
||||||
body[data-theme="dark"]{
|
|
||||||
--bg:var(--app-bg);
|
|
||||||
--panel:var(--app-surface);
|
|
||||||
--glass:var(--app-card);
|
|
||||||
--border:var(--app-border);
|
|
||||||
--border-bright:rgba(27,217,106,0.55);
|
|
||||||
--text:var(--app-text);
|
|
||||||
--dim:var(--app-muted);
|
|
||||||
--accent:var(--app-green);
|
|
||||||
--accent2:var(--app-blue);
|
|
||||||
background:var(--app-bg);
|
|
||||||
}
|
|
||||||
body[data-theme="light"]{
|
|
||||||
--bg:#eef1f4;
|
|
||||||
--panel:#ffffff;
|
|
||||||
--glass:#ffffff;
|
|
||||||
--border:#d6dde5;
|
|
||||||
--border-bright:rgba(18,177,88,0.55);
|
|
||||||
--text:#161a21;
|
|
||||||
--dim:#5e6875;
|
|
||||||
--accent:#11b858;
|
|
||||||
--accent2:#2677c9;
|
|
||||||
--app-bg:#eef1f4;
|
|
||||||
--app-surface:#ffffff;
|
|
||||||
--app-surface-2:#f5f7fa;
|
|
||||||
--app-card:#ffffff;
|
|
||||||
--app-card-soft:#f3f6f9;
|
|
||||||
--app-border:#d6dde5;
|
|
||||||
--app-border-soft:#e1e7ee;
|
|
||||||
--app-text:#161a21;
|
|
||||||
--app-muted:#5e6875;
|
|
||||||
--app-dim:#788391;
|
|
||||||
color:#161a21;
|
|
||||||
}
|
|
||||||
body{font-family:var(--sans);letter-spacing:0;background:var(--app-bg)}
|
|
||||||
.bg-grid,.bg-radial{display:none}
|
|
||||||
#main.app-root{display:grid;grid-template-columns:112px minmax(0,1fr);gap:18px;min-height:100vh;padding:0;background:var(--app-bg)}
|
|
||||||
.app-sidebar{position:sticky;top:0;height:100vh;padding:26px 18px 24px;border-right:1px solid rgba(255,255,255,0.05);background:#20232b;display:flex;flex-direction:column;align-items:center;gap:28px}
|
|
||||||
body[data-theme="light"] .app-sidebar{background:#f8fafc;border-right-color:#dde3ea}
|
|
||||||
.app-brand-mark{width:54px;height:54px;border:3px solid var(--app-green);border-radius:18px;display:grid;place-items:center;color:var(--app-green);font-weight:800;font-size:19px;letter-spacing:0;background:rgba(27,217,106,0.06)}
|
|
||||||
.app-brand-mark span{line-height:1}
|
|
||||||
.app-nav{display:flex;flex-direction:column;gap:12px;width:100%;align-items:center}
|
|
||||||
.nav-item{width:74px;height:74px;border:0;border-radius:999px;background:transparent;color:var(--app-muted);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;cursor:pointer;transition:background .18s ease,color .18s ease,transform .18s ease}
|
|
||||||
.nav-item span{width:28px;height:28px;display:grid;place-items:center;font-weight:800;font-size:17px;border:2px solid currentColor;border-radius:10px;line-height:1}
|
|
||||||
.nav-item small{font-size:9px;font-weight:700;letter-spacing:0;text-transform:none}
|
|
||||||
.nav-item:hover{background:rgba(255,255,255,0.06);color:var(--app-text);transform:translateY(-1px)}
|
|
||||||
.nav-item.active{background:rgba(27,217,106,0.22);color:var(--app-green)}
|
|
||||||
.sidebar-status{margin-top:auto;width:74px;min-height:42px;border-top:1px solid var(--app-border);padding-top:14px;display:flex;align-items:center;justify-content:center;gap:7px;color:var(--app-muted);font-size:11px;font-weight:700}
|
|
||||||
.sidebar-status-dot{width:8px;height:8px;border-radius:50%;background:var(--app-green);box-shadow:0 0 16px rgba(27,217,106,.5)}
|
|
||||||
.app-shell{min-width:0;margin:24px 24px 24px 0;border:1px solid var(--app-border);border-radius:30px 0 0 30px;background:#181b21;overflow:hidden;box-shadow:0 24px 70px rgba(0,0,0,.26)}
|
|
||||||
body[data-theme="light"] .app-shell{background:#eef1f4;box-shadow:0 20px 50px rgba(33,43,54,.12)}
|
|
||||||
.topbar{border:0;border-bottom:1px solid var(--app-border);border-radius:0;background:var(--app-surface);padding:26px 36px;backdrop-filter:none;display:grid;grid-template-columns:minmax(220px,340px) minmax(240px,1fr);gap:18px;align-items:center}
|
|
||||||
.top-left,.top-center,.top-right{width:auto}
|
|
||||||
.top-left{align-items:flex-start;flex-direction:column;gap:6px}
|
|
||||||
.brand{font-family:var(--sans);font-size:34px;font-weight:800;letter-spacing:0;text-transform:none;color:var(--app-text);line-height:1}
|
|
||||||
.view-subtitle{font-size:14px;line-height:1.4;color:var(--app-muted);font-weight:600}
|
|
||||||
.regime-chip{border:0;border-radius:999px;background:var(--app-green-soft);color:var(--app-green);font-family:var(--sans);font-size:12px;letter-spacing:0;text-transform:none;font-weight:800;padding:7px 12px}
|
|
||||||
.regime-chip .blink{background:var(--app-green);box-shadow:0 0 10px rgba(27,217,106,.6)}
|
|
||||||
.app-search{height:46px;border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft);color:var(--app-dim);display:flex;align-items:center;padding:0 18px;font-size:14px;font-weight:600;min-width:0}
|
|
||||||
.app-search span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
||||||
.top-right{grid-column:1/-1;justify-content:flex-start;flex-wrap:wrap}
|
|
||||||
@media(min-width:1240px){.topbar{grid-template-columns:minmax(260px,1fr) minmax(260px,420px) auto}.top-right{grid-column:auto;justify-content:flex-end}}
|
|
||||||
.meta-pill,.guide-btn,.alert-badge,.perf-pill{border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft);color:var(--app-muted);font-family:var(--sans);font-size:12px;letter-spacing:0;text-transform:none;font-weight:700;padding:8px 12px}
|
|
||||||
.meta-pill .v{color:var(--app-text)}
|
|
||||||
.guide-btn{color:var(--app-blue)}
|
|
||||||
.alert-badge{border-color:rgba(255,95,111,.35);background:rgba(255,95,111,.1);color:#ff9da8}
|
|
||||||
.theme-switch{display:flex;align-items:center;gap:4px;padding:4px;border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft)}
|
|
||||||
.theme-btn{border:0;border-radius:999px;background:transparent;color:var(--app-muted);font:700 11px var(--sans);padding:7px 10px;cursor:pointer}
|
|
||||||
.theme-btn.active{background:var(--app-green);color:#06120b}
|
|
||||||
.grid{display:grid;margin:0;padding:24px;grid-template-columns:300px minmax(0,1fr);gap:16px;min-height:calc(100vh - 122px);background:#181b21}
|
|
||||||
body[data-theme="light"] .grid{background:#eef1f4}
|
|
||||||
.col{gap:16px;min-width:0}
|
|
||||||
#leftRail,#centerCol,#rightRail{order:0}
|
|
||||||
#rightRail{grid-column:1/-1;display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:16px}
|
|
||||||
@media(min-width:1320px){.grid{grid-template-columns:280px minmax(0,1fr) 360px}#rightRail{grid-column:auto;display:flex;flex-direction:column}}
|
|
||||||
.g-panel,.map-region-bar,.map-container,.glossary-panel{border:1px solid var(--app-border);border-radius:18px;background:var(--app-card);box-shadow:none;backdrop-filter:none}
|
|
||||||
.g-panel{padding:18px;overflow:hidden}
|
|
||||||
.g-panel::before{display:none}
|
|
||||||
.sec-head{margin-bottom:14px;gap:10px}
|
|
||||||
.sec-head h3{font-family:var(--sans);font-size:17px;font-weight:800;letter-spacing:0;text-transform:none;color:var(--app-text)}
|
|
||||||
.badge{border:1px solid var(--app-border);border-radius:999px;background:var(--app-card-soft);font-family:var(--sans);font-size:11px;font-weight:800;color:var(--app-green);padding:4px 9px}
|
|
||||||
.layer-item,.site-row,.econ-row,.src-item,.mc,.signal-row,.sm,.idea-card,.ic,.tk-card{border-color:var(--app-border-soft);border-radius:12px;background:var(--app-card-soft)}
|
|
||||||
.layer-item{margin-bottom:8px;padding:11px}
|
|
||||||
.layer-item.focused{border-color:var(--app-green);background:var(--app-green-soft)}
|
|
||||||
.layer-name,.idea-title{font-weight:800;color:var(--app-text)}
|
|
||||||
.layer-sub,.layer-mode,.idea-text,.ic .ic-t,.tk-head{color:var(--app-muted)}
|
|
||||||
.layer-count,.site-val,.eval,.sm .smv{color:var(--app-green)}
|
|
||||||
.mini-btn,.action-btn,.region-btn,.map-ctrl-btn,.proj-toggle,.glossary-close{border:1px solid var(--app-border);border-radius:10px;background:var(--app-card-soft);font-family:var(--sans);letter-spacing:0;color:var(--app-muted)}
|
|
||||||
.action-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
|
|
||||||
.action-btn{font-size:12px;font-weight:800;padding:10px 8px}
|
|
||||||
.action-btn:hover,.mini-btn:hover,.region-btn:hover{border-color:var(--app-green);color:var(--app-green);background:var(--app-green-soft)}
|
|
||||||
.terminal-output{border:1px solid var(--app-border);border-radius:14px;background:#151820;color:var(--app-muted)}
|
|
||||||
body[data-theme="light"] .terminal-output{background:#f6f8fb}
|
|
||||||
.map-region-bar{padding:12px;gap:8px;margin-bottom:0}
|
|
||||||
.region-btn{font-weight:800;text-transform:none;font-size:12px;padding:8px 12px}
|
|
||||||
.region-btn.active{background:var(--app-green);color:#06120b;border-color:var(--app-green)}
|
|
||||||
.map-container{min-height:590px;background:#101319}
|
|
||||||
body[data-theme="light"] .map-container{background:#e7ecf3}
|
|
||||||
.map-legend{border:1px solid var(--app-border);border-radius:14px;background:rgba(24,27,33,.86);padding:9px 11px;backdrop-filter:blur(8px)}
|
|
||||||
.map-hint,.map-hint-id{top:14px;right:18px;color:var(--app-muted);letter-spacing:0;font-family:var(--sans);font-weight:700}
|
|
||||||
.lower{gap:16px;margin-top:16px}
|
|
||||||
.lower .lp-ticker{flex:1.2 1 300px;max-width:460px}
|
|
||||||
.lower .lp-macro{flex:2.4 1 520px}
|
|
||||||
.lower .lp-ideas{flex:1.4 1 340px}
|
|
||||||
.lower .source-health{flex:1.5 1 420px}
|
|
||||||
.metrics-row{gap:8px}
|
|
||||||
.src-grid{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:8px}
|
|
||||||
.feed{max-height:none}
|
|
||||||
|
|
||||||
body[data-view="worldview"] .grid{grid-template-columns:300px minmax(0,1fr)}
|
|
||||||
body[data-view="worldview"] #rightRail,body[data-view="worldview"] .lower{display:none}
|
|
||||||
body[data-view="worldview"] .map-container{min-height:calc(100vh - 250px)}
|
|
||||||
body[data-view="sources"] .grid{grid-template-columns:360px minmax(0,1fr)}
|
|
||||||
body[data-view="sources"] #rightRail,body[data-view="sources"] #mapContainer,body[data-view="sources"] #mapRegionBar{display:none!important}
|
|
||||||
body[data-view="sources"] .lower .g-panel:not(.source-health){display:none}
|
|
||||||
body[data-view="sources"] .lower{margin-top:0}
|
|
||||||
body[data-view="signals"] .grid,body[data-view="ops"] .grid{display:block}
|
|
||||||
body[data-view="signals"] #leftRail,body[data-view="signals"] #centerCol{display:none}
|
|
||||||
body[data-view="signals"] #rightRail{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
|
|
||||||
body[data-view="signals"] #rightRail .right-actions{display:none}
|
|
||||||
body[data-view="ops"] #leftRail,body[data-view="ops"] #centerCol{display:none}
|
|
||||||
body[data-view="ops"] #rightRail{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
|
|
||||||
body[data-view="ops"] #rightRail .right-signals,body[data-view="ops"] #rightRail .right-scenarios,body[data-view="ops"] #rightRail .right-delta,body[data-view="ops"] #rightRail .right-osint{display:none}
|
|
||||||
body[data-view="markets"] .grid{display:block}
|
|
||||||
body[data-view="markets"] #leftRail,body[data-view="markets"] #rightRail,body[data-view="markets"] #mapContainer,body[data-view="markets"] #mapRegionBar{display:none!important}
|
|
||||||
body[data-view="markets"] .lower{margin-top:0}
|
|
||||||
body[data-view="markets"] .lower .g-panel:not(.lp-macro):not(.lp-ideas){display:none}
|
|
||||||
body[data-view="markets"] .lp-macro,body[data-view="markets"] .lp-ideas{max-width:none}
|
|
||||||
|
|
||||||
@media(max-width:760px){
|
|
||||||
#main.app-root{display:block;padding:0 0 86px}
|
|
||||||
.app-sidebar{position:fixed;z-index:30;left:0;right:0;bottom:0;top:auto;height:76px;flex-direction:row;padding:8px 12px;border-right:0;border-top:1px solid var(--app-border);gap:12px}
|
|
||||||
.app-brand-mark,.sidebar-status{display:none}
|
|
||||||
.app-nav{flex-direction:row;justify-content:space-between;gap:6px;width:100%}
|
|
||||||
.nav-item{width:54px;height:54px;border-radius:18px}
|
|
||||||
.nav-item span{width:23px;height:23px;font-size:13px;border-radius:8px}
|
|
||||||
.nav-item small{display:none}
|
|
||||||
.app-shell{margin:10px;border-radius:24px;min-height:calc(100vh - 96px)}
|
|
||||||
.topbar{grid-template-columns:1fr;padding:22px;gap:12px}
|
|
||||||
.brand{font-size:30px}
|
|
||||||
.top-center{display:flex;width:100%;overflow:auto}
|
|
||||||
.top-right{justify-content:flex-start}
|
|
||||||
.grid{padding:14px;display:flex;flex-direction:column}
|
|
||||||
body[data-view="worldview"] .grid,body[data-view="sources"] .grid{display:flex}
|
|
||||||
body[data-view="signals"] #rightRail,body[data-view="ops"] #rightRail{display:flex;flex-direction:column}
|
|
||||||
.map-container{min-height:420px}
|
|
||||||
.map-legend{left:8px;right:8px;bottom:8px}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FINAL APP-SHELL FIDELITY PASS */
|
|
||||||
body::before{content:'';position:fixed;inset:0;z-index:-1;pointer-events:none;background-image:linear-gradient(180deg,rgba(10,13,18,.72),rgba(10,13,18,.88)),url('./assets/app-shell-texture.png');background-size:auto,420px 420px;background-blend-mode:normal,soft-light;opacity:.5}
|
|
||||||
#main.app-root{grid-template-columns:150px minmax(0,1fr);gap:0;background:linear-gradient(180deg,#171a20,#11141a)}
|
|
||||||
.app-sidebar{width:150px;padding:28px 20px 24px;background:linear-gradient(180deg,#151a21,#10151c);border-right:1px solid rgba(124,138,154,.24);box-shadow:inset -1px 0 rgba(255,255,255,.03)}
|
|
||||||
.rail-live{height:34px;min-width:86px;padding:0 13px;border-radius:999px;background:rgba(255,255,255,.035);display:flex;align-items:center;justify-content:center;gap:8px;color:var(--app-muted);font-weight:800;font-size:12px}
|
|
||||||
.app-nav{position:relative;gap:18px;margin-top:6px}
|
|
||||||
.nav-glider{position:absolute;left:50%;top:0;width:86px;height:86px;border-radius:999px;background:radial-gradient(circle at 50% 46%,rgba(27,217,106,.42),rgba(27,217,106,.2) 48%,rgba(27,217,106,0) 72%);box-shadow:0 0 34px rgba(27,217,106,.34),inset 0 0 0 1px rgba(27,217,106,.22);transform:translate(-50%,0);transition:transform .58s cubic-bezier(.2,.9,.2,1),opacity .28s ease;pointer-events:none}
|
|
||||||
.nav-glider::after{content:'';position:absolute;inset:13px;border-radius:999px;border:2px solid rgba(27,217,106,.8);box-shadow:0 0 22px rgba(27,217,106,.42)}
|
|
||||||
.nav-item{position:relative;z-index:1;width:96px;height:92px;border-radius:28px;background:transparent;color:#aeb7c4;gap:8px;transition:transform .24s ease,color .24s ease,filter .24s ease}
|
|
||||||
.nav-item:hover{background:transparent;color:#f3f7fa;transform:translateY(-2px)}
|
|
||||||
.nav-item.active{background:transparent;color:#fff;filter:drop-shadow(0 12px 28px rgba(27,217,106,.18))}
|
|
||||||
.nav-icon{width:46px;height:46px;border:0;border-radius:16px;display:grid;place-items:center;background:rgba(255,255,255,.035);box-shadow:inset 0 0 0 2px currentColor;transition:background .24s ease,box-shadow .24s ease,transform .24s ease}
|
|
||||||
.nav-icon svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:2.05;stroke-linecap:round;stroke-linejoin:round}
|
|
||||||
.nav-item.active .nav-icon{background:rgba(27,217,106,.18);box-shadow:inset 0 0 0 2px var(--app-green),0 0 0 10px rgba(27,217,106,.04);transform:scale(1.04)}
|
|
||||||
.nav-item small{font-size:11px;font-weight:800;line-height:1;color:currentColor;text-shadow:0 1px 14px rgba(0,0,0,.35)}
|
|
||||||
.sidebar-status{width:96px;border-top-color:rgba(124,138,154,.28);font-size:12px}
|
|
||||||
.app-shell{margin:14px 16px 14px 0;border-radius:32px;background:#181c23;border-color:#35404c;box-shadow:0 24px 80px rgba(0,0,0,.34),inset 0 1px rgba(255,255,255,.035)}
|
|
||||||
.topbar{padding:30px 34px 18px;grid-template-columns:minmax(280px,420px) minmax(280px,1fr) auto;background:linear-gradient(180deg,#20242c,#1e222a);gap:26px}
|
|
||||||
.brand{font-size:28px;letter-spacing:-.01em}
|
|
||||||
.view-subtitle{max-width:420px;color:#97a2b2}
|
|
||||||
.regime-chip{display:none}
|
|
||||||
.app-search{height:56px;border-radius:18px;background:#1a1f27;border-color:#38424e;font-size:15px;box-shadow:inset 0 1px rgba(255,255,255,.03)}
|
|
||||||
.top-right{grid-column:auto;justify-content:flex-end;align-content:flex-start;gap:10px}
|
|
||||||
.theme-switch,.meta-pill,.guide-btn,.alert-badge,.perf-pill{min-height:43px;border-radius:20px;border-color:#38424e;background:#1d222b;box-shadow:inset 0 1px rgba(255,255,255,.025)}
|
|
||||||
.theme-switch{padding:5px}
|
|
||||||
.theme-btn{min-width:52px;padding:8px 12px;font-size:12px}
|
|
||||||
.theme-btn.active{box-shadow:0 7px 20px rgba(27,217,106,.22)}
|
|
||||||
.alert-badge{background:rgba(255,95,111,.13);border-color:rgba(255,95,111,.38)}
|
|
||||||
.grid{padding:20px 24px 24px;grid-template-columns:300px minmax(0,1fr);gap:18px;background:#181c23}
|
|
||||||
.top-metric-row{grid-column:1/-1;display:grid;grid-template-columns:repeat(6,minmax(130px,1fr));gap:14px}
|
|
||||||
.top-metric-card{position:relative;overflow:hidden;min-height:86px;padding:16px 18px;border:1px solid #333d49;border-radius:19px;background:linear-gradient(145deg,#252b34,#1d232c);box-shadow:inset 0 1px rgba(255,255,255,.035)}
|
|
||||||
.top-metric-card::after{content:'';position:absolute;inset:auto -20% -65% -20%;height:80%;background:radial-gradient(circle,rgba(27,217,106,.13),transparent 62%);opacity:0;transition:opacity .3s ease}
|
|
||||||
.top-metric-card:hover::after{opacity:1}
|
|
||||||
.tm-icon{width:38px;height:38px;border-radius:14px;display:grid;place-items:center;background:rgba(27,217,106,.11);color:var(--app-green);margin-bottom:8px}
|
|
||||||
.tm-icon svg{width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
|
|
||||||
.tm-label{font-size:10px;font-weight:900;letter-spacing:.06em;text-transform:uppercase;color:#98a4b3}
|
|
||||||
.tm-value{margin-top:2px;font-size:20px;font-weight:900;color:#f3f6f8;letter-spacing:-.02em}
|
|
||||||
.tm-sub{font-size:11px;color:#8e99a8}
|
|
||||||
.g-panel,.map-region-bar,.map-container,.glossary-panel{border-color:#333d49;background:linear-gradient(180deg,#252a33,#202630)}
|
|
||||||
.g-panel{border-radius:20px;padding:18px;box-shadow:inset 0 1px rgba(255,255,255,.035)}
|
|
||||||
.map-container{border-radius:22px;background:#0c1118;box-shadow:inset 0 1px rgba(255,255,255,.04),0 18px 48px rgba(0,0,0,.22)}
|
|
||||||
.map-region-bar{border-radius:20px;padding:16px}
|
|
||||||
.layer-item,.site-row,.econ-row,.src-item,.mc,.signal-row,.sm,.idea-card,.ic,.tk-card{background:#202630;border-color:#303946;transition:transform .22s ease,border-color .22s ease,background .22s ease,box-shadow .22s ease}
|
|
||||||
.layer-item:hover,.signal-row:hover,.ic:hover,.tk-card.clickable:hover,.mc:hover{transform:translateY(-2px);border-color:rgba(27,217,106,.32);box-shadow:0 12px 32px rgba(0,0,0,.14)}
|
|
||||||
.layer-count,.site-val,.eval,.sm .smv{font-weight:900;color:var(--app-green)}
|
|
||||||
.sec-head h3{font-size:18px;letter-spacing:-.01em}
|
|
||||||
.badge{background:#1c222b;border-color:#33404c}
|
|
||||||
.right-actions .terminal-output{min-height:72px}
|
|
||||||
.sm .smb span,.mc .mbar span{background:linear-gradient(90deg,#1bd96a,#7ee787)}
|
|
||||||
.regime-chip .blink,.sidebar-status-dot{animation:status-pulse 2.2s ease-in-out infinite}
|
|
||||||
.map-container::after{content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(90deg,transparent,rgba(27,217,106,.035),transparent);transform:translateX(-120%);animation:surface-sweep 8s ease-in-out infinite}
|
|
||||||
body.view-transitioning .grid{animation:view-shift .42s cubic-bezier(.2,.9,.2,1)}
|
|
||||||
body.theme-transitioning .app-shell{animation:theme-wash .42s ease}
|
|
||||||
.motion-stagger{animation:panel-rise .52s cubic-bezier(.2,.9,.2,1) both}
|
|
||||||
.alert-badge,.delta-badge.new{animation:soft-alert 2.4s ease-in-out infinite}
|
|
||||||
@keyframes status-pulse{0%,100%{box-shadow:0 0 0 0 rgba(27,217,106,.32)}50%{box-shadow:0 0 0 7px rgba(27,217,106,0)}}
|
|
||||||
@keyframes surface-sweep{0%,72%{transform:translateX(-120%)}100%{transform:translateX(120%)}}
|
|
||||||
@keyframes panel-rise{from{opacity:0;transform:translateY(16px) scale(.985);filter:blur(4px)}to{opacity:1;transform:none;filter:none}}
|
|
||||||
@keyframes view-shift{from{opacity:.18;transform:translateX(18px) scale(.985);filter:blur(8px)}to{opacity:1;transform:none;filter:none}}
|
|
||||||
@keyframes theme-wash{0%{filter:brightness(.75) saturate(.75)}100%{filter:none}}
|
|
||||||
@keyframes soft-alert{0%,100%{box-shadow:inset 0 1px rgba(255,255,255,.025)}50%{box-shadow:0 0 0 5px rgba(255,95,111,.05),inset 0 1px rgba(255,255,255,.025)}}
|
|
||||||
body.low-perf .motion-stagger,body.low-perf .alert-badge,body.low-perf .delta-badge.new,body.low-perf .map-container::after,body.low-perf .regime-chip .blink,body.low-perf .sidebar-status-dot{animation:none!important}
|
|
||||||
body.low-perf .nav-glider,body.low-perf .nav-item,body.low-perf .layer-item,body.low-perf .signal-row,body.low-perf .ic,body.low-perf .tk-card,body.low-perf .mc{transition:none!important}
|
|
||||||
|
|
||||||
@media(min-width:1320px){
|
|
||||||
.grid{grid-template-columns:300px minmax(0,1fr) 380px}
|
|
||||||
#rightRail{grid-column:auto}
|
|
||||||
}
|
|
||||||
@media(max-width:1240px){
|
|
||||||
.topbar{grid-template-columns:1fr;gap:14px}
|
|
||||||
.top-right{justify-content:flex-start}
|
|
||||||
.top-metric-row{grid-template-columns:repeat(3,minmax(0,1fr))}
|
|
||||||
}
|
|
||||||
@media(max-width:760px){
|
|
||||||
body::before{background-size:auto,300px 300px}
|
|
||||||
#main.app-root{display:block;padding:0 0 88px}
|
|
||||||
.app-sidebar{width:auto;height:78px;padding:8px 12px}
|
|
||||||
.rail-live,.sidebar-status{display:none}
|
|
||||||
.app-nav{margin:0;gap:6px}
|
|
||||||
.nav-glider{left:0;width:58px;height:58px}
|
|
||||||
.nav-glider::after{inset:8px}
|
|
||||||
.nav-item{width:56px;height:58px;border-radius:19px}
|
|
||||||
.nav-icon{width:30px;height:30px;border-radius:10px}
|
|
||||||
.nav-icon svg{width:18px;height:18px}
|
|
||||||
.nav-item small{display:none}
|
|
||||||
.app-shell{margin:10px;border-radius:24px}
|
|
||||||
.topbar{padding:22px}
|
|
||||||
.top-metric-row{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
|
|
||||||
.top-metric-card{min-height:76px;padding:13px}
|
|
||||||
}
|
|
||||||
@media(prefers-reduced-motion:reduce){
|
|
||||||
*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body data-theme="dark" data-view="home">
|
<body>
|
||||||
<div id="boot">
|
<div id="boot">
|
||||||
<div class="logo-ring"><span class="logo-text">IT</span></div>
|
<div class="logo-ring"><span class="logo-text">CRUCIX</span></div>
|
||||||
<div id="bootLines"></div>
|
<div id="bootLines"></div>
|
||||||
<div id="bootFinal">APP READY</div>
|
<div id="bootFinal">TERMINAL ACTIVE</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-radial" id="bgRadial"></div>
|
<div class="bg-radial" id="bgRadial"></div>
|
||||||
<div class="bg-grid" id="bgGrid"></div>
|
<div class="bg-grid" id="bgGrid"></div>
|
||||||
<div id="main" class="app-root">
|
<div class="scanline" id="scanline"></div>
|
||||||
<aside class="app-sidebar" aria-label="Primary views">
|
<div id="main">
|
||||||
<div class="rail-live"><span class="sidebar-status-dot"></span><span>Live</span></div>
|
<div class="topbar" id="topbar"></div>
|
||||||
<nav class="app-nav" id="appNav">
|
<div class="grid">
|
||||||
<div class="nav-glider" id="navGlider" aria-hidden="true"></div>
|
|
||||||
<button class="nav-item active" data-view-target="home" onclick="setAppView('home')" title="Home" aria-label="Home"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M3 10.5 12 3l9 7.5"/><path d="M5 9.5V21h14V9.5"/><path d="M9 21v-7h6v7"/></svg></span><small>Home</small></button>
|
|
||||||
<button class="nav-item" data-view-target="worldview" onclick="setAppView('worldview')" title="Worldview" aria-label="Worldview"><span class="nav-icon"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.8 2.6 4.2 5.6 4.2 9S14.8 18.4 12 21"/><path d="M12 3c-2.8 2.6-4.2 5.6-4.2 9S9.2 18.4 12 21"/></svg></span><small>Worldview</small></button>
|
|
||||||
<button class="nav-item" data-view-target="sources" onclick="setAppView('sources')" title="Sources" aria-label="Sources"><span class="nav-icon"><svg viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="7" ry="3"/><path d="M5 5v6c0 1.7 3.1 3 7 3s7-1.3 7-3V5"/><path d="M5 11v6c0 1.7 3.1 3 7 3s7-1.3 7-3v-6"/></svg></span><small>Sources</small></button>
|
|
||||||
<button class="nav-item" data-view-target="signals" onclick="setAppView('signals')" title="Signals" aria-label="Signals"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M3 13h4l3-8 4 14 3-6h4"/><path d="M4 20h16"/></svg></span><small>Signals</small></button>
|
|
||||||
<button class="nav-item" data-view-target="markets" onclick="setAppView('markets')" title="Markets" aria-label="Markets"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M4 19V5"/><path d="M4 19h17"/><path d="M8 16v-5"/><path d="M13 16V8"/><path d="M18 16v-9"/></svg></span><small>Markets</small></button>
|
|
||||||
<button class="nav-item" data-view-target="ops" onclick="setAppView('ops')" title="Ops" aria-label="Ops"><span class="nav-icon"><svg viewBox="0 0 24 24"><path d="M4 5h16v14H4z"/><path d="m8 9 3 3-3 3"/><path d="M13 15h4"/></svg></span><small>Ops</small></button>
|
|
||||||
</nav>
|
|
||||||
<div class="sidebar-status">
|
|
||||||
<span class="sidebar-status-dot"></span>
|
|
||||||
<span id="sidebarStatus">Live</span>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
<main class="app-shell">
|
|
||||||
<div class="topbar" id="topbar"></div>
|
|
||||||
<div class="grid">
|
|
||||||
<div class="top-metric-row" id="topMetricRow"></div>
|
|
||||||
<div class="col" id="leftRail"></div>
|
<div class="col" id="leftRail"></div>
|
||||||
<div class="col" id="centerCol">
|
<div class="col" id="centerCol">
|
||||||
<div class="map-region-bar" id="mapRegionBar"></div>
|
<div class="map-region-bar" id="mapRegionBar"></div>
|
||||||
@@ -643,8 +369,7 @@ body.low-perf .nav-glider,body.low-perf .nav-item,body.low-perf .layer-item,body
|
|||||||
<div class="lower" id="lowerGrid"></div>
|
<div class="lower" id="lowerGrid"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col" id="rightRail"></div>
|
<div class="col" id="rightRail"></div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="glossary-overlay" id="glossaryOverlay" onclick="if(event.target===this) closeGlossary()">
|
<div class="glossary-overlay" id="glossaryOverlay" onclick="if(event.target===this) closeGlossary()">
|
||||||
<div class="glossary-panel">
|
<div class="glossary-panel">
|
||||||
@@ -709,137 +434,6 @@ let currentRegion = 'world';
|
|||||||
let flatSvg, flatProjection, flatPath, flatG, flatZoom, flatW, flatH;
|
let flatSvg, flatProjection, flatPath, flatG, flatZoom, flatW, flatH;
|
||||||
const terminalActionTokenKey = 'crucix_sweep_token';
|
const terminalActionTokenKey = 'crucix_sweep_token';
|
||||||
|
|
||||||
const appViews = {
|
|
||||||
home: { title: 'Home', subtitle: 'Sweep summary, alert posture, source status, and the latest operator feed.' },
|
|
||||||
worldview: { title: 'Worldview', subtitle: 'Globe or flat map with regional focus and layer controls.' },
|
|
||||||
sources: { title: 'Sources', subtitle: 'Sensor grid, source health, API-key degradation, and data coverage.' },
|
|
||||||
signals: { title: 'Signals', subtitle: 'Cross-source signals, sweep delta, scenario watchlist, and OSINT context.' },
|
|
||||||
markets: { title: 'Markets', subtitle: 'Macro indicators, live markets, volatility gauges, and AI-assisted ideas.' },
|
|
||||||
ops: { title: 'Ops', subtitle: 'Terminal actions, integration state, and low-performance controls.' }
|
|
||||||
};
|
|
||||||
let currentView = localStorage.getItem('intelligence_terminal_view') || 'home';
|
|
||||||
if(!appViews[currentView]) currentView = 'home';
|
|
||||||
let themePreference = localStorage.getItem('intelligence_terminal_theme') || 'dark';
|
|
||||||
|
|
||||||
function resolveTheme(pref){
|
|
||||||
if(pref === 'auto'){
|
|
||||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
||||||
}
|
|
||||||
return pref === 'light' ? 'light' : 'dark';
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyTheme(pref = themePreference){
|
|
||||||
themePreference = pref;
|
|
||||||
document.body.dataset.theme = resolveTheme(pref);
|
|
||||||
document.querySelectorAll('.theme-btn').forEach(btn => btn.classList.toggle('active', btn.dataset.themeChoice === pref));
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTheme(pref){
|
|
||||||
themePreference = pref;
|
|
||||||
localStorage.setItem('intelligence_terminal_theme', pref);
|
|
||||||
document.body.classList.add('theme-transitioning');
|
|
||||||
window.setTimeout(() => document.body.classList.remove('theme-transitioning'), 480);
|
|
||||||
applyTheme(pref);
|
|
||||||
renderTopbar();
|
|
||||||
}
|
|
||||||
|
|
||||||
function iconSvg(name){
|
|
||||||
const icons = {
|
|
||||||
sweep:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3M2 12h3M19 12h3"/></svg>',
|
|
||||||
delta:'<svg viewBox="0 0 24 24"><path d="M4 15l5-5 4 4 7-8"/><path d="M16 6h4v4"/></svg>',
|
|
||||||
signals:'<svg viewBox="0 0 24 24"><path d="M3 13h4l3-8 4 14 3-6h4"/></svg>',
|
|
||||||
regions:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.8 2.6 4.2 5.6 4.2 9S14.8 18.4 12 21"/><path d="M12 3c-2.8 2.6-4.2 5.6-4.2 9S9.2 18.4 12 21"/></svg>',
|
|
||||||
alert:'<svg viewBox="0 0 24 24"><path d="M12 3 5 6v5c0 4.7 3 8.3 7 10 4-1.7 7-5.3 7-10V6z"/><path d="M12 8v5"/><path d="M12 16h.01"/></svg>',
|
|
||||||
status:'<svg viewBox="0 0 24 24"><path d="M12 3 5 6v6c0 4.2 2.8 7.1 7 9 4.2-1.9 7-4.8 7-9V6z"/><path d="m8.5 12 2.2 2.2 4.8-5"/></svg>'
|
|
||||||
};
|
|
||||||
return icons[name] || icons.status;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTopMetrics(){
|
|
||||||
const el = document.getElementById('topMetricRow');
|
|
||||||
if(!el) return;
|
|
||||||
const delta = D.delta?.summary;
|
|
||||||
const signalCount = (D.tSignals||[]).length;
|
|
||||||
const activeRegions = ['world','americas','europe','middleEast','asiaPacific','africa'].length - (D.meta.sourcesQueried === 0 ? 1 : 0);
|
|
||||||
const highAlert = (D.tg?.urgent?.length || 0) > 0 || (D.noaa?.totalAlerts || 0) > 0 || (D.acled?.totalFatalities || 0) > 0;
|
|
||||||
const sourceState = D.meta.sourcesQueried ? `${D.meta.sourcesOk}/${D.meta.sourcesQueried}` : '0/0';
|
|
||||||
const cards = [
|
|
||||||
{icon:'sweep',label:'Sweep',value:`${(D.meta.totalDurationMs/1000).toFixed(1)}s`,sub:'Last run'},
|
|
||||||
{icon:'delta',label:'Delta',value:delta?.direction ? delta.direction.replace('-', ' ') : 'Baseline',sub:'Change'},
|
|
||||||
{icon:'signals',label:'Signals',value:signalCount || (D.tg?.urgent?.length || 0),sub:'New'},
|
|
||||||
{icon:'regions',label:'Regions',value:activeRegions,sub:'Active'},
|
|
||||||
{icon:'alert',label:'Alert posture',value:highAlert ? 'High alert' : 'Normal',sub:highAlert ? 'Elevated' : 'Stable',tone:highAlert?'danger':''},
|
|
||||||
{icon:'status',label:'System status',value:D.meta.sourcesFailed ? 'Degraded' : 'Operational',sub:`Sources ${sourceState}`,tone:D.meta.sourcesFailed?'warn':'ok'}
|
|
||||||
];
|
|
||||||
el.innerHTML = cards.map(card => `
|
|
||||||
<div class="top-metric-card ${card.tone||''}">
|
|
||||||
<div class="tm-icon">${iconSvg(card.icon)}</div>
|
|
||||||
<div class="tm-label">${card.label}</div>
|
|
||||||
<div class="tm-value">${card.value}</div>
|
|
||||||
<div class="tm-sub">${card.sub}</div>
|
|
||||||
</div>`).join('');
|
|
||||||
}
|
|
||||||
|
|
||||||
function positionNavGlider(){
|
|
||||||
const nav = document.getElementById('appNav');
|
|
||||||
const glider = document.getElementById('navGlider');
|
|
||||||
const active = nav?.querySelector('.nav-item.active');
|
|
||||||
if(!nav || !glider || !active) return;
|
|
||||||
const navRect = nav.getBoundingClientRect();
|
|
||||||
const activeRect = active.getBoundingClientRect();
|
|
||||||
if(isMobileLayout()){
|
|
||||||
const x = activeRect.left - navRect.left + (activeRect.width - glider.offsetWidth) / 2;
|
|
||||||
glider.style.transform = `translate(${x}px,0)`;
|
|
||||||
} else {
|
|
||||||
const y = activeRect.top - navRect.top + (activeRect.height - glider.offsetHeight) / 2;
|
|
||||||
glider.style.transform = `translate(-50%,${y}px)`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldAnimateUi(){
|
|
||||||
return !lowPerfMode && !(window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches);
|
|
||||||
}
|
|
||||||
|
|
||||||
function playPanelEntrances(){
|
|
||||||
if(!shouldAnimateUi()) return;
|
|
||||||
const items = [...document.querySelectorAll('.top-metric-card,.g-panel,.map-region-bar,.map-container')];
|
|
||||||
items.forEach((el,i) => {
|
|
||||||
el.classList.remove('motion-stagger');
|
|
||||||
el.style.animationDelay = `${Math.min(i * 36, 420)}ms`;
|
|
||||||
void el.offsetWidth;
|
|
||||||
el.classList.add('motion-stagger');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderAppNav(){
|
|
||||||
document.body.dataset.view = currentView;
|
|
||||||
document.querySelectorAll('.nav-item[data-view-target]').forEach(btn => {
|
|
||||||
btn.classList.toggle('active', btn.dataset.viewTarget === currentView);
|
|
||||||
});
|
|
||||||
const status = document.getElementById('sidebarStatus');
|
|
||||||
if(status) status.textContent = currentView === 'home' ? 'Live' : appViews[currentView].title;
|
|
||||||
requestAnimationFrame(positionNavGlider);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setAppView(view){
|
|
||||||
if(!appViews[view]) return;
|
|
||||||
const changed = currentView !== view;
|
|
||||||
currentView = view;
|
|
||||||
localStorage.setItem('intelligence_terminal_view', view);
|
|
||||||
if(changed && shouldAnimateUi()){
|
|
||||||
document.body.classList.add('view-transitioning');
|
|
||||||
window.setTimeout(() => document.body.classList.remove('view-transitioning'), 460);
|
|
||||||
}
|
|
||||||
renderAppNav();
|
|
||||||
renderTopbar();
|
|
||||||
renderTopMetrics();
|
|
||||||
renderLeftRail();
|
|
||||||
renderLower();
|
|
||||||
renderRight();
|
|
||||||
playPanelEntrances();
|
|
||||||
refreshMapViewport(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const layerTypeMap = {
|
const layerTypeMap = {
|
||||||
air: ['air'],
|
air: ['air'],
|
||||||
thermal: ['thermal'],
|
thermal: ['thermal'],
|
||||||
@@ -1040,35 +634,23 @@ function renderTopbar(){
|
|||||||
const d = ts.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}).toUpperCase();
|
const d = ts.toLocaleDateString('en-US',{month:'short',day:'numeric',year:'numeric'}).toUpperCase();
|
||||||
const timeStr = ts.toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',hour12:true});
|
const timeStr = ts.toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',hour12:true});
|
||||||
const hasActionToken = !!getTerminalActionToken();
|
const hasActionToken = !!getTerminalActionToken();
|
||||||
const view = appViews[currentView] || appViews.home;
|
|
||||||
const direction = D.delta?.summary?.direction;
|
|
||||||
const deltaLabel = direction === 'risk-off' ? '▲ '+t('dashboard.riskOff','RISK-OFF') : direction === 'risk-on' ? '▼ '+t('dashboard.riskOn','RISK-ON') : '◆ '+t('dashboard.mixed','MIXED');
|
|
||||||
document.getElementById('topbar').innerHTML=`
|
document.getElementById('topbar').innerHTML=`
|
||||||
<div class="top-left">
|
<div class="top-left">
|
||||||
<span class="brand">Intelligence Terminal</span>
|
<span class="brand">CRUCIX MONITOR</span>
|
||||||
<span class="view-subtitle">${view.title} / ${view.subtitle}</span>
|
<span class="regime-chip"><span class="blink"></span>WARTIME STAGFLATION RISK</span>
|
||||||
<span class="regime-chip"><span class="blink"></span>Operator dashboard</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="app-search"><span>Search sources, signals, regions, markets...</span></div>
|
|
||||||
${mobile ? `<div class="top-center">${getRegionControlsMarkup()}</div>` : ''}
|
${mobile ? `<div class="top-center">${getRegionControlsMarkup()}</div>` : ''}
|
||||||
<div class="top-right">
|
<div class="top-right">
|
||||||
<div class="theme-switch" aria-label="Theme selector">
|
<button class="meta-pill perf-pill" onclick="togglePerfMode()" title="Reduce visual effects and start mobile in flat mode">${t('dashboard.visuals','VISUALS')} <span class="v" id="perfStatus">${lowPerfMode?t('dashboard.visualsLite','LITE'):t('dashboard.visualsFull','FULL')}</span></button>
|
||||||
<button class="theme-btn" data-theme-choice="dark" onclick="setTheme('dark')">Dark</button>
|
|
||||||
<button class="theme-btn" data-theme-choice="auto" onclick="setTheme('auto')">Auto</button>
|
|
||||||
<button class="theme-btn" data-theme-choice="light" onclick="setTheme('light')">Light</button>
|
|
||||||
</div>
|
|
||||||
<button class="meta-pill perf-pill" onclick="togglePerfMode()" title="Reduce visual effects and start mobile in flat mode">${t('dashboard.visuals','Visuals')} <span class="v" id="perfStatus">${lowPerfMode?t('dashboard.visualsLite','Lite'):t('dashboard.visualsFull','Full')}</span></button>
|
|
||||||
<span class="meta-pill">${t('dashboard.sweep','SWEEP')} <span class="v">${(D.meta.totalDurationMs/1000).toFixed(1)}s</span></span>
|
<span class="meta-pill">${t('dashboard.sweep','SWEEP')} <span class="v">${(D.meta.totalDurationMs/1000).toFixed(1)}s</span></span>
|
||||||
<span class="meta-pill">${d} <span class="v">${timeStr}</span></span>
|
<span class="meta-pill">${d} <span class="v">${timeStr}</span></span>
|
||||||
<span class="meta-pill">${t('dashboard.sources','SOURCES')} <span class="v">${D.meta.sourcesOk}/${D.meta.sourcesQueried}</span></span>
|
<span class="meta-pill">${t('dashboard.sources','SOURCES')} <span class="v">${D.meta.sourcesOk}/${D.meta.sourcesQueried}</span></span>
|
||||||
${D.delta?.summary ? `<span class="meta-pill">${t('dashboard.delta','DELTA')} <span class="v">${deltaLabel}</span></span>` : ''}
|
${D.delta?.summary ? `<span class="meta-pill">${t('dashboard.delta','DELTA')} <span class="v">${D.delta.summary.direction==='risk-off'?'▲ '+t('dashboard.riskOff','RISK-OFF'):D.delta.summary.direction==='risk-on'?'▼ '+t('dashboard.riskOn','RISK-ON'):'◆ '+t('dashboard.mixed','MIXED')}</span></span>` : ''}
|
||||||
<button class="guide-btn" onclick="configureTerminalActionToken()" title="Configure SWEEP_TOKEN for protected terminal actions">${hasActionToken?'TOKEN SET':'SET TOKEN'}</button>
|
<button class="guide-btn" onclick="configureTerminalActionToken()" title="Configure SWEEP_TOKEN for protected terminal actions">${hasActionToken?'TOKEN SET':'SET TOKEN'}</button>
|
||||||
<button class="guide-btn" onclick="openGlossary()">${t('dashboard.guideBtn','What Signals Mean')}</button>
|
<button class="guide-btn" onclick="openGlossary()">${t('dashboard.guideBtn','What Signals Mean')}</button>
|
||||||
<span class="alert-badge">${t('dashboard.highAlert','HIGH ALERT')}</span>
|
<span class="alert-badge">${t('dashboard.highAlert','HIGH ALERT')}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
renderRegionControls();
|
renderRegionControls();
|
||||||
renderAppNav();
|
|
||||||
applyTheme(themePreference);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTerminalActionToken(){
|
function getTerminalActionToken(){
|
||||||
@@ -1152,16 +734,16 @@ function renderLeftRail(){
|
|||||||
const claims=D.fred.find(f=>f.id==='ICSA');
|
const claims=D.fred.find(f=>f.id==='ICSA');
|
||||||
|
|
||||||
document.getElementById('leftRail').innerHTML=`
|
document.getElementById('leftRail').innerHTML=`
|
||||||
<div class="g-panel source-layers">
|
<div class="g-panel">
|
||||||
<div class="sec-head"><h3>${t('panels.sensorGrid','Sensor Grid')}</h3><div class="sensor-actions"><button class="mini-btn" onclick="resetLayerModes();event.stopPropagation()">RESET</button><span class="badge">${t('badges.live','LIVE')}</span></div></div>
|
<div class="sec-head"><h3>${t('panels.sensorGrid','Sensor Grid')}</h3><div class="sensor-actions"><button class="mini-btn" onclick="resetLayerModes();event.stopPropagation()">RESET</button><span class="badge">${t('badges.live','LIVE')}</span></div></div>
|
||||||
${layers.map(l=>`<div class="layer-item ${layerMode(l.key)==='focus'?'focused':''} ${layerMode(l.key)==='hidden'?'hidden-layer':''}" onclick="cycleLayerMode('${l.key}',event)" title="Click to focus. Shift/Ctrl-click to hide."><div class="layer-left"><div class="ldot ${l.dot}"></div><div><div class="layer-name">${l.name}</div><div class="layer-sub">${l.sub}</div><div class="layer-mode">${layerModeLabel(l.key)}</div></div></div><div class="layer-count">${l.count}</div></div>`).join('')}
|
${layers.map(l=>`<div class="layer-item ${layerMode(l.key)==='focus'?'focused':''} ${layerMode(l.key)==='hidden'?'hidden-layer':''}" onclick="cycleLayerMode('${l.key}',event)" title="Click to focus. Shift/Ctrl-click to hide."><div class="layer-left"><div class="ldot ${l.dot}"></div><div><div class="layer-name">${l.name}</div><div class="layer-sub">${l.sub}</div><div class="layer-mode">${layerModeLabel(l.key)}</div></div></div><div class="layer-count">${l.count}</div></div>`).join('')}
|
||||||
</div>
|
</div>
|
||||||
<div class="g-panel nuclear-panel">
|
<div class="g-panel">
|
||||||
<div class="sec-head"><h3>${t('panels.nuclearWatch','Nuclear Watch')}</h3><span class="badge">${t('badges.radiation','RADIATION')}</span></div>
|
<div class="sec-head"><h3>${t('panels.nuclearWatch','Nuclear Watch')}</h3><span class="badge">${t('badges.radiation','RADIATION')}</span></div>
|
||||||
<div class="nuke-ok">${allNormal?'● '+t('nuclear.allSitesNormal','ALL SITES NORMAL'):'⚠ '+t('nuclear.anomalyDetected','ANOMALY DETECTED')}</div>
|
<div class="nuke-ok">${allNormal?'● '+t('nuclear.allSitesNormal','ALL SITES NORMAL'):'⚠ '+t('nuclear.anomalyDetected','ANOMALY DETECTED')}</div>
|
||||||
${nukeHtml}
|
${nukeHtml}
|
||||||
</div>
|
</div>
|
||||||
<div class="g-panel risk-panel">
|
<div class="g-panel">
|
||||||
<div class="sec-head"><h3>${t('panels.riskGauges','Risk Gauges')}</h3><span class="badge">${t('badges.stress','STRESS')}</span></div>
|
<div class="sec-head"><h3>${t('panels.riskGauges','Risk Gauges')}</h3><span class="badge">${t('badges.stress','STRESS')}</span></div>
|
||||||
<div class="econ-row"><span class="elabel">${t('metrics.vix','VIX')} (Fear)</span><span class="eval" style="color:${vix?.value>20?'var(--warn)':'var(--accent)'}">${vix?.value||'--'}</span></div>
|
<div class="econ-row"><span class="elabel">${t('metrics.vix','VIX')} (Fear)</span><span class="eval" style="color:${vix?.value>20?'var(--warn)':'var(--accent)'}">${vix?.value||'--'}</span></div>
|
||||||
<div class="econ-row"><span class="elabel">${t('metrics.hySpread','HY Spread')}</span><span class="eval">${hy?.value||'--'}</span></div>
|
<div class="econ-row"><span class="elabel">${t('metrics.hySpread','HY Spread')}</span><span class="eval">${hy?.value||'--'}</span></div>
|
||||||
@@ -1171,7 +753,7 @@ function renderLeftRail(){
|
|||||||
<div class="econ-row"><span class="elabel">${t('metrics.m2Supply','M2 Supply')}</span><span class="eval">$${(m2?.value/1000)?.toFixed(1)||'--'}T</span></div>
|
<div class="econ-row"><span class="elabel">${t('metrics.m2Supply','M2 Supply')}</span><span class="eval">$${(m2?.value/1000)?.toFixed(1)||'--'}T</span></div>
|
||||||
<div class="econ-row"><span class="elabel">${t('metrics.natDebt','Nat. Debt')}</span><span class="eval">$${(parseFloat(D.treasury.totalDebt)/1e12).toFixed(2)}T</span></div>
|
<div class="econ-row"><span class="elabel">${t('metrics.natDebt','Nat. Debt')}</span><span class="eval">$${(parseFloat(D.treasury.totalDebt)/1e12).toFixed(2)}T</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="g-panel space-panel">
|
<div class="g-panel">
|
||||||
<div class="sec-head"><h3>${t('panels.spaceWatch','Space Watch')}</h3><button class="mini-btn" onclick="toggleSpaceDisplay()">${spaceDisplayMode.toUpperCase()}</button></div>
|
<div class="sec-head"><h3>${t('panels.spaceWatch','Space Watch')}</h3><button class="mini-btn" onclick="toggleSpaceDisplay()">${spaceDisplayMode.toUpperCase()}</button></div>
|
||||||
${D.space ? `
|
${D.space ? `
|
||||||
<div class="econ-row"><span class="elabel">New Objects (30d)</span><span class="eval" style="color:var(--accent2)">${D.space.totalNewObjects||0}</span></div>
|
<div class="econ-row"><span class="elabel">New Objects (30d)</span><span class="eval" style="color:var(--accent2)">${D.space.totalNewObjects||0}</span></div>
|
||||||
@@ -2021,12 +1603,7 @@ function renderLower(){
|
|||||||
${ideasHtml}
|
${ideasHtml}
|
||||||
<div class="disclosure">FOR INFORMATIONAL PURPOSES ONLY. This is not financial advice, a recommendation to buy or sell any security, or a solicitation of any kind. All signal-based observations are derived from publicly available OSINT data and should not be relied upon for investment decisions. Consult a licensed financial advisor before making any investment. Past performance does not guarantee future results.</div>
|
<div class="disclosure">FOR INFORMATIONAL PURPOSES ONLY. This is not financial advice, a recommendation to buy or sell any security, or a solicitation of any kind. All signal-based observations are derived from publicly available OSINT data and should not be relied upon for investment decisions. Consult a licensed financial advisor before making any investment. Past performance does not guarantee future results.</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
const sourcePanel = `<div class="g-panel source-health">
|
document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}`;
|
||||||
<div class="sec-head"><h3>Source Health</h3><span class="badge">${D.meta.sourcesOk}/${D.meta.sourcesQueried} online</span></div>
|
|
||||||
<div class="src-grid">${srcHtml || '<div class="src-item"><div class="sd err"></div><span>No source snapshot</span></div>'}</div>
|
|
||||||
<div class="disclosure">Sources that require API keys degrade visibly here while the rest of the sweep continues. The dashboard stays useful with partial data instead of hiding failures.</div>
|
|
||||||
</div>`;
|
|
||||||
document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}${sourcePanel}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runTerminalAction(action){
|
async function runTerminalAction(action){
|
||||||
@@ -2179,7 +1756,7 @@ function safeExternalUrl(raw){try{const u=new URL(raw,location.href);return u.pr
|
|||||||
function runBoot(){
|
function runBoot(){
|
||||||
const acledStatus = D.acled?.totalEvents > 0 ? `<span class="ok">${D.acled.totalEvents} EVENTS</span>` : '<span style="color:var(--warn)">DEGRADED</span>';
|
const acledStatus = D.acled?.totalEvents > 0 ? `<span class="ok">${D.acled.totalEvents} EVENTS</span>` : '<span style="color:var(--warn)">DEGRADED</span>';
|
||||||
const lines=[
|
const lines=[
|
||||||
{text:t('boot.initializing','INITIALIZING INTELLIGENCE TERMINAL'),delay:0},
|
{text:t('boot.initializing','INITIALIZING CRUCIX ENGINE v2.1.0'),delay:0},
|
||||||
{text:t('boot.connecting','CONNECTING {count} OSINT SOURCES...').replace('{count}',D.meta.sourcesQueried),delay:400},
|
{text:t('boot.connecting','CONNECTING {count} OSINT SOURCES...').replace('{count}',D.meta.sourcesQueried),delay:400},
|
||||||
{text:'├─ '+t('boot.sourceGroup1','OPENSKY · FIRMS · KIWISDR · MARITIME'),delay:700},
|
{text:'├─ '+t('boot.sourceGroup1','OPENSKY · FIRMS · KIWISDR · MARITIME'),delay:700},
|
||||||
{text:'├─ '+t('boot.sourceGroup2','FRED · BLS · EIA · TREASURY · GSCPI'),delay:900},
|
{text:'├─ '+t('boot.sourceGroup2','FRED · BLS · EIA · TREASURY · GSCPI'),delay:900},
|
||||||
@@ -2191,7 +1768,7 @@ function runBoot(){
|
|||||||
{text:t('boot.intelligenceSynthesis','INTELLIGENCE SYNTHESIS')+': <span class="ok">'+t('boot.active','ACTIVE')+'</span>',delay:2400},
|
{text:t('boot.intelligenceSynthesis','INTELLIGENCE SYNTHESIS')+': <span class="ok">'+t('boot.active','ACTIVE')+'</span>',delay:2400},
|
||||||
];
|
];
|
||||||
const container=document.getElementById('bootLines');
|
const container=document.getElementById('bootLines');
|
||||||
document.getElementById('bootFinal').textContent=t('dashboard.terminalActive','APP READY');
|
document.getElementById('bootFinal').textContent=t('dashboard.terminalActive','TERMINAL ACTIVE');
|
||||||
const tl=gsap.timeline();
|
const tl=gsap.timeline();
|
||||||
tl.to('.logo-ring',{opacity:1,duration:0.6,ease:'power2.out'},0);
|
tl.to('.logo-ring',{opacity:1,duration:0.6,ease:'power2.out'},0);
|
||||||
tl.to(container,{opacity:1,duration:0.3},0.3);
|
tl.to(container,{opacity:1,duration:0.3},0.3);
|
||||||
@@ -2206,6 +1783,7 @@ function runBoot(){
|
|||||||
tl.set('#boot',{display:'none'},4.2);
|
tl.set('#boot',{display:'none'},4.2);
|
||||||
tl.to('#bgRadial',{opacity:1,duration:1},3.8);
|
tl.to('#bgRadial',{opacity:1,duration:1},3.8);
|
||||||
tl.to('#bgGrid',{opacity:1,duration:1.2},4.0);
|
tl.to('#bgGrid',{opacity:1,duration:1.2},4.0);
|
||||||
|
tl.to('#scanline',{opacity:1,duration:0.8},4.3);
|
||||||
tl.to('#main',{opacity:1,duration:0.6},3.9);
|
tl.to('#main',{opacity:1,duration:0.6},3.9);
|
||||||
tl.call(()=>{
|
tl.call(()=>{
|
||||||
gsap.from('.g-panel,.topbar,.map-container',{opacity:0,y:20,scale:0.97,duration:0.5,stagger:0.06,ease:'power2.out'});
|
gsap.from('.g-panel,.topbar,.map-container',{opacity:0,y:20,scale:0.97,duration:0.5,stagger:0.06,ease:'power2.out'});
|
||||||
@@ -2220,7 +1798,7 @@ function runBoot(){
|
|||||||
},[],4.0);
|
},[],4.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isMobileLayout(){ return window.innerWidth <= 760; }
|
function isMobileLayout(){ return window.innerWidth <= 1100; }
|
||||||
|
|
||||||
function buildOsintPanel(panelClass='', maxHeight=260){
|
function buildOsintPanel(panelClass='', maxHeight=260){
|
||||||
const allPosts=[...D.tg.urgent,...D.tg.topPosts].sort((a,b)=>new Date(b.date||0)-new Date(a.date||0));
|
const allPosts=[...D.tg.urgent,...D.tg.topPosts].sort((a,b)=>new Date(b.date||0)-new Date(a.date||0));
|
||||||
@@ -2309,19 +1887,16 @@ function syncResponsiveLayout(force=false){
|
|||||||
if(force || lastResponsiveMobile === null || mobileNow !== lastResponsiveMobile){
|
if(force || lastResponsiveMobile === null || mobileNow !== lastResponsiveMobile){
|
||||||
lastResponsiveMobile = mobileNow;
|
lastResponsiveMobile = mobileNow;
|
||||||
renderTopbar();
|
renderTopbar();
|
||||||
renderTopMetrics();
|
|
||||||
renderLeftRail();
|
renderLeftRail();
|
||||||
renderLower();
|
renderLower();
|
||||||
renderRight();
|
renderRight();
|
||||||
playPanelEntrances();
|
|
||||||
}
|
}
|
||||||
refreshMapViewport(force && !isFlat);
|
refreshMapViewport(force && !isFlat);
|
||||||
positionNavGlider();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === REINIT (for live updates without boot sequence) ===
|
// === REINIT (for live updates without boot sequence) ===
|
||||||
function reinit(){
|
function reinit(){
|
||||||
renderTopbar();renderTopMetrics();renderLeftRail();renderLower();renderRight();playPanelEntrances();
|
renderTopbar();renderLeftRail();renderLower();renderRight();
|
||||||
plotMarkers();
|
plotMarkers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2360,9 +1935,7 @@ function connectSSE(){
|
|||||||
// === INIT ===
|
// === INIT ===
|
||||||
let booted = false;
|
let booted = false;
|
||||||
function init(){
|
function init(){
|
||||||
applyTheme(themePreference);
|
renderTopbar();renderLeftRail();renderLower();renderRight();
|
||||||
renderAppNav();
|
|
||||||
renderTopbar();renderTopMetrics();renderLeftRail();renderLower();renderRight();playPanelEntrances();
|
|
||||||
renderGlossary();
|
renderGlossary();
|
||||||
initMap();
|
initMap();
|
||||||
if (!booted) { runBoot(); booted = true; }
|
if (!booted) { runBoot(); booted = true; }
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 881 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.4 MiB |
Reference in New Issue
Block a user