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:
@@ -572,8 +572,8 @@ html[data-phase="night"] {
|
||||
two never fight over the same transform. */
|
||||
.pete-card {
|
||||
perspective: 700px;
|
||||
height: 6.4rem;
|
||||
width: 4.5rem;
|
||||
height: 7rem;
|
||||
width: 5rem;
|
||||
animation: pete-deal 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||||
}
|
||||
.pete-card-inner {
|
||||
@@ -597,8 +597,16 @@ html[data-phase="night"] {
|
||||
-webkit-backface-visibility: hidden;
|
||||
box-shadow: 0 3px 0 rgba(0,0,0,0.18), 0 6px 14px rgba(0,0,0,0.22);
|
||||
}
|
||||
/* A real card face: index in two opposite corners, pips or a court figure in
|
||||
the middle. Laid out as three rows — corner, body, corner — so the body gets
|
||||
whatever's left and the indices stay pinned where they're printed. */
|
||||
.pete-card-front {
|
||||
place-items: center;
|
||||
/* minmax(0,…) on the body row, or the pip grid's min-content height (seven
|
||||
rows of glyphs) wins the argument and pushes the bottom index off the
|
||||
card entirely. */
|
||||
grid-template-rows: auto minmax(0, 1fr) auto;
|
||||
overflow: hidden;
|
||||
padding: 0.2rem 0.28rem;
|
||||
background: #fdfaf2;
|
||||
border: 2px solid rgba(30,20,10,0.12);
|
||||
color: #2b2118;
|
||||
@@ -606,6 +614,53 @@ html[data-phase="night"] {
|
||||
line-height: 1;
|
||||
}
|
||||
.pete-card-front[data-red="1"] { color: #cc3d4a; }
|
||||
|
||||
/* The corner index: rank over suit, tight. The bottom-right one is the same
|
||||
thing upside down, because that's what makes a card readable from a fanned
|
||||
hand held either way up. */
|
||||
.pete-card-corner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
line-height: 0.95;
|
||||
}
|
||||
.pete-card-corner-tl { justify-self: start; }
|
||||
.pete-card-corner-br { justify-self: end; transform: rotate(180deg); }
|
||||
.pete-card-corner-rank { font-size: 0.9rem; font-weight: 700; }
|
||||
.pete-card-corner-suit { font-size: 0.72rem; }
|
||||
|
||||
/* The pip field: three columns, seven rows — the skeleton every printed deck
|
||||
shares. blackjack.js places each pip on it. */
|
||||
.pete-card-pips {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-rows: repeat(7, minmax(0, 1fr));
|
||||
place-items: center;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0 0.08rem;
|
||||
}
|
||||
.pete-card-pip { font-size: 0.82rem; line-height: 1; }
|
||||
.pete-card-pip[data-flip="1"] { transform: rotate(180deg); }
|
||||
|
||||
/* An ace is one pip, and it's allowed to be enormous. */
|
||||
.pete-card-pips[data-ace="1"] .pete-card-pip { font-size: 2.1rem; }
|
||||
|
||||
/* Court cards: the letter with a suit over each shoulder. */
|
||||
.pete-card-court {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.05rem;
|
||||
}
|
||||
.pete-card-court-letter {
|
||||
font-size: 1.7rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.pete-card-court-suit { font-size: 0.8rem; }
|
||||
.pete-card-court-suit[data-flip="1"] { transform: rotate(180deg); }
|
||||
.pete-card-back {
|
||||
transform: rotateY(180deg);
|
||||
background:
|
||||
@@ -614,9 +669,6 @@ html[data-phase="night"] {
|
||||
border: 2px solid rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.pete-card-rank { font-size: 1.5rem; font-weight: 700; }
|
||||
.pete-card-suit { font-size: 1.15rem; margin-top: 0.1rem; }
|
||||
|
||||
/* The flight itself: out of the shoe (up and to the right), scaled down and
|
||||
spinning slightly, into place. */
|
||||
@keyframes pete-deal {
|
||||
@@ -656,3 +708,112 @@ html[data-phase="night"] {
|
||||
.pete-hand[data-won="1"] .pete-card { animation: none; }
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
The rooms.
|
||||
|
||||
The casino borrows the news app's design language wholesale — the same four
|
||||
palette vars, the same Fredoka/Nunito, the same fat rounded cards and dropped
|
||||
shadows — and none of its furniture.
|
||||
|
||||
It borrows the clock too, but tells a different joke with it. News shifts
|
||||
dawn→day→dusk→night. The casino has two rooms and swaps between them on the
|
||||
hour, name and all:
|
||||
|
||||
casinopolis by day — honey and green felt, lamps low
|
||||
casino-night by dark — neon blue, pinball purple, lit like a machine
|
||||
|
||||
Both are dark rooms of the same shape; only the light and the sign change.
|
||||
Adding a third is a palette block, a felt, and a name — nothing else.
|
||||
|
||||
Written outside @layer on purpose: these must beat the base .shadow-pete.
|
||||
---------------------------------------------------------------------------- */
|
||||
|
||||
html[data-room="casinopolis"] {
|
||||
--bg: #16211c; /* the room, lights low */
|
||||
--card: #23342b; /* a table in it */
|
||||
--ink: #f6ecd8; /* warm cream, not white */
|
||||
--accent: #f2b53d; /* honey */
|
||||
--felt-a: #2f7d5b;
|
||||
--felt-b: #24614a;
|
||||
--felt-c: #1c4d3c;
|
||||
--glow: 242,181,61; /* what the lamps throw, as rgb parts */
|
||||
}
|
||||
|
||||
html[data-room="casino-night"] {
|
||||
--bg: #140f2e; /* the machine's cabinet */
|
||||
--card: #241a4d;
|
||||
--ink: #f2ecff;
|
||||
--accent: #ffcc2f; /* the jackpot bulb */
|
||||
--felt-a: #4a2fa8; /* the table is a pinball table now */
|
||||
--felt-b: #351f80;
|
||||
--felt-c: #241659;
|
||||
--glow: 126,86,255;
|
||||
}
|
||||
|
||||
/* A lamp over the table and a darker floor at the edges, so the page has a
|
||||
middle to sit in rather than being flat dark. */
|
||||
html[data-room] .cs-room {
|
||||
background:
|
||||
radial-gradient(80% 55% at 50% -8%, rgba(var(--glow), 0.18), transparent 62%),
|
||||
radial-gradient(120% 90% at 50% 120%, rgba(0,0,0,0.45), transparent 55%);
|
||||
}
|
||||
|
||||
/* Casino Night is lit by bulbs, so it gets a few of them: a slow chase across
|
||||
the top of the room. Cheap (one gradient, one transform) and it makes the
|
||||
place feel plugged in rather than painted. */
|
||||
html[data-room="casino-night"] .cs-room::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 -50% auto -50%;
|
||||
height: 0.5rem;
|
||||
background: repeating-linear-gradient(90deg,
|
||||
rgba(255,204,47,0.55) 0 0.5rem, transparent 0.5rem 2.25rem);
|
||||
filter: blur(1px);
|
||||
animation: cs-bulbs 2.4s linear infinite;
|
||||
}
|
||||
@keyframes cs-bulbs {
|
||||
from { transform: translateX(0); }
|
||||
to { transform: translateX(2.75rem); }
|
||||
}
|
||||
|
||||
/* The house shadow, restruck for a dark room: the news app's soft brown lift is
|
||||
invisible down here, so the same shape is cut in black instead. */
|
||||
html[data-room] .shadow-pete { box-shadow: 0 4px 0 rgba(0,0,0,0.30), 0 10px 26px rgba(0,0,0,0.30); }
|
||||
html[data-room] .shadow-pete-lg { box-shadow: 0 6px 0 rgba(0,0,0,0.34), 0 20px 40px rgba(0,0,0,0.38); }
|
||||
|
||||
html[data-room] .cs-comb svg { filter: drop-shadow(0 3px 0 rgba(0,0,0,0.35)); }
|
||||
|
||||
/* The felt takes its colours from the room, so switching rooms reupholsters the
|
||||
table too. */
|
||||
html[data-room] .pete-felt {
|
||||
background:
|
||||
radial-gradient(120% 90% at 50% -10%, rgba(255,255,255,0.10), transparent 60%),
|
||||
linear-gradient(160deg, var(--felt-a) 0%, var(--felt-b) 55%, var(--felt-c) 100%);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html[data-room="casino-night"] .cs-room::before { animation: none; }
|
||||
}
|
||||
|
||||
/* Three stacks of chips on the lobby's welcome card, in the same denomination
|
||||
colours you actually bet with. Height comes from --stack, so the row reads as
|
||||
a real pile someone left on the table rather than an icon of one. */
|
||||
.cs-stack {
|
||||
display: block;
|
||||
width: 2.75rem;
|
||||
height: calc(0.45rem * var(--stack, 1) + 0.55rem);
|
||||
border-radius: 999px;
|
||||
background:
|
||||
radial-gradient(circle at 50% 30%, rgba(255,255,255,0.30), transparent 60%),
|
||||
var(--chip, #e07a5f);
|
||||
border: 2px solid rgba(0,0,0,0.25);
|
||||
box-shadow:
|
||||
0 3px 0 rgba(0,0,0,0.30),
|
||||
inset 0 -0.4rem 0 rgba(0,0,0,0.16),
|
||||
inset 0 0.35rem 0 rgba(255,255,255,0.14);
|
||||
}
|
||||
.cs-stack[data-chip="5"] { --chip: #5aa9e6; }
|
||||
.cs-stack[data-chip="25"] { --chip: #4caf7d; }
|
||||
.cs-stack[data-chip="100"] { --chip: #2b2118; }
|
||||
.cs-stack[data-chip="500"] { --chip: #b079d6; }
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user