games: burial send-offs, money rain on wins, and honest stack targeting

Three front-end changes to the casino games:

- uno: a draw-card stack now names the seat it's actually pointed at
  ("+N on Beep") instead of always reading "+N on you". The bill reads
  v.turn, which the engine advances to the target when a draw card lands.

- uno: the No Mercy rule buries a seat with some ceremony now. bury()
  rolls one of four send-offs over the seat (or your hand, if it's you) —
  a rockslide that piles up, a tombstone, a coffin, or the Mega Man death
  burst — each with its own synth sound, and a static headstone under
  reduced motion. The "Buried on N" badge still rides alongside.

- all games: winning makes it rain. FX.moneyRain drops a curtain of bills
  and coins with a jackpot coin-cascade sound, gated by significance so it
  reads as a win and not as noise: hold'em keeps its light per-pot confetti
  and only rains on a real haul (>= 20bb), while the confetti stays the
  rare cherry (natural 21, full board clear, phrase solved outright).
This commit is contained in:
prosolis
2026-07-15 18:37:19 -07:00
parent e0d90ff7cc
commit 8504c4f47a
10 changed files with 326 additions and 25 deletions

View File

@@ -940,6 +940,18 @@ html[data-phase="night"] {
will-change: transform, opacity;
}
/* Falling money — the win curtain. Positioned entirely by the transform WAAPI
drives (see moneyRain in casino-fx.js), so it starts pinned to the top-left. */
.pete-money {
position: absolute;
top: 0;
left: 0;
font-size: 1.6rem;
line-height: 1;
filter: drop-shadow(0 2px 2px rgba(0,0,0,0.35));
will-change: transform, opacity;
}
/* The dealer's beat before they draw out. */
.pete-dealer-think {
animation: pete-think 0.9s ease-in-out infinite;
@@ -1978,6 +1990,93 @@ html[data-phase="night"] {
}
.pete-uno-seat[data-out="1"] .pete-uno-count { color: #ffb3ba; }
/* The send-off. When the mercy rule buries a seat, uno.js rolls one of four
of these and drops it over that seat — or over your hand, if it's you going
down. Every piece is built fresh and clears itself, so it can't outlive the
hand. The wrap is a zero-size anchor at the seat's middle; the pieces hang
off it. */
.pete-uno-bury {
position: absolute;
left: 50%;
top: 45%;
width: 0;
height: 0;
z-index: 40;
pointer-events: none;
}
/* Rockslide: a handful of boulders come down and pile where the seat was. */
.pete-uno-rock {
position: absolute;
left: 0;
top: 0;
font-size: 1.5rem;
line-height: 1;
filter: drop-shadow(0 2px 1px rgba(0,0,0,0.4));
animation: pete-bury-rock 0.6s cubic-bezier(0.5, 0, 0.7, 1) both;
animation-delay: var(--d, 0ms);
}
@keyframes pete-bury-rock {
0% { transform: translate(calc(-50% + var(--dx)), -3.4rem) rotate(0deg); opacity: 0; }
20% { opacity: 1; }
75% { transform: translate(calc(-50% + var(--dx)), var(--dy)) rotate(var(--rot)); }
85% { transform: translate(calc(-50% + var(--dx)), calc(var(--dy) - 0.22rem)) rotate(var(--rot)); }
100% { transform: translate(calc(-50% + var(--dx)), var(--dy)) rotate(var(--rot)); opacity: 1; }
}
/* Tombstone and coffin: one heavy thing drops in and sets, with a small bounce. */
.pete-uno-tomb,
.pete-uno-coffin {
position: absolute;
left: 0;
top: 0;
line-height: 1;
filter: drop-shadow(0 3px 2px rgba(0,0,0,0.45));
animation: pete-bury-drop 0.5s cubic-bezier(0.34, 1.3, 0.64, 1) both;
}
.pete-uno-tomb { font-size: 2.4rem; }
.pete-uno-coffin { font-size: 2.5rem; }
@keyframes pete-bury-drop {
0% { transform: translate(-50%, -3.2rem) scale(0.7); opacity: 0; }
55% { opacity: 1; }
70% { transform: translate(-50%, -50%) scale(1.06); }
82% { transform: translate(-50%, -44%) scale(1); }
100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}
/* The Mega Man death: a flash, then eight pieces fire out and wink off. */
.pete-uno-zap-flash {
position: absolute;
left: 0;
top: 0;
width: 1.5rem;
height: 1.5rem;
margin: -0.75rem 0 0 -0.75rem;
border-radius: 999px;
background: radial-gradient(circle, #fff 0%, rgba(180,220,255,0) 70%);
animation: pete-bury-flash 0.35s ease-out both;
}
@keyframes pete-bury-flash {
0% { transform: scale(0.3); opacity: 0.95; }
100% { transform: scale(1.9); opacity: 0; }
}
.pete-uno-zap-bit {
position: absolute;
left: 0;
top: 0;
width: 0.5rem;
height: 0.5rem;
margin: -0.25rem 0 0 -0.25rem;
border-radius: 999px;
background: #cfeaff;
box-shadow: 0 0 6px 1px rgba(150,210,255,0.9);
animation: pete-bury-zap 0.5s ease-out both;
}
@keyframes pete-bury-zap {
0% { transform: rotate(var(--ang)) translateX(0.2rem); opacity: 1; }
100% { transform: rotate(var(--ang)) translateX(3.4rem); opacity: 0; }
}
/* The rules switch: two dials, and this is the one that isn't the table size. */
.pete-seg {
display: inline-flex;
@@ -2043,6 +2142,9 @@ html[data-phase="night"] {
.pete-uno-card[data-glow="1"]::before { animation: none; }
.pete-uno-card[data-glow="1"] .pete-uno-face::after { display: none; }
.pete-uno-pending { animation: none; }
/* The grave still gets marked — that's information — it just doesn't fall in.
uno.js drops a single static headstone here rather than the full rockslide. */
.pete-uno-tomb { animation: none; transform: translate(-50%, -50%); }
}
}

File diff suppressed because one or more lines are too long