/* Murder, she Bingo!
   The page is a garden tea table at dusk; the bingo card is the printed card lying on it.
   Two screens share the table: the front door (.door) and the card (.play). */

/* All faces are self-hosted, so every phone sees the same page.
   Lansbury (Allen R. Walden, 1993) is a free look-alike of the print design's Fletcher Gothic; its own notice reads
   "UNPROTECTED - Please distribute freely." It has no curly quotes or dashes, so keep display text plain.
   Solway and Zilla Slab (SIL OFL, texts in fonts/) stand in for American Typewriter. */
@font-face { font-family: "Lansbury"; src: url("fonts/lansbury.ttf") format("truetype"); font-display: swap; size-adjust: 112%; }
@font-face { font-family: "Solway"; src: url("fonts/solway-400.woff2") format("woff2"); font-weight: 400; font-display: swap; }
@font-face { font-family: "Solway"; src: url("fonts/solway-700.woff2") format("woff2"); font-weight: 700; font-display: swap; }
@font-face { font-family: "Zilla Slab"; src: url("fonts/zilla-slab-400.woff2") format("woff2"); font-display: swap; }

:root {
  /* From the flavour photos */
  --table: #1e3a2b;
  --table-lit: #2d5540;
  --table-deep: #12261b;
  --gold: #cdaa52;
  --gold-soft: #e6cf8d;
  --teapot: #a3bf5c;
  --hydrangea: #86a2e0;
  --sunflower: #e0742c;
  --cream: #f4ecd8;
  --cream-faint: #c3c9b4;
  /* The printed card */
  --parchment: #efe6cf;
  --parchment-edge: #e2d6b8;
  --ink: #1d1b18;
  --ink-soft: #5b564b;
  --rule: #7b7567;
  --blood: #a3161c;
  --blood-tint: rgba(163, 22, 28, .1);

  --display: "Lansbury", Futura, "Century Gothic", sans-serif;
  --type: "Solway", Rockwell, "Courier New", serif;
  --grid-type: "Zilla Slab", Rockwell, serif;
  --italic: Baskerville, "Baskerville Old Face", "Libre Baskerville", Georgia, serif;

  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' seed='11'/%3E%3CfeColorMatrix values='0 0 0 0 .42 0 0 0 0 .34 0 0 0 0 .2 1.7 0 0 0 -1.02'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  /* Paper: speckle (--grain) over slow brown mottling and faint horizontal fibres. */
  --mottle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='520' height='520'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.011 .015' numOctaves='3' seed='5' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .47 0 0 0 0 .34 0 0 0 0 .14 .85 0 0 0 -.33'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  --fibre: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='400' height='400'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.018 .7' numOctaves='2' seed='8' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 .98 0 0 0 0 .9 .42 0 0 0 -.19'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  --paper-tex: var(--grain), var(--fibre), var(--mottle);
  /* The table: a flocked wallpaper cloth. A fine even nap, and a tone-on-tone damask of lotus buds in an ogee
     lattice, half-drop repeat (source drawing: tools/damask.svg). */
  --nap: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.4' numOctaves='1' seed='4' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .9 0 0 0 0 1 0 0 0 0 .85 .42 0 0 0 -.17'/%3E%3C/filter%3E%3Cfilter id='b'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.4' numOctaves='1' seed='9' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .8 0 0 0 -.3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3Crect width='100%25' height='100%25' filter='url(%23b)'/%3E%3C/svg%3E");
  --damask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='104' height='156' viewBox='0 0 104 156'%3E%3Cdefs%3E%3Cg id='m'%3E%3Cpath d='M0-46C-8-33-13-24-13-15C-13-5-7 1 0 1C7 1 13-5 13-15C13-24 8-33 0-46Z'/%3E%3Cpath d='M-4 3C-16 3-27-6-30-21C-20-18-10-10-4 3ZM4 3C16 3 27-6 30-21C20-18 10-10 4 3Z'/%3E%3Cpath d='M-3 9C-6 23-15 31-27 33C-21 24-13 16-3 9ZM3 9C6 23 15 31 27 33C21 24 13 16 3 9Z'/%3E%3Cpath d='M0 10C-4 21-4 31 0 42C4 31 4 21 0 10Z'/%3E%3Ccircle cy='49' r='2.6'/%3E%3Ccircle cy='-52' r='2'/%3E%3Ccircle cx='-34' cy='-27' r='1.8'/%3E%3Ccircle cx='34' cy='-27' r='1.8'/%3E%3Cpath d='M0-34C-3-27-5-21-5-15C-5-10-3-7 0-7C3-7 5-10 5-15C5-21 3-27 0-34Z' fill='black' fill-opacity='.16'/%3E%3C/g%3E%3Cg id='o' fill='none' stroke-width='1.1'%3E%3Cpath d='M52 0C52 26 20 40 0 78C20 116 52 130 52 156M52 0C52 26 84 40 104 78C84 116 52 130 52 156'/%3E%3C/g%3E%3Cg id='t'%3E%3Cuse href='%23m' x='52' y='82'/%3E%3Cuse href='%23m' x='0' y='4'/%3E%3Cuse href='%23m' x='104' y='4'/%3E%3Cuse href='%23m' x='0' y='160'/%3E%3Cuse href='%23m' x='104' y='160'/%3E%3C/g%3E%3C/defs%3E%3Cuse href='%23t' x='.8' y='1.3' fill='black' fill-opacity='.22'/%3E%3Cuse href='%23o' x='.6' y='1' stroke='black' stroke-opacity='.2'/%3E%3Cuse href='%23t' fill='%23cfe6c4' fill-opacity='.085'/%3E%3Cuse href='%23o' stroke='%23cfe6c4' stroke-opacity='.1'/%3E%3C/svg%3E");
  --gilt: linear-gradient(160deg, #f6e7ae, #9a7a28 42%, #ecd692 68%, #7d6220);
  --fleuron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='18' viewBox='0 0 220 18'%3E%3Cg fill='none' stroke='%23cdaa52' stroke-width='1' stroke-linecap='round'%3E%3Cpath d='M4 9H84M136 9H216'/%3E%3Cpath d='M84 9C92 9 94 3 100 3C96 6 96 12 100 15C94 15 92 9 84 9ZM136 9C128 9 126 3 120 3C124 6 124 12 120 15C126 15 128 9 136 9Z' fill='%23cdaa52' fill-opacity='.55'/%3E%3C/g%3E%3Cpath d='M110 1C106 5 104 7 104 10C104 13 107 16 110 17C113 16 116 13 116 10C116 7 114 5 110 1Z' fill='%23e6cf8d'/%3E%3Ccircle cx='4' cy='9' r='1.6' fill='%23cdaa52'/%3E%3Ccircle cx='216' cy='9' r='1.6' fill='%23cdaa52'/%3E%3C/svg%3E");
  --lighthouse: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='46' viewBox='0 0 220 46'%3E%3Cg fill='none' stroke='%23cdaa52' stroke-width='1' stroke-linecap='round'%3E%3Cpath d='M4 44H88M132 44H216'/%3E%3C/g%3E%3Ccircle cx='4' cy='44' r='1.6' fill='%23cdaa52'/%3E%3Ccircle cx='216' cy='44' r='1.6' fill='%23cdaa52'/%3E%3Cg transform='translate(110 0)'%3E%3Cpath d='M-3 -1L-34 6V16L-3 13ZM3 -1L34 6V16L3 13Z' fill='%23e6cf8d' fill-opacity='.16' transform='translate(0 10)'/%3E%3Cpath d='M-5 15L0 8L5 15Z' fill='%23e6cf8d'/%3E%3Crect x='-4' y='15' width='8' height='7' fill='none' stroke='%23e6cf8d' stroke-width='1.2'/%3E%3Ccircle cy='18.5' r='1.7' fill='%23fff6cf'/%3E%3Cpath d='M-7 22.6H7' stroke='%23e6cf8d' stroke-width='1.4' stroke-linecap='round'/%3E%3Cpath d='M-4.6 24H4.6L7 44H-7Z' fill='none' stroke='%23cdaa52' stroke-width='1.2' stroke-linejoin='round'/%3E%3Cpath d='M-5.1 28.5H5.1L5.7 33H-5.7ZM-6.2 37.5H6.2L7 44H-7Z' fill='%23cdaa52' fill-opacity='.75'/%3E%3Cpath d='M-16 44C-12 40-8 43-7 44H7C9 41 13 41 17 44Z' fill='%23cdaa52' fill-opacity='.6'/%3E%3C/g%3E%3C/svg%3E");
  --bicycle: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='66' height='42' viewBox='0 0 66 42'%3E%3Cg fill='none' stroke='%23e6cf8d' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='13' cy='29' r='10.5'/%3E%3Ccircle cx='51' cy='29' r='10.5'/%3E%3Cpath d='M13 29L24 13H43L51 29M24 13L33 29H13M33 29L43 13M43 13L45 6H50M21 11H28M24 13L23.4 11'/%3E%3Cpath d='M47.5 5.5H58V13.5H49Z M49.5 5.5V13.5M52.5 5.5V13.5M55.5 5.5V13.5M48 9.5H58' stroke-width='1'/%3E%3Cpath d='M13 29L13 18.5M13 29L22 34M13 29L4 34M51 29L51 18.5M51 29L60 34M51 29L42 34' stroke-width='.6' stroke-opacity='.7'/%3E%3Cpath d='M33 29L36 34M34 34H38'/%3E%3C/g%3E%3Ccircle cx='33' cy='29' r='2' fill='%23e6cf8d'/%3E%3C/svg%3E");
  --stain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cfilter id='w' x='-20%25' y='-20%25' width='140%25' height='140%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.035' numOctaves='2' seed='6'/%3E%3CfeDisplacementMap in='SourceGraphic' scale='9'/%3E%3CfeGaussianBlur stdDeviation='.9'/%3E%3C/filter%3E%3Cg filter='url(%23w)' fill='none' stroke='%237a4e16'%3E%3Ccircle cx='100' cy='100' r='74' stroke-width='5' stroke-opacity='.3' stroke-dasharray='330 26 70 40'/%3E%3Ccircle cx='100' cy='100' r='70' stroke-width='11' stroke-opacity='.07'/%3E%3Ccircle cx='100' cy='100' r='66' fill='%237a4e16' fill-opacity='.035' stroke='none'/%3E%3C/g%3E%3C/svg%3E");
  --pillow: inset 0 1px 0 rgba(255, 255, 255, .16), inset 0 -3px 7px rgba(0, 0, 0, .38), 0 1px 0 rgba(0, 0, 0, .55), 0 8px 16px rgba(0, 0, 0, .42);
  color-scheme: dark;
}
/* Night: the table goes dark, the card stays paper. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --table: #101f17; --table-lit: #1a3326; --table-deep: #08120d;
    --parchment: #e7dcc2; --parchment-edge: #d6c9a8; --cream: #ece4cf;
  }
}
:root[data-theme="dark"] {
  --table: #101f17; --table-lit: #1a3326; --table-deep: #08120d;
  --parchment: #e7dcc2; --parchment-edge: #d6c9a8; --cream: #ece4cf;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; background: var(--table-deep); }
body {
  margin: 0; min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column;
  color: var(--cream); font-family: var(--type); font-size: 16px; line-height: 1.45;
  padding-top: max(14px, env(safe-area-inset-top, 0px));
  padding-bottom: max(22px, env(safe-area-inset-bottom, 0px));
  padding-left: max(12px, env(safe-area-inset-left, 0px));
  padding-right: max(12px, env(safe-area-inset-right, 0px));
}
/* The cloth, fixed behind everything, with a deep soft shadow round the edge of the screen. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -2;
  background:
    radial-gradient(70% 38% at 50% 16%, rgba(255, 210, 130, .13), transparent 72%), /* lamplight */
    var(--nap), var(--damask) 50% 0,
    radial-gradient(120% 70% at 50% 0%, var(--table-lit), transparent 70%),
    linear-gradient(var(--table), var(--table-deep));
}
body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  box-shadow: inset 0 0 90px 24px rgba(0, 0, 0, .5), inset 0 0 18px rgba(0, 0, 0, .45);
}
/* A gilt picture-frame line round the whole screen, with a flourish in each corner. */
.frame {
  position: fixed; z-index: -1; pointer-events: none; border: 1px solid rgba(205, 170, 82, .5); border-radius: 2px;
  inset: max(5px, env(safe-area-inset-top, 0px)) max(5px, env(safe-area-inset-right, 0px)) max(5px, env(safe-area-inset-bottom, 0px)) max(5px, env(safe-area-inset-left, 0px));
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='46' viewBox='0 0 46 46'%3E%3Cg transform='' fill='none' stroke='%23cdaa52' stroke-linecap='round'%3E%3Cpath d='M1 45V15C1 7 7 1 15 1H45' stroke-width='1.2'/%3E%3Cpath d='M6 34C6 18 18 6 34 6' stroke-width='1' stroke-opacity='.8'/%3E%3Cpath d='M11 24C11 16 16 11 24 11C19 13 13 19 11 24Z' fill='%23cdaa52' stroke='none' fill-opacity='.8'/%3E%3Ccircle cx='7.5' cy='7.5' r='1.8' fill='%23e6cf8d' stroke='none'/%3E%3C/g%3E%3C/svg%3E") left -1px top -1px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='46' viewBox='0 0 46 46'%3E%3Cg transform='translate(46 0) scale(-1 1)' fill='none' stroke='%23cdaa52' stroke-linecap='round'%3E%3Cpath d='M1 45V15C1 7 7 1 15 1H45' stroke-width='1.2'/%3E%3Cpath d='M6 34C6 18 18 6 34 6' stroke-width='1' stroke-opacity='.8'/%3E%3Cpath d='M11 24C11 16 16 11 24 11C19 13 13 19 11 24Z' fill='%23cdaa52' stroke='none' fill-opacity='.8'/%3E%3Ccircle cx='7.5' cy='7.5' r='1.8' fill='%23e6cf8d' stroke='none'/%3E%3C/g%3E%3C/svg%3E") right -1px top -1px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='46' viewBox='0 0 46 46'%3E%3Cg transform='translate(0 46) scale(1 -1)' fill='none' stroke='%23cdaa52' stroke-linecap='round'%3E%3Cpath d='M1 45V15C1 7 7 1 15 1H45' stroke-width='1.2'/%3E%3Cpath d='M6 34C6 18 18 6 34 6' stroke-width='1' stroke-opacity='.8'/%3E%3Cpath d='M11 24C11 16 16 11 24 11C19 13 13 19 11 24Z' fill='%23cdaa52' stroke='none' fill-opacity='.8'/%3E%3Ccircle cx='7.5' cy='7.5' r='1.8' fill='%23e6cf8d' stroke='none'/%3E%3C/g%3E%3C/svg%3E") left -1px bottom -1px no-repeat,
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='46' height='46' viewBox='0 0 46 46'%3E%3Cg transform='translate(46 46) scale(-1 -1)' fill='none' stroke='%23cdaa52' stroke-linecap='round'%3E%3Cpath d='M1 45V15C1 7 7 1 15 1H45' stroke-width='1.2'/%3E%3Cpath d='M6 34C6 18 18 6 34 6' stroke-width='1' stroke-opacity='.8'/%3E%3Cpath d='M11 24C11 16 16 11 24 11C19 13 13 19 11 24Z' fill='%23cdaa52' stroke='none' fill-opacity='.8'/%3E%3Ccircle cx='7.5' cy='7.5' r='1.8' fill='%23e6cf8d' stroke='none'/%3E%3C/g%3E%3C/svg%3E") right -1px bottom -1px no-repeat;
}
.fleuron { width: min(70%, 220px); height: 18px; margin: 2px auto; background: var(--fleuron) center / contain no-repeat; }
/* The Cabot Cove light, standing on the rule above the buttons. */
.fleuron.light { height: 46px; margin-top: -6px; background-image: var(--lighthouse); }
main { width: 100%; max-width: 520px; margin: 0 auto; flex: 1; display: flex; flex-direction: column; gap: 14px; }

