Merge pull request 'feat(ui): finalize app-style dashboard motion and QA' (#43) from codex/modrinth-app-finalization into codex/production-intelligence-terminal
All checks were successful
Release Dry Run / release-dry-run (push) Successful in 14s
Codex Template Compliance / template-compliance (push) Successful in 6s
Build / test-and-image (push) Successful in 26s

Reviewed-on: #43
This commit was merged in pull request #43.
This commit is contained in:
2026-05-17 20:08:03 +00:00
5 changed files with 192 additions and 11 deletions

View File

@@ -728,6 +728,7 @@ The `docs/` folder contains dashboard screenshots referenced by this README. The
| File | Description | | File | Description |
|------|-------------| |------|-------------|
| `docs/dashboard.png` | Full operator dashboard - hero image at the top of this README | | `docs/dashboard.png` | Full operator 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` | Boot sequence animation | | `docs/boot.png` | Boot sequence animation |
| `docs/map.png` | Worldview 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 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -498,6 +498,100 @@ body[data-view="markets"] .lp-macro,body[data-view="markets"] .lp-ideas{max-widt
.map-container{min-height:420px} .map-container{min-height:420px}
.map-legend{left:8px;right:8px;bottom:8px} .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 data-theme="dark" data-view="home">
@@ -510,14 +604,15 @@ body[data-view="markets"] .lp-macro,body[data-view="markets"] .lp-ideas{max-widt
<div class="bg-grid" id="bgGrid"></div> <div class="bg-grid" id="bgGrid"></div>
<div id="main" class="app-root"> <div id="main" class="app-root">
<aside class="app-sidebar" aria-label="Primary views"> <aside class="app-sidebar" aria-label="Primary views">
<div class="app-brand-mark"><span>IT</span></div> <div class="rail-live"><span class="sidebar-status-dot"></span><span>Live</span></div>
<nav class="app-nav" id="appNav"> <nav class="app-nav" id="appNav">
<button class="nav-item active" data-view-target="home" onclick="setAppView('home')" title="Home"><span>H</span><small>Home</small></button> <div class="nav-glider" id="navGlider" aria-hidden="true"></div>
<button class="nav-item" data-view-target="worldview" onclick="setAppView('worldview')" title="Worldview"><span>W</span><small>World</small></button> <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="sources" onclick="setAppView('sources')" title="Sources"><span>S</span><small>Sources</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="signals" onclick="setAppView('signals')" title="Signals"><span>I</span><small>Signals</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="markets" onclick="setAppView('markets')" title="Markets"><span>M</span><small>Markets</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="ops" onclick="setAppView('ops')" title="Ops"><span>O</span><small>Ops</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> </nav>
<div class="sidebar-status"> <div class="sidebar-status">
<span class="sidebar-status-dot"></span> <span class="sidebar-status-dot"></span>
@@ -527,6 +622,7 @@ body[data-view="markets"] .lp-macro,body[data-view="markets"] .lp-ideas{max-widt
<main class="app-shell"> <main class="app-shell">
<div class="topbar" id="topbar"></div> <div class="topbar" id="topbar"></div>
<div class="grid"> <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>
@@ -641,10 +737,80 @@ function applyTheme(pref = themePreference){
function setTheme(pref){ function setTheme(pref){
themePreference = pref; themePreference = pref;
localStorage.setItem('intelligence_terminal_theme', pref); localStorage.setItem('intelligence_terminal_theme', pref);
document.body.classList.add('theme-transitioning');
window.setTimeout(() => document.body.classList.remove('theme-transitioning'), 480);
applyTheme(pref); applyTheme(pref);
renderTopbar(); 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(){ function renderAppNav(){
document.body.dataset.view = currentView; document.body.dataset.view = currentView;
document.querySelectorAll('.nav-item[data-view-target]').forEach(btn => { document.querySelectorAll('.nav-item[data-view-target]').forEach(btn => {
@@ -652,14 +818,25 @@ function renderAppNav(){
}); });
const status = document.getElementById('sidebarStatus'); const status = document.getElementById('sidebarStatus');
if(status) status.textContent = currentView === 'home' ? 'Live' : appViews[currentView].title; if(status) status.textContent = currentView === 'home' ? 'Live' : appViews[currentView].title;
requestAnimationFrame(positionNavGlider);
} }
function setAppView(view){ function setAppView(view){
if(!appViews[view]) return; if(!appViews[view]) return;
const changed = currentView !== view;
currentView = view; currentView = view;
localStorage.setItem('intelligence_terminal_view', 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(); renderAppNav();
renderTopbar(); renderTopbar();
renderTopMetrics();
renderLeftRail();
renderLower();
renderRight();
playPanelEntrances();
refreshMapViewport(true); refreshMapViewport(true);
} }
@@ -868,8 +1045,8 @@ function renderTopbar(){
const deltaLabel = direction === 'risk-off' ? '&#x25B2; '+t('dashboard.riskOff','RISK-OFF') : direction === 'risk-on' ? '&#x25BC; '+t('dashboard.riskOn','RISK-ON') : '&#x25C6; '+t('dashboard.mixed','MIXED'); const deltaLabel = direction === 'risk-off' ? '&#x25B2; '+t('dashboard.riskOff','RISK-OFF') : direction === 'risk-on' ? '&#x25BC; '+t('dashboard.riskOn','RISK-ON') : '&#x25C6; '+t('dashboard.mixed','MIXED');
document.getElementById('topbar').innerHTML=` document.getElementById('topbar').innerHTML=`
<div class="top-left"> <div class="top-left">
<span class="brand">${view.title}</span> <span class="brand">Intelligence Terminal</span>
<span class="view-subtitle">${view.subtitle}</span> <span class="view-subtitle">${view.title} / ${view.subtitle}</span>
<span class="regime-chip"><span class="blink"></span>Operator dashboard</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> <div class="app-search"><span>Search sources, signals, regions, markets...</span></div>
@@ -2132,16 +2309,19 @@ 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();renderLeftRail();renderLower();renderRight(); renderTopbar();renderTopMetrics();renderLeftRail();renderLower();renderRight();playPanelEntrances();
plotMarkers(); plotMarkers();
} }
@@ -2182,7 +2362,7 @@ let booted = false;
function init(){ function init(){
applyTheme(themePreference); applyTheme(themePreference);
renderAppNav(); renderAppNav();
renderTopbar();renderLeftRail();renderLower();renderRight(); 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: 66 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB