games: the casino moves out, and gets a clock of its own

The tables were living in the news app's shell: Pete's face in the header
and the footer, the channel nav, search, the reader, the weather canvas,
the PWA. A casino is not a news page with a felt on it.

So it gets its own layout. What carries over is the design language — the
four palette vars, Fredoka/Nunito, the fat rounded cards, the dropped
shadow. What doesn't is every control it has no use for. gamesPage stops
embedding the news pageData, which is what keeps the furniture from
drifting back one convenient field at a time.

It keeps a clock, but tells a different joke with it: Casinopolis by day,
Casino Night Zone from six, palette and felt and the sign over the door all
changing together. The rule lives in roomAt() for the first paint and again
in the browser, so a player abroad gets their own evening.

And the cards are cards now — corner indices in both corners, the bottom
one upside down as printed, pips on the three-by-seven grid every real deck
has used for four hundred years, courts as a letter with the suit over each
shoulder. Driven in a real browser, both rooms, dealt through to a payout.
This commit is contained in:
prosolis
2026-07-13 23:40:33 -07:00
parent c69fbb63db
commit 8ec13eab5b
10 changed files with 538 additions and 52 deletions

View File

@@ -73,18 +73,104 @@
return wrap;
}
// Pip layouts, the way a real deck lays them out — which is not "N suits in a
// row". Each entry is [column, row] on a 3×7 grid: three columns, seven rows,
// the same skeleton every printed deck has used for about four hundred years.
// A pip below the halfway line is printed upside down, so it does that here.
//
// Sevens and tens have a pip that sits *between* two rows, so those span a pair
// of rows and centre themselves across the pair.
var PIPS = {
"A": [[2, 4]],
"2": [[2, 1], [2, 7]],
"3": [[2, 1], [2, 4], [2, 7]],
"4": [[1, 1], [3, 1], [1, 7], [3, 7]],
"5": [[1, 1], [3, 1], [2, 4], [1, 7], [3, 7]],
"6": [[1, 1], [3, 1], [1, 4], [3, 4], [1, 7], [3, 7]],
"7": [[1, 1], [3, 1], [2, "2/4"], [1, 4], [3, 4], [1, 7], [3, 7]],
"8": [[1, 1], [3, 1], [2, "2/4"], [1, 4], [3, 4], [2, "4/6"], [1, 7], [3, 7]],
"9": [[1, 1], [3, 1], [1, 3], [3, 3], [2, 4], [1, 5], [3, 5], [1, 7], [3, 7]],
"10": [[1, 1], [3, 1], [2, "2/4"], [1, 3], [3, 3], [1, 5], [3, 5], [2, "4/6"], [1, 7], [3, 7]],
};
var COURT = { "J": "Jack", "Q": "Queen", "K": "King" };
function corner(face, where) {
var el = document.createElement("span");
el.className = "pete-card-corner pete-card-corner-" + where;
var r = document.createElement("span");
r.className = "pete-card-corner-rank";
r.textContent = face.rank;
var s = document.createElement("span");
s.className = "pete-card-corner-suit";
s.textContent = face.suit;
el.appendChild(r);
el.appendChild(s);
return el;
}
function pip(face, col, row) {
var el = document.createElement("span");
el.className = "pete-card-pip";
el.textContent = face.suit;
el.style.gridColumn = String(col);
// A row given as "2/4" spans that pair and centres between them.
el.style.gridRow = String(row);
// The bottom half of a real card is printed the other way up.
var mid = typeof row === "number" ? row > 4 : row === "4/6";
if (mid) el.dataset.flip = "1";
return el;
}
// paintFace draws an actual playing card: index in two opposite corners, and
// either the pips or a court figure in the middle. The dealer's cards and
// yours use the same face, because they came out of the same shoe.
function paintFace(front, face) {
front.dataset.red = face.red ? "1" : "0";
front.innerHTML = "";
var rank = document.createElement("span");
rank.className = "pete-card-rank";
rank.textContent = face.rank;
var suit = document.createElement("span");
suit.className = "pete-card-suit";
suit.textContent = face.suit;
front.appendChild(rank);
front.appendChild(suit);
front.setAttribute("aria-label", face.label);
front.appendChild(corner(face, "tl"));
var body = document.createElement("span");
if (COURT[face.rank]) {
// Court cards: the letter, big, with the suit sitting over each shoulder.
// No portrait — a drawn face would fight the room, and this reads instantly.
body.className = "pete-card-court";
var top = document.createElement("span");
top.className = "pete-card-court-suit";
top.textContent = face.suit;
var letter = document.createElement("span");
letter.className = "pete-card-court-letter";
letter.textContent = face.rank;
var bot = document.createElement("span");
bot.className = "pete-card-court-suit";
bot.dataset.flip = "1";
bot.textContent = face.suit;
body.appendChild(top);
body.appendChild(letter);
body.appendChild(bot);
} else {
body.className = "pete-card-pips";
// An ace gets one big pip in the middle, the way an ace does.
if (face.rank === "A") body.dataset.ace = "1";
var spots = PIPS[face.rank] || [];
for (var i = 0; i < spots.length; i++) {
body.appendChild(pip(face, spots[i][0], spots[i][1]));
}
}
front.appendChild(body);
front.appendChild(corner(face, "br"));
front.setAttribute("aria-label", ariaFor(face));
}
// "A♠" is not something a screen reader should be asked to pronounce.
function ariaFor(face) {
var SUITS = { "♠": "spades", "♥": "hearts", "♦": "diamonds", "♣": "clubs" };
var name = COURT[face.rank] || (face.rank === "A" ? "Ace" : face.rank);
var suit = SUITS[face.suit];
return suit ? name + " of " + suit : face.label;
}
// turnOver flips a face-down card up, now that we've been told what it is.