Four small surfaces the game has had all along and the web has never shown. The party roster on the adventurer page is the one with a bug behind it. The board resolved an expedition by owner id, so a player seated on somebody else's run has been reading as "idle in town" while standing in a tier-4 dungeon. Seats now ride the roster detail beside the supply and threat numbers, and an opted-out player's seat is anonymised rather than dropped: a party of three rendered as a pair contradicts everything printed next to it. Pets show their levelling. They have earned XP from every won fight since that wiring was fixed and the only place a level ever appeared was a Matrix line that scrolled away. The threshold comes from the engine, in the engine's own centi-XP, because a copy of the curve here would drift the first time a band moved. "While you were away" is the one panel on the site about the reader rather than the realm. Two stamps behind it, not one: a single column would show the news, move the clock, and render an empty box over the same events on the reader's first refresh. And the story permalink names its region, which has been hardcoded empty behind a `// reserved` comment since the page shipped.
3081 lines
113 KiB
CSS
3081 lines
113 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>.
|
||
---------------------------------------------------------------------------- */
|
||
|
||
/* --warn is the "you should look at this" amber, and it is a phase variable for
|
||
the same reason --ink is: the phase decides how dark the card underneath is.
|
||
Tailwind's dark: variant cannot do this job here — darkMode is unconfigured,
|
||
so dark: follows the OS's prefers-color-scheme, which has nothing to do with
|
||
which phase Pete is showing. A dark phase under a light OS renders amber-700
|
||
on the night card at 2.34:1, and a light phase under a dark OS renders
|
||
amber-400 on cream at 1.55:1 — half of the four combinations unreadable, and
|
||
which half depends on a setting outside the page. Only night has a dark card;
|
||
dusk and dawn are lit despite their names. */
|
||
:root,
|
||
html[data-phase="day"] {
|
||
--bg: #fff7e4; /* warm cream */
|
||
--bg-grad: #ffeec2; /* soft sun wash */
|
||
--card: #ffffff;
|
||
--ink: #3a2e1f;
|
||
--accent: #f2a541; /* sunshine yellow */
|
||
--warn: #b45309;
|
||
}
|
||
|
||
html[data-phase="dawn"] {
|
||
--bg: #ffe7d6;
|
||
--bg-grad: #ffc9c9;
|
||
--card: #fff4ea;
|
||
--ink: #4a2e2a;
|
||
--accent: #ff8a65;
|
||
--warn: #b45309;
|
||
}
|
||
|
||
html[data-phase="dusk"] {
|
||
--bg: #ffd6a8;
|
||
--bg-grad: #f7a07e;
|
||
--card: #fff1de;
|
||
--ink: #3d2417;
|
||
--accent: #e6553a;
|
||
--warn: #b45309;
|
||
}
|
||
|
||
html[data-phase="night"] {
|
||
--bg: #1a1f3a;
|
||
--bg-grad: #2a3358;
|
||
--card: #2d365a;
|
||
--ink: #f1ecd8; /* moonlight */
|
||
--accent: #f9d976; /* lantern */
|
||
--warn: #fbbf24; /* the only dark card, so the only light amber */
|
||
}
|
||
|
||
@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; }
|
||
|
||
/* Night repaint. The colours above are all tuned to sit on a light card, and
|
||
nobody checked them against a dark one when the phases were built: on
|
||
night's #2d365a every single one lands between 1.08:1 (eu) and 3.12:1
|
||
(finance), i.e. the whole family is below AA and eu is very nearly
|
||
invisible. Same hue, lifted lightness, saturation floored at 0.62 so the
|
||
dulled ones stay a colour instead of going grey — all now ≥5.5:1.
|
||
|
||
Only night gets this. Dusk and dawn are lit cards despite the names, so
|
||
they keep the originals exactly. Lego cannot stay pillar-box red and be
|
||
readable on navy — a red light enough to pass reads as salmon, and that is
|
||
the honest trade rather than a red nobody can see.
|
||
|
||
Deliberately NOT Tailwind's dark: variant: darkMode is unconfigured, so
|
||
dark: follows the OS's prefers-color-scheme, which knows nothing about
|
||
which phase Pete is showing. Keyed off the phase, like --ink is. */
|
||
html[data-phase="night"] .text-theme-gaming { color: #30cb7b; }
|
||
html[data-phase="night"] .text-theme-tech { color: #7fb8e1; }
|
||
html[data-phase="night"] .text-theme-politics { color: #e5a191; }
|
||
html[data-phase="night"] .text-theme-eu { color: #8bb1ff; }
|
||
html[data-phase="night"] .text-theme-music { color: #caa2e9; }
|
||
html[data-phase="night"] .text-theme-anime { color: #e89cb6; }
|
||
html[data-phase="night"] .text-theme-foss { color: #f69d5d; }
|
||
html[data-phase="night"] .text-theme-kids { color: #19c7ba; }
|
||
html[data-phase="night"] .text-theme-finance { color: #07cb8e; }
|
||
html[data-phase="night"] .text-theme-lego { color: #f79898; }
|
||
html[data-phase="night"] .text-theme-adventure { color: #baa9eb; }
|
||
|
||
.decoration-theme-gaming { text-decoration-color: #4caf7d; }
|
||
.decoration-theme-tech { text-decoration-color: #5aa9e6; }
|
||
.decoration-theme-politics { text-decoration-color: #e07a5f; }
|
||
.decoration-theme-eu { text-decoration-color: #003399; }
|
||
.decoration-theme-music { text-decoration-color: #b079d6; }
|
||
.decoration-theme-anime { text-decoration-color: #ec5e8a; }
|
||
.decoration-theme-foss { text-decoration-color: #d97706; }
|
||
.decoration-theme-kids { text-decoration-color: #14b8a6; }
|
||
.decoration-theme-finance { text-decoration-color: #10b981; }
|
||
.decoration-theme-lego { text-decoration-color: #d01012; }
|
||
.decoration-theme-adventure { text-decoration-color: #6d4bd8; }
|
||
|
||
.border-theme-gaming { border-color: #4caf7d; }
|
||
.border-theme-tech { border-color: #5aa9e6; }
|
||
.border-theme-politics { border-color: #e07a5f; }
|
||
.border-theme-eu { border-color: #003399; }
|
||
.border-theme-music { border-color: #b079d6; }
|
||
.border-theme-anime { border-color: #ec5e8a; }
|
||
.border-theme-foss { border-color: #d97706; }
|
||
.border-theme-kids { border-color: #14b8a6; }
|
||
.border-theme-finance { border-color: #10b981; }
|
||
.border-theme-lego { border-color: #d01012; }
|
||
.border-theme-adventure { border-color: #6d4bd8; }
|
||
|
||
/* "Posted to Matrix" glow — soft pulsing aura in the channel's theme color. */
|
||
.glow-theme-gaming { box-shadow: 0 0 0 2px rgba(76,175,125,0.35), 0 0 24px 4px rgba(76,175,125,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-tech { box-shadow: 0 0 0 2px rgba(90,169,230,0.35), 0 0 24px 4px rgba(90,169,230,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-politics { box-shadow: 0 0 0 2px rgba(224,122,95,0.35), 0 0 24px 4px rgba(224,122,95,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-eu { box-shadow: 0 0 0 2px rgba(0,51,153,0.35), 0 0 24px 4px rgba(0,51,153,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-music { box-shadow: 0 0 0 2px rgba(176,121,214,0.35), 0 0 24px 4px rgba(176,121,214,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-anime { box-shadow: 0 0 0 2px rgba(236,94,138,0.35), 0 0 24px 4px rgba(236,94,138,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-foss { box-shadow: 0 0 0 2px rgba(217,119,6,0.35), 0 0 24px 4px rgba(217,119,6,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-kids { box-shadow: 0 0 0 2px rgba(20,184,166,0.35), 0 0 24px 4px rgba(20,184,166,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-finance { box-shadow: 0 0 0 2px rgba(16,185,129,0.35), 0 0 24px 4px rgba(16,185,129,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-lego { box-shadow: 0 0 0 2px rgba(208,16,18,0.35), 0 0 24px 4px rgba(208,16,18,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
.glow-theme-adventure { box-shadow: 0 0 0 2px rgba(109,75,216,0.35), 0 0 24px 4px rgba(109,75,216,0.45); animation: pete-glow-pulse 2.4s ease-in-out infinite; }
|
||
|
||
@keyframes pete-glow-pulse {
|
||
0%, 100% { filter: brightness(1); }
|
||
50% { filter: brightness(1.08); }
|
||
}
|
||
|
||
.line-clamp-3 {
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 3;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* "PAYWALLED" diagonal rubber stamp overlay — slapped across the whole
|
||
card when the source is gated and no archive snapshot worked. The card
|
||
stays clickable (link still goes to the original); the stamp is just
|
||
visual warning. pointer-events:none keeps it from eating clicks. */
|
||
.paywall-stamp {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 5;
|
||
background: rgba(255, 250, 240, 0.18);
|
||
}
|
||
.paywall-stamp::before {
|
||
content: "PAYWALLED";
|
||
font-family: var(--font-display, ui-sans-serif), system-ui, sans-serif;
|
||
font-weight: 900;
|
||
font-size: clamp(1rem, 3.2vw, 1.75rem);
|
||
letter-spacing: 0.12em;
|
||
color: rgba(180, 30, 30, 0.85);
|
||
border: 0.28rem double rgba(180, 30, 30, 0.85);
|
||
padding: 0.3rem 0.9rem;
|
||
transform: rotate(-15deg);
|
||
text-shadow: 0 1px 0 rgba(255,255,255,0.4);
|
||
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.15);
|
||
background: rgba(255, 240, 230, 0.55);
|
||
border-radius: 0.25rem;
|
||
/* slight roughness to mimic a real ink stamp */
|
||
filter: contrast(1.05);
|
||
}
|
||
}
|
||
|
||
/* ----------------------------------------------------------------------------
|
||
Feed / reader mode. A focused, one-story-at-a-time overlay driven by
|
||
reader.js. Left/right arrows page through the stories currently on screen,
|
||
marking each read as it's shown. Styling leans on the phase palette vars so
|
||
it recolours with day/night like everything else.
|
||
---------------------------------------------------------------------------- */
|
||
@layer components {
|
||
.pete-reader-backdrop {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: rgba(20, 14, 6, 0.55);
|
||
backdrop-filter: blur(6px);
|
||
}
|
||
html[data-phase="night"] .pete-reader-backdrop { background: rgba(6, 8, 20, 0.65); }
|
||
|
||
.pete-reader-shell {
|
||
position: relative;
|
||
height: 100%;
|
||
width: 100%;
|
||
max-width: 44rem;
|
||
margin: 0 auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: max(env(safe-area-inset-top), 0.75rem) 1rem 0.75rem;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.pete-reader-bar {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 0.75rem;
|
||
color: #fff;
|
||
}
|
||
.pete-reader-progress {
|
||
font-family: "Fredoka", "Nunito", system-ui, sans-serif;
|
||
font-weight: 600;
|
||
font-size: 0.85rem;
|
||
letter-spacing: 0.02em;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||
}
|
||
.pete-reader-nav { display: flex; align-items: center; gap: 0.4rem; }
|
||
.pete-reader-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 2.25rem;
|
||
height: 2.25rem;
|
||
padding: 0 0.7rem;
|
||
border-radius: 9999px;
|
||
background: rgba(255, 255, 255, 0.14);
|
||
color: #fff;
|
||
font-weight: 700;
|
||
font-size: 0.95rem;
|
||
border: 2px solid rgba(255, 255, 255, 0.18);
|
||
cursor: pointer;
|
||
transition: background 0.15s ease, transform 0.15s ease, opacity 0.15s ease;
|
||
}
|
||
.pete-reader-btn:hover { background: rgba(255, 255, 255, 0.28); transform: translateY(-1px); }
|
||
.pete-reader-btn:disabled { opacity: 0.35; cursor: default; transform: none; }
|
||
.pete-reader-btn-open { background: var(--accent); border-color: transparent; color: #1c1305; text-decoration: none; }
|
||
.pete-reader-btn-open:hover { background: var(--accent); filter: brightness(1.08); }
|
||
/* Active/pressed toolbar toggle (Listen while speaking, Aa while menu open). */
|
||
.pete-reader-btn[aria-pressed="true"],
|
||
.pete-reader-btn[aria-expanded="true"] {
|
||
background: var(--accent);
|
||
border-color: transparent;
|
||
color: #1c1305;
|
||
}
|
||
|
||
/* Text-options popover, anchored under the Aa button in the reader bar. */
|
||
.pete-reader-type { position: relative; display: inline-flex; }
|
||
.pete-reader-typemenu[hidden] { display: none; }
|
||
.pete-reader-typemenu {
|
||
position: absolute;
|
||
top: calc(100% + 0.5rem);
|
||
right: 0;
|
||
z-index: 10;
|
||
width: 15rem;
|
||
padding: 0.75rem;
|
||
border-radius: 1rem;
|
||
background: var(--card);
|
||
color: var(--ink);
|
||
border: 2px solid rgba(0, 0, 0, 0.08);
|
||
box-shadow: 0 10px 30px rgba(20, 14, 6, 0.28);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.6rem;
|
||
}
|
||
html[data-phase="night"] .pete-reader-typemenu { border-color: rgba(255, 255, 255, 0.12); }
|
||
.pete-reader-typerow { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }
|
||
.pete-reader-typelabel { font-size: 0.8rem; font-weight: 700; opacity: 0.7; }
|
||
.pete-reader-typebtns { display: inline-flex; gap: 0.25rem; }
|
||
.pete-reader-typebtns button {
|
||
min-width: 2rem;
|
||
height: 2rem;
|
||
padding: 0 0.55rem;
|
||
border-radius: 0.6rem;
|
||
background: rgba(20, 14, 6, 0.06);
|
||
color: var(--ink);
|
||
font-weight: 700;
|
||
font-size: 0.82rem;
|
||
border: 2px solid transparent;
|
||
cursor: pointer;
|
||
transition: background 0.15s ease;
|
||
}
|
||
html[data-phase="night"] .pete-reader-typebtns button { background: rgba(255, 255, 255, 0.08); }
|
||
.pete-reader-typebtns button:hover { background: rgba(20, 14, 6, 0.12); }
|
||
.pete-reader-typebtns button.is-active {
|
||
background: var(--accent);
|
||
color: #1c1305;
|
||
border-color: transparent;
|
||
}
|
||
.pete-reader-voicerow[hidden] { display: none; }
|
||
.pete-reader-voice {
|
||
font: inherit;
|
||
font-size: 0.8rem;
|
||
font-weight: 600;
|
||
padding: 0.25rem 0.4rem;
|
||
border-radius: 0.55rem;
|
||
border: 1px solid rgba(20, 14, 6, 0.18);
|
||
background: rgba(20, 14, 6, 0.06);
|
||
color: inherit;
|
||
max-width: 11rem;
|
||
cursor: pointer;
|
||
}
|
||
html[data-phase="night"] .pete-reader-voice {
|
||
border-color: rgba(255, 255, 255, 0.14);
|
||
background: rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.pete-reader-scroll {
|
||
flex: 1 1 auto;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
border-radius: 1.75rem;
|
||
}
|
||
.pete-reader-scroll:focus { outline: none; }
|
||
|
||
.pete-reader-article {
|
||
background: var(--card);
|
||
color: var(--ink);
|
||
border-radius: 1.75rem;
|
||
border: 2px solid rgba(0, 0, 0, 0.06);
|
||
box-shadow: 0 6px 0 rgba(60, 40, 20, 0.12), 0 16px 32px rgba(60, 40, 20, 0.18);
|
||
padding: clamp(1.25rem, 4vw, 2.5rem);
|
||
}
|
||
html[data-phase="night"] .pete-reader-article { border-color: rgba(255, 255, 255, 0.08); }
|
||
|
||
.pete-reader-eyebrow {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
font-size: 0.75rem;
|
||
margin-bottom: 0.85rem;
|
||
}
|
||
.pete-reader-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
border-radius: 9999px;
|
||
padding: 0.15rem 0.65rem;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
.pete-reader-source { font-weight: 700; opacity: 0.75; }
|
||
.pete-reader-time { opacity: 0.55; }
|
||
|
||
.pete-reader-title {
|
||
font-family: "Fredoka", "Nunito", system-ui, sans-serif;
|
||
font-weight: 700;
|
||
line-height: 1.15;
|
||
letter-spacing: -0.01em;
|
||
font-size: clamp(1.6rem, 4.5vw, 2.4rem);
|
||
margin-bottom: 1rem;
|
||
}
|
||
.pete-reader-hero {
|
||
width: 100%;
|
||
border-radius: 1.1rem;
|
||
margin-bottom: 1.25rem;
|
||
background: rgba(0, 0, 0, 0.05);
|
||
}
|
||
|
||
/* Reader typography, driven by the Aa popover and persisted per-device. The
|
||
size lives as a CSS var on the scroll container; font + paper are classes. */
|
||
.pete-reader-scroll { --reader-fs: 1.08rem; }
|
||
.pete-reader-body { font-size: var(--reader-fs); line-height: 1.75; }
|
||
.pete-reader-body p { margin-bottom: 1.05em; }
|
||
.pete-reader-body p:last-child { margin-bottom: 0; }
|
||
.pete-reader-scroll.is-size-s { --reader-fs: 0.98rem; }
|
||
.pete-reader-scroll.is-size-m { --reader-fs: 1.08rem; }
|
||
.pete-reader-scroll.is-size-l { --reader-fs: 1.22rem; }
|
||
.pete-reader-scroll.is-size-xl { --reader-fs: 1.4rem; }
|
||
.pete-reader-scroll.is-serif .pete-reader-body {
|
||
font-family: Georgia, "Iowan Old Style", "Times New Roman", serif;
|
||
}
|
||
/* Sepia paper: warm background + ink regardless of day/night phase. */
|
||
.pete-reader-scroll.is-sepia .pete-reader-article {
|
||
background: #f6ecd6;
|
||
color: #4a3a28;
|
||
border-color: rgba(74, 58, 40, 0.16);
|
||
}
|
||
.pete-reader-scroll.is-sepia .pete-reader-note { border-top-color: rgba(74, 58, 40, 0.18); opacity: 0.8; }
|
||
.pete-reader-scroll.is-sepia .pete-reader-hero { background: rgba(74, 58, 40, 0.08); }
|
||
/* Currently-spoken paragraph highlight while reading aloud. */
|
||
.pete-reader-body p.is-speaking {
|
||
background: color-mix(in srgb, var(--accent) 32%, transparent);
|
||
border-radius: 0.4rem;
|
||
box-shadow: 0 0 0 0.35rem color-mix(in srgb, var(--accent) 32%, transparent);
|
||
}
|
||
|
||
.pete-reader-note {
|
||
margin-top: 1.25rem;
|
||
padding-top: 1rem;
|
||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||
font-size: 0.85rem;
|
||
opacity: 0.7;
|
||
}
|
||
html[data-phase="night"] .pete-reader-note { border-color: rgba(255, 255, 255, 0.12); }
|
||
.pete-reader-note a { color: var(--accent); font-weight: 700; }
|
||
|
||
.pete-reader-empty {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
text-align: center;
|
||
gap: 0.75rem;
|
||
padding: clamp(2rem, 8vw, 4rem) 1rem;
|
||
color: var(--ink);
|
||
}
|
||
.pete-reader-empty-emoji { font-size: 2.5rem; }
|
||
|
||
/* Transient confirmation toast (e.g. "Link copied") shown over the reader. */
|
||
.pete-reader-toast {
|
||
position: absolute;
|
||
left: 50%;
|
||
bottom: 4.5rem;
|
||
transform: translate(-50%, 0.5rem);
|
||
z-index: 20;
|
||
padding: 0.5rem 0.9rem;
|
||
border-radius: 9999px;
|
||
background: rgba(20, 14, 6, 0.9);
|
||
color: #fff;
|
||
font-weight: 700;
|
||
font-size: 0.85rem;
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.2s ease, transform 0.2s ease;
|
||
}
|
||
.pete-reader-toast.is-shown { opacity: 1; transform: translate(-50%, 0); }
|
||
|
||
.pete-reader-hint {
|
||
text-align: center;
|
||
color: rgba(255, 255, 255, 0.85);
|
||
font-size: 0.75rem;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||
}
|
||
.pete-reader-hint kbd {
|
||
font-family: ui-monospace, monospace;
|
||
background: rgba(255, 255, 255, 0.16);
|
||
border-radius: 0.3rem;
|
||
padding: 0.05rem 0.3rem;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.pete-reader-hint { display: none; }
|
||
/* With Listen/Share/Aa added, let the toolbar wrap instead of overflowing. */
|
||
.pete-reader-bar { flex-wrap: wrap; }
|
||
.pete-reader-nav { flex-wrap: wrap; justify-content: flex-end; row-gap: 0.4rem; }
|
||
}
|
||
|
||
/* "You might also like" rail, shown under the article in feed mode. Lives
|
||
inside the reader's scroll area, below the article card. */
|
||
.pete-reader-related { width: 100%; margin: 0.85rem auto 0; }
|
||
.pete-reader-related-title {
|
||
font-family: "Fredoka", "Nunito", system-ui, sans-serif;
|
||
font-weight: 700;
|
||
font-size: 0.95rem;
|
||
color: #fff;
|
||
margin: 0 0 0.6rem;
|
||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||
}
|
||
.pete-reader-related-grid { display: grid; gap: 0.6rem; }
|
||
.pete-reader-related-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
background: var(--card);
|
||
color: var(--ink);
|
||
border-radius: 1rem;
|
||
border: 2px solid rgba(0, 0, 0, 0.06);
|
||
padding: 0.6rem;
|
||
text-decoration: none;
|
||
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
||
}
|
||
.pete-reader-related-card:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 6px 16px rgba(60, 40, 20, 0.16);
|
||
}
|
||
html[data-phase="night"] .pete-reader-related-card { border-color: rgba(255, 255, 255, 0.08); }
|
||
.pete-reader-related-thumb {
|
||
width: 4.5rem;
|
||
height: 3.25rem;
|
||
flex-shrink: 0;
|
||
object-fit: cover;
|
||
border-radius: 0.6rem;
|
||
background: rgba(0, 0, 0, 0.06);
|
||
}
|
||
.pete-reader-related-meta { min-width: 0; display: flex; flex-direction: column; gap: 0.25rem; }
|
||
.pete-reader-related-eyebrow {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 0.4rem;
|
||
font-size: 0.7rem;
|
||
}
|
||
.pete-reader-related-source { font-weight: 700; opacity: 0.7; }
|
||
.pete-reader-related-headline {
|
||
font-weight: 700;
|
||
line-height: 1.25;
|
||
font-size: 0.92rem;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Grid treatment for stories already read in feed mode: dimmed, with a small
|
||
corner check. Hovering restores full opacity so nothing feels lost. */
|
||
[data-story-card][data-read="1"] { opacity: 0.5; transition: opacity 0.2s ease; }
|
||
[data-story-card][data-read="1"]:hover { opacity: 1; }
|
||
[data-story-card][data-read="1"]::after {
|
||
content: "✓";
|
||
position: absolute;
|
||
top: 0.6rem;
|
||
right: 0.6rem;
|
||
z-index: 6;
|
||
display: grid;
|
||
place-items: center;
|
||
height: 1.5rem;
|
||
width: 1.5rem;
|
||
border-radius: 9999px;
|
||
background: rgba(20, 14, 6, 0.72);
|
||
color: #fff;
|
||
font-size: 0.8rem;
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* ---- the casino ---------------------------------------------------------
|
||
Cards are dealt, not swapped in: each one starts at the shoe in the corner,
|
||
flies to its place, and turns over. The whole point of the table is that you
|
||
watch it happen, so this is written as one animation per card with a delay,
|
||
and the JS just says which cards exist and in what order. */
|
||
|
||
.pete-felt {
|
||
background:
|
||
radial-gradient(120% 90% at 50% -10%, rgba(255,255,255,0.10), transparent 60%),
|
||
linear-gradient(160deg, #2f7d5b 0%, #24614a 55%, #1c4d3c 100%);
|
||
}
|
||
|
||
/* The shoe: where every card comes from. */
|
||
.pete-shoe {
|
||
position: absolute;
|
||
top: 1.25rem;
|
||
right: 1.25rem;
|
||
height: 4.2rem;
|
||
width: 3rem;
|
||
border-radius: 0.55rem;
|
||
background: linear-gradient(150deg, #b4553f, #8d3f2f);
|
||
border: 2px solid rgba(0,0,0,0.18);
|
||
box-shadow: 0 4px 0 rgba(0,0,0,0.18), inset 0 0 0 3px rgba(255,255,255,0.12);
|
||
}
|
||
.pete-shoe::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0.45rem 0.4rem auto 0.4rem;
|
||
height: 0.5rem;
|
||
border-radius: 0.2rem;
|
||
background: rgba(0,0,0,0.22);
|
||
}
|
||
|
||
.pete-hand {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.6rem;
|
||
min-height: 8.6rem;
|
||
}
|
||
|
||
/* One card. The wrapper does the flight, the inner face does the flip, so the
|
||
two never fight over the same transform. */
|
||
/* The size is a variable because solitaire has seven columns to fit and
|
||
blackjack has two hands. Everything below is in terms of it, so a table can
|
||
shrink its cards without a second set of rules. */
|
||
.pete-card {
|
||
perspective: 700px;
|
||
height: var(--card-h, 8.4rem);
|
||
width: var(--card-w, 6rem);
|
||
animation: pete-deal 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||
}
|
||
.pete-card-inner {
|
||
position: relative;
|
||
height: 100%;
|
||
width: 100%;
|
||
transform-style: preserve-3d;
|
||
transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
/* Face-down is the resting state of a card that hasn't been turned over: the
|
||
hole card sits like this until the dealer plays. */
|
||
.pete-card[data-face="down"] .pete-card-inner { transform: rotateY(180deg); }
|
||
|
||
.pete-card-front,
|
||
.pete-card-back {
|
||
position: absolute;
|
||
inset: 0;
|
||
display: grid;
|
||
border-radius: 0.55rem;
|
||
backface-visibility: hidden;
|
||
-webkit-backface-visibility: hidden;
|
||
box-shadow: 0 3px 0 rgba(0,0,0,0.18), 0 6px 14px rgba(0,0,0,0.22);
|
||
}
|
||
/* The face is a single SVG on a 100×140 field (see blackjack.js) — suits as
|
||
vector shapes, pips at printed-deck coordinates. The card element only has
|
||
to hold it and colour it: `fill: currentColor` means the red/black switch
|
||
below repaints every pip, index and court letter at once. */
|
||
.pete-card-front {
|
||
place-items: stretch;
|
||
background: #fdfaf2;
|
||
border: 2px solid rgba(30,20,10,0.12);
|
||
color: #2b2118;
|
||
font-family: "Fredoka", "Nunito", system-ui, sans-serif;
|
||
line-height: 1;
|
||
overflow: hidden;
|
||
}
|
||
.pete-card-front[data-red="1"] { color: #cc3d4a; }
|
||
|
||
/* The back. Turned away from you until the card is: this is what the dealer's
|
||
hole card shows while you're still acting. */
|
||
.pete-card-back {
|
||
transform: rotateY(180deg);
|
||
background:
|
||
repeating-linear-gradient(45deg, rgba(255,255,255,0.10) 0 6px, transparent 6px 12px),
|
||
linear-gradient(150deg, #b4553f, #8d3f2f);
|
||
border: 2px solid rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.pete-card-svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
fill: currentColor;
|
||
}
|
||
|
||
/* The corner index. Tabular-ish and tight, so a 10 doesn't shove the suit. */
|
||
.pete-card-idx {
|
||
font-size: 26px;
|
||
font-weight: 700;
|
||
text-anchor: middle;
|
||
fill: currentColor;
|
||
}
|
||
|
||
/* The court panel: a frame, not a portrait. */
|
||
.pete-card-panel {
|
||
fill: currentColor;
|
||
fill-opacity: 0.06;
|
||
stroke: currentColor;
|
||
stroke-opacity: 0.35;
|
||
stroke-width: 1.5;
|
||
}
|
||
.pete-card-court {
|
||
font-size: 34px;
|
||
font-weight: 700;
|
||
text-anchor: middle;
|
||
fill: currentColor;
|
||
}
|
||
|
||
/* The flight itself: out of the shoe (up and to the right), scaled down and
|
||
spinning slightly, into place — and then a beat of settling, because a card
|
||
that stops dead on the felt looks like it was pasted there. It overshoots a
|
||
little and rocks back onto --tilt, the small resting angle the JS gives each
|
||
card so a hand looks handled rather than typeset. */
|
||
@keyframes pete-deal {
|
||
0% {
|
||
opacity: 0;
|
||
transform: translate(var(--deal-x, 14rem), var(--deal-y, -7rem)) scale(0.72) rotate(9deg);
|
||
}
|
||
62% {
|
||
opacity: 1;
|
||
transform: translate(0, 0.35rem) scale(1.05) rotate(calc(var(--tilt, 0deg) - 2deg));
|
||
}
|
||
100% {
|
||
opacity: 1;
|
||
transform: translate(0, 0) scale(1) rotate(var(--tilt, 0deg));
|
||
}
|
||
}
|
||
/* And the weight: the felt takes the hit. The shadow under a landing card
|
||
spreads and darkens for the instant it arrives. */
|
||
.pete-card::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: auto 6% -0.35rem 6%;
|
||
height: 0.6rem;
|
||
border-radius: 999px;
|
||
background: rgba(0, 0, 0, 0.35);
|
||
filter: blur(5px);
|
||
animation: pete-thud 0.42s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||
}
|
||
@keyframes pete-thud {
|
||
0%, 55% { opacity: 0; transform: scale(0.4); }
|
||
72% { opacity: 0.9; transform: scale(1.15); }
|
||
100% { opacity: 0.45; transform: scale(1); }
|
||
}
|
||
|
||
/* A settled hand: the winning side gets a little lift, so a win reads at a
|
||
glance rather than only in the text. */
|
||
.pete-hand[data-won="1"] .pete-card { animation: pete-deal 0.42s cubic-bezier(0.22,1,0.36,1) backwards, pete-win 0.6s ease 0.1s; }
|
||
@keyframes pete-win {
|
||
0%, 100% { transform: rotate(var(--tilt, 0deg)) translateY(0); }
|
||
40% { transform: rotate(var(--tilt, 0deg)) translateY(-0.6rem); }
|
||
}
|
||
/* A losing hand doesn't just sit there being wrong: it greys off. */
|
||
.pete-hand[data-won="-1"] .pete-card {
|
||
filter: saturate(0.45) brightness(0.78);
|
||
transition: filter 0.5s ease 0.2s;
|
||
}
|
||
|
||
/* ---- chips ---------------------------------------------------------------
|
||
One disc, three jobs: the buttons you bet with, the stack sitting in the bet
|
||
spot, and the chip in mid-air between them. Same face on all three, because
|
||
they are meant to read as the *same chip* being moved around. */
|
||
|
||
.pete-disc {
|
||
position: relative;
|
||
border-radius: 999px;
|
||
background:
|
||
radial-gradient(circle at 50% 30%, rgba(255,255,255,0.30), transparent 62%),
|
||
var(--chip, #e07a5f);
|
||
border: 2px solid rgba(0,0,0,0.22);
|
||
box-shadow: 0 3px 0 rgba(0,0,0,0.28), 0 6px 14px rgba(0,0,0,0.20);
|
||
}
|
||
/* The edge spots every casino chip has, cut as a dashed ring inside the rim. */
|
||
.pete-disc::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 11%;
|
||
border-radius: 999px;
|
||
border: 2px dashed rgba(255,255,255,0.55);
|
||
}
|
||
.pete-disc[data-chip="5"] { --chip: #5aa9e6; }
|
||
.pete-disc[data-chip="25"] { --chip: #4caf7d; }
|
||
.pete-disc[data-chip="100"] { --chip: #2b2118; }
|
||
.pete-disc[data-chip="500"] { --chip: #b079d6; }
|
||
|
||
.pete-chip { /* the buttons */
|
||
transition: transform 0.12s ease;
|
||
}
|
||
.pete-chip > span {
|
||
position: relative; /* over the dashed ring */
|
||
}
|
||
.pete-chip:active { transform: translateY(1px) scale(0.94); }
|
||
|
||
/* The bet spot: where your stake actually sits while the hand is played. */
|
||
.pete-spot {
|
||
position: relative;
|
||
display: grid;
|
||
place-items: center;
|
||
height: 7rem;
|
||
width: 7rem;
|
||
flex: none;
|
||
border-radius: 999px;
|
||
border: 2px dashed rgba(255,255,255,0.35);
|
||
background: radial-gradient(circle at 50% 40%, rgba(255,255,255,0.10), transparent 70%);
|
||
box-shadow: inset 0 0 0 6px rgba(0,0,0,0.08);
|
||
transition: box-shadow 0.3s ease, border-color 0.3s ease;
|
||
}
|
||
.pete-spot[data-live="1"] {
|
||
border-color: rgba(var(--glow, 242,181,61), 0.85);
|
||
box-shadow: inset 0 0 0 6px rgba(0,0,0,0.08), 0 0 22px rgba(var(--glow, 242,181,61), 0.35);
|
||
}
|
||
.pete-spot-label {
|
||
font-size: 0.65rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.12em;
|
||
text-transform: uppercase;
|
||
color: rgba(255,255,255,0.35);
|
||
}
|
||
.pete-spot[data-live="1"] .pete-spot-label { opacity: 0; }
|
||
|
||
/* The player's hands at blackjack. There is one of them, until you split.
|
||
Then there are up to four, side by side, each with its own chips on its own
|
||
spot — because after a split they are not one bet in two piles, they are two
|
||
bets that win and lose separately. */
|
||
.bj-hands {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: flex-start;
|
||
gap: 0.75rem 1rem;
|
||
}
|
||
.bj-hand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.85rem;
|
||
border-radius: 1.5rem;
|
||
padding: 0.4rem 0.7rem 0.4rem 0.4rem;
|
||
transition: opacity 0.25s ease, box-shadow 0.25s ease, background 0.25s ease;
|
||
}
|
||
/* Which hand you are actually playing. The rest are still on the table; they
|
||
are simply not your problem this second. */
|
||
.bj-hand[data-live="0"] { opacity: 0.5; }
|
||
.bj-hand[data-live="1"] {
|
||
background: rgba(0,0,0,0.16);
|
||
box-shadow: 0 0 0 2px rgba(var(--glow, 242,181,61), 0.5);
|
||
}
|
||
/* Four hands do not fit at one hand's worth of card, and the fix is to tell the
|
||
row what a card *is* rather than to squash the box a card is in: everything
|
||
about a card is sized off these two vars. The spot shrinks with them, or the
|
||
chips end up bigger than the cards they're riding on. */
|
||
.bj-hands[data-count="2"] { --card-h: 6.6rem; --card-w: 4.7rem; }
|
||
.bj-hands[data-count="3"],
|
||
.bj-hands[data-count="4"] { --card-h: 5rem; --card-w: 3.6rem; }
|
||
.bj-hands[data-count="2"] .pete-hand { min-height: 6.8rem; }
|
||
.bj-hands[data-count="3"] .pete-hand,
|
||
.bj-hands[data-count="4"] .pete-hand { min-height: 5.2rem; gap: 0.35rem; }
|
||
.bj-hands[data-count="2"] .pete-spot { height: 5.5rem; width: 5.5rem; }
|
||
.bj-hands[data-count="3"] .pete-spot,
|
||
.bj-hands[data-count="4"] .pete-spot { height: 4.25rem; width: 4.25rem; }
|
||
.bj-hands[data-count="3"] .pete-spot-label,
|
||
.bj-hands[data-count="4"] .pete-spot-label { display: none; }
|
||
|
||
/* The card a split is about to take away. It lifts, and then it's gone —
|
||
otherwise a card simply teleports into a hand that didn't exist yet. */
|
||
.bj-splitting { animation: bj-split-lift 0.32s ease forwards; }
|
||
@keyframes bj-split-lift {
|
||
from { transform: translateY(0) rotate(0); }
|
||
to { transform: translateY(-1.1rem) rotate(6deg); }
|
||
}
|
||
|
||
/* The stack in the spot. Each chip is offset up the pile by its index, so ten
|
||
chips look like ten chips rather than one chip with a number on it. */
|
||
.pete-stack {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
}
|
||
.pete-stack .pete-disc {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
height: 2.6rem;
|
||
width: 2.6rem;
|
||
margin: -1.3rem 0 0 -1.3rem;
|
||
/* Each chip sits proud of the one below it by more than its own rim, so a
|
||
stack of three reads as three. Less than this and they hide inside each
|
||
other and a 150 bet looks the same as a 25 one. */
|
||
transform: translateY(calc(-0.5rem * var(--i, 0))) rotate(var(--spin, 0deg));
|
||
box-shadow: 0 2px 0 rgba(0,0,0,0.35), 0 4px 8px rgba(0,0,0,0.25);
|
||
animation: pete-chip-land 0.28s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
|
||
}
|
||
@keyframes pete-chip-land {
|
||
from { transform: translateY(calc(-0.5rem * var(--i, 0) - 1.1rem)) rotate(var(--spin, 0deg)) scale(1.18); }
|
||
}
|
||
.pete-spot-total {
|
||
position: absolute;
|
||
bottom: -0.6rem;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
white-space: nowrap;
|
||
border-radius: 999px;
|
||
background: rgba(0,0,0,0.45);
|
||
padding: 0.1rem 0.6rem;
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
font-variant-numeric: tabular-nums;
|
||
color: #fff;
|
||
}
|
||
|
||
/* A chip in mid-air. Lives in a fixed overlay so it can cross from the betting
|
||
buttons, over the page, and onto the felt without any container clipping it. */
|
||
.pete-fly-layer {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 60;
|
||
pointer-events: none;
|
||
overflow: hidden;
|
||
}
|
||
.pete-fly {
|
||
position: absolute;
|
||
height: 2.6rem;
|
||
width: 2.6rem;
|
||
margin: -1.3rem 0 0 -1.3rem;
|
||
will-change: transform;
|
||
}
|
||
|
||
/* The house's own chips, racked on the felt. This is where a payout comes
|
||
from, and where a losing stake goes — so the money has somewhere to be.
|
||
It sits alongside the shoe rather than over on the left, because the left of
|
||
the felt is where the dealer's cards land. */
|
||
.pete-rack {
|
||
position: absolute;
|
||
top: 1.25rem;
|
||
right: 5.75rem;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
gap: 0.4rem;
|
||
padding: 0.5rem 0.6rem 0.45rem;
|
||
border-radius: 0.75rem;
|
||
background: rgba(0,0,0,0.20);
|
||
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.06);
|
||
}
|
||
/* A column in the rack is a *stack*, so it's striped: one band per chip, with
|
||
the shadow of the chip above it cut into each. A plain coloured lozenge here
|
||
read as a blob, which is the one thing a pile of money shouldn't. */
|
||
.pete-rack span {
|
||
display: block;
|
||
width: 1.7rem;
|
||
border-radius: 0.35rem;
|
||
background:
|
||
linear-gradient(90deg, rgba(0,0,0,0.28), transparent 35%, transparent 65%, rgba(0,0,0,0.28)),
|
||
repeating-linear-gradient(180deg,
|
||
rgba(255,255,255,0.18) 0 0.06rem,
|
||
var(--chip, #e07a5f) 0.06rem 0.3rem,
|
||
rgba(0,0,0,0.35) 0.3rem 0.36rem);
|
||
border: 1px solid rgba(0,0,0,0.35);
|
||
height: calc(0.36rem * var(--stack, 3));
|
||
}
|
||
.pete-rack span[data-chip="5"] { --chip: #5aa9e6; }
|
||
.pete-rack span[data-chip="25"] { --chip: #4caf7d; }
|
||
.pete-rack span[data-chip="100"] { --chip: #2b2118; }
|
||
.pete-rack span[data-chip="500"] { --chip: #b079d6; }
|
||
|
||
/* On a phone the rack has to get out of the way. At its desk size it is a
|
||
147px block sitting 92px in from the edge — a corner on a wide felt, but the
|
||
middle of the table on a 390px one, where it sat on top of trivia's
|
||
multiplier. So on a small screen it gets smaller, and where there's nothing
|
||
in the corner it pulls into the corner.
|
||
|
||
Where the rack sits is the one thing that differs per table, which is what
|
||
data-at says. Unmarked, it's alone in the corner. "shoe" means blackjack,
|
||
where the 5.75rem inset is not a margin but the width of the shoe it sits
|
||
beside — pull that one to the edge and it slides under the deck. "rail" means
|
||
solitaire, where the rack isn't on the felt at all (position: static). */
|
||
@media (max-width: 639px) {
|
||
.pete-rack:not([data-at="rail"]) {
|
||
gap: 0.25rem;
|
||
padding: 0.35rem 0.4rem 0.3rem;
|
||
}
|
||
.pete-rack:not([data-at="rail"]) span { width: 1.15rem; }
|
||
.pete-rack:not([data-at]) { right: 1rem; }
|
||
}
|
||
|
||
/* The burst. A natural gets confetti; nothing else in the room does, which is
|
||
what keeps it worth something. */
|
||
.pete-spark {
|
||
position: absolute;
|
||
height: 0.75rem;
|
||
width: 0.45rem;
|
||
border-radius: 2px;
|
||
box-shadow: 0 1px 3px rgba(0,0,0,0.35);
|
||
will-change: transform, opacity;
|
||
}
|
||
|
||
/* Falling money — the win curtain. Positioned entirely by the transform WAAPI
|
||
drives (see moneyRain in casino-fx.js), so it starts pinned to the top-left. */
|
||
.pete-money {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
font-size: 1.6rem;
|
||
line-height: 1;
|
||
filter: drop-shadow(0 2px 2px rgba(0,0,0,0.35));
|
||
will-change: transform, opacity;
|
||
}
|
||
|
||
/* The dealer's beat before they draw out. */
|
||
.pete-dealer-think {
|
||
animation: pete-think 0.9s ease-in-out infinite;
|
||
}
|
||
@keyframes pete-think {
|
||
0%, 100% { opacity: 0.6; }
|
||
50% { opacity: 1; transform: translateY(-1px); }
|
||
}
|
||
|
||
/* ---- hangman -------------------------------------------------------------
|
||
The gallows is the meter: it counts down your lives and your winnings at the
|
||
same time. So a miss has to *land* — the limb draws itself in along its own
|
||
length, the whole thing flinches, and the multiple falls. Three parts of one
|
||
event, and the game is about watching it happen to you. */
|
||
|
||
.pete-gallows {
|
||
overflow: visible;
|
||
fill: none;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
.pete-gallows-frame path {
|
||
stroke: rgba(0, 0, 0, 0.35);
|
||
stroke-width: 6;
|
||
}
|
||
/* The rope. Same post, thinner, so it reads as something that would hold. */
|
||
.pete-gallows-frame path:last-child {
|
||
stroke: rgba(0, 0, 0, 0.3);
|
||
stroke-width: 3.5;
|
||
}
|
||
.pete-gallows-body > * {
|
||
stroke: #fff;
|
||
stroke-width: 5;
|
||
opacity: 0;
|
||
filter: drop-shadow(0 2px 0 rgba(0, 0, 0, 0.25));
|
||
}
|
||
.pete-gallows-body > [data-on="1"] { opacity: 1; }
|
||
|
||
/* Drawn, not faded in: the stroke runs on along its own path. 260 is longer
|
||
than any limb here, which is all a dash offset needs to be. */
|
||
.pete-part-draw {
|
||
stroke-dasharray: 260;
|
||
animation: pete-part 0.45s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||
}
|
||
@keyframes pete-part {
|
||
from { stroke-dashoffset: 260; }
|
||
to { stroke-dashoffset: 0; }
|
||
}
|
||
|
||
.pete-shake { animation: pete-shake 0.42s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
|
||
@keyframes pete-shake {
|
||
10%, 90% { transform: translateX(-1.5px); }
|
||
20%, 80% { transform: translateX(3px); }
|
||
30%, 50%, 70% { transform: translateX(-5px); }
|
||
40%, 60% { transform: translateX(5px); }
|
||
}
|
||
|
||
/* The phrase. Tiles wrap between words, never inside one. */
|
||
.pete-board {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.35rem 1.1rem;
|
||
min-height: 5rem;
|
||
align-items: center;
|
||
}
|
||
.pete-word {
|
||
display: flex;
|
||
gap: 0.3rem;
|
||
}
|
||
|
||
.pete-tile {
|
||
display: grid;
|
||
place-items: center;
|
||
height: 2.9rem;
|
||
width: 2.2rem;
|
||
border-radius: 0.5rem;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 1.4rem;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
background: rgba(0, 0, 0, 0.22);
|
||
border-bottom: 3px solid rgba(0, 0, 0, 0.28);
|
||
text-transform: uppercase;
|
||
}
|
||
/* A letter you've earned sits proud of the ones you haven't. */
|
||
.pete-tile[data-up="1"] {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
color: #2b2118;
|
||
border-bottom-color: rgba(0, 0, 0, 0.35);
|
||
}
|
||
/* Punctuation is scaffolding: it was never yours to guess, so it gets no tile
|
||
to guess it into. */
|
||
.pete-tile[data-punct="1"] {
|
||
background: none;
|
||
border: 0;
|
||
width: auto;
|
||
min-width: 0.7rem;
|
||
color: rgba(255, 255, 255, 0.55);
|
||
}
|
||
.pete-tile-hit { animation: pete-tile-hit 0.34s cubic-bezier(0.34, 1.56, 0.64, 1); }
|
||
@keyframes pete-tile-hit {
|
||
0% { transform: rotateX(90deg) scale(1.1); }
|
||
100% { transform: rotateX(0) scale(1); }
|
||
}
|
||
|
||
.pete-missed {
|
||
display: grid;
|
||
place-items: center;
|
||
height: 1.5rem;
|
||
min-width: 1.5rem;
|
||
padding: 0 0.3rem;
|
||
border-radius: 0.375rem;
|
||
background: rgba(0, 0, 0, 0.25);
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
color: rgba(255, 255, 255, 0.5);
|
||
text-decoration: line-through;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
/* What a win is worth, right now. */
|
||
.pete-meter {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.5rem;
|
||
border-radius: 999px;
|
||
background: rgba(0, 0, 0, 0.28);
|
||
padding: 0.4rem 0.9rem;
|
||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||
transition: box-shadow 0.3s ease;
|
||
}
|
||
.pete-meter-label {
|
||
font-size: 0.65rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.1em;
|
||
color: rgba(255, 255, 255, 0.45);
|
||
}
|
||
.pete-meter-value {
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 1.5rem;
|
||
font-weight: 700;
|
||
font-variant-numeric: tabular-nums;
|
||
color: var(--accent);
|
||
}
|
||
/* Down at the floor: a win now hands back the stake and nothing else. */
|
||
.pete-meter[data-cold="1"] .pete-meter-value { color: rgba(255, 255, 255, 0.55); }
|
||
.pete-meter[data-hit="1"] {
|
||
box-shadow: inset 0 0 0 2px rgba(204, 61, 74, 0.9);
|
||
animation: pete-meter-hit 0.4s ease;
|
||
}
|
||
@keyframes pete-meter-hit {
|
||
0% { transform: scale(1); }
|
||
35% { transform: scale(0.94); }
|
||
100% { transform: scale(1); }
|
||
}
|
||
|
||
/* The keyboard. */
|
||
.pete-keys { display: grid; gap: 0.35rem; }
|
||
.pete-key-row {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 0.35rem;
|
||
}
|
||
.pete-key-row[data-digits="1"] { margin-top: 0.25rem; opacity: 0.75; }
|
||
.pete-key-row[data-digits="1"] .pete-key {
|
||
height: 2rem;
|
||
min-width: 1.8rem;
|
||
font-size: 0.8rem;
|
||
}
|
||
.pete-key {
|
||
height: 2.75rem;
|
||
min-width: 2.2rem;
|
||
flex: 0 1 2.4rem;
|
||
border-radius: 0.6rem;
|
||
background: color-mix(in srgb, var(--ink) 6%, transparent);
|
||
border: 2px solid color-mix(in srgb, var(--ink) 10%, transparent);
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-weight: 700;
|
||
color: var(--ink);
|
||
transition: transform 0.08s ease, background 0.15s ease, opacity 0.15s ease;
|
||
}
|
||
.pete-key:hover:not(:disabled) { background: color-mix(in srgb, var(--ink) 12%, transparent); }
|
||
.pete-key:active:not(:disabled) { transform: translateY(1px) scale(0.96); }
|
||
.pete-key:disabled { cursor: default; }
|
||
/* A key that's been spent looks spent — otherwise you spend it twice. */
|
||
.pete-key[data-state="hit"] {
|
||
background: #4caf7d;
|
||
border-color: #3d9367;
|
||
color: #fff;
|
||
opacity: 1;
|
||
}
|
||
.pete-key[data-state="miss"] {
|
||
background: color-mix(in srgb, var(--ink) 4%, transparent);
|
||
color: color-mix(in srgb, var(--ink) 30%, transparent);
|
||
text-decoration: line-through;
|
||
}
|
||
.pete-key:disabled[data-state=""] { opacity: 0.4; }
|
||
|
||
/* Picking a length. The one you're on is lit. */
|
||
.pete-tier {
|
||
background: color-mix(in srgb, var(--ink) 4%, transparent);
|
||
border-color: color-mix(in srgb, var(--ink) 10%, transparent);
|
||
}
|
||
.pete-tier:hover { background: color-mix(in srgb, var(--ink) 8%, transparent); }
|
||
.pete-tier[data-on="1"] {
|
||
background: color-mix(in srgb, var(--accent) 18%, transparent);
|
||
border-color: var(--accent);
|
||
}
|
||
|
||
/* ---- trivia --------------------------------------------------------------
|
||
The clock is the game, so the clock is the biggest thing on the felt: a bar
|
||
that drains across the top of the question, going hot as it runs out. It is
|
||
*decoration* — the server timed the answer the moment it arrived — but it
|
||
has to be honest decoration, so it's driven from the seconds the server said
|
||
were left rather than from when the browser happened to paint. */
|
||
|
||
.pete-clock {
|
||
position: relative;
|
||
height: 0.6rem;
|
||
border-radius: 999px;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
overflow: hidden;
|
||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.08);
|
||
}
|
||
.pete-clock-fill {
|
||
height: 100%;
|
||
width: 100%;
|
||
border-radius: 999px;
|
||
background: var(--accent);
|
||
transform-origin: left center;
|
||
/* Driven by a transform the browser can run on its own: a width animated on
|
||
every frame from JS is a layout on every frame. */
|
||
transform: scaleX(1);
|
||
}
|
||
/* The last few seconds. The bar stops being a progress meter and starts being
|
||
a warning. */
|
||
.pete-clock[data-hot="1"] .pete-clock-fill { background: #cc3d4a; }
|
||
.pete-clock[data-hot="1"] { animation: pete-clock-pulse 0.7s ease-in-out infinite; }
|
||
@keyframes pete-clock-pulse {
|
||
0%, 100% { box-shadow: inset 0 0 0 2px rgba(204, 61, 74, 0.35); }
|
||
50% { box-shadow: inset 0 0 0 2px rgba(204, 61, 74, 0.95); }
|
||
}
|
||
|
||
/* The four answers. Big targets, because the clock is already the hard part —
|
||
a question you lose to a mis-tap is a question about pointing. */
|
||
.pete-answer {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
width: 100%;
|
||
border-radius: 1rem;
|
||
padding: 0.85rem 1rem;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: #fff;
|
||
background: rgba(0, 0, 0, 0.26);
|
||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1);
|
||
transition: background 0.15s ease, transform 0.1s ease, box-shadow 0.2s ease;
|
||
}
|
||
.pete-answer:hover:not(:disabled) {
|
||
background: rgba(0, 0, 0, 0.36);
|
||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.28);
|
||
}
|
||
.pete-answer:active:not(:disabled) { transform: translateY(1px); }
|
||
.pete-answer:disabled { cursor: default; }
|
||
|
||
/* The letter down the side, so an answer can be picked with the keyboard and
|
||
the key you press is printed on the thing you're pressing it for. */
|
||
.pete-answer-key {
|
||
display: grid;
|
||
place-items: center;
|
||
height: 1.75rem;
|
||
width: 1.75rem;
|
||
flex: none;
|
||
border-radius: 0.6rem;
|
||
background: rgba(255, 255, 255, 0.12);
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.8rem;
|
||
font-weight: 700;
|
||
color: rgba(255, 255, 255, 0.7);
|
||
}
|
||
|
||
/* How it went. Only ever set once the server has decided — the browser has no
|
||
idea which of these is right until it's told. */
|
||
.pete-answer[data-state="right"] {
|
||
background: rgba(46, 160, 103, 0.9);
|
||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.5);
|
||
}
|
||
.pete-answer[data-state="wrong"] {
|
||
background: rgba(204, 61, 74, 0.9);
|
||
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35);
|
||
animation: pete-answer-no 0.45s ease;
|
||
}
|
||
/* The one they *should* have picked, shown alongside the one they did. */
|
||
.pete-answer[data-state="missed"] {
|
||
box-shadow: inset 0 0 0 2px rgba(46, 160, 103, 0.95);
|
||
}
|
||
.pete-answer[data-state="dim"] { opacity: 0.4; }
|
||
@keyframes pete-answer-no {
|
||
0%, 100% { transform: translateX(0); }
|
||
20% { transform: translateX(-6px); }
|
||
45% { transform: translateX(5px); }
|
||
70% { transform: translateX(-3px); }
|
||
}
|
||
|
||
/* The ladder: one pip per rung, filling as they're climbed. It is the only
|
||
picture of how far there is left to fall. */
|
||
.pete-ladder {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.3rem;
|
||
}
|
||
.pete-rung {
|
||
height: 0.5rem;
|
||
width: 1.1rem;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.14);
|
||
transition: background 0.3s ease, transform 0.3s ease;
|
||
}
|
||
.pete-rung[data-on="1"] {
|
||
background: var(--accent);
|
||
transform: scaleY(1.35);
|
||
}
|
||
|
||
/* ---- solitaire -----------------------------------------------------------
|
||
Seven columns, four foundations, a stock and a waste, all of which have to
|
||
fit across the felt on a phone as well as a desk. So the card size is one
|
||
variable derived from the width available, and every gap and fan below is
|
||
derived from *that* — the board scales as one thing rather than as nine
|
||
things that each stop fitting at a different width.
|
||
|
||
The cards themselves don't move by CSS here. A move in solitaire can take a
|
||
card from anywhere to anywhere, so the table re-renders the board and then
|
||
animates each card from where it just was (see the FLIP note in
|
||
solitaire.js). CSS's job is only to say where things sit. */
|
||
|
||
.pete-solitaire {
|
||
--card-w: clamp(2.5rem, 10.6vw, 5.2rem);
|
||
--card-h: calc(var(--card-w) * 1.4);
|
||
--fan-up: calc(var(--card-h) * 0.29); /* how much of a face-up card shows under the next */
|
||
--fan-down: calc(var(--card-h) * 0.14); /* a face-down one shows less: there's nothing to read */
|
||
}
|
||
|
||
/* An empty place a card could be: the stock when it's spent, a foundation
|
||
waiting for its ace, a column waiting for a king. Same footprint as a card,
|
||
so nothing on the board reflows when one empties. */
|
||
/* Where cards go home. Solitaire lifts a copy of every card bound for a
|
||
foundation into here so it can keep flying while the board underneath it
|
||
redraws in one go. It never takes a click and it never scrolls. */
|
||
.pete-flight-layer {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 60;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.pete-slot {
|
||
position: relative;
|
||
display: grid;
|
||
place-items: center;
|
||
height: var(--card-h);
|
||
width: var(--card-w);
|
||
flex: none;
|
||
border-radius: 0.55rem;
|
||
border: 2px dashed rgba(255, 255, 255, 0.25);
|
||
background: rgba(0, 0, 0, 0.12);
|
||
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.18);
|
||
transition: border-color 0.18s ease, background 0.18s ease, transform 0.12s ease;
|
||
}
|
||
.pete-slot-glyph {
|
||
font-size: calc(var(--card-w) * 0.42);
|
||
line-height: 1;
|
||
color: rgba(255, 255, 255, 0.3);
|
||
}
|
||
.pete-slot-glyph[data-red="1"] { color: rgba(255, 170, 170, 0.35); }
|
||
|
||
/* A card sitting *in* a slot — the top of a foundation. The slot keeps its
|
||
footprint whether or not there's a card on it, so an ace going home doesn't
|
||
reflow the row, and a drop target has somewhere to be while it's empty. */
|
||
.pete-slot > .pete-card {
|
||
position: absolute;
|
||
inset: 0;
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
|
||
/* The stock is a button, and it looks like the back of a card because that is
|
||
what it is: a pile you can turn over. */
|
||
.pete-stock {
|
||
border-style: solid;
|
||
border-color: rgba(0, 0, 0, 0.15);
|
||
background:
|
||
repeating-linear-gradient(45deg, rgba(255,255,255,0.10) 0 6px, transparent 6px 12px),
|
||
linear-gradient(150deg, #b4553f, #8d3f2f);
|
||
cursor: pointer;
|
||
}
|
||
.pete-stock:hover { filter: brightness(1.08); }
|
||
.pete-stock:active { transform: translateY(1px) scale(0.98); }
|
||
/* Spent, but there's a pass left: it stops being a pile of cards and starts
|
||
being the gesture of turning the waste back over. */
|
||
.pete-stock[data-empty="1"] {
|
||
background: rgba(0, 0, 0, 0.12);
|
||
border-style: dashed;
|
||
border-color: rgba(255, 255, 255, 0.25);
|
||
}
|
||
/* Spent, and no passes left. Not a button any more — and it says so by looking
|
||
like the dead thing it is rather than by silently refusing clicks. */
|
||
.pete-stock[data-dead="1"] {
|
||
cursor: default;
|
||
opacity: 0.4;
|
||
}
|
||
.pete-stock[data-dead="1"]:hover { filter: none; }
|
||
.pete-stock[data-dead="1"]:active { transform: none; }
|
||
|
||
.pete-slot-count {
|
||
position: absolute;
|
||
bottom: -0.5rem;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
border-radius: 999px;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
padding: 0.05rem 0.5rem;
|
||
font-size: 0.7rem;
|
||
font-weight: 700;
|
||
font-variant-numeric: tabular-nums;
|
||
color: #fff;
|
||
}
|
||
.pete-slot-recycle { color: rgba(255, 255, 255, 0.45); }
|
||
.pete-slot-recycle svg { height: 1.4rem; width: 1.4rem; }
|
||
|
||
/* The waste. Three cards fanned sideways, the top one on the right — you can
|
||
see what's under it, and only the top one is yours to take. */
|
||
.pete-waste {
|
||
position: relative;
|
||
display: flex;
|
||
height: var(--card-h);
|
||
min-width: var(--card-w);
|
||
}
|
||
.pete-waste .pete-card + .pete-card {
|
||
margin-left: calc(var(--card-w) * -0.72);
|
||
}
|
||
.pete-waste .pete-card { position: relative; }
|
||
|
||
/* The seven columns. They share the width evenly and never scroll: a board you
|
||
have to scroll sideways is a board you can't plan on. */
|
||
.pete-tableau {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, var(--card-w));
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
align-items: start;
|
||
min-height: calc(var(--card-h) * 2.6);
|
||
}
|
||
.pete-col {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
min-height: var(--card-h);
|
||
}
|
||
/* Cards overlap up the column, and how much of one shows depends on whether
|
||
there's anything to see. The rule keys off the card *above*, which is why
|
||
it's an adjacent-sibling selector and not something the JS has to compute. */
|
||
.pete-col .pete-card { position: relative; }
|
||
.pete-col .pete-card[data-face="down"] + .pete-card {
|
||
margin-top: calc(var(--fan-down) - var(--card-h));
|
||
}
|
||
.pete-col .pete-card[data-face="up"] + .pete-card {
|
||
margin-top: calc(var(--fan-up) - var(--card-h));
|
||
}
|
||
|
||
/* A card you can pick up says so. */
|
||
.pete-card[data-live="1"] { cursor: pointer; }
|
||
.pete-card[data-live="1"]:hover .pete-card-front {
|
||
filter: brightness(1.06);
|
||
}
|
||
/* The card (or run) in your hand: lifted off the felt, and glowing, so it's
|
||
obvious what a click on a column is about to move. */
|
||
.pete-card[data-held="1"] {
|
||
z-index: 5;
|
||
transform: translateY(-0.55rem);
|
||
transition: transform 0.14s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
.pete-card[data-held="1"] .pete-card-front {
|
||
box-shadow:
|
||
0 3px 0 rgba(0,0,0,0.18),
|
||
0 10px 22px rgba(0,0,0,0.32),
|
||
0 0 0 3px rgba(242, 181, 61, 0.9);
|
||
}
|
||
/* A place the held card would actually go. Lit *before* you click it, because
|
||
the alternative is learning the rules by being told no. */
|
||
.pete-slot[data-drop="1"],
|
||
.pete-col[data-drop="1"] .pete-slot,
|
||
.pete-col[data-drop="1"] .pete-card:last-child .pete-card-front {
|
||
border-color: rgba(242, 181, 61, 0.9);
|
||
box-shadow: 0 0 0 3px rgba(242, 181, 61, 0.45);
|
||
}
|
||
.pete-slot[data-drop="1"],
|
||
.pete-col[data-drop="1"] .pete-slot {
|
||
background: rgba(242, 181, 61, 0.12);
|
||
}
|
||
|
||
/* A move that won't go. Said in the one language a board can speak. */
|
||
.pete-nope { animation: pete-shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97); }
|
||
|
||
/* Dragging. A press on a card that can be lifted belongs to the card and not to
|
||
the page, or a drag down a column scrolls the board instead of moving a run. */
|
||
.pete-card[data-live="1"] { touch-action: none; }
|
||
[data-solitaire][data-dragging] { cursor: grabbing; user-select: none; }
|
||
[data-solitaire][data-dragging] .pete-card[data-live="1"]:hover .pete-card-front {
|
||
filter: none;
|
||
}
|
||
/* The run you lifted stays on the felt, faded: where it came from, and where it
|
||
goes back to if you let go over nothing. */
|
||
[data-solitaire][data-dragging] .pete-card[data-held="1"] {
|
||
opacity: 0.3;
|
||
transform: none;
|
||
}
|
||
[data-solitaire][data-dragging] .pete-card[data-held="1"] .pete-card-front {
|
||
box-shadow: none;
|
||
}
|
||
|
||
/* The copy under your hand. It doesn't answer to hit tests — the felt beneath it
|
||
has to be the thing you're pointing at. */
|
||
.pete-drag {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 0;
|
||
z-index: 60;
|
||
pointer-events: none;
|
||
filter: drop-shadow(0 14px 22px rgba(0, 0, 0, 0.45));
|
||
transform: translate3d(-999px, -999px, 0);
|
||
}
|
||
.pete-drag .pete-card { position: relative; }
|
||
.pete-drag[data-ok="1"] .pete-card-front {
|
||
box-shadow: 0 0 0 3px rgba(242, 181, 61, 0.9);
|
||
}
|
||
|
||
/* The pile you're actually over, told apart from the ones that would merely
|
||
take it. */
|
||
.pete-slot[data-over="1"],
|
||
.pete-col[data-over="1"] .pete-slot,
|
||
.pete-col[data-over="1"] .pete-card:last-child .pete-card-front {
|
||
border-color: rgba(242, 181, 61, 1);
|
||
box-shadow: 0 0 0 4px rgba(242, 181, 61, 0.75);
|
||
}
|
||
.pete-slot[data-over="1"],
|
||
.pete-col[data-over="1"] .pete-slot {
|
||
background: rgba(242, 181, 61, 0.24);
|
||
}
|
||
|
||
/* A card arriving on a foundation lands with a flash: it is the only move in
|
||
the game that pays you, so it is the only one that gets a noise. */
|
||
.pete-home-flash { animation: pete-home 0.5s ease-out; }
|
||
@keyframes pete-home {
|
||
0% { box-shadow: 0 0 0 0 rgba(242, 181, 61, 0.9); }
|
||
100% { box-shadow: 0 0 0 1.4rem rgba(242, 181, 61, 0); }
|
||
}
|
||
|
||
/* The finish button on a won board breathes, so the one press left to make is
|
||
the one the eye lands on. */
|
||
.pete-finish { animation: pete-finish-pulse 1.6s ease-in-out infinite; }
|
||
@keyframes pete-finish-pulse {
|
||
0%, 100% { box-shadow: 0 0 0 0 rgba(242, 181, 61, 0.55); }
|
||
50% { box-shadow: 0 0 0 0.9rem rgba(242, 181, 61, 0); }
|
||
}
|
||
|
||
/* The rail: the house's rack, what you've banked, the meter. On a wide screen
|
||
it's a column down the right of the felt; on a narrow one it lies down and
|
||
sits under the board. */
|
||
.pete-rail {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1rem 1.5rem;
|
||
}
|
||
@media (min-width: 1024px) {
|
||
.pete-rail {
|
||
flex-direction: column;
|
||
justify-content: flex-start;
|
||
width: 9.5rem;
|
||
gap: 1.5rem;
|
||
}
|
||
}
|
||
/* In the rail the rack is just another thing in a column, so it drops the
|
||
absolute positioning it uses when it's pinned to the corner of a felt. */
|
||
.pete-rack[data-at="rail"] {
|
||
position: static;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* ---- uno ------------------------------------------------------------------
|
||
The card is the whole look: a coloured rectangle with a white oval laid
|
||
across it at an angle. Drop the oval and it reads as a button, so the oval
|
||
is not decoration — it is what makes the thing a card.
|
||
|
||
The bots are drawn from a *count*. That is all the server sends, and the fan
|
||
of backs up there is generated from a number rather than from cards, because
|
||
there are no cards to draw. */
|
||
|
||
/* The felt takes a tint of the colour in play. After a wild, the card on the
|
||
pile is not the colour you have to follow — this is the table saying so. */
|
||
.pete-uno[data-c="red"] { --play: #d64545; }
|
||
.pete-uno[data-c="blue"] { --play: #3d7fd6; }
|
||
.pete-uno[data-c="yellow"] { --play: #e0b02c; }
|
||
.pete-uno[data-c="green"] { --play: #46a86b; }
|
||
.pete-uno::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
background: radial-gradient(80% 55% at 50% 45%, var(--play, transparent), transparent 70%);
|
||
opacity: 0.16;
|
||
transition: opacity 0.5s ease, background 0.5s ease;
|
||
}
|
||
|
||
.pete-uno-card {
|
||
position: relative;
|
||
height: var(--uno-h, 6.4rem);
|
||
width: var(--uno-w, 4.3rem);
|
||
border-radius: 0.55rem;
|
||
padding: 0.28rem;
|
||
background: #fff;
|
||
box-shadow: 0 3px 0 rgba(0,0,0,0.22), 0 6px 14px rgba(0,0,0,0.18);
|
||
flex: none;
|
||
}
|
||
.pete-uno-face {
|
||
position: relative;
|
||
display: grid;
|
||
place-items: center;
|
||
height: 100%;
|
||
width: 100%;
|
||
border-radius: 0.38rem;
|
||
overflow: hidden;
|
||
background: var(--uno, #2b2118);
|
||
color: #fff;
|
||
}
|
||
.pete-uno-card[data-c="red"] { --uno: #d64545; }
|
||
.pete-uno-card[data-c="blue"] { --uno: #3d7fd6; }
|
||
.pete-uno-card[data-c="yellow"] { --uno: #e0b02c; }
|
||
.pete-uno-card[data-c="green"] { --uno: #46a86b; }
|
||
.pete-uno-card[data-c="wild"] { --uno: #2b2118; }
|
||
|
||
/* A wild that has been played wears the colour it was called as — the pile has
|
||
to show what you must follow, not what was printed. */
|
||
.pete-uno-card[data-named="red"] { box-shadow: 0 0 0 3px #d64545, 0 3px 0 rgba(0,0,0,0.22); }
|
||
.pete-uno-card[data-named="blue"] { box-shadow: 0 0 0 3px #3d7fd6, 0 3px 0 rgba(0,0,0,0.22); }
|
||
.pete-uno-card[data-named="yellow"] { box-shadow: 0 0 0 3px #e0b02c, 0 3px 0 rgba(0,0,0,0.22); }
|
||
.pete-uno-card[data-named="green"] { box-shadow: 0 0 0 3px #46a86b, 0 3px 0 rgba(0,0,0,0.22); }
|
||
|
||
/* The oval. Tilted, because it is tilted on the card in your hand. */
|
||
.pete-uno-oval {
|
||
position: relative;
|
||
z-index: 1;
|
||
display: grid;
|
||
place-items: center;
|
||
height: 78%;
|
||
width: 62%;
|
||
border-radius: 50%;
|
||
transform: rotate(-24deg);
|
||
background: #fff;
|
||
color: var(--uno, #2b2118);
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 1.45rem;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
text-shadow: 0 1px 0 rgba(0,0,0,0.10);
|
||
}
|
||
.pete-uno-card[data-c="wild"] .pete-uno-oval { color: #2b2118; font-size: 1.15rem; }
|
||
|
||
.pete-uno-corner {
|
||
position: absolute;
|
||
z-index: 1;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.62rem;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
color: #fff;
|
||
text-shadow: 0 1px 1px rgba(0,0,0,0.35);
|
||
}
|
||
.pete-uno-corner[data-at="tl"] { top: 0.22rem; left: 0.28rem; }
|
||
.pete-uno-corner[data-at="br"] { bottom: 0.22rem; right: 0.28rem; transform: rotate(180deg); }
|
||
|
||
/* The four quadrants of a wild, under the oval. */
|
||
.pete-uno-wheel {
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
conic-gradient(#d64545 0 25%, #e0b02c 0 50%, #46a86b 0 75%, #3d7fd6 0);
|
||
opacity: 0.92;
|
||
}
|
||
|
||
/* The back. Every card you are not entitled to see is one of these. */
|
||
.pete-uno-card-back { padding: 0.28rem; }
|
||
.pete-uno-back {
|
||
display: grid;
|
||
place-items: center;
|
||
height: 100%;
|
||
width: 100%;
|
||
border-radius: 0.38rem;
|
||
background:
|
||
radial-gradient(120% 80% at 50% 0%, rgba(255,255,255,0.16), transparent 60%),
|
||
#2b2118;
|
||
}
|
||
.pete-uno-back::after {
|
||
content: "UNO";
|
||
display: block;
|
||
transform: rotate(-24deg);
|
||
padding: 0.1rem 0.35rem;
|
||
border-radius: 999px;
|
||
background: #e0b02c;
|
||
color: #2b2118;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.6rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
/* Your hand. It fans, it wraps, and a card you can play stands up out of it —
|
||
being shown what goes is the game teaching you, and the server still decides. */
|
||
.pete-uno-hand {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
gap: 0.4rem;
|
||
min-height: 6.8rem;
|
||
padding-top: 0.6rem;
|
||
}
|
||
.pete-uno-hand .pete-uno-card {
|
||
animation: pete-uno-in 0.34s cubic-bezier(0.22, 1, 0.36, 1) backwards;
|
||
animation-delay: calc(var(--i, 0) * 45ms);
|
||
transition: transform 0.16s ease, filter 0.16s ease;
|
||
}
|
||
/* A card that was already in your hand before this redraw doesn't get dealt to
|
||
you again. The hand is redrawn several times a lap now (every event that
|
||
changes it), and without this every one of them re-fans the whole thing. */
|
||
.pete-uno-hand .pete-uno-card[data-settled="1"] { animation: none; }
|
||
.pete-uno-hand .pete-uno-card[data-on="1"] {
|
||
cursor: pointer;
|
||
transform: translateY(-0.5rem);
|
||
}
|
||
.pete-uno-hand .pete-uno-card[data-on="1"]:hover { transform: translateY(-1.1rem) scale(1.03); }
|
||
.pete-uno-hand .pete-uno-card[data-on="0"] { filter: brightness(0.62) saturate(0.7); }
|
||
@keyframes pete-uno-in {
|
||
from { transform: translateY(2rem) rotate(6deg); opacity: 0; }
|
||
to { transform: translateY(-0.5rem); opacity: 1; }
|
||
}
|
||
|
||
/* A seat: a fan of backs, a name, a count. The fan overlaps, so eight cards
|
||
read as a hand rather than a row. */
|
||
.pete-uno-seat {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.3rem;
|
||
position: relative;
|
||
padding: 0.5rem 0.7rem;
|
||
border-radius: 1rem;
|
||
transition: background 0.25s ease, transform 0.25s ease;
|
||
}
|
||
.pete-uno-seat[data-turn="1"] {
|
||
background: rgba(255,255,255,0.12);
|
||
transform: translateY(-2px);
|
||
}
|
||
.pete-uno-seat[data-uno="1"] .pete-uno-count { color: #ffd76e; }
|
||
|
||
.pete-uno-fan {
|
||
display: flex;
|
||
--uno-h: 3.2rem;
|
||
--uno-w: 2.15rem;
|
||
}
|
||
.pete-uno-fan .pete-uno-card {
|
||
margin-left: -1.35rem;
|
||
transform: rotate(calc((var(--i, 0) - (var(--n, 1) - 1) / 2) * 4deg));
|
||
transform-origin: bottom center;
|
||
box-shadow: 0 2px 0 rgba(0,0,0,0.22);
|
||
}
|
||
.pete-uno-fan .pete-uno-card:first-child { margin-left: 0; }
|
||
.pete-uno-fan .pete-uno-back::after { font-size: 0.4rem; padding: 0.06rem 0.22rem; }
|
||
|
||
.pete-uno-name {
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.9rem;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
line-height: 1;
|
||
}
|
||
.pete-uno-count {
|
||
font-size: 0.7rem;
|
||
font-weight: 700;
|
||
color: rgba(255,255,255,0.55);
|
||
line-height: 1;
|
||
}
|
||
|
||
/* The deck you draw from, and the pile you play onto. */
|
||
.pete-uno-deck {
|
||
position: relative;
|
||
--uno-h: 6.4rem;
|
||
--uno-w: 4.3rem;
|
||
height: var(--uno-h);
|
||
width: var(--uno-w);
|
||
border-radius: 0.55rem;
|
||
padding: 0.28rem;
|
||
background: #fff;
|
||
box-shadow:
|
||
0 3px 0 rgba(0,0,0,0.22),
|
||
0.28rem 0.28rem 0 -0.06rem rgba(255,255,255,0.35),
|
||
0.5rem 0.5rem 0 -0.12rem rgba(255,255,255,0.18);
|
||
transition: transform 0.15s ease;
|
||
}
|
||
.pete-uno-deck:not(:disabled):hover { transform: translateY(-3px); }
|
||
.pete-uno-deck:disabled { opacity: 0.75; }
|
||
.pete-uno-deck-count {
|
||
position: absolute;
|
||
bottom: -0.55rem;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
padding: 0.1rem 0.5rem;
|
||
border-radius: 999px;
|
||
background: rgba(0,0,0,0.55);
|
||
color: #fff;
|
||
font-size: 0.65rem;
|
||
font-weight: 700;
|
||
line-height: 1.4;
|
||
}
|
||
.pete-uno-shuffle { animation: pete-uno-shuffle 0.42s ease; }
|
||
@keyframes pete-uno-shuffle {
|
||
0%, 100% { transform: none; }
|
||
30% { transform: translateY(-4px) rotate(-3deg); }
|
||
65% { transform: translateY(2px) rotate(2deg); }
|
||
}
|
||
|
||
.pete-uno-discard {
|
||
display: grid;
|
||
place-items: center;
|
||
height: 6.4rem;
|
||
width: 4.3rem;
|
||
border-radius: 0.55rem;
|
||
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.14);
|
||
}
|
||
.pete-uno-land { animation: pete-uno-land 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); }
|
||
@keyframes pete-uno-land {
|
||
from { transform: scale(1.14) rotate(-4deg); }
|
||
to { transform: none; }
|
||
}
|
||
|
||
.pete-uno-colour {
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
padding: 0.15rem 0.6rem;
|
||
border-radius: 999px;
|
||
color: #fff;
|
||
background: rgba(0,0,0,0.3);
|
||
}
|
||
.pete-uno-colour[data-c="red"] { background: #d64545; }
|
||
.pete-uno-colour[data-c="blue"] { background: #3d7fd6; }
|
||
.pete-uno-colour[data-c="yellow"] { background: #e0b02c; color: #2b2118; }
|
||
.pete-uno-colour[data-c="green"] { background: #46a86b; }
|
||
|
||
/* A word thrown at a seat: SKIPPED, UNO!, +4. */
|
||
.pete-uno-badge {
|
||
position: absolute;
|
||
top: -0.4rem;
|
||
left: 50%;
|
||
z-index: 5;
|
||
padding: 0.2rem 0.6rem;
|
||
border-radius: 999px;
|
||
background: #fff;
|
||
color: #2b2118;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
box-shadow: 0 3px 0 rgba(0,0,0,0.2);
|
||
animation: pete-uno-badge 0.9s ease forwards;
|
||
}
|
||
.pete-uno-badge[data-tone="bad"] { background: #cc3d4a; color: #fff; }
|
||
.pete-uno-badge[data-tone="uno"] { background: #e0b02c; }
|
||
@keyframes pete-uno-badge {
|
||
0% { transform: translate(-50%, 0.4rem) scale(0.7); opacity: 0; }
|
||
25% { transform: translate(-50%, -0.5rem) scale(1.06); opacity: 1; }
|
||
75% { transform: translate(-50%, -0.9rem) scale(1); opacity: 1; }
|
||
100% { transform: translate(-50%, -1.6rem) scale(0.95); opacity: 0; }
|
||
}
|
||
|
||
/* Naming a colour. It covers the felt because until it's answered there is no
|
||
legal move on the table underneath it. */
|
||
.pete-uno-wild {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 10;
|
||
display: grid;
|
||
place-items: center;
|
||
background: rgba(10, 20, 15, 0.55);
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
.pete-uno-wild-box {
|
||
padding: 1.25rem 1.5rem;
|
||
border-radius: 1.25rem;
|
||
background: rgba(20, 40, 30, 0.92);
|
||
box-shadow: 0 12px 30px rgba(0,0,0,0.35);
|
||
text-align: center;
|
||
}
|
||
.pete-uno-swatch {
|
||
padding: 0.7rem 1.4rem;
|
||
border-radius: 0.75rem;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 1rem;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
background: var(--sw, #666);
|
||
box-shadow: 0 3px 0 rgba(0,0,0,0.3);
|
||
transition: transform 0.12s ease, filter 0.12s ease;
|
||
}
|
||
.pete-uno-swatch:hover { transform: translateY(-2px); filter: brightness(1.08); }
|
||
.pete-uno-swatch:active { transform: translateY(1px); }
|
||
.pete-uno-swatch[data-c="red"] { --sw: #d64545; }
|
||
.pete-uno-swatch[data-c="blue"] { --sw: #3d7fd6; }
|
||
.pete-uno-swatch[data-c="yellow"] { --sw: #e0b02c; color: #2b2118; }
|
||
.pete-uno-swatch[data-c="green"] { --sw: #46a86b; }
|
||
|
||
/* ---- calling it, and catching the seat that didn't -------------------------
|
||
The call is a button with a clock on it. It wants to be the loudest thing on
|
||
the felt for the two-and-a-bit seconds it exists, because the whole rule is
|
||
that you can miss it. */
|
||
.pete-uno-call {
|
||
margin-top: 0.85rem;
|
||
padding: 0.9rem 2.6rem;
|
||
border-radius: 999px;
|
||
background: #e0b02c;
|
||
color: #2b2118;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 1.7rem;
|
||
font-weight: 700;
|
||
letter-spacing: 0.02em;
|
||
box-shadow: 0 5px 0 rgba(0,0,0,0.3), 0 0 0 0 rgba(224,176,44,0.55);
|
||
animation: pete-uno-call 0.9s ease-in-out infinite;
|
||
}
|
||
.pete-uno-call:hover { filter: brightness(1.07); }
|
||
.pete-uno-call:active { transform: translateY(2px); box-shadow: 0 3px 0 rgba(0,0,0,0.3); }
|
||
@keyframes pete-uno-call {
|
||
0%, 100% { transform: scale(1); box-shadow: 0 5px 0 rgba(0,0,0,0.3), 0 0 0 0 rgba(224,176,44,0.5); }
|
||
50% { transform: scale(1.05); box-shadow: 0 5px 0 rgba(0,0,0,0.3), 0 0 0 0.9rem rgba(224,176,44,0); }
|
||
}
|
||
|
||
/* How long you've got, draining left to right. The duration is set on the
|
||
element: the window lives in uno.js, and a bar that disagrees with the clock
|
||
it's drawing is worse than no bar. */
|
||
.pete-uno-timer {
|
||
margin-top: 0.8rem;
|
||
height: 0.35rem;
|
||
border-radius: 999px;
|
||
background: rgba(255,255,255,0.16);
|
||
overflow: hidden;
|
||
}
|
||
.pete-uno-timer > span {
|
||
display: block;
|
||
height: 100%;
|
||
width: 100%;
|
||
background: #e0b02c;
|
||
transform-origin: left center;
|
||
animation: pete-uno-drain linear forwards;
|
||
}
|
||
@keyframes pete-uno-drain {
|
||
from { transform: scaleX(1); }
|
||
to { transform: scaleX(0); }
|
||
}
|
||
|
||
/* A seat on one card that never said so. It gets a button, and the button is
|
||
the only thing on the felt that says it — there's no badge, because not
|
||
announcing is exactly what it did. */
|
||
.pete-uno-catch {
|
||
position: absolute;
|
||
top: -0.55rem;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
z-index: 6;
|
||
padding: 0.22rem 0.7rem;
|
||
border-radius: 999px;
|
||
background: #cc3d4a;
|
||
color: #fff;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
box-shadow: 0 3px 0 rgba(0,0,0,0.25);
|
||
animation: pete-uno-catch 1.1s ease-in-out infinite;
|
||
transition: filter 0.12s ease;
|
||
}
|
||
.pete-uno-catch:hover { filter: brightness(1.12); }
|
||
.pete-uno-catch:disabled { opacity: 0.45; animation: none; }
|
||
@keyframes pete-uno-catch {
|
||
0%, 100% { transform: translateX(-50%) scale(1); }
|
||
50% { transform: translateX(-50%) scale(1.08); }
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.pete-uno-call, .pete-uno-catch { animation: none; }
|
||
}
|
||
|
||
/* ---- No Mercy -------------------------------------------------------------
|
||
A rules dial, not a fourth table, and the felt says so: the same table, the
|
||
same cards, plus six faces the normal box doesn't print and one thing that
|
||
can be pointed at you — a stack of draw cards with a bill on it.
|
||
|
||
Sizing note, and it is the rule this table was taught the hard way: a card is
|
||
sized by --uno-h/--uno-w and nothing else. These faces set a *font*, never a
|
||
box, so a "DISCARD ALL" is the same card as a "7" at every width. */
|
||
|
||
/* The long faces. "DISCARD ALL" does not fit across an oval at card size, so it
|
||
wraps — and the oval is tilted, so it wraps on the tilt, which is what the
|
||
printed card does too. Specificity has to clear the wild rule above (a
|
||
reverse-draw-four is a wild), hence the .pete-uno-card in front. */
|
||
.pete-uno-card .pete-uno-oval[data-size="mid"] { font-size: 1.05rem; }
|
||
.pete-uno-card .pete-uno-oval[data-size="words"] {
|
||
font-size: 0.5rem;
|
||
line-height: 1.1;
|
||
letter-spacing: 0.02em;
|
||
text-align: center;
|
||
padding: 0 0.15rem;
|
||
}
|
||
|
||
/* The wild draws glow.
|
||
|
||
These are the cards that decide a game, and No Mercy prints a *coloured* +4
|
||
right next to the wild one, so at a glance in a hand of twenty they have to be
|
||
tellable apart. The aura is a sibling behind the card rather than a shadow on
|
||
it: a box-shadow here would fight the one that says which colour a played wild
|
||
was named as (data-named, above), and lose.
|
||
|
||
It sits under the card (z-index -1) and does not take clicks, so the card is
|
||
still the button. */
|
||
.pete-uno-card[data-glow="1"] { position: relative; z-index: 0; }
|
||
.pete-uno-card[data-glow="1"]::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: -0.35rem;
|
||
z-index: -1;
|
||
border-radius: 0.9rem;
|
||
pointer-events: none;
|
||
background: conic-gradient(from 0deg,
|
||
#d64545, #e0b02c, #46a86b, #3d7fd6, #d64545);
|
||
filter: blur(7px);
|
||
opacity: 0.75;
|
||
animation: pete-uno-glow 3.2s linear infinite;
|
||
}
|
||
/* The shimmer across the face itself: a slow sweep of light, the way a foil card
|
||
catches the room when you tilt it. */
|
||
.pete-uno-card[data-glow="1"] .pete-uno-face::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 2;
|
||
pointer-events: none;
|
||
background: linear-gradient(115deg,
|
||
transparent 35%, rgba(255,255,255,0.55) 50%, transparent 65%);
|
||
background-size: 260% 100%;
|
||
animation: pete-uno-shine 2.6s ease-in-out infinite;
|
||
}
|
||
@keyframes pete-uno-glow {
|
||
to { transform: rotate(1turn); }
|
||
}
|
||
@keyframes pete-uno-shine {
|
||
0% { background-position: 140% 0; }
|
||
55% { background-position: -40% 0; }
|
||
100% { background-position: -40% 0; }
|
||
}
|
||
|
||
/* The bill. While a stack stands it is the only thing on the table that matters,
|
||
so it is loud, and it is red. */
|
||
.pete-uno-pending {
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.72rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
padding: 0.18rem 0.7rem;
|
||
border-radius: 999px;
|
||
color: #fff;
|
||
background: #cc3d4a;
|
||
box-shadow: 0 3px 0 rgba(0,0,0,0.2);
|
||
animation: pete-uno-bill 1.1s ease-in-out infinite;
|
||
}
|
||
@keyframes pete-uno-bill {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.06); }
|
||
}
|
||
|
||
/* A seat the mercy rule buried. It stays at the table — the felt would jump if
|
||
it didn't, and you want to see who's gone — but it is plainly out. */
|
||
.pete-uno-seat[data-out="1"] {
|
||
opacity: 0.4;
|
||
filter: grayscale(1);
|
||
}
|
||
.pete-uno-seat[data-out="1"] .pete-uno-count { color: #ffb3ba; }
|
||
|
||
/* The send-off. When the mercy rule buries a seat, uno.js rolls one of four
|
||
of these and drops it over that seat — or over your hand, if it's you going
|
||
down. Every piece is built fresh and clears itself, so it can't outlive the
|
||
hand. The wrap is a zero-size anchor at the seat's middle; the pieces hang
|
||
off it. */
|
||
.pete-uno-bury {
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 45%;
|
||
width: 0;
|
||
height: 0;
|
||
z-index: 40;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Rockslide: a handful of boulders come down and pile where the seat was. */
|
||
.pete-uno-rock {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
font-size: 1.5rem;
|
||
line-height: 1;
|
||
filter: drop-shadow(0 2px 1px rgba(0,0,0,0.4));
|
||
animation: pete-bury-rock 0.6s cubic-bezier(0.5, 0, 0.7, 1) both;
|
||
animation-delay: var(--d, 0ms);
|
||
}
|
||
@keyframes pete-bury-rock {
|
||
0% { transform: translate(calc(-50% + var(--dx)), -3.4rem) rotate(0deg); opacity: 0; }
|
||
20% { opacity: 1; }
|
||
75% { transform: translate(calc(-50% + var(--dx)), var(--dy)) rotate(var(--rot)); }
|
||
85% { transform: translate(calc(-50% + var(--dx)), calc(var(--dy) - 0.22rem)) rotate(var(--rot)); }
|
||
100% { transform: translate(calc(-50% + var(--dx)), var(--dy)) rotate(var(--rot)); opacity: 1; }
|
||
}
|
||
|
||
/* Tombstone and coffin: one heavy thing drops in and sets, with a small bounce. */
|
||
.pete-uno-tomb,
|
||
.pete-uno-coffin {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
line-height: 1;
|
||
filter: drop-shadow(0 3px 2px rgba(0,0,0,0.45));
|
||
animation: pete-bury-drop 0.5s cubic-bezier(0.34, 1.3, 0.64, 1) both;
|
||
}
|
||
.pete-uno-tomb { font-size: 2.4rem; }
|
||
.pete-uno-coffin { font-size: 2.5rem; }
|
||
@keyframes pete-bury-drop {
|
||
0% { transform: translate(-50%, -3.2rem) scale(0.7); opacity: 0; }
|
||
55% { opacity: 1; }
|
||
70% { transform: translate(-50%, -50%) scale(1.06); }
|
||
82% { transform: translate(-50%, -44%) scale(1); }
|
||
100% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
|
||
}
|
||
|
||
/* The Mega Man death: a flash, then eight pieces fire out and wink off. */
|
||
.pete-uno-zap-flash {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 1.5rem;
|
||
height: 1.5rem;
|
||
margin: -0.75rem 0 0 -0.75rem;
|
||
border-radius: 999px;
|
||
background: radial-gradient(circle, #fff 0%, rgba(180,220,255,0) 70%);
|
||
animation: pete-bury-flash 0.35s ease-out both;
|
||
}
|
||
@keyframes pete-bury-flash {
|
||
0% { transform: scale(0.3); opacity: 0.95; }
|
||
100% { transform: scale(1.9); opacity: 0; }
|
||
}
|
||
.pete-uno-zap-bit {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
width: 0.5rem;
|
||
height: 0.5rem;
|
||
margin: -0.25rem 0 0 -0.25rem;
|
||
border-radius: 999px;
|
||
background: #cfeaff;
|
||
box-shadow: 0 0 6px 1px rgba(150,210,255,0.9);
|
||
animation: pete-bury-zap 0.5s ease-out both;
|
||
}
|
||
@keyframes pete-bury-zap {
|
||
0% { transform: rotate(var(--ang)) translateX(0.2rem); opacity: 1; }
|
||
100% { transform: rotate(var(--ang)) translateX(3.4rem); opacity: 0; }
|
||
}
|
||
|
||
/* The rules switch: two dials, and this is the one that isn't the table size. */
|
||
.pete-seg {
|
||
display: inline-flex;
|
||
padding: 0.2rem;
|
||
border-radius: 999px;
|
||
background: color-mix(in srgb, var(--ink) 6%, transparent);
|
||
}
|
||
.pete-seg-btn {
|
||
padding: 0.3rem 0.9rem;
|
||
border-radius: 999px;
|
||
font-family: "Fredoka", ui-sans-serif, system-ui, sans-serif;
|
||
font-size: 0.8rem;
|
||
font-weight: 700;
|
||
color: color-mix(in srgb, var(--ink) 55%, transparent);
|
||
transition: background 0.15s ease, color 0.15s ease;
|
||
}
|
||
.pete-seg-btn:hover { color: var(--ink); }
|
||
.pete-seg-btn[data-on="1"] {
|
||
background: var(--accent);
|
||
color: #fff;
|
||
box-shadow: 0 2px 0 rgba(0,0,0,0.15);
|
||
}
|
||
|
||
/* A phone. The cards get smaller so a hand of ten still fits without the felt
|
||
scrolling sideways, which is the thing to check at 390px with a game on it.
|
||
No Mercy deals bigger hands than that — it is a deck built to bury you — so
|
||
this is the table where a twenty-card hand has to wrap and not overflow. */
|
||
@media (max-width: 639px) {
|
||
.pete-uno-hand { --uno-h: 5.2rem; --uno-w: 3.5rem; gap: 0.3rem; }
|
||
.pete-uno-deck { --uno-h: 5.2rem; --uno-w: 3.5rem; }
|
||
.pete-uno-card .pete-uno-oval[data-size="words"] { font-size: 0.42rem; }
|
||
.pete-uno-card .pete-uno-oval[data-size="mid"] { font-size: 0.9rem; }
|
||
/* The vars, not just the box: the card in the discard is a .pete-uno-card and
|
||
takes its size from them, so sizing the box alone left a full-size card
|
||
hanging out of a small hole, on top of the colour in play. */
|
||
.pete-uno-discard { --uno-h: 5.2rem; --uno-w: 3.5rem; height: 5.2rem; width: 3.5rem; }
|
||
.pete-uno-hand .pete-uno-card { padding: 0.22rem; }
|
||
.pete-uno-oval { font-size: 1.15rem; }
|
||
.pete-uno-seat { padding: 0.35rem 0.45rem; }
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.pete-card,
|
||
.pete-card::after,
|
||
.pete-stack .pete-disc,
|
||
.pete-dealer-think { animation: none; }
|
||
.pete-card-inner { transition: none; }
|
||
.pete-hand[data-won="1"] .pete-card { animation: none; }
|
||
.pete-part-draw,
|
||
.pete-shake,
|
||
.pete-tile-hit,
|
||
.pete-meter[data-hit="1"] { animation: none; }
|
||
.pete-nope,
|
||
.pete-home-flash,
|
||
.pete-finish { animation: none; }
|
||
.pete-card[data-held="1"] { transition: none; }
|
||
/* The clock still drains — it is information, not decoration — but it stops
|
||
pulsing at you, and a wrong answer stops shaking. */
|
||
.pete-clock[data-hot="1"],
|
||
.pete-answer[data-state="wrong"] { animation: none; }
|
||
.pete-rung { transition: none; }
|
||
/* The wild draws keep their glow — it is what tells the card apart from the
|
||
coloured +4 beside it, so it is information — but it stops moving. */
|
||
.pete-uno-card[data-glow="1"]::before { animation: none; }
|
||
.pete-uno-card[data-glow="1"] .pete-uno-face::after { display: none; }
|
||
.pete-uno-pending { animation: none; }
|
||
/* The grave still gets marked — that's information — it just doesn't fall in.
|
||
uno.js drops a single static headstone here rather than the full rockslide. */
|
||
.pete-uno-tomb { animation: none; transform: translate(-50%, -50%); }
|
||
}
|
||
}
|
||
|
||
/* ----------------------------------------------------------------------------
|
||
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 row of them: a slow chase across
|
||
the top of the room. One radial tile repeated, one transform — each bulb is a
|
||
hot white core in a yellow body, and the glow is a pair of drop-shadows so it
|
||
actually throws light rather than being a painted dot. */
|
||
html[data-room="casino-night"] .cs-room::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0.4rem -50% auto -50%;
|
||
height: 0.7rem;
|
||
background:
|
||
radial-gradient(circle at center,
|
||
#fffbe6 0 0.14rem,
|
||
#ffcc2f 0.14rem 0.26rem,
|
||
rgba(255,204,47,0.45) 0.26rem 0.42rem,
|
||
transparent 0.44rem)
|
||
0.3rem 50% / 1.9rem 100% repeat-x;
|
||
filter:
|
||
drop-shadow(0 0 3px rgba(255,214,90,0.95))
|
||
drop-shadow(0 0 10px rgba(255,204,47,0.6));
|
||
animation: cs-bulbs 1.8s linear infinite;
|
||
}
|
||
@keyframes cs-bulbs {
|
||
from { transform: translateX(0); }
|
||
to { transform: translateX(1.9rem); }
|
||
}
|
||
|
||
/* The house shadow, restruck for a dark room: the news app's soft brown lift is
|
||
invisible down here, so the same shape is cut in black instead. */
|
||
html[data-room] .shadow-pete { box-shadow: 0 4px 0 rgba(0,0,0,0.30), 0 10px 26px rgba(0,0,0,0.30); }
|
||
html[data-room] .shadow-pete-lg { box-shadow: 0 6px 0 rgba(0,0,0,0.34), 0 20px 40px rgba(0,0,0,0.38); }
|
||
|
||
html[data-room] .cs-comb svg { filter: drop-shadow(0 3px 0 rgba(0,0,0,0.35)); }
|
||
|
||
/* The felt takes its colours from the room, so switching rooms reupholsters the
|
||
table too. */
|
||
html[data-room] .pete-felt {
|
||
background:
|
||
radial-gradient(120% 90% at 50% -10%, rgba(255,255,255,0.10), transparent 60%),
|
||
linear-gradient(160deg, var(--felt-a) 0%, var(--felt-b) 55%, var(--felt-c) 100%);
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
html[data-room="casino-night"] .cs-room::before { animation: none; }
|
||
}
|
||
|
||
/* Three stacks of chips on the lobby's welcome card, in the same denomination
|
||
colours you actually bet with. Height comes from --stack, so the row reads as
|
||
a real pile someone left on the table rather than an icon of one. */
|
||
.cs-stack {
|
||
display: block;
|
||
width: 2.75rem;
|
||
height: calc(0.45rem * var(--stack, 1) + 0.55rem);
|
||
border-radius: 999px;
|
||
background:
|
||
radial-gradient(circle at 50% 30%, rgba(255,255,255,0.30), transparent 60%),
|
||
var(--chip, #e07a5f);
|
||
border: 2px solid rgba(0,0,0,0.25);
|
||
box-shadow:
|
||
0 3px 0 rgba(0,0,0,0.30),
|
||
inset 0 -0.4rem 0 rgba(0,0,0,0.16),
|
||
inset 0 0.35rem 0 rgba(255,255,255,0.14);
|
||
}
|
||
.cs-stack[data-chip="5"] { --chip: #5aa9e6; }
|
||
.cs-stack[data-chip="25"] { --chip: #4caf7d; }
|
||
.cs-stack[data-chip="100"] { --chip: #2b2118; }
|
||
.cs-stack[data-chip="500"] { --chip: #b079d6; }
|
||
|
||
/* ── Hold'em ────────────────────────────────────────────────────────────────
|
||
The one table with other people at it. Everything else in the casino has a
|
||
single bet spot, because there was only ever one player; here every seat has
|
||
its own, chips travel between them and the pot rather than between you and the
|
||
house, and the pot in the middle is the thing they all move toward.
|
||
|
||
Seats are a wrapping row rather than an ellipse. An oval of six seats is what a
|
||
poker table looks like, and it is also what stops fitting the moment a phone is
|
||
held up — this keeps the geometry honest at 390px and still reads as a table
|
||
because the board and the pot sit in the middle of it, which is where the eye
|
||
goes anyway. */
|
||
|
||
.pete-poker { --seat-w: 7.5rem; }
|
||
|
||
.pete-poker-seats {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.pete-seat {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
width: var(--seat-w);
|
||
transition: opacity 0.3s ease, filter 0.3s ease;
|
||
}
|
||
/* A folded seat is still there — you can see they're in the game and out of the
|
||
hand, which is information you're entitled to. It just stops competing. */
|
||
.pete-seat[data-state="folded"] { opacity: 0.38; filter: grayscale(0.7); }
|
||
.pete-seat[data-state="out"] { opacity: 0.2; }
|
||
|
||
/* Whose turn it is. The ring is the only thing on the felt that moves on its own,
|
||
because it is the only thing you are waiting for. */
|
||
.pete-seat[data-turn="1"] .pete-seat-plate {
|
||
box-shadow: 0 0 0 2px var(--accent), 0 0 1.6rem -0.2rem var(--accent);
|
||
animation: pete-seat-wait 1.6s ease-in-out infinite;
|
||
}
|
||
@keyframes pete-seat-wait {
|
||
0%, 100% { transform: translateY(0); }
|
||
50% { transform: translateY(-2px); }
|
||
}
|
||
|
||
.pete-seat-cards {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 0.15rem;
|
||
min-height: 4.4rem;
|
||
--card-h: 4.4rem;
|
||
--card-w: 3.15rem;
|
||
}
|
||
/* A seat that folded throws its cards in. */
|
||
.pete-seat-cards[data-mucked="1"] { opacity: 0; transform: translateY(-0.6rem) scale(0.9); transition: all 0.35s ease; }
|
||
|
||
.pete-seat-plate {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
width: 100%;
|
||
padding: 0.3rem 0.4rem;
|
||
border-radius: 0.85rem;
|
||
background: rgba(0,0,0,0.32);
|
||
border: 1px solid rgba(255,255,255,0.12);
|
||
transition: box-shadow 0.25s ease;
|
||
}
|
||
.pete-seat-name {
|
||
font-family: var(--font-display, inherit);
|
||
font-weight: 700;
|
||
font-size: 0.8rem;
|
||
line-height: 1.1;
|
||
color: rgba(255,255,255,0.92);
|
||
max-width: 100%;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.pete-seat-stack {
|
||
font-weight: 700;
|
||
font-size: 0.78rem;
|
||
font-variant-numeric: tabular-nums;
|
||
color: rgba(255,255,255,0.58);
|
||
}
|
||
.pete-seat[data-state="allin"] .pete-seat-stack::after {
|
||
content: " all in";
|
||
color: var(--accent);
|
||
}
|
||
|
||
/* The dealer button, and the blinds. Small, and worth having: position is most of
|
||
what makes one hand different from the next, and a player who cannot see where
|
||
the button is cannot see the game. */
|
||
.pete-seat-pos {
|
||
position: absolute;
|
||
top: -0.5rem;
|
||
right: -0.5rem;
|
||
display: grid;
|
||
place-items: center;
|
||
min-width: 1.45rem;
|
||
height: 1.45rem;
|
||
padding: 0 0.3rem;
|
||
border-radius: 999px;
|
||
font-size: 0.58rem;
|
||
font-weight: 800;
|
||
letter-spacing: 0.02em;
|
||
background: rgba(255,255,255,0.9);
|
||
color: #2b2118;
|
||
box-shadow: 0 2px 0 rgba(0,0,0,0.3);
|
||
}
|
||
.pete-seat-pos[data-pos="BTN"] { background: #f5d76e; }
|
||
|
||
/* Every seat's bet, sitting between them and the pot.
|
||
A .pete-spot is 7rem across, because blackjack has exactly one of them. Six of
|
||
those is most of a felt, so a seat's is less than half the size — and so are the
|
||
chips in it, which are otherwise bigger than the ring they land in. */
|
||
.pete-seat-spot {
|
||
height: 3.6rem;
|
||
width: 3.6rem;
|
||
margin-bottom: 0.5rem; /* the bet total hangs below the ring; leave it somewhere to hang */
|
||
}
|
||
.pete-seat-spot .pete-disc {
|
||
height: 1.6rem;
|
||
width: 1.6rem;
|
||
margin: -0.8rem 0 0 -0.8rem;
|
||
}
|
||
.pete-seat-spot .pete-spot-total {
|
||
font-size: 0.66rem;
|
||
padding: 0.05rem 0.45rem;
|
||
}
|
||
|
||
/* The middle of the table: the board, and the pot under it. */
|
||
.pete-poker-middle {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.35rem;
|
||
padding: 0.5rem 0;
|
||
}
|
||
/* The pot's chips stack upwards, so they need room under the board or they climb
|
||
into the river card. */
|
||
.pete-poker-pot { margin-top: 0.9rem; }
|
||
/* The board keeps its height when it is empty, so the felt does not jump a hundred
|
||
pixels the moment the flop lands. */
|
||
.pete-poker-board {
|
||
display: flex;
|
||
gap: 0.35rem;
|
||
min-height: 6rem;
|
||
--card-h: 6rem;
|
||
--card-w: 4.3rem;
|
||
}
|
||
.pete-poker-pot {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 0.1rem;
|
||
}
|
||
/* The pot's chips need a box to themselves. .pete-stack is position:absolute with
|
||
inset:0, so a pile that shares a box with the number under it lands on top of
|
||
the number — which is exactly what it did: the pot showed a chip and no total. */
|
||
.pete-poker-pot-pile {
|
||
position: relative;
|
||
width: 7rem;
|
||
height: 3rem;
|
||
}
|
||
.pete-poker-pot-label {
|
||
font-size: 0.62rem;
|
||
font-weight: 800;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
color: rgba(255,255,255,0.42);
|
||
}
|
||
.pete-poker-pot-total {
|
||
font-family: var(--font-display, inherit);
|
||
font-size: 1.5rem;
|
||
font-weight: 800;
|
||
font-variant-numeric: tabular-nums;
|
||
color: #fff;
|
||
text-shadow: 0 2px 0 rgba(0,0,0,0.28);
|
||
}
|
||
.pete-poker-side {
|
||
font-size: 0.68rem;
|
||
font-weight: 700;
|
||
color: rgba(255,255,255,0.5);
|
||
}
|
||
|
||
/* Your seat. Bigger cards, because they are the two you are actually reading. */
|
||
.pete-poker-you .pete-seat { width: auto; }
|
||
.pete-poker-you .pete-seat-cards {
|
||
--card-h: 6.8rem;
|
||
--card-w: 4.85rem;
|
||
gap: 0.3rem;
|
||
min-height: 6.8rem;
|
||
}
|
||
.pete-poker-you .pete-seat-plate { padding: 0.4rem 1.1rem; }
|
||
.pete-poker-you .pete-seat-name { font-size: 0.95rem; }
|
||
.pete-poker-you .pete-seat-stack { font-size: 0.95rem; }
|
||
|
||
/* What the hand did to you, said once, in the middle of the felt. */
|
||
.pete-poker-verdict {
|
||
border-radius: 999px;
|
||
background: rgba(255,255,255,0.95);
|
||
padding: 0.5rem 1.25rem;
|
||
font-family: var(--font-display, inherit);
|
||
font-size: 1.05rem;
|
||
font-weight: 700;
|
||
color: #2b2118;
|
||
box-shadow: 0 4px 0 rgba(0,0,0,0.18);
|
||
}
|
||
.pete-poker-verdict[data-tone="win"] { background: #4caf7d; color: #fff; }
|
||
.pete-poker-verdict[data-tone="lose"] { background: rgba(255,255,255,0.9); color: #7a5c50; }
|
||
|
||
/* The rail. Chat runs along the felt — messages only, no typing indicators, and
|
||
it never leaves for Matrix. Your own lines lean the other way and take the
|
||
accent, so a glance tells you who is talking. */
|
||
.pete-chat-line {
|
||
display: flex;
|
||
gap: 0.4rem;
|
||
align-items: baseline;
|
||
padding: 0.15rem 0.1rem;
|
||
line-height: 1.35;
|
||
}
|
||
.pete-chat-who {
|
||
flex-shrink: 0;
|
||
font-weight: 700;
|
||
color: var(--accent);
|
||
}
|
||
.pete-chat-body {
|
||
color: color-mix(in srgb, var(--ink) 85%, transparent);
|
||
word-break: break-word;
|
||
}
|
||
.pete-chat-mine .pete-chat-who { color: color-mix(in srgb, var(--ink) 55%, transparent); }
|
||
|
||
/* The action bar. Raise is a slider, because a raise is a *size* and a text box
|
||
makes you type a number you have not thought about. */
|
||
.pete-raise {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.6rem;
|
||
flex: 1 1 14rem;
|
||
min-width: 0;
|
||
}
|
||
.pete-raise input[type="range"] {
|
||
flex: 1;
|
||
min-width: 0;
|
||
accent-color: var(--accent);
|
||
}
|
||
.pete-raise-to {
|
||
font-family: var(--font-display, inherit);
|
||
font-size: 1.15rem;
|
||
font-weight: 800;
|
||
font-variant-numeric: tabular-nums;
|
||
min-width: 3.5rem;
|
||
text-align: right;
|
||
}
|
||
|
||
.pete-poker-log {
|
||
max-height: 7rem;
|
||
overflow-y: auto;
|
||
font-size: 0.75rem;
|
||
line-height: 1.5;
|
||
color: rgba(255,255,255,0.55);
|
||
}
|
||
.pete-poker-log b { color: rgba(255,255,255,0.85); font-weight: 700; }
|
||
|
||
@media (max-width: 640px) {
|
||
.pete-poker { --seat-w: 5.6rem; }
|
||
.pete-seat-cards { --card-h: 3.5rem; --card-w: 2.5rem; min-height: 3.5rem; }
|
||
.pete-poker-board { --card-h: 5rem; --card-w: 3.55rem; min-height: 5rem; gap: 0.25rem; }
|
||
.pete-poker-you .pete-seat-cards { --card-h: 6rem; --card-w: 4.3rem; min-height: 6rem; }
|
||
.pete-poker-pot-total { font-size: 1.25rem; }
|
||
}
|
||
|
||
@layer components {
|
||
/* Dungeon map (who page). The graph arrives already cut to the fog-of-war
|
||
frontier; who_map.go lays it out and who.html draws it as inline SVG. Node
|
||
colours ride the phase through --ink/--card like every card does, with a
|
||
per-kind tint carried in --map-fill so the SVG discs and the legend dots
|
||
read from one source. The adventure purple marks the room you're in. */
|
||
.map-svg { display: block; overflow: visible; }
|
||
|
||
.map-edge {
|
||
stroke: color-mix(in srgb, var(--ink) 26%, transparent);
|
||
stroke-width: 2.5;
|
||
stroke-linecap: round;
|
||
}
|
||
.map-edge-locked {
|
||
stroke: #c98a2b; /* a barred door reads amber, not ink */
|
||
stroke-dasharray: 3 5;
|
||
}
|
||
|
||
.map-disc {
|
||
fill: var(--map-fill, color-mix(in srgb, var(--ink) 9%, var(--card)));
|
||
stroke: color-mix(in srgb, var(--map-stroke, var(--ink)) 55%, transparent);
|
||
stroke-width: 2;
|
||
}
|
||
.map-glyph {
|
||
fill: color-mix(in srgb, var(--map-stroke, var(--ink)) 85%, var(--ink));
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
pointer-events: none;
|
||
}
|
||
.map-node-current .map-disc { stroke: #6d4bd8; stroke-width: 2.5; }
|
||
.map-ring { fill: none; stroke: #6d4bd8; stroke-width: 2; opacity: 0.5; }
|
||
|
||
/* Per-kind tint. Set on the node group (and the legend dot); both the disc
|
||
fill and the glyph colour derive from it. */
|
||
.map-node-entry { --map-fill: color-mix(in srgb, #3fa66a 22%, var(--card)); --map-stroke: #2f8a54; }
|
||
.map-node-boss { --map-fill: color-mix(in srgb, #c0392b 22%, var(--card)); --map-stroke: #a52f22; }
|
||
.map-node-trap { --map-fill: color-mix(in srgb, #d98324 22%, var(--card)); --map-stroke: #b56a17; }
|
||
.map-node-elite { --map-fill: color-mix(in srgb, #6d4bd8 20%, var(--card)); --map-stroke: #5836b8; }
|
||
.map-node-secret { --map-fill: color-mix(in srgb, #b08d2e 22%, var(--card)); --map-stroke: #8f7018; }
|
||
.map-node-harvest { --map-fill: color-mix(in srgb, #3f8f6a 18%, var(--card)); --map-stroke: #2f7355; }
|
||
.map-node-rest { --map-fill: color-mix(in srgb, #3f83a6 18%, var(--card)); --map-stroke: #2f6a88; }
|
||
.map-node-plain { --map-fill: color-mix(in srgb, var(--ink) 9%, var(--card)); --map-stroke: var(--ink); }
|
||
.map-node-unknown { --map-fill: color-mix(in srgb, var(--ink) 5%, var(--card)); --map-stroke: color-mix(in srgb, var(--ink) 40%, transparent); }
|
||
.map-node-unknown .map-disc { stroke-dasharray: 3 4; }
|
||
|
||
/* Legend swatches. Same --map-fill source as the nodes. */
|
||
.map-dot {
|
||
width: 11px; height: 11px; border-radius: 9999px;
|
||
background: var(--map-fill, color-mix(in srgb, var(--ink) 9%, var(--card)));
|
||
border: 1.5px solid color-mix(in srgb, var(--map-stroke, var(--ink)) 55%, transparent);
|
||
}
|
||
.map-node-unknown.map-dot { border-style: dashed; }
|
||
.map-door-legend {
|
||
width: 16px; height: 0;
|
||
border-top: 2.5px dashed #c98a2b;
|
||
}
|
||
}
|
||
|
||
@layer components {
|
||
/* Item compare chips (who page, owner's backpack). gogobee decides the verdict
|
||
and per-stat deltas; Pete only colours them. Every colour mixes a fixed hue
|
||
into --ink for the text and --card for the fill, so it lands on the readable
|
||
side of the card in all four phases — the same by-construction contrast trick
|
||
the dungeon map uses, not a Tailwind dark: variant (which follows the OS, not
|
||
Pete's phase). The verdict chip is the anchor and always renders; a phone has
|
||
no hover, so nothing hides behind one. */
|
||
.cmp-chip {
|
||
display: inline-flex; align-items: center; gap: 0.25rem;
|
||
font-size: 11px; font-weight: 600; line-height: 1;
|
||
border-radius: 9999px; padding: 0.18rem 0.5rem;
|
||
border: 1px solid transparent;
|
||
}
|
||
.cmp-up {
|
||
color: color-mix(in srgb, #3fa66a 65%, var(--ink));
|
||
background: color-mix(in srgb, #3fa66a 16%, var(--card));
|
||
border-color: color-mix(in srgb, #3fa66a 38%, transparent);
|
||
}
|
||
.cmp-down {
|
||
color: color-mix(in srgb, #c0392b 60%, var(--ink));
|
||
background: color-mix(in srgb, #c0392b 15%, var(--card));
|
||
border-color: color-mix(in srgb, #c0392b 36%, transparent);
|
||
}
|
||
.cmp-side {
|
||
color: color-mix(in srgb, var(--ink) 80%, transparent);
|
||
background: color-mix(in srgb, var(--ink) 8%, var(--card));
|
||
border-color: color-mix(in srgb, var(--ink) 22%, transparent);
|
||
}
|
||
.cmp-new {
|
||
color: color-mix(in srgb, #6d4bd8 62%, var(--ink));
|
||
background: color-mix(in srgb, #6d4bd8 15%, var(--card));
|
||
border-color: color-mix(in srgb, #6d4bd8 36%, transparent);
|
||
}
|
||
.cmp-inert {
|
||
color: var(--warn); /* --warn is already a phase variable — safe on night */
|
||
background: color-mix(in srgb, var(--warn) 16%, var(--card));
|
||
border-color: color-mix(in srgb, var(--warn) 36%, transparent);
|
||
}
|
||
.cmp-same {
|
||
color: color-mix(in srgb, var(--ink) 55%, transparent);
|
||
background: color-mix(in srgb, var(--ink) 6%, var(--card));
|
||
border-color: color-mix(in srgb, var(--ink) 16%, transparent);
|
||
}
|
||
|
||
/* Per-stat deltas — tint-only, lighter than the verdict chip so it stays the
|
||
anchor. Green reads as a gain, red as a loss; the engine set the flag. */
|
||
.cmp-delta {
|
||
font-size: 11px; font-weight: 500; line-height: 1;
|
||
border-radius: 9999px; padding: 0.15rem 0.45rem;
|
||
}
|
||
.cmp-delta-up { color: color-mix(in srgb, #3fa66a 65%, var(--ink)); background: color-mix(in srgb, #3fa66a 13%, var(--card)); }
|
||
.cmp-delta-down { color: color-mix(in srgb, #c0392b 60%, var(--ink)); background: color-mix(in srgb, #c0392b 12%, var(--card)); }
|
||
}
|
||
|
||
@layer components {
|
||
/* The Siege war room. One rule carries the whole page: the health bar has a
|
||
width transition, so a poll that lands a lower pool *slides* the bar down
|
||
instead of snapping it. That is the difference between watching the town
|
||
chip a boss and reading a report about it, and it costs one line.
|
||
prefers-reduced-motion turns the slide off — the number is still correct,
|
||
it just arrives instantly.
|
||
|
||
The ember palette is deliberate and not the adventure purple: a siege is
|
||
the one thing on the site that should look like an emergency. It mixes a
|
||
fixed hue into --card/--ink like the map and compare chips do, so it lands
|
||
readable in all four phases without a dark: variant. */
|
||
.siege-track {
|
||
position: relative;
|
||
height: 1.75rem;
|
||
border-radius: 9999px;
|
||
overflow: hidden;
|
||
background: color-mix(in srgb, var(--ink) 12%, var(--card));
|
||
box-shadow: inset 0 2px 4px rgba(0,0,0,0.12);
|
||
}
|
||
.siege-fill {
|
||
height: 100%;
|
||
border-radius: 9999px;
|
||
background: linear-gradient(90deg, #e0562f 0%, #c0392b 60%, #8f1f16 100%);
|
||
transition: width 1.4s cubic-bezier(0.22, 0.61, 0.36, 1);
|
||
}
|
||
.siege-fill-spent { background: linear-gradient(90deg, #6b7280 0%, #4b5563 100%); }
|
||
|
||
/* A living siege breathes. Slow and low-contrast on purpose — it should read
|
||
as "this is happening now", not as a thing demanding to be clicked. */
|
||
.siege-live .siege-fill { animation: siege-pulse 3.2s ease-in-out infinite; }
|
||
@keyframes siege-pulse {
|
||
0%, 100% { filter: brightness(1); }
|
||
50% { filter: brightness(1.12); }
|
||
}
|
||
|
||
.siege-chip {
|
||
display: inline-flex; align-items: center; gap: 0.3rem;
|
||
font-size: 11px; font-weight: 600; line-height: 1;
|
||
border-radius: 9999px; padding: 0.22rem 0.6rem;
|
||
border: 1px solid transparent;
|
||
}
|
||
.siege-chip-fought {
|
||
color: color-mix(in srgb, #3fa66a 65%, var(--ink));
|
||
background: color-mix(in srgb, #3fa66a 15%, var(--card));
|
||
border-color: color-mix(in srgb, #3fa66a 36%, transparent);
|
||
}
|
||
.siege-chip-waiting {
|
||
color: var(--warn);
|
||
background: color-mix(in srgb, var(--warn) 14%, var(--card));
|
||
border-color: color-mix(in srgb, var(--warn) 34%, transparent);
|
||
}
|
||
.siege-chip-held {
|
||
color: color-mix(in srgb, #3fa66a 65%, var(--ink));
|
||
background: color-mix(in srgb, #3fa66a 15%, var(--card));
|
||
border-color: color-mix(in srgb, #3fa66a 36%, transparent);
|
||
}
|
||
.siege-chip-fell {
|
||
color: color-mix(in srgb, #c0392b 60%, var(--ink));
|
||
background: color-mix(in srgb, #c0392b 15%, var(--card));
|
||
border-color: color-mix(in srgb, #c0392b 36%, transparent);
|
||
}
|
||
|
||
/* The past-siege bar: same track, smaller, and frozen at the pool the Siege
|
||
ended on. A won Siege ends at zero and draws as an empty track, which is
|
||
exactly the right picture. */
|
||
.siege-track-sm { height: 0.5rem; }
|
||
.siege-track-sm .siege-fill { transition: none; }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.siege-fill { transition: none; }
|
||
.siege-live .siege-fill { animation: none; }
|
||
}
|
||
|
||
/* The expedition liveblog: a column of what happened, room by room, under the
|
||
map that says where. Deliberately plainer than the rest of the page — this
|
||
is a log, and forty decorated cards would be unreadable at the length a real
|
||
run reaches. The rail down the left is what makes it read as one journey
|
||
rather than as a list of unrelated lines. */
|
||
.runlog {
|
||
list-style: none; margin: 0; padding: 0 0 0 1.1rem;
|
||
border-left: 2px solid color-mix(in srgb, var(--ink) 12%, transparent);
|
||
display: flex; flex-direction: column; gap: 0.55rem;
|
||
max-height: 26rem; overflow-y: auto;
|
||
}
|
||
.runlog-line {
|
||
display: grid; grid-template-columns: 1.4rem 1fr auto;
|
||
align-items: baseline; gap: 0.5rem;
|
||
font-size: 13px; line-height: 1.4;
|
||
color: color-mix(in srgb, var(--ink) 78%, transparent);
|
||
}
|
||
.runlog-emoji { font-size: 14px; }
|
||
.runlog-text { min-width: 0; overflow-wrap: anywhere; }
|
||
.runlog-meta {
|
||
font-size: 11px; font-variant-numeric: tabular-nums; white-space: nowrap;
|
||
color: color-mix(in srgb, var(--ink) 42%, transparent);
|
||
}
|
||
/* Two tints and no more. A log where every second line is coloured is a log
|
||
with no emphasis at all; these mark the beats a reader is scanning for —
|
||
what hurt, and what was worth having. */
|
||
.runlog-hurt .runlog-text { color: color-mix(in srgb, #c0392b 62%, var(--ink)); font-weight: 600; }
|
||
.runlog-good .runlog-text { color: color-mix(in srgb, #3fa66a 60%, var(--ink)); }
|
||
/* On the report the log is the page, not a panel inside one: it scrolls with
|
||
the document instead of trapping the whole run in a 26rem window that a
|
||
reader has to find the edge of before they can move through it. */
|
||
.runlog-full { max-height: none; overflow-y: visible; }
|
||
|
||
/* ── The realm: map, board, hall of firsts ──────────────────────────────
|
||
These are hand-written component classes, not generated utilities, and that
|
||
matters: a Tailwind class built from a template value gets purged out of the
|
||
stylesheet and fails SILENTLY. .standings-tier-{{.DeepestTier}} is exactly
|
||
that shape, so all six variants are spelled out below rather than composed.
|
||
If a seventh tier ever ships, it needs a line here.
|
||
|
||
The realm's accent is the adventure purple, unlike the Siege's ember — the
|
||
Siege is an emergency, the realm is the standing shape of the world. */
|
||
|
||
/* Tabs across the four standing pages. */
|
||
.realm-tab {
|
||
font-weight: 600;
|
||
color: color-mix(in srgb, var(--ink) 55%, transparent);
|
||
border-bottom: 2px solid transparent;
|
||
padding-bottom: 0.15rem;
|
||
transition: color 0.15s ease, border-color 0.15s ease;
|
||
}
|
||
.realm-tab:hover { color: var(--ink); }
|
||
.realm-tab-on {
|
||
color: #6d4bd8;
|
||
border-bottom-color: #6d4bd8;
|
||
}
|
||
/* The one place in this block a raw purple is set as a foreground rather than
|
||
mixed into --ink, so the one place that needs the night override the
|
||
existing .text-theme-adventure rule already carries. Everything else here
|
||
goes through color-mix and lands readable in all four phases by itself. */
|
||
html[data-phase="night"] .realm-tab-on {
|
||
color: #baa9eb;
|
||
border-bottom-color: #baa9eb;
|
||
}
|
||
|
||
/* A zone card. The default is a place people go: readable, unremarkable. */
|
||
.realm-zone {
|
||
border-radius: 1.25rem;
|
||
padding: 1rem 1.15rem;
|
||
background: var(--card);
|
||
border: 2px solid color-mix(in srgb, var(--ink) 10%, transparent);
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||
transition: border-color 0.15s ease, transform 0.15s ease;
|
||
}
|
||
.realm-zone:hover { transform: translateY(-1px); }
|
||
|
||
/* A zone NOBODY has ever cleared. This is the one piece of styling on the page
|
||
that has to carry a fact on its own, without being read: a visitor scanning
|
||
the map should see where the realm ends before they read a word of it.
|
||
Desaturated, dashed, recessed — the opposite of the busy state below. */
|
||
.realm-zone-unbeaten {
|
||
background: color-mix(in srgb, var(--ink) 5%, var(--card));
|
||
border-color: color-mix(in srgb, var(--ink) 18%, transparent);
|
||
border-style: dashed;
|
||
}
|
||
.realm-unbeaten-line {
|
||
color: color-mix(in srgb, var(--ink) 52%, transparent);
|
||
font-style: italic;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
/* Somebody is in there right now. It wins over the unbeaten styling by source
|
||
order, deliberately: a party currently inside a place nobody has ever beaten
|
||
is the most interesting card on the page and should read as live, not dead. */
|
||
.realm-zone-busy {
|
||
border-color: color-mix(in srgb, #6d4bd8 45%, transparent);
|
||
border-style: solid;
|
||
background: color-mix(in srgb, #6d4bd8 5%, var(--card));
|
||
}
|
||
|
||
/* The postgame band, drawn apart from the rest of the map: it is gated content
|
||
and the page should look like it changes character there. */
|
||
.realm-band-postgame {
|
||
background: color-mix(in srgb, var(--ink) 6%, var(--card));
|
||
border: 2px dashed color-mix(in srgb, var(--ink) 20%, transparent);
|
||
}
|
||
|
||
/* The board's deepest-tier chip. Six literal classes on purpose — see the note
|
||
at the top of this block. The ramp runs cool-to-hot so a column of them
|
||
reads as a gradient of how far people have got. */
|
||
.standings-tier {
|
||
display: inline-flex; align-items: center;
|
||
font-size: 11px; font-weight: 700; line-height: 1;
|
||
border-radius: 9999px; padding: 0.25rem 0.5rem;
|
||
border: 1px solid transparent;
|
||
}
|
||
.standings-tier-1 { color: color-mix(in srgb, #6b7280 70%, var(--ink)); background: color-mix(in srgb, #6b7280 12%, var(--card)); border-color: color-mix(in srgb, #6b7280 30%, transparent); }
|
||
.standings-tier-2 { color: color-mix(in srgb, #3fa66a 65%, var(--ink)); background: color-mix(in srgb, #3fa66a 12%, var(--card)); border-color: color-mix(in srgb, #3fa66a 30%, transparent); }
|
||
.standings-tier-3 { color: color-mix(in srgb, #2f7fd0 65%, var(--ink)); background: color-mix(in srgb, #2f7fd0 12%, var(--card)); border-color: color-mix(in srgb, #2f7fd0 30%, transparent); }
|
||
.standings-tier-4 { color: color-mix(in srgb, #8b5cf6 65%, var(--ink)); background: color-mix(in srgb, #8b5cf6 12%, var(--card)); border-color: color-mix(in srgb, #8b5cf6 32%, transparent); }
|
||
.standings-tier-5 { color: color-mix(in srgb, #e0562f 65%, var(--ink)); background: color-mix(in srgb, #e0562f 13%, var(--card)); border-color: color-mix(in srgb, #e0562f 34%, transparent); }
|
||
.standings-tier-6 { color: color-mix(in srgb, #c9a227 72%, var(--ink)); background: color-mix(in srgb, #c9a227 15%, var(--card)); border-color: color-mix(in srgb, #c9a227 40%, transparent); font-weight: 800; }
|
||
|
||
/* A realm-first count. Gold, because it is the one number on the board that
|
||
can never go up for anybody else once it has been claimed. */
|
||
.standings-firsts {
|
||
color: color-mix(in srgb, #c9a227 72%, var(--ink));
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* The hall of firsts: a ruled ledger with a spine, so a long list reads as a
|
||
record rather than as a feed. */
|
||
.firsts-ledger {
|
||
border-left: 2px solid color-mix(in srgb, var(--ink) 12%, transparent);
|
||
padding-left: 1.15rem;
|
||
}
|
||
.firsts-entry {
|
||
position: relative;
|
||
padding: 0.7rem 0;
|
||
border-bottom: 1px solid color-mix(in srgb, var(--ink) 7%, transparent);
|
||
}
|
||
.firsts-entry:last-child { border-bottom: 0; }
|
||
.firsts-entry::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: -1.45rem; top: 1.15rem;
|
||
width: 0.5rem; height: 0.5rem;
|
||
border-radius: 9999px;
|
||
background: var(--first-dot, color-mix(in srgb, var(--ink) 25%, var(--card)));
|
||
}
|
||
/* The per-kind dot colour goes through a custom property rather than through
|
||
a `.firsts-entry-zone::before` rule, and that is a purge fix, not a style
|
||
preference. tailwind.config.js has input.css itself in its content glob, so
|
||
a hand-written component class survives the purge only when the extractor
|
||
can lift its literal name out of this file — and a class name glued to
|
||
`::before` does not extract. It fails SILENTLY, which is how it got noticed
|
||
here only because the rule was grepped for afterwards. Any new
|
||
`.realm-*`/`.firsts-*` variant wants a plain-selector declaration for the
|
||
same reason. */
|
||
.firsts-entry-zone { --first-dot: #6d4bd8; }
|
||
.firsts-entry-treasure { --first-dot: #c9a227; }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.realm-zone:hover { transform: none; }
|
||
}
|
||
}
|
||
|
||
@layer components {
|
||
/* W7 small surfaces: the pet XP bar and the party roster, both on the
|
||
adventurer page. Same purge discipline as the block above — every class here
|
||
is declared on a plain selector so Tailwind's extractor can lift its literal
|
||
name out of this file. Nothing here is keyed to a pseudo-element. */
|
||
|
||
/* Pet levelling. It has been happening since the XP wiring was fixed and no
|
||
surface has ever shown it. Deliberately a thin rail rather than a health-bar
|
||
lookalike: a pet's progress is a nice thing to notice, not a stat to watch. */
|
||
.pet-xp-track {
|
||
height: 0.3rem;
|
||
border-radius: 9999px;
|
||
background: color-mix(in srgb, var(--ink) 10%, var(--card));
|
||
overflow: hidden;
|
||
}
|
||
.pet-xp-fill {
|
||
height: 100%;
|
||
border-radius: 9999px;
|
||
background: color-mix(in srgb, #3fa66a 70%, var(--ink));
|
||
}
|
||
/* At the cap there is nothing left to fill, and an empty track would read as
|
||
the opposite. Fill it whole and let the label say why. */
|
||
.pet-xp-capped {
|
||
background: color-mix(in srgb, #c9a227 72%, var(--ink));
|
||
}
|
||
|
||
/* The party roster. One row per seat, and the three kinds have to be tellable
|
||
apart at a glance because they mean different things about the run: the
|
||
leader owns the clock, a member is another player, the companion is hired. */
|
||
.party-seat {
|
||
display: flex;
|
||
align-items: baseline;
|
||
gap: 0.6rem;
|
||
padding: 0.45rem 0;
|
||
border-bottom: 1px solid color-mix(in srgb, var(--ink) 7%, transparent);
|
||
}
|
||
.party-seat:last-child { border-bottom: 0; }
|
||
.party-seat-role {
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
border-radius: 9999px;
|
||
padding: 0.15rem 0.45rem;
|
||
color: color-mix(in srgb, var(--ink) 55%, transparent);
|
||
background: color-mix(in srgb, var(--ink) 8%, var(--card));
|
||
white-space: nowrap;
|
||
}
|
||
.party-seat-leader {
|
||
color: color-mix(in srgb, #6d4bd8 70%, var(--ink));
|
||
background: color-mix(in srgb, #6d4bd8 12%, var(--card));
|
||
}
|
||
/* An opted-out player's seat. It stays on the roster because the party size is
|
||
load-bearing — the supply burn and the threat level printed on this same page
|
||
felt that body — but it carries no name and no link. Italic and recessed so
|
||
it reads as withheld rather than as missing data. */
|
||
.party-seat-anon {
|
||
font-style: italic;
|
||
color: color-mix(in srgb, var(--ink) 45%, transparent);
|
||
}
|
||
}
|
||
|
||
@layer components {
|
||
/* "While you were away" (adventure section, signed-in owner only). Rows are
|
||
quiet by default; the two kinds of line worth an eye — a realm first, a death
|
||
— carry a marker. Same plain-selector purge discipline as everything above. */
|
||
.away-line {
|
||
padding: 0.3rem 0.55rem;
|
||
border-radius: 0.75rem;
|
||
border-left: 3px solid transparent;
|
||
}
|
||
.away-line:hover {
|
||
background: color-mix(in srgb, var(--ink) 4%, transparent);
|
||
}
|
||
.away-line-notable {
|
||
border-left-color: color-mix(in srgb, #c9a227 60%, transparent);
|
||
background: color-mix(in srgb, #c9a227 6%, transparent);
|
||
}
|
||
}
|