132132 gap : 16px ;
133133 }
134134
135+ body .app-loading .shell {
136+ display : none;
137+ }
138+
139+ body : not (.app-loading ) .boot-screen {
140+ display : none;
141+ }
142+
143+ .boot-screen {
144+ width : min (680px , calc (100vw - 36px ));
145+ min-height : calc (100svh - 72px );
146+ margin : 0 auto;
147+ display : grid;
148+ align-items : center;
149+ padding : 34px 0 ;
150+ }
151+
152+ .boot-panel {
153+ display : grid;
154+ gap : 13px ;
155+ padding : 8px 0 ;
156+ }
157+
158+ .boot-kicker {
159+ color : var (--accent );
160+ font-size : 12px ;
161+ line-height : 1.2 ;
162+ font-weight : 800 ;
163+ text-transform : uppercase;
164+ }
165+
166+ .boot-panel h2 {
167+ color : var (--ink );
168+ font-size : clamp (28px , 5vw , 44px );
169+ line-height : 1.04 ;
170+ font-weight : 820 ;
171+ }
172+
173+ .boot-panel p {
174+ color : var (--muted );
175+ font-size : 14px ;
176+ line-height : 1.5 ;
177+ max-width : 520px ;
178+ }
179+
180+ .loading-track {
181+ width : 100% ;
182+ height : 5px ;
183+ overflow : hidden;
184+ border-radius : 999px ;
185+ background : # dde4ea ;
186+ }
187+
188+ .loading-track ::before {
189+ content : "" ;
190+ display : block;
191+ width : 42% ;
192+ height : 100% ;
193+ border-radius : inherit;
194+ background : var (--accent );
195+ animation : loading-sweep 1.05s ease-in-out infinite;
196+ }
197+
135198 .pill ,
136199 .btn {
137200 min-height : 36px ;
536599 width : 100% ;
537600 }
538601
602+ .boot-screen {
603+ width : min (100% - 28px , 680px );
604+ min-height : calc (100svh - 122px );
605+ padding-top : 22px ;
606+ }
607+
539608 .shell {
540609 width : min (100% - 28px , 1160px );
541610 padding-top : 18px ;
580649 transform : translateY (0 );
581650 }
582651 }
652+
653+ @keyframes loading-sweep {
654+ from {
655+ transform : translateX (-110% );
656+ }
657+ to {
658+ transform : translateX (260% );
659+ }
660+ }
661+ }
662+
663+ @media (prefers-reduced-motion : reduce) {
664+ .loading-track ::before {
665+ animation : none;
666+ transform : translateX (0 );
667+ }
583668 }
584669 </ style >
585670</ head >
586- < body >
671+ < body class =" app-loading " >
587672 < header class ="topbar ">
588673 < div class ="brand ">
589674 < h1 data-i18n ="appTitle "> 策略切换</ h1 >
@@ -597,7 +682,16 @@ <h1 data-i18n="appTitle">策略切换</h1>
597682 </ div >
598683 </ header >
599684
600- < main class ="shell ">
685+ < main class ="boot-screen " id ="boot-screen " role ="status " aria-live ="polite ">
686+ < div class ="boot-panel ">
687+ < span class ="boot-kicker " data-i18n ="bootKicker "> 初始化控制台</ span >
688+ < h2 data-i18n ="bootTitle "> 读取策略配置</ h2 >
689+ < p id ="boot-message " data-i18n ="bootMessage "> 正在读取登录状态、账号配置和当前状态。</ p >
690+ < div class ="loading-track " aria-hidden ="true "> </ div >
691+ </ div >
692+ </ main >
693+
694+ < main class ="shell " id ="app-shell ">
601695 < nav class ="platform-strip " id ="platform-strip " aria-label ="Platforms "> </ nav >
602696
603697 < section class ="switch-surface ">
@@ -734,6 +828,13 @@ <h2 data-i18n="summary">切换摘要</h2>
734828 zh : {
735829 appTitle : "策略切换" ,
736830 appSubtitle : "选平台、目标账号和策略,一次执行完成切换。" ,
831+ bootKicker : "初始化控制台" ,
832+ bootTitle : "读取策略配置" ,
833+ bootMessage : "正在读取登录状态、账号配置和当前状态。" ,
834+ bootStrategy : "正在读取策略目录。" ,
835+ bootSession : "正在验证登录状态。" ,
836+ bootConfig : "正在读取账号配置和当前状态。" ,
837+ bootPublic : "公开预览已就绪。" ,
737838 login : "登录" ,
738839 logout : "退出" ,
739840 signedInAs : "已登录 {login}" ,
@@ -787,6 +888,13 @@ <h2 data-i18n="summary">切换摘要</h2>
787888 en : {
788889 appTitle : "Strategy Switch" ,
789890 appSubtitle : "Pick platform, target account, and strategy. One action switches everything." ,
891+ bootKicker : "Starting console" ,
892+ bootTitle : "Loading strategy config" ,
893+ bootMessage : "Reading session, account config, and current state." ,
894+ bootStrategy : "Reading strategy catalog." ,
895+ bootSession : "Checking sign-in status." ,
896+ bootConfig : "Reading account config and current state." ,
897+ bootPublic : "Public preview is ready." ,
790898 login : "Sign in" ,
791899 logout : "Sign out" ,
792900 signedInAs : "Signed in as {login}" ,
@@ -848,6 +956,8 @@ <h2 data-i18n="summary">切换摘要</h2>
848956 const state = {
849957 selected : "longbridge" ,
850958 lang : initialLang ,
959+ appReady : false ,
960+ bootMessageKey : "bootMessage" ,
851961 auth : { available : false , allowed : false , admin : false , login : null } ,
852962 accountOptions : clone ( defaultAccountOptions ) ,
853963 currentStrategies : { } ,
@@ -1263,15 +1373,23 @@ <h2 data-i18n="summary">切换摘要</h2>
12631373 note . classList . toggle ( "warning" , state . auth . allowed && ! loadingConfig && ( ! hasPrivateAccounts || ! hasValidStrategy ) ) ;
12641374 }
12651375
1376+ function renderAppVisibility ( ) {
1377+ document . body . classList . toggle ( "app-loading" , ! state . appReady ) ;
1378+ el ( "boot-message" ) . textContent = t ( state . bootMessageKey ) ;
1379+ }
1380+
12661381 function render ( ) {
12671382 applyLanguage ( ) ;
12681383 renderPlatforms ( ) ;
12691384 renderControls ( ) ;
12701385 renderSummary ( ) ;
12711386 renderAuth ( ) ;
1387+ renderAppVisibility ( ) ;
12721388 }
12731389
12741390 async function refreshSession ( ) {
1391+ state . bootMessageKey = "bootSession" ;
1392+ render ( ) ;
12751393 try {
12761394 const response = await fetch ( "/api/session" , { cache : "no-store" } ) ;
12771395 if ( ! response . ok ) throw new Error ( "no backend" ) ;
@@ -1285,11 +1403,18 @@ <h2 data-i18n="summary">切换摘要</h2>
12851403 } catch {
12861404 state . auth = { available : false , allowed : false , admin : false , login : null } ;
12871405 }
1288- render ( ) ;
1289- await refreshConfig ( ) ;
1406+ if ( state . auth . allowed ) {
1407+ await refreshConfig ( ) ;
1408+ } else {
1409+ state . bootMessageKey = "bootPublic" ;
1410+ state . appReady = true ;
1411+ render ( ) ;
1412+ }
12901413 }
12911414
12921415 async function refreshStrategyProfiles ( ) {
1416+ state . bootMessageKey = "bootStrategy" ;
1417+ render ( ) ;
12931418 try {
12941419 const response = await fetch ( "/api/strategy-profiles" , { cache : "no-store" } ) ;
12951420 if ( ! response . ok ) throw new Error ( "no strategy profiles" ) ;
@@ -1306,6 +1431,7 @@ <h2 data-i18n="summary">切换摘要</h2>
13061431 async function refreshConfig ( ) {
13071432 if ( ! state . auth . available || ! state . auth . allowed ) return ;
13081433 state . configSource = "loading" ;
1434+ state . bootMessageKey = "bootConfig" ;
13091435 render ( ) ;
13101436 try {
13111437 const response = await fetch ( "/api/config" , { cache : "no-store" } ) ;
@@ -1320,11 +1446,16 @@ <h2 data-i18n="summary">切换摘要</h2>
13201446 ensureAccountSelection ( platform ) ;
13211447 syncStrategyForAccount ( platform ) ;
13221448 }
1323- render ( ) ;
1449+ } else {
1450+ state . configSource = "default" ;
1451+ state . currentStrategies = { } ;
13241452 }
13251453 } catch {
13261454 state . configSource = "default" ;
13271455 state . currentStrategies = { } ;
1456+ } finally {
1457+ state . appReady = true ;
1458+ render ( ) ;
13281459 }
13291460 }
13301461
@@ -1454,8 +1585,12 @@ <h2 data-i18n="summary">切换摘要</h2>
14541585 applyStrategyProfiles ( defaultStrategyProfiles ) ;
14551586 for ( const platform of Object . keys ( platformMeta ) ) syncStrategyForAccount ( platform ) ;
14561587 render ( ) ;
1457- refreshStrategyProfiles ( ) ;
1458- refreshSession ( ) ;
1588+ boot ( ) ;
1589+
1590+ async function boot ( ) {
1591+ await refreshStrategyProfiles ( ) ;
1592+ await refreshSession ( ) ;
1593+ }
14591594 </ script >
14601595</ body >
14611596</ html >
0 commit comments