Spaces:
Running
Running
File size: 28,999 Bytes
84bc42a bd26019 84bc42a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 | ;
;
/* ============================================================================
FIRST-CAREER ONBOARDING — one choice, two real teaching systems
----------------------------------------------------------------------------
Training already owns the live RTS course (tutorial.js), and the War Table
Primer owns galaxy -> deployment navigation (warprimer.js). This file does
not duplicate either system or mark either one complete. It gives a fresh
career an explicit, skippable choice and exposes durable KEEL hint events so
later battle/space HUD presenters can render the same authored message.
============================================================================ */
(function(){
'use strict';
var ONBOARDING_VERSION=2;
var PRESENTER_EVENT='massfront:keel-hint';
var CHOICE_EVENT='massfront:onboarding-choice';
var presenter=null,choiceNode=null,lastFocus=null,initialized=false,activeOfferOptions=null;
/* This is a career contract, not a cut-scene asset list. The space module
stages these beats with its existing live system view and story rail, so a
missing video or portrait can never block a new career. */
var NEW_CAREER_SEQUENCE={
schema:'massfront.new-career-onboarding.v2',entryView:'live-space',required:true,
stages:[
{id:'world-introduction',surface:'space-story-rail',scene:'deep-space',
title:'THE FRACTURED FRONTIER',text:'Humanity crossed the relay network and found a frontier already at war.'},
{id:'planet-approach',surface:'space-story-rail',scene:'planet-approach',
title:'APPROACHING AELOS',text:'UGA traffic control has cleared one protected landing corridor for your first field operation.'},
{id:'orientation-choice',surface:'space-story-rail',scene:'aelos-orbit',
title:'YOUR FIRST COMMAND DECISION',text:'Take the short planetary basics course, or skip directly to required faction commissioning.'},
{id:'protected-training',optional:true,surface:'battle-minimap',next:'faction-commissioning'},
{id:'faction-commissioning',required:true,next:'starter-commander-1'},
{id:'starter-commander-1',required:true,next:'full-uga-space'},
{id:'full-uga-space',required:true}
],
invariants:{mainMenuPreserved:true,trainingOptional:true,factionRequired:true,
commanderGrant:'selected-faction-commander-1',keelAffiliation:'uga',
keelSelectable:false,battleTransmissionSurface:'battle-minimap',
spaceTransmissionSurface:'space-story-rail'}
};
/* Routing is declarative here. The Galactic integration owns the actual space
entry and career transitions; callers receive these outcomes through the
choice event/callback and route them on their own surface. */
var FLOWS={
'default':{
entryView:'front-command',trainingOutcome:'protected-planetary-training',skipOutcome:'front-command',
title:'FIELD BASICS BEFORE DEPLOYMENT?',
lead:'KEEL can guide one protected planetary drop, or you can continue to the current MASSFRONT command menu. This choice never replaces the main menu.',
trainingTitle:'10-MINUTE PLANETARY BASICS',
trainingText:'Learn camera control, deployment, Commander selection, economy, construction, production, army selection and orders in a protected mission.',
skipTitle:'STRATEGIC NAVIGATION',
skipText:'The War Table Primer teaches galaxy, system, planet, region and deployment when you enter Standard.',
trainingLabel:'BEGIN PLANETARY BASICS',skipLabel:'KEEP CURRENT MAIN MENU',
foot:'The tutorial remains replayable from the War Room, Settings, and UGA Command.'
},
'experimental-space':{
entryView:'live-space',trainingOutcome:'protected-planetary-training',skipOutcome:'required-faction-selection',
title:'CHOOSE YOUR FIRST PLANETARY DROP',
lead:'Aelos is below. KEEL can run a short protected RTS orientation, or an experienced player can continue directly to commissioning.',
trainingTitle:'BASIC ORIENTATION · RECOMMENDED',
trainingText:'Ten essential objectives: camera, landing, Commander, mass, power, Factory, production, army selection, orders and extraction.',
skipTitle:'COMMISSION IMMEDIATELY',
skipText:'Skip the mission, then choose a faction and receive that faction\'s real Commander 1. Faction selection is still required.',
trainingLabel:'LAND FOR BASIC TRAINING',skipLabel:'SKIP TRAINING · COMMISSION',
foot:'Both paths converge on required faction selection and Commander 1 before full UGA-space access.'
}
};
/* These ids match tutorial.js's new-career BASIC course. The longer field
certification remains replayable, but it is not allowed to turn a first
launch into a twenty-one-objective campaign. Progress remains a high-water
mark; this layer never awards or advances a lesson. */
var RTS_STEPS=[
['camera','Camera'],['deploy','Landing'],['commander','Commander'],['mex','Mass economy'],
['power','Power grid'],['fac','Factory'],['queue','Production'],['train','Army selection'],
['orders','Basic orders'],['objective','Extraction']
];
var NAV_STEPS=[
['galaxy','Galaxy'],['system','System'],['planet','Planet'],['region','Region'],['deploy','Deployment plan']
];
/* Contexts are stable integration points. UGA can call presentContext() from a
space surface; battle HUD code can use the battle-* contexts. Text names
controls and rules that exist today — no placeholder missions or rewards. */
var HINTS={
'menu-command':{
context:'menu',surface:'front-end',durationMs:5600,priority:30,animationId:'keel-link',
text:'Training is a protected live-fire orientation. Standard continues through the strategic deployment route.'
},
'war-table-route':{
context:'war-table',surface:'front-end',durationMs:6000,priority:35,animationId:'keel-route',
text:'Follow the active route: galaxy, system, planet, region, then deployment. Locked destinations remain on the current stage.'
},
'uga-command':{
context:'uga-command',surface:'space-story-rail',durationMs:6200,priority:45,animationId:'keel-space-link',
text:'UGA Command can route you to the protected Training Operation or a live deployment. Training progress remains with this profile.'
},
'uga-training':{
context:'uga-training',surface:'space-story-rail',durationMs:5800,priority:50,animationId:'keel-space-link',
text:'Training bypasses strategic deployment and starts a fixed protected operation under KEEL guidance.'
},
'space-first-choice':{
context:'space-intro',surface:'space-story-rail',durationMs:6000,priority:70,animationId:'keel-space-link',
text:'Live-space link stable. Deploy to protected planetary training, or continue directly to required faction commissioning.'
},
'battle-camera':{
context:'battle-camera',surface:'battle-minimap',durationMs:5200,priority:50,animationId:'keel-tactical-link',
text:'Drag to pan, pinch to zoom, and open VIEW for rotation and tilt.'
},
'battle-economy':{
context:'battle-economy',surface:'battle-minimap',durationMs:6000,priority:55,animationId:'keel-tactical-link',
text:'Build an Extractor on a mass deposit, then add Reactor power before expanding production.'
},
'battle-production':{
context:'battle-production',surface:'battle-minimap',durationMs:5900,priority:55,animationId:'keel-tactical-link',
text:'Select a Factory, queue units, then open ARMY to select the force you produced.'
},
'battle-orders':{
context:'battle-orders',surface:'battle-minimap',durationMs:6200,priority:60,animationId:'keel-tactical-link',
text:'Select combat units. Single-tap ground issues attack-move; double-tap breaks contact and retreats.'
},
'battle-intel':{
context:'battle-intel',surface:'battle-minimap',durationMs:5800,priority:60,animationId:'keel-tactical-link',
text:'Long-press suspected enemy ground to mark intel, then scout beyond friendly vision to confirm the contact.'
}
};
function stamp(){ return Date.now(); }
function save(){
if(typeof metaSave!=='function') return false;
try{ return metaSave()!==false; }catch(e){ return false; }
}
function onboardingMeta(){
if(typeof META==='undefined'||!META) return {version:ONBOARDING_VERSION,choice:'',hints:{}};
var M=META.onboarding;
if(!M||typeof M!=='object'||Array.isArray(M)) M=META.onboarding={};
if(typeof M.version!=='number') M.version=0;
if(M.choice!=='training'&&M.choice!=='skipped') M.choice='';
if(!M.hints||typeof M.hints!=='object'||Array.isArray(M.hints)) M.hints={};
return M;
}
function hintMeta(id){
var M=onboardingMeta(),H=M.hints[id];
if(!H||typeof H!=='object'||Array.isArray(H)) H=M.hints[id]={};
return H;
}
function tutorialMeta(){
return typeof META!=='undefined'&&META&&META.tutorial&&typeof META.tutorial==='object'?META.tutorial:{};
}
function primerMeta(){
return typeof META!=='undefined'&&META&&META.warPrimer&&typeof META.warPrimer==='object'?META.warPrimer:{};
}
function trainingState(){
try{ if(typeof window.trainingUiState==='function') return window.trainingUiState()||{}; }catch(e){}
return {};
}
function cloneMap(src){
if(Array.isArray(src)) return src.map(cloneMap);
if(!src||typeof src!=='object') return src;
var out={},k;
for(k in src) if(Object.prototype.hasOwnProperty.call(src,k)){
var v=src[k];
out[k]=cloneMap(v);
}
return out;
}
/* Public progress is derived from the two authoritative systems. "Complete"
therefore always means the real saved lesson/stage says so. */
function skills(){
var T=tutorialMeta(),P=primerMeta(),TS=trainingState();
var tDone=!!(T.basicDone||T.done||TS.done),active=!!TS.active,seen=P.seen||{},navDone=!!P.done;
var stored=typeof T.basicProgress==='number'?T.basicProgress:(T.done?RTS_STEPS.length:T.progress|0);
var progress=Math.max(0,Math.min(RTS_STEPS.length,TS.course==='basic'&&active?TS.progress|0:stored));
var nav=NAV_STEPS.map(function(S){
return {id:S[0],label:S[1],state:(navDone||!!seen[S[0]])?'complete':'pending'};
});
var rts=RTS_STEPS.map(function(S,i){
var state=tDone||i<progress?'complete':active&&i===progress?'current':T.skipped?'deferred':'pending';
return {id:S[0],label:S[1],state:state};
});
var navSeen=nav.filter(function(S){return S.state==='complete';}).length;
var rtsSeen=rts.filter(function(S){return S.state==='complete';}).length;
return [
{id:'strategic-navigation',label:'Strategic navigation',source:'War Table Primer',
status:navDone?'complete':navSeen?'in-progress':'pending',complete:navSeen,total:nav.length,steps:nav},
{id:'rts-foundations',label:'RTS field skills',source:'KEEL Training Operation',
status:tDone?'complete':active?'in-progress':T.skipped?'deferred':rtsSeen?'in-progress':'pending',
complete:rtsSeen,total:rts.length,steps:rts}
];
}
function eligible(){
if(typeof META==='undefined'||!META) return false;
var M=onboardingMeta(),T=tutorialMeta();
if(M.choice) return false;
if(((META.matches|0)+(META.standardMatches|0))>0) return false;
if(T.done||T.basicDone||T.skipped||T.basicSkipped||(T.progress|0)>0||(T.basicProgress|0)>0) return false;
if(trainingState().active) return false;
return true;
}
function experimentalEnabled(){
return window.__MF_BUILD_HAS_GALACTIC_EXPLORATION===true||window.__MF_OTA_HAS_GALACTIC_DELIVERY===true;
}
function automaticEligible(){
/* Experimental new careers make their choice after live space appears. A
base-menu modal would reverse that sequence and cover the world intro. */
return eligible()&&!experimentalEnabled();
}
function flowDef(id){ return FLOWS[id]||FLOWS['default']; }
function careerSequence(){ return cloneMap(NEW_CAREER_SEQUENCE); }
function phase(){
try{
var gate=window.MFNewCareerFactionGate;
var G=gate&&typeof gate.state==='function'?gate.state():null;
if(G&&G.phase==='ready') return 'ready';
if(G&&G.phase==='faction-selection') return 'faction-commissioning';
if(G&&G.phase==='training') return 'protected-training';
}catch(e){}
var M=onboardingMeta(),T=tutorialMeta(),TS=trainingState();
if(!M.choice) return experimentalEnabled()?'world-introduction':'orientation-choice';
if(M.choice==='skipped') return M.flowId==='experimental-space'?'faction-commissioning':'front-command';
if(T.basicDone||T.done||TS.done) return M.flowId==='experimental-space'?'faction-commissioning':'front-command';
return 'protected-training';
}
function choiceContract(id){
id=FLOWS[id]?id:'default';
var F=flowDef(id);
return {schema:'massfront.onboarding-flow.v2',flowId:id,entryView:F.entryView,
choiceContext:id==='experimental-space'?'space-intro':'front-command',
speaker:'KEEL',speakerId:'keel',affiliation:'uga',speakerRole:'UGA EXPEDITION GUIDE',
channel:'UGA PERSONNEL LINK',voiceId:'keen',profileId:'uga-keel-expedition-guide',
animationId:id==='experimental-space'?'keel-space-link':'keel-link',
title:F.title,text:F.lead,careerSequence:careerSequence(),
continuation:id==='experimental-space'?{
schema:'massfront.new-career-continuation.v1',required:true,
afterTraining:'faction-commissioning',afterSkip:'faction-commissioning',
afterFaction:'starter-commander-1',afterCommander:'full-uga-space'
}:null,
actions:[
{choice:'training',label:F.trainingLabel,outcome:F.trainingOutcome,nextStep:'protected-planetary-training'},
{choice:'skipped',label:F.skipLabel,outcome:F.skipOutcome,
nextStep:id==='experimental-space'?'faction-commissioning':'front-command'}
]};
}
function state(){
var M=onboardingMeta();
return {version:ONBOARDING_VERSION,choice:M.choice||'',flowId:M.flowId||'',outcome:M.outcome||'',
eligible:eligible(),automaticEligible:automaticEligible(),awaitingSpaceChoice:eligible()&&experimentalEnabled(),
phase:phase(),trainingOptional:true,factionCommissioningRequired:experimentalEnabled(),
offeredAt:M.offeredAt||0,decidedAt:M.decidedAt||0,offerCount:M.offerCount|0,
careerSequence:careerSequence(),hints:cloneMap(M.hints||{}),skills:skills()};
}
function shown(el){
if(!el) return false;
try{ var s=getComputedStyle(el); return s.display!=='none'&&s.visibility!=='hidden'; }
catch(e){ return !el.hidden; }
}
function menuReady(){
if(typeof document==='undefined'||!document.body) return false;
if(typeof bootConfirmed!=='undefined'&&!bootConfirmed) return false;
if(typeof running!=='undefined'&&running) return false;
if(document.body.classList.contains('mfIntroOpen')||document.body.classList.contains('trainingOperation')) return false;
try{ if(/(?:^|[?&])galacticRoute=/.test(String(location.search||''))) return false; }catch(e){}
var front=document.getElementById('startScreen');
if(!shown(front)||front.getAttribute('aria-hidden')==='true') return false;
var blockers=['mfPreAlphaIntro','apOverlay','apConfirmOverlay','accDlg','loadScr'];
for(var i=0;i<blockers.length;i++){
var b=document.getElementById(blockers[i]);
if(b&&shown(b)&&!b.hidden&&b.getAttribute('aria-hidden')!=='true') return false;
}
return true;
}
function signal(type,detail,cancelable){
if(typeof window==='undefined'||typeof window.dispatchEvent!=='function') return true;
try{
var ev;
if(typeof CustomEvent==='function') ev=new CustomEvent(type,{detail:detail,cancelable:!!cancelable});
else if(document&&document.createEvent){ ev=document.createEvent('CustomEvent');ev.initCustomEvent(type,false,!!cancelable,detail); }
if(!ev) return true;
var accepted=window.dispatchEvent(ev);
if(accepted===false) detail.handled=true;
return accepted;
}catch(e){ return true; }
}
function payloadFor(id,def,opt){
opt=opt||{};
return {
schema:'massfront.keel-hint.v1',hintId:id,context:opt.context||def.context,
/* Surface is authored by context and cannot be redirected by callers.
In battle KEEL takes the minimap receiver; in normal space she takes the
existing story rail. This prevents a malformed hint from creating a
third talking-head panel over either game surface. */
surface:def.surface,speaker:'KEEL',speakerId:'keel',affiliation:'uga',
speakerRole:'UGA EXPEDITION GUIDE',channel:'UGA PERSONNEL LINK',
/* KEEL is UGA personnel. Do not let a caller skin the shared presenter as
a selectable-faction voice or commander profile. */
voiceId:'keen',profileId:'uga-keel-expedition-guide',
animationId:opt.animationId||def.animationId||'keel-link',
text:String(opt.text||def.text),durationMs:Math.max(1000,Math.min(12000,opt.durationMs||def.durationMs||5000)),
priority:Math.max(0,opt.priority==null?def.priority||50:opt.priority|0),
issuedAt:stamp(),handled:false
};
}
function battleSurfaceActive(){
if(typeof running==='undefined'||!running) return false;
if(typeof matchLive==='undefined'||!matchLive) return false;
if(typeof gameEnded!=='undefined'&&gameEnded) return false;
try{
var b=document&&document.body;
if(b&&b.classList&&(b.classList.contains('mfMenuOpen')||b.classList.contains('menuMode')))return false;
}catch(e){}
return true;
}
function deliver(P){
if(typeof presenter==='function'){
try{ if(presenter(P)===true) P.handled=true; }
catch(e){ if(window.console) console.error('MFOnboarding presenter',e); }
}
signal(PRESENTER_EVENT,P,true);
return !!P.handled;
}
function retryBattle(P,attempt){
if(P.handled||attempt>=24||!battleSurfaceActive()){
if(!P.handled)P.presenter='battle-minimap-timeout';
return;
}
setTimeout(function(){
if(P.handled||!battleSurfaceActive())return;
P.issuedAt=stamp();
if(!deliver(P))retryBattle(P,attempt+1);
},180);
}
function fallbackPresent(P){
try{
var D=typeof window.__tutDebug==='function'?window.__tutDebug():null;
if(D&&typeof D.speak==='function'){
D.speak(P.text,P.durationMs/1000,'onboarding',P.hintId);
P.handled=true;P.presenter='keel-bubble';return;
}
}catch(e){}
if(typeof toast==='function'){
try{ toast('KEEL: '+P.text);P.handled=true;P.presenter='toast'; }catch(e2){}
}
}
function present(id,opt){
opt=opt||{};
var def=HINTS[id];
if(!def) return null;
var H=hintMeta(id);
if(!opt.force&&(H.completed||H.dismissed||(def.once!==false&&H.seen))) return null;
var P=payloadFor(id,def,opt);
if(opt.persist!==false){
H.seen=true;H.count=(H.count|0)+1;H.lastAt=P.issuedAt;save();
}
deliver(P);
if(!P.handled&&P.surface==='battle-minimap'&&battleSurfaceActive()){
P.presenter='battle-minimap-pending';retryBattle(P,0);
} else if(!P.handled) fallbackPresent(P);
return P;
}
function presentContext(context,opt){
var ids=Object.keys(HINTS).filter(function(id){return HINTS[id].context===context;});
ids.sort(function(a,b){return (HINTS[b].priority|0)-(HINTS[a].priority|0);});
for(var i=0;i<ids.length;i++){
var out=present(ids[i],opt);
if(out) return out;
}
return null;
}
function completeHint(id){
if(!HINTS[id]) return false;
var H=hintMeta(id);H.completed=true;H.dismissed=false;H.completedAt=stamp();save();return true;
}
function dismissHint(id){
if(!HINTS[id]) return false;
var H=hintMeta(id);H.dismissed=true;H.dismissedAt=stamp();save();return true;
}
function setPresenter(fn){
presenter=typeof fn==='function'?fn:null;
return function(){ if(presenter===fn) presenter=null; };
}
/* Onboarding is product UI, so its design belongs in the shipped stylesheet
rather than a late inline block that wins the cascade and cannot be reviewed
alongside the rest of the HUD. */
function ensureStyle(){}
function bindTap(el,fn){
if(typeof mfBindTap==='function') mfBindTap(el,fn); else el.addEventListener('click',fn);
}
function onChoiceKey(ev){
if(!choiceNode) return;
/* Skip is a career decision, not a dialog dismissal. Escape cannot silently
turn into a persisted skip; players choose one of the two explicit 52px
actions. */
if(ev.key==='Escape'){ ev.preventDefault();return; }
if(ev.key!=='Tab') return;
var list=Array.prototype.slice.call(choiceNode.querySelectorAll('button:not([disabled])'));
if(!list.length) return;
var first=list[0],last=list[list.length-1];
if(ev.shiftKey&&document.activeElement===first){ev.preventDefault();last.focus();}
else if(!ev.shiftKey&&document.activeElement===last){ev.preventDefault();first.focus();}
}
function closeChoice(restore){
if(!choiceNode) return;
document.removeEventListener('keydown',onChoiceKey,true);
if(choiceNode.contains(document.activeElement)&&document.activeElement.blur) document.activeElement.blur();
var old=choiceNode;choiceNode=null;
if(old.parentNode) old.parentNode.removeChild(old);
document.body.classList.remove('mfOnboardingOpen');
if(restore&&lastFocus&&lastFocus.focus){ try{lastFocus.focus({preventScroll:true});}catch(e){lastFocus.focus();} }
lastFocus=null;activeOfferOptions=null;
}
function decisionOptions(opt){
var out={},src=activeOfferOptions||{},k;
for(k in src) if(Object.prototype.hasOwnProperty.call(src,k)) out[k]=src[k];
src=opt||{};
for(k in src) if(Object.prototype.hasOwnProperty.call(src,k)) out[k]=src[k];
out.flowId=FLOWS[out.flowId]?out.flowId:'default';
return out;
}
function markChoice(value,opt){
if((value!=='training'&&value!=='skipped')||(!opt.force&&!eligible())) return null;
if(opt.flowId==='experimental-space'&&!experimentalEnabled()&&!opt.force) return null;
if(opt.flowId==='default'&&experimentalEnabled()&&!opt.force) return null;
var F=flowDef(opt.flowId),outcome=value==='training'?F.trainingOutcome:F.skipOutcome;
var M=onboardingMeta(),before=cloneMap(M);
M.version=ONBOARDING_VERSION;M.choice=value;M.flowId=opt.flowId;
M.outcome=outcome;M.decidedAt=stamp();
/* A cross-document training/faction handoff must never be emitted if the
local decision was not durably saved. Revert the whole onboarding record
on failure so a reload cannot observe half of a career transition. */
if(!save()){
if(typeof META!=='undefined'&&META)META.onboarding=before;
if(typeof toast==='function')try{toast('Career choice could not be saved — try again');}catch(e){}
return null;
}
var detail={schema:'massfront.onboarding-choice.v2',flowId:opt.flowId,entryView:F.entryView,
choice:value,outcome:outcome,decidedAt:M.decidedAt,
nextStep:value==='training'?'protected-planetary-training':
(opt.flowId==='experimental-space'?'faction-commissioning':'front-command'),
continuation:opt.flowId==='experimental-space'?{
required:true,trainingOptional:true,factionRequired:true,
commanderGrant:'selected-faction-commander-1',finalStep:'full-uga-space'
}:null};
signal(CHOICE_EVENT,detail,false);
return detail;
}
function chooseTraining(opt){
var cfg=decisionOptions(opt);
if(typeof cfg.onTraining!=='function'&&cfg.launchTraining!==false&&typeof window.resumeTrainingMission!=='function'){
if(typeof toast==='function') toast('Training is still loading — try again in a moment');
return false;
}
var detail=markChoice('training',cfg);if(!detail)return false;closeChoice(false);
var launch=function(){
try{
if(typeof cfg.onTraining==='function') cfg.onTraining(detail);
else if(cfg.launchTraining!==false) window.resumeTrainingMission();
}catch(e){if(window.console)console.error('Training launch',e);}
};
if(typeof requestAnimationFrame==='function')requestAnimationFrame(launch);else setTimeout(launch,0);
return true;
}
function skip(opt){
var cfg=decisionOptions(opt),detail=markChoice('skipped',cfg);if(!detail)return false;closeChoice(true);
if(typeof cfg.onSkip==='function'){
try{cfg.onSkip(detail);}catch(e){if(window.console)console.error('Onboarding skip route',e);}
}
/* Skipping the prompt is not tutorial completion. Leave the real Training
card available, then give one concise menu orientation through the same
presenter contract future HUDs will consume. */
if(cfg.flowId==='default')setTimeout(function(){
/* A delayed menu hint used to survive a fast Training launch and then
appear over the battlefield. Re-check the surface at delivery time. */
if((typeof running==='undefined'||!running)&&menuReady()) present('menu-command');
},120);
return true;
}
function decide(choice,opt){
return choice==='training'?chooseTraining(opt):choice==='skipped'?skip(opt):false;
}
function offer(opt){
opt=opt||{};
if(choiceNode) return true;
if(!opt.force&&(!eligible()||!menuReady())) return false;
if(typeof document==='undefined'||!document.body) return false;
var flowId=FLOWS[opt.flowId]?opt.flowId:'default',F=flowDef(flowId);
activeOfferOptions=decisionOptions(opt);activeOfferOptions.flowId=flowId;
ensureStyle();lastFocus=document.activeElement;
var d=document.createElement('section');d.id='mfOnboardingChoice';d.setAttribute('role','dialog');
d.dataset.flow=flowId;d.setAttribute('aria-modal','true');d.setAttribute('aria-labelledby','mfOnboardingTitle');
d.innerHTML='<div class="mfOnboardingFrame"><div class="mfOnboardingScene" aria-hidden="true">'+
'<span class="mfOnboardingPlanet"></span><span class="mfOnboardingMoon"></span><span class="mfOnboardingVector"></span>'+
'<b>AELOS // PROTECTED CORRIDOR</b><i>UGA APPROACH VECTOR 07</i></div>'+
'<div class="mfOnboardingCard"><header class="mfOnboardingHeader"><span class="mfOnboardingKeel" aria-hidden="true">K</span><span><b>KEEL</b><small>UGA EXPEDITION GUIDE · NEUTRAL PERSONNEL</small></span></header>'+
'<div class="mfOnboardingPath" aria-label="New career sequence"><span class="done"><b>01</b>SPACE ARRIVAL</span><span class="on"><b>02</b>ORIENTATION</span><span><b>03</b>FACTION</span><span><b>04</b>COMMANDER 1</span></div>'+
'<main><div class="mfOnboardingEyebrow">NEW CAREER · PLANETARY APPROACH</div>'+
'<h2 id="mfOnboardingTitle">'+F.title+'</h2><p class="mfOnboardingLead">'+F.lead+'</p>'+
'<div class="mfOnboardingTracks"><section class="mfOnboardingTrack recommended"><small>RECOMMENDED</small><b>'+F.trainingTitle+'</b><span>'+F.trainingText+'</span></section>'+
'<section class="mfOnboardingTrack"><small>EXPERIENCED RTS PLAYERS</small><b>'+F.skipTitle+'</b><span>'+F.skipText+'</span></section></div>'+
'<div class="mfOnboardingSteps" aria-label="Basic RTS tutorial objectives"><span>CAMERA</span><span>LAND</span><span>COMMANDER</span><span>MASS</span><span>POWER</span><span>BUILD</span><span>PRODUCE</span><span>SELECT</span><span>ORDER</span><span>EXTRACT</span></div>'+
'<div class="mfOnboardingActions"><button id="mfOnboardingBegin" type="button"><small>OPTION 01</small>'+F.trainingLabel+'</button><button id="mfOnboardingSkip" class="quiet" type="button"><small>OPTION 02</small>'+F.skipLabel+'</button></div>'+
'<p class="mfOnboardingFoot">'+F.foot+'</p></main></div></div>';
choiceNode=d;document.body.appendChild(d);document.body.classList.add('mfOnboardingOpen');
bindTap(document.getElementById('mfOnboardingBegin'),chooseTraining);
bindTap(document.getElementById('mfOnboardingSkip'),skip);
document.addEventListener('keydown',onChoiceKey,true);
var M=onboardingMeta(),before=cloneMap(M);M.offeredAt=stamp();M.offerCount=(M.offerCount|0)+1;
if(!save()){
if(typeof META!=='undefined'&&META)META.onboarding=before;
closeChoice(false);if(typeof toast==='function')try{toast('Career setup is unavailable until saving recovers');}catch(e){}
return false;
}
var focus=function(){var b=document.getElementById('mfOnboardingBegin');if(b)try{b.focus({preventScroll:true});}catch(e){b.focus();}};
if(typeof requestAnimationFrame==='function')requestAnimationFrame(focus);else setTimeout(focus,0);
return true;
}
function definitions(){
return Object.keys(HINTS).map(function(id){var d=cloneMap(HINTS[id]);d.id=id;return d;});
}
function init(){
if(initialized) return;initialized=true;
var poll=function(){ try{
/* The account/offline gate can open a frame after the menu first becomes
eligible. If that happens while this choice is already mounted, yield
the modal layer and offer again after the blocking surface closes. */
if(choiceNode&&!menuReady()){closeChoice(false);return;}
if(automaticEligible()&&menuReady()) offer();
}catch(e){if(window.console)console.error('MFOnboarding',e);} };
setInterval(poll,450);
if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',poll,{once:true});
else setTimeout(poll,0);
}
window.MFOnboarding={
VERSION:ONBOARDING_VERSION,PRESENTER_EVENT:PRESENTER_EVENT,CHOICE_EVENT:CHOICE_EVENT,
state:state,skills:skills,eligible:eligible,automaticEligible:automaticEligible,
sequence:choiceContract,choiceContract:choiceContract,careerSequence:careerSequence,offer:offer,decide:decide,
chooseTraining:chooseTraining,skip:skip,
definitions:definitions,present:present,presentContext:presentContext,
completeHint:completeHint,dismissHint:dismissHint,setPresenter:setPresenter
};
try{init();}catch(e){if(window.console)console.error('init onboarding',e);}
})();
|