diff --git a/docs/gameplay.md b/docs/gameplay.md index 5abfa44..09b3a61 100644 --- a/docs/gameplay.md +++ b/docs/gameplay.md @@ -84,9 +84,9 @@ Your best score is saved in `localStorage` as `meatslicer_best` and shown on the |---|---| | **Enter / Space / Click** | Start run (title), restart after death | -The title screen has **[X] Exit to Desktop**. Pause has **[Q] Main Menu** and **[X] Exit**. +The desktop app title screen has **[X] Exit to Desktop**. Pause has **[Q] Main Menu** and **[X] Exit**. The first matching press arms a 3-second confirmation; press it again to confirm. `P` or -`Escape` resumes. Electron quits directly; the browser fallback asks you to close the tab. +`Escape` resumes. In a web browser, desktop-exit controls are greyed out and non-interactive. Right-click is disabled (context menu suppressed). There is **no dash, no interact key** — pickups are collected by walking over them. diff --git a/js/main.js b/js/main.js index b44d671..20f82e3 100644 --- a/js/main.js +++ b/js/main.js @@ -121,13 +121,12 @@ function returnToMenu() { Music.playMenu(); } +function canQuitDesktop() { + return !!(window.MSDesktop && typeof window.MSDesktop.quit === 'function'); +} + function quitToDesktop() { - if (window.MSDesktop && typeof window.MSDesktop.quit === 'function') { - window.MSDesktop.quit(); - return; - } - window.close(); - addToast('EXIT BLOCKED', 'close this browser tab to exit.'); + if (canQuitDesktop()) window.MSDesktop.quit(); } function confirmAction(name, action) { @@ -181,7 +180,7 @@ function update(dt) { case 'menu': { const sl = PRESSURE_SLIDER_RECT; const exitClick = Input.mpressed && inRect(Input.mx, Input.my, MENU_EXIT_BUTTON); - if (keyPressed('x') || exitClick) { + if (canQuitDesktop() && (keyPressed('x') || exitClick)) { confirmAction('desktop', quitToDesktop); break; } @@ -221,7 +220,7 @@ function update(dt) { const menuClick = Input.mpressed && inRect(Input.mx, Input.my, PAUSE_MENU_BUTTON); const exitClick = Input.mpressed && inRect(Input.mx, Input.my, PAUSE_EXIT_BUTTON); if (keyPressed('q') || menuClick) { confirmAction('menu', returnToMenu); break; } - if (keyPressed('x') || exitClick) { confirmAction('desktop', quitToDesktop); break; } + if (canQuitDesktop() && (keyPressed('x') || exitClick)) { confirmAction('desktop', quitToDesktop); break; } if (keyPressed('p', 'escape')) { G.mode = 'play'; G.confirmAction = null; G.confirmT = 0; Sfx.menu(); break; } if (keyPressed('h') || (Input.mpressed && inRect(Input.mx, Input.my, HELP_BUTTON))) { G.pauseHelp = true; G.helpPage = 0; G.confirmAction = null; G.confirmT = 0; Sfx.menu(); break; @@ -388,11 +387,12 @@ function drawMenu(ctx) { }); } const exitHover = inRect(Input.mx, Input.my, MENU_EXIT_BUTTON); - const exitConfirm = G.confirmAction === 'desktop'; - drawPixelTag(ctx, exitConfirm ? '[X] CONFIRM EXIT' : '[X] EXIT TO DESKTOP', MENU_EXIT_BUTTON.x, MENU_EXIT_BUTTON.y, { + const desktopExit = canQuitDesktop(); + const exitConfirm = desktopExit && G.confirmAction === 'desktop'; + drawPixelTag(ctx, !desktopExit ? '[X] DESKTOP APP ONLY' : (exitConfirm ? '[X] CONFIRM EXIT' : '[X] EXIT TO DESKTOP'), MENU_EXIT_BUTTON.x, MENU_EXIT_BUTTON.y, { width: MENU_EXIT_BUTTON.w, height: MENU_EXIT_BUTTON.h, - color: exitConfirm ? '#ffd36a' : (exitHover ? '#f5e9d6' : '#ad8f8c'), - accent: exitConfirm ? '#d69a22' : (exitHover ? '#e22b46' : '#6e3843'), + color: !desktopExit ? '#65595b' : (exitConfirm ? '#ffd36a' : (exitHover ? '#f5e9d6' : '#ad8f8c')), + accent: !desktopExit ? '#3a2d31' : (exitConfirm ? '#d69a22' : (exitHover ? '#e22b46' : '#6e3843')), }); } @@ -488,17 +488,18 @@ function drawPause(ctx) { const menuHover = inRect(Input.mx, Input.my, PAUSE_MENU_BUTTON); const exitHover = inRect(Input.mx, Input.my, PAUSE_EXIT_BUTTON); const menuConfirm = G.confirmAction === 'menu'; - const exitConfirm = G.confirmAction === 'desktop'; + const desktopExit = canQuitDesktop(); + const exitConfirm = desktopExit && G.confirmAction === 'desktop'; drawPixelTag(ctx, '[M] MUTE AUDIO', gridX[0], 526, { width: 150, height: 28, color: '#bba9a1', accent: '#6e3843' }); drawPixelTag(ctx, menuConfirm ? '[Q] CONFIRM MENU' : '[Q] MAIN MENU', PAUSE_MENU_BUTTON.x, PAUSE_MENU_BUTTON.y, { width: PAUSE_MENU_BUTTON.w, height: PAUSE_MENU_BUTTON.h, color: menuConfirm ? '#ffd36a' : (menuHover ? '#f5e9d6' : '#bba9a1'), accent: menuConfirm ? '#d69a22' : (menuHover ? '#b5243a' : '#6e3843'), }); - drawPixelTag(ctx, exitConfirm ? '[X] CONFIRM EXIT' : '[X] EXIT', PAUSE_EXIT_BUTTON.x, PAUSE_EXIT_BUTTON.y, { + drawPixelTag(ctx, !desktopExit ? '[X] DESKTOP ONLY' : (exitConfirm ? '[X] CONFIRM EXIT' : '[X] EXIT'), PAUSE_EXIT_BUTTON.x, PAUSE_EXIT_BUTTON.y, { width: PAUSE_EXIT_BUTTON.w, height: PAUSE_EXIT_BUTTON.h, - color: exitConfirm ? '#ffd36a' : (exitHover ? '#f5e9d6' : '#bba9a1'), - accent: exitConfirm ? '#d69a22' : (exitHover ? '#b5243a' : '#6e3843'), + color: !desktopExit ? '#65595b' : (exitConfirm ? '#ffd36a' : (exitHover ? '#f5e9d6' : '#bba9a1')), + accent: !desktopExit ? '#3a2d31' : (exitConfirm ? '#d69a22' : (exitHover ? '#b5243a' : '#6e3843')), }); const hb = HELP_BUTTON; diff --git a/test/smoke.js b/test/smoke.js index 7f3e72d..28263ec 100644 --- a/test/smoke.js +++ b/test/smoke.js @@ -1582,6 +1582,7 @@ check('restart works', ctx.G.mode === 'play' && ctx.G.floor === 1); console.log('== confirmed destructive actions =='); { + check('web renderer disables desktop-only exit', ctx.canQuitDesktop() === false); let fired = 0; ctx.confirmAction('probe', () => fired++); check('first destructive action arms confirmation without firing', ctx.G.confirmAction === 'probe' && ctx.G.confirmT > 2.9 && fired === 0);