@tailwind base; @tailwind components; @tailwind utilities; /* ---------------------------------------------------------------------------- Day/night palette. JS sets data-phase={dawn|day|dusk|night} on . Each phase drives a small set of CSS vars; the page transitions smoothly thanks to the `transition-colors duration-1000` on . ---------------------------------------------------------------------------- */ :root, html[data-phase="day"] { --bg: #fff7e4; /* warm cream */ --bg-grad: #ffeec2; /* soft sun wash */ --card: #ffffff; --ink: #3a2e1f; --accent: #f2a541; /* sunshine yellow */ } html[data-phase="dawn"] { --bg: #ffe7d6; --bg-grad: #ffc9c9; --card: #fff4ea; --ink: #4a2e2a; --accent: #ff8a65; } html[data-phase="dusk"] { --bg: #ffd6a8; --bg-grad: #f7a07e; --card: #fff1de; --ink: #3d2417; --accent: #e6553a; } html[data-phase="night"] { --bg: #1a1f3a; --bg-grad: #2a3358; --card: #2d365a; --ink: #f1ecd8; /* moonlight */ --accent: #f9d976; /* lantern */ } @layer base { body { font-family: "Nunito", system-ui, sans-serif; } html { scroll-behavior: smooth; } /* Belt-and-braces: never let stray wide content push horizontal scroll on the whole page (the channel nav scrolls inside itself instead). */ html, body { overflow-x: hidden; } } @layer components { /* Channel pill row may exceed viewport on mobile once enough channels are configured. Let it scroll inside its own rounded container and hide the scrollbar chrome so it still reads as a clean pill. */ .pete-channel-nav { scrollbar-width: none; -ms-overflow-style: none; } .pete-channel-nav::-webkit-scrollbar { display: none; } } @layer utilities { .font-display { font-family: "Fredoka", "Nunito", system-ui, sans-serif; letter-spacing: -0.01em; } /* Soft signage shadow — Animal Crossing wooden plank vibe. */ .shadow-pete { box-shadow: 0 4px 0 rgba(60,40,20,0.10), 0 8px 24px rgba(60,40,20,0.08); } .shadow-pete-lg { box-shadow: 0 6px 0 rgba(60,40,20,0.12), 0 16px 32px rgba(60,40,20,0.12); } /* Per-channel theme colors. Kept as plain utilities so Tailwind doesn't need to know to safelist them. */ .bg-theme-gaming { background-color: #4caf7d; } .bg-theme-tech { background-color: #5aa9e6; } .bg-theme-politics { background-color: #e07a5f; } .bg-theme-eu { background-color: #003399; } .bg-theme-music { background-color: #b079d6; } .bg-theme-anime { background-color: #ec5e8a; } .bg-theme-foss { background-color: #d97706; } .bg-theme-kids { background-color: #14b8a6; } .bg-theme-finance { background-color: #10b981; } .bg-theme-lego { background-color: #d01012; } .bg-theme-adventure { background-color: #6d4bd8; } .text-theme-gaming { color: #2d8a5a; } .text-theme-tech { color: #2f7fb8; } .text-theme-politics { color: #b8523a; } .text-theme-eu { color: #003399; } .text-theme-music { color: #8a4fb8; } .text-theme-anime { color: #c33a6a; } .text-theme-foss { color: #b8530a; } .text-theme-kids { color: #0f766e; } .text-theme-finance { color: #059669; } .text-theme-lego { color: #b00d0e; } .text-theme-adventure { color: #5836b8; } .decoration-theme-gaming { text-decoration-color: #4caf7d; } .decoration-theme-tech { text-decoration-color: #5aa9e6; } .decoration-theme-politics { text-decoration-color: #e07a5f; } .decoration-theme-eu { text-decoration-color: #003399; } .decoration-theme-music { text-decoration-color: #b079d6; } .decoration-theme-anime { text-decoration-color: #ec5e8a; } .decoration-theme-foss { text-decoration-color: #d97706; } .decoration-theme-kids { text-decoration-color: #14b8a6; } .decoration-theme-finance { text-decoration-color: #10b981; } .decoration-theme-lego { text-decoration-color: #d01012; } .decoration-theme-adventure { text-decoration-color: #6d4bd8; } .border-theme-gaming { border-color: #4caf7d; } .border-theme-tech { border-color: #5aa9e6; } .border-theme-politics { border-color: #e07a5f; } .border-theme-eu { border-color: #003399; } .border-theme-music { border-color: #b079d6; } .border-theme-anime { border-color: #ec5e8a; } .border-theme-foss { border-color: #d97706; } .border-theme-kids { border-color: #14b8a6; } .border-theme-finance { border-color: #10b981; } .border-theme-lego { border-color: #d01012; } .border-theme-adventure { border-color: #6d4bd8; } /* "Posted to Matrix" glow — soft pulsing aura in the channel's theme color. */ .glow-theme-gaming { box-shadow: 0 0 0 2px rgba(76,175,125,0.35), 0 0 24px 4px rgba(76,175,125,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-tech { box-shadow: 0 0 0 2px rgba(90,169,230,0.35), 0 0 24px 4px rgba(90,169,230,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-politics { box-shadow: 0 0 0 2px rgba(224,122,95,0.35), 0 0 24px 4px rgba(224,122,95,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-eu { box-shadow: 0 0 0 2px rgba(0,51,153,0.35), 0 0 24px 4px rgba(0,51,153,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-music { box-shadow: 0 0 0 2px rgba(176,121,214,0.35), 0 0 24px 4px rgba(176,121,214,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-anime { box-shadow: 0 0 0 2px rgba(236,94,138,0.35), 0 0 24px 4px rgba(236,94,138,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-foss { box-shadow: 0 0 0 2px rgba(217,119,6,0.35), 0 0 24px 4px rgba(217,119,6,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-kids { box-shadow: 0 0 0 2px rgba(20,184,166,0.35), 0 0 24px 4px rgba(20,184,166,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-finance { box-shadow: 0 0 0 2px rgba(16,185,129,0.35), 0 0 24px 4px rgba(16,185,129,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-lego { box-shadow: 0 0 0 2px rgba(208,16,18,0.35), 0 0 24px 4px rgba(208,16,18,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } .glow-theme-adventure { box-shadow: 0 0 0 2px rgba(109,75,216,0.35), 0 0 24px 4px rgba(109,75,216,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; } @keyframes pete-glow-pulse { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.08); } } .line-clamp-3 { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; } /* "PAYWALLED" diagonal rubber stamp overlay — slapped across the whole card when the source is gated and no archive snapshot worked. The card stays clickable (link still goes to the original); the stamp is just visual warning. pointer-events:none keeps it from eating clicks. */ .paywall-stamp { position: absolute; inset: 0; pointer-events: none; display: flex; align-items: center; justify-content: center; z-index: 5; background: rgba(255, 250, 240, 0.18); } .paywall-stamp::before { content: "PAYWALLED"; font-family: var(--font-display, ui-sans-serif), system-ui, sans-serif; font-weight: 900; font-size: clamp(1rem, 3.2vw, 1.75rem); letter-spacing: 0.12em; color: rgba(180, 30, 30, 0.85); border: 0.28rem double rgba(180, 30, 30, 0.85); padding: 0.3rem 0.9rem; transform: rotate(-15deg); text-shadow: 0 1px 0 rgba(255,255,255,0.4); box-shadow: inset 0 0 0 2px rgba(255,255,255,0.15); background: rgba(255, 240, 230, 0.55); border-radius: 0.25rem; /* slight roughness to mimic a real ink stamp */ filter: contrast(1.05); } } /* ---------------------------------------------------------------------------- Feed / reader mode. A focused, one-story-at-a-time overlay driven by reader.js. Left/right arrows page through the stories currently on screen, marking each read as it's shown. Styling leans on the phase palette vars so it recolours with day/night like everything else. ---------------------------------------------------------------------------- */ @layer components { .pete-reader-backdrop { position: absolute; inset: 0; background: rgba(20, 14, 6, 0.55); backdrop-filter: blur(6px); } html[data-phase="night"] .pete-reader-backdrop { background: rgba(6, 8, 20, 0.65); } .pete-reader-shell { position: relative; height: 100%; width: 100%; max-width: 44rem; margin: 0 auto; display: flex; flex-direction: column; padding: max(env(safe-area-inset-top), 0.75rem) 1rem 0.75rem; gap: 0.75rem; } .pete-reader-bar { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; color: #fff; } .pete-reader-progress { font-family: "Fredoka", "Nunito", system-ui, sans-serif; font-weight: 600; font-size: 0.85rem; letter-spacing: 0.02em; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } .pete-reader-nav { display: flex; align-items: center; gap: 0.4rem; } .pete-reader-btn { display: inline-flex; align-items: center; justify-content: center; min-width: 2.25rem; height: 2.25rem; padding: 0 0.7rem; border-radius: 9999px; background: rgba(255, 255, 255, 0.14); color: #fff; font-weight: 700; font-size: 0.95rem; border: 2px solid rgba(255, 255, 255, 0.18); cursor: pointer; transition: background 0.15s ease, transform 0.15s ease, opacity 0.15s ease; } .pete-reader-btn:hover { background: rgba(255, 255, 255, 0.28); transform: translateY(-1px); } .pete-reader-btn:disabled { opacity: 0.35; cursor: default; transform: none; } .pete-reader-btn-open { background: var(--accent); border-color: transparent; color: #1c1305; text-decoration: none; } .pete-reader-btn-open:hover { background: var(--accent); filter: brightness(1.08); } /* Active/pressed toolbar toggle (Listen while speaking, Aa while menu open). */ .pete-reader-btn[aria-pressed="true"], .pete-reader-btn[aria-expanded="true"] { background: var(--accent); border-color: transparent; color: #1c1305; } /* Text-options popover, anchored under the Aa button in the reader bar. */ .pete-reader-type { position: relative; display: inline-flex; } .pete-reader-typemenu[hidden] { display: none; } .pete-reader-typemenu { position: absolute; top: calc(100% + 0.5rem); right: 0; z-index: 10; width: 15rem; padding: 0.75rem; border-radius: 1rem; background: var(--card); color: var(--ink); border: 2px solid rgba(0, 0, 0, 0.08); box-shadow: 0 10px 30px rgba(20, 14, 6, 0.28); display: flex; flex-direction: column; gap: 0.6rem; } html[data-phase="night"] .pete-reader-typemenu { border-color: rgba(255, 255, 255, 0.12); } .pete-reader-typerow { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; } .pete-reader-typelabel { font-size: 0.8rem; font-weight: 700; opacity: 0.7; } .pete-reader-typebtns { display: inline-flex; gap: 0.25rem; } .pete-reader-typebtns button { min-width: 2rem; height: 2rem; padding: 0 0.55rem; border-radius: 0.6rem; background: rgba(20, 14, 6, 0.06); color: var(--ink); font-weight: 700; font-size: 0.82rem; border: 2px solid transparent; cursor: pointer; transition: background 0.15s ease; } html[data-phase="night"] .pete-reader-typebtns button { background: rgba(255, 255, 255, 0.08); } .pete-reader-typebtns button:hover { background: rgba(20, 14, 6, 0.12); } .pete-reader-typebtns button.is-active { background: var(--accent); color: #1c1305; border-color: transparent; } .pete-reader-voicerow[hidden] { display: none; } .pete-reader-voice { font: inherit; font-size: 0.8rem; font-weight: 600; padding: 0.25rem 0.4rem; border-radius: 0.55rem; border: 1px solid rgba(20, 14, 6, 0.18); background: rgba(20, 14, 6, 0.06); color: inherit; max-width: 11rem; cursor: pointer; } html[data-phase="night"] .pete-reader-voice { border-color: rgba(255, 255, 255, 0.14); background: rgba(255, 255, 255, 0.08); } .pete-reader-scroll { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; border-radius: 1.75rem; } .pete-reader-scroll:focus { outline: none; } .pete-reader-article { background: var(--card); color: var(--ink); border-radius: 1.75rem; border: 2px solid rgba(0, 0, 0, 0.06); box-shadow: 0 6px 0 rgba(60, 40, 20, 0.12), 0 16px 32px rgba(60, 40, 20, 0.18); padding: clamp(1.25rem, 4vw, 2.5rem); } html[data-phase="night"] .pete-reader-article { border-color: rgba(255, 255, 255, 0.08); } .pete-reader-eyebrow { display: flex; align-items: center; flex-wrap: wrap; gap: 0.5rem; font-size: 0.75rem; margin-bottom: 0.85rem; } .pete-reader-chip { display: inline-flex; align-items: center; gap: 0.35rem; border-radius: 9999px; padding: 0.15rem 0.65rem; color: #fff; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; } .pete-reader-source { font-weight: 700; opacity: 0.75; } .pete-reader-time { opacity: 0.55; } .pete-reader-title { font-family: "Fredoka", "Nunito", system-ui, sans-serif; font-weight: 700; line-height: 1.15; letter-spacing: -0.01em; font-size: clamp(1.6rem, 4.5vw, 2.4rem); margin-bottom: 1rem; } .pete-reader-hero { width: 100%; border-radius: 1.1rem; margin-bottom: 1.25rem; background: rgba(0, 0, 0, 0.05); } /* Reader typography, driven by the Aa popover and persisted per-device. The size lives as a CSS var on the scroll container; font + paper are classes. */ .pete-reader-scroll { --reader-fs: 1.08rem; } .pete-reader-body { font-size: var(--reader-fs); line-height: 1.75; } .pete-reader-body p { margin-bottom: 1.05em; } .pete-reader-body p:last-child { margin-bottom: 0; } .pete-reader-scroll.is-size-s { --reader-fs: 0.98rem; } .pete-reader-scroll.is-size-m { --reader-fs: 1.08rem; } .pete-reader-scroll.is-size-l { --reader-fs: 1.22rem; } .pete-reader-scroll.is-size-xl { --reader-fs: 1.4rem; } .pete-reader-scroll.is-serif .pete-reader-body { font-family: Georgia, "Iowan Old Style", "Times New Roman", serif; } /* Sepia paper: warm background + ink regardless of day/night phase. */ .pete-reader-scroll.is-sepia .pete-reader-article { background: #f6ecd6; color: #4a3a28; border-color: rgba(74, 58, 40, 0.16); } .pete-reader-scroll.is-sepia .pete-reader-note { border-top-color: rgba(74, 58, 40, 0.18); opacity: 0.8; } .pete-reader-scroll.is-sepia .pete-reader-hero { background: rgba(74, 58, 40, 0.08); } /* Currently-spoken paragraph highlight while reading aloud. */ .pete-reader-body p.is-speaking { background: color-mix(in srgb, var(--accent) 32%, transparent); border-radius: 0.4rem; box-shadow: 0 0 0 0.35rem color-mix(in srgb, var(--accent) 32%, transparent); } .pete-reader-note { margin-top: 1.25rem; padding-top: 1rem; border-top: 1px solid rgba(0, 0, 0, 0.1); font-size: 0.85rem; opacity: 0.7; } html[data-phase="night"] .pete-reader-note { border-color: rgba(255, 255, 255, 0.12); } .pete-reader-note a { color: var(--accent); font-weight: 700; } .pete-reader-empty { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; gap: 0.75rem; padding: clamp(2rem, 8vw, 4rem) 1rem; color: var(--ink); } .pete-reader-empty-emoji { font-size: 2.5rem; } /* Transient confirmation toast (e.g. "Link copied") shown over the reader. */ .pete-reader-toast { position: absolute; left: 50%; bottom: 4.5rem; transform: translate(-50%, 0.5rem); z-index: 20; padding: 0.5rem 0.9rem; border-radius: 9999px; background: rgba(20, 14, 6, 0.9); color: #fff; font-weight: 700; font-size: 0.85rem; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); opacity: 0; pointer-events: none; transition: opacity 0.2s ease, transform 0.2s ease; } .pete-reader-toast.is-shown { opacity: 1; transform: translate(-50%, 0); } .pete-reader-hint { text-align: center; color: rgba(255, 255, 255, 0.85); font-size: 0.75rem; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } .pete-reader-hint kbd { font-family: ui-monospace, monospace; background: rgba(255, 255, 255, 0.16); border-radius: 0.3rem; padding: 0.05rem 0.3rem; } @media (max-width: 640px) { .pete-reader-hint { display: none; } /* With Listen/Share/Aa added, let the toolbar wrap instead of overflowing. */ .pete-reader-bar { flex-wrap: wrap; } .pete-reader-nav { flex-wrap: wrap; justify-content: flex-end; row-gap: 0.4rem; } } /* "You might also like" rail, shown under the article in feed mode. Lives inside the reader's scroll area, below the article card. */ .pete-reader-related { width: 100%; margin: 0.85rem auto 0; } .pete-reader-related-title { font-family: "Fredoka", "Nunito", system-ui, sans-serif; font-weight: 700; font-size: 0.95rem; color: #fff; margin: 0 0 0.6rem; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } .pete-reader-related-grid { display: grid; gap: 0.6rem; } .pete-reader-related-card { display: flex; align-items: center; gap: 0.75rem; background: var(--card); color: var(--ink); border-radius: 1rem; border: 2px solid rgba(0, 0, 0, 0.06); padding: 0.6rem; text-decoration: none; transition: transform 0.15s ease, box-shadow 0.15s ease; } .pete-reader-related-card:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(60, 40, 20, 0.16); } html[data-phase="night"] .pete-reader-related-card { border-color: rgba(255, 255, 255, 0.08); } .pete-reader-related-thumb { width: 4.5rem; height: 3.25rem; flex-shrink: 0; object-fit: cover; border-radius: 0.6rem; background: rgba(0, 0, 0, 0.06); } .pete-reader-related-meta { min-width: 0; display: flex; flex-direction: column; gap: 0.25rem; } .pete-reader-related-eyebrow { display: flex; align-items: center; flex-wrap: wrap; gap: 0.4rem; font-size: 0.7rem; } .pete-reader-related-source { font-weight: 700; opacity: 0.7; } .pete-reader-related-headline { font-weight: 700; line-height: 1.25; font-size: 0.92rem; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } /* Grid treatment for stories already read in feed mode: dimmed, with a small corner check. Hovering restores full opacity so nothing feels lost. */ [data-story-card][data-read="1"] { opacity: 0.5; transition: opacity 0.2s ease; } [data-story-card][data-read="1"]:hover { opacity: 1; } [data-story-card][data-read="1"]::after { content: "✓"; position: absolute; top: 0.6rem; right: 0.6rem; z-index: 6; display: grid; place-items: center; height: 1.5rem; width: 1.5rem; border-radius: 9999px; background: rgba(20, 14, 6, 0.72); color: #fff; 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: 8.6rem; } /* One card. The wrapper does the flight, the inner face does the flip, so the two never fight over the same transform. */ /* The size is a variable because solitaire has seven columns to fit and blackjack has two hands. Everything below is in terms of it, so a table can shrink its cards without a second set of rules. */ .pete-card { perspective: 700px; height: var(--card-h, 8.4rem); width: var(--card-w, 6rem); 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); } /* 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 { 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 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: 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-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 — and then a beat of settling, because a card that stops dead on the felt looks like it was pasted there. It overshoots a little and rocks back onto --tilt, the small resting angle the JS gives each card so a hand looks handled rather than typeset. */ @keyframes pete-deal { 0% { opacity: 0; transform: translate(var(--deal-x, 14rem), var(--deal-y, -7rem)) scale(0.72) rotate(9deg); } 62% { opacity: 1; transform: translate(0, 0.35rem) scale(1.05) rotate(calc(var(--tilt, 0deg) - 2deg)); } 100% { opacity: 1; transform: translate(0, 0) scale(1) rotate(var(--tilt, 0deg)); } } /* And the weight: the felt takes the hit. The shadow under a landing card spreads and darkens for the instant it arrives. */ .pete-card::after { content: ""; position: absolute; inset: auto 6% -0.35rem 6%; height: 0.6rem; border-radius: 999px; background: rgba(0, 0, 0, 0.35); filter: blur(5px); animation: pete-thud 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards; } @keyframes pete-thud { 0%, 55% { opacity: 0; transform: scale(0.4); } 72% { opacity: 0.9; transform: scale(1.15); } 100% { opacity: 0.45; transform: scale(1); } } /* 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: rotate(var(--tilt, 0deg)) translateY(0); } 40% { transform: rotate(var(--tilt, 0deg)) translateY(-0.6rem); } } /* A losing hand doesn't just sit there being wrong: it greys off. */ .pete-hand[data-won="-1"] .pete-card { filter: saturate(0.45) brightness(0.78); transition: filter 0.5s ease 0.2s; } /* ---- chips --------------------------------------------------------------- One disc, three jobs: the buttons you bet with, the stack sitting in the bet spot, and the chip in mid-air between them. Same face on all three, because they are meant to read as the *same chip* being moved around. */ .pete-disc { position: relative; border-radius: 999px; background: radial-gradient(circle at 50% 30%, rgba(255,255,255,0.30), transparent 62%), var(--chip, #e07a5f); border: 2px solid rgba(0,0,0,0.22); box-shadow: 0 3px 0 rgba(0,0,0,0.28), 0 6px 14px rgba(0,0,0,0.20); } /* The edge spots every casino chip has, cut as a dashed ring inside the rim. */ .pete-disc::before { content: ""; position: absolute; inset: 11%; border-radius: 999px; border: 2px dashed rgba(255,255,255,0.55); } .pete-disc[data-chip="5"] { --chip: #5aa9e6; } .pete-disc[data-chip="25"] { --chip: #4caf7d; } .pete-disc[data-chip="100"] { --chip: #2b2118; } .pete-disc[data-chip="500"] { --chip: #b079d6; } .pete-chip { /* the buttons */ transition: transform 0.12s ease; } .pete-chip > span { position: relative; /* over the dashed ring */ } .pete-chip:active { transform: translateY(1px) scale(0.94); } /* The bet spot: where your stake actually sits while the hand is played. */ .pete-spot { position: relative; display: grid; place-items: center; height: 7rem; width: 7rem; flex: none; border-radius: 999px; border: 2px dashed rgba(255,255,255,0.35); background: radial-gradient(circle at 50% 40%, rgba(255,255,255,0.10), transparent 70%); box-shadow: inset 0 0 0 6px rgba(0,0,0,0.08); transition: box-shadow 0.3s ease, border-color 0.3s ease; } .pete-spot[data-live="1"] { border-color: rgba(var(--glow, 242,181,61), 0.85); box-shadow: inset 0 0 0 6px rgba(0,0,0,0.08), 0 0 22px rgba(var(--glow, 242,181,61), 0.35); } .pete-spot-label { font-size: 0.65rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: rgba(255,255,255,0.35); } .pete-spot[data-live="1"] .pete-spot-label { opacity: 0; } /* The stack in the spot. Each chip is offset up the pile by its index, so ten chips look like ten chips rather than one chip with a number on it. */ .pete-stack { position: absolute; inset: 0; pointer-events: none; } .pete-stack .pete-disc { position: absolute; left: 50%; top: 50%; height: 2.6rem; width: 2.6rem; margin: -1.3rem 0 0 -1.3rem; /* Each chip sits proud of the one below it by more than its own rim, so a stack of three reads as three. Less than this and they hide inside each other and a 150 bet looks the same as a 25 one. */ transform: translateY(calc(-0.5rem * var(--i, 0))) rotate(var(--spin, 0deg)); box-shadow: 0 2px 0 rgba(0,0,0,0.35), 0 4px 8px rgba(0,0,0,0.25); animation: pete-chip-land 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) backwards; } @keyframes pete-chip-land { from { transform: translateY(calc(-0.5rem * var(--i, 0) - 1.1rem)) rotate(var(--spin, 0deg)) scale(1.18); } } .pete-spot-total { position: absolute; bottom: -0.6rem; left: 50%; transform: translateX(-50%); white-space: nowrap; border-radius: 999px; background: rgba(0,0,0,0.45); padding: 0.1rem 0.6rem; font-size: 0.72rem; font-weight: 700; font-variant-numeric: tabular-nums; color: #fff; } /* A chip in mid-air. Lives in a fixed overlay so it can cross from the betting buttons, over the page, and onto the felt without any container clipping it. */ .pete-fly-layer { position: fixed; inset: 0; z-index: 60; pointer-events: none; overflow: hidden; } .pete-fly { position: absolute; height: 2.6rem; width: 2.6rem; margin: -1.3rem 0 0 -1.3rem; will-change: transform; } /* The house's own chips, racked on the felt. This is where a payout comes from, and where a losing stake goes — so the money has somewhere to be. It sits alongside the shoe rather than over on the left, because the left of the felt is where the dealer's cards land. */ .pete-rack { position: absolute; top: 1.25rem; right: 5.75rem; display: flex; align-items: flex-end; gap: 0.4rem; padding: 0.5rem 0.6rem 0.45rem; border-radius: 0.75rem; background: rgba(0,0,0,0.20); box-shadow: inset 0 0 0 2px rgba(255,255,255,0.06); } /* A column in the rack is a *stack*, so it's striped: one band per chip, with the shadow of the chip above it cut into each. A plain coloured lozenge here read as a blob, which is the one thing a pile of money shouldn't. */ .pete-rack span { display: block; width: 1.7rem; border-radius: 0.35rem; background: linear-gradient(90deg, rgba(0,0,0,0.28), transparent 35%, transparent 65%, rgba(0,0,0,0.28)), repeating-linear-gradient(180deg, rgba(255,255,255,0.18) 0 0.06rem, var(--chip, #e07a5f) 0.06rem 0.3rem, rgba(0,0,0,0.35) 0.3rem 0.36rem); border: 1px solid rgba(0,0,0,0.35); height: calc(0.36rem * var(--stack, 3)); } .pete-rack span[data-chip="5"] { --chip: #5aa9e6; } .pete-rack span[data-chip="25"] { --chip: #4caf7d; } .pete-rack span[data-chip="100"] { --chip: #2b2118; } .pete-rack span[data-chip="500"] { --chip: #b079d6; } /* On a phone the rack has to get out of the way. At its desk size it is a 147px block sitting 92px in from the edge — a corner on a wide felt, but the middle of the table on a 390px one, where it sat on top of trivia's multiplier. So on a small screen it gets smaller, and where there's nothing in the corner it pulls into the corner. Where the rack sits is the one thing that differs per table, which is what data-at says. Unmarked, it's alone in the corner. "shoe" means blackjack, where the 5.75rem inset is not a margin but the width of the shoe it sits beside — pull that one to the edge and it slides under the deck. "rail" means solitaire, where the rack isn't on the felt at all (position: static). */ @media (max-width: 639px) { .pete-rack:not([data-at="rail"]) { gap: 0.25rem; padding: 0.35rem 0.4rem 0.3rem; } .pete-rack:not([data-at="rail"]) span { width: 1.15rem; } .pete-rack:not([data-at]) { right: 1rem; } } /* The burst. A natural gets confetti; nothing else in the room does, which is what keeps it worth something. */ .pete-spark { position: absolute; height: 0.75rem; width: 0.45rem; border-radius: 2px; box-shadow: 0 1px 3px 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; } @keyframes pete-think { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; transform: translateY(-1px); } } /* ---- hangman ------------------------------------------------------------- The gallows is the meter: it counts down your lives and your winnings at the same time. So a miss has to *land* — the limb draws itself in along its own length, the whole thing flinches, and the multiple falls. Three parts of one event, and the game is about watching it happen to you. */ .pete-gallows { overflow: visible; fill: none; stroke-linecap: round; stroke-linejoin: round; } .pete-gallows-frame path { stroke: rgba(0, 0, 0, 0.35); stroke-width: 6; } /* The rope. Same post, thinner, so it reads as something that would hold. */ .pete-gallows-frame path:last-child { stroke: rgba(0, 0, 0, 0.3); stroke-width: 3.5; } .pete-gallows-body > * { stroke: #fff; stroke-width: 5; opacity: 0; filter: drop-shadow(0 2px 0 rgba(0, 0, 0, 0.25)); } .pete-gallows-body > [data-on="1"] { opacity: 1; } /* Drawn, not faded in: the stroke runs on along its own path. 260 is longer than any limb here, which is all a dash offset needs to be. */ .pete-part-draw { stroke-dasharray: 260; animation: pete-part 0.45s cubic-bezier(0.22, 1, 0.36, 1) backwards; } @keyframes pete-part { from { stroke-dashoffset: 260; } to { stroke-dashoffset: 0; } } .pete-shake { animation: pete-shake 0.42s cubic-bezier(0.36, 0.07, 0.19, 0.97); } @keyframes pete-shake { 10%, 90% { transform: translateX(-1.5px); } 20%, 80% { transform: translateX(3px); } 30%, 50%, 70% { transform: translateX(-5px); } 40%, 60% { transform: translateX(5px); } } /* The phrase. Tiles wrap between words, never inside one. */ .pete-board { display: flex; flex-wrap: wrap; gap: 0.35rem 1.1rem; min-height: 5rem; align-items: center; } .pete-word { display: flex; gap: 0.3rem; } .pete-tile { display: grid; place-items: center; height: 2.9rem; width: 2.2rem; border-radius: 0.5rem; font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 1.4rem; font-weight: 700; color: #fff; background: rgba(0, 0, 0, 0.22); border-bottom: 3px solid rgba(0, 0, 0, 0.28); text-transform: uppercase; } /* A letter you've earned sits proud of the ones you haven't. */ .pete-tile[data-up="1"] { background: rgba(255, 255, 255, 0.95); color: #2b2118; border-bottom-color: rgba(0, 0, 0, 0.35); } /* Punctuation is scaffolding: it was never yours to guess, so it gets no tile to guess it into. */ .pete-tile[data-punct="1"] { background: none; border: 0; width: auto; min-width: 0.7rem; color: rgba(255, 255, 255, 0.55); } .pete-tile-hit { animation: pete-tile-hit 0.34s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes pete-tile-hit { 0% { transform: rotateX(90deg) scale(1.1); } 100% { transform: rotateX(0) scale(1); } } .pete-missed { display: grid; place-items: center; height: 1.5rem; min-width: 1.5rem; padding: 0 0.3rem; border-radius: 0.375rem; background: rgba(0, 0, 0, 0.25); font-size: 0.75rem; font-weight: 700; color: rgba(255, 255, 255, 0.5); text-decoration: line-through; text-transform: uppercase; } /* What a win is worth, right now. */ .pete-meter { display: flex; align-items: baseline; gap: 0.5rem; border-radius: 999px; background: rgba(0, 0, 0, 0.28); padding: 0.4rem 0.9rem; box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1); transition: box-shadow 0.3s ease; } .pete-meter-label { font-size: 0.65rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: rgba(255, 255, 255, 0.45); } .pete-meter-value { font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--accent); } /* Down at the floor: a win now hands back the stake and nothing else. */ .pete-meter[data-cold="1"] .pete-meter-value { color: rgba(255, 255, 255, 0.55); } .pete-meter[data-hit="1"] { box-shadow: inset 0 0 0 2px rgba(204, 61, 74, 0.9); animation: pete-meter-hit 0.4s ease; } @keyframes pete-meter-hit { 0% { transform: scale(1); } 35% { transform: scale(0.94); } 100% { transform: scale(1); } } /* The keyboard. */ .pete-keys { display: grid; gap: 0.35rem; } .pete-key-row { display: flex; justify-content: center; gap: 0.35rem; } .pete-key-row[data-digits="1"] { margin-top: 0.25rem; opacity: 0.75; } .pete-key-row[data-digits="1"] .pete-key { height: 2rem; min-width: 1.8rem; font-size: 0.8rem; } .pete-key { height: 2.75rem; min-width: 2.2rem; flex: 0 1 2.4rem; border-radius: 0.6rem; background: color-mix(in srgb, var(--ink) 6%, transparent); border: 2px solid color-mix(in srgb, var(--ink) 10%, transparent); font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-weight: 700; color: var(--ink); transition: transform 0.08s ease, background 0.15s ease, opacity 0.15s ease; } .pete-key:hover:not(:disabled) { background: color-mix(in srgb, var(--ink) 12%, transparent); } .pete-key:active:not(:disabled) { transform: translateY(1px) scale(0.96); } .pete-key:disabled { cursor: default; } /* A key that's been spent looks spent — otherwise you spend it twice. */ .pete-key[data-state="hit"] { background: #4caf7d; border-color: #3d9367; color: #fff; opacity: 1; } .pete-key[data-state="miss"] { background: color-mix(in srgb, var(--ink) 4%, transparent); color: color-mix(in srgb, var(--ink) 30%, transparent); text-decoration: line-through; } .pete-key:disabled[data-state=""] { opacity: 0.4; } /* Picking a length. The one you're on is lit. */ .pete-tier { background: color-mix(in srgb, var(--ink) 4%, transparent); border-color: color-mix(in srgb, var(--ink) 10%, transparent); } .pete-tier:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); } .pete-tier[data-on="1"] { background: color-mix(in srgb, var(--accent) 18%, transparent); border-color: var(--accent); } /* ---- trivia -------------------------------------------------------------- The clock is the game, so the clock is the biggest thing on the felt: a bar that drains across the top of the question, going hot as it runs out. It is *decoration* — the server timed the answer the moment it arrived — but it has to be honest decoration, so it's driven from the seconds the server said were left rather than from when the browser happened to paint. */ .pete-clock { position: relative; height: 0.6rem; border-radius: 999px; background: rgba(0, 0, 0, 0.3); overflow: hidden; box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.08); } .pete-clock-fill { height: 100%; width: 100%; border-radius: 999px; background: var(--accent); transform-origin: left center; /* Driven by a transform the browser can run on its own: a width animated on every frame from JS is a layout on every frame. */ transform: scaleX(1); } /* The last few seconds. The bar stops being a progress meter and starts being a warning. */ .pete-clock[data-hot="1"] .pete-clock-fill { background: #cc3d4a; } .pete-clock[data-hot="1"] { animation: pete-clock-pulse 0.7s ease-in-out infinite; } @keyframes pete-clock-pulse { 0%, 100% { box-shadow: inset 0 0 0 2px rgba(204, 61, 74, 0.35); } 50% { box-shadow: inset 0 0 0 2px rgba(204, 61, 74, 0.95); } } /* The four answers. Big targets, because the clock is already the hard part — a question you lose to a mis-tap is a question about pointing. */ .pete-answer { position: relative; display: flex; align-items: center; gap: 0.75rem; width: 100%; border-radius: 1rem; padding: 0.85rem 1rem; text-align: left; font-weight: 600; color: #fff; background: rgba(0, 0, 0, 0.26); box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1); transition: background 0.15s ease, transform 0.1s ease, box-shadow 0.2s ease; } .pete-answer:hover:not(:disabled) { background: rgba(0, 0, 0, 0.36); box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.28); } .pete-answer:active:not(:disabled) { transform: translateY(1px); } .pete-answer:disabled { cursor: default; } /* The letter down the side, so an answer can be picked with the keyboard and the key you press is printed on the thing you're pressing it for. */ .pete-answer-key { display: grid; place-items: center; height: 1.75rem; width: 1.75rem; flex: none; border-radius: 0.6rem; background: rgba(255, 255, 255, 0.12); font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.8rem; font-weight: 700; color: rgba(255, 255, 255, 0.7); } /* How it went. Only ever set once the server has decided — the browser has no idea which of these is right until it's told. */ .pete-answer[data-state="right"] { background: rgba(46, 160, 103, 0.9); box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.5); } .pete-answer[data-state="wrong"] { background: rgba(204, 61, 74, 0.9); box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35); animation: pete-answer-no 0.45s ease; } /* The one they *should* have picked, shown alongside the one they did. */ .pete-answer[data-state="missed"] { box-shadow: inset 0 0 0 2px rgba(46, 160, 103, 0.95); } .pete-answer[data-state="dim"] { opacity: 0.4; } @keyframes pete-answer-no { 0%, 100% { transform: translateX(0); } 20% { transform: translateX(-6px); } 45% { transform: translateX(5px); } 70% { transform: translateX(-3px); } } /* The ladder: one pip per rung, filling as they're climbed. It is the only picture of how far there is left to fall. */ .pete-ladder { display: flex; flex-wrap: wrap; gap: 0.3rem; } .pete-rung { height: 0.5rem; width: 1.1rem; border-radius: 999px; background: rgba(255, 255, 255, 0.14); transition: background 0.3s ease, transform 0.3s ease; } .pete-rung[data-on="1"] { background: var(--accent); transform: scaleY(1.35); } /* ---- solitaire ----------------------------------------------------------- Seven columns, four foundations, a stock and a waste, all of which have to fit across the felt on a phone as well as a desk. So the card size is one variable derived from the width available, and every gap and fan below is derived from *that* — the board scales as one thing rather than as nine things that each stop fitting at a different width. The cards themselves don't move by CSS here. A move in solitaire can take a card from anywhere to anywhere, so the table re-renders the board and then animates each card from where it just was (see the FLIP note in solitaire.js). CSS's job is only to say where things sit. */ .pete-solitaire { --card-w: clamp(2.5rem, 10.6vw, 5.2rem); --card-h: calc(var(--card-w) * 1.4); --fan-up: calc(var(--card-h) * 0.29); /* how much of a face-up card shows under the next */ --fan-down: calc(var(--card-h) * 0.14); /* a face-down one shows less: there's nothing to read */ } /* An empty place a card could be: the stock when it's spent, a foundation waiting for its ace, a column waiting for a king. Same footprint as a card, so nothing on the board reflows when one empties. */ .pete-slot { position: relative; display: grid; place-items: center; height: var(--card-h); width: var(--card-w); flex: none; border-radius: 0.55rem; border: 2px dashed rgba(255, 255, 255, 0.25); background: rgba(0, 0, 0, 0.12); box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.18); transition: border-color 0.18s ease, background 0.18s ease, transform 0.12s ease; } .pete-slot-glyph { font-size: calc(var(--card-w) * 0.42); line-height: 1; color: rgba(255, 255, 255, 0.3); } .pete-slot-glyph[data-red="1"] { color: rgba(255, 170, 170, 0.35); } /* A card sitting *in* a slot — the top of a foundation. The slot keeps its footprint whether or not there's a card on it, so an ace going home doesn't reflow the row, and a drop target has somewhere to be while it's empty. */ .pete-slot > .pete-card { position: absolute; inset: 0; height: 100%; width: 100%; } /* The stock is a button, and it looks like the back of a card because that is what it is: a pile you can turn over. */ .pete-stock { border-style: solid; border-color: rgba(0, 0, 0, 0.15); background: repeating-linear-gradient(45deg, rgba(255,255,255,0.10) 0 6px, transparent 6px 12px), linear-gradient(150deg, #b4553f, #8d3f2f); cursor: pointer; } .pete-stock:hover { filter: brightness(1.08); } .pete-stock:active { transform: translateY(1px) scale(0.98); } /* Spent, but there's a pass left: it stops being a pile of cards and starts being the gesture of turning the waste back over. */ .pete-stock[data-empty="1"] { background: rgba(0, 0, 0, 0.12); border-style: dashed; border-color: rgba(255, 255, 255, 0.25); } /* Spent, and no passes left. Not a button any more — and it says so by looking like the dead thing it is rather than by silently refusing clicks. */ .pete-stock[data-dead="1"] { cursor: default; opacity: 0.4; } .pete-stock[data-dead="1"]:hover { filter: none; } .pete-stock[data-dead="1"]:active { transform: none; } .pete-slot-count { position: absolute; bottom: -0.5rem; left: 50%; transform: translateX(-50%); border-radius: 999px; background: rgba(0, 0, 0, 0.5); padding: 0.05rem 0.5rem; font-size: 0.7rem; font-weight: 700; font-variant-numeric: tabular-nums; color: #fff; } .pete-slot-recycle { color: rgba(255, 255, 255, 0.45); } .pete-slot-recycle svg { height: 1.4rem; width: 1.4rem; } /* The waste. Three cards fanned sideways, the top one on the right — you can see what's under it, and only the top one is yours to take. */ .pete-waste { position: relative; display: flex; height: var(--card-h); min-width: var(--card-w); } .pete-waste .pete-card + .pete-card { margin-left: calc(var(--card-w) * -0.72); } .pete-waste .pete-card { position: relative; } /* The seven columns. They share the width evenly and never scroll: a board you have to scroll sideways is a board you can't plan on. */ .pete-tableau { display: grid; grid-template-columns: repeat(7, var(--card-w)); justify-content: space-between; gap: 0.5rem; align-items: start; min-height: calc(var(--card-h) * 2.6); } .pete-col { display: flex; flex-direction: column; align-items: center; min-height: var(--card-h); } /* Cards overlap up the column, and how much of one shows depends on whether there's anything to see. The rule keys off the card *above*, which is why it's an adjacent-sibling selector and not something the JS has to compute. */ .pete-col .pete-card { position: relative; } .pete-col .pete-card[data-face="down"] + .pete-card { margin-top: calc(var(--fan-down) - var(--card-h)); } .pete-col .pete-card[data-face="up"] + .pete-card { margin-top: calc(var(--fan-up) - var(--card-h)); } /* A card you can pick up says so. */ .pete-card[data-live="1"] { cursor: pointer; } .pete-card[data-live="1"]:hover .pete-card-front { filter: brightness(1.06); } /* The card (or run) in your hand: lifted off the felt, and glowing, so it's obvious what a click on a column is about to move. */ .pete-card[data-held="1"] { z-index: 5; transform: translateY(-0.55rem); transition: transform 0.14s cubic-bezier(0.34, 1.56, 0.64, 1); } .pete-card[data-held="1"] .pete-card-front { box-shadow: 0 3px 0 rgba(0,0,0,0.18), 0 10px 22px rgba(0,0,0,0.32), 0 0 0 3px rgba(242, 181, 61, 0.9); } /* A place the held card would actually go. Lit *before* you click it, because the alternative is learning the rules by being told no. */ .pete-slot[data-drop="1"], .pete-col[data-drop="1"] .pete-slot, .pete-col[data-drop="1"] .pete-card:last-child .pete-card-front { border-color: rgba(242, 181, 61, 0.9); box-shadow: 0 0 0 3px rgba(242, 181, 61, 0.45); } .pete-slot[data-drop="1"], .pete-col[data-drop="1"] .pete-slot { background: rgba(242, 181, 61, 0.12); } /* A move that won't go. Said in the one language a board can speak. */ .pete-nope { animation: pete-shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97); } /* A card arriving on a foundation lands with a flash: it is the only move in the game that pays you, so it is the only one that gets a noise. */ .pete-home-flash { animation: pete-home 0.5s ease-out; } @keyframes pete-home { 0% { box-shadow: 0 0 0 0 rgba(242, 181, 61, 0.9); } 100% { box-shadow: 0 0 0 1.4rem rgba(242, 181, 61, 0); } } /* The rail: the house's rack, what you've banked, the meter. On a wide screen it's a column down the right of the felt; on a narrow one it lies down and sits under the board. */ .pete-rail { display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; justify-content: center; gap: 1rem 1.5rem; } @media (min-width: 1024px) { .pete-rail { flex-direction: column; justify-content: flex-start; width: 9.5rem; gap: 1.5rem; } } /* In the rail the rack is just another thing in a column, so it drops the absolute positioning it uses when it's pinned to the corner of a felt. */ .pete-rack[data-at="rail"] { position: static; justify-content: center; } /* ---- uno ------------------------------------------------------------------ The card is the whole look: a coloured rectangle with a white oval laid across it at an angle. Drop the oval and it reads as a button, so the oval is not decoration — it is what makes the thing a card. The bots are drawn from a *count*. That is all the server sends, and the fan of backs up there is generated from a number rather than from cards, because there are no cards to draw. */ /* The felt takes a tint of the colour in play. After a wild, the card on the pile is not the colour you have to follow — this is the table saying so. */ .pete-uno[data-c="red"] { --play: #d64545; } .pete-uno[data-c="blue"] { --play: #3d7fd6; } .pete-uno[data-c="yellow"] { --play: #e0b02c; } .pete-uno[data-c="green"] { --play: #46a86b; } .pete-uno::before { content: ""; position: absolute; inset: 0; pointer-events: none; background: radial-gradient(80% 55% at 50% 45%, var(--play, transparent), transparent 70%); opacity: 0.16; transition: opacity 0.5s ease, background 0.5s ease; } .pete-uno-card { position: relative; height: var(--uno-h, 6.4rem); width: var(--uno-w, 4.3rem); border-radius: 0.55rem; padding: 0.28rem; background: #fff; box-shadow: 0 3px 0 rgba(0,0,0,0.22), 0 6px 14px rgba(0,0,0,0.18); flex: none; } .pete-uno-face { position: relative; display: grid; place-items: center; height: 100%; width: 100%; border-radius: 0.38rem; overflow: hidden; background: var(--uno, #2b2118); color: #fff; } .pete-uno-card[data-c="red"] { --uno: #d64545; } .pete-uno-card[data-c="blue"] { --uno: #3d7fd6; } .pete-uno-card[data-c="yellow"] { --uno: #e0b02c; } .pete-uno-card[data-c="green"] { --uno: #46a86b; } .pete-uno-card[data-c="wild"] { --uno: #2b2118; } /* A wild that has been played wears the colour it was called as — the pile has to show what you must follow, not what was printed. */ .pete-uno-card[data-named="red"] { box-shadow: 0 0 0 3px #d64545, 0 3px 0 rgba(0,0,0,0.22); } .pete-uno-card[data-named="blue"] { box-shadow: 0 0 0 3px #3d7fd6, 0 3px 0 rgba(0,0,0,0.22); } .pete-uno-card[data-named="yellow"] { box-shadow: 0 0 0 3px #e0b02c, 0 3px 0 rgba(0,0,0,0.22); } .pete-uno-card[data-named="green"] { box-shadow: 0 0 0 3px #46a86b, 0 3px 0 rgba(0,0,0,0.22); } /* The oval. Tilted, because it is tilted on the card in your hand. */ .pete-uno-oval { position: relative; z-index: 1; display: grid; place-items: center; height: 78%; width: 62%; border-radius: 50%; transform: rotate(-24deg); background: #fff; color: var(--uno, #2b2118); font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 1.45rem; font-weight: 700; line-height: 1; text-shadow: 0 1px 0 rgba(0,0,0,0.10); } .pete-uno-card[data-c="wild"] .pete-uno-oval { color: #2b2118; font-size: 1.15rem; } .pete-uno-corner { position: absolute; z-index: 1; font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.62rem; font-weight: 700; line-height: 1; color: #fff; text-shadow: 0 1px 1px rgba(0,0,0,0.35); } .pete-uno-corner[data-at="tl"] { top: 0.22rem; left: 0.28rem; } .pete-uno-corner[data-at="br"] { bottom: 0.22rem; right: 0.28rem; transform: rotate(180deg); } /* The four quadrants of a wild, under the oval. */ .pete-uno-wheel { position: absolute; inset: 0; background: conic-gradient(#d64545 0 25%, #e0b02c 0 50%, #46a86b 0 75%, #3d7fd6 0); opacity: 0.92; } /* The back. Every card you are not entitled to see is one of these. */ .pete-uno-card-back { padding: 0.28rem; } .pete-uno-back { display: grid; place-items: center; height: 100%; width: 100%; border-radius: 0.38rem; background: radial-gradient(120% 80% at 50% 0%, rgba(255,255,255,0.16), transparent 60%), #2b2118; } .pete-uno-back::after { content: "UNO"; display: block; transform: rotate(-24deg); padding: 0.1rem 0.35rem; border-radius: 999px; background: #e0b02c; color: #2b2118; font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.6rem; font-weight: 700; letter-spacing: 0.02em; } /* Your hand. It fans, it wraps, and a card you can play stands up out of it — being shown what goes is the game teaching you, and the server still decides. */ .pete-uno-hand { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.4rem; min-height: 6.8rem; padding-top: 0.6rem; } .pete-uno-hand .pete-uno-card { animation: pete-uno-in 0.34s cubic-bezier(0.22, 1, 0.36, 1) backwards; animation-delay: calc(var(--i, 0) * 45ms); transition: transform 0.16s ease, filter 0.16s ease; } .pete-uno-hand .pete-uno-card[data-on="1"] { cursor: pointer; transform: translateY(-0.5rem); } .pete-uno-hand .pete-uno-card[data-on="1"]:hover { transform: translateY(-1.1rem) scale(1.03); } .pete-uno-hand .pete-uno-card[data-on="0"] { filter: brightness(0.62) saturate(0.7); } @keyframes pete-uno-in { from { transform: translateY(2rem) rotate(6deg); opacity: 0; } to { transform: translateY(-0.5rem); opacity: 1; } } /* A seat: a fan of backs, a name, a count. The fan overlaps, so eight cards read as a hand rather than a row. */ .pete-uno-seat { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; position: relative; padding: 0.5rem 0.7rem; border-radius: 1rem; transition: background 0.25s ease, transform 0.25s ease; } .pete-uno-seat[data-turn="1"] { background: rgba(255,255,255,0.12); transform: translateY(-2px); } .pete-uno-seat[data-uno="1"] .pete-uno-count { color: #ffd76e; } .pete-uno-fan { display: flex; --uno-h: 3.2rem; --uno-w: 2.15rem; } .pete-uno-fan .pete-uno-card { margin-left: -1.35rem; transform: rotate(calc((var(--i, 0) - (var(--n, 1) - 1) / 2) * 4deg)); transform-origin: bottom center; box-shadow: 0 2px 0 rgba(0,0,0,0.22); } .pete-uno-fan .pete-uno-card:first-child { margin-left: 0; } .pete-uno-fan .pete-uno-back::after { font-size: 0.4rem; padding: 0.06rem 0.22rem; } .pete-uno-name { font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.9rem; font-weight: 700; color: #fff; line-height: 1; } .pete-uno-count { font-size: 0.7rem; font-weight: 700; color: rgba(255,255,255,0.55); line-height: 1; } /* The deck you draw from, and the pile you play onto. */ .pete-uno-deck { position: relative; --uno-h: 6.4rem; --uno-w: 4.3rem; height: var(--uno-h); width: var(--uno-w); border-radius: 0.55rem; padding: 0.28rem; background: #fff; box-shadow: 0 3px 0 rgba(0,0,0,0.22), 0.28rem 0.28rem 0 -0.06rem rgba(255,255,255,0.35), 0.5rem 0.5rem 0 -0.12rem rgba(255,255,255,0.18); transition: transform 0.15s ease; } .pete-uno-deck:not(:disabled):hover { transform: translateY(-3px); } .pete-uno-deck:disabled { opacity: 0.75; } .pete-uno-deck-count { position: absolute; bottom: -0.55rem; left: 50%; transform: translateX(-50%); padding: 0.1rem 0.5rem; border-radius: 999px; background: rgba(0,0,0,0.55); color: #fff; font-size: 0.65rem; font-weight: 700; line-height: 1.4; } .pete-uno-shuffle { animation: pete-uno-shuffle 0.42s ease; } @keyframes pete-uno-shuffle { 0%, 100% { transform: none; } 30% { transform: translateY(-4px) rotate(-3deg); } 65% { transform: translateY(2px) rotate(2deg); } } .pete-uno-discard { display: grid; place-items: center; height: 6.4rem; width: 4.3rem; border-radius: 0.55rem; box-shadow: inset 0 0 0 2px rgba(255,255,255,0.14); } .pete-uno-land { animation: pete-uno-land 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); } @keyframes pete-uno-land { from { transform: scale(1.14) rotate(-4deg); } to { transform: none; } } .pete-uno-colour { font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 0.15rem 0.6rem; border-radius: 999px; color: #fff; background: rgba(0,0,0,0.3); } .pete-uno-colour[data-c="red"] { background: #d64545; } .pete-uno-colour[data-c="blue"] { background: #3d7fd6; } .pete-uno-colour[data-c="yellow"] { background: #e0b02c; color: #2b2118; } .pete-uno-colour[data-c="green"] { background: #46a86b; } /* A word thrown at a seat: SKIPPED, UNO!, +4. */ .pete-uno-badge { position: absolute; top: -0.4rem; left: 50%; z-index: 5; padding: 0.2rem 0.6rem; border-radius: 999px; background: #fff; color: #2b2118; font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.75rem; font-weight: 700; white-space: nowrap; box-shadow: 0 3px 0 rgba(0,0,0,0.2); animation: pete-uno-badge 0.9s ease forwards; } .pete-uno-badge[data-tone="bad"] { background: #cc3d4a; color: #fff; } .pete-uno-badge[data-tone="uno"] { background: #e0b02c; } @keyframes pete-uno-badge { 0% { transform: translate(-50%, 0.4rem) scale(0.7); opacity: 0; } 25% { transform: translate(-50%, -0.5rem) scale(1.06); opacity: 1; } 75% { transform: translate(-50%, -0.9rem) scale(1); opacity: 1; } 100% { transform: translate(-50%, -1.6rem) scale(0.95); opacity: 0; } } /* Naming a colour. It covers the felt because until it's answered there is no legal move on the table underneath it. */ .pete-uno-wild { position: absolute; inset: 0; z-index: 10; display: grid; place-items: center; background: rgba(10, 20, 15, 0.55); backdrop-filter: blur(2px); } .pete-uno-wild-box { padding: 1.25rem 1.5rem; border-radius: 1.25rem; background: rgba(20, 40, 30, 0.92); box-shadow: 0 12px 30px rgba(0,0,0,0.35); text-align: center; } .pete-uno-swatch { padding: 0.7rem 1.4rem; border-radius: 0.75rem; font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 1rem; font-weight: 700; color: #fff; background: var(--sw, #666); box-shadow: 0 3px 0 rgba(0,0,0,0.3); transition: transform 0.12s ease, filter 0.12s ease; } .pete-uno-swatch:hover { transform: translateY(-2px); filter: brightness(1.08); } .pete-uno-swatch:active { transform: translateY(1px); } .pete-uno-swatch[data-c="red"] { --sw: #d64545; } .pete-uno-swatch[data-c="blue"] { --sw: #3d7fd6; } .pete-uno-swatch[data-c="yellow"] { --sw: #e0b02c; color: #2b2118; } .pete-uno-swatch[data-c="green"] { --sw: #46a86b; } /* ---- No Mercy ------------------------------------------------------------- A rules dial, not a fourth table, and the felt says so: the same table, the same cards, plus six faces the normal box doesn't print and one thing that can be pointed at you — a stack of draw cards with a bill on it. Sizing note, and it is the rule this table was taught the hard way: a card is sized by --uno-h/--uno-w and nothing else. These faces set a *font*, never a box, so a "DISCARD ALL" is the same card as a "7" at every width. */ /* The long faces. "DISCARD ALL" does not fit across an oval at card size, so it wraps — and the oval is tilted, so it wraps on the tilt, which is what the printed card does too. Specificity has to clear the wild rule above (a reverse-draw-four is a wild), hence the .pete-uno-card in front. */ .pete-uno-card .pete-uno-oval[data-size="mid"] { font-size: 1.05rem; } .pete-uno-card .pete-uno-oval[data-size="words"] { font-size: 0.5rem; line-height: 1.1; letter-spacing: 0.02em; text-align: center; padding: 0 0.15rem; } /* The wild draws glow. These are the cards that decide a game, and No Mercy prints a *coloured* +4 right next to the wild one, so at a glance in a hand of twenty they have to be tellable apart. The aura is a sibling behind the card rather than a shadow on it: a box-shadow here would fight the one that says which colour a played wild was named as (data-named, above), and lose. It sits under the card (z-index -1) and does not take clicks, so the card is still the button. */ .pete-uno-card[data-glow="1"] { position: relative; z-index: 0; } .pete-uno-card[data-glow="1"]::before { content: ""; position: absolute; inset: -0.35rem; z-index: -1; border-radius: 0.9rem; pointer-events: none; background: conic-gradient(from 0deg, #d64545, #e0b02c, #46a86b, #3d7fd6, #d64545); filter: blur(7px); opacity: 0.75; animation: pete-uno-glow 3.2s linear infinite; } /* The shimmer across the face itself: a slow sweep of light, the way a foil card catches the room when you tilt it. */ .pete-uno-card[data-glow="1"] .pete-uno-face::after { content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none; background: linear-gradient(115deg, transparent 35%, rgba(255,255,255,0.55) 50%, transparent 65%); background-size: 260% 100%; animation: pete-uno-shine 2.6s ease-in-out infinite; } @keyframes pete-uno-glow { to { transform: rotate(1turn); } } @keyframes pete-uno-shine { 0% { background-position: 140% 0; } 55% { background-position: -40% 0; } 100% { background-position: -40% 0; } } /* The bill. While a stack stands it is the only thing on the table that matters, so it is loud, and it is red. */ .pete-uno-pending { font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; padding: 0.18rem 0.7rem; border-radius: 999px; color: #fff; background: #cc3d4a; box-shadow: 0 3px 0 rgba(0,0,0,0.2); animation: pete-uno-bill 1.1s ease-in-out infinite; } @keyframes pete-uno-bill { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.06); } } /* A seat the mercy rule buried. It stays at the table — the felt would jump if it didn't, and you want to see who's gone — but it is plainly out. */ .pete-uno-seat[data-out="1"] { opacity: 0.4; filter: grayscale(1); } .pete-uno-seat[data-out="1"] .pete-uno-count { color: #ffb3ba; } /* The rules switch: two dials, and this is the one that isn't the table size. */ .pete-seg { display: inline-flex; padding: 0.2rem; border-radius: 999px; background: color-mix(in srgb, var(--ink) 6%, transparent); } .pete-seg-btn { padding: 0.3rem 0.9rem; border-radius: 999px; font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif; font-size: 0.8rem; font-weight: 700; color: color-mix(in srgb, var(--ink) 55%, transparent); transition: background 0.15s ease, color 0.15s ease; } .pete-seg-btn:hover { color: var(--ink); } .pete-seg-btn[data-on="1"] { background: var(--accent); color: #fff; box-shadow: 0 2px 0 rgba(0,0,0,0.15); } /* A phone. The cards get smaller so a hand of ten still fits without the felt scrolling sideways, which is the thing to check at 390px with a game on it. No Mercy deals bigger hands than that — it is a deck built to bury you — so this is the table where a twenty-card hand has to wrap and not overflow. */ @media (max-width: 639px) { .pete-uno-hand { --uno-h: 5.2rem; --uno-w: 3.5rem; gap: 0.3rem; } .pete-uno-deck { --uno-h: 5.2rem; --uno-w: 3.5rem; } .pete-uno-card .pete-uno-oval[data-size="words"] { font-size: 0.42rem; } .pete-uno-card .pete-uno-oval[data-size="mid"] { font-size: 0.9rem; } /* The vars, not just the box: the card in the discard is a .pete-uno-card and takes its size from them, so sizing the box alone left a full-size card hanging out of a small hole, on top of the colour in play. */ .pete-uno-discard { --uno-h: 5.2rem; --uno-w: 3.5rem; height: 5.2rem; width: 3.5rem; } .pete-uno-hand .pete-uno-card { padding: 0.22rem; } .pete-uno-oval { font-size: 1.15rem; } .pete-uno-seat { padding: 0.35rem 0.45rem; } } @media (prefers-reduced-motion: reduce) { .pete-card, .pete-card::after, .pete-stack .pete-disc, .pete-dealer-think { animation: none; } .pete-card-inner { transition: none; } .pete-hand[data-won="1"] .pete-card { animation: none; } .pete-part-draw, .pete-shake, .pete-tile-hit, .pete-meter[data-hit="1"] { animation: none; } .pete-nope, .pete-home-flash { animation: none; } .pete-card[data-held="1"] { transition: none; } /* The clock still drains — it is information, not decoration — but it stops pulsing at you, and a wrong answer stops shaking. */ .pete-clock[data-hot="1"], .pete-answer[data-state="wrong"] { animation: none; } .pete-rung { transition: none; } /* The wild draws keep their glow — it is what tells the card apart from the coloured +4 beside it, so it is information — but it stops moving. */ .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 rooms. The casino borrows the news app's design language wholesale — the same four palette vars, the same Fredoka/Nunito, the same fat rounded cards and dropped shadows — and none of its furniture. It borrows the clock too, but tells a different joke with it. News shifts dawn→day→dusk→night. The casino has two rooms and swaps between them on the hour, name and all: casinopolis by day — honey and green felt, lamps low casino-night by dark — neon blue, pinball purple, lit like a machine Both are dark rooms of the same shape; only the light and the sign change. Adding a third is a palette block, a felt, and a name — nothing else. Written outside @layer on purpose: these must beat the base .shadow-pete. ---------------------------------------------------------------------------- */ html[data-room="casinopolis"] { --bg: #16211c; /* the room, lights low */ --card: #23342b; /* a table in it */ --ink: #f6ecd8; /* warm cream, not white */ --accent: #f2b53d; /* honey */ --felt-a: #2f7d5b; --felt-b: #24614a; --felt-c: #1c4d3c; --glow: 242,181,61; /* what the lamps throw, as rgb parts */ } html[data-room="casino-night"] { --bg: #140f2e; /* the machine's cabinet */ --card: #241a4d; --ink: #f2ecff; --accent: #ffcc2f; /* the jackpot bulb */ --felt-a: #4a2fa8; /* the table is a pinball table now */ --felt-b: #351f80; --felt-c: #241659; --glow: 126,86,255; } /* A lamp over the table and a darker floor at the edges, so the page has a middle to sit in rather than being flat dark. */ html[data-room] .cs-room { background: radial-gradient(80% 55% at 50% -8%, rgba(var(--glow), 0.18), transparent 62%), radial-gradient(120% 90% at 50% 120%, rgba(0,0,0,0.45), transparent 55%); } /* Casino Night is lit by bulbs, so it gets a few of them: a slow chase across the top of the room. Cheap (one gradient, one transform) and it makes the place feel plugged in rather than painted. */ html[data-room="casino-night"] .cs-room::before { content: ""; position: absolute; inset: 0 -50% auto -50%; height: 0.5rem; background: repeating-linear-gradient(90deg, rgba(255,204,47,0.55) 0 0.5rem, transparent 0.5rem 2.25rem); filter: blur(1px); animation: cs-bulbs 2.4s linear infinite; } @keyframes cs-bulbs { from { transform: translateX(0); } to { transform: translateX(2.75rem); } } /* The house shadow, restruck for a dark room: the news app's soft brown lift is invisible down here, so the same shape is cut in black instead. */ html[data-room] .shadow-pete { box-shadow: 0 4px 0 rgba(0,0,0,0.30), 0 10px 26px rgba(0,0,0,0.30); } html[data-room] .shadow-pete-lg { box-shadow: 0 6px 0 rgba(0,0,0,0.34), 0 20px 40px rgba(0,0,0,0.38); } html[data-room] .cs-comb svg { filter: drop-shadow(0 3px 0 rgba(0,0,0,0.35)); } /* The felt takes its colours from the room, so switching rooms reupholsters the table too. */ html[data-room] .pete-felt { background: radial-gradient(120% 90% at 50% -10%, rgba(255,255,255,0.10), transparent 60%), linear-gradient(160deg, var(--felt-a) 0%, var(--felt-b) 55%, var(--felt-c) 100%); } @media (prefers-reduced-motion: reduce) { html[data-room="casino-night"] .cs-room::before { animation: none; } } /* Three stacks of chips on the lobby's welcome card, in the same denomination colours you actually bet with. Height comes from --stack, so the row reads as a real pile someone left on the table rather than an icon of one. */ .cs-stack { display: block; width: 2.75rem; height: calc(0.45rem * var(--stack, 1) + 0.55rem); border-radius: 999px; background: radial-gradient(circle at 50% 30%, rgba(255,255,255,0.30), transparent 60%), var(--chip, #e07a5f); border: 2px solid rgba(0,0,0,0.25); box-shadow: 0 3px 0 rgba(0,0,0,0.30), inset 0 -0.4rem 0 rgba(0,0,0,0.16), inset 0 0.35rem 0 rgba(255,255,255,0.14); } .cs-stack[data-chip="5"] { --chip: #5aa9e6; } .cs-stack[data-chip="25"] { --chip: #4caf7d; } .cs-stack[data-chip="100"] { --chip: #2b2118; } .cs-stack[data-chip="500"] { --chip: #b079d6; } /* ── Hold'em ──────────────────────────────────────────────────────────────── The one table with other people at it. Everything else in the casino has a single bet spot, because there was only ever one player; here every seat has its own, chips travel between them and the pot rather than between you and the house, and the pot in the middle is the thing they all move toward. Seats are a wrapping row rather than an ellipse. An oval of six seats is what a poker table looks like, and it is also what stops fitting the moment a phone is held up — this keeps the geometry honest at 390px and still reads as a table because the board and the pot sit in the middle of it, which is where the eye goes anyway. */ .pete-poker { --seat-w: 7.5rem; } .pete-poker-seats { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: center; gap: 0.75rem; } .pete-seat { position: relative; display: flex; flex-direction: column; align-items: center; gap: 0.4rem; width: var(--seat-w); transition: opacity 0.3s ease, filter 0.3s ease; } /* A folded seat is still there — you can see they're in the game and out of the hand, which is information you're entitled to. It just stops competing. */ .pete-seat[data-state="folded"] { opacity: 0.38; filter: grayscale(0.7); } .pete-seat[data-state="out"] { opacity: 0.2; } /* Whose turn it is. The ring is the only thing on the felt that moves on its own, because it is the only thing you are waiting for. */ .pete-seat[data-turn="1"] .pete-seat-plate { box-shadow: 0 0 0 2px var(--accent), 0 0 1.6rem -0.2rem var(--accent); animation: pete-seat-wait 1.6s ease-in-out infinite; } @keyframes pete-seat-wait { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2px); } } .pete-seat-cards { display: flex; justify-content: center; gap: 0.15rem; min-height: 4.4rem; --card-h: 4.4rem; --card-w: 3.15rem; } /* A seat that folded throws its cards in. */ .pete-seat-cards[data-mucked="1"] { opacity: 0; transform: translateY(-0.6rem) scale(0.9); transition: all 0.35s ease; } .pete-seat-plate { position: relative; display: flex; flex-direction: column; align-items: center; width: 100%; padding: 0.3rem 0.4rem; border-radius: 0.85rem; background: rgba(0,0,0,0.32); border: 1px solid rgba(255,255,255,0.12); transition: box-shadow 0.25s ease; } .pete-seat-name { font-family: var(--font-display, inherit); font-weight: 700; font-size: 0.8rem; line-height: 1.1; color: rgba(255,255,255,0.92); max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .pete-seat-stack { font-weight: 700; font-size: 0.78rem; font-variant-numeric: tabular-nums; color: rgba(255,255,255,0.58); } .pete-seat[data-state="allin"] .pete-seat-stack::after { content: " all in"; color: var(--accent); } /* The dealer button, and the blinds. Small, and worth having: position is most of what makes one hand different from the next, and a player who cannot see where the button is cannot see the game. */ .pete-seat-pos { position: absolute; top: -0.5rem; right: -0.5rem; display: grid; place-items: center; min-width: 1.45rem; height: 1.45rem; padding: 0 0.3rem; border-radius: 999px; font-size: 0.58rem; font-weight: 800; letter-spacing: 0.02em; background: rgba(255,255,255,0.9); color: #2b2118; box-shadow: 0 2px 0 rgba(0,0,0,0.3); } .pete-seat-pos[data-pos="BTN"] { background: #f5d76e; } /* Every seat's bet, sitting between them and the pot. A .pete-spot is 7rem across, because blackjack has exactly one of them. Six of those is most of a felt, so a seat's is less than half the size — and so are the chips in it, which are otherwise bigger than the ring they land in. */ .pete-seat-spot { height: 3.6rem; width: 3.6rem; margin-bottom: 0.5rem; /* the bet total hangs below the ring; leave it somewhere to hang */ } .pete-seat-spot .pete-disc { height: 1.6rem; width: 1.6rem; margin: -0.8rem 0 0 -0.8rem; } .pete-seat-spot .pete-spot-total { font-size: 0.66rem; padding: 0.05rem 0.45rem; } /* The middle of the table: the board, and the pot under it. */ .pete-poker-middle { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; padding: 0.5rem 0; } /* The pot's chips stack upwards, so they need room under the board or they climb into the river card. */ .pete-poker-pot { margin-top: 0.9rem; } /* The board keeps its height when it is empty, so the felt does not jump a hundred pixels the moment the flop lands. */ .pete-poker-board { display: flex; gap: 0.35rem; min-height: 6rem; --card-h: 6rem; --card-w: 4.3rem; } .pete-poker-pot { display: flex; flex-direction: column; align-items: center; gap: 0.1rem; } /* The pot's chips need a box to themselves. .pete-stack is position:absolute with inset:0, so a pile that shares a box with the number under it lands on top of the number — which is exactly what it did: the pot showed a chip and no total. */ .pete-poker-pot-pile { position: relative; width: 7rem; height: 3rem; } .pete-poker-pot-label { font-size: 0.62rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; color: rgba(255,255,255,0.42); } .pete-poker-pot-total { font-family: var(--font-display, inherit); font-size: 1.5rem; font-weight: 800; font-variant-numeric: tabular-nums; color: #fff; text-shadow: 0 2px 0 rgba(0,0,0,0.28); } .pete-poker-side { font-size: 0.68rem; font-weight: 700; color: rgba(255,255,255,0.5); } /* Your seat. Bigger cards, because they are the two you are actually reading. */ .pete-poker-you .pete-seat { width: auto; } .pete-poker-you .pete-seat-cards { --card-h: 6.8rem; --card-w: 4.85rem; gap: 0.3rem; min-height: 6.8rem; } .pete-poker-you .pete-seat-plate { padding: 0.4rem 1.1rem; } .pete-poker-you .pete-seat-name { font-size: 0.95rem; } .pete-poker-you .pete-seat-stack { font-size: 0.95rem; } /* What the hand did to you, said once, in the middle of the felt. */ .pete-poker-verdict { border-radius: 999px; background: rgba(255,255,255,0.95); padding: 0.5rem 1.25rem; font-family: var(--font-display, inherit); font-size: 1.05rem; font-weight: 700; color: #2b2118; box-shadow: 0 4px 0 rgba(0,0,0,0.18); } .pete-poker-verdict[data-tone="win"] { background: #4caf7d; color: #fff; } .pete-poker-verdict[data-tone="lose"] { background: rgba(255,255,255,0.9); color: #7a5c50; } /* The action bar. Raise is a slider, because a raise is a *size* and a text box makes you type a number you have not thought about. */ .pete-raise { display: flex; align-items: center; gap: 0.6rem; flex: 1 1 14rem; min-width: 0; } .pete-raise input[type="range"] { flex: 1; min-width: 0; accent-color: var(--accent); } .pete-raise-to { font-family: var(--font-display, inherit); font-size: 1.15rem; font-weight: 800; font-variant-numeric: tabular-nums; min-width: 3.5rem; text-align: right; } .pete-poker-log { max-height: 7rem; overflow-y: auto; font-size: 0.75rem; line-height: 1.5; color: rgba(255,255,255,0.55); } .pete-poker-log b { color: rgba(255,255,255,0.85); font-weight: 700; } @media (max-width: 640px) { .pete-poker { --seat-w: 5.6rem; } .pete-seat-cards { --card-h: 3.5rem; --card-w: 2.5rem; min-height: 3.5rem; } .pete-poker-board { --card-h: 5rem; --card-w: 3.55rem; min-height: 5rem; gap: 0.25rem; } .pete-poker-you .pete-seat-cards { --card-h: 6rem; --card-w: 4.3rem; min-height: 6rem; } .pete-poker-pot-total { font-size: 1.25rem; } }