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,
+18 -2
View File
@@ -338,12 +338,18 @@ dialog h2 {
gap: 6px;
margin-top: 6px;
}
/* The original's YES/NO box is vertical, YES above NO, moved with Up/Down. */
.native-dialogue > .dialogue-choice[data-kind="yesno"] {
flex-direction: column;
flex-wrap: nowrap;
align-items: flex-end;
}
/* Every entry shows (the bedroom PC has six). The dialogue box grows upward
and scrolls only when the screen is too small. */
.dialogue-menu {
display: flex;
flex-direction: column;
gap: 4px;
max-height: 9.5em;
overflow-y: auto;
}
.dialogue-choice .mail-lines {
display: grid;
@@ -452,6 +458,16 @@ dialog h2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 6px;
}
/* Everything but the 2x2 battle menu is a vertical list, as in the original:
YES above NO, one move or item per row. */
.battle-menu[data-layout="list"] {
grid-template-columns: minmax(0, 1fr);
}
.battle-menu[data-kind="yesno"] button,
.battle-menu[data-kind="contestswitch"] button:not(.wide) {
justify-self: end;
min-width: 6em;
}
.battle-menu button {
padding: 6px 10px;
text-align: left;