- commit
- 5865bc4931625741e81ed6ea6351b1d2a03fb990
- parent
- 58a90aa96da287064b7563c22549f9bc97911066
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 6 | +++++ |
| M | src/ | 35 | ++++++++++++++++++++---------- |
| M | tests/ | 4 | +++- |
@@ -61,6 +61,11 @@ export function pauseMotionFrame({
6161 const exiting = !reducedMotion && phase === 'exiting';6262 const openProgress = reducedMotion ? 1 : progress(now, openedAt, OPEN_DURATION);6363 const exitProgress = reducedMotion ? 1 : progress(now, exitStartedAt, EXIT_DURATION);64+ const dimProgress = reducedMotion65+ ? 166+ : exiting67+ ? 1 - easeInCubic(exitProgress)68+ : easeOutCubic(progress(now, openedAt, 140));6469 const titleProgress = reducedMotion ? 1 : progress(now, openedAt + 60, 160);6570 const hintProgress = reducedMotion6671 ? 1@@ -93,6 +98,7 @@ export function pauseMotionFrame({
9398 done: phase === 'exiting' ? exitProgress >= 1 : openProgress >= 1,9499 openProgress,95100 exitProgress,101+ dimProgress,96102 shutterProgress: exiting ? 1 - easeInCubic(exitProgress) : easeOutBack(openProgress),97103 titleProgress,98104 hintProgress,@@ -20,7 +20,7 @@ export function wrapCanvasText(ctx, value, maxWidth) {
2020 }2121 2222 export function pausePawX(width) {23- return Math.max(24, width / 2 - 176);23+ return Math.max(32, width / 2 - 176);2424 }2525 2626 export function renderPauseScene(ctx, view = {}) {@@ -55,25 +55,29 @@ export function renderPauseScene(ctx, view = {}) {
5555 ctx.fillRect(0, 0, w, Math.max(96, h * 0.16));5656 ctx.fillRect(0, h - Math.max(72, h * 0.12), w, Math.max(72, h * 0.12));5757 }58- ctx.fillStyle = `rgba(0,0,0,${0.74 * motion.openProgress})`;58+ ctx.fillStyle = `rgba(0,0,0,${0.74 * motion.dimProgress})`;5959 ctx.fillRect(0, 0, w, h);60- const shutterHeight = h * 0.62 * motion.shutterProgress;60+ const itemTop = h * 0.24;61+ const menuTitleY = itemTop - 58;62+ const lastItemY = itemTop + Math.max(0, pauseItems.length - 1) * pauseMotionLayout.pitch;63+ const bandTop = menuTitleY - 25;64+ const bandBottom = lastItemY + pauseMotionLayout.height / 2 + 26;65+ const bandCenter = (bandTop + bandBottom) / 2;66+ const shutterHeight = (bandBottom - bandTop) * motion.shutterProgress;6167 ctx.fillStyle = "#0F0E0D";62- ctx.fillRect(0, (h - shutterHeight) / 2, w, shutterHeight);68+ ctx.fillRect(0, bandCenter - shutterHeight / 2, w, shutterHeight);6369 ctx.strokeStyle = "#F2EEE6";6470 ctx.lineWidth = 1;6571 ctx.beginPath();66- ctx.moveTo(0, (h - shutterHeight) / 2); ctx.lineTo(w, (h - shutterHeight) / 2);67- ctx.moveTo(0, (h + shutterHeight) / 2); ctx.lineTo(w, (h + shutterHeight) / 2);72+ ctx.moveTo(0, bandCenter - shutterHeight / 2); ctx.lineTo(w, bandCenter - shutterHeight / 2);73+ ctx.moveTo(0, bandCenter + shutterHeight / 2); ctx.lineTo(w, bandCenter + shutterHeight / 2);6874 ctx.stroke();6975 ctx.textAlign = "center"; ctx.textBaseline = "middle";7076 ctx.fillStyle = "#FF4B3E"; ctx.font = "700 13px Recursive, monospace";7177 const pauseTitle = "PAUSED".slice(0, Math.ceil(6 * motion.titleProgress));7278 ctx.globalAlpha = motion.titleProgress;73- ctx.fillText(pauseTitle, w / 2, Math.max(75, h * 0.12));79+ ctx.fillText(pauseTitle, w / 2, menuTitleY);7480 ctx.globalAlpha = 1;75- const itemTop = h * 0.24;76- const selectedY = itemTop + motion.selectedPosition * pauseMotionLayout.pitch;7781 pauseItems.forEach((item, i) => {7882 const itemMotion = motion.itemProgress[i] ?? 1;7983 const y = itemTop + i * pauseMotionLayout.pitch - (1 - itemMotion) * 22;@@ -84,10 +88,18 @@ export function renderPauseScene(ctx, view = {}) {
8488 ctx.globalAlpha = 1;8589 });8690 if (pauseItems.length && motion.openProgress > 0) {91+ const selectedMotion = motion.itemProgress[selected] ?? 1;92+ const selectedY = itemTop + motion.selectedPosition * pauseMotionLayout.pitch - (1 - selectedMotion) * 22;93+ ctx.globalAlpha = selectedMotion;8794 ctx.fillStyle = "#FF4B3E";8895 ctx.fillRect(w / 2 - 160, selectedY - 27, 320, pauseMotionLayout.height);96+ ctx.globalAlpha = 1;8997 }90- const pawX = pausePawX(w);98+ const coarsePointer = view.coarsePointer ?? false;99+ const mobileMenu = w < 760 || coarsePointer;100+ const pawX = mobileMenu ? w / 2 - 160 + 16 : pausePawX(w);101+ const selectedMotion = motion.itemProgress[selected] ?? 1;102+ const selectedY = itemTop + motion.selectedPosition * pauseMotionLayout.pitch - (1 - selectedMotion) * 22;91103 pauseItems.forEach((item, i) => {92104 const itemMotion = motion.itemProgress[i] ?? 1;93105 const y = itemTop + i * pauseMotionLayout.pitch - (1 - itemMotion) * 22;@@ -96,7 +108,7 @@ export function renderPauseScene(ctx, view = {}) {
96108 ctx.globalAlpha = itemMotion;97109 ctx.fillStyle = "#0F0E0D";98110 ctx.font = "800 20px 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif";99- ctx.textAlign = "right";111+ ctx.textAlign = mobileMenu ? "left" : "right";100112 ctx.fillText(i === Math.round(motion.selectedPosition) ? "🐾" : "", pawX,101113 y + 1 - (i === Math.round(motion.selectedPosition) ? motion.pawHop : 0));102114 ctx.fillStyle = labelInHighlight ? "#0F0E0D" : "#F2EEE6";@@ -112,7 +124,6 @@ export function renderPauseScene(ctx, view = {}) {
112124 const hintStart = h - 86 - (hintLines.length - 1) * hintLineHeight;113125 hintLines.forEach((line, index) => ctx.fillText(line, 32, hintStart + index * hintLineHeight));114126 }115- const coarsePointer = view.coarsePointer ?? false;116127 if (w > 760 && !coarsePointer) {117128 drawKeycap(ctx, "↑↓", "choose", 32, h - 46);118129 drawKeycap(ctx, "ENTER", "select", w / 2 - 4, h - 46, "right");@@ -46,7 +46,7 @@ test("pause hints wrap to the available canvas width", () => {
4646 });4747 4848 test("pause paw stays inside narrow menu bounds", () => {49- assert.equal(pausePawX(390), 24);49+ assert.equal(pausePawX(390), 32);5050 assert.equal(pausePawX(1280), 464);5151 });5252 @@ -55,6 +55,8 @@ test("pause hints wait until the menu lands and fade out on exit", () => {
5555 const after = pauseMotionFrame({ now: 1400, openedAt: 1000, phase: "idle", itemCount: 2 });5656 assert.equal(before.hintProgress, 0);5757 assert.equal(after.hintProgress, 1);58+ assert.equal(pauseMotionFrame({ now: 1000, openedAt: 1000, phase: "opening" }).dimProgress, 0);59+ assert.equal(pauseMotionFrame({ now: 1140, openedAt: 1000, phase: "opening" }).dimProgress, 1);5860 const exiting = pauseMotionFrame({ now: 1100, openedAt: 0, phase: "exiting", exitStartedAt: 1000, itemCount: 2 });5961 assert.ok(exiting.hintProgress < 1);6062 assert.ok(exiting.hintProgress > 0);