games: draw the card, don't type it
The last attempt built a card face out of text: a "♠" in a span for every pip. At the size a card actually is, a suit character renders as a speck — the shape is whatever font answered, it doesn't scale, and it can't be put on the half-row a real pip layout needs. The result read worse than the plain rank it replaced. So each face is one SVG on a 100×140 field, suits as vector shapes, pips at the coordinates a printed deck puts them. Courts get a framed panel with the suit above the letter and again below it upside down — mirroring a letter, which is what the first pass did, just stacks two of them into a blob; a real court mirrors a figure. Also restores .pete-card-back, which went out with the text rules it was sitting among: without it a face-down card had no back at all, so the dealer's hole card was invisible on the felt. Caught by driving a hand.
This commit is contained in:
@@ -565,15 +565,15 @@ html[data-phase="night"] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
min-height: 6.6rem;
|
||||
min-height: 8.6rem;
|
||||
}
|
||||
|
||||
/* One card. The wrapper does the flight, the inner face does the flip, so the
|
||||
two never fight over the same transform. */
|
||||
.pete-card {
|
||||
perspective: 700px;
|
||||
height: 7rem;
|
||||
width: 5rem;
|
||||
height: 8.4rem;
|
||||
width: 6rem;
|
||||
animation: pete-deal 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||||
}
|
||||
.pete-card-inner {
|
||||
@@ -597,70 +597,23 @@ 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. */
|
||||
/* The face is a single SVG on a 100×140 field (see blackjack.js) — suits as
|
||||
vector shapes, pips at printed-deck coordinates. The card element only has
|
||||
to hold it and colour it: `fill: currentColor` means the red/black switch
|
||||
below repaints every pip, index and court letter at once. */
|
||||
.pete-card-front {
|
||||
/* 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;
|
||||
place-items: stretch;
|
||||
background: #fdfaf2;
|
||||
border: 2px solid rgba(30,20,10,0.12);
|
||||
color: #2b2118;
|
||||
font-family: "Fredoka", "Nunito", system-ui, sans-serif;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
.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); }
|
||||
/* The back. Turned away from you until the card is: this is what the dealer's
|
||||
hole card shows while you're still acting. */
|
||||
.pete-card-back {
|
||||
transform: rotateY(180deg);
|
||||
background:
|
||||
@@ -669,6 +622,36 @@ html[data-phase="night"] {
|
||||
border: 2px solid rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.pete-card-svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* The corner index. Tabular-ish and tight, so a 10 doesn't shove the suit. */
|
||||
.pete-card-idx {
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
text-anchor: middle;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* The court panel: a frame, not a portrait. */
|
||||
.pete-card-panel {
|
||||
fill: currentColor;
|
||||
fill-opacity: 0.06;
|
||||
stroke: currentColor;
|
||||
stroke-opacity: 0.35;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
.pete-card-court {
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
text-anchor: middle;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
/* The flight itself: out of the shoe (up and to the right), scaled down and
|
||||
spinning slightly, into place. */
|
||||
@keyframes pete-deal {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -73,96 +73,96 @@
|
||||
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.
|
||||
// ---- the face ------------------------------------------------------------
|
||||
//
|
||||
// Sevens and tens have a pip that sits *between* two rows, so those span a pair
|
||||
// of rows and centre themselves across the pair.
|
||||
// A card is drawn, not typed. The first attempt set the pips as text — "♠" in a
|
||||
// span — and at the size a card actually is, a suit character renders as a
|
||||
// speck: the shape is whatever font happened to answer, it doesn't scale, and
|
||||
// it can't be positioned to the half-row a real pip layout needs.
|
||||
//
|
||||
// So each face is one SVG on a 100×140 field (the proportions of a real card),
|
||||
// with the suits as vector shapes. Everything below is coordinates on that
|
||||
// field, which is why the pips land where a printed deck puts them instead of
|
||||
// where a flexbox felt like putting them.
|
||||
|
||||
var SUIT_ART = {
|
||||
"♠": '<path d="M50 6C50 6 16 34 16 58a17 17 0 0 0 28 13c-1 12-5 19-12 25h36c-7-6-11-13-12-25a17 17 0 0 0 28-13C84 34 50 6 50 6Z"/>',
|
||||
"♥": '<path d="M50 96C50 96 8 66 8 38A22 22 0 0 1 50 24 22 22 0 0 1 92 38c0 28-42 58-42 58Z"/>',
|
||||
"♦": '<path d="M50 4 88 50 50 96 12 50Z"/>',
|
||||
"♣": '<g><circle cx="50" cy="28" r="19"/><circle cx="24" cy="62" r="19"/><circle cx="76" cy="62" r="19"/>' +
|
||||
'<path d="M44 60h12l7 36H37Z"/></g>',
|
||||
};
|
||||
|
||||
// Pip layouts, the way a real deck lays them out — which is not "N suits in a
|
||||
// row". [x, y] on the 100×140 field. The seven canonical rows sit at y = 27,
|
||||
// 41, 56, 70, 84, 99, 113; sevens, eights and tens carry a pip *between* two of
|
||||
// them, which is the whole reason this is a table of coordinates and not a
|
||||
// grid. Anything below the middle is printed upside down, so it is.
|
||||
var R = [0, 27, 41.4, 55.7, 70, 84.3, 98.6, 113]; // 1-indexed, R[4] is the middle
|
||||
var L = 30, C = 50, Rr = 70; // the three columns
|
||||
|
||||
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]],
|
||||
"A": [[C, 70, 2.1]],
|
||||
"2": [[C, R[1]], [C, R[7]]],
|
||||
"3": [[C, R[1]], [C, R[4]], [C, R[7]]],
|
||||
"4": [[L, R[1]], [Rr, R[1]], [L, R[7]], [Rr, R[7]]],
|
||||
"5": [[L, R[1]], [Rr, R[1]], [C, R[4]], [L, R[7]], [Rr, R[7]]],
|
||||
"6": [[L, R[1]], [Rr, R[1]], [L, R[4]], [Rr, R[4]], [L, R[7]], [Rr, R[7]]],
|
||||
"7": [[L, R[1]], [Rr, R[1]], [C, 48.5], [L, R[4]], [Rr, R[4]], [L, R[7]], [Rr, R[7]]],
|
||||
"8": [[L, R[1]], [Rr, R[1]], [C, 48.5], [L, R[4]], [Rr, R[4]], [C, 91.5], [L, R[7]], [Rr, R[7]]],
|
||||
"9": [[L, R[1]], [Rr, R[1]], [L, R[3]], [Rr, R[3]], [C, R[4]], [L, R[5]], [Rr, R[5]], [L, R[7]], [Rr, R[7]]],
|
||||
"10": [[L, R[1]], [Rr, R[1]], [C, 48.5], [L, R[3]], [Rr, R[3]], [L, R[5]], [Rr, R[5]], [C, 91.5], [L, R[7]], [Rr, R[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;
|
||||
// One pip: the suit art, scaled and dropped at [x, y], turned over if it sits
|
||||
// below the middle of the card.
|
||||
function pipAt(suit, x, y, scale) {
|
||||
var s = (scale || 1) * 0.17;
|
||||
var turn = y > 70 ? " rotate(180 50 50)" : "";
|
||||
return '<g transform="translate(' + x + ' ' + y + ') scale(' + s + ') translate(-50 -50)' + turn + '">' +
|
||||
SUIT_ART[suit] + "</g>";
|
||||
}
|
||||
|
||||
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;
|
||||
// The corner index: rank over suit. Printed in both corners, the second one
|
||||
// upside down, which is what lets you read a card from a fanned hand.
|
||||
function index(face) {
|
||||
var g =
|
||||
'<g>' +
|
||||
'<text x="12" y="24" class="pete-card-idx">' + face.rank + "</text>" +
|
||||
'<g transform="translate(12 36) scale(0.13) translate(-50 -50)">' + SUIT_ART[face.suit] + "</g>" +
|
||||
"</g>";
|
||||
return g + '<g transform="rotate(180 50 70)">' + g + "</g>";
|
||||
}
|
||||
|
||||
// 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.
|
||||
// paintFace draws the card. 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 = "";
|
||||
|
||||
front.appendChild(corner(face, "tl"));
|
||||
|
||||
var body = document.createElement("span");
|
||||
var body = "";
|
||||
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);
|
||||
// Court cards: a framed panel, the suit above the letter and again below it
|
||||
// the other way up. A real court mirrors a *figure*; mirroring a letter just
|
||||
// stacks two of them into a blob, which is exactly what the first attempt
|
||||
// did. No portrait either — a drawn king would fight the room, and this
|
||||
// reads instantly at the size a card actually is.
|
||||
body =
|
||||
'<rect x="20" y="22" width="60" height="96" rx="6" class="pete-card-panel"/>' +
|
||||
pipAt(face.suit, 50, 38, 0.95) +
|
||||
'<text x="50" y="82" class="pete-card-court">' + face.rank + "</text>" +
|
||||
pipAt(face.suit, 50, 102, 0.95);
|
||||
} 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]));
|
||||
body += pipAt(face.suit, spots[i][0], spots[i][1], spots[i][2]);
|
||||
}
|
||||
}
|
||||
front.appendChild(body);
|
||||
|
||||
front.appendChild(corner(face, "br"));
|
||||
|
||||
front.setAttribute("aria-label", ariaFor(face));
|
||||
front.innerHTML =
|
||||
'<svg class="pete-card-svg" viewBox="0 0 100 140" xmlns="http://www.w3.org/2000/svg" ' +
|
||||
'role="img" aria-label="' + ariaFor(face) + '">' + index(face) + body + "</svg>";
|
||||
}
|
||||
|
||||
// "A♠" is not something a screen reader should be asked to pronounce.
|
||||
|
||||
Reference in New Issue
Block a user