@@ -86,61 +86,74 @@ const buttonAnimationTokens = new WeakMap()
8686 * Handles cancellation via per-element tokens and updates 'selected' class and image borders.
8787 */
8888/**
89- * Swap selection entre deux boutons de sidebar (mode instantané pour performance)
89+ * Swap selection entre deux boutons de sidebar avec animations fluides
9090 */
9191async function animateButtonSwap ( prevBtn , nextBtn ) {
92- // Mode instantané - pas d'animations pour améliorer les performances
9392 try {
94- // Retirer la sélection du bouton précédent
93+ // Animer la sortie du bouton précédent
9594 if ( prevBtn ) {
96- prevBtn . classList . remove ( 'selected ')
95+ prevBtn . classList . add ( 'instance-fade-out ')
9796 const img = prevBtn . querySelector ( 'img' )
9897 if ( img ) {
98+ img . style . transition = 'border-color 0.3s ease-out'
9999 img . classList . remove ( 'border-[#F8BA59]' )
100100 img . classList . add ( 'border-white/20' )
101101 }
102- // Nettoyage des classes d'animation existantes
103- try {
104- const labelPrev = prevBtn . querySelector ( '.font-semibold.text-xl.leading-tight' )
105- if ( labelPrev ) labelPrev . classList . remove ( 'label-slide-out' , 'label-slide-in' )
106- } catch ( e ) { }
107- prevBtn . classList . remove ( 'instance-btn-exit' , 'instance-btn-enter' )
108102 }
109103
110- // Appliquer la sélection au nouveau bouton immédiatement
104+ // Attendre la fin de l'animation de sortie
105+ await new Promise ( r => setTimeout ( r , 150 ) )
106+
107+ // Retirer la sélection et nettoyer le bouton précédent
108+ if ( prevBtn ) {
109+ prevBtn . classList . remove ( 'selected' , 'instance-fade-out' )
110+ }
111+
112+ // Appliquer la sélection au nouveau bouton avec animation d'entrée
111113 if ( nextBtn ) {
112- nextBtn . classList . add ( 'selected' )
114+ nextBtn . classList . add ( 'selected' , 'instance-fade-in' )
113115 const img = nextBtn . querySelector ( 'img' )
114116 if ( img ) {
117+ img . style . transition = 'border-color 0.3s ease-out'
115118 img . classList . remove ( 'border-white/20' )
116119 img . classList . add ( 'border-[#F8BA59]' )
117120 }
118- // Nettoyage des classes d'animation existantes
119- try {
120- const labelNext = nextBtn . querySelector ( '.font-semibold.text-xl.leading-tight' )
121- if ( labelNext ) labelNext . classList . remove ( 'label-slide-out' , 'label-slide-in' )
122- } catch ( e ) { }
123- nextBtn . classList . remove ( 'instance-btn-exit' , 'instance-btn-enter' )
121+
122+ // Nettoyer la classe d'animation après qu'elle soit terminée
123+ setTimeout ( ( ) => {
124+ nextBtn . classList . remove ( 'instance-fade-in' )
125+ } , 300 )
124126 }
125127 } catch ( e ) {
126128 console . debug ( '[Landing] animateButtonSwap error' , e )
127129 }
128130}
129131
130132/**
131- * Fonction de changement de texte instantanée (optimisée pour performance)
133+ * Fonction de changement de texte avec animation fade
132134 */
133135function animateTextSwap ( el , newHTML , opts = { } ) {
134136 if ( ! el ) return Promise . resolve ( )
135137
136- // Mode instantané - change directement le contenu
137- try {
138- el . innerHTML = newHTML
139- } catch ( e ) {
140- console . debug ( '[Landing] animateTextSwap error' , e )
141- }
142-
143- return Promise . resolve ( )
138+ return new Promise ( resolve => {
139+ // Appliquer l'animation de sortie via classe CSS
140+ el . classList . add ( 'text-fade-out' )
141+
142+ setTimeout ( ( ) => {
143+ // Changer le contenu
144+ el . innerHTML = newHTML
145+
146+ // Retirer la classe de sortie et appliquer l'entrée
147+ el . classList . remove ( 'text-fade-out' )
148+ el . classList . add ( 'text-fade-in' )
149+
150+ // Nettoyer après l'animation
151+ setTimeout ( ( ) => {
152+ el . classList . remove ( 'text-fade-in' )
153+ resolve ( )
154+ } , 300 )
155+ } , 200 )
156+ } )
144157}
145158
146159/**
@@ -193,7 +206,10 @@ function updateInstanceUI() {
193206
194207 if ( count > 0 ) {
195208 // Au moins une instance tourne - afficher les contrôles
196- if ( launchBtn ) launchBtn . classList . add ( 'hidden' )
209+ if ( launchBtn ) {
210+ launchBtn . classList . add ( 'hidden' , 'btn-hidden' )
211+ launchBtn . style . display = 'none'
212+ }
197213 if ( runningControls ) runningControls . classList . add ( 'visible' )
198214 if ( instanceCounter ) instanceCounter . classList . add ( 'visible' )
199215
@@ -219,7 +235,8 @@ function updateInstanceUI() {
219235 } else {
220236 // Aucune instance - afficher le bouton Lancer
221237 if ( launchBtn ) {
222- launchBtn . classList . remove ( 'hidden' )
238+ launchBtn . classList . remove ( 'hidden' , 'btn-hidden' )
239+ launchBtn . style . display = ''
223240 launchBtn . disabled = false
224241 }
225242 if ( runningControls ) runningControls . classList . remove ( 'visible' )
@@ -254,9 +271,28 @@ function updateLaunchUIForServer(serverId){
254271 // Ajouter cette instance au tracker si pas déjà présente
255272 addRunningInstance ( serverId , state . pid )
256273
257- // Cacher le statut de téléchargement
258- if ( launchStatus ) {
259- launchStatus . classList . add ( 'hidden' ) ;
274+ // Afficher le bouton Stop via LaunchUI
275+ if ( window . LaunchUI ) {
276+ console . log ( '[Landing] Calling LaunchUI.showRunning()' ) ;
277+ window . LaunchUI . showRunning ( ) ;
278+ } else {
279+ // Fallback direct si LaunchUI n'est pas disponible
280+ console . log ( '[Landing] LaunchUI not available, using direct DOM' ) ;
281+ const launchBtn = document . getElementById ( 'launch_button' ) ;
282+ const runningControls = document . getElementById ( 'running_controls' ) ;
283+ const launchStatus = document . getElementById ( 'launch_status' ) ;
284+
285+ if ( launchBtn ) {
286+ launchBtn . classList . add ( 'hidden' , 'btn-hidden' ) ;
287+ launchBtn . style . display = 'none' ;
288+ }
289+ if ( runningControls ) {
290+ runningControls . classList . add ( 'visible' ) ;
291+ }
292+ if ( launchStatus ) {
293+ launchStatus . classList . add ( 'hidden' ) ;
294+ launchStatus . style . display = 'none' ;
295+ }
260296 }
261297
262298 } else if ( state && state . starting ) {
@@ -316,7 +352,15 @@ function toggleLaunchArea(loading){
316352 // Mode chargement déjà géré par showDownloading
317353 return ;
318354 } else {
319- window . LaunchUI . showReady ( ) ;
355+ // Vérifier si des instances sont en cours d'exécution
356+ const runningCount = Object . values ( instanceStateMap || { } ) . filter ( s => s . started ) . length ;
357+ console . log ( '[Landing] toggleLaunchArea - running instances:' , runningCount ) ;
358+ if ( runningCount > 0 ) {
359+ // Des instances tournent, afficher les contrôles running
360+ window . LaunchUI . showRunning ( ) ;
361+ } else {
362+ window . LaunchUI . showReady ( ) ;
363+ }
320364 return ;
321365 }
322366 }
@@ -844,33 +888,51 @@ async function updateSelectedServer(serv, instant = true){
844888 ConfigManager . setSelectedServer ( serv != null ? serv . rawServer . id : null )
845889 ConfigManager . save ( )
846890
847- // Update server info in the new UI. Force instant updates for performance
891+ // Update server info in the new UI with animations
848892 const serverTitle = document . querySelector ( '.server-title' )
849893 const serverDesc = document . querySelector ( '.server-desc' )
850894 const serverVersion = document . querySelector ( '.server-version' )
851895 const serverLoader = document . querySelector ( '.server-loader' )
852896 const serverStatusName = document . querySelector ( '.server-status-name' )
853897 const playInstance = document . querySelector ( '.play-instance' )
854898
855- // Always use instant updates for better performance
899+ // Animate the content change
856900 try {
857901 if ( serv != null ) {
858902 const titleHtml = DOMPurify . sanitize ( serv . rawServer . name || '' )
859903 const descHtml = DOMPurify . sanitize ( serv . rawServer . description || '' )
860- if ( serverTitle ) serverTitle . innerHTML = titleHtml
861- if ( serverDesc ) serverDesc . innerHTML = descHtml
862- if ( serverVersion ) serverVersion . textContent = serv . rawServer . minecraftVersion || '--'
863- if ( serverLoader ) serverLoader . textContent = serv . rawServer . loader || '--'
904+
905+ // Animate title and description
906+ animateTextSwap ( serverTitle , titleHtml )
907+ animateTextSwap ( serverDesc , descHtml )
908+
909+ // Update other fields with simple fade
910+ if ( serverVersion ) {
911+ serverVersion . style . transition = 'opacity 0.2s ease-out'
912+ serverVersion . style . opacity = '0'
913+ setTimeout ( ( ) => {
914+ serverVersion . textContent = serv . rawServer . minecraftVersion || '--'
915+ serverVersion . style . opacity = '1'
916+ } , 150 )
917+ }
918+ if ( serverLoader ) {
919+ serverLoader . style . transition = 'opacity 0.2s ease-out'
920+ serverLoader . style . opacity = '0'
921+ setTimeout ( ( ) => {
922+ serverLoader . textContent = serv . rawServer . loader || '--'
923+ serverLoader . style . opacity = '1'
924+ } , 150 )
925+ }
864926 if ( serverStatusName ) serverStatusName . textContent = serv . rawServer . name
865927 } else {
866- if ( serverTitle ) serverTitle . innerHTML = 'Veuillez sélectionner une instance'
867- if ( serverDesc ) serverDesc . innerHTML = 'Aucune instance sélectionnée.<br>Choisissez une instance pour voir ses informations.'
928+ animateTextSwap ( serverTitle , 'Veuillez sélectionner une instance' )
929+ animateTextSwap ( serverDesc , 'Aucune instance sélectionnée.<br>Choisissez une instance pour voir ses informations.' )
868930 if ( serverVersion ) serverVersion . textContent = '--'
869931 if ( serverLoader ) serverLoader . textContent = '--'
870932 if ( serverStatusName ) serverStatusName . textContent = 'Multigames-Studio.fr'
871933 }
872934 } catch ( e ) {
873- console . debug ( '[Landing] instant updateSelectedServer failed' , e )
935+ console . debug ( '[Landing] animated updateSelectedServer failed' , e )
874936 }
875937
876938 // Update server technical info (mods count, RAM allocation)
@@ -984,9 +1046,10 @@ const refreshMojangStatuses = async function(){
9841046
9851047 const mojangEssEl = document . getElementById ( 'mojangStatusEssentialContainer' )
9861048 const mojangNonEssEl = document . getElementById ( 'mojangStatusNonEssentialContainer' )
1049+ const mojangStatusIcon = document . getElementById ( 'mojang_status_icon' )
9871050 if ( mojangEssEl ) mojangEssEl . innerHTML = DOMPurify . sanitize ( tooltipEssentialHTML )
9881051 if ( mojangNonEssEl ) mojangNonEssEl . innerHTML = DOMPurify . sanitize ( tooltipNonEssentialHTML )
989- document . getElementById ( 'mojang_status_icon' ) . style . color = MojangRestAPI . statusToHex ( status )
1052+ if ( mojangStatusIcon ) mojangStatusIcon . style . color = MojangRestAPI . statusToHex ( status )
9901053}
9911054
9921055const refreshServerStatus = async ( fade = false ) => {
@@ -3471,6 +3534,53 @@ window.onInstanceStateChanged = function(payload){
34713534 instanceStateMap [ serverId ] . starting = ! ! payload . starting
34723535 instanceStateMap [ serverId ] . timestamp = Date . now ( )
34733536
3537+ // === GESTION UI DIRECTE ===
3538+ if ( payload . started === true ) {
3539+ console . log ( '[Landing] Game started - showing stop button' ) ;
3540+ // Appel direct à LaunchUI
3541+ if ( window . LaunchUI && typeof window . LaunchUI . showRunning === 'function' ) {
3542+ window . LaunchUI . showRunning ( ) ;
3543+ } else {
3544+ // Fallback DOM direct
3545+ const launchBtn = document . getElementById ( 'launch_button' ) ;
3546+ const runningControls = document . getElementById ( 'running_controls' ) ;
3547+ const launchStatus = document . getElementById ( 'launch_status' ) ;
3548+
3549+ if ( launchBtn ) {
3550+ launchBtn . classList . add ( 'hidden' , 'btn-hidden' ) ;
3551+ launchBtn . style . display = 'none' ;
3552+ }
3553+ if ( runningControls ) {
3554+ runningControls . classList . add ( 'visible' ) ;
3555+ }
3556+ if ( launchStatus ) {
3557+ launchStatus . classList . add ( 'hidden' ) ;
3558+ }
3559+ }
3560+ } else if ( payload . starting === true ) {
3561+ console . log ( '[Landing] Game starting - showing loading' ) ;
3562+ if ( window . LaunchUI && typeof window . LaunchUI . showDownloading === 'function' ) {
3563+ window . LaunchUI . showDownloading ( 'Démarrage...' , 0 ) ;
3564+ }
3565+ } else if ( payload . started === false ) {
3566+ console . log ( '[Landing] Game stopped - showing launch button' ) ;
3567+ if ( window . LaunchUI && typeof window . LaunchUI . showReady === 'function' ) {
3568+ window . LaunchUI . showReady ( ) ;
3569+ } else {
3570+ // Fallback DOM direct
3571+ const launchBtn = document . getElementById ( 'launch_button' ) ;
3572+ const runningControls = document . getElementById ( 'running_controls' ) ;
3573+
3574+ if ( launchBtn ) {
3575+ launchBtn . classList . remove ( 'hidden' , 'btn-hidden' ) ;
3576+ launchBtn . style . display = '' ;
3577+ }
3578+ if ( runningControls ) {
3579+ runningControls . classList . remove ( 'visible' ) ;
3580+ }
3581+ }
3582+ }
3583+
34743584 // Clear progress when game stops, but only if it was previously marked started
34753585 if ( payload . started === false && prevStarted === true ) {
34763586 setTimeout ( ( ) => {
0 commit comments