Add dashboard signal guide glossary (#42)
This commit is contained in:
@@ -59,6 +59,8 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||
.meta-pill{font-family:var(--mono);font-size:11px;color:var(--dim);letter-spacing:0.06em;padding:5px 10px;border:1px solid var(--border)}
|
||||
.meta-pill .v{color:var(--text);font-weight:500}
|
||||
.alert-badge{padding:5px 12px;font-family:var(--mono);font-size:11px;font-weight:700;letter-spacing:0.1em;text-transform:uppercase;border:1px solid rgba(255,95,99,0.4);color:#fff;background:linear-gradient(135deg,rgba(255,95,99,0.2),rgba(255,95,99,0.08))}
|
||||
.guide-btn{padding:5px 12px;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;border:1px solid rgba(68,204,255,0.28);color:var(--accent2);background:rgba(68,204,255,0.07);cursor:pointer;transition:all 0.2s}
|
||||
.guide-btn:hover{border-color:rgba(68,204,255,0.5);background:rgba(68,204,255,0.12);color:#d9f7ff}
|
||||
.perf-pill{cursor:pointer;background:rgba(255,255,255,0.05);transition:all 0.2s}
|
||||
.perf-pill:hover{border-color:var(--accent2);color:var(--text);background:rgba(68,204,255,0.08)}
|
||||
|
||||
@@ -124,6 +126,23 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||
.map-popup .pp-text{font-size:11px;line-height:1.4;color:#c8d8d2}
|
||||
.map-popup .pp-meta{font-family:var(--mono);font-size:10px;color:var(--dim);margin-top:6px}
|
||||
.map-popup .pp-close{position:absolute;top:6px;right:8px;background:none;border:none;color:var(--dim);font-size:14px;cursor:pointer}
|
||||
.glossary-overlay{position:fixed;inset:0;z-index:1200;background:rgba(2,6,10,0.72);backdrop-filter:blur(10px);opacity:0;pointer-events:none;transition:opacity 0.25s ease}
|
||||
.glossary-overlay.show{opacity:1;pointer-events:auto}
|
||||
.glossary-panel{position:absolute;top:18px;right:18px;width:min(420px,calc(100vw - 32px));max-height:calc(100vh - 36px);display:flex;flex-direction:column;border:1px solid rgba(68,204,255,0.22);background:rgba(5,12,19,0.96);box-shadow:0 18px 48px rgba(0,0,0,0.45)}
|
||||
.glossary-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:14px 16px 10px;border-bottom:1px solid rgba(255,255,255,0.06)}
|
||||
.glossary-kicker{font-family:var(--mono);font-size:10px;letter-spacing:0.12em;text-transform:uppercase;color:var(--accent2);margin-bottom:5px}
|
||||
.glossary-title{font-size:18px;font-weight:600;line-height:1.15}
|
||||
.glossary-sub{font-size:11px;line-height:1.45;color:var(--dim);margin-top:6px}
|
||||
.glossary-close{border:1px solid var(--border);background:rgba(255,255,255,0.03);color:var(--dim);width:30px;height:30px;font-size:18px;cursor:pointer;flex-shrink:0}
|
||||
.glossary-body{overflow:auto;padding:12px 16px 16px;display:flex;flex-direction:column;gap:10px}
|
||||
.glossary-card{padding:12px;border:1px solid rgba(255,255,255,0.05);background:rgba(255,255,255,0.02)}
|
||||
.glossary-term{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:7px}
|
||||
.glossary-term strong{font-family:var(--mono);font-size:11px;letter-spacing:0.08em;text-transform:uppercase}
|
||||
.glossary-tag{font-family:var(--mono);font-size:9px;letter-spacing:0.08em;text-transform:uppercase;padding:2px 6px;border:1px solid rgba(100,240,200,0.18);color:var(--accent);background:rgba(100,240,200,0.05)}
|
||||
.glossary-line{font-size:11px;line-height:1.5;color:#c8d8d2}
|
||||
.glossary-line + .glossary-line{margin-top:5px}
|
||||
.glossary-label{font-family:var(--mono);font-size:9px;letter-spacing:0.08em;text-transform:uppercase;color:var(--dim);margin-right:6px}
|
||||
.glossary-foot{padding:10px 16px 14px;border-top:1px solid rgba(255,255,255,0.06);font-family:var(--mono);font-size:9px;line-height:1.5;color:rgba(106,138,130,0.8)}
|
||||
.map-loading{position:absolute;inset:0;z-index:12;display:none;align-items:center;justify-content:center;background:linear-gradient(180deg,rgba(2,6,10,0.78),rgba(2,6,10,0.9));backdrop-filter:blur(10px)}
|
||||
.map-loading.show{display:flex}
|
||||
.map-loading-card{display:flex;flex-direction:column;align-items:center;gap:10px;padding:16px 18px;border:1px solid rgba(68,204,255,0.18);background:rgba(6,14,22,0.88);box-shadow:0 12px 32px rgba(0,0,0,0.35)}
|
||||
@@ -250,7 +269,7 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200,
|
||||
.top-left,.top-center,.top-right{width:100%}
|
||||
.top-center{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}
|
||||
.top-right{gap:6px}
|
||||
.region-btn,.meta-pill,.alert-badge{font-size:10px}
|
||||
.region-btn,.meta-pill,.alert-badge,.guide-btn{font-size:10px}
|
||||
.grid{display:flex;flex-direction:column}
|
||||
#centerCol{order:1}
|
||||
#rightRail{order:2}
|
||||
@@ -262,6 +281,7 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200,
|
||||
.lower .lp-ticker,.lower .lp-osint,.lower .lp-delta,.lower .lp-macro,.lower .lp-ideas{flex:1 1 100%;max-width:none}
|
||||
.metrics-row{grid-template-columns:repeat(2,1fr)}
|
||||
.src-grid{grid-template-columns:repeat(2,1fr)}
|
||||
.glossary-panel{top:auto;right:0;left:0;bottom:0;width:100%;max-height:min(72vh,720px);border-left:none;border-right:none;border-bottom:none}
|
||||
}
|
||||
|
||||
/* CONFLICT LAYER */
|
||||
@@ -326,6 +346,20 @@ body.low-perf .ticker-wrap::-webkit-scrollbar-thumb{background:rgba(100,240,200,
|
||||
<div class="col" id="rightRail"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="glossary-overlay" id="glossaryOverlay" onclick="if(event.target===this) closeGlossary()">
|
||||
<div class="glossary-panel">
|
||||
<div class="glossary-head">
|
||||
<div>
|
||||
<div class="glossary-kicker">Signal Guide</div>
|
||||
<div class="glossary-title">What the signals actually mean</div>
|
||||
<div class="glossary-sub">Plain-English interpretation, why it matters, and what you should not infer from it.</div>
|
||||
</div>
|
||||
<button class="glossary-close" onclick="closeGlossary()" aria-label="Close signal guide">×</button>
|
||||
</div>
|
||||
<div class="glossary-body" id="glossaryBody"></div>
|
||||
<div class="glossary-foot">Treat these as interpretation guides, not conclusions. Stronger judgments should come from corroboration across multiple layers, not from a single signal viewed in isolation.</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// === DATA ===
|
||||
let D = null;
|
||||
@@ -336,6 +370,120 @@ let flightsVisible = true;
|
||||
let lowPerfMode = localStorage.getItem('crucix_low_perf') === 'true';
|
||||
let isFlat = shouldStartFlat();
|
||||
let flatSvg, flatProjection, flatPath, flatG, flatZoom, flatW, flatH;
|
||||
const signalGuideItems = [
|
||||
{
|
||||
term:'No Callsign',
|
||||
category:'Air',
|
||||
meaning:'OpenSky received an aircraft track without a usable callsign or flight ID in that record.',
|
||||
matters:'Useful as an opacity signal. A cluster of missing callsigns can indicate incomplete transponder metadata or less transparent traffic.',
|
||||
notMeaning:'Not proof of military, covert, or hostile activity on its own.',
|
||||
example:'South China Sea: No Callsign 6 of 152 means 6 tracks in that theater had no usable callsign in the feed.'
|
||||
},
|
||||
{
|
||||
term:'High Altitude',
|
||||
category:'Air',
|
||||
meaning:'Aircraft above 12,000 meters, roughly 39,000 feet, in the current OpenSky snapshot.',
|
||||
matters:'Separates cruise-level traffic from lower-altitude local or regional movement.',
|
||||
notMeaning:'Not a danger score and not inherently unusual. Commercial jets commonly operate here.',
|
||||
example:'High Altitude 2 means only 2 tracked aircraft in that hotspot were above the cruise threshold at that snapshot.'
|
||||
},
|
||||
{
|
||||
term:'Top Countries',
|
||||
category:'Air',
|
||||
meaning:'The most common OpenSky origin_country values among aircraft in that hotspot.',
|
||||
matters:'Useful for understanding the rough composition of traffic flowing through a theater.',
|
||||
notMeaning:'Not who is controlling the aircraft right now and not a direct indicator of military ownership.',
|
||||
example:'China (61), Philippines (39), Taiwan (17) means those were the top registered origin countries in the snapshot.'
|
||||
},
|
||||
{
|
||||
term:'FRP',
|
||||
category:'Thermal',
|
||||
meaning:'Fire Radiative Power. This is the intensity of one specific FIRMS hotspot, measured in megawatts.',
|
||||
matters:'Higher FRP usually means a hotter, larger, or more energetic fire event at that exact point.',
|
||||
notMeaning:'Not the intensity of the whole region and not automatic proof of conflict activity.',
|
||||
example:'Sudan / Horn of Africa: FRP 92.3 MW describes that one hotspot, while Total 1,451 describes the entire regional detection count.'
|
||||
},
|
||||
{
|
||||
term:'Total Detections',
|
||||
category:'Thermal',
|
||||
meaning:'The total number of FIRMS thermal detections in the entire region bucket for the current sweep.',
|
||||
matters:'Useful for spotting unusually active fire clusters, especially when compared with historical baselines or night activity.',
|
||||
notMeaning:'Not a count for the single map point you clicked and not necessarily a conflict count.',
|
||||
example:'Total 1,451 means the whole Sudan / Horn of Africa bucket had 1,451 detections in that sweep.'
|
||||
},
|
||||
{
|
||||
term:'Night Detections',
|
||||
category:'Thermal',
|
||||
meaning:'Thermal detections tagged as occurring at night inside the broader FIRMS region bucket.',
|
||||
matters:'Nighttime heat can be more noteworthy because it is less likely to be routine daytime land burning.',
|
||||
notMeaning:'Not a direct combat indicator. It still needs context from location, baseline, and corroborating sources.',
|
||||
example:'Night 140 means 140 of the 1,451 regional detections were nighttime detections in that sweep.'
|
||||
},
|
||||
{
|
||||
term:'Chokepoint',
|
||||
category:'Maritime',
|
||||
meaning:'A strategic maritime corridor or passage where trade and energy flows can be delayed, diverted, or disrupted.',
|
||||
matters:'These nodes matter because a disruption here can affect shipping costs, transit times, and commodity pricing globally.',
|
||||
notMeaning:'Not proof that disruption is happening now. It is a strategic watch location.',
|
||||
example:'Bab el-Mandeb or the Strait of Hormuz matter because shipping and energy flows concentrate there.'
|
||||
},
|
||||
{
|
||||
term:'SDR Receiver',
|
||||
category:'Signals',
|
||||
meaning:'A publicly reachable software-defined radio receiver in or near a region of interest.',
|
||||
matters:'Dense receiver coverage can give you more ability to monitor communications or signal activity in a theater.',
|
||||
notMeaning:'Not evidence of hostile emissions or a threat by itself. It is an observation and monitoring layer.',
|
||||
example:'South China Sea SDR count means publicly accessible KiwiSDR receivers are available in or near that zone.'
|
||||
},
|
||||
{
|
||||
term:'CPM',
|
||||
category:'Radiation',
|
||||
meaning:'Counts per minute from a radiation monitoring source, used here for relative radiation status at a site.',
|
||||
matters:'Useful for spotting anomalies against the site’s normal range or comparing consecutive readings.',
|
||||
notMeaning:'Not a direct safety verdict on its own. Interpretation depends on local baseline and trend, not the raw number alone.',
|
||||
example:'A site reading 33 CPM can be normal if that location’s usual background level is in the same range.'
|
||||
},
|
||||
{
|
||||
term:'HY Spread',
|
||||
category:'Macro',
|
||||
meaning:'High-yield credit spread, shown here as a stress proxy from FRED credit data.',
|
||||
matters:'When spreads widen, markets are usually pricing more credit stress and tighter financial conditions.',
|
||||
notMeaning:'Not a recession call by itself. It is one stress signal among many.',
|
||||
example:'A rising HY Spread alongside higher VIX and weaker equities is a stronger risk-off pattern than HY alone.'
|
||||
},
|
||||
{
|
||||
term:'VIX',
|
||||
category:'Macro',
|
||||
meaning:'The CBOE Volatility Index, commonly used as a market-implied fear or volatility gauge.',
|
||||
matters:'Higher VIX often means more expected equity volatility and more defensive market positioning.',
|
||||
notMeaning:'Not a direct forecast of a crash and not a geopolitical indicator by itself.',
|
||||
example:'VIX above 20 with widening HY spreads is a stronger stress pattern than VIX alone.'
|
||||
},
|
||||
{
|
||||
term:'GSCPI',
|
||||
category:'Macro',
|
||||
meaning:'The Global Supply Chain Pressure Index, a broad indicator of global supply-chain strain.',
|
||||
matters:'It helps translate geopolitical or weather disruptions into likely pressure on shipping, inventory, and pricing.',
|
||||
notMeaning:'Not a live market price and not a company-specific supply-chain score by itself.',
|
||||
example:'A higher GSCPI makes route or energy shocks more likely to spill into broader cost pressure.'
|
||||
},
|
||||
{
|
||||
term:'WHO Alert',
|
||||
category:'Health',
|
||||
meaning:'A WHO Disease Outbreak News item or outbreak-related bulletin surfaced in the health layer.',
|
||||
matters:'Useful for watching outbreaks that could affect travel, supply chains, humanitarian stress, or regional operating conditions.',
|
||||
notMeaning:'Not a pandemic declaration and not automatically high severity.',
|
||||
example:'A WHO alert in a port-heavy region matters more if it overlaps shipping, border controls, or local instability signals.'
|
||||
},
|
||||
{
|
||||
term:'Sweep Delta',
|
||||
category:'Platform',
|
||||
meaning:'The change summary between the current sweep and the previous one, including new, escalated, and de-escalated signals.',
|
||||
matters:'Useful for spotting what changed recently instead of re-reading the full dashboard from scratch.',
|
||||
notMeaning:'Not a full risk model. It is a directional change layer on top of the raw signals.',
|
||||
example:'A delta marked risk-off with several new and escalated items means the latest sweep materially worsened the signal mix.'
|
||||
}
|
||||
];
|
||||
const regionPOV = {
|
||||
world: { lat: 20, lng: 20, altitude: 1.8 },
|
||||
americas: { lat: 35, lng: -95, altitude: 1.0 },
|
||||
@@ -406,6 +554,7 @@ function renderTopbar(){
|
||||
<span class="meta-pill">${d} <span class="v">${t}</span></span>
|
||||
<span class="meta-pill">SOURCES <span class="v">${D.meta.sourcesOk}/${D.meta.sourcesQueried}</span></span>
|
||||
${D.delta?.summary ? `<span class="meta-pill">DELTA <span class="v">${D.delta.summary.direction==='risk-off'?'▲ RISK-OFF':D.delta.summary.direction==='risk-on'?'▼ RISK-ON':'◆ MIXED'}</span></span>` : ''}
|
||||
<button class="guide-btn" onclick="openGlossary()">What Signals Mean</button>
|
||||
<span class="alert-badge">HIGH ALERT</span>
|
||||
</div>`;
|
||||
}
|
||||
@@ -1404,6 +1553,37 @@ function buildOsintPanel(panelClass='', maxHeight=260){
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function renderGlossary(){
|
||||
const body = document.getElementById('glossaryBody');
|
||||
if(!body) return;
|
||||
body.innerHTML = signalGuideItems.map(item => `
|
||||
<div class="glossary-card">
|
||||
<div class="glossary-term">
|
||||
<strong>${item.term}</strong>
|
||||
<span class="glossary-tag">${item.category}</span>
|
||||
</div>
|
||||
<div class="glossary-line"><span class="glossary-label">Meaning</span>${item.meaning}</div>
|
||||
<div class="glossary-line"><span class="glossary-label">Why it matters</span>${item.matters}</div>
|
||||
<div class="glossary-line"><span class="glossary-label">Not proof of</span>${item.notMeaning}</div>
|
||||
<div class="glossary-line"><span class="glossary-label">Example</span>${item.example}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function openGlossary(){
|
||||
const overlay = document.getElementById('glossaryOverlay');
|
||||
if(!overlay) return;
|
||||
overlay.classList.add('show');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeGlossary(){
|
||||
const overlay = document.getElementById('glossaryOverlay');
|
||||
if(!overlay) return;
|
||||
overlay.classList.remove('show');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function refreshMapViewport(forceGlobeReflow=false){
|
||||
const container = document.getElementById('mapContainer');
|
||||
if(!container) return;
|
||||
@@ -1487,6 +1667,7 @@ function connectSSE(){
|
||||
let booted = false;
|
||||
function init(){
|
||||
renderTopbar();renderLeftRail();renderLower();renderRight();
|
||||
renderGlossary();
|
||||
initMap();
|
||||
if (!booted) { runBoot(); booted = true; }
|
||||
// Close popup on click outside markers
|
||||
@@ -1501,6 +1682,9 @@ function init(){
|
||||
if(url) window.open(url,'_blank','noopener');
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown',e=>{
|
||||
if(e.key === 'Escape') closeGlossary();
|
||||
});
|
||||
syncResponsiveLayout(true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user