diff --git a/README.md b/README.md index a9bb0ff..ae6bcd6 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # Intelligence Terminal -**Self-hosted intelligence dashboard. 27 open sources. Docker-first. No telemetry.** +**Modrinth-app-inspired operator dashboard. 27 open sources. Docker-first. No telemetry.** [![Gitea Repository](https://img.shields.io/badge/source-Gitea-609926?style=for-the-badge&logo=gitea&logoColor=white)](https://git.wilkensxl.de/MrSphay/intelligence-terminal) [![Docker Image](https://img.shields.io/badge/image-Gitea%20Registry-0b1220?style=for-the-badge&logo=docker&logoColor=white)](https://git.wilkensxl.de/MrSphay/-/packages/container/intelligence-terminal/latest) @@ -34,8 +34,10 @@ > 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) > 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 on a single self-contained dashboard. +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. 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. @@ -301,26 +303,21 @@ Gitea Actions publishes the same image automatically when the repository secret ## What You Get ### Live Dashboard -A self-contained Jarvis-style HUD with: -- **3D WebGL globe** (Globe.gl) with atmosphere glow, star field, and smooth rotation — plus a classic flat map toggle -- **9 marker types** across both views: fire detections, air traffic, radiation sites, maritime chokepoints, SDR receivers, OSINT events, health alerts, geolocated news, conflict events -- **Animated 3D flight corridor arcs** between air traffic hotspots and global hubs -- **Region filters** (World, Americas, Europe, Middle East, Asia Pacific, Africa) — rotates the globe or zooms the flat map -- **Live market data** — indexes, crypto, energy, commodities via Yahoo Finance (no API key needed) -- **Risk gauges** — VIX, high-yield spread, supply chain pressure index -- **OSINT feed** — English-language posts from 17 Telegram intelligence channels (expandable) -- **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) +A self-contained app-style operator dashboard with dark mode by default, a large left navigation rail, rounded content surfaces, and view-specific panels: +- **Home** - sweep status, alert posture, latest feed, source count, macro summary, and high-level signal state +- **Worldview** - Globe.gl 3D globe or flat map, regional focus controls, 9 marker types, flight corridors, and layer focus/hide controls +- **Sources** - sensor grid, source health, API-key degradation signals, and transparent partial-data states +- **Signals** - cross-source signals, sweep delta, scenario watchlist, OSINT feed, and escalation/de-escalation context +- **Markets** - live indexes, crypto, energy, commodities, VIX, high-yield spread, supply-chain pressure, and LLM-assisted ideas +- **Ops** - browser-triggered terminal actions, performance mode, Telegram/Discord operator workflows, and system status + +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. ### 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. When you switch to **VISUALS LITE**, the dashboard: -- Disables decorative background effects such as the radial/grid overlays and scanlines +- Disables decorative background effects such as radial and grid 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 - Disables globe auto-rotation and turns off animated flight-arc dashes @@ -508,7 +505,7 @@ intelligence-terminal/ ├── dashboard/ │ ├── inject.mjs # Data synthesis + standalone HTML injection │ └── public/ -│ └── jarvis.html # Self-contained Jarvis HUD +│ └── jarvis.html # Self-contained app-style operator dashboard │ ├── lib/ │ ├── llm/ # LLM abstraction (8 providers, raw fetch, no SDKs) @@ -647,7 +644,7 @@ When running `npm run dev`: | Endpoint | Description | |----------|-------------| -| `GET /` | Jarvis HUD dashboard | +| `GET /` | App-style operator dashboard | | `GET /api/data` | Current synthesized intelligence data (JSON) | | `GET /api/health` | Server status, uptime, source count, LLM status | | `GET /events` | SSE stream for live push updates | @@ -726,16 +723,16 @@ Check these in order: ## Screenshots -The `docs/` folder contains dashboard screenshots referenced by this README: +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. | File | Description | |------|-------------| -| `docs/dashboard.png` | Full dashboard — hero image at the top of this README | -| `docs/boot.png` | Cinematic boot sequence animation | -| `docs/map.png` | D3 world map with marker types and flight arcs | +| `docs/dashboard.png` | Full operator dashboard - hero image at the top of this README | +| `docs/boot.png` | Boot sequence animation | +| `docs/map.png` | Worldview map with marker types and flight arcs | | `docs/globe.png` | 3D WebGL globe view with atmosphere glow and markers | -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. +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. --- diff --git a/dashboard/public/jarvis.html b/dashboard/public/jarvis.html index cb0b228..d3efaac 100644 --- a/dashboard/public/jarvis.html +++ b/dashboard/public/jarvis.html @@ -3,9 +3,9 @@ -CRUCIX — Intelligence Terminal +Intelligence Terminal - + @@ -17,7 +17,7 @@ --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); --text:#e8f4f0;--dim:#6a8a82;--accent:#64f0c8;--accent2:#44ccff; - --warn:#ffb84c;--danger:#ff5f63;--mono:'IBM Plex Mono',monospace;--sans:'Space Grotesk',sans-serif; + --warn:#ffb84c;--danger:#ff5f63;--mono:'IBM Plex Mono',monospace;--sans:'Inter',system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif; } 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; @@ -25,10 +25,6 @@ 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%)} .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%)} -.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{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} @@ -274,7 +270,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)} /* LOW PERFORMANCE MODE */ -body.low-perf .bg-grid,body.low-perf .bg-radial,body.low-perf .scanline{display:none!important} +body.low-perf .bg-grid,body.low-perf .bg-radial{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 .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} @@ -335,20 +331,202 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200, /* 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} + +/* 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} +} - +
-
CRUCIX
+
IT
-
TERMINAL ACTIVE
+
APP READY
-
-
-
-
+
+ +
+
+
@@ -369,7 +547,8 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200,
-
+
+
@@ -434,6 +613,56 @@ let currentRegion = 'world'; let flatSvg, flatProjection, flatPath, flatG, flatZoom, flatW, flatH; 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); + applyTheme(pref); + renderTopbar(); +} + +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; +} + +function setAppView(view){ + if(!appViews[view]) return; + currentView = view; + localStorage.setItem('intelligence_terminal_view', view); + renderAppNav(); + renderTopbar(); + refreshMapViewport(true); +} + const layerTypeMap = { air: ['air'], thermal: ['thermal'], @@ -634,23 +863,35 @@ function renderTopbar(){ 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 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=`
- CRUCIX MONITOR - WARTIME STAGFLATION RISK + ${view.title} + ${view.subtitle} + Operator dashboard
+ ${mobile ? `
${getRegionControlsMarkup()}
` : ''}
- +
+ + + +
+ ${t('dashboard.sweep','SWEEP')} ${(D.meta.totalDurationMs/1000).toFixed(1)}s ${d} ${timeStr} ${t('dashboard.sources','SOURCES')} ${D.meta.sourcesOk}/${D.meta.sourcesQueried} - ${D.delta?.summary ? `${t('dashboard.delta','DELTA')} ${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')}` : ''} + ${D.delta?.summary ? `${t('dashboard.delta','DELTA')} ${deltaLabel}` : ''} ${t('dashboard.highAlert','HIGH ALERT')}
`; renderRegionControls(); + renderAppNav(); + applyTheme(themePreference); } function getTerminalActionToken(){ @@ -734,16 +975,16 @@ function renderLeftRail(){ const claims=D.fred.find(f=>f.id==='ICSA'); document.getElementById('leftRail').innerHTML=` -
+

${t('panels.sensorGrid','Sensor Grid')}

${t('badges.live','LIVE')}
${layers.map(l=>`
${l.name}
${l.sub}
${layerModeLabel(l.key)}
${l.count}
`).join('')}
-
+

${t('panels.nuclearWatch','Nuclear Watch')}

${t('badges.radiation','RADIATION')}
${allNormal?'● '+t('nuclear.allSitesNormal','ALL SITES NORMAL'):'⚠ '+t('nuclear.anomalyDetected','ANOMALY DETECTED')}
${nukeHtml}
-
+

${t('panels.riskGauges','Risk Gauges')}

${t('badges.stress','STRESS')}
${t('metrics.vix','VIX')} (Fear)${vix?.value||'--'}
${t('metrics.hySpread','HY Spread')}${hy?.value||'--'}
@@ -753,7 +994,7 @@ function renderLeftRail(){
${t('metrics.m2Supply','M2 Supply')}$${(m2?.value/1000)?.toFixed(1)||'--'}T
${t('metrics.natDebt','Nat. Debt')}$${(parseFloat(D.treasury.totalDebt)/1e12).toFixed(2)}T
-
+

${t('panels.spaceWatch','Space Watch')}

${D.space ? `
New Objects (30d)${D.space.totalNewObjects||0}
@@ -1603,7 +1844,12 @@ function renderLower(){ ${ideasHtml}
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.
`; - document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}`; + const sourcePanel = `
+

Source Health

${D.meta.sourcesOk}/${D.meta.sourcesQueried} online
+
${srcHtml || '
No source snapshot
'}
+
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.
+
`; + document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}${sourcePanel}`; } async function runTerminalAction(action){ @@ -1756,7 +2002,7 @@ function safeExternalUrl(raw){try{const u=new URL(raw,location.href);return u.pr function runBoot(){ const acledStatus = D.acled?.totalEvents > 0 ? `${D.acled.totalEvents} EVENTS` : 'DEGRADED'; const lines=[ - {text:t('boot.initializing','INITIALIZING CRUCIX ENGINE v2.1.0'),delay:0}, + {text:t('boot.initializing','INITIALIZING INTELLIGENCE TERMINAL'),delay:0}, {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.sourceGroup2','FRED · BLS · EIA · TREASURY · GSCPI'),delay:900}, @@ -1768,7 +2014,7 @@ function runBoot(){ {text:t('boot.intelligenceSynthesis','INTELLIGENCE SYNTHESIS')+': '+t('boot.active','ACTIVE')+'',delay:2400}, ]; const container=document.getElementById('bootLines'); - document.getElementById('bootFinal').textContent=t('dashboard.terminalActive','TERMINAL ACTIVE'); + document.getElementById('bootFinal').textContent=t('dashboard.terminalActive','APP READY'); const tl=gsap.timeline(); tl.to('.logo-ring',{opacity:1,duration:0.6,ease:'power2.out'},0); tl.to(container,{opacity:1,duration:0.3},0.3); @@ -1783,7 +2029,6 @@ function runBoot(){ tl.set('#boot',{display:'none'},4.2); tl.to('#bgRadial',{opacity:1,duration:1},3.8); 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.call(()=>{ gsap.from('.g-panel,.topbar,.map-container',{opacity:0,y:20,scale:0.97,duration:0.5,stagger:0.06,ease:'power2.out'}); @@ -1798,7 +2043,7 @@ function runBoot(){ },[],4.0); } -function isMobileLayout(){ return window.innerWidth <= 1100; } +function isMobileLayout(){ return window.innerWidth <= 760; } 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)); @@ -1935,6 +2180,8 @@ function connectSSE(){ // === INIT === let booted = false; function init(){ + applyTheme(themePreference); + renderAppNav(); renderTopbar();renderLeftRail();renderLower();renderRight(); renderGlossary(); initMap(); diff --git a/docs/dashboard.png b/docs/dashboard.png index e76cc0a..e0b1fb3 100644 Binary files a/docs/dashboard.png and b/docs/dashboard.png differ