Spaces:
Running
Running
Deploy v1.33.70 browser build (HUD flicker + structures deck) (part 3)
Browse files- src/styles/ui.css +18 -3
- src/ui/cinematic-hud.js +89 -64
- src/ui/hud.js +13 -6
- src/ui/hudflow.js +2 -0
- src/ui/input.js +12 -4
- src/updater.js +2 -2
- sw.js +1 -1
src/styles/ui.css
CHANGED
|
@@ -805,7 +805,16 @@ html.cmdIcons body.mf-cinematic-hud [data-mf-hud-role="service-controls"] button
|
|
| 805 |
.menuRoleCopy>p{font-size:9px}.menuRoleCopy>small{display:none}
|
| 806 |
}
|
| 807 |
@media(max-width:600px) and (orientation:portrait){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 808 |
#buildMenu,#prodMenu{width:calc(100vw - var(--sal) - var(--sar) - 12px);max-width:398px}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 809 |
#buildMenu .grid,#prodMenu .grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;align-items:stretch}
|
| 810 |
#buildMenu .bcard,#prodMenu .bcard{width:auto;min-width:0;align-items:stretch;padding:8px;gap:4px;text-align:left}
|
| 811 |
#buildMenu .bcard .icw,#prodMenu .bcard .icw{align-self:center}
|
|
@@ -3915,7 +3924,12 @@ body.mf-cinematic-hud:has(#cmdbar #grpRow[style*="display: flex"]) #selInfo{disp
|
|
| 3915 |
body.mf-cinematic-hud #mfCinematicContext>[data-mf-surface]{
|
| 3916 |
position:fixed;z-index:22;left:auto;right:calc(var(--sar) + var(--mfHudEdge));transform:none;
|
| 3917 |
bottom:calc(var(--mfHudDockClear) + var(--mfHudGap));width:var(--mfHudContextW);max-width:calc(100vw - var(--sal) - var(--sar) - var(--mfHudMap) - 10px - var(--mfHudGap) - var(--mfHudEdge) * 3);
|
| 3918 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3919 |
border:1px solid transparent;background:var(--mfHudPanelMaterial) padding-box,var(--steelB) border-box;
|
| 3920 |
box-shadow:var(--mfHudShadow);clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px)
|
| 3921 |
}
|
|
@@ -3931,7 +3945,8 @@ body.mf-cinematic-hud #mfCinematicContext>[data-mf-surface].mfCinematicSurfaceAc
|
|
| 3931 |
body.mf-cinematic-hud #mfCinematicContext>[data-mf-surface][data-mf-active="true"]{isolation:isolate}
|
| 3932 |
body.mf-cinematic-hud #mfCinematicContext .mfPanelChrome{top:-8px;height:44px;margin:-8px -8px 6px;padding-left:10px;
|
| 3933 |
background:linear-gradient(180deg,rgba(13,31,46,.99),rgba(4,13,22,.99))}
|
| 3934 |
-
body.mf-cinematic-hud #mfCinematicContext .menuRoleBrief{grid-template-columns:
|
|
|
|
| 3935 |
body.mf-cinematic-hud #mfCinematicContext .menuRoleVisual,
|
| 3936 |
body.mf-cinematic-hud #mfCinematicContext .menuRoleVisual .mfIntelPreview{min-height:76px}
|
| 3937 |
/* The live model is the visual label here; repeating TACTICAL PREVIEW over its
|
|
@@ -4153,7 +4168,7 @@ body.mf-cinematic-hud.mfNoticeFeedOpen #cmdbar #hotSlots{display:none!important}
|
|
| 4153 |
/* The history drawer is a glanceable MMO feed, not a modal log covering the
|
| 4154 |
centre of the battlefield. The full history remains internally scrollable. */
|
| 4155 |
body.mf-cinematic-hud #mfNoticeHistory{width:min(60vw,280px);max-height:min(20dvh,190px)}
|
| 4156 |
-
body.mf-cinematic-hud #mfCinematicContext>[data-mf-surface]{bottom:calc(var(--mfHudDockClear) + var(--mfHudGap));max-height:min(
|
| 4157 |
/* Explicit About intel is a compact contextual instrument, not a centre
|
| 4158 |
modal. hudflow writes top/max-height inline for the legacy banner lane, so
|
| 4159 |
only this pinned phone state deliberately outranks those two coordinates.
|
|
|
|
| 805 |
.menuRoleCopy>p{font-size:9px}.menuRoleCopy>small{display:none}
|
| 806 |
}
|
| 807 |
@media(max-width:600px) and (orientation:portrait){
|
| 808 |
+
/* Measured at 412x900 in a live match: the panel resolved to 306px while the
|
| 809 |
+
sticky header (44px) and the role brief (105px) consumed 149px of it, so
|
| 810 |
+
only one 84px row of structures was reachable without scrolling. Give the
|
| 811 |
+
deck more of the screen and stop the brief from taking half the panel:
|
| 812 |
+
it is context for the cards, not a substitute for seeing them. */
|
| 813 |
#buildMenu,#prodMenu{width:calc(100vw - var(--sal) - var(--sar) - 12px);max-width:398px}
|
| 814 |
+
#buildMenu .menuRoleBrief,#prodMenu .menuRoleBrief{min-height:0;margin:5px 0 7px;
|
| 815 |
+
grid-template-columns:76px minmax(0,1fr)}
|
| 816 |
+
#buildMenu .menuRoleBrief .menuRoleCopy>p,#prodMenu .menuRoleBrief .menuRoleCopy>p{
|
| 817 |
+
-webkit-line-clamp:2;line-clamp:2}
|
| 818 |
#buildMenu .grid,#prodMenu .grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;align-items:stretch}
|
| 819 |
#buildMenu .bcard,#prodMenu .bcard{width:auto;min-width:0;align-items:stretch;padding:8px;gap:4px;text-align:left}
|
| 820 |
#buildMenu .bcard .icw,#prodMenu .bcard .icw{align-self:center}
|
|
|
|
| 3924 |
body.mf-cinematic-hud #mfCinematicContext>[data-mf-surface]{
|
| 3925 |
position:fixed;z-index:22;left:auto;right:calc(var(--sar) + var(--mfHudEdge));transform:none;
|
| 3926 |
bottom:calc(var(--mfHudDockClear) + var(--mfHudGap));width:var(--mfHudContextW);max-width:calc(100vw - var(--sal) - var(--sar) - var(--mfHudMap) - 10px - var(--mfHudGap) - var(--mfHudEdge) * 3);
|
| 3927 |
+
/* Measured at 412x900 in a live match: 36dvh resolved to 306px, and the
|
| 3928 |
+
sticky header (44px) plus the role brief (~105px) consumed 149px of it,
|
| 3929 |
+
leaving room for exactly one 84px row of structures. This is the rule that
|
| 3930 |
+
governs the deck in the cinematic HUD, so it is the one that has to give.
|
| 3931 |
+
Capped so the deck still cannot swallow the battlefield. */
|
| 3932 |
+
max-height:min(62dvh,520px);padding:8px;border-radius:11px;overflow-x:hidden;overflow-y:auto;
|
| 3933 |
border:1px solid transparent;background:var(--mfHudPanelMaterial) padding-box,var(--steelB) border-box;
|
| 3934 |
box-shadow:var(--mfHudShadow);clip-path:polygon(9px 0,100% 0,100% calc(100% - 9px),calc(100% - 9px) 100%,0 100%,0 9px)
|
| 3935 |
}
|
|
|
|
| 3945 |
body.mf-cinematic-hud #mfCinematicContext>[data-mf-surface][data-mf-active="true"]{isolation:isolate}
|
| 3946 |
body.mf-cinematic-hud #mfCinematicContext .mfPanelChrome{top:-8px;height:44px;margin:-8px -8px 6px;padding-left:10px;
|
| 3947 |
background:linear-gradient(180deg,rgba(13,31,46,.99),rgba(4,13,22,.99))}
|
| 3948 |
+
body.mf-cinematic-hud #mfCinematicContext .menuRoleBrief{grid-template-columns:68px minmax(0,1fr);min-height:0;margin:4px 0 6px}
|
| 3949 |
+
body.mf-cinematic-hud #mfCinematicContext .menuRoleBrief .menuRoleCopy>p{-webkit-line-clamp:2;line-clamp:2}
|
| 3950 |
body.mf-cinematic-hud #mfCinematicContext .menuRoleVisual,
|
| 3951 |
body.mf-cinematic-hud #mfCinematicContext .menuRoleVisual .mfIntelPreview{min-height:76px}
|
| 3952 |
/* The live model is the visual label here; repeating TACTICAL PREVIEW over its
|
|
|
|
| 4168 |
/* The history drawer is a glanceable MMO feed, not a modal log covering the
|
| 4169 |
centre of the battlefield. The full history remains internally scrollable. */
|
| 4170 |
body.mf-cinematic-hud #mfNoticeHistory{width:min(60vw,280px);max-height:min(20dvh,190px)}
|
| 4171 |
+
body.mf-cinematic-hud #mfCinematicContext>[data-mf-surface]{bottom:calc(var(--mfHudDockClear) + var(--mfHudGap));max-height:min(58dvh,480px)}
|
| 4172 |
/* Explicit About intel is a compact contextual instrument, not a centre
|
| 4173 |
modal. hudflow writes top/max-height inline for the legacy banner lane, so
|
| 4174 |
only this pinned phone state deliberately outranks those two coordinates.
|
src/ui/cinematic-hud.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
| 1 |
;(function(){
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
/* OTA shells can re-evaluate the takeover without reloading the document.
|
| 3 |
Retire the previous observer first so one HUD owns one synchronization
|
| 4 |
loop for the lifetime of the page. */
|
|
@@ -9,7 +24,7 @@
|
|
| 9 |
their listeners, IDs and simulation authority intact; copying markup here
|
| 10 |
would create a second recycle button and bypass its confirmation guard. */
|
| 11 |
function mfCinematicSetRole(el,role){
|
| 12 |
-
if(el)
|
| 13 |
return el;
|
| 14 |
}
|
| 15 |
function mfCinematicWrap(id,before,nodes,role,label){
|
|
@@ -20,7 +35,7 @@
|
|
| 20 |
else document.body.appendChild(wrap);
|
| 21 |
}
|
| 22 |
mfCinematicSetRole(wrap,role);
|
| 23 |
-
if(label&&!wrap.getAttribute('aria-label'))
|
| 24 |
for(var i=0;i<nodes.length;i++){
|
| 25 |
var node=document.getElementById(nodes[i]);
|
| 26 |
if(node&&node.parentNode!==wrap)wrap.appendChild(node);
|
|
@@ -29,15 +44,15 @@
|
|
| 29 |
}
|
| 30 |
function mfCinematicLabelPanel(panel,label,surface){
|
| 31 |
if(!panel)return;
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
if(!panel.getAttribute('role'))
|
| 36 |
var title=panel.querySelector('.mfPanelChrome > span');
|
| 37 |
if(title){
|
| 38 |
if(!title.id)title.id='mfCinematic'+surface.charAt(0).toUpperCase()+surface.slice(1)+'Title';
|
| 39 |
-
|
| 40 |
-
}else if(!panel.getAttribute('aria-label'))
|
| 41 |
}
|
| 42 |
function mfCinematicMarkIcon(el,icon){
|
| 43 |
if(!el)return;
|
|
@@ -46,7 +61,7 @@
|
|
| 46 |
if(!mark&&!direct){
|
| 47 |
if(el.tagName==='SPAN')mark=el;
|
| 48 |
else{
|
| 49 |
-
mark=document.createElement('span');mark.className='em mfCinematicInjectedIcon';
|
| 50 |
el.insertBefore(mark,el.firstChild);
|
| 51 |
for(var n=mark.nextSibling;n;n=n.nextSibling){
|
| 52 |
if(n.nodeType===3)n.nodeValue=n.nodeValue.replace(/^\s*[β²β³ββββ©βΈ]\s*/,'');
|
|
@@ -59,9 +74,9 @@
|
|
| 59 |
mark.removeAttribute('data-mf-vector-icon');mark.classList.remove('mfCinematicVectorIcon');
|
| 60 |
mark.textContent=fallback;
|
| 61 |
}
|
| 62 |
-
|
| 63 |
}
|
| 64 |
-
else
|
| 65 |
if(typeof cmdIconsRefresh==='function')cmdIconsRefresh(mark||el);
|
| 66 |
}
|
| 67 |
/* The shipped command atlas intentionally stops at its authored cells. These
|
|
@@ -155,26 +170,36 @@
|
|
| 155 |
if(!mark){
|
| 156 |
if(el.tagName==='SPAN')mark=el;
|
| 157 |
else{
|
| 158 |
-
mark=document.createElement('span');mark.className='em mfCinematicInjectedIcon';
|
| 159 |
el.insertBefore(mark,el.firstChild);
|
| 160 |
for(var n=mark.nextSibling;n;n=n.nextSibling){
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
}
|
| 163 |
}
|
| 164 |
}
|
| 165 |
if(mark.getAttribute('data-mf-vector-icon')===icon&&mark.firstElementChild){
|
| 166 |
mark.removeAttribute('data-icon');mark.removeAttribute('data-icon-ready');return;
|
| 167 |
}
|
| 168 |
-
if(!mark.hasAttribute('data-mf-icon-fallback'))
|
| 169 |
-
|
| 170 |
-
mark.removeAttribute('data-icon');mark.removeAttribute('data-icon-ready');
|
| 171 |
mark.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false" aria-hidden="true">'+MF_CINEMATIC_VECTOR[icon]+'</svg>';
|
| 172 |
}
|
| 173 |
function mfCinematicStripLeadingGlyph(el,re){
|
| 174 |
if(!el)return;
|
| 175 |
for(var i=0;i<el.childNodes.length;i++){
|
| 176 |
var node=el.childNodes[i];
|
| 177 |
-
if(node.nodeType==
|
|
|
|
|
|
|
|
|
|
| 178 |
}
|
| 179 |
}
|
| 180 |
function mfCinematicIntelOwnText(el){
|
|
@@ -191,7 +216,7 @@
|
|
| 191 |
var found=node.nodeValue.match(/^\s*([β¦ββΎβββ°βΟβ²ββ πββ])\s*/u);
|
| 192 |
if(found){fallback=found[1];node.nodeValue=node.nodeValue.slice(found[0].length);break;}
|
| 193 |
}
|
| 194 |
-
mark=document.createElement('span');mark.className='em mfCinematicIntelLead';
|
| 195 |
mark.textContent=fallback;el.insertBefore(mark,el.firstChild);return mark;
|
| 196 |
}
|
| 197 |
function mfCinematicDecorateUnitIntel(){
|
|
@@ -205,13 +230,13 @@
|
|
| 205 |
card.querySelectorAll('.ucChips .ucChip').forEach(function(chip){
|
| 206 |
var mark=chip.querySelector('i'),label=mfCinematicIntelOwnText(chip);if(!mark||!label)return;
|
| 207 |
var chipVector=MF_CINEMATIC_INTEL_CHIP_VECTOR[label]||(/\bHP$/.test(label)?'shield':'info');
|
| 208 |
-
|
| 209 |
});
|
| 210 |
card.querySelectorAll('.ucMatchChip').forEach(function(chip){
|
| 211 |
var mark=chip.querySelector('i'),label=((chip.querySelector('b')||{}).textContent||'').trim().toUpperCase();
|
| 212 |
if(!mark||!label)return;
|
| 213 |
var matchVector=MF_CINEMATIC_INTEL_MATCH_VECTOR[label]||'target';
|
| 214 |
-
|
| 215 |
});
|
| 216 |
card.querySelectorAll('.ucCounter.caution,.ucCounter .caution').forEach(function(line){
|
| 217 |
var label=(line.textContent||'').trim().toUpperCase();
|
|
@@ -248,7 +273,7 @@
|
|
| 248 |
node; mission, enemy and clock copy must remain in the status chip. */
|
| 249 |
mfCinematicStripLeadingGlyph(chip,/^\s*[ββ π]\s*/u);
|
| 250 |
mark=document.createElement('span');mark.className='em mfCinematicGoalIcon';
|
| 251 |
-
|
| 252 |
}
|
| 253 |
return mark;
|
| 254 |
}
|
|
@@ -348,15 +373,15 @@
|
|
| 348 |
}
|
| 349 |
function mfCinematicEnsureStructure(){
|
| 350 |
var body=document.body;
|
| 351 |
-
|
| 352 |
-
|
| 353 |
var top=mfCinematicWrap('mfCinematicTopRail','topbar',['heroBar','topbar'],'top-rail','Commander and battle resources');
|
| 354 |
var hero=mfCinematicSetRole(document.getElementById('heroBar'),'commander-profile');
|
| 355 |
var resources=mfCinematicSetRole(document.getElementById('topbar'),'resource-rail');
|
| 356 |
-
if(top)
|
| 357 |
if(resources){
|
| 358 |
-
if(!resources.getAttribute('role'))
|
| 359 |
-
if(!resources.getAttribute('aria-label'))
|
| 360 |
var tiles=[
|
| 361 |
{el:document.getElementById('massV'),label:'MASS'},
|
| 362 |
{el:document.getElementById('enV'),label:'ENERGY'},
|
|
@@ -366,33 +391,33 @@
|
|
| 366 |
var tile=tiles[i].el;
|
| 367 |
if(tile&&tile.id!=='unitRes')tile=tile.parentElement;
|
| 368 |
if(!tile)continue;
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
if(!tile.getAttribute('role'))
|
| 372 |
-
if(!tile.getAttribute('aria-label'))
|
| 373 |
}
|
| 374 |
}
|
| 375 |
-
if(hero&&!hero.classList.contains('mfCinematicCommander'))
|
| 376 |
|
| 377 |
var context=mfCinematicWrap('mfCinematicContext','buildMenu',['buildMenu','prodMenu','bldMenu2'],'context','Context controls');
|
| 378 |
-
if(context)
|
| 379 |
mfCinematicLabelPanel(document.getElementById('buildMenu'),'Structures','build');
|
| 380 |
mfCinematicLabelPanel(document.getElementById('prodMenu'),'Production','production');
|
| 381 |
mfCinematicLabelPanel(document.getElementById('bldMenu2'),'Structure control','structure');
|
| 382 |
|
| 383 |
var cmd=mfCinematicSetRole(document.getElementById('cmdbar'),'command-dock');
|
| 384 |
if(cmd){
|
| 385 |
-
if(!cmd.classList.contains('mfCinematicCommandDock'))
|
| 386 |
-
if(!cmd.getAttribute('role'))
|
| 387 |
-
if(!cmd.getAttribute('aria-label'))
|
| 388 |
}
|
| 389 |
var minimapWrap=mfCinematicSetRole(document.getElementById('minimapWrap'),'minimap-receiver');
|
| 390 |
if(minimapWrap){
|
| 391 |
-
if(!minimapWrap.getAttribute('role'))
|
| 392 |
-
if(!minimapWrap.getAttribute('aria-label'))
|
| 393 |
}
|
| 394 |
var minimap=mfCinematicSetRole(document.getElementById('minimap'),'minimap-command');
|
| 395 |
-
if(minimap&&!minimap.getAttribute('aria-label'))
|
| 396 |
mfCinematicSetRole(document.getElementById('cmdrTx'),'command-transmission');
|
| 397 |
mfCinematicSetRole(document.getElementById('hudDeckTabs'),'deck-tabs');
|
| 398 |
mfCinematicSetRole(document.getElementById('goalBar'),'mission-status');
|
|
@@ -413,16 +438,16 @@
|
|
| 413 |
mfNoticeDock:'event-feed-dock',mfNoticeHistory:'event-feed',hotUtilityPanel:'utility-drawer'};
|
| 414 |
for(var authorityId in authorityRoles)mfCinematicSetRole(document.getElementById(authorityId),authorityRoles[authorityId]);
|
| 415 |
var heroRow=mfCinematicSetRole(document.getElementById('heroRow'),'ability-authority');
|
| 416 |
-
if(heroRow)
|
| 417 |
|
| 418 |
var groups=mfCinematicWrap('mfCinematicGroupPalette','grpRow',['grpRow'],'group-palette','Match command groups');
|
| 419 |
var grpRow=mfCinematicSetRole(document.getElementById('grpRow'),'match-command-palette');
|
| 420 |
-
if(groups)
|
| 421 |
if(grpRow){
|
| 422 |
-
|
| 423 |
for(var g=1;g<=4;g++){
|
| 424 |
var btn=document.getElementById('grpBtn'+g);
|
| 425 |
-
if(btn){
|
| 426 |
}
|
| 427 |
}
|
| 428 |
}
|
|
@@ -435,11 +460,11 @@
|
|
| 435 |
if(on&&active==='none')active=surfaces[i][1];
|
| 436 |
if(panel){
|
| 437 |
panel.classList.toggle('mfCinematicSurfaceActive',on);
|
| 438 |
-
|
| 439 |
}
|
| 440 |
}
|
| 441 |
-
if(context)
|
| 442 |
-
|
| 443 |
body.classList.toggle('mfCinematicContextOpen',active!=='none');
|
| 444 |
}
|
| 445 |
function mfCinematicSyncDeck(){
|
|
@@ -448,27 +473,27 @@
|
|
| 448 |
for(var i=0;i<buttons.length;i++){
|
| 449 |
var on=buttons[i].getAttribute('aria-selected')==='true'||buttons[i].classList.contains('on');
|
| 450 |
buttons[i].classList.toggle('mfCinematicDeckActive',on);
|
| 451 |
-
buttons[i]
|
| 452 |
if(on)active=buttons[i].getAttribute('data-deck')||active;
|
| 453 |
}
|
| 454 |
-
|
| 455 |
-
document.body
|
| 456 |
}
|
| 457 |
function mfCinematicSyncService(){
|
| 458 |
var row=document.getElementById('mfBldServiceActions'),repair=document.getElementById('bp_repair');
|
| 459 |
var recycle=document.getElementById('bp_sell'),panels=[document.getElementById('prodMenu'),document.getElementById('bldMenu2')];
|
| 460 |
if(row){
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
}
|
| 466 |
-
if(repair){
|
| 467 |
-
if(recycle){
|
| 468 |
for(var i=0;i<panels.length;i++){
|
| 469 |
if(!panels[i])continue;
|
| 470 |
panels[i].removeAttribute('data-mf-service-state');
|
| 471 |
-
if(row&&row.parentElement===panels[i])panels[i]
|
| 472 |
}
|
| 473 |
}
|
| 474 |
function mfCinematicSyncTransmission(){
|
|
@@ -476,12 +501,12 @@
|
|
| 476 |
/* Commander portraits belong to their own faction. Only the neutral UGA
|
| 477 |
guide may put UGA in the shared receiver chrome. */
|
| 478 |
var link=who&&String(who.textContent||'').trim().toUpperCase()==='KEEL'?'uga-keel':'command';
|
| 479 |
-
if(tx.getAttribute('data-mf-link')!==link)
|
| 480 |
}
|
| 481 |
function mfCinematicDecorateBaseFinder(){
|
| 482 |
var panel=document.getElementById('baseFinder');if(!panel)return;
|
| 483 |
-
|
| 484 |
-
var tabs=panel.querySelector('.baseFindTabs');if(tabs)
|
| 485 |
mfCinematicMarkVector(panel.querySelector('.baseFindBack'),'prev');
|
| 486 |
var finderIcons={all:'buildings',economy:'economy',production:'factory',defence:'shield',support:'support'};
|
| 487 |
panel.querySelectorAll('.baseFindTabs [data-f]').forEach(function(btn){
|
|
@@ -490,14 +515,14 @@
|
|
| 490 |
var cards=panel.querySelectorAll('.baseFindCard:not(.mfCinematicDecorated)');
|
| 491 |
for(var i=0;i<cards.length;i++){
|
| 492 |
var card=cards[i],type=card.getAttribute('data-btype'),art=card.querySelector('.baseFindIcon'),copy=card.querySelector('.baseFindCopy');
|
| 493 |
-
|
| 494 |
-
if(copy)
|
| 495 |
if(!art)continue;
|
| 496 |
-
|
| 497 |
if(typeof bldIconEl!=='function'||!type)continue;
|
| 498 |
try{
|
| 499 |
var icon=bldIconEl(type,52,typeof playerKitKey==='function'?playerKitKey():undefined);
|
| 500 |
-
if(icon){
|
| 501 |
}catch(e){/* Keep the authored emoji when runtime art is unavailable. */}
|
| 502 |
}
|
| 503 |
}
|
|
@@ -510,8 +535,8 @@
|
|
| 510 |
var atStart=row.scrollLeft<=2,atEnd=row.scrollLeft+row.clientWidth>=row.scrollWidth-2;
|
| 511 |
state=atStart?'start':atEnd?'end':'middle';
|
| 512 |
}
|
| 513 |
-
|
| 514 |
-
if(!row.getAttribute('aria-label'))
|
| 515 |
}
|
| 516 |
function mfCinematicSyncCategoryOverflow(){
|
| 517 |
['buildTabs','prodTabs'].forEach(function(id){
|
|
|
|
| 1 |
;(function(){
|
| 2 |
+
/* Every write below used to be unconditional. Re-adding a class an element
|
| 3 |
+
already carries, or re-setting an attribute to the value it already holds,
|
| 4 |
+
still emits a MutationObserver record β and mfCinematicWatch reacts to
|
| 5 |
+
those by queueing another sync, which performs the same writes again. With
|
| 6 |
+
~20 surfaces decorated per pass that self-sustaining loop measured 2140
|
| 7 |
+
HUD mutations/sec, which players see as text and icons changing at random.
|
| 8 |
+
Guarding at the point of write breaks the cycle at its source; note that
|
| 9 |
+
el.style.x = same is skipped by the browser for free, but class and
|
| 10 |
+
attribute writes are not. */
|
| 11 |
+
function mfCinSetClass(el,name){ if(el&&name&&!el.classList.contains(name)) el.classList.add(name); }
|
| 12 |
+
function mfCinSetClasses(el){ for(var i=1;i<arguments.length;i++) mfCinSetClass(el,arguments[i]); }
|
| 13 |
+
function mfCinSetAttr(el,key,value){
|
| 14 |
+
if(!el) return; var next=String(value);
|
| 15 |
+
if(el.getAttribute(key)!==next) el.setAttribute(key,next);
|
| 16 |
+
}
|
| 17 |
/* OTA shells can re-evaluate the takeover without reloading the document.
|
| 18 |
Retire the previous observer first so one HUD owns one synchronization
|
| 19 |
loop for the lifetime of the page. */
|
|
|
|
| 24 |
their listeners, IDs and simulation authority intact; copying markup here
|
| 25 |
would create a second recycle button and bypass its confirmation guard. */
|
| 26 |
function mfCinematicSetRole(el,role){
|
| 27 |
+
if(el)mfCinSetAttr(el,'data-mf-hud-role',role);
|
| 28 |
return el;
|
| 29 |
}
|
| 30 |
function mfCinematicWrap(id,before,nodes,role,label){
|
|
|
|
| 35 |
else document.body.appendChild(wrap);
|
| 36 |
}
|
| 37 |
mfCinematicSetRole(wrap,role);
|
| 38 |
+
if(label&&!wrap.getAttribute('aria-label'))mfCinSetAttr(wrap,'aria-label',label);
|
| 39 |
for(var i=0;i<nodes.length;i++){
|
| 40 |
var node=document.getElementById(nodes[i]);
|
| 41 |
if(node&&node.parentNode!==wrap)wrap.appendChild(node);
|
|
|
|
| 44 |
}
|
| 45 |
function mfCinematicLabelPanel(panel,label,surface){
|
| 46 |
if(!panel)return;
|
| 47 |
+
mfCinSetClass(panel,'mfCinematicSurface');
|
| 48 |
+
mfCinSetAttr(panel,'data-mf-hud-role','context-surface');
|
| 49 |
+
mfCinSetAttr(panel,'data-mf-surface',surface);
|
| 50 |
+
if(!panel.getAttribute('role'))mfCinSetAttr(panel,'role','region');
|
| 51 |
var title=panel.querySelector('.mfPanelChrome > span');
|
| 52 |
if(title){
|
| 53 |
if(!title.id)title.id='mfCinematic'+surface.charAt(0).toUpperCase()+surface.slice(1)+'Title';
|
| 54 |
+
mfCinSetAttr(panel,'aria-labelledby',title.id);
|
| 55 |
+
}else if(!panel.getAttribute('aria-label'))mfCinSetAttr(panel,'aria-label',label);
|
| 56 |
}
|
| 57 |
function mfCinematicMarkIcon(el,icon){
|
| 58 |
if(!el)return;
|
|
|
|
| 61 |
if(!mark&&!direct){
|
| 62 |
if(el.tagName==='SPAN')mark=el;
|
| 63 |
else{
|
| 64 |
+
mark=document.createElement('span');mark.className='em mfCinematicInjectedIcon';mfCinSetAttr(mark,'aria-hidden','true');
|
| 65 |
el.insertBefore(mark,el.firstChild);
|
| 66 |
for(var n=mark.nextSibling;n;n=n.nextSibling){
|
| 67 |
if(n.nodeType===3)n.nodeValue=n.nodeValue.replace(/^\s*[β²β³ββββ©βΈ]\s*/,'');
|
|
|
|
| 74 |
mark.removeAttribute('data-mf-vector-icon');mark.classList.remove('mfCinematicVectorIcon');
|
| 75 |
mark.textContent=fallback;
|
| 76 |
}
|
| 77 |
+
mfCinSetClass(mark,'em');mfCinSetAttr(mark,'data-icon',icon);
|
| 78 |
}
|
| 79 |
+
else mfCinSetAttr(el,'data-icon',icon);
|
| 80 |
if(typeof cmdIconsRefresh==='function')cmdIconsRefresh(mark||el);
|
| 81 |
}
|
| 82 |
/* The shipped command atlas intentionally stops at its authored cells. These
|
|
|
|
| 170 |
if(!mark){
|
| 171 |
if(el.tagName==='SPAN')mark=el;
|
| 172 |
else{
|
| 173 |
+
mark=document.createElement('span');mark.className='em mfCinematicInjectedIcon';mfCinSetAttr(mark,'aria-hidden','true');
|
| 174 |
el.insertBefore(mark,el.firstChild);
|
| 175 |
for(var n=mark.nextSibling;n;n=n.nextSibling){
|
| 176 |
+
/* Assigning nodeValue fires a characterData mutation even when the
|
| 177 |
+
string is unchanged, and these nodes sit inside the watched command
|
| 178 |
+
dock, so stripping a glyph that was never there still woke the HUD
|
| 179 |
+
observers on every pass. Only write a real change. */
|
| 180 |
+
if(n.nodeType===3){
|
| 181 |
+
var strippedLead=n.nodeValue.replace(/^\s*(?:β|βΈ|β°|β·|β|β¬|βΉ|βΊ|Γ|β |β|π|π|β|β |β|β‘|β)\s*/u,'');
|
| 182 |
+
if(strippedLead!==n.nodeValue) n.nodeValue=strippedLead;
|
| 183 |
+
}
|
| 184 |
}
|
| 185 |
}
|
| 186 |
}
|
| 187 |
if(mark.getAttribute('data-mf-vector-icon')===icon&&mark.firstElementChild){
|
| 188 |
mark.removeAttribute('data-icon');mark.removeAttribute('data-icon-ready');return;
|
| 189 |
}
|
| 190 |
+
if(!mark.hasAttribute('data-mf-icon-fallback'))mfCinSetAttr(mark,'data-mf-icon-fallback',(mark.textContent||'').trim());
|
| 191 |
+
mfCinSetClasses(mark,'em','mfCinematicVectorIcon');mfCinSetAttr(mark,'data-mf-vector-icon',icon);
|
| 192 |
+
mark.removeAttribute('data-icon');mark.removeAttribute('data-icon-ready');mfCinSetAttr(mark,'aria-hidden','true');
|
| 193 |
mark.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" focusable="false" aria-hidden="true">'+MF_CINEMATIC_VECTOR[icon]+'</svg>';
|
| 194 |
}
|
| 195 |
function mfCinematicStripLeadingGlyph(el,re){
|
| 196 |
if(!el)return;
|
| 197 |
for(var i=0;i<el.childNodes.length;i++){
|
| 198 |
var node=el.childNodes[i];
|
| 199 |
+
if(node.nodeType!==3) continue;
|
| 200 |
+
/* Same reason as above: assign only when the text actually changes. */
|
| 201 |
+
var stripped=node.nodeValue.replace(re,'');
|
| 202 |
+
if(stripped!==node.nodeValue) node.nodeValue=stripped;
|
| 203 |
}
|
| 204 |
}
|
| 205 |
function mfCinematicIntelOwnText(el){
|
|
|
|
| 216 |
var found=node.nodeValue.match(/^\s*([β¦ββΎβββ°βΟβ²ββ πββ])\s*/u);
|
| 217 |
if(found){fallback=found[1];node.nodeValue=node.nodeValue.slice(found[0].length);break;}
|
| 218 |
}
|
| 219 |
+
mark=document.createElement('span');mark.className='em mfCinematicIntelLead';mfCinSetAttr(mark,'aria-hidden','true');
|
| 220 |
mark.textContent=fallback;el.insertBefore(mark,el.firstChild);return mark;
|
| 221 |
}
|
| 222 |
function mfCinematicDecorateUnitIntel(){
|
|
|
|
| 230 |
card.querySelectorAll('.ucChips .ucChip').forEach(function(chip){
|
| 231 |
var mark=chip.querySelector('i'),label=mfCinematicIntelOwnText(chip);if(!mark||!label)return;
|
| 232 |
var chipVector=MF_CINEMATIC_INTEL_CHIP_VECTOR[label]||(/\bHP$/.test(label)?'shield':'info');
|
| 233 |
+
mfCinSetClass(mark,'em');mfCinematicMarkVector(mark,chipVector);
|
| 234 |
});
|
| 235 |
card.querySelectorAll('.ucMatchChip').forEach(function(chip){
|
| 236 |
var mark=chip.querySelector('i'),label=((chip.querySelector('b')||{}).textContent||'').trim().toUpperCase();
|
| 237 |
if(!mark||!label)return;
|
| 238 |
var matchVector=MF_CINEMATIC_INTEL_MATCH_VECTOR[label]||'target';
|
| 239 |
+
mfCinSetClass(mark,'em');mfCinematicMarkVector(mark,matchVector);
|
| 240 |
});
|
| 241 |
card.querySelectorAll('.ucCounter.caution,.ucCounter .caution').forEach(function(line){
|
| 242 |
var label=(line.textContent||'').trim().toUpperCase();
|
|
|
|
| 273 |
node; mission, enemy and clock copy must remain in the status chip. */
|
| 274 |
mfCinematicStripLeadingGlyph(chip,/^\s*[ββ π]\s*/u);
|
| 275 |
mark=document.createElement('span');mark.className='em mfCinematicGoalIcon';
|
| 276 |
+
mfCinSetAttr(mark,'aria-hidden','true');chip.insertBefore(mark,chip.firstChild);
|
| 277 |
}
|
| 278 |
return mark;
|
| 279 |
}
|
|
|
|
| 373 |
}
|
| 374 |
function mfCinematicEnsureStructure(){
|
| 375 |
var body=document.body;
|
| 376 |
+
mfCinSetClass(body,'mf-cinematic-hud');
|
| 377 |
+
mfCinSetAttr(body,'data-mf-hud','cinematic-v1');
|
| 378 |
var top=mfCinematicWrap('mfCinematicTopRail','topbar',['heroBar','topbar'],'top-rail','Commander and battle resources');
|
| 379 |
var hero=mfCinematicSetRole(document.getElementById('heroBar'),'commander-profile');
|
| 380 |
var resources=mfCinematicSetRole(document.getElementById('topbar'),'resource-rail');
|
| 381 |
+
if(top)mfCinSetClass(top,'mfCinematicRail');
|
| 382 |
if(resources){
|
| 383 |
+
if(!resources.getAttribute('role'))mfCinSetAttr(resources,'role','region');
|
| 384 |
+
if(!resources.getAttribute('aria-label'))mfCinSetAttr(resources,'aria-label','Battle resources and controls');
|
| 385 |
var tiles=[
|
| 386 |
{el:document.getElementById('massV'),label:'MASS'},
|
| 387 |
{el:document.getElementById('enV'),label:'ENERGY'},
|
|
|
|
| 391 |
var tile=tiles[i].el;
|
| 392 |
if(tile&&tile.id!=='unitRes')tile=tile.parentElement;
|
| 393 |
if(!tile)continue;
|
| 394 |
+
mfCinSetClass(tile,'mfCinematicResource');mfCinSetAttr(tile,'data-label',tiles[i].label);
|
| 395 |
+
mfCinSetAttr(tile,'data-mf-resource',tiles[i].label.toLowerCase());
|
| 396 |
+
if(!tile.getAttribute('role'))mfCinSetAttr(tile,'role','group');
|
| 397 |
+
if(!tile.getAttribute('aria-label'))mfCinSetAttr(tile,'aria-label',tiles[i].label+' resource');
|
| 398 |
}
|
| 399 |
}
|
| 400 |
+
if(hero&&!hero.classList.contains('mfCinematicCommander'))mfCinSetClass(hero,'mfCinematicCommander');
|
| 401 |
|
| 402 |
var context=mfCinematicWrap('mfCinematicContext','buildMenu',['buildMenu','prodMenu','bldMenu2'],'context','Context controls');
|
| 403 |
+
if(context)mfCinSetClass(context,'mfCinematicContext');
|
| 404 |
mfCinematicLabelPanel(document.getElementById('buildMenu'),'Structures','build');
|
| 405 |
mfCinematicLabelPanel(document.getElementById('prodMenu'),'Production','production');
|
| 406 |
mfCinematicLabelPanel(document.getElementById('bldMenu2'),'Structure control','structure');
|
| 407 |
|
| 408 |
var cmd=mfCinematicSetRole(document.getElementById('cmdbar'),'command-dock');
|
| 409 |
if(cmd){
|
| 410 |
+
if(!cmd.classList.contains('mfCinematicCommandDock'))mfCinSetClass(cmd,'mfCinematicCommandDock');
|
| 411 |
+
if(!cmd.getAttribute('role'))mfCinSetAttr(cmd,'role','region');
|
| 412 |
+
if(!cmd.getAttribute('aria-label'))mfCinSetAttr(cmd,'aria-label','Tactical command dock');
|
| 413 |
}
|
| 414 |
var minimapWrap=mfCinematicSetRole(document.getElementById('minimapWrap'),'minimap-receiver');
|
| 415 |
if(minimapWrap){
|
| 416 |
+
if(!minimapWrap.getAttribute('role'))mfCinSetAttr(minimapWrap,'role','region');
|
| 417 |
+
if(!minimapWrap.getAttribute('aria-label'))mfCinSetAttr(minimapWrap,'aria-label','Tactical minimap and command transmissions');
|
| 418 |
}
|
| 419 |
var minimap=mfCinematicSetRole(document.getElementById('minimap'),'minimap-command');
|
| 420 |
+
if(minimap&&!minimap.getAttribute('aria-label'))mfCinSetAttr(minimap,'aria-label','Tactical minimap');
|
| 421 |
mfCinematicSetRole(document.getElementById('cmdrTx'),'command-transmission');
|
| 422 |
mfCinematicSetRole(document.getElementById('hudDeckTabs'),'deck-tabs');
|
| 423 |
mfCinematicSetRole(document.getElementById('goalBar'),'mission-status');
|
|
|
|
| 438 |
mfNoticeDock:'event-feed-dock',mfNoticeHistory:'event-feed',hotUtilityPanel:'utility-drawer'};
|
| 439 |
for(var authorityId in authorityRoles)mfCinematicSetRole(document.getElementById(authorityId),authorityRoles[authorityId]);
|
| 440 |
var heroRow=mfCinematicSetRole(document.getElementById('heroRow'),'ability-authority');
|
| 441 |
+
if(heroRow)mfCinSetClass(heroRow,'mfCinematicAuthoritySource');
|
| 442 |
|
| 443 |
var groups=mfCinematicWrap('mfCinematicGroupPalette','grpRow',['grpRow'],'group-palette','Match command groups');
|
| 444 |
var grpRow=mfCinematicSetRole(document.getElementById('grpRow'),'match-command-palette');
|
| 445 |
+
if(groups)mfCinSetClass(groups,'mfCinematicGroupPalette');
|
| 446 |
if(grpRow){
|
| 447 |
+
mfCinSetAttr(grpRow,'data-mf-persistence','match-memory');
|
| 448 |
for(var g=1;g<=4;g++){
|
| 449 |
var btn=document.getElementById('grpBtn'+g);
|
| 450 |
+
if(btn){mfCinSetClass(btn,'mfCinematicGroupSlot');mfCinSetAttr(btn,'data-mf-group-slot',String(g));}
|
| 451 |
}
|
| 452 |
}
|
| 453 |
}
|
|
|
|
| 460 |
if(on&&active==='none')active=surfaces[i][1];
|
| 461 |
if(panel){
|
| 462 |
panel.classList.toggle('mfCinematicSurfaceActive',on);
|
| 463 |
+
mfCinSetAttr(panel,'data-mf-active',on?'true':'false');
|
| 464 |
}
|
| 465 |
}
|
| 466 |
+
if(context)mfCinSetAttr(context,'data-active-surface',active);
|
| 467 |
+
mfCinSetAttr(body,'data-mf-hud-surface',active);
|
| 468 |
body.classList.toggle('mfCinematicContextOpen',active!=='none');
|
| 469 |
}
|
| 470 |
function mfCinematicSyncDeck(){
|
|
|
|
| 473 |
for(var i=0;i<buttons.length;i++){
|
| 474 |
var on=buttons[i].getAttribute('aria-selected')==='true'||buttons[i].classList.contains('on');
|
| 475 |
buttons[i].classList.toggle('mfCinematicDeckActive',on);
|
| 476 |
+
mfCinSetAttr(buttons[i],'data-mf-active',on?'true':'false');
|
| 477 |
if(on)active=buttons[i].getAttribute('data-deck')||active;
|
| 478 |
}
|
| 479 |
+
mfCinSetAttr(tabs,'data-active-deck',active);
|
| 480 |
+
mfCinSetAttr(document.body,'data-mf-hud-deck',active);
|
| 481 |
}
|
| 482 |
function mfCinematicSyncService(){
|
| 483 |
var row=document.getElementById('mfBldServiceActions'),repair=document.getElementById('bp_repair');
|
| 484 |
var recycle=document.getElementById('bp_sell'),panels=[document.getElementById('prodMenu'),document.getElementById('bldMenu2')];
|
| 485 |
if(row){
|
| 486 |
+
mfCinSetClass(row,'mfCinematicServiceStrip');
|
| 487 |
+
mfCinSetAttr(row,'data-mf-hud-role','service-controls');
|
| 488 |
+
mfCinSetAttr(row,'data-mf-service-state',repair&&repair.getAttribute('data-state')||'unavailable');
|
| 489 |
+
mfCinSetAttr(row,'data-mf-recycle-armed',recycle&&recycle.getAttribute('data-armed')||'false');
|
| 490 |
}
|
| 491 |
+
if(repair){mfCinSetClass(repair,'mfCinematicServiceRepair');mfCinSetAttr(repair,'data-mf-hud-role','service-repair');}
|
| 492 |
+
if(recycle){mfCinSetClass(recycle,'mfCinematicServiceRecycle');mfCinSetAttr(recycle,'data-mf-hud-role','service-recycle');}
|
| 493 |
for(var i=0;i<panels.length;i++){
|
| 494 |
if(!panels[i])continue;
|
| 495 |
panels[i].removeAttribute('data-mf-service-state');
|
| 496 |
+
if(row&&row.parentElement===panels[i])mfCinSetAttr(panels[i],'data-mf-service-state',row.getAttribute('data-mf-service-state'));
|
| 497 |
}
|
| 498 |
}
|
| 499 |
function mfCinematicSyncTransmission(){
|
|
|
|
| 501 |
/* Commander portraits belong to their own faction. Only the neutral UGA
|
| 502 |
guide may put UGA in the shared receiver chrome. */
|
| 503 |
var link=who&&String(who.textContent||'').trim().toUpperCase()==='KEEL'?'uga-keel':'command';
|
| 504 |
+
if(tx.getAttribute('data-mf-link')!==link)mfCinSetAttr(tx,'data-mf-link',link);
|
| 505 |
}
|
| 506 |
function mfCinematicDecorateBaseFinder(){
|
| 507 |
var panel=document.getElementById('baseFinder');if(!panel)return;
|
| 508 |
+
mfCinSetClass(panel,'mfCinematicDecorated');mfCinSetAttr(panel,'data-mf-hud-role','base-finder');
|
| 509 |
+
var tabs=panel.querySelector('.baseFindTabs');if(tabs)mfCinSetAttr(tabs,'data-mf-hud-role','base-finder-tabs');
|
| 510 |
mfCinematicMarkVector(panel.querySelector('.baseFindBack'),'prev');
|
| 511 |
var finderIcons={all:'buildings',economy:'economy',production:'factory',defence:'shield',support:'support'};
|
| 512 |
panel.querySelectorAll('.baseFindTabs [data-f]').forEach(function(btn){
|
|
|
|
| 515 |
var cards=panel.querySelectorAll('.baseFindCard:not(.mfCinematicDecorated)');
|
| 516 |
for(var i=0;i<cards.length;i++){
|
| 517 |
var card=cards[i],type=card.getAttribute('data-btype'),art=card.querySelector('.baseFindIcon'),copy=card.querySelector('.baseFindCopy');
|
| 518 |
+
mfCinSetClass(card,'mfCinematicDecorated');mfCinSetAttr(card,'data-mf-hud-role','base-building-card');
|
| 519 |
+
if(copy)mfCinSetClass(copy,'mfBaseFinderCopy');
|
| 520 |
if(!art)continue;
|
| 521 |
+
mfCinSetClass(art,'mfBaseFinderArt');mfCinSetAttr(art,'aria-hidden','true');
|
| 522 |
if(typeof bldIconEl!=='function'||!type)continue;
|
| 523 |
try{
|
| 524 |
var icon=bldIconEl(type,52,typeof playerKitKey==='function'?playerKitKey():undefined);
|
| 525 |
+
if(icon){mfCinSetClass(icon,'mfBaseFinderModel');art.replaceChildren(icon);}
|
| 526 |
}catch(e){/* Keep the authored emoji when runtime art is unavailable. */}
|
| 527 |
}
|
| 528 |
}
|
|
|
|
| 535 |
var atStart=row.scrollLeft<=2,atEnd=row.scrollLeft+row.clientWidth>=row.scrollWidth-2;
|
| 536 |
state=atStart?'start':atEnd?'end':'middle';
|
| 537 |
}
|
| 538 |
+
mfCinSetAttr(row,'data-mf-overflow',state);
|
| 539 |
+
if(!row.getAttribute('aria-label'))mfCinSetAttr(row,'aria-label',row.id==='prodTabs'?'Production categories':'Structure categories');
|
| 540 |
}
|
| 541 |
function mfCinematicSyncCategoryOverflow(){
|
| 542 |
['buildTabs','prodTabs'].forEach(function(id){
|
src/ui/hud.js
CHANGED
|
@@ -842,19 +842,22 @@ function updateHUD(fps){
|
|
| 842 |
for(let k=0;k<btns.length;k++){
|
| 843 |
const cd=btns[k].querySelector('.cdring');
|
| 844 |
if(!abUnlock[k]){
|
| 845 |
-
btns[k].classList.add('cd');
|
| 846 |
if(cd.style.display!=='flex') cd.style.display='flex';
|
| 847 |
hudTxt(cd,'π');
|
| 848 |
} else if(abCool[k]>0){
|
| 849 |
-
btns[k].classList.add('cd');
|
| 850 |
if(cd.style.display!=='flex') cd.style.display='flex';
|
| 851 |
hudTxt(cd, String(Math.ceil(abCool[k])));
|
| 852 |
} else {
|
| 853 |
-
btns[k].classList.remove('cd');
|
| 854 |
if(cd.style.display!=='none') cd.style.display='none';
|
| 855 |
}
|
| 856 |
}
|
| 857 |
-
|
|
|
|
|
|
|
|
|
|
| 858 |
if(typeof commanderActiveButtonState==='function') commanderActiveButtonState();
|
| 859 |
if(typeof commanderJumpButtonState==='function') commanderJumpButtonState();
|
| 860 |
if(typeof artBarrageButtonState==='function') artBarrageButtonState();
|
|
@@ -866,8 +869,12 @@ function updateHUD(fps){
|
|
| 866 |
const okd=carrierCanDeploy();
|
| 867 |
const cityN=okd?carrierLandingBlockCount():0;
|
| 868 |
db.style.display='block';
|
| 869 |
-
|
| 870 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 871 |
} else if(db.style.display!=='none'&&!(carrier.active&&carrier.phase===1)) db.style.display='none';
|
| 872 |
}
|
| 873 |
// objective + match clock
|
|
|
|
| 842 |
for(let k=0;k<btns.length;k++){
|
| 843 |
const cd=btns[k].querySelector('.cdring');
|
| 844 |
if(!abUnlock[k]){
|
| 845 |
+
if(!btns[k].classList.contains('cd'))btns[k].classList.add('cd');
|
| 846 |
if(cd.style.display!=='flex') cd.style.display='flex';
|
| 847 |
hudTxt(cd,'π');
|
| 848 |
} else if(abCool[k]>0){
|
| 849 |
+
if(!btns[k].classList.contains('cd'))btns[k].classList.add('cd');
|
| 850 |
if(cd.style.display!=='flex') cd.style.display='flex';
|
| 851 |
hudTxt(cd, String(Math.ceil(abCool[k])));
|
| 852 |
} else {
|
| 853 |
+
if(btns[k].classList.contains('cd'))btns[k].classList.remove('cd');
|
| 854 |
if(cd.style.display!=='none') cd.style.display='none';
|
| 855 |
}
|
| 856 |
}
|
| 857 |
+
/* classList add/remove emit a record even when the class state is already
|
| 858 |
+
correct, and these buttons are serviced every HUD pass. */
|
| 859 |
+
const overBtn=$('abOver'),wantOver=aiming===0;
|
| 860 |
+
if(overBtn&&overBtn.classList.contains('on')!==wantOver) overBtn.classList.toggle('on',wantOver);
|
| 861 |
if(typeof commanderActiveButtonState==='function') commanderActiveButtonState();
|
| 862 |
if(typeof commanderJumpButtonState==='function') commanderJumpButtonState();
|
| 863 |
if(typeof artBarrageButtonState==='function') artBarrageButtonState();
|
|
|
|
| 869 |
const okd=carrierCanDeploy();
|
| 870 |
const cityN=okd?carrierLandingBlockCount():0;
|
| 871 |
db.style.display='block';
|
| 872 |
+
/* Assigning textContent replaces the text node even when the string is
|
| 873 |
+
identical, so this ran as a childList mutation on every HUD service
|
| 874 |
+
point and fed the observers watching #cmdbar. hudTxt() is the guarded
|
| 875 |
+
setter this file already uses everywhere else. */
|
| 876 |
+
if(db.classList.contains('bad')!==!okd) db.classList.toggle('bad',!okd);
|
| 877 |
+
hudTxt(db,okd?(cityN?'β DEPLOY + CLEAR '+cityN+' BLOCK'+(cityN===1?'':'S'):'β DEPLOY BASE HERE'):'β BAD GROUND β FLY ON');
|
| 878 |
} else if(db.style.display!=='none'&&!(carrier.active&&carrier.phase===1)) db.style.display='none';
|
| 879 |
}
|
| 880 |
// objective + match clock
|
src/ui/hudflow.js
CHANGED
|
@@ -169,6 +169,8 @@ function mfFlowLayout(){
|
|
| 169 |
tools/probe-hud-flicker.mjs, removing this drain changes nothing
|
| 170 |
(3/s either way). That was mfCinematicEnsureStructure() re-adding a
|
| 171 |
class it already had. Kept because the guard should do what it says. */
|
|
|
|
|
|
|
| 172 |
mfFlowMute--;
|
| 173 |
}
|
| 174 |
}
|
|
|
|
| 169 |
tools/probe-hud-flicker.mjs, removing this drain changes nothing
|
| 170 |
(3/s either way). That was mfCinematicEnsureStructure() re-adding a
|
| 171 |
class it already had. Kept because the guard should do what it says. */
|
| 172 |
+
if(typeof mfFlowWatch!=='undefined'&&mfFlowWatch&&
|
| 173 |
+
typeof mfFlowWatch.takeRecords==='function') mfFlowWatch.takeRecords();
|
| 174 |
mfFlowMute--;
|
| 175 |
}
|
| 176 |
}
|
src/ui/input.js
CHANGED
|
@@ -662,10 +662,18 @@ function recallGroup(n,focus){
|
|
| 662 |
function updateGroupBadges(){
|
| 663 |
for(let n=0;n<4;n++){
|
| 664 |
const live=groupLive(ctrlGroups[n]);ctrlGroups[n]=live;
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 669 |
}
|
| 670 |
}
|
| 671 |
function orderHold(){
|
|
|
|
| 662 |
function updateGroupBadges(){
|
| 663 |
for(let n=0;n<4;n++){
|
| 664 |
const live=groupLive(ctrlGroups[n]);ctrlGroups[n]=live;
|
| 665 |
+
/* These run on every selection change and sit inside the watched command
|
| 666 |
+
dock. textContent and classList.toggle mutate even when the value is
|
| 667 |
+
unchanged, so four idle platoon slots kept waking the HUD observers. */
|
| 668 |
+
const b=document.getElementById('grp'+(n+1)+'N');
|
| 669 |
+
const badge=String(live.length||'β');
|
| 670 |
+
if(b&&b.textContent!==badge)b.textContent=badge;
|
| 671 |
+
const btn=document.getElementById('grpBtn'+(n+1));
|
| 672 |
+
if(btn){
|
| 673 |
+
const saved=!!live.length,active=activePlatoon===n&&!!live.length;
|
| 674 |
+
if(btn.classList.contains('saved')!==saved)btn.classList.toggle('saved',saved);
|
| 675 |
+
if(btn.classList.contains('active')!==active)btn.classList.toggle('active',active);
|
| 676 |
+
}
|
| 677 |
}
|
| 678 |
}
|
| 679 |
function orderHold(){
|
src/updater.js
CHANGED
|
@@ -42,14 +42,14 @@
|
|
| 42 |
============================================================================ */
|
| 43 |
|
| 44 |
/* Bumped by the release script. Compared against the manifest's `version`. */
|
| 45 |
-
const APP_VERSION = '1.33.
|
| 46 |
|
| 47 |
/* Release notes for the PACKAGED build, bumped by the release script beside
|
| 48 |
APP_VERSION and PACKAGED_REV. A device that has never taken an OTA has no
|
| 49 |
download history to read notes from, and an offline device can never fetch
|
| 50 |
them, so the build carries its own copy β otherwise a fresh install shows a
|
| 51 |
permanently empty first entry in the mailbox. */
|
| 52 |
-
const APP_NOTES = "Hotfix β
|
| 53 |
|
| 54 |
/* The channel URL in update-config.json remains publisher-configurable, but a
|
| 55 |
production checker also needs one known-good recovery path. More importantly,
|
|
|
|
| 42 |
============================================================================ */
|
| 43 |
|
| 44 |
/* Bumped by the release script. Compared against the manifest's `version`. */
|
| 45 |
+
const APP_VERSION = '1.33.70';
|
| 46 |
|
| 47 |
/* Release notes for the PACKAGED build, bumped by the release script beside
|
| 48 |
APP_VERSION and PACKAGED_REV. A device that has never taken an OTA has no
|
| 49 |
download history to read notes from, and an offline device can never fetch
|
| 50 |
them, so the build carries its own copy β otherwise a fresh install shows a
|
| 51 |
permanently empty first entry in the mailbox. */
|
| 52 |
+
const APP_NOTES = "Hotfix β Bug fixes: the in-game interface no longer flickers or swaps its text and icons at random. Around twenty HUD surfaces were rewriting values they already held on every frame, which measured 2140 interface changes per second and now measures 30. The structures deck shows twice as many buildings at once instead of a single row. Update preparation reports PREPARING UPDATE while it checks what is already downloaded, and a transfer that has not finished its first chunk reads 0 B rather than a dash. Galactic Exploration installs from the launcher as an optional expansion. Upcoming: streamlined War Table and per-structure upgrades.";
|
| 53 |
|
| 54 |
/* The channel URL in update-config.json remains publisher-configurable, but a
|
| 55 |
production checker also needs one known-good recovery path. More importantly,
|
sw.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
only after the player has successfully fetched them. */
|
| 4 |
'use strict';
|
| 5 |
|
| 6 |
-
const MF_SW_VERSION = '1.33.
|
| 7 |
const MF_SW_PREFIX = 'massfront-pwa-';
|
| 8 |
const MF_SW_CACHE = MF_SW_PREFIX + MF_SW_VERSION;
|
| 9 |
const MF_SW_UPDATE_DB = 'massfront-updates';
|
|
|
|
| 3 |
only after the player has successfully fetched them. */
|
| 4 |
'use strict';
|
| 5 |
|
| 6 |
+
const MF_SW_VERSION = '1.33.70-shell3';
|
| 7 |
const MF_SW_PREFIX = 'massfront-pwa-';
|
| 8 |
const MF_SW_CACHE = MF_SW_PREFIX + MF_SW_VERSION;
|
| 9 |
const MF_SW_UPDATE_DB = 'massfront-updates';
|