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:
prosolis
2026-07-13 23:49:17 -07:00
parent 8ec13eab5b
commit b00da21a47
3 changed files with 112 additions and 129 deletions

View File

@@ -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 {