1
0
Fork 0

feat(chips): felt, chip, and popup styles plus win sound asset

Felt table, chip stack, delta popup, spinner, and flash styles for the card minigames; chaching.mp3 win sound.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-14 15:38:49 -04:00
parent 17f3cc6f47
commit 4583596903
2 changed files with 544 additions and 8 deletions

BIN
public/assets/chaching.mp3 Normal file

Binary file not shown.

View file

@ -565,6 +565,126 @@
background: color-mix(in oklch, var(--color-destructive) 8%, transparent); background: color-mix(in oklch, var(--color-destructive) 8%, transparent);
} }
/* Lexical rich-text typography (scoped to .lexical-content); Tailwind preflight resets headings */
.lexical-content {
font-size: 0.875rem;
line-height: 1.7;
color: var(--color-muted-foreground);
}
.lexical-content > :first-child {
margin-top: 0;
}
.lexical-content > :last-child {
margin-bottom: 0;
}
.lexical-content h1,
.lexical-content h2,
.lexical-content h3,
.lexical-content h4 {
font-weight: 600;
line-height: 1.3;
color: var(--color-foreground);
}
.lexical-content h1 {
font-size: 1.25rem;
margin: 1.25rem 0 0.5rem;
}
.lexical-content h2 {
font-size: 1.125rem;
margin: 1.125rem 0 0.4375rem;
}
.lexical-content h3 {
font-size: 1rem;
margin: 1rem 0 0.375rem;
}
.lexical-content h4 {
font-size: 0.9375rem;
margin: 0.875rem 0 0.25rem;
}
.lexical-content p {
margin: 0.5rem 0;
}
.lexical-content ul,
.lexical-content ol {
margin: 0.5rem 0;
padding-left: 1.5rem;
}
.lexical-content ul {
list-style: disc;
}
.lexical-content ol {
list-style: decimal;
}
.lexical-content li + li {
margin-top: 0.25rem;
}
.lexical-content li > ul,
.lexical-content li > ol {
margin: 0.25rem 0;
}
.lexical-content blockquote {
margin: 0.75rem 0;
border-left: 3px solid var(--color-border);
padding-left: 1rem;
color: var(--color-muted-foreground);
}
.lexical-content hr {
margin: 1.25rem 0;
border: 0;
border-top: 1px solid var(--color-border);
}
.lexical-content strong {
color: var(--color-foreground);
}
.lexical-content a {
color: var(--color-primary);
text-decoration: underline;
text-underline-offset: 2px;
}
.lexical-content a:hover {
text-decoration-color: var(--color-primary);
}
.lexical-content :not(pre) > code {
background: var(--color-muted);
border-radius: 0.25rem;
padding: 0.125rem 0.375rem;
font-size: 0.8125rem;
}
.lexical-content pre {
background: var(--color-muted);
border: 1px solid var(--color-border);
border-radius: 0.5rem;
padding: 0.875rem 1rem;
overflow-x: auto;
margin: 0.75rem 0;
}
.lexical-content pre code {
background: transparent;
padding: 0;
font-size: 0.8125rem;
}
/* Wiki prose-article typography (scoped to .wiki-markdown) */ /* Wiki prose-article typography (scoped to .wiki-markdown) */
.wiki-markdown { .wiki-markdown {
font-size: 0.9375rem; font-size: 0.9375rem;
@ -1217,6 +1337,47 @@
transform: translateY(-50%); transform: translateY(-50%);
} }
/* Blackjack seats hug the bottom two-thirds of the felt so the top third
stays clear for the dealer. Six slots push out to the rim of the 50%/48%
ellipse (three per side, bulging outward at the middle) with margins that
keep even the widest seat panel fully inside the curved edge, spaced for
breathing room between neighbors. */
.blackjack-seat-arc-corner-right {
top: 29%;
left: 74%;
transform: translate(-50%, -50%);
}
.blackjack-seat-arc-corner-left {
top: 29%;
left: 26%;
transform: translate(-50%, -50%);
}
.blackjack-seat-arc-mid-right {
top: 50%;
left: 83.5%;
transform: translate(-50%, -50%);
}
.blackjack-seat-arc-mid-left {
top: 50%;
left: 16.5%;
transform: translate(-50%, -50%);
}
.blackjack-seat-arc-low-right {
top: 71%;
left: 74%;
transform: translate(-50%, -50%);
}
.blackjack-seat-arc-low-left {
top: 71%;
left: 26%;
transform: translate(-50%, -50%);
}
.poker-felt-fullscreen { .poker-felt-fullscreen {
min-height: max(76vh, 42rem); min-height: max(76vh, 42rem);
border-radius: 50% / 48%; border-radius: 50% / 48%;
@ -1262,8 +1423,8 @@
.poker-seat-avatar { .poker-seat-avatar {
display: flex; display: flex;
width: 1.9rem; width: 2.4rem;
height: 1.9rem; height: 2.4rem;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border: 2px solid color-mix(in srgb, white 52%, transparent); border: 2px solid color-mix(in srgb, white 52%, transparent);
@ -1273,7 +1434,7 @@
0 0.2rem 0.45rem rgb(0 0 0 / 34%); 0 0.2rem 0.45rem rgb(0 0 0 / 34%);
color: white; color: white;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.8rem; font-size: 1rem;
font-weight: 700; font-weight: 700;
} }
@ -1316,15 +1477,15 @@
gap: 0.3rem; gap: 0.3rem;
color: #f3d28b; color: #f3d28b;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 1.1rem; font-size: 1.35rem;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
font-weight: 700; font-weight: 700;
line-height: 1.15; line-height: 1.15;
} }
.poker-seat-stack-chip { .poker-seat-stack-chip {
width: 0.7rem; width: 0.85rem;
height: 0.7rem; height: 0.85rem;
flex: 0 0 auto; flex: 0 0 auto;
border: 1px solid #d6b36a; border: 1px solid #d6b36a;
border-radius: 50%; border-radius: 50%;
@ -1379,10 +1540,10 @@
.poker-seat-action-badge { .poker-seat-action-badge {
border: 1px solid color-mix(in srgb, var(--poker-trim) 60%, transparent); border: 1px solid color-mix(in srgb, var(--poker-trim) 60%, transparent);
border-radius: 9999px; border-radius: 9999px;
padding: 0.08rem 0.35rem; padding: 0.12rem 0.45rem;
background: color-mix(in srgb, var(--poker-trim) 14%, transparent); background: color-mix(in srgb, var(--poker-trim) 14%, transparent);
color: #f3d28b; color: #f3d28b;
font-size: 0.55rem; font-size: 0.65rem;
font-weight: 700; font-weight: 700;
letter-spacing: 0.08em; letter-spacing: 0.08em;
line-height: 1.2; line-height: 1.2;
@ -1893,6 +2054,75 @@
animation: poker-royal-wash 6s ease-in-out infinite alternate; animation: poker-royal-wash 6s ease-in-out infinite alternate;
} }
/* The acting seat's halo: a translucent emerald glow that breathes behind the
pill, rendered by a transparent pseudo so the ring never covers content. */
.poker-action-log {
position: absolute;
bottom: 12.5rem;
left: 50%;
z-index: 10;
display: flex;
width: min(24rem, 80%);
flex-direction: column;
gap: 0.2rem;
transform: translateX(-50%);
pointer-events: none;
-webkit-mask-image: linear-gradient(to bottom, black 45%, transparent 100%);
mask-image: linear-gradient(to bottom, black 45%, transparent 100%);
}
.poker-action-log-line {
text-align: center;
color: rgb(255 255 255 / 70%);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.7rem;
letter-spacing: 0.03em;
overflow-wrap: anywhere;
}
.poker-log-wager {
color: rgb(253 230 138 / 80%);
}
.poker-log-win {
color: rgb(167 243 208 / 85%);
}
.poker-log-fold,
.poker-log-leave {
color: rgb(255 255 255 / 45%);
}
.poker-log-action {
color: rgb(191 219 254 / 80%);
}
/* The acting seat's halo: a translucent emerald glow that breathes behind the
pill, rendered by a transparent pseudo so the ring never covers content. */
.poker-seat-active {
border-color: rgb(52 211 153 / 85%);
}
.poker-seat-active::after {
content: "";
position: absolute;
inset: -0.3rem;
z-index: 1;
border-radius: 0.9rem;
pointer-events: none;
animation: poker-seat-pulse 2s ease-in-out infinite;
}
@keyframes poker-seat-pulse {
0%,
100% {
box-shadow: 0 0 0.4rem 0 rgb(52 211 153 / 18%);
}
50% {
box-shadow: 0 0 1.4rem 0.25rem rgb(52 211 153 / 55%);
}
}
/* The rainbow lives only on the border ring (masked pseudo-element) and the /* The rainbow lives only on the border ring (masked pseudo-element) and the
near-transparent background wash, never on the element's content: the hue near-transparent background wash, never on the element's content: the hue
filter animates the pseudo itself, so cards and text keep their true colors. */ filter animates the pseudo itself, so cards and text keep their true colors. */
@ -2547,9 +2777,315 @@
animation: none; animation: none;
} }
.poker-seat-active::after {
animation: none;
box-shadow: 0 0 0.9rem 0.1rem rgb(52 211 153 / 40%);
}
.poker-card-inner, .poker-card-inner,
.poker-card-deal { .poker-card-deal {
animation: none; animation: none;
transition: none; transition: none;
} }
} }
/* Blackjack chip stacks: denomination-colored CSS chip piles. Amounts are
broken into piles (1000/500/100/25/10/5/1), max 5 discs per pile; the
exact number always rides alongside in a text label. Pile heights are set
inline from the chip count so nothing clips or overlaps. */
.bj-chip-stack {
display: inline-flex;
align-items: flex-end;
gap: 0.3rem;
}
.bj-chip-pile {
position: relative;
display: inline-block;
width: 1.6rem;
}
.bj-chip {
position: absolute;
left: 0;
width: 1.6rem;
height: 0.95rem;
border: 2px dashed rgb(255 255 255 / 72%);
border-radius: 50%;
box-shadow:
0 2px 3px rgb(0 0 0 / 38%),
inset 0 -2px 3px rgb(0 0 0 / 22%);
animation: bj-chip-pop 180ms ease-out;
transition:
box-shadow 150ms ease,
border-color 150ms ease;
}
/* Hover feedback on a pile: a subtle glow in the chip's own color, hinting
at the denomination tooltip. */
.bj-chip-pile:hover .bj-chip {
border-color: color-mix(in srgb, white 82%, var(--bj-chip-glow, white));
box-shadow:
0 2px 3px rgb(0 0 0 / 38%),
inset 0 -2px 3px rgb(0 0 0 / 22%),
0 0 0.5rem var(--bj-chip-glow, rgb(255 255 255 / 55%)),
0 0 0.2rem var(--bj-chip-glow, rgb(255 255 255 / 55%));
}
/* Inner face ring so the disc reads as a chip, not a squashed circle. */
.bj-chip::after {
content: "";
position: absolute;
inset: 2px 4px;
border: 1.5px solid rgb(255 255 255 / 35%);
border-radius: 50%;
}
.bj-chip-gold {
--bj-chip-glow: rgb(217 164 65 / 60%);
background: #d9a441;
}
.bj-chip-purple {
--bj-chip-glow: rgb(139 92 246 / 60%);
background: #8b5cf6;
}
.bj-chip-black {
--bj-chip-glow: rgb(255 255 255 / 38%);
background: #27272a;
border-color: rgb(255 255 255 / 5%);
}
.bj-chip-green {
--bj-chip-glow: rgb(47 133 90 / 65%);
background: #2f855a;
}
.bj-chip-blue {
--bj-chip-glow: rgb(59 110 165 / 60%);
background: #3b6ea5;
}
.bj-chip-red {
--bj-chip-glow: rgb(185 67 65 / 60%);
background: #b94341;
}
.bj-chip-white {
--bj-chip-glow: rgb(231 224 212 / 55%);
background: #e7e0d4;
border-color: rgb(120 110 95 / 62%);
}
.bj-chip-white::after {
border-color: rgb(120 110 95 / 40%);
}
@keyframes bj-chip-pop {
0% {
opacity: 0;
transform: translateY(-40%) scale(0.6);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* Blackjack readability: bump shared poker-* gameplay text inside the
blackjack felt only; poker tables keep their original sizing. */
.blackjack-felt .poker-console-kicker {
font-size: 0.7rem;
letter-spacing: 0.16em;
}
.blackjack-felt .poker-pot-plaque {
min-width: 6.5rem;
gap: 0.2rem;
padding: 0.35rem 1rem 0.4rem;
}
.blackjack-felt .poker-pot-plaque strong {
font-size: 1.4rem;
}
.blackjack-felt .poker-popup {
padding: 0.15rem 0.65rem;
font-size: 0.8rem;
}
.blackjack-felt .poker-action-log {
gap: 0.3rem;
}
.blackjack-felt .poker-action-log-line {
font-size: 0.82rem;
}
.blackjack-felt .poker-seat-action-badge {
padding: 0.16rem 0.55rem;
font-size: 0.72rem;
}
/* Lift the viewer's hand cluster clear of the bankroll + controls column
(the cluster peaks near 9.7rem when the bankroll pill carries chip piles). */
.blackjack-felt .poker-viewer-cards {
bottom: 10rem;
}
/* Chip flight: every chip starts stacked at one origin (the hand spot) and
launches one after another: payouts toward the bankroll pill, lost bets
toward the dealer. Fill-mode both holds unlaunched chips in the pile. */
.bj-chip-flight {
position: absolute;
bottom: 11rem;
left: 50%;
z-index: 35;
width: 1.6rem;
height: 0.95rem;
transform: translateX(-50%);
pointer-events: none;
}
.bj-chip-flight .bj-chip {
bottom: auto;
left: 0;
animation: bj-chip-fly 700ms ease-in both;
}
.bj-chip-flight-out .bj-chip {
animation-name: bj-chip-fly-out;
}
@keyframes bj-chip-fly {
0% {
opacity: 1;
transform: translateY(0) scale(1);
}
70% {
opacity: 1;
}
100% {
opacity: 0;
transform: translateY(9rem) scale(0.7);
}
}
@keyframes bj-chip-fly-out {
0% {
opacity: 1;
transform: translateY(0) scale(1);
}
70% {
opacity: 1;
}
100% {
opacity: 0;
transform: translateY(-18rem) scale(0.7);
}
}
@media (prefers-reduced-motion: reduce) {
.bj-chip-flight {
display: none;
}
}
/* Fullscreen contain-fit: absolute with zero insets + auto margins sizes the
felt to the largest 4:3 rectangle inside the viewport area (the same rules
browsers use for replaced elements), so it can never collapse to content
size and never overflow. Must stay after .poker-felt-table to win the
width tie. */
.blackjack-felt-fullscreen {
position: absolute;
inset: 0;
margin: auto;
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
aspect-ratio: 4 / 3;
}
/* Fixed Chip Bank card: dark panel, delta popup that lands large and scales
down before fading, and a sign-colored flash across the card on movement. */
.chip-bank-card {
background: rgb(0 0 0 / 55%);
backdrop-filter: blur(6px);
}
.chip-bank-delta {
animation: chip-bank-delta-pop 2.5s ease-out forwards;
}
@keyframes chip-bank-delta-pop {
0% {
opacity: 1;
transform: scale(1.7);
}
30% {
opacity: 1;
transform: scale(1);
}
75% {
opacity: 1;
}
100% {
opacity: 0;
transform: scale(1);
}
}
.chip-bank-flash-up {
animation: chip-bank-flash-up-anim 1.2s ease-out;
}
.chip-bank-flash-down {
animation: chip-bank-flash-down-anim 1.2s ease-out;
}
@keyframes chip-bank-flash-up-anim {
0% {
background: rgb(16 185 129 / 30%);
border-color: #10b981;
}
100% {
background: rgb(0 0 0 / 55%);
border-color: var(--border);
}
}
@keyframes chip-bank-flash-down-anim {
0% {
background: rgb(244 63 94 / 28%);
border-color: #f43f5e;
}
100% {
background: rgb(0 0 0 / 55%);
border-color: var(--border);
}
}
@media (prefers-reduced-motion: reduce) {
.chip-bank-delta {
animation: none;
}
.chip-bank-flash-up,
.chip-bank-flash-down {
animation: none;
}
}
/* Chip Bank card: small spinning indicator shown while waiting for the chip
bank to refresh after the player leaves a table. */
.chip-bank-spinner {
@apply size-5 animate-spin rounded-full border-[3px] border-white/20 border-t-white/80;
}
@media (prefers-reduced-motion: reduce) {
.bj-chip {
animation: none;
}
}