clone

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

Make Workshop notes touch-first on narrow screens

Dami committed 200d50d
commit
200d50d5dbf50de4413185b46e93120e89b888ae
parent
f812b80474eea47db42087507d8dd32a6cbb5f44
author
Dami <dami.etoile@gmail.com>
2 files changed, +32 -8
Msrc/scenes/workshop.js20++++++++++++--------
Mtests/workshop.test.js20++++++++++++++++++++
M src/scenes/workshop.js +12 -8
@@ -212,6 +212,9 @@ export function createWorkshopScene(options = {}) {
212212 }213213 if (note) {214214 if (pointerY >= hitLayout.noteCopy.y && pointerY <= hitLayout.noteCopy.y + hitLayout.noteCopy.height && pointerX >= hitLayout.noteCopy.x) return copyDetails();215+ if (hitLayout.noteClose && pointerX >= hitLayout.noteClose.x && pointerX <= hitLayout.noteClose.x + hitLayout.noteClose.width && pointerY >= hitLayout.noteClose.y && pointerY <= hitLayout.noteClose.y + hitLayout.noteClose.height) {216+ note = null; feedback = null; return { ...view(), event: { type: 'notes-close' } };217+ }215218 if (hitLayout.noteCard && (pointerX < hitLayout.noteCard.x || pointerX > hitLayout.noteCard.x + hitLayout.noteCard.width || pointerY < hitLayout.noteCard.y || pointerY > hitLayout.noteCard.y + hitLayout.noteCard.height)) {216219 note = null; feedback = null; return { ...view(), event: { type: 'notes-close' } };217220 }
@@ -235,26 +238,26 @@ export function createWorkshopScene(options = {}) {
235238 const width = dimensions.width || ctx.canvas?.width || 800;236239 const height = dimensions.height || ctx.canvas?.height || 600;237240 const compact = width < 600;241+ const coarsePointer = dimensions.coarsePointer ?? globalThis.matchMedia?.('(pointer: coarse)')?.matches ?? false;242+ const noKeycaps = width < 760 || coarsePointer;238243 ctx.fillStyle = colors.stage; ctx.fillRect(0, 0, width, height);239244 const margin = compact ? 24 : 48;240245 drawText(ctx, 'WORKSHOP', margin, compact ? 30 : 48, 13, colors.muted, 'left', 800);241246 if (compact) {242247 drawText(ctx, 'Packs of proofs', margin, 76, 24, colors.fg, 'left', 850);243248 drawText(ctx, "you've added", margin, 105, 24, colors.fg, 'left', 850);244- let hintX = width - margin;245- for (const [key, verb] of [['ESC', 'back'], ['ENTER', 'select']]) hintX = drawKeyHint(ctx, key, verb, hintX, 145, colors);246- hintX = width - margin;247- for (const [key, verb] of [['←→', 'action'], ['↑↓', 'choose']]) hintX = drawKeyHint(ctx, key, verb, hintX, 173, colors);248249 } else {249250 drawText(ctx, "Packs of proofs you've added", margin, 86, 28, colors.fg, 'left', 850);250- let hintX = width - margin;251- for (const [key, verb] of [['ESC', 'back'], ['ENTER', 'select'], ['←→', 'action'], ['↑↓', 'choose']]) hintX = drawKeyHint(ctx, key, verb, hintX, 48, colors);251+ if (!noKeycaps) {252+ let hintX = width - margin;253+ for (const [key, verb] of [['ESC', 'back'], ['ENTER', 'select'], ['←→', 'action'], ['↑↓', 'choose']]) hintX = drawKeyHint(ctx, key, verb, hintX, 48, colors);254+ }252255 }253256 const top = compact ? 204 : 130;254257 const rowHeight = compact ? 120 : Math.max(62, Math.min(84, (height - top - 42) / Math.max(rows().length, 1)));255258 const visibleCount = Math.max(1, Math.floor((height - top - 20) / rowHeight));256259 const firstRow = Math.min(Math.max(0, rowIndex - visibleCount + 1), Math.max(0, rows().length - visibleCount));257- hitLayout = { width, height, rows: [], noteCopy: { x: width, y: height, height: 0 }, noteCard: null };260+ hitLayout = { width, height, rows: [], noteCopy: { x: width, y: height, height: 0 }, noteClose: null, noteCard: null };258261 rows().forEach((row, index) => {259262 if (index < firstRow || index >= firstRow + visibleCount) return;260263 const y = top + (index - firstRow) * rowHeight; const selected = index === rowIndex;
@@ -311,6 +314,7 @@ export function createWorkshopScene(options = {}) {
311314 const noteX = compact ? 24 : 48; const noteY = compact ? Math.max(24, (height - noteHeight) / 2) : height - noteHeight - 24;312315 hitLayout.noteCard = { x: noteX, y: noteY, width: noteWidth, height: noteHeight };313316 hitLayout.noteCopy = { x: noteX + noteWidth * 0.52, y: noteY + noteHeight - 38, height: 34 };317+ hitLayout.noteClose = { x: noteX + 12, y: noteY + noteHeight - 38, width: 82, height: 34 };314318 const noteColor = note.kind === 'warning' ? colors.PUSH : colors.YOU;315319 if (compact) { ctx.fillStyle = 'rgba(0,0,0,.72)'; ctx.fillRect(0, 0, width, height); }316320 ctx.fillStyle = colors.raised; ctx.fillRect(noteX, noteY, noteWidth, noteHeight); ctx.strokeStyle = noteColor; ctx.strokeRect(noteX, noteY, noteWidth, noteHeight);
@@ -318,7 +322,7 @@ export function createWorkshopScene(options = {}) {
318322 wrappedNotes.forEach((error, index) => {319323 ctx.font = `600 10px ${FONT}`; ctx.fillStyle = colors.fg; ctx.textAlign = 'left'; ctx.textBaseline = 'middle'; ctx.fillText(error, noteX + 20, noteY + 44 + index * noteLineHeight);320324 });321- drawText(ctx, compact ? 'ESC CLOSE' : 'ESC CLOSE', noteX + 20, noteY + noteHeight - 18, 10, colors.muted, 'left', 800);325+ drawText(ctx, noKeycaps ? 'CLOSE' : 'ESC CLOSE', noteX + 20, noteY + noteHeight - 18, 10, colors.muted, 'left', 800);322326 drawText(ctx, `${note.remaining ? `AND ${note.remaining} MORE | ` : ''}COPY DETAILS`, noteX + noteWidth - 20, noteY + noteHeight - 18, 10, noteColor, 'right', 800);323327 } else if (feedback) drawText(ctx, feedback.message, 48, height - 28, 11, feedback.kind === 'error' ? colors.YOU : feedback.kind === 'warning' ? colors.PUSH : colors.WIN, 'left', 800);324328 if (confirm) {
M tests/workshop.test.js +20 -0
@@ -134,6 +134,10 @@ test('warning notes add an amber Workshop chip and NOTES action', async () => {
134134 const closed = scene.hit(2, 2);135135 assert.equal(closed.event.type, 'notes-close');136136 await scene.handleInput('enter');137+ const closeTarget = scene.hitLayout.noteClose;138+ const closedByButton = scene.hit(closeTarget.x + 4, closeTarget.y + 4);139+ assert.equal(closedByButton.event.type, 'notes-close');140+ await scene.handleInput('enter');137141 assert.equal(await scene.handleInput('c'), true);138142 assert.match(copied, /SHUT/);139143 scene.handleInput('escape');
@@ -192,6 +196,8 @@ test('mobile Workshop keeps header and pack actions within the viewport while se
192196 assert.ok(labels.includes('Packs of proofs'));193197 assert.ok(labels.includes("you've added"));194198 assert.ok(!labels.includes("Packs of proofs you've added"));199+ assert.equal(labels.includes('ENTER'), false);200+ assert.equal(labels.includes('ESC'), false);195201 for (const row of scene.hitLayout.rows) {196202 for (const action of row.actions) {197203 assert.ok(action.x >= 24 && action.x + action.width <= 366);
@@ -224,6 +230,20 @@ test('header hints render keycaps before lowercase verbs', () => {
224230 assert.equal(labels.some(label => String(label).includes('[')), false);225231 });226232 233+test('Workshop hides header keycaps below 760px and for coarse pointers', () => {234+ const scene = createWorkshopScene({ packs: [makePack('garden')] });235+ const medium = canvasContext();236+ scene.draw(medium, { width: 700, height: 600 });237+ const mediumLabels = medium.calls.filter(call => call[0] === 'fillText').map(call => call[1]);238+ assert.equal(mediumLabels.includes('ENTER'), false);239+ assert.equal(mediumLabels.includes('ESC'), false);240+ const coarse = canvasContext();241+ scene.draw(coarse, { width: 800, height: 600, coarsePointer: true });242+ const coarseLabels = coarse.calls.filter(call => call[0] === 'fillText').map(call => call[1]);243+ assert.equal(coarseLabels.includes('ENTER'), false);244+ assert.equal(coarseLabels.includes('ESC'), false);245+});246+227247 test('uses deck copy, resolves COPY OF to a source name, and uses sentence case errors', () => {228248 const scene = createWorkshopScene({ packs: [229249 makePack('source'),