@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. */
  .pete-slot {
    position: relative;
    display: grid;
    place-items: center;
    height: var(--card-h);
    width: var(--card-w);
    flex: none;
    border-radius: 0.55rem;
    border: 2px dashed rgba(255, 255, 255, 0.25);
    background: rgba(0, 0, 0, 0.12);
    box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.18);
    transition: border-color 0.18s ease, background 0.18s ease, transform 0.12s ease;
  }
  .pete-slot-glyph {
    font-size: calc(var(--card-w) * 0.42);
    line-height: 1;
    color: rgba(255, 255, 255, 0.3);
  }
  .pete-slot-glyph[data-red="1"] { color: rgba(255, 170, 170, 0.35); }

  /* A card sitting *in* a slot — the top of a foundation. The slot keeps its
     footprint whether or not there's a card on it, so an ace going home doesn't
     reflow the row, and a drop target has somewhere to be while it's empty. */
  .pete-slot > .pete-card {
    position: absolute;
    inset: 0;
    height: 100%;
    width: 100%;
  }

  /* The stock is a button, and it looks like the back of a card because that is
     what it is: a pile you can turn over. */
  .pete-stock {
    border-style: solid;
    border-color: rgba(0, 0, 0, 0.15);
    background:
      repeating-linear-gradient(45deg, rgba(255,255,255,0.10) 0 6px, transparent 6px 12px),
      linear-gradient(150deg, #b4553f, #8d3f2f);
    cursor: pointer;
  }
  .pete-stock:hover { filter: brightness(1.08); }
  .pete-stock:active { transform: translateY(1px) scale(0.98); }
  /* Spent, but there's a pass left: it stops being a pile of cards and starts
     being the gesture of turning the waste back over. */
  .pete-stock[data-empty="1"] {
    background: rgba(0, 0, 0, 0.12);
    border-style: dashed;
    border-color: rgba(255, 255, 255, 0.25);
  }
  /* Spent, and no passes left. Not a button any more — and it says so by looking
     like the dead thing it is rather than by silently refusing clicks. */
  .pete-stock[data-dead="1"] {
    cursor: default;
    opacity: 0.4;
  }
  .pete-stock[data-dead="1"]:hover { filter: none; }
  .pete-stock[data-dead="1"]:active { transform: none; }

  .pete-slot-count {
    position: absolute;
    bottom: -0.5rem;
    left: 50%;
    transform: translateX(-50%);
    border-radius: 999px;
    background: rgba(0, 0, 0, 0.5);
    padding: 0.05rem 0.5rem;
    font-size: 0.7rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: #fff;
  }
  .pete-slot-recycle { color: rgba(255, 255, 255, 0.45); }
  .pete-slot-recycle svg { height: 1.4rem; width: 1.4rem; }

  /* The waste. Three cards fanned sideways, the top one on the right — you can
     see what's under it, and only the top one is yours to take. */
  .pete-waste {
    position: relative;
    display: flex;
    height: var(--card-h);
    min-width: var(--card-w);
  }
  .pete-waste .pete-card + .pete-card {
    margin-left: calc(var(--card-w) * -0.72);
  }
  .pete-waste .pete-card { position: relative; }

  /* The seven columns. They share the width evenly and never scroll: a board you
     have to scroll sideways is a board you can't plan on. */
  .pete-tableau {
    display: grid;
    grid-template-columns: repeat(7, var(--card-w));
    justify-content: space-between;
    gap: 0.5rem;
    align-items: start;
    min-height: calc(var(--card-h) * 2.6);
  }
  .pete-col {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: var(--card-h);
  }
  /* Cards overlap up the column, and how much of one shows depends on whether
     there's anything to see. The rule keys off the card *above*, which is why
     it's an adjacent-sibling selector and not something the JS has to compute. */
  .pete-col .pete-card { position: relative; }
  .pete-col .pete-card[data-face="down"] + .pete-card {
    margin-top: calc(var(--fan-down) - var(--card-h));
  }
  .pete-col .pete-card[data-face="up"] + .pete-card {
    margin-top: calc(var(--fan-up) - var(--card-h));
  }

  /* A card you can pick up says so. */
  .pete-card[data-live="1"] { cursor: pointer; }
  .pete-card[data-live="1"]:hover .pete-card-front {
    filter: brightness(1.06);
  }
  /* The card (or run) in your hand: lifted off the felt, and glowing, so it's
     obvious what a click on a column is about to move. */
  .pete-card[data-held="1"] {
    z-index: 5;
    transform: translateY(-0.55rem);
    transition: transform 0.14s cubic-bezier(0.34, 1.56, 0.64, 1);
  }
  .pete-card[data-held="1"] .pete-card-front {
    box-shadow:
      0 3px 0 rgba(0,0,0,0.18),
      0 10px 22px rgba(0,0,0,0.32),
      0 0 0 3px rgba(242, 181, 61, 0.9);
  }
  /* A place the held card would actually go. Lit *before* you click it, because
     the alternative is learning the rules by being told no. */
  .pete-slot[data-drop="1"],
  .pete-col[data-drop="1"] .pete-slot,
  .pete-col[data-drop="1"] .pete-card:last-child .pete-card-front {
    border-color: rgba(242, 181, 61, 0.9);
    box-shadow: 0 0 0 3px rgba(242, 181, 61, 0.45);
  }
  .pete-slot[data-drop="1"],
  .pete-col[data-drop="1"] .pete-slot {
    background: rgba(242, 181, 61, 0.12);
  }

  /* A move that won't go. Said in the one language a board can speak. */
  .pete-nope { animation: pete-shake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97); }

  /* A card arriving on a foundation lands with a flash: it is the only move in
     the game that pays you, so it is the only one that gets a noise. */
  .pete-home-flash { animation: pete-home 0.5s ease-out; }
  @keyframes pete-home {
    0%   { box-shadow: 0 0 0 0 rgba(242, 181, 61, 0.9); }
    100% { box-shadow: 0 0 0 1.4rem rgba(242, 181, 61, 0); }
  }

  /* The 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)); }
}
