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.
820 lines
30 KiB
CSS
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; }
|