games: a blackjack table you can actually sit down at
The engine, the escrow and the wire were all in place; nothing had a browser on the end of it. This is that end: a lobby, a table, and the five endpoints between them. The browser holds no game. It sends intents and gets back a view — the cards it is entitled to see, and the script of how they arrived, one event per card off the shoe. The dealer's hole card is not in the payload at all until the reveal, because a field the client is told to ignore is a field somebody reads in devtools. The shoe lives in game_live_hands, which also means a redeploy mid-hand no longer costs a player their stake: the hand is still there when they come back. The money is ordered so nothing can be spent twice. The stake leaves the stack in the same statement that checks it exists, before a card is dealt. Every new hand is seated with a plain INSERT, so a double-clicked Deal is decided by the primary key rather than by a read that raced — it loses, gets its chips back, and the hand in progress is untouched. A double takes its raise up front and hands it straight back if the engine refuses the move. Cards are dealt rather than swapped in — they fly out of the shoe and turn over, which was a requirement and not a flourish. The faces and the chips are still plain; that's next.
This commit is contained in:
@@ -527,4 +527,132 @@ html[data-phase="night"] {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ---- the casino ---------------------------------------------------------
|
||||
Cards are dealt, not swapped in: each one starts at the shoe in the corner,
|
||||
flies to its place, and turns over. The whole point of the table is that you
|
||||
watch it happen, so this is written as one animation per card with a delay,
|
||||
and the JS just says which cards exist and in what order. */
|
||||
|
||||
.pete-felt {
|
||||
background:
|
||||
radial-gradient(120% 90% at 50% -10%, rgba(255,255,255,0.10), transparent 60%),
|
||||
linear-gradient(160deg, #2f7d5b 0%, #24614a 55%, #1c4d3c 100%);
|
||||
}
|
||||
|
||||
/* The shoe: where every card comes from. */
|
||||
.pete-shoe {
|
||||
position: absolute;
|
||||
top: 1.25rem;
|
||||
right: 1.25rem;
|
||||
height: 4.2rem;
|
||||
width: 3rem;
|
||||
border-radius: 0.55rem;
|
||||
background: linear-gradient(150deg, #b4553f, #8d3f2f);
|
||||
border: 2px solid rgba(0,0,0,0.18);
|
||||
box-shadow: 0 4px 0 rgba(0,0,0,0.18), inset 0 0 0 3px rgba(255,255,255,0.12);
|
||||
}
|
||||
.pete-shoe::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0.45rem 0.4rem auto 0.4rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 0.2rem;
|
||||
background: rgba(0,0,0,0.22);
|
||||
}
|
||||
|
||||
.pete-hand {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.6rem;
|
||||
min-height: 6.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: 6.4rem;
|
||||
width: 4.5rem;
|
||||
animation: pete-deal 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||||
}
|
||||
.pete-card-inner {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transform-style: preserve-3d;
|
||||
transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
/* Face-down is the resting state of a card that hasn't been turned over: the
|
||||
hole card sits like this until the dealer plays. */
|
||||
.pete-card[data-face="down"] .pete-card-inner { transform: rotateY(180deg); }
|
||||
|
||||
.pete-card-front,
|
||||
.pete-card-back {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: grid;
|
||||
border-radius: 0.55rem;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
box-shadow: 0 3px 0 rgba(0,0,0,0.18), 0 6px 14px rgba(0,0,0,0.22);
|
||||
}
|
||||
.pete-card-front {
|
||||
place-items: center;
|
||||
background: #fdfaf2;
|
||||
border: 2px solid rgba(30,20,10,0.12);
|
||||
color: #2b2118;
|
||||
font-family: "Fredoka", "Nunito", system-ui, sans-serif;
|
||||
line-height: 1;
|
||||
}
|
||||
.pete-card-front[data-red="1"] { color: #cc3d4a; }
|
||||
.pete-card-back {
|
||||
transform: rotateY(180deg);
|
||||
background:
|
||||
repeating-linear-gradient(45deg, rgba(255,255,255,0.10) 0 6px, transparent 6px 12px),
|
||||
linear-gradient(150deg, #b4553f, #8d3f2f);
|
||||
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 {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translate(var(--deal-x, 14rem), var(--deal-y, -7rem)) scale(0.72) rotate(9deg);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate(0, 0) scale(1) rotate(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* A settled hand: the winning side gets a little lift, so a win reads at a
|
||||
glance rather than only in the text. */
|
||||
.pete-hand[data-won="1"] .pete-card { animation: pete-deal 0.42s cubic-bezier(0.22,1,0.36,1) backwards, pete-win 0.6s ease 0.1s; }
|
||||
@keyframes pete-win {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
40% { transform: translateY(-0.6rem); }
|
||||
}
|
||||
|
||||
/* Chips: the denominations you bet with. */
|
||||
.pete-chip {
|
||||
background: radial-gradient(circle at 50% 35%, rgba(255,255,255,0.28), transparent 60%), var(--chip, #e07a5f);
|
||||
border: 3px dashed rgba(255,255,255,0.55);
|
||||
box-shadow: 0 3px 0 rgba(60,40,20,0.22), 0 6px 14px rgba(60,40,20,0.18);
|
||||
}
|
||||
.pete-chip[data-chip="5"] { --chip: #5aa9e6; }
|
||||
.pete-chip[data-chip="25"] { --chip: #4caf7d; }
|
||||
.pete-chip[data-chip="100"] { --chip: #2b2118; }
|
||||
.pete-chip[data-chip="500"] { --chip: #b079d6; }
|
||||
.pete-chip[aria-pressed="true"] { outline: 3px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.pete-card { animation: none; }
|
||||
.pete-card-inner { transition: none; }
|
||||
.pete-hand[data-won="1"] .pete-card { animation: none; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user