Put bets on the felt as chips instead of numbers in a box
The seat puck showed a green figure that meant "what this player just bet" during a street and "what they've put in this hand" between streets — two different numbers wearing the same badge, sitting directly above their stack size. Easy to misread, and nothing on the table looked like money. Bets now sit on the felt between their player and the pot, as a chip coloured by the largest denomination in the chip legend that fits the amount: a $30 bet shows green against a $25 chip, a $2 bet shows white. When a street closes the chips come off the felt and the pot badge keeps the money, the way they'd be swept in. The puck's figure now always means the same thing — this player's total for the hand. On a phone the bet clings to its own puck instead of sitting on its own ring. That isn't a preference: sweeping every radius between the seat ring and the pot badge at 390px showed each one collides with either the pucks or the pot, because the felt has no room for a third ring at that size. Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
This commit is contained in:
@@ -31,6 +31,7 @@
|
|||||||
<span id="potBadgeAmount">$0</span>
|
<span id="potBadgeAmount">$0</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="betsLayer" aria-hidden="true"></div>
|
||||||
<div id="seatsLayer"></div>
|
<div id="seatsLayer"></div>
|
||||||
</div>
|
</div>
|
||||||
<p id="tableHint" class="tableHint"></p>
|
<p id="tableHint" class="tableHint"></p>
|
||||||
|
|||||||
@@ -223,6 +223,68 @@ body {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Bets on the felt ---------- */
|
||||||
|
|
||||||
|
/* Shares the seat ring's box exactly, so a bet always lands on the spoke between its
|
||||||
|
player and the pot. When the seat ring gets inset on a phone, this moves with it —
|
||||||
|
measured the alternative, and a bets ring on its own geometry ends up either behind
|
||||||
|
the pucks or on top of the pot badge. */
|
||||||
|
#betsLayer {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.betChip {
|
||||||
|
position: absolute;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.28rem;
|
||||||
|
padding: 0.1rem 0.42rem 0.1rem 0.2rem;
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.5);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reads as a chip seen from above: a lighter top-left, a rim, and the notches a
|
||||||
|
casino chip has around its edge. */
|
||||||
|
.betChipDisc {
|
||||||
|
width: 13px;
|
||||||
|
height: 13px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.45);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 1px rgba(255, 255, 255, 0.45),
|
||||||
|
inset 0 -1px 2px rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.betChipAmount {
|
||||||
|
font-size: 0.62rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #f0f0f0;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clinging to a puck rather than sitting on its own ring — the phone layout. */
|
||||||
|
.betChipAnchored {
|
||||||
|
left: 50%;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.betChipAbove {
|
||||||
|
top: -11px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.betChipBelow {
|
||||||
|
top: auto;
|
||||||
|
bottom: -11px;
|
||||||
|
transform: translate(-50%, 50%);
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- Seats (compact pucks on the table) ---------- */
|
/* ---------- Seats (compact pucks on the table) ---------- */
|
||||||
|
|
||||||
.seat {
|
.seat {
|
||||||
@@ -1734,7 +1796,8 @@ body.hasActionBar #toastHost {
|
|||||||
|
|
||||||
/* Keeps every puck inside the viewport: the ring is drawn within this box,
|
/* Keeps every puck inside the viewport: the ring is drawn within this box,
|
||||||
so half a puck of padding is exactly enough to stop the overhang. */
|
so half a puck of padding is exactly enough to stop the overhang. */
|
||||||
#seatsLayer {
|
#seatsLayer,
|
||||||
|
#betsLayer {
|
||||||
inset: 26px 44px;
|
inset: 26px 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,13 +69,85 @@ const Render = {
|
|||||||
// ---- Seats around the table (compact pucks) ----
|
// ---- Seats around the table (compact pucks) ----
|
||||||
|
|
||||||
seatPosition(index, count) {
|
seatPosition(index, count) {
|
||||||
|
return this.positionOnRing(index, count, 44, 42);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Bets sit on the felt between their player and the pot, the way chips actually
|
||||||
|
// do — closer in than the seats, on the same spoke. The bets layer is inset to
|
||||||
|
// match the seat ring (see the CSS), so this one radius holds at every size.
|
||||||
|
betPosition(index, count) {
|
||||||
|
return this.positionOnRing(index, count, 27, 25);
|
||||||
|
},
|
||||||
|
|
||||||
|
positionOnRing(index, count, rx, ry) {
|
||||||
const angle = -90 + (360 / count) * index;
|
const angle = -90 + (360 / count) * index;
|
||||||
const radians = (angle * Math.PI) / 180;
|
const radians = (angle * Math.PI) / 180;
|
||||||
const rx = 44;
|
return {
|
||||||
const ry = 42;
|
left: 50 + rx * Math.cos(radians),
|
||||||
const left = 50 + rx * Math.cos(radians);
|
top: 50 + ry * Math.sin(radians),
|
||||||
const top = 50 + ry * Math.sin(radians);
|
};
|
||||||
return { left, top };
|
},
|
||||||
|
|
||||||
|
// Colour a bet by the biggest chip that fits it, so a table with a chip legend
|
||||||
|
// gets bets that look like the chips actually being pushed out.
|
||||||
|
chipColorFor(amount) {
|
||||||
|
const legend = [...(this.state.chipLegend || [])]
|
||||||
|
.filter(chip => Number(chip.value) > 0)
|
||||||
|
.sort((a, b) => Number(b.value) - Number(a.value));
|
||||||
|
const match = legend.find(chip => amount >= Number(chip.value));
|
||||||
|
return (match || legend[legend.length - 1] || {}).color || "#8a8a8a";
|
||||||
|
},
|
||||||
|
|
||||||
|
// ---- Bets on the felt ----
|
||||||
|
|
||||||
|
makeBetChip(bet) {
|
||||||
|
const chip = document.createElement("div");
|
||||||
|
chip.className = "betChip";
|
||||||
|
|
||||||
|
const disc = document.createElement("span");
|
||||||
|
disc.className = "betChipDisc";
|
||||||
|
disc.style.background = this.chipColorFor(bet);
|
||||||
|
|
||||||
|
const amount = document.createElement("span");
|
||||||
|
amount.className = "betChipAmount";
|
||||||
|
amount.textContent = Money.format(bet);
|
||||||
|
|
||||||
|
chip.appendChild(disc);
|
||||||
|
chip.appendChild(amount);
|
||||||
|
return chip;
|
||||||
|
},
|
||||||
|
|
||||||
|
// A phone's felt has no room for a third ring: measured every radius between the
|
||||||
|
// seats and the pot badge and each one collides with one or the other. So on a
|
||||||
|
// narrow screen the bet clings to its own puck instead — same chip, no ring —
|
||||||
|
// sitting on whichever side faces the middle of the table.
|
||||||
|
renderBets() {
|
||||||
|
const layer = document.getElementById("betsLayer");
|
||||||
|
if (!layer) return;
|
||||||
|
layer.innerHTML = "";
|
||||||
|
|
||||||
|
const anchorToSeat = window.matchMedia("(max-width: 700px)").matches;
|
||||||
|
|
||||||
|
this.state.seats.forEach((player, index) => {
|
||||||
|
const bet = Betting.committed(player);
|
||||||
|
if (bet <= 0) return;
|
||||||
|
|
||||||
|
const chip = this.makeBetChip(bet);
|
||||||
|
const pos = this.betPosition(index, this.state.seatCount);
|
||||||
|
|
||||||
|
if (anchorToSeat) {
|
||||||
|
const seat = document.querySelector(`.seat[data-seat-index="${index}"]`);
|
||||||
|
if (!seat) return;
|
||||||
|
const seatPos = this.seatPosition(index, this.state.seatCount);
|
||||||
|
chip.classList.add("betChipAnchored", seatPos.top > 50 ? "betChipAbove" : "betChipBelow");
|
||||||
|
seat.appendChild(chip);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
chip.style.left = `${pos.left}%`;
|
||||||
|
chip.style.top = `${pos.top}%`;
|
||||||
|
layer.appendChild(chip);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
puckLabel(player, seatIndex) {
|
puckLabel(player, seatIndex) {
|
||||||
@@ -118,6 +190,8 @@ const Render = {
|
|||||||
|
|
||||||
layer.appendChild(node);
|
layer.appendChild(node);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.renderBets();
|
||||||
},
|
},
|
||||||
|
|
||||||
isRedSuit(card) {
|
isRedSuit(card) {
|
||||||
@@ -168,11 +242,13 @@ const Render = {
|
|||||||
span.textContent = this.formatCard(card);
|
span.textContent = this.formatCard(card);
|
||||||
cardsEl.appendChild(span);
|
cardsEl.appendChild(span);
|
||||||
});
|
});
|
||||||
// What's in front of them this street reads as the live number during betting;
|
// Always this player's total for the hand. What they've just pushed out on this
|
||||||
// between hands it falls back to their total for the hand.
|
// street is shown as a chip on the felt instead — the puck used to flip between
|
||||||
const streetBet = Betting.committed(player);
|
// the two meanings depending on the moment, which is a bad thing for a number
|
||||||
const total = streetBet > 0
|
// sitting next to a stack size to do.
|
||||||
? streetBet
|
const handTotal = Betting.handTotal(player);
|
||||||
|
const total = handTotal > 0
|
||||||
|
? handTotal
|
||||||
: HoldemState.computeContribution(player, this.state.chipLegend, this.state.basePotAmount);
|
: HoldemState.computeContribution(player, this.state.chipLegend, this.state.basePotAmount);
|
||||||
node.querySelector(".seatPuckTotal").textContent = total > 0 ? Money.format(total) : "";
|
node.querySelector(".seatPuckTotal").textContent = total > 0 ? Money.format(total) : "";
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user