Improve mobile dashboard layout and map defaults
This commit is contained in:
@@ -226,7 +226,25 @@ html,body{height:100%;background:var(--bg);color:var(--text);font-family:var(--s
|
||||
|
||||
/* RESPONSIVE */
|
||||
@media(max-width:1400px){.grid{grid-template-columns:240px 1fr 320px}.metrics-row{grid-template-columns:repeat(3,1fr)}.src-grid{grid-template-columns:repeat(3,1fr)}}
|
||||
@media(max-width:1100px){.grid{grid-template-columns:1fr}.lower .lp-ticker,.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)}}
|
||||
@media(max-width:1100px){
|
||||
#main{padding:8px}
|
||||
.topbar{padding:10px 12px}
|
||||
.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}
|
||||
.grid{display:flex;flex-direction:column}
|
||||
#centerCol{order:1}
|
||||
#rightRail{order:2}
|
||||
#leftRail{order:3}
|
||||
.map-container{min-height:420px}
|
||||
.map-hint{font-size:8px;right:8px}
|
||||
.map-legend{left:8px;right:8px;bottom:8px;gap:4px}
|
||||
.leg-item{font-size:8px}
|
||||
.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)}
|
||||
}
|
||||
|
||||
/* CONFLICT LAYER */
|
||||
@keyframes pulse-conflict{0%,100%{opacity:0.5;stroke-width:1.5}50%{opacity:0.9;stroke-width:2.5}}
|
||||
@@ -295,7 +313,7 @@ let D = null;
|
||||
// === GLOBALS ===
|
||||
let globe = null;
|
||||
let flightsVisible = true;
|
||||
let isFlat = true;
|
||||
let isFlat = !isMobileLayout();
|
||||
let flatSvg, flatProjection, flatPath, flatG, flatZoom, flatW, flatH;
|
||||
const regionPOV = {
|
||||
world: { lat: 20, lng: 20, altitude: 1.8 },
|
||||
@@ -486,18 +504,27 @@ function initMap(){
|
||||
});
|
||||
|
||||
// Resize handler
|
||||
window.addEventListener('resize', () => {
|
||||
const c = document.getElementById('mapContainer');
|
||||
globe.width(c.clientWidth).height(c.clientHeight || 560);
|
||||
window.addEventListener('resize', () => syncResponsiveLayout());
|
||||
window.addEventListener('orientationchange', () => setTimeout(() => syncResponsiveLayout(true), 150));
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if(!document.hidden) setTimeout(() => syncResponsiveLayout(true), 150);
|
||||
});
|
||||
window.addEventListener('pageshow', () => setTimeout(() => syncResponsiveLayout(true), 150));
|
||||
|
||||
// Plot globe markers (preloaded but hidden)
|
||||
plotMarkers();
|
||||
|
||||
// Start in flat mode — hide globe, show flat map
|
||||
document.getElementById('globeViz').style.display = 'none';
|
||||
document.getElementById('flatMapSvg').style.display = 'block';
|
||||
initFlatMap();
|
||||
if(isFlat){
|
||||
document.getElementById('globeViz').style.display = 'none';
|
||||
document.getElementById('flatMapSvg').style.display = 'block';
|
||||
initFlatMap();
|
||||
} else {
|
||||
document.getElementById('globeViz').style.display = 'block';
|
||||
document.getElementById('flatMapSvg').style.display = 'none';
|
||||
document.getElementById('projToggle').textContent = 'FLAT MODE';
|
||||
document.getElementById('mapHint').textContent = 'DRAG TO ROTATE · SCROLL TO ZOOM';
|
||||
}
|
||||
|
||||
// Legend
|
||||
document.getElementById('mapLegend').innerHTML=
|
||||
@@ -976,6 +1003,7 @@ function mkSparkSvg(values, isGood){
|
||||
|
||||
// === LOWER GRID ===
|
||||
function renderLower(){
|
||||
const mobile = isMobileLayout();
|
||||
const spread=D.fred.find(f=>f.id==='T10Y2Y');
|
||||
const ff=D.fred.find(f=>f.id==='DFF');
|
||||
const ue=D.bls.find(b=>b.id==='LNS14000000');
|
||||
@@ -1092,23 +1120,14 @@ function renderLower(){
|
||||
}
|
||||
const deltaHtml = hasDelta ? deltaRows.join('') : '<div style="padding:12px;text-align:center;color:var(--dim);font-family:var(--mono);font-size:10px">No changes since last sweep</div>';
|
||||
|
||||
document.getElementById('lowerGrid').innerHTML=`
|
||||
<div class="g-panel lp-ticker" style="display:flex;flex-direction:column">
|
||||
const tickerPanel = `<div class="g-panel lp-ticker" style="display:flex;flex-direction:column">
|
||||
<div class="sec-head"><h3>Live News Ticker</h3><span class="badge">${feed.length} ITEMS</span></div>
|
||||
<div class="ticker-wrap" style="--ticker-duration:${tickerDuration}s">
|
||||
<div class="ticker-track">${tickerCards}${tickerCards}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="g-panel lp-delta">
|
||||
<div class="sec-head"><h3>Sweep Delta</h3><span class="badge ${dirClass}">${dirEmoji} ${ds.direction?ds.direction.toUpperCase():'BASELINE'}</span></div>
|
||||
${hasDelta?`<div style="display:flex;gap:12px;margin-bottom:6px;font-family:var(--mono);font-size:10px">
|
||||
<span style="color:var(--dim)">Changes: <span style="color:var(--accent)">${ds.totalChanges}</span></span>
|
||||
<span style="color:var(--dim)">Critical: <span style="color:${ds.criticalChanges>0?'var(--warn)':'var(--dim)'}">${ds.criticalChanges||0}</span></span>
|
||||
${ds.signalBreakdown?`<span style="color:var(--dim)">New: <span style="color:#4dd0e1">${ds.signalBreakdown.new}</span> ↑${ds.signalBreakdown.escalated} ↓${ds.signalBreakdown.deescalated}</span>`:''}
|
||||
</div>`:''}
|
||||
<div class="delta-list">${deltaHtml}</div>
|
||||
</div>
|
||||
<div class="g-panel lp-macro">
|
||||
</div>`;
|
||||
const osintPanel = mobile ? buildOsintPanel('lp-osint', 240) : '';
|
||||
const macroPanel = `<div class="g-panel lp-macro">
|
||||
<div class="sec-head"><h3>Macro + Markets</h3><span class="badge">${mkt.timestamp?'LIVE':'DELAYED'}</span></div>
|
||||
${hasMarkets?`<div style="margin-bottom:8px">
|
||||
<div style="font-family:var(--mono);font-size:9px;color:var(--dim);margin-bottom:4px;letter-spacing:1px">INDEXES</div>
|
||||
@@ -1129,33 +1148,32 @@ function renderLower(){
|
||||
<div style="font-family:var(--mono);font-size:10px;color:var(--dim);margin-bottom:4px">WTI 5-DAY</div>
|
||||
<div class="spark">${sparkHtml}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="g-panel lp-ideas">
|
||||
</div>`;
|
||||
const ideasPanel = `<div class="g-panel lp-ideas">
|
||||
<div class="sec-head"><h3>Leverageable Ideas</h3>${D.ideasSource==='llm'?'<span class="ideas-src llm">AI ENHANCED</span>':D.ideasSource==='disabled'?'<span class="ideas-src static">LLM OFF</span>':'<span class="ideas-src static">PENDING</span>'}</div>
|
||||
${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>`;
|
||||
const deltaPanel = `<div class="g-panel lp-delta">
|
||||
<div class="sec-head"><h3>Sweep Delta</h3><span class="badge ${dirClass}">${dirEmoji} ${ds.direction?ds.direction.toUpperCase():'BASELINE'}</span></div>
|
||||
${hasDelta?`<div style="display:flex;gap:12px;margin-bottom:6px;font-family:var(--mono);font-size:10px">
|
||||
<span style="color:var(--dim)">Changes: <span style="color:var(--accent)">${ds.totalChanges}</span></span>
|
||||
<span style="color:var(--dim)">Critical: <span style="color:${ds.criticalChanges>0?'var(--warn)':'var(--dim)'}">${ds.criticalChanges||0}</span></span>
|
||||
${ds.signalBreakdown?`<span style="color:var(--dim)">New: <span style="color:#4dd0e1">${ds.signalBreakdown.new}</span> ↑${ds.signalBreakdown.escalated} ↓${ds.signalBreakdown.deescalated}</span>`:''}
|
||||
</div>`:''}
|
||||
<div class="delta-list">${deltaHtml}</div>
|
||||
</div>`;
|
||||
|
||||
document.getElementById('lowerGrid').innerHTML=`${tickerPanel}${osintPanel}${macroPanel}${ideasPanel}${deltaPanel}`;
|
||||
}
|
||||
|
||||
// === RIGHT RAIL ===
|
||||
function renderRight(){
|
||||
const mobile = isMobileLayout();
|
||||
// CROSS-SOURCE SIGNALS — moved from lower grid to right rail
|
||||
const signals=D.tSignals.slice(0,6).map((s,i)=>`<div class="signal-row"><strong>Signal ${i+1}</strong><p>${s}</p></div>`).join('');
|
||||
|
||||
// OSINT TICKER — Telegram + WHO as flowing cards
|
||||
const allPosts=[...D.tg.urgent,...D.tg.topPosts].sort((a,b)=>new Date(b.date||0)-new Date(a.date||0));
|
||||
const whoItems=D.who.slice(0,4).map(w=>({channel:'WHO ALERT',text:w.title,date:w.date,isWho:true}));
|
||||
const osintItems=[...allPosts.slice(0,15),...whoItems];
|
||||
const osintCards=osintItems.map(p=>{
|
||||
const isU=p.urgentFlags&&p.urgentFlags.length>0;
|
||||
const views=p.views?p.views>=1000?`${(p.views/1000).toFixed(0)}K`:p.views:'';
|
||||
const age=p.date?getAge(p.date):'';
|
||||
const flags=(p.urgentFlags||[]).map(f=>`<span class="tk-src tg" style="margin-right:2px">${f}</span>`).join('');
|
||||
const srcCls=p.isWho?'style="color:#69f0ae;border-color:rgba(105,240,174,0.4)"':'class="tk-src tg"';
|
||||
return `<div class="tk-card ${isU?'urgent':''}"><span ${srcCls}>${(p.channel||'OSINT').toUpperCase().substring(0,14)}</span>${views?`<span class="tk-src other">${views}</span>`:''}<span class="tk-time">${age}</span>${flags}<div class="tk-head">${cleanText((p.text||'').substring(0,160))}</div></div>`;
|
||||
}).join('');
|
||||
const osintDuration=Math.max(25,osintItems.length*3);
|
||||
|
||||
const signalMetrics=[
|
||||
{l:'Incident Tempo',v:D.tg.urgent.length,p:70},
|
||||
{l:'Air Theaters',v:D.air.length,p:60},
|
||||
@@ -1166,17 +1184,12 @@ function renderRight(){
|
||||
];
|
||||
|
||||
document.getElementById('rightRail').innerHTML=`
|
||||
<div class="g-panel">
|
||||
<div class="g-panel right-signals">
|
||||
<div class="sec-head"><h3>Cross-Source Signals</h3><span class="badge">WORLDVIEW</span></div>
|
||||
${signals}
|
||||
</div>
|
||||
<div class="g-panel" style="display:flex;flex-direction:column">
|
||||
<div class="sec-head"><h3>OSINT Stream</h3><span class="badge">${D.tg.urgent.length} URGENT</span></div>
|
||||
<div class="ticker-wrap" style="--ticker-duration:${osintDuration}s;max-height:260px">
|
||||
<div class="ticker-track">${osintCards}${osintCards}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="g-panel">
|
||||
${mobile ? '' : buildOsintPanel('right-osint', 260)}
|
||||
<div class="g-panel right-core">
|
||||
<div class="sec-head"><h3>Signal Core</h3><span class="badge">HOT METRICS</span></div>
|
||||
${signalMetrics.map(s=>`<div class="sm"><span class="sml">${s.l}</span><div class="smb"><span style="width:${s.p}%"></span></div><span class="smv">${s.v}</span></div>`).join('')}
|
||||
</div>`;
|
||||
@@ -1210,8 +1223,8 @@ function runBoot(){
|
||||
tl.call(()=>{
|
||||
const div=document.createElement('div');div.innerHTML=line.text;div.style.opacity='0';
|
||||
container.appendChild(div);gsap.to(div,{opacity:1,duration:0.2});
|
||||
},null,line.delay/1000+0.5);
|
||||
});
|
||||
},[],line.delay/1000+0.5);
|
||||
});
|
||||
tl.to('#bootFinal',{opacity:1,duration:0.4},3.1);
|
||||
tl.to('#boot',{opacity:0,duration:0.5,ease:'power2.in'},3.7);
|
||||
tl.set('#boot',{display:'none'},4.2);
|
||||
@@ -1232,6 +1245,70 @@ function runBoot(){
|
||||
},4.0);
|
||||
}
|
||||
|
||||
function isMobileLayout(){ return window.innerWidth <= 1100; }
|
||||
|
||||
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 whoItems=D.who.slice(0,4).map(w=>({channel:'WHO ALERT',text:w.title,date:w.date,isWho:true}));
|
||||
const osintItems=[...allPosts.slice(0,15),...whoItems];
|
||||
const osintCards=osintItems.map(p=>{
|
||||
const isU=p.urgentFlags&&p.urgentFlags.length>0;
|
||||
const views=p.views?p.views>=1000?`${(p.views/1000).toFixed(0)}K`:p.views:'';
|
||||
const age=p.date?getAge(p.date):'';
|
||||
const flags=(p.urgentFlags||[]).map(f=>`<span class="tk-src tg" style="margin-right:2px">${f}</span>`).join('');
|
||||
const srcCls=p.isWho?'style="color:#69f0ae;border-color:rgba(105,240,174,0.4)"':'class="tk-src tg"';
|
||||
return `<div class="tk-card ${isU?'urgent':''}"><span ${srcCls}>${(p.channel||'OSINT').toUpperCase().substring(0,14)}</span>${views?`<span class="tk-src other">${views}</span>`:''}<span class="tk-time">${age}</span>${flags}<div class="tk-head">${cleanText((p.text||'').substring(0,160))}</div></div>`;
|
||||
}).join('');
|
||||
const osintDuration=Math.max(25,osintItems.length*3);
|
||||
return `<div class="g-panel ${panelClass}" style="display:flex;flex-direction:column">
|
||||
<div class="sec-head"><h3>OSINT Stream</h3><span class="badge">${D.tg.urgent.length} URGENT</span></div>
|
||||
<div class="ticker-wrap" style="--ticker-duration:${osintDuration}s;max-height:${maxHeight}px">
|
||||
<div class="ticker-track">${osintCards}${osintCards}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function refreshMapViewport(forceGlobeReflow=false){
|
||||
const container = document.getElementById('mapContainer');
|
||||
if(!container) return;
|
||||
const width = container.clientWidth;
|
||||
const height = container.clientHeight || (isMobileLayout() ? 420 : 560);
|
||||
if(globe){
|
||||
globe.width(width).height(height);
|
||||
if(forceGlobeReflow && !isFlat){
|
||||
const globeEl = document.getElementById('globeViz');
|
||||
globeEl.style.display = 'none';
|
||||
requestAnimationFrame(() => {
|
||||
globeEl.style.display = 'block';
|
||||
globe.width(width).height(height);
|
||||
});
|
||||
}
|
||||
}
|
||||
if(flatSvg){
|
||||
flatW = width;
|
||||
flatH = height;
|
||||
flatSvg.attr('viewBox',`0 0 ${flatW} ${flatH}`).attr('preserveAspectRatio','xMidYMid meet');
|
||||
if(flatProjection && flatG){
|
||||
flatProjection = d3.geoNaturalEarth1().fitSize([flatW-20,flatH-20],{type:'Sphere'}).translate([flatW/2,flatH/2]);
|
||||
flatPath = d3.geoPath(flatProjection);
|
||||
flatG.selectAll('*').remove();
|
||||
drawFlatMap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let lastResponsiveMobile = null;
|
||||
function syncResponsiveLayout(force=false){
|
||||
const mobileNow = isMobileLayout();
|
||||
if(force || lastResponsiveMobile === null || mobileNow !== lastResponsiveMobile){
|
||||
lastResponsiveMobile = mobileNow;
|
||||
renderLeftRail();
|
||||
renderLower();
|
||||
renderRight();
|
||||
}
|
||||
refreshMapViewport(force && !isFlat);
|
||||
}
|
||||
|
||||
// === REINIT (for live updates without boot sequence) ===
|
||||
function reinit(){
|
||||
renderTopbar();renderLeftRail();renderLower();renderRight();
|
||||
@@ -1288,6 +1365,7 @@ function init(){
|
||||
if(url) window.open(url,'_blank','noopener');
|
||||
}
|
||||
});
|
||||
syncResponsiveLayout(true);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
Reference in New Issue
Block a user