Add double-tap and press-and-hold shortcuts to the seats

Folding someone meant opening their editor, finding the Fold box, ticking it,
and dismissing the sheet — four interactions for the thing that happens most
often in a hand.

Double-tap a seat to fold, and again to put them back. Press and hold for a
quick menu of everything else you do to one player: fold, all-in, sit out or
deal in, hand them the button, or open the full editor. Both are undoable the
same way the header buttons are, because a gesture is easier to fire by
accident than a button.

The single tap now waits out the double-tap window before opening the editor —
a fifth of a second of latency on opening a form, in exchange for a fold that
doesn't also fling a sheet up over the table. A press that moves is a drag, so
the hold is cancelled and swapping seats still works, and the puck shrinks
slightly while held so a press that's being counted looks different from one
that's being ignored.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
2026-08-31 21:54:56 -06:00
co-authored by Claude Sonnet 5
parent 0759d89d13
commit dabf1100c9
5 changed files with 354 additions and 13 deletions
@@ -1,9 +1,62 @@
document.addEventListener("DOMContentLoaded", () => {
const state = HoldemState.load();
// A gesture on a seat is a shortcut for something the dealer could do the long
// way, so each one is undoable the same way the header buttons are.
const seatAction = (index, message, mutate) => {
const snapshot = JSON.parse(JSON.stringify(state));
mutate(state.seats[index], index);
HoldemState.save(state);
Render.renderAll();
Toast.show(message, {
actionLabel: "Undo",
onAction: () => {
Object.keys(state).forEach(key => delete state[key]);
Object.assign(state, snapshot);
HoldemState.save(state);
Render.renderAll();
Toast.show("Undone.", { duration: 2500 });
},
duration: 6000,
});
};
const nameOf = index => HoldemState.displayName(state.seats[index], index);
const hooks = {
save: s => HoldemState.save(s),
rerender: () => Render.renderAll(),
onSeatDoubleTap: index => {
const player = state.seats[index];
if (!player.inHand) return;
if (player.folded) {
seatAction(index, `${nameOf(index)} is back in the hand.`, p => { p.folded = false; });
return;
}
seatAction(index, `${nameOf(index)} folds.`, () => {
if (state.actingSeatIndex === index) Betting.fold(state, index);
else player.folded = true;
});
},
onSeatLongPress: index => SeatMenu.open(state, seatMenuHooks, index),
};
const seatMenuHooks = {
fold: index => hooks.onSeatDoubleTap(index),
unfold: index => seatAction(index, `${nameOf(index)} is back in the hand.`, p => { p.folded = false; }),
allIn: index => seatAction(index, `${nameOf(index)} is all-in.`, () => Betting.allIn(state, index)),
toggleDealtIn: index => {
const dealtIn = state.seats[index].inHand;
seatAction(index, dealtIn ? `${nameOf(index)} sits out.` : `${nameOf(index)} is dealt in.`, p => {
p.inHand = !dealtIn;
if (!p.inHand) p.folded = false;
});
},
makeDealer: index => seatAction(index, `Button on ${nameOf(index)}.`, () => { state.dealerSeatIndex = index; }),
editSeat: index => Render.toggleSeatEditor(index),
};
Render.mount(state, hooks);
@@ -144,6 +197,11 @@ document.addEventListener("DOMContentLoaded", () => {
document.getElementById("closeSeatEditorBtn").addEventListener("click", () => Render.closeSeatEditor());
document.getElementById("seatMenuCancelBtn").addEventListener("click", () => SeatMenu.close());
document.getElementById("seatMenuOverlay").addEventListener("click", e => {
if (e.target.id === "seatMenuOverlay") SeatMenu.close();
});
document.getElementById("cardPickerCloseBtn").addEventListener("click", () => CardPicker.close());
document.getElementById("cardPickerClearBtn").addEventListener("click", () => CardPicker.clearSlot());
document.getElementById("cardPickerOverlay").addEventListener("click", e => {