@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. */ .pete-card { perspective: 700px; height: 8.4rem; width: 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; } /* 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); } @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; } } } /* ---------------------------------------------------------------------------- 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; }