clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

main
miau-is-true / src / render / stage-pause.js
stage-pause.js 6.3K
1import { drawBoard } from "./board.js";2import { drawIndex } from "./index.js";3import { drawKeycap } from "./stage-hud.js";4import { pauseMotionFrame, pauseMotionLayout } from "./menu-motion.js";56export function wrapCanvasText(ctx, value, maxWidth) {7 const words = String(value || "").trim().split(/\s+/).filter(Boolean);8 if (!words.length) return [];9 const lines = [];10 let line = "";11 for (const word of words) {12 const candidate = line ? `${line} ${word}` : word;13 if (line && ctx.measureText(candidate).width > maxWidth) {14 lines.push(line);15 line = word;16 } else line = candidate;17 }18 if (line) lines.push(line);19 return lines;20}2122export function pausePawX(width) {23 return Math.max(32, width / 2 - 176);24}2526export function renderPauseScene(ctx, view = {}) {27 const { width: w, height: h } = view;28 const {29 boardState, boardGame, boardPalette = {}, boardGlyphs = {}, boardFrame,30 pausedFrom, indexLayout, indexNavigator, indexFacing, indexPopNodeId,31 indexPopStart, indexLockedNode, title, pauseItems = [], selected = 0,32 reducedMotion = false, pauseMotion,33 } = view;34 const motion = pauseMotionFrame({ ...(pauseMotion || {}), now: view.now,35 itemCount: pauseItems.length, selected, reducedMotion });36 if (pausedFrom === "index" && indexLayout && indexNavigator) {37 view.onIndexGeometry?.(drawIndex(ctx, indexLayout, indexNavigator, {38 availableWidth: w, availableHeight: h, cat: boardGlyphs.MIAU || "🐈",39 catFacing: indexFacing, popNodeId: indexPopNodeId,40 popProgress: indexPopStart ? Math.min(1, (view.now - indexPopStart) / 520) : 1,41 reducedMotion, hideBanner: true, hideFooter: true, lockedNode: indexLockedNode,42 }));43 } else if (pausedFrom === "title" && title) {44 title.draw(ctx, { width: w, height: h, now: view.now, reducedMotion });45 } else if (boardState) {46 const cell = Math.min(96, Math.max(22, Math.floor(Math.min(47 (w - 80) / boardState.width, (h - 320) / boardState.height,48 ))));49 drawBoard(ctx, boardState, { x: Math.floor((w - boardState.width * cell) / 2),50 y: Math.max(118, Math.floor((h - boardState.height * cell) / 2 - 30)), cell,51 game: boardGame, palette: boardPalette, glyphs: boardGlyphs, frame: boardFrame });52 }53 if (pausedFrom === "index") {54 ctx.fillStyle = "#0F0E0D";55 ctx.fillRect(0, 0, w, Math.max(96, h * 0.16));56 ctx.fillRect(0, h - Math.max(72, h * 0.12), w, Math.max(72, h * 0.12));57 }58 ctx.fillStyle = `rgba(0,0,0,${0.74 * motion.dimProgress})`;59 ctx.fillRect(0, 0, w, h);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;67 ctx.fillStyle = "#0F0E0D";68 ctx.fillRect(0, bandCenter - shutterHeight / 2, w, shutterHeight);69 ctx.strokeStyle = "#F2EEE6";70 ctx.lineWidth = 1;71 ctx.beginPath();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);74 ctx.stroke();75 ctx.textAlign = "center"; ctx.textBaseline = "middle";76 ctx.fillStyle = "#FF4B3E"; ctx.font = "700 13px Recursive, monospace";77 const pauseTitle = "PAUSED".slice(0, Math.ceil(6 * motion.titleProgress));78 ctx.globalAlpha = motion.titleProgress;79 ctx.fillText(pauseTitle, w / 2, menuTitleY);80 ctx.globalAlpha = 1;81 pauseItems.forEach((item, i) => {82 const itemMotion = motion.itemProgress[i] ?? 1;83 const y = itemTop + i * pauseMotionLayout.pitch - (1 - itemMotion) * 22;84 ctx.globalAlpha = itemMotion;85 ctx.fillStyle = "#000000"; ctx.fillRect(w / 2 - 154, y - 21, 320, 54);86 ctx.fillStyle = "#232120";87 ctx.fillRect(w / 2 - 160, y - 27, 320, 54);88 ctx.globalAlpha = 1;89 });90 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;94 ctx.fillStyle = "#FF4B3E";95 ctx.fillRect(w / 2 - 160, selectedY - 27, 320, pauseMotionLayout.height);96 ctx.globalAlpha = 1;97 }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;103 pauseItems.forEach((item, i) => {104 const itemMotion = motion.itemProgress[i] ?? 1;105 const y = itemTop + i * pauseMotionLayout.pitch - (1 - itemMotion) * 22;106 const highlightTop = selectedY - 27;107 const labelInHighlight = y >= highlightTop && y <= highlightTop + pauseMotionLayout.height;108 ctx.globalAlpha = itemMotion;109 ctx.fillStyle = "#0F0E0D";110 ctx.font = "800 20px 'Apple Color Emoji', 'Segoe UI Emoji', sans-serif";111 ctx.textAlign = mobileMenu ? "left" : "right";112 ctx.fillText(i === Math.round(motion.selectedPosition) ? "🐾" : "", pawX,113 y + 1 - (i === Math.round(motion.selectedPosition) ? motion.pawHop : 0));114 ctx.fillStyle = labelInHighlight ? "#0F0E0D" : "#F2EEE6";115 ctx.font = "800 13px Recursive, monospace"; ctx.textAlign = "center";116 ctx.fillText(item.label, w / 2, y + 1 + (i === selected ? motion.pressOffset : 0));117 ctx.globalAlpha = 1;118 });119 ctx.globalAlpha = motion.hintProgress;120 ctx.textAlign = "left"; ctx.fillStyle = "#8E877C"; ctx.font = "500 11px Recursive, monospace";121 if (pausedFrom !== "index" && pausedFrom !== "title") {122 const hintLines = wrapCanvasText(ctx, boardState?.hint || "Choose an action", Math.max(120, w - 64));123 const hintLineHeight = 14;124 const hintStart = h - 86 - (hintLines.length - 1) * hintLineHeight;125 hintLines.forEach((line, index) => ctx.fillText(line, 32, hintStart + index * hintLineHeight));126 }127 if (w > 760 && !coarsePointer) {128 drawKeycap(ctx, "↑↓", "choose", 32, h - 46);129 drawKeycap(ctx, "ENTER", "select", w / 2 - 4, h - 46, "right");130 drawKeycap(ctx, "ESC", "back", w - 32, h - 46, "right");131 }132 ctx.globalAlpha = 1;133}