/* ---------- Shared pieces ---------- */
.tonight { margin: 0; text-align: center; font-size: 14px; letter-spacing: .1em; color: var(--gold-soft); }
.btn {
  font: inherit; font-size: 16px; color: var(--cream);
  background: linear-gradient(rgba(255, 255, 255, .07), rgba(0, 0, 0, .2)) padding-box, linear-gradient(var(--table), var(--table)) padding-box, var(--gilt) border-box;
  border: 1.5px solid transparent; border-radius: 999px;
  min-height: 48px; padding: 10px 22px; cursor: pointer;
  /* a padded cushion with a second gold line of piping */
  box-shadow: inset 0 0 0 3px transparent, inset 0 0 0 4px rgba(205, 170, 82, .45), var(--pillow);
  text-shadow: 0 1px 0 rgba(0, 0, 0, .5);
  -webkit-tap-highlight-color: transparent; touch-action: manipulation;
  transition: border-color .15s, color .15s, filter .15s;
}
.btn.primary {
  background: linear-gradient(#f3e3aa, var(--gold) 50%, #a98835) padding-box, var(--gilt) border-box; color: var(--ink); text-shadow: 0 1px 0 rgba(255, 255, 255, .35);
  box-shadow: inset 0 0 0 3px transparent, inset 0 0 0 4px rgba(29, 27, 24, .45), inset 0 1px 0 rgba(255, 255, 255, .5), inset 0 -3px 7px rgba(90, 60, 0, .4), 0 1px 0 rgba(0, 0, 0, .55), 0 8px 16px rgba(0, 0, 0, .42);
}
.btn.small { min-height: 44px; padding: 8px 16px; font-size: 15px; }
.btn.round { width: 44px; min-height: 44px; padding: 0; font-size: 20px; line-height: 1; }
@media (hover: hover) { .btn:hover { border-color: var(--gold-soft); filter: brightness(1.12); } }
.btn:active { transform: translateY(2px); filter: brightness(.94); }
.link {
  font: inherit; font-size: 15px; color: var(--gold-soft); background: none; border: 0; padding: 12px 8px; border-radius: 6px;
  text-decoration: underline; text-underline-offset: 4px; cursor: pointer;
}

/* Focus, for keyboards and switches. On the velvet it is a ring of gold stitching;
   on paper it is the printer's "cut along the dotted line". */
:focus { outline: none; }
:focus-visible { outline: 2px dotted var(--gold-soft); outline-offset: 5px; }
.btn:focus-visible { border-color: var(--gold-soft); filter: brightness(1.15); }
.card :focus-visible, .slip :focus-visible, .bingo :focus-visible, .rules :focus-visible { outline: 1.5px dashed var(--ink); outline-offset: 4px; }
.cell:focus-visible { outline: 1.5px dashed var(--ink); outline-offset: -5px; background-color: rgba(255, 252, 240, .5); }
.cell.on:focus-visible { outline-color: var(--blood); }
.cell.win:focus-visible { outline-color: var(--parchment); background-color: var(--blood); }
.slip input:focus-visible { outline: none; border-bottom: 2.5px solid var(--blood); background: rgba(255, 252, 240, .5); }
.bingo .btn:focus-visible { outline-color: var(--blood); }

/* ---------- Front door ---------- */
.door { align-items: center; text-align: center; gap: 12px; padding-top: 6px; }
.title {
  font-family: var(--display); font-weight: 400; line-height: 1.05; margin: 0;
  font-size: clamp(46px, 15vw, 78px); color: var(--cream); text-wrap: balance;
  padding-bottom: .12em;
  /* gold leaf, with a slow glint crossing it */
  background: linear-gradient(100deg, transparent 42%, rgba(255, 255, 255, .9) 50%, transparent 58%) 160% 0 / 260% 100% no-repeat,
              linear-gradient(176deg, #fff4c9, #e9d28f 30%, #b48d36 56%, #f1dc9a 74%, #a17c2a);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  filter: drop-shadow(0 2px 0 rgba(0, 0, 0, .5)) drop-shadow(0 10px 16px rgba(0, 0, 0, .45));
}
.arch {
  position: relative; margin: 4px 0 0; width: min(78%, 330px); aspect-ratio: 4 / 5;
  border-radius: 999px 999px 10px 10px; overflow: hidden;
  border: 2.5px solid transparent; background: var(--gilt) border-box; outline: 1px solid var(--gold); outline-offset: 4px;
  box-shadow: 0 0 0 5px var(--table-deep), 0 2px 2px 5px rgba(0, 0, 0, .5), 0 22px 44px rgba(0, 0, 0, .55);
}
.arch::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 0 26px rgba(0, 0, 0, .55), inset 0 2px 3px rgba(0, 0, 0, .5); }
.arch img { display: block; width: 100%; height: 100%; object-fit: cover; object-position: 62% 60%; }
.pitch { margin: 8px 0 0; max-width: 30ch; text-wrap: balance; text-shadow: 0 1px 0 rgba(0, 0, 0, .5); }
.choices { display: flex; flex-direction: column; gap: 10px; width: min(100%, 300px); margin-top: 4px; }
.slip {
  width: min(100%, 340px); padding: 16px; text-align: left;
  background: var(--paper-tex), var(--parchment); color: var(--ink);
  box-shadow: 0 1px 1px rgba(0, 0, 0, .4), 0 14px 28px rgba(0, 0, 0, .5), inset 0 0 30px rgba(120, 96, 40, .18); display: flex; flex-direction: column; gap: 12px;
}
.slip h2 { margin: 0; font-family: var(--display); font-weight: 400; font-size: 34px; line-height: 1; }
.slip label { display: flex; flex-direction: column; gap: 4px; font-size: 14px; color: var(--ink-soft); }
.slip input {
  font: inherit; font-size: 18px; color: var(--ink); background: transparent;
  border: 0; border-bottom: 1.5px solid var(--ink); border-radius: 0; padding: 6px 2px; min-height: 44px; width: 100%;
}
.slip input.code { letter-spacing: .4em; text-transform: uppercase; }
.slip .btn { color: var(--ink); background: transparent; border-color: var(--ink); box-shadow: none; text-shadow: none; align-self: flex-start; }
.slip .note { margin: 0; font-size: 14px; color: var(--blood); }

/* ---------- The card screen ---------- */
.bar { display: flex; align-items: center; justify-content: space-between; gap: 6px; }

.card-shadow { position: relative; transform: rotate(var(--tilt, 0deg)); filter: drop-shadow(0 1px 1px rgba(0, 0, 0, .45)) drop-shadow(0 14px 20px rgba(0, 0, 0, .5)); container-type: inline-size; }
.card-shadow::before { /* the card sinks a little into the velvet */
  content: ""; position: absolute; inset: -14px; z-index: -1; border-radius: 28px; background: rgba(0, 0, 0, .34); filter: blur(16px);
}
.card {
  position: relative; color: var(--ink);
  background: var(--paper-tex), var(--parchment);
  padding: 2.4cqw 3cqw 3.2cqw;
  /* scalloped corners, as on the printed stock */
  --notch: 3.4cqw;
  -webkit-mask-image:
    radial-gradient(circle at 0 0, transparent var(--notch), #000 calc(var(--notch) + .5px)),
    radial-gradient(circle at 100% 0, transparent var(--notch), #000 calc(var(--notch) + .5px)),
    radial-gradient(circle at 0 100%, transparent var(--notch), #000 calc(var(--notch) + .5px)),
    radial-gradient(circle at 100% 100%, transparent var(--notch), #000 calc(var(--notch) + .5px));
  mask-image:
    radial-gradient(circle at 0 0, transparent var(--notch), #000 calc(var(--notch) + .5px)),
    radial-gradient(circle at 100% 0, transparent var(--notch), #000 calc(var(--notch) + .5px)),
    radial-gradient(circle at 0 100%, transparent var(--notch), #000 calc(var(--notch) + .5px)),
    radial-gradient(circle at 100% 100%, transparent var(--notch), #000 calc(var(--notch) + .5px));
  -webkit-mask-composite: source-in; mask-composite: intersect;
}
.card::after { /* wear and shading toward the edges */
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(ellipse at center, transparent 58%, rgba(120, 92, 36, .2)),
    linear-gradient(105deg, rgba(255, 255, 255, .22), transparent 30%, transparent 75%, rgba(70, 50, 10, .1));
}
.card::before { /* someone put their teacup down; app.js moves the ring about from card to card */
  content: ""; position: absolute; left: var(--sx, 58%); top: var(--sy, 6%); width: 40cqw; aspect-ratio: 1; pointer-events: none;
  background: var(--stain) center / contain no-repeat; rotate: var(--sr, 0deg); mix-blend-mode: multiply;
}
.grid-wrap { position: relative; }
.card-title {
  margin: 0; text-align: center; font-family: var(--display); font-weight: 400;
  font-size: 12.6cqw; line-height: 1.12; white-space: nowrap; padding-bottom: .5cqw;
}
.grid {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); grid-template-rows: repeat(5, minmax(0, 1fr)); aspect-ratio: 1;
  border-top: 1px solid var(--rule); border-left: 1px solid var(--rule);
}
.cell {
  position: relative; min-width: 0; min-height: 0; overflow: hidden; margin: 0;
  display: flex; align-items: center; justify-content: center; text-align: center;
  border: 0; border-right: 1px solid var(--rule); border-bottom: 1px solid var(--rule); border-radius: 0;
  background: transparent; color: var(--ink);
  font-family: var(--grid-type); font-weight: 400; line-height: 1.16;
  font-size: clamp(8px, 2.95cqw, 15px);
  padding: 2px; overflow-wrap: anywhere; hyphens: manual;
  cursor: pointer; user-select: none; -webkit-user-select: none; touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
.cell span { position: relative; z-index: 1; }
.cell.center { font-family: var(--type); font-weight: 700; }

/* The mark: a red magnifying-glass stamp under the words, which keep a paper halo so they stay readable. */
.cell { --stamp: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' fill='none' stroke='black' stroke-linecap='round'%3E%3Ccircle cx='50' cy='50' r='46' stroke-width='4'/%3E%3Ccircle cx='50' cy='50' r='39' stroke-width='1.5' stroke-dasharray='3 4'/%3E%3Ccircle cx='44' cy='44' r='16' stroke-width='6'/%3E%3Cpath d='M56 56 L73 73' stroke-width='9'/%3E%3Cpath d='M36 40 a9 9 0 0 1 8 -6' stroke-width='2.5'/%3E%3C/svg%3E"); }
.cell.on::before, .cell.ghost::before {
  content: ""; position: absolute; inset: 3%; pointer-events: none;
  background: var(--blood);
  -webkit-mask: var(--stamp) center / contain no-repeat; mask: var(--stamp) center / contain no-repeat;
  transform: rotate(-14deg); opacity: .34;
}
.cell.on { background: var(--blood-tint); }
.cell.on span { text-shadow: 0 0 2px var(--parchment), 0 0 2px var(--parchment), 0 0 3px var(--parchment), 0 0 4px var(--parchment); }

.cell.win { background: var(--blood); color: var(--parchment); }
.cell.win::before { display: none; }
.cell.win span { text-shadow: none; }

/* Lace band and maker's plate at the foot of the card */
.lace { position: relative; height: 8.6cqw; margin-top: .7cqw; display: flex; align-items: flex-end; justify-content: center; }
.lace::before {
  content: ""; position: absolute; inset: 0; background: var(--ink);
  -webkit-mask: url("img/lace.png") left top / auto 100% repeat-x; mask: url("img/lace.png") left top / auto 100% repeat-x;
}
.plate {
  position: relative; margin-bottom: 1.3cqw; padding: .9cqw 4.5cqw; white-space: nowrap;
  font-family: var(--grid-type); font-size: clamp(8px, 2.7cqw, 13px); line-height: 1.2; color: var(--ink-soft);
  text-decoration: none; background: var(--parchment); border: 1px solid var(--ink); border-radius: 1.4cqw / 50%;
}
.plate::after { content: ""; position: absolute; inset: -12px -6px; } /* thumb-sized hit area */
/* The card's code, tucked in the corner like the print's page number. Tap it to look a card up. */
.card-no {
  position: absolute; right: 4.2cqw; bottom: .4cqw; z-index: 2; margin: 0; padding: 0; border: 0; background: none; cursor: pointer;
  font-family: var(--italic); font-style: italic; font-size: clamp(8px, 2.3cqw, 11px); letter-spacing: .04em; color: var(--ink-soft);
}
.card-no::after { content: ""; position: absolute; inset: -14px -10px -8px -24px; }

.status { margin: 0; text-align: center; min-height: 1.5em; color: var(--cream-faint); font-size: 15px; text-shadow: 0 1px 0 rgba(0, 0, 0, .5); }
.actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.btn.danger.armed { border-color: var(--sunflower); color: var(--sunflower); }

.bingo {
  align-self: center; width: min(100%, 420px); text-align: center; color: var(--ink);
  background: var(--paper-tex), var(--parchment); padding: 14px 16px 16px;
  border: 3px double var(--blood); outline: 5px solid var(--parchment);
  box-shadow: 0 1px 1px 5px rgba(0, 0, 0, .35), 0 16px 30px 5px rgba(0, 0, 0, .5), inset 0 0 30px rgba(120, 96, 40, .16); transform: rotate(-1.2deg); margin: 8px 0;
}
.bingo strong { display: block; font-family: var(--display); font-weight: 400; font-size: 68px; line-height: 1.05; color: var(--blood); }
.bingo p { margin: 4px 0 0; font-size: 15px; }
.bingo .why { color: var(--blood); }
.bingo .btn { margin-top: 12px; color: var(--parchment); background: linear-gradient(#b8262c, var(--blood) 60%, #84090f); border-color: var(--blood); box-shadow: inset 0 0 0 3px transparent, inset 0 0 0 4px rgba(239, 230, 207, .6), 0 5px 10px rgba(60, 0, 0, .35); }

footer { width: 100%; max-width: 520px; margin: 22px auto 0; text-align: center; font-size: 12px; line-height: 1.6; color: var(--cream-faint); }
/* A gold rule along the foot of the page, with a bicycle and its basket parked on it. */
footer { position: relative; padding-top: 44px; overflow-x: clip; }
footer::before { content: ""; position: absolute; left: 8%; right: 8%; top: 31px; height: 1px; background: linear-gradient(90deg, transparent, var(--gold) 12%, var(--gold) 88%, transparent); opacity: .7; }
footer::after { content: ""; position: absolute; left: 50%; top: 5px; width: 42px; height: 27px; margin-left: -21px; background: var(--bicycle) center / contain no-repeat; }
footer p { margin: 0; text-wrap: balance; }
footer a { color: var(--gold-soft); text-underline-offset: 3px; padding-block: 6px; }

/* ---------- Claiming bingo: the deciding square, held up for the room ---------- */
dialog.claim {
  width: 100%; max-width: 100%; height: 100%; max-height: 100%; margin: 0; padding: 20px; border: 0; background: none; overflow: hidden;
  color: var(--cream); text-align: center;
}
dialog.claim[open] { display: grid; place-content: center; justify-items: center; gap: 18px; }
dialog.claim::backdrop { background: rgba(6, 14, 10, .86); }
.exhibit {
  --side: min(78vw, 340px, 44vh); /* everything inside is sized from this, so it stays a square on any screen */
  width: var(--side); height: var(--side); display: grid; place-items: center; padding: calc(var(--side) * .07); overflow: hidden;
  background: var(--paper-tex), var(--parchment); color: var(--ink); border: 1px solid var(--rule); outline: 7px solid var(--parchment);
  box-shadow: 0 2px 2px 7px rgba(0, 0, 0, .4), 0 30px 60px 7px rgba(0, 0, 0, .65), inset 0 0 40px rgba(120, 96, 40, .2);
  font-family: var(--grid-type); font-size: calc(var(--side) * .115); line-height: 1.14; text-wrap: balance; transform: rotate(-2deg);
}
.exhibit.center { font-family: var(--type); font-weight: 700; }
.claim .plea { margin: 4px 0 0; max-width: 30ch; text-wrap: balance; text-shadow: 0 1px 0 rgba(0, 0, 0, .6); }
.claim .plea b { font-weight: 400; color: var(--gold-soft); }
.claim .row { display: flex; flex-direction: column; gap: 10px; width: min(100%, 300px); }
/* A phone on its side: the square on the left, the question on the right. */
@media (max-height: 560px) and (orientation: landscape) {
  dialog.claim[open] { grid-template-columns: auto minmax(0, 300px); column-gap: 28px; row-gap: 10px; align-items: center; }
  .claim .exhibit { --side: min(72vh, 340px); grid-column: 1; grid-row: 1 / span 3; }
  .claim .tonight, .claim .plea, .claim .row { grid-column: 2; }
}
.cell.pending span { opacity: .12; } /* its square is out of the card, in your hand */
.cell.pending { outline: 1.5px dashed var(--blood); outline-offset: -5px; }

/* ---------- Checking a card by its code ---------- */
dialog.lookup { width: min(92vw, 380px); max-height: 92dvh; padding: 0; border: 0; background: none; overflow: visible; }
dialog.lookup::backdrop { background: rgba(8, 18, 13, .78); }
.lookup .slip { width: 100%; max-height: 92dvh; overflow-y: auto; }
.lookup .hint { margin: 0; font-size: 14px; color: var(--ink-soft); }
.lookup .row { display: flex; gap: 8px; flex-wrap: wrap; }
.mini { container-type: inline-size; }
.mini .cell { cursor: default; }
.mini .grid { background: rgba(255, 252, 240, .35); }

/* ---------- The letter from J.B. ---------- */
dialog.rules {
  width: 100%; max-width: 560px; height: 100%; max-height: 100%; margin: 0 auto; padding: 0; border: 0;
  background: var(--fibre), var(--mottle), #f6f1e3; color: var(--ink); color-scheme: light; overflow-x: hidden; overflow-y: auto;
}
dialog.rules::backdrop { background: rgba(8, 18, 13, .8); }
.rules .top { position: sticky; top: 0; z-index: 3; display: flex; justify-content: flex-end; padding: max(8px, env(safe-area-inset-top, 0px)) 12px 8px; background: #f6f1e3; border-bottom: 1px solid rgba(29, 27, 24, .15); }
.rules .top .btn { color: var(--ink); background: #f6f1e3; border-color: var(--ink); box-shadow: 0 3px 8px rgba(0, 0, 0, .18); text-shadow: none; }
.rules .tonight { color: var(--ink-soft); margin-top: 14px; }
.rules h2 { margin: 2px 0 14px; text-align: center; font-family: var(--display); font-weight: 400; font-size: clamp(40px, 13vw, 64px); line-height: 1.1; }
.paper {
  position: relative; z-index: 2; margin: 0 20px; padding: 26px 22px 30px; background: var(--fibre), #fffdf7;
  box-shadow: 0 -2px 6px rgba(0, 0, 0, .12), 0 1px 1px rgba(0, 0, 0, .25), 0 12px 24px rgba(0, 0, 0, .3);
  font-size: 15px; line-height: 1.5; display: flex; flex-direction: column; gap: 12px;
}
.paper p { margin: 0; }
.paper p + p:not(.sign) { text-indent: 1.4em; }
.paper .sign { align-self: flex-end; margin-top: 10px; }
.typewriter { display: block; position: relative; z-index: 1; width: 100%; height: auto; margin: -15% 0 0; mix-blend-mode: multiply; }

/* ---------- Set pieces ---------- */

/* Cuts between screens, borrowed from old television. Both close over the screen, swap it, then open.
   The iris is the rim of a magnifying glass (--r is the radius of the hole); the clock wipe sweeps
   round like a second hand (--a is how far it has turned). */
@property --r { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --a { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
.cut { position: fixed; inset: 0; z-index: 40; pointer-events: none; }
.cut.iris {
  --r: 0px;
  background: radial-gradient(circle at 50% 44%,
    transparent calc(var(--r) - 16px), #0a0a0a calc(var(--r) - 15px), #1b1b1b calc(var(--r) - 5px), #000 calc(var(--r) - 3px),
    var(--gold) calc(var(--r) - 2px), var(--gold) var(--r), var(--table-deep) calc(var(--r) + 1px));
}
.cut.iris.close { animation: iris-close .55s cubic-bezier(.4, 0, .7, .6) forwards; }
.cut.iris.open { animation: iris-open .8s cubic-bezier(.3, .3, .4, 1) .12s both; }
/* 80vmax is just past the corners of any screen */
@keyframes iris-close { from { --r: 80vmax; } to { --r: 0px; } }
@keyframes iris-open { from { --r: 0px; } to { --r: 80vmax; } }
.cut.clock.close {
  background: conic-gradient(at 50% 44%, var(--table-deep) calc(var(--a) - 1.6deg), var(--gold) calc(var(--a) - 1.6deg), var(--gold) var(--a), transparent var(--a));
  animation: sweep .5s linear forwards;
}
.cut.clock.open {
  background: conic-gradient(at 50% 44%, transparent var(--a), var(--gold) var(--a), var(--gold) calc(var(--a) + 1.6deg), var(--table-deep) calc(var(--a) + 1.6deg));
  animation: sweep .55s linear .08s both;
}
@keyframes sweep { from { --a: 0deg; } to { --a: 363deg; } }

/* BINGO: lightning, a headline that spins in under marquee bulbs, turning gold rays and a fall of sequins. Tap to dismiss. */
.fanfare { position: fixed; inset: 0; z-index: 50; overflow: hidden; display: grid; place-items: center; background: rgba(6, 14, 10, .8); cursor: pointer; }
.fanfare .rays {
  position: absolute; left: 50%; top: 50%; width: 220vmax; height: 220vmax; margin: -110vmax 0 0 -110vmax;
  background: repeating-conic-gradient(rgba(230, 207, 141, .28) 0 5deg, transparent 5deg 15deg);
  -webkit-mask: radial-gradient(circle, #000, transparent 38%); mask: radial-gradient(circle, #000, transparent 38%);
}
.fanfare .flash { position: absolute; inset: 0; background: #fffbe8; opacity: 0; }
.fanfare .slam {
  position: relative; transform: rotate(-8deg); padding: .05em .28em .1em; text-align: center;
  font-family: var(--display); font-size: min(21vw, 140px); line-height: 1; color: var(--blood);
  background: var(--paper-tex), var(--parchment);
  border: 6px double var(--blood); outline: 8px solid var(--parchment);
  box-shadow: 0 2px 2px 8px rgba(0, 0, 0, .4), 0 30px 60px 8px rgba(0, 0, 0, .6), inset 0 0 40px rgba(120, 96, 40, .2);
}
.fanfare .slam::before, .fanfare .slam::after { /* two interleaved strings of bulbs */
  content: ""; position: absolute; inset: -26px; border: 8px dotted var(--gold-soft); border-radius: 10px;
  filter: drop-shadow(0 0 5px rgba(255, 230, 150, .9));
}
.fanfare .slam::after { inset: -27px -30px -25px -22px; border-color: #fff6cf; opacity: 0; }
.fanfare .slam small { display: block; font-family: var(--type); font-size: max(13px, .11em); letter-spacing: .08em; padding-bottom: .5em; }
.sequins { position: absolute; inset: 0; }
.sequin {
  position: absolute; top: -4%; width: var(--s, 10px); height: var(--s, 10px); border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #fffbe0, var(--gold) 45%, #7a5a16);
  box-shadow: 0 0 6px rgba(255, 240, 180, .5);
}
.fanfare.out { opacity: 0; transition: opacity .35s; }

/* A murder!: three crash zooms on the centre square, each with a flash of lightning; the room turns red and
   goes to letterbox; ink bursts from the square and the words land across the screen. Then it lets go. */
.dread { position: fixed; inset: 0; z-index: 30; pointer-events: none; opacity: 0;
  background: radial-gradient(ellipse at center, rgba(110, 0, 0, .12) 20%, rgba(110, 0, 0, .7) 100%); }
.dread::before { content: ""; position: absolute; inset: 0; background: #fff8e0; opacity: 0; }
.dread::after { content: ""; position: absolute; inset: 0; background: repeating-linear-gradient(rgba(0, 0, 0, .22) 0 1px, transparent 1px 3px); }
.snap .dread { opacity: 1; background: none; }
.snap .dread::after { display: none; }
.scream { position: fixed; inset: 0; z-index: 32; pointer-events: none; visibility: hidden; overflow: hidden; display: grid; place-items: end center; }
.scream::before, .scream::after { content: ""; position: absolute; left: 0; right: 0; height: 12vh; background: #000; }
.scream::before { top: 0; transform: translateY(-101%); }
.scream::after { bottom: 0; transform: translateY(101%); }
.scream b {
  position: relative; z-index: 1; margin-bottom: 17vh; transform: rotate(-6deg); white-space: nowrap; opacity: 0;
  font-family: var(--display); font-weight: 400; font-size: min(21vw, 132px); line-height: 1; color: var(--cream);
  text-shadow: 3px 3px 0 var(--blood), 6px 6px 0 rgba(0, 0, 0, .65), 0 0 40px rgba(255, 60, 60, .5);
}
.murder .scream { visibility: visible; }
.murder .card-shadow { z-index: 20; }
.splat {
  position: absolute; left: 50%; top: 50%; width: 78%; aspect-ratio: 1; translate: -50% -50%; z-index: 2; pointer-events: none;
  background: var(--blood); opacity: 0; mix-blend-mode: multiply;
  --splat: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cg fill='black'%3E%3Ccircle cx='100' cy='100' r='30'/%3E%3Ccircle cx='82' cy='92' r='21'/%3E%3Ccircle cx='119' cy='108' r='23'/%3E%3Ccircle cx='105' cy='79' r='19'/%3E%3Ccircle cx='91' cy='121' r='19'/%3E%3Ccircle cx='41' cy='60' r='7'/%3E%3Ccircle cx='161' cy='52' r='9'/%3E%3Ccircle cx='171' cy='121' r='6'/%3E%3Ccircle cx='31' cy='131' r='8'/%3E%3Ccircle cx='70' cy='171' r='7'/%3E%3Ccircle cx='141' cy='166' r='10'/%3E%3Ccircle cx='100' cy='21' r='6'/%3E%3Ccircle cx='187' cy='88' r='4'/%3E%3Ccircle cx='17' cy='96' r='4'/%3E%3Ccircle cx='60' cy='27' r='4'/%3E%3Ccircle cx='151' cy='23' r='5'/%3E%3Ccircle cx='120' cy='187' r='4'/%3E%3C/g%3E%3Cg fill='black'%3E%3Cpath d='M96 86L108 100L44 61zM98 86L110 104L158 55zM108 96L94 110L139 162zM90 104L108 108L71 167zM90 96L110 96L100 26zM104 96L104 114L168 120zM98 92L98 112L35 128z'/%3E%3C/g%3E%3C/svg%3E");
  -webkit-mask: var(--splat) center / contain no-repeat; mask: var(--splat) center / contain no-repeat;
}

/* Clearing the card rewinds the tape. */
.rewind {
  position: fixed; inset: 0; z-index: 35; pointer-events: none; visibility: hidden; overflow: hidden;
  background: repeating-linear-gradient(rgba(255, 255, 255, .07) 0 2px, transparent 2px 5px);
}
.rewind::before, .rewind::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 10vh;
  background: linear-gradient(transparent, rgba(255, 255, 255, .5) 30%, rgba(255, 255, 255, .12) 55%, rgba(0, 0, 0, .35) 75%, transparent);
}
.rewind b {
  position: absolute; left: 18px; top: calc(74px + env(safe-area-inset-top, 0px)); font: 700 22px/1 "Courier New", Courier, monospace; letter-spacing: .14em;
  color: #fff; text-shadow: 2px 0 rgba(255, 0, 200, .7), -2px 0 rgba(0, 240, 255, .7);
}
.rewinding .rewind { visibility: visible; }

/* ---------- Motion ---------- */
@media (prefers-reduced-motion: no-preference) {
  /* The front door assembles itself like a title card. */
  .door.enter > * { animation: rise .55s ease-out both; }
  .door.enter > .tonight { animation-delay: .05s; }
  .door.enter > .title { animation: title-in .8s cubic-bezier(.2, .8, .2, 1) .15s both, glint 7s ease-in-out 2s infinite; }
  .door > .title { animation: glint 7s ease-in-out 2s infinite; }
  @keyframes glint { 0%, 55% { background-position: 160% 0, 0 0; } 85%, 100% { background-position: -60% 0, 0 0; } }
  .door.enter > .arch { animation: arch-in .9s cubic-bezier(.3, .7, .2, 1) .35s both; }
  .door.enter > .pitch { animation-delay: .75s; }
  .door.enter > .fleuron { animation-delay: .82s; }
  .door.enter > .choices { animation: none; }
  .door.enter > .choices > * { animation: rise .45s ease-out both; }
  .door.enter > .choices > :nth-child(1) { animation-delay: .9s; }
  .door.enter > .choices > :nth-child(2) { animation-delay: 1s; }
  .door.enter > .choices > :nth-child(3) { animation-delay: 1.1s; }
  .door.enter > .choices > :nth-child(4) { animation-delay: 1.2s; }
  .door.enter > .link { animation-delay: 1.25s; }
  @keyframes rise { from { opacity: 0; transform: translateY(10px); } }
  @keyframes title-in { from { opacity: 0; transform: scale(1.14); filter: blur(7px); } to { filter: drop-shadow(0 2px 0 rgba(0, 0, 0, .5)) drop-shadow(0 10px 16px rgba(0, 0, 0, .45)); } }
  @keyframes arch-in { from { clip-path: circle(0% at 50% 62%); transform: scale(.9); } to { clip-path: circle(90% at 50% 62%); } }
  .slip { animation: unfold .4s cubic-bezier(.2, .9, .3, 1.1) both; transform-origin: 50% 0; }
  @keyframes unfold { from { transform: perspective(700px) rotateX(-85deg); opacity: 0; } }

  /* A card lands on the table; its squares are then typed in, row by row, and the lace unrolls. */
  .play.enter .bar, .play.enter .actions, .play.enter .status { animation: rise .5s ease-out .2s both; }
  .card-shadow.dealt { animation: deal .46s cubic-bezier(.2, .9, .3, 1.15) both; }
  .card-shadow.dealt.start { animation: toss .8s cubic-bezier(.2, .85, .25, 1.12) .3s both; --lead: .75s; }
  .card-shadow.leaving { animation: flick .26s ease-in forwards; }
  @keyframes deal { from { transform: translate(60%, 30px) rotate(9deg); opacity: 0; } }
  @keyframes toss {
    0% { transform: translate(34%, 70vh) rotate(26deg) scale(1.1); opacity: 0; }
    25% { opacity: 1; }
    70% { transform: translate(-1%, -6px) rotate(calc(var(--tilt, 0deg) - 2.2deg)); }
    100% { transform: rotate(var(--tilt, 0deg)); }
  }
  @keyframes flick { to { transform: translate(-130%, -6%) rotate(-24deg); opacity: 0; } }
  .dealt .cell span { animation: typed .01s linear both; animation-delay: calc(var(--lead, .18s) + var(--i) * 22ms); }
  @keyframes typed { from { opacity: 0; } }
  .dealt .card-title { animation: rise .5s ease-out both; animation-delay: var(--lead, .1s); }
  .dealt .lace { animation: unroll .6s ease-out both; animation-delay: calc(var(--lead, .18s) + .45s); }
  @keyframes unroll { from { clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 -5% -60% 0); } }
  .dealt .card-no { animation: rise .4s ease-out both; animation-delay: calc(var(--lead, .18s) + .9s); }

  .cell { transition: background-color .2s; }
  .cell.on::before { animation: press .18s ease-out; }
  @keyframes press { from { scale: 1.6; opacity: 0; } }
  .cell.ghost::before, .cell.lifting::before { animation: lift .3s ease-in var(--d, 0s) forwards; }
  @keyframes lift { to { scale: 1.7; opacity: 0; } }
  .cell.pop { animation: pop .34s cubic-bezier(.2, 1.5, .4, 1) both; animation-delay: var(--d, 0s); }
  @keyframes pop { from { background-color: transparent; color: var(--ink); scale: .82; } }
  .bingo { animation: thump .38s cubic-bezier(.2, 1.6, .4, 1); }
  @keyframes thump { from { transform: scale(1.5) rotate(-6deg); opacity: 0; } }
  .status.tick { animation: rise .3s ease-out; }

  /* The deciding square lifts out of the card (from --fx, --fy at scale --fs), sways while it is shown round, and goes back if withdrawn. */
  dialog.claim[open] .exhibit { animation: lift-out .5s cubic-bezier(.2, .9, .3, 1.12) both, sway 3s ease-in-out .5s infinite; }
  @keyframes lift-out { from { transform: translate(var(--fx, 0), var(--fy, 0)) scale(var(--fs, .2)); box-shadow: none; outline-width: 0; } }
  @keyframes sway { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2.5deg) translateY(-4px); } }
  dialog.claim[open]::backdrop { animation: fade .35s; }
  dialog.claim[open] .tonight, dialog.claim[open] .plea, dialog.claim[open] .row { animation: rise .4s ease-out .3s both; }
  dialog.claim.closing .exhibit { animation: put-back .26s ease-in forwards; }
  @keyframes put-back { to { transform: translate(var(--fx, 0), var(--fy, 0)) scale(var(--fs, .2)); box-shadow: none; outline-width: 0; } }
  dialog.claim.closing .tonight, dialog.claim.closing .plea, dialog.claim.closing .row, dialog.claim.closing::backdrop { animation: fade-out .22s forwards; }

  /* The letter feeds up out of the typewriter, and is typed a paragraph at a time. */
  dialog.rules[open] { animation: feed .6s cubic-bezier(.2, .9, .3, 1.04); }
  dialog.rules[open]::backdrop, dialog.lookup[open]::backdrop { animation: fade .4s; }
  dialog.rules.closing { animation: unfeed .32s ease-in forwards; }
  @keyframes feed { from { transform: translateY(100%); } }
  @keyframes unfeed { to { transform: translateY(100%); } }
  @keyframes fade { from { opacity: 0; } }
  dialog.rules[open] .tonight, dialog.rules[open] h2 { animation: rise .5s ease-out .3s both; }
  dialog.rules[open] .paper { animation: paper-up .7s cubic-bezier(.2, .9, .3, 1) .25s both; }
  @keyframes paper-up { from { transform: translateY(60px); opacity: 0; } }
  dialog.rules[open] .paper > p { animation: typed-line .45s ease-out both; }
  dialog.rules[open] .paper > :nth-child(1) { animation-delay: .6s; } dialog.rules[open] .paper > :nth-child(2) { animation-delay: .75s; }
  dialog.rules[open] .paper > :nth-child(3) { animation-delay: .9s; } dialog.rules[open] .paper > :nth-child(4) { animation-delay: 1.05s; }
  dialog.rules[open] .paper > :nth-child(5) { animation-delay: 1.2s; } dialog.rules[open] .paper > :nth-child(6) { animation-delay: 1.35s; }
  dialog.rules[open] .paper > :nth-child(7) { animation-delay: 1.5s; } dialog.rules[open] .paper > :nth-child(8) { animation-delay: 1.65s; }
  @keyframes typed-line { from { opacity: 0; clip-path: inset(0 100% 0 0); } to { clip-path: inset(0 -2% -10% -2%); } }
  dialog.lookup.closing { animation: fade-out .2s forwards; }
  dialog.rules.closing::backdrop { animation: fade-out .32s ease-in forwards; }
  dialog.lookup.closing::backdrop { animation: fade-out .2s forwards; }
  @keyframes fade-out { to { opacity: 0; } }

  /* BINGO */
  .fanfare .flash { animation: lightning .7s linear; }
  @keyframes lightning { 0% { opacity: .95; } 12% { opacity: 0; } 20% { opacity: .7; } 34% { opacity: 0; } 46% { opacity: .35; } 100% { opacity: 0; } }
  .fanfare .rays { animation: spin 26s linear infinite, bloom .9s ease-out both; }
  @keyframes spin { to { rotate: 360deg; } }
  @keyframes bloom { from { opacity: 0; scale: .4; } }
  .fanfare .slam { animation: headline .85s cubic-bezier(.2, .7, .2, 1) .05s both; }
  @keyframes headline { from { transform: rotate(-1088deg) scale(.03); opacity: 0; } 30% { opacity: 1; } 78% { transform: rotate(-8deg) scale(1.12); } }
  .fanfare .slam::before { animation: bulbs .5s steps(1) infinite; }
  .fanfare .slam::after { animation: bulbs .5s steps(1) -.25s infinite; }
  @keyframes bulbs { 0% { opacity: 1; } 50% { opacity: .15; } }
  .fanfare:not([hidden]) ~ .play { animation: quake .45s linear .7s; }
  .sequin { animation: fall var(--t, 2.2s) cubic-bezier(.3, .1, .7, 1) var(--w, 0s) both, twinkle .5s ease-in-out infinite alternate; }
  @keyframes fall { to { transform: translate(var(--x, 0), 112vh) rotate(540deg); } }
  @keyframes twinkle { to { filter: brightness(1.7); } }
  @keyframes quake {
    10% { transform: translate(-5px, 2px) rotate(-.8deg); } 25% { transform: translate(6px, -3px) rotate(.9deg); }
    40% { transform: translate(-6px, -2px) rotate(-.7deg); } 55% { transform: translate(4px, 3px) rotate(.5deg); }
    70% { transform: translate(-3px, 1px) rotate(-.3deg); } 85% { transform: translate(2px, -1px); }
  }

  /* A murder! (2.3 seconds) */
  .murder .card { animation: crash 2.3s linear both; transform-origin: 50% 54%; }
  @keyframes crash {
    0% { transform: none; }
    2.5%, 11% { transform: scale(1.2) rotate(-1deg); }
    13.5%, 22% { transform: scale(1.44) rotate(1.3deg); }
    24.5% { transform: scale(1.72) rotate(-1.6deg); }
    27% { transform: scale(1.72) rotate(-1.6deg) translate(-5px, 3px); } 29.5% { transform: scale(1.72) rotate(-1.6deg) translate(5px, -3px); }
    32% { transform: scale(1.72) rotate(-1.6deg) translate(-3px, -2px); } 34.5% { transform: scale(1.72) rotate(-1.6deg) translate(2px, 2px); }
    37%, 74% { transform: scale(1.72) rotate(-1.6deg); animation-timing-function: cubic-bezier(.5, 0, .3, 1); }
    100% { transform: none; }
  }
  .murder .dread { animation: dread 2.3s linear; }
  @keyframes dread { 0% { opacity: 0; } 2.5% { opacity: .55; } 11% { opacity: .3; } 13.5% { opacity: .8; } 22% { opacity: .5; } 24.5%, 74% { opacity: 1; } 100% { opacity: 0; } }
  .murder .dread::before { animation: thrice 2.3s linear; }
  @keyframes thrice { 0% { opacity: .9; } 4% { opacity: 0; } 11% { opacity: 0; } 11.5% { opacity: .9; } 15.5% { opacity: 0; } 22% { opacity: 0; } 22.5% { opacity: 1; } 30% { opacity: 0; } 100% { opacity: 0; } }
  .murder .scream::before { animation: bar-top 2.3s cubic-bezier(.2, .8, .2, 1); }
  .murder .scream::after { animation: bar-bottom 2.3s cubic-bezier(.2, .8, .2, 1); }
  @keyframes bar-top { 0% { transform: translateY(-101%); } 12%, 78% { transform: none; } 100% { transform: translateY(-101%); } }
  @keyframes bar-bottom { 0% { transform: translateY(101%); } 12%, 78% { transform: none; } 100% { transform: translateY(101%); } }
  .murder .scream b { animation: scream 2.3s linear; }
  @keyframes scream {
    0%, 25% { opacity: 0; transform: rotate(-6deg) scale(3.2); }
    29% { opacity: 1; transform: rotate(-6deg) scale(.94); } 32% { transform: rotate(-6deg) scale(1.07); } 35% { transform: rotate(-6deg) scale(1); }
    76% { opacity: 1; transform: rotate(-6deg) scale(1.05); } 100% { opacity: 0; transform: rotate(-6deg) scale(1.1); }
  }
  .murder .splat { animation: splat 2.3s linear both; }
  @keyframes splat { 0%, 23% { opacity: 0; scale: .1; } 27% { opacity: .92; scale: 1; } 74% { opacity: .78; scale: 1.06; } 100% { opacity: 0; scale: 1.1; } }
  /* Review aid: freeze the murder at its peak. */
  .murder.hold .card, .murder.hold .dread, .murder.hold .dread::before, .murder.hold .scream::before, .murder.hold .scream::after,
  .murder.hold .scream b, .murder.hold .splat { animation-delay: -1.1s; animation-play-state: paused; }

  /* Camera flash for "Send the evidence" */
  .snap .dread::before { animation: flashbulb .5s ease-out; }
  @keyframes flashbulb { from { opacity: .9; } to { opacity: 0; } }

  /* Rewind */
  .rewinding .rewind { animation: flicker .12s steps(2) infinite; }
  .rewinding .rewind::before { animation: roll .38s linear infinite; }
  .rewinding .rewind::after { animation: roll .38s linear -.19s infinite; }
  .rewinding .card-shadow { animation: tracking .75s linear; }
  @keyframes flicker { 50% { opacity: .75; } }
  @keyframes roll { to { top: -12vh; } }
  @keyframes tracking {
    10% { transform: rotate(var(--tilt, 0deg)) translateX(-7px) skewX(-3deg); } 25% { transform: rotate(var(--tilt, 0deg)) translateX(8px) skewX(4deg); }
    40% { transform: rotate(var(--tilt, 0deg)) translateX(-4px) skewX(-2deg); } 55% { transform: rotate(var(--tilt, 0deg)) translateX(6px) skewX(3deg); }
    70% { transform: rotate(var(--tilt, 0deg)) translateX(-3px) skewX(-1deg); } 85% { transform: rotate(var(--tilt, 0deg)) translateX(2px); }
  }
}

/* ---------- Opening titles (first visit in a while) ---------- */
.titles {
  position: fixed; inset: 0; z-index: 60; cursor: pointer; padding: 24px; text-align: center;
  display: grid; place-content: center; justify-items: center; gap: 22px;
  background: radial-gradient(90% 60% at 50% 40%, #16261c, #060a07 75%);
}
.titles .sheet {
  width: min(84vw, 380px); min-height: 3.2em; padding: .55em .6em .7em; text-align: left; white-space: pre;
  font: 700 clamp(30px, 10.4vw, 50px)/1.2 var(--type); color: var(--ink);
  background: var(--fibre), #fffdf7; transform: rotate(-1.6deg); transition: transform .16s ease-out;
  box-shadow: 0 1px 1px rgba(0, 0, 0, .4), 0 24px 50px rgba(0, 0, 0, .7);
}
.titles .sheet .red { color: var(--blood); }
.titles .caret { display: inline-block; width: .5em; height: .09em; margin-left: .04em; background: currentColor; }
.titles .billing { margin: 0; min-height: 4.6em; opacity: 0; transition: opacity .35s; color: var(--cream); font-size: 15px; }
.titles .billing.show { opacity: 1; }
.titles .billing b { display: block; font: 700 26px/1.25 var(--type); color: #f6e27a; text-shadow: 2px 2px 0 #000; }
.titles .billing small { display: block; font-size: 12px; letter-spacing: .2em; text-transform: uppercase; }
.titles > small, .credits > small { position: absolute; left: 0; right: 0; bottom: max(14px, env(safe-area-inset-bottom, 0px)); font-size: 12px; letter-spacing: .12em; color: var(--cream-faint); opacity: .7; }
.titles.out { opacity: 0; transition: opacity .3s; }

/* ---------- Freeze frame and end credits (first bingo on a card) ---------- */
.frozen .play, .frozen footer { filter: grayscale(.75) sepia(.35) brightness(.6) contrast(1.15); }
.frozen .play { transform: scale(1.035); }
.frozen .play *, .frozen .play *::before, .frozen .play *::after { animation-play-state: paused !important; }
.credits {
  position: fixed; inset: 0; z-index: 55; overflow: hidden; cursor: pointer; text-align: center;
  background: linear-gradient(rgba(0, 0, 0, .7), rgba(0, 0, 0, .28) 22%, rgba(0, 0, 0, .28) 75%, rgba(0, 0, 0, .75));
}
.credits .roll { position: absolute; left: 0; right: 0; top: 80%; padding: 0 22px 8vh; }
.credits h3 { margin: 0 0 6px; font-family: var(--display); font-weight: 400; font-size: clamp(40px, 13vw, 64px); line-height: 1.1; color: #f6e27a; text-shadow: 3px 3px 0 #000; }
.credits .cr { margin: 0 auto 30px; max-width: 30ch; text-shadow: 2px 2px 0 #000, 0 0 12px rgba(0, 0, 0, .8); }
.credits .cr small { display: block; font-size: 12px; letter-spacing: .2em; text-transform: uppercase; color: var(--cream); }
.credits .cr b { display: block; font: 700 22px/1.25 var(--type); color: #f6e27a; text-wrap: balance; }
.credits .cr i { display: block; font-style: normal; font-size: 15px; color: var(--cream); text-wrap: balance; }
.credits .cr.big b { font-size: 30px; }
.credits .cr.end { margin-top: 60px; }
.credits.out { opacity: 0; transition: opacity .35s; }

@media (prefers-reduced-motion: no-preference) {
  .titles .caret { animation: caret .5s steps(1) infinite; }
  @keyframes caret { 50% { opacity: 0; } }
  .titles .tonight { animation: rise .6s ease-out both; }
  .frozen .play, .frozen footer { transition: filter .1s, transform .1s; }
  .credits { animation: fade .5s; }
  .credits .roll { animation: roll-up var(--dur, 14s) linear .9s forwards; }
  @keyframes roll-up { to { transform: translateY(calc(-100% - 80vh)); } }
  /* The lighthouse lamp turns. */
  .fleuron.light { animation: lamp 5s ease-in-out infinite; }
  @keyframes lamp { 50% { filter: brightness(1.35) drop-shadow(0 0 6px rgba(255, 236, 170, .45)); } }
}


/* ---------- The magnifying glass: hold a square (or hover with a mouse) to read it large ---------- */
.loupe {
  position: fixed; left: 0; top: 0; z-index: 25; pointer-events: none; --d: min(66vw, 250px);
  width: var(--d); height: var(--d); transform: translate(var(--x, 0), var(--y, 0));
}
.loupe .handle { /* points from the glass towards the finger */
  position: absolute; left: 50%; top: 50%; width: 11%; height: 88%; margin-left: -5.5%; transform-origin: 50% 0; rotate: var(--h, -35deg);
  border-radius: 0 0 999px 999px; background: linear-gradient(90deg, #070707, #2e2e2e 45%, #0b0b0b); box-shadow: 0 10px 20px rgba(0, 0, 0, .5);
}
.loupe .lens {
  position: absolute; inset: 0; border-radius: 50%; display: grid; place-items: center; padding: 15%; text-align: center;
  color: var(--ink); font-family: var(--grid-type); font-size: calc(var(--d) * .105); line-height: 1.14; text-wrap: balance;
  background: radial-gradient(circle at 30% 24%, rgba(255, 255, 255, .6), transparent 40%), var(--paper-tex), var(--parchment);
  border: calc(var(--d) * .045) solid #131313;
  box-shadow: 0 0 0 1.5px var(--gold), inset 0 0 0 1.5px rgba(205, 170, 82, .75), inset 0 0 28px rgba(0, 0, 0, .35), 0 18px 40px rgba(0, 0, 0, .6);
}
.loupe.center .lens { font-family: var(--type); font-weight: 700; }
.loupe.on .lens { background: radial-gradient(circle at 30% 24%, rgba(255, 255, 255, .6), transparent 40%), linear-gradient(rgba(163, 22, 28, .14), rgba(163, 22, 28, .14)), var(--paper-tex), var(--parchment); }
.loupe.win .lens { background: var(--blood); color: var(--parchment); }
.cell.peeked { background-color: rgba(255, 252, 240, .65); box-shadow: inset 0 0 0 2px var(--ink); }
.grid { -webkit-touch-callout: none; }
@media (prefers-reduced-motion: no-preference) {
  .loupe.show { animation: loupe-in .16s cubic-bezier(.2, .9, .3, 1.2); }
  @keyframes loupe-in { from { opacity: 0; scale: .5; } }
}

/* ---------- Rooms ---------- */
/* In the top bar: the room's code and head-count. Opens the cast list. */
.roombar {
  flex: 1; min-width: 0; margin: 0 4px; padding: 6px 4px; min-height: 44px; border: 0; border-radius: 8px; background: none; cursor: pointer;
  font: inherit; font-size: 14px; line-height: 1; color: var(--cream); text-shadow: 0 1px 0 rgba(0, 0, 0, .55); text-align: center; white-space: nowrap;
}
.roombar b { font-weight: 700; font-size: 24px; letter-spacing: .16em; color: var(--gold-soft); vertical-align: -2px; margin-right: 2px; }
@media (max-width: 379px) { .roombar .more { display: none; } .roombar b { font-size: 21px; } }
/* The code in the cast list: four tiles, big enough to read out across a room. */
.roomcode { display: flex; justify-content: center; gap: 8px; margin: 2px 0 4px; }
.roomcode span {
  width: 1.5em; height: 1.6em; display: grid; place-items: center; font: 700 clamp(34px, 12vw, 46px)/1 var(--type); color: var(--ink);
  background: rgba(255, 252, 240, .55); border: 1.5px solid var(--ink); box-shadow: inset 0 0 0 3px var(--parchment), inset 0 0 0 4px var(--rule), 0 3px 6px rgba(0, 0, 0, .2);
}
.cast .slip > .tonight { color: var(--ink-soft); }
.cast .players { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.cast .players li { display: grid; grid-template-columns: 1fr auto; gap: 2px 10px; align-items: baseline; padding: 9px 0; border-top: 1px solid rgba(29, 27, 24, .25); }
.cast .players li:last-child { border-bottom: 1px solid rgba(29, 27, 24, .25); }
.cast .players .who { font-weight: 700; font-size: 17px; }
.cast .players .as { grid-column: 1; font-size: 13px; color: var(--ink-soft); }
.cast .players .how { grid-row: 1 / span 2; grid-column: 2; align-self: center; text-align: right; font-size: 13px; max-width: 13ch; }
.cast .players .how b { color: var(--blood); }
.cast .players .see { grid-column: 1 / -1; justify-self: start; font: inherit; font-size: 13px; color: var(--ink); background: none; border: 0; padding: 6px 0; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.cast .players li.away { opacity: .55; }
.mini .cell.on { background: var(--blood-tint); }

/* The murderer, named: a line on the bingo slip. */
.accuse { display: flex; gap: 8px; align-items: flex-end; margin-top: 12px; text-align: left; }
.accuse label { flex: 1; display: flex; flex-direction: column; gap: 2px; font-size: 13px; color: var(--ink-soft); }
.accuse input { font: inherit; font-size: 17px; color: var(--ink); background: transparent; border: 0; border-bottom: 1.5px solid var(--ink); border-radius: 0; padding: 4px 2px; min-height: 40px; width: 100%; }
.accuse input:focus-visible { outline: none; border-bottom: 2.5px solid var(--blood); background: rgba(255, 252, 240, .5); }
.bingo .accuse .btn { margin-top: 0; }

/* Telegrams: news from the room drops in at the top of the screen. */
.wire { position: fixed; z-index: 45; left: 0; right: 0; top: max(10px, env(safe-area-inset-top, 0px)); display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 0 12px; pointer-events: none; }
.telegram {
  pointer-events: auto; width: min(100%, 420px); padding: 10px 14px 12px; color: var(--ink); cursor: pointer;
  background: var(--fibre), #f3e3b5; border: 1px solid #b89a4e; box-shadow: 0 1px 1px rgba(0, 0, 0, .4), 0 14px 28px rgba(0, 0, 0, .5);
  font-family: "Courier New", Courier, monospace; font-size: 15px; line-height: 1.3; text-transform: uppercase;
}
.telegram small { display: block; margin-bottom: 4px; font-size: 10px; letter-spacing: .22em; color: var(--blood); border-bottom: 1px dashed rgba(29, 27, 24, .4); padding-bottom: 4px; }
.telegram button { display: inline-block; margin-top: 6px; font: inherit; font-size: 13px; color: var(--blood); background: none; border: 0; padding: 4px 0; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; text-transform: uppercase; }
.telegram.out { opacity: 0; transform: translateY(-12px); transition: opacity .3s, transform .3s; }
@media (prefers-reduced-motion: no-preference) {
  .telegram { animation: wire-in .45s cubic-bezier(.2, .9, .3, 1.15); }
  dialog.cast[open] .roomcode span { animation: tile-in .4s cubic-bezier(.2, .9, .3, 1.3) both; }
  dialog.cast[open] .roomcode span:nth-child(2) { animation-delay: .08s; } dialog.cast[open] .roomcode span:nth-child(3) { animation-delay: .16s; } dialog.cast[open] .roomcode span:nth-child(4) { animation-delay: .24s; }
  @keyframes tile-in { from { opacity: 0; transform: translateY(-14px) rotate(-8deg); } }
  @keyframes wire-in { from { transform: translateY(-140%) rotate(-3deg); opacity: 0; } }
}
