Files
Pete/internal/web/static/css/input.css
prosolis 8ec13eab5b games: the casino moves out, and gets a clock of its own
The tables were living in the news app's shell: Pete's face in the header
and the footer, the channel nav, search, the reader, the weather canvas,
the PWA. A casino is not a news page with a felt on it.

So it gets its own layout. What carries over is the design language — the
four palette vars, Fredoka/Nunito, the fat rounded cards, the dropped
shadow. What doesn't is every control it has no use for. gamesPage stops
embedding the news pageData, which is what keeps the furniture from
drifting back one convenient field at a time.

It keeps a clock, but tells a different joke with it: Casinopolis by day,
Casino Night Zone from six, palette and felt and the sign over the door all
changing together. The rule lives in roomAt() for the first paint and again
in the browser, so a player abroad gets their own evening.

And the cards are cards now — corner indices in both corners, the bottom
one upside down as printed, pips on the three-by-seven grid every real deck
has used for four hundred years, courts as a letter with the suit over each
shoulder. Driven in a real browser, both rooms, dealt through to a payout.
2026-07-13 23:40:33 -07:00

820 lines
30 KiB
CSS

@tailwind base;
@tailwind components;
@tailwind utilities;
/* ----------------------------------------------------------------------------
Day/night palette. JS sets data-phase={dawn|day|dusk|night} on <html>.
Each phase drives a small set of CSS vars; the page transitions smoothly
thanks to the `transition-colors duration-1000` on <body>.
---------------------------------------------------------------------------- */
: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: 6.6rem;
}
/* One card. The wrapper does the flight, the inner face does the flip, so the
two never fight over the same transform. */
.pete-card {
perspective: 700px;
height: 7rem;
width: 5rem;
animation: pete-deal 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
.pete-card-inner {
position: relative;
height: 100%;
width: 100%;
transform-style: preserve-3d;
transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
/* Face-down is the resting state of a card that hasn't been turned over: the
hole card sits like this until the dealer plays. */
.pete-card[data-face="down"] .pete-card-inner { transform: rotateY(180deg); }
.pete-card-front,
.pete-card-back {
position: absolute;
inset: 0;
display: grid;
border-radius: 0.55rem;
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
box-shadow: 0 3px 0 rgba(0,0,0,0.18), 0 6px 14px rgba(0,0,0,0.22);
}
/* A real card face: index in two opposite corners, pips or a court figure in
the middle. Laid out as three rows — corner, body, corner — so the body gets
whatever's left and the indices stay pinned where they're printed. */
.pete-card-front {
/* minmax(0,…) on the body row, or the pip grid's min-content height (seven
rows of glyphs) wins the argument and pushes the bottom index off the
card entirely. */
grid-template-rows: auto minmax(0, 1fr) auto;
overflow: hidden;
padding: 0.2rem 0.28rem;
background: #fdfaf2;
border: 2px solid rgba(30,20,10,0.12);
color: #2b2118;
font-family: "Fredoka", "Nunito", system-ui, sans-serif;
line-height: 1;
}
.pete-card-front[data-red="1"] { color: #cc3d4a; }
/* The corner index: rank over suit, tight. The bottom-right one is the same
thing upside down, because that's what makes a card readable from a fanned
hand held either way up. */
.pete-card-corner {
display: flex;
flex-direction: column;
align-items: center;
line-height: 0.95;
}
.pete-card-corner-tl { justify-self: start; }
.pete-card-corner-br { justify-self: end; transform: rotate(180deg); }
.pete-card-corner-rank { font-size: 0.9rem; font-weight: 700; }
.pete-card-corner-suit { font-size: 0.72rem; }
/* The pip field: three columns, seven rows — the skeleton every printed deck
shares. blackjack.js places each pip on it. */
.pete-card-pips {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(7, minmax(0, 1fr));
place-items: center;
min-height: 0;
width: 100%;
height: 100%;
padding: 0 0.08rem;
}
.pete-card-pip { font-size: 0.82rem; line-height: 1; }
.pete-card-pip[data-flip="1"] { transform: rotate(180deg); }
/* An ace is one pip, and it's allowed to be enormous. */
.pete-card-pips[data-ace="1"] .pete-card-pip { font-size: 2.1rem; }
/* Court cards: the letter with a suit over each shoulder. */
.pete-card-court {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.05rem;
}
.pete-card-court-letter {
font-size: 1.7rem;
font-weight: 700;
}
.pete-card-court-suit { font-size: 0.8rem; }
.pete-card-court-suit[data-flip="1"] { transform: rotate(180deg); }
.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);
}
/* The flight itself: out of the shoe (up and to the right), scaled down and
spinning slightly, into place. */
@keyframes pete-deal {
from {
opacity: 0;
transform: translate(var(--deal-x, 14rem), var(--deal-y, -7rem)) scale(0.72) rotate(9deg);
}
to {
opacity: 1;
transform: translate(0, 0) scale(1) rotate(0);
}
}
/* A settled hand: the winning side gets a little lift, so a win reads at a
glance rather than only in the text. */
.pete-hand[data-won="1"] .pete-card { animation: pete-deal 0.42s cubic-bezier(0.22,1,0.36,1) backwards, pete-win 0.6s ease 0.1s; }
@keyframes pete-win {
0%, 100% { transform: translateY(0); }
40% { transform: translateY(-0.6rem); }
}
/* Chips: the denominations you bet with. */
.pete-chip {
background: radial-gradient(circle at 50% 35%, rgba(255,255,255,0.28), transparent 60%), var(--chip, #e07a5f);
border: 3px dashed rgba(255,255,255,0.55);
box-shadow: 0 3px 0 rgba(60,40,20,0.22), 0 6px 14px rgba(60,40,20,0.18);
}
.pete-chip[data-chip="5"] { --chip: #5aa9e6; }
.pete-chip[data-chip="25"] { --chip: #4caf7d; }
.pete-chip[data-chip="100"] { --chip: #2b2118; }
.pete-chip[data-chip="500"] { --chip: #b079d6; }
.pete-chip[aria-pressed="true"] { outline: 3px solid var(--accent); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) {
.pete-card { animation: none; }
.pete-card-inner { transition: none; }
.pete-hand[data-won="1"] .pete-card { animation: none; }
}
}
/* ----------------------------------------------------------------------------
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; }