- commit
- 76f7a79a973e2e965f5070db857eac503d160e02
- parent
- f7fafa617b50d26f2057720b13a920041301f482
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 121 | +++++++++++++++++++++--------- |
@@ -149,24 +149,50 @@ export function createStage(options = {}) {
149149 }150150 function drawKeycap(ctx, key, label, x, y, align = "left") {151151 ctx.save();152- ctx.textAlign = align;153152 ctx.textBaseline = "middle";154153 ctx.font = "800 10px Recursive, monospace";155154 const keyWidth = Math.max(24, ctx.measureText(key).width + 12);156- const left = align === "right" ? x - keyWidth : x;155+ ctx.font = "600 10px Recursive, monospace";156+ const labelWidth = ctx.measureText(label).width;157+ const left = align === "right" ? x - keyWidth - 8 - labelWidth : x;157158 ctx.fillStyle = "#000000";158159 ctx.fillRect(left + 4, y - 6, keyWidth, 20);159160 ctx.fillStyle = "#F2EEE6";160161 ctx.fillRect(left, y - 10, keyWidth, 20);161162 ctx.fillStyle = "#0F0E0D";162- ctx.fillText(key, align === "right" ? x - 6 : x + 6, y + 1);163+ ctx.textAlign = "left";164+ ctx.font = "800 10px Recursive, monospace";165+ ctx.fillText(key, left + 6, y + 1);163166 ctx.font = "600 10px Recursive, monospace";164167 ctx.fillStyle = "#8E877C";165- ctx.fillText(166- label,167- align === "right" ? left - 8 : left + keyWidth + 8,168- y + 1,169- );168+ ctx.fillText(label, left + keyWidth + 8, y + 1);169+ ctx.restore();170+ }171+ function drawInlineActions(ctx, items, centerX, y) {172+ ctx.save();173+ ctx.font = "800 10px Recursive, monospace";174+ const metrics = items.map(({ key, label }) => {175+ const keyWidth = Math.max(24, ctx.measureText(key).width + 12);176+ ctx.font = "600 10px Recursive, monospace";177+ const labelWidth = ctx.measureText(label).width;178+ ctx.font = "800 10px Recursive, monospace";179+ return {180+ key,181+ label,182+ keyWidth,183+ labelWidth,184+ width: keyWidth + 8 + labelWidth,185+ };186+ });187+ const gap = 18;188+ const total =189+ metrics.reduce((sum, item) => sum + item.width, 0) +190+ gap * (metrics.length - 1);191+ let x = centerX - total / 2;192+ for (const item of metrics) {193+ drawKeycap(ctx, item.key, item.label, x, y);194+ x += item.width + gap;195+ }170196 ctx.restore();171197 }172198 function drawStackedAction(ctx, key, label, centerX, y) {@@ -184,8 +210,8 @@ export function createStage(options = {}) {
184210 ctx.fillText(label, centerX, y + 27);185211 ctx.restore();186212 }187- function drawTouchControls(ctx, width, height) {188- if (width > 760) return;213+ function drawTouchControls(ctx, width, height, force = false) {214+ if (width > 760 && !force) return;189215 const y = height - 30;190216 const controls = [191217 { label: "UNDO", key: "↶", x: 18 },@@ -253,7 +279,7 @@ export function createStage(options = {}) {
253279 ctx.fillStyle = fills[role] || fills.noun;254280 ctx.fillRect(x, y, width, size * 1.5);255281 }256- ctx.fillStyle = "#0F0E0D";282+ ctx.fillStyle = role === "IS" || role === "AND" ? "#F2EEE6" : "#0F0E0D";257283 ctx.textAlign = "center";258284 ctx.textBaseline = "middle";259285 ctx.fillText(text, x + width / 2, y + size * 0.78);@@ -524,11 +550,15 @@ export function createStage(options = {}) {
524550 h - 60,525551 );526552 }527- const proofKeyY = w > 760 ? h - 26 : h - 72;528- drawKeycap(ctx, "Z", "undo", w - 290, proofKeyY);529- drawKeycap(ctx, "R", "restart", w - 185, proofKeyY);530- drawKeycap(ctx, "ESC", "menu", w - 32, proofKeyY, "right");531- drawTouchControls(ctx, w, h);553+ const coarsePointer =554+ globalThis.matchMedia?.("(pointer: coarse)").matches ?? false;555+ if (w > 760 && !coarsePointer) {556+ const proofKeyY = h - 26;557+ drawKeycap(ctx, "Z", "undo", w - 290, proofKeyY);558+ drawKeycap(ctx, "R", "restart", w - 185, proofKeyY);559+ drawKeycap(ctx, "ESC", "menu", w - 32, proofKeyY, "right");560+ }561+ drawTouchControls(ctx, w, h, coarsePointer);532562 } else if (scene === "index" && indexLayout && indexNavigator) {533563 indexGeometry = drawIndex(ctx, indexLayout, indexNavigator, {534564 availableWidth: w,@@ -627,9 +657,15 @@ export function createStage(options = {}) {
627657 h - 86,628658 );629659 }630- drawKeycap(ctx, "↑↓", "choose", 32, h - 46);631- drawKeycap(ctx, "ENTER", "select", w / 2 - 4, h - 46, "right");632- drawKeycap(ctx, "ESC", "back", w - 32, h - 46, "right");660+ const coarsePointer =661+ globalThis.matchMedia?.("(pointer: coarse)").matches ?? false;662+ if (w > 760 && !coarsePointer) {663+ drawKeycap(ctx, "↑↓", "choose", 32, h - 46);664+ drawKeycap(ctx, "ENTER", "select", w / 2 - 4, h - 46, "right");665+ drawKeycap(ctx, "ESC", "back", w - 32, h - 46, "right");666+ } else {667+ drawTouchControls(ctx, w, h, coarsePointer);668+ }633669 } else if (scene === "win") {634670 if (boardState) {635671 const cell = Math.min(@@ -669,15 +705,16 @@ export function createStage(options = {}) {
669705 back * Math.pow(winProgress - 1, 2);670706 const winEase = Math.max(0, Math.min(1.1, rawBackEase));671707 const cardY = Math.max(120, h * 0.25) + (1 - winEase) * 80;708+ const cardHeight = 290;672709 ctx.fillStyle = "#191816";673- ctx.fillRect(cardX + 10, cardY + 10, cardWidth, 250);710+ ctx.fillRect(cardX + 10, cardY + 10, cardWidth, cardHeight);674711 ctx.fillStyle = "#F2EEE6";675- ctx.fillRect(cardX, cardY, cardWidth, 250);712+ ctx.fillRect(cardX, cardY, cardWidth, cardHeight);676713 ctx.fillStyle = "#0F0E0D";677- ctx.fillRect(cardX + 2, cardY + 2, cardWidth - 4, 246);714+ ctx.fillRect(cardX + 2, cardY + 2, cardWidth - 4, cardHeight - 4);678715 ctx.strokeStyle = "#2EE6A8";679716 ctx.lineWidth = 2;680- ctx.strokeRect(cardX, cardY, cardWidth, 250);717+ ctx.strokeRect(cardX, cardY, cardWidth, cardHeight);681718 ctx.textAlign = "left";682719 ctx.fillStyle = "#8E877C";683720 ctx.font = "600 10px Recursive, monospace";@@ -727,16 +764,20 @@ export function createStage(options = {}) {
727764 break;728765 headlineSize -= 2;729766 } while (headlineSize > 22);730- ctx.fillText(headline, w / 2, cardY + 154);767+ const headlineY = cardY + 154;768+ ctx.fillText(headline, w / 2, headlineY);731769 ctx.fillStyle = "#8E877C";732- ctx.font = "600 12px Recursive, monospace";770+ ctx.font = "600 16px Recursive, monospace";771+ const progressY = headlineY + headlineSize / 2 + 16 + 8;733772 ctx.fillText(734773 `${boardState?.completedCount ?? 0} of ${boardState?.proofTotal ?? 9} proofs complete`,735774 w / 2,736- cardY + 182,775+ progressY,737776 );777+ const coarsePointer =778+ globalThis.matchMedia?.("(pointer: coarse)").matches ?? false;738779 if (w <= 760) {739- const actionY = cardY + 208;780+ const actionY = cardY + 250;740781 drawStackedAction(741782 ctx,742783 "ENTER",@@ -752,19 +793,20 @@ export function createStage(options = {}) {
752793 cardX + (cardWidth * 5) / 6,753794 actionY,754795 );755- } else {756- drawKeycap(ctx, "ENTER", "continue", cardX + 32, cardY + 220);757- drawKeycap(ctx, "Z", "undo", w / 2 - 4, cardY + 220, "right");758- drawKeycap(796+ } else if (!coarsePointer) {797+ drawInlineActions(759798 ctx,760- "R",761- "replay",762- cardX + cardWidth - 32,763- cardY + 220,764- "right",799+ [800+ { key: "ENTER", label: "continue" },801+ { key: "Z", label: "undo" },802+ { key: "R", label: "replay" },803+ ],804+ w / 2,805+ cardY + 250,765806 );766807 }767- if (w <= 760) drawTouchControls(ctx, w, h);808+ if (w <= 760 || coarsePointer)809+ drawTouchControls(ctx, w, h, coarsePointer);768810 ctx.textAlign = "left";769811 } else {770812 const spec = SCENES[scene] || proofScene;@@ -1093,7 +1135,10 @@ export function createStage(options = {}) {
10931135 if (scene === "title") {10941136 const titleHit = title?.hit?.(x, y);10951137 if (titleHit?.kind === "workshop" || titleHit?.kind === "skip") return;1096- if (titleHit?.kind === "menu") { show("pause"); return; }1138+ if (titleHit?.kind === "menu") {1139+ show("pause");1140+ return;1141+ }10971142 }10981143 if (scene === "index" && indexGeometry?.nodes?.length) {10991144 const workshopHit = indexGeometry.footerActions?.workshop;