Match the original's choice layouts: vertical YES/NO and lists, full PC menu [no-card]

YES/NO and battle lists are single columns moved with Up/Down, the battle
menu cursor follows the 2x2 grid, and the dialogue menu shows every entry.
Not pushed yet: awaiting manual testing (#64, #65).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Leo CompeandClaude Sonnet 5.5 committed 2026-10-04 01:19:43 +02:00
1 parent ce8129703f
commit 5546e575f4
3 files changed
+79 -7

No files matched your search

+56 -3
View File
@@ -143,6 +143,7 @@ function showChoice(choice: Choice | undefined) {
const key = choice ? JSON.stringify(choice) : "";
if (container.dataset.choice === key) return;
container.dataset.choice = key;
container.dataset.kind = choice?.kind ?? "";
const button = (
label: string,
pressed: boolean | undefined,
@@ -294,6 +295,10 @@ function showChoice(choice: Choice | undefined) {
list,
button("CANCEL", undefined, () => runtime.choose(-1)),
);
// On a small screen the box scrolls; keep the highlighted entry in view.
list
.querySelector('[aria-selected="true"]')
?.scrollIntoView({ block: "nearest" });
}
}
/** The naming screen as a text field and OK: an empty name keeps the
@@ -627,6 +632,42 @@ function battleButtons() {
),
];
}
/** The enabled button next to `from` in the arrow's direction as drawn, so
* the cursor always follows the layout: a column moves with up and down, a
* row with left and right, and a grid with all four. */
function adjacentButton(
buttons: HTMLButtonElement[],
from: number,
arrow: "up" | "down" | "left" | "right",
): number | undefined {
const origin = buttons[from]?.getBoundingClientRect();
if (!origin) return undefined;
const vertical = arrow === "up" || arrow === "down";
let best: number | undefined;
let bestScore = Infinity;
buttons.forEach((candidate, index) => {
if (index === from) return;
const box = candidate.getBoundingClientRect();
const gap =
arrow === "up"
? origin.top - box.bottom
: arrow === "down"
? box.top - origin.bottom
: arrow === "left"
? origin.left - box.right
: box.left - origin.right;
if (gap < -1) return;
const offset = vertical
? Math.abs(box.left + box.width / 2 - (origin.left + origin.width / 2))
: Math.abs(box.top + box.height / 2 - (origin.top + origin.height / 2));
const score = Math.max(gap, 0) + offset * 2;
if (score < bestScore) {
bestScore = score;
best = index;
}
});
return best;
}
function highlightBattleChoice() {
battleButtons().forEach((button, index) => {
button.classList.toggle("battle-selected", index === battleCursor);
@@ -664,9 +705,14 @@ function handleBattleInput(button: Button): boolean {
buttons[battleCursor]?.click();
return true;
}
if (["up", "down", "left", "right"].includes(button)) {
const delta = button === "up" || button === "left" ? -1 : 1;
battleCursor = (battleCursor + delta + buttons.length) % buttons.length;
if (
button === "up" ||
button === "down" ||
button === "left" ||
button === "right"
) {
battleCursor =
adjacentButton(buttons, battleCursor, button) ?? battleCursor;
if (yesNoPrompt(world.battle.prompt))
world.battle.selected = battleCursor === 0 ? 1 : 0;
highlightBattleChoice();
@@ -1012,6 +1058,13 @@ function showBattle(world: GameWorld) {
button("Continue ▶", () => runtime.advanceDialogue(), { wide: true }),
);
}
// FIGHT/PKMN/PACK/RUN is the original's 2x2 grid; every other choice is a
// vertical list, moved with Up and Down as it is drawn.
const grid =
(prompt?.kind === "action" && !showMoves && !showPack) ||
prompt?.kind === "stats";
menu.dataset.layout = grid ? "grid" : "list";
menu.dataset.kind = prompt?.kind ?? "";
menu.replaceChildren(...buttons);
battleCursor = Math.min(
battleCursor,