index.html

HTML structure
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Video Poker - Ten Play</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <main class="app">
    <section class="machine" aria-label="Video Poker machine">
      <header class="marquee">
        <div class="brand">
          <h1>Video Poker</h1>
        </div>
        <section class="meters" aria-label="Game totals">
          <div>
            <span>Credits</span>
            <strong id="credits">1000</strong>
          </div>
          <div>
            <span>Bet</span>
            <strong id="currentBet">60</strong>
          </div>
          <div>
            <span>Paid</span>
            <strong id="lastWin">0</strong>
          </div>
        </section>
      </header>

      <section class="pay-glass" aria-label="Main paytable">
        <div class="pay-title">Jacks or Better Bonus</div>
        <div id="paytableStrip" class="paytable-strip"></div>
      </section>

      <section class="screen">
        <div class="status-row">
          <div id="multiplierBadge" class="multiplier-badge idle">Multiplier eligible</div>
          <div>
            <p id="stateLabel" class="state-label">Ready</p>
            <p id="message" class="message">Choose your bet, then deal. No real money, just credits.</p>
          </div>
        </div>

        <section class="main-hand" aria-label="Base hand">
          <div id="baseHand" class="card-row"></div>
        </section>

        <section class="results-panel">
          <div class="section-heading">
            <h2>Ten Play</h2>
            <span id="resultSummary">Wins appear after draw</span>
          </div>
          <div id="finalHands" class="hands-grid" aria-live="polite"></div>
        </section>
      </section>

      <section class="controls" aria-label="Game controls">
        <button id="dealDrawButton" class="primary-button" type="button">Deal</button>

        <label>
          Coin
          <select id="betPerHand">
            <option value="1">1</option>
            <option value="2">2</option>
            <option value="3">3</option>
            <option value="4">4</option>
            <option value="5" selected>5</option>
          </select>
        </label>

        <label>
          Hands
          <select id="handCount">
            <option value="3">3</option>
            <option value="5">5</option>
            <option value="10" selected>10</option>
          </select>
        </label>

        <button id="paytableButton" class="secondary-button" type="button">Pays</button>
        <button id="resetButton" class="secondary-button" type="button">Reset</button>
      </section>
    </section>
  </main>

  <dialog id="paytableDialog">
    <div class="dialog-header">
      <h2>Paytable</h2>
      <button id="closePaytable" type="button" aria-label="Close paytable">&times;</button>
    </div>
    <div id="paytableList" class="paytable-list"></div>
  </dialog>

  <script src="engine.js"></script>
  <script src="app.js"></script>
</body>
</html>

styles.css

Responsive app styling
:root {
  color-scheme: dark;
  --cabinet: #07124a;
  --cabinet-2: #113e96;
  --screen: #061833;
  --screen-2: #082b58;
  --pay-red: #ba1727;
  --pay-blue: #132d82;
  --gold: #ffd34f;
  --gold-2: #ad7b18;
  --white: #fff7db;
  --ink: #f9f2cc;
  --muted: #b9c6e8;
  --line: rgba(255, 232, 116, .42);
  --red: #d7192f;
  --black-card: #111111;
  --paper: #fffaf0;
  --button-red: #b91521;
  --button-blue: #0b64b6;
}

* {
  box-sizing: border-box;
}

html,
body {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

body {
  margin: 0;
  font-family: "Arial Black", Impact, "Segoe UI", system-ui, sans-serif;
  color: var(--ink);
  background:
    linear-gradient(90deg, rgba(255, 255, 255, .04) 1px, transparent 1px),
    radial-gradient(circle at 50% 0%, rgba(255, 211, 79, .22), transparent 30rem),
    linear-gradient(160deg, #050815, #0a1640 48%, #040610);
  background-size: 28px 28px, auto, auto;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

button,
select {
  font: inherit;
}

button {
  cursor: pointer;
}

button:disabled,
select:disabled {
  cursor: not-allowed;
  opacity: .58;
}

h1,
h2,
p {
  margin: 0;
}

.app {
  width: 100vw;
  height: 100vh;
  height: 100svh;
  max-height: 100vh;
  max-height: 100svh;
  display: grid;
  place-items: center;
  overflow: hidden;
  padding: clamp(4px, 1.5vmin, 14px);
}

.machine {
  width: min(1280px, 100%);
  height: 100%;
  min-height: 0;
  display: grid;
  grid-template-rows: clamp(62px, 12svh, 112px) clamp(38px, 6.5svh, 58px) minmax(0, 1fr) clamp(60px, 10.5svh, 88px);
  gap: clamp(4px, .8vmin, 10px);
  padding: clamp(6px, 1.2vmin, 14px);
  border: clamp(3px, .7vmin, 8px) solid var(--gold-2);
  border-radius: 8px;
  background:
    linear-gradient(90deg, rgba(255, 255, 255, .13), transparent 7%, transparent 93%, rgba(255, 255, 255, .11)),
    linear-gradient(155deg, var(--cabinet), var(--cabinet-2) 54%, #050929);
  box-shadow:
    inset 0 0 0 2px rgba(255, 245, 181, .36),
    inset 0 0 38px rgba(0, 0, 0, .55),
    0 16px 60px rgba(0, 0, 0, .45);
}

.machine > * {
  min-height: 0;
}

.marquee {
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(270px, .72fr);
  gap: clamp(8px, 1.5vmin, 18px);
  align-items: center;
  padding: clamp(8px, 1.4vmin, 16px);
  border: 2px solid var(--line);
  border-radius: 7px;
  background:
    repeating-linear-gradient(90deg, rgba(255, 211, 79, .13) 0 2px, transparent 2px 12px),
    linear-gradient(180deg, #1c48a3, #07195c 58%, #020a34);
}

.eyebrow {
  color: #ffef9a;
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(.58rem, 1.35vmin, .82rem);
  font-weight: 900;
  letter-spacing: 0;
  text-transform: uppercase;
}

h1 {
  color: var(--gold);
  font-size: clamp(1.35rem, 5.1vmin, 4.15rem);
  line-height: .86;
  letter-spacing: 0;
  text-shadow:
    0 2px 0 #801318,
    0 4px 0 #2b0815,
    0 0 18px rgba(255, 211, 79, .55);
  text-transform: uppercase;
}

.meters {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(4px, .8vmin, 8px);
}

.meters div {
  min-width: 0;
  padding: clamp(6px, 1vmin, 12px);
  border: 2px solid rgba(255, 211, 79, .76);
  border-radius: 5px;
  color: #111;
  background: linear-gradient(180deg, #fff0a4, #e1a726 50%, #7b4204);
  box-shadow: inset 0 1px rgba(255, 255, 255, .75);
  text-align: center;
}

.meters span,
label,
.section-heading span,
.message {
  font-family: "Segoe UI", system-ui, sans-serif;
}

.meters span {
  display: block;
  color: #301a00;
  font-size: clamp(.56rem, 1.2vmin, .74rem);
  font-weight: 900;
  text-transform: uppercase;
}

.meters strong {
  display: block;
  margin-top: 1px;
  color: #101010;
  font-size: clamp(1rem, 2.5vmin, 1.65rem);
  line-height: 1;
}

.pay-glass {
  min-height: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: stretch;
  overflow: hidden;
  border: 2px solid var(--line);
  border-radius: 6px;
  background: linear-gradient(180deg, #fbe476, #b2182a 13%, #560813 100%);
  box-shadow: inset 0 0 20px rgba(255, 255, 255, .16);
}

.pay-title {
  display: grid;
  place-items: center;
  padding: 0 clamp(8px, 1.2vmin, 16px);
  color: var(--white);
  background: linear-gradient(180deg, #2f65c6, #07165c);
  font-size: clamp(.64rem, 1.4vmin, .86rem);
  text-align: center;
  text-transform: uppercase;
  text-shadow: 0 2px #05081b;
}

.paytable-strip {
  display: grid;
  grid-template-columns: repeat(9, minmax(0, 1fr));
}

.paytable-strip div {
  min-width: 0;
  display: grid;
  place-items: center;
  gap: 1px;
  padding: 2px 3px;
  border-left: 1px solid rgba(255, 255, 255, .38);
  text-align: center;
}

.paytable-strip span {
  color: #fff7d5;
  font-size: clamp(.45rem, 1vmin, .66rem);
  line-height: 1;
  text-transform: uppercase;
}

.paytable-strip strong {
  color: var(--gold);
  font-size: clamp(.72rem, 1.8vmin, 1.08rem);
  line-height: 1;
  text-shadow: 0 2px #3c050b;
}

.screen {
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(72px, 1fr) minmax(0, 1.55fr);
  overflow: hidden;
  border: 2px solid rgba(140, 205, 255, .52);
  border-radius: 7px;
  background:
    radial-gradient(circle at 50% 0%, rgba(64, 170, 255, .24), transparent 30%),
    linear-gradient(180deg, var(--screen-2), var(--screen) 48%, #020817);
  box-shadow: inset 0 0 0 2px rgba(0, 0, 0, .38), inset 0 0 46px rgba(0, 0, 0, .62);
}

.status-row {
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(150px, .45fr) minmax(0, 1fr);
  gap: clamp(6px, 1.2vmin, 14px);
  align-items: center;
  padding: clamp(6px, 1vmin, 12px);
  border-bottom: 1px solid rgba(140, 205, 255, .28);
}

.state-label {
  color: var(--gold);
  font-size: clamp(.72rem, 1.6vmin, 1rem);
  line-height: 1;
  text-transform: uppercase;
  text-shadow: 0 2px #21060a;
}

.message {
  margin-top: 2px;
  color: var(--muted);
  font-size: clamp(.66rem, 1.45vmin, .95rem);
  font-weight: 700;
  line-height: 1.16;
}

.multiplier-badge {
  min-width: 0;
  height: clamp(38px, 6.4vmin, 54px);
  display: grid;
  place-items: center;
  padding: clamp(6px, 1.1vmin, 11px);
  border: 2px solid rgba(255, 211, 79, .72);
  border-radius: 5px;
  color: var(--white);
  background: linear-gradient(180deg, #113fad, #07185f);
  font-size: clamp(.72rem, 1.95vmin, 1.2rem);
  line-height: 1;
  text-align: center;
  text-transform: uppercase;
  text-shadow: 0 2px #060816;
  overflow: hidden;
}

.multiplier-badge.eligible {
  color: #fff6b4;
}

.multiplier-badge.hit {
  color: #1a0b00;
  background: linear-gradient(180deg, #fff6a5, var(--gold) 45%, #c37b0c);
  text-shadow: 0 1px rgba(255, 255, 255, .45);
  animation: pulse-win .7s ease-in-out infinite alternate;
}

.multiplier-badge.spinning {
  position: relative;
  gap: 1px;
  padding-block: clamp(3px, .65vmin, 6px);
  color: #fff9b8;
  background:
    linear-gradient(90deg, transparent, rgba(255, 255, 255, .28), transparent),
    repeating-linear-gradient(90deg, rgba(255, 255, 255, .12) 0 2px, transparent 2px 9px),
    linear-gradient(180deg, #092c96, #07155c 60%, #02071f);
  background-size: 70px 100%, auto, auto;
  animation: multiplier-scan .34s linear infinite;
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, .13),
    0 0 18px rgba(255, 211, 79, .32);
}

.multiplier-badge.spinning strong {
  color: var(--gold);
  font-size: clamp(.9rem, 2.3vmin, 1.35rem);
  line-height: .9;
  text-shadow:
    0 2px #52040b,
    0 0 12px rgba(255, 211, 79, .75);
  transform-origin: center;
  animation: multiplier-pop .18s ease-out infinite alternate;
}

.reveal-label,
.reveal-dots {
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(.4rem, .82vmin, .58rem);
  font-weight: 1000;
  line-height: 1;
}

.reveal-dots {
  display: flex;
  gap: 4px;
}

.reveal-dots i {
  width: clamp(3px, .65vmin, 5px);
  height: clamp(3px, .65vmin, 5px);
  border-radius: 50%;
  background: var(--gold);
  box-shadow: 0 0 8px rgba(255, 211, 79, .8);
  animation: reveal-dot .45s ease-in-out infinite alternate;
}

.reveal-dots i:nth-child(2) {
  animation-delay: .12s;
}

.reveal-dots i:nth-child(3) {
  animation-delay: .24s;
}

.multiplier-badge.miss,
.multiplier-badge.idle {
  color: #cdd9ff;
}

.main-hand {
  min-height: 0;
  display: grid;
  align-items: center;
  padding: clamp(8px, 1.5vmin, 18px);
}

.card-row {
  display: grid;
  grid-template-columns: repeat(5, minmax(44px, min(11.5vw, 15.5svh, 150px)));
  justify-content: center;
  gap: clamp(5px, 1.45vmin, 16px);
}

.card {
  position: relative;
  min-width: 0;
  aspect-ratio: 5 / 7;
  border: 3px solid #f4df9a;
  border-radius: 7px;
  color: var(--black-card);
  background:
    radial-gradient(circle at 50% 46%, rgba(255, 255, 255, .9), transparent 28%),
    linear-gradient(135deg, #ffffff, var(--paper));
  box-shadow: 0 10px 18px rgba(0, 0, 0, .36), inset 0 0 0 2px rgba(0, 0, 0, .08);
}

.card.back {
  background:
    linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%),
    radial-gradient(circle, #3176e0, #0b277a);
  background-size: 18px 18px, auto;
}

.card.red {
  color: var(--red);
}

.card.held {
  transform: translateY(-7%);
  border-color: var(--gold);
  box-shadow: 0 0 0 3px #ff5d2e, 0 14px 24px rgba(0, 0, 0, .44);
}

.card.advised:not(.held) {
  border-color: #ffe77a;
  box-shadow: 0 0 0 2px rgba(255, 211, 79, .75), 0 10px 18px rgba(0, 0, 0, .36), inset 0 0 0 2px rgba(0, 0, 0, .08);
}

.rank {
  position: absolute;
  top: 7%;
  left: 8%;
  font-size: clamp(1rem, 3.2vmin, 2rem);
  line-height: 1;
}

.rank.bottom {
  inset: auto 8% 7% auto;
  transform: rotate(180deg);
}

.suit {
  display: grid;
  place-items: center;
  height: 100%;
  font-size: clamp(2.5rem, 8.5vmin, 5.4rem);
  line-height: 1;
}

.card em {
  position: absolute;
  left: 50%;
  bottom: -10px;
  transform: translateX(-50%);
  padding: 3px 9px;
  border: 2px solid #8d2000;
  border-radius: 4px;
  color: #230c00;
  background: linear-gradient(180deg, #fff4a5, var(--gold));
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(.54rem, 1.25vmin, .78rem);
  font-style: normal;
  font-weight: 1000;
  line-height: 1;
  text-transform: uppercase;
}

.advice-star {
  position: absolute;
  top: 3%;
  right: 8%;
  width: clamp(1.05rem, 3.1vmin, 1.85rem);
  height: clamp(1.05rem, 3.1vmin, 1.85rem);
  display: grid;
  place-items: center;
  border: 2px solid #8d2000;
  border-radius: 50%;
  color: #230c00;
  background: linear-gradient(180deg, #fff8b8, var(--gold));
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(.9rem, 2.6vmin, 1.55rem);
  font-weight: 1000;
  line-height: 1;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .3);
}

.results-panel {
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  padding: 0 clamp(6px, 1.2vmin, 12px) clamp(6px, 1.2vmin, 12px);
}

.section-heading {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: center;
  min-height: 0;
  padding-bottom: clamp(4px, .8vmin, 8px);
}

.section-heading h2 {
  color: var(--gold);
  font-size: clamp(.74rem, 1.65vmin, 1.05rem);
  line-height: 1;
  text-transform: uppercase;
}

.section-heading span {
  color: var(--muted);
  font-size: clamp(.62rem, 1.35vmin, .86rem);
  font-weight: 800;
}

.hands-grid {
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: clamp(4px, .9vmin, 8px);
}

.mini-hand {
  min-width: 0;
  min-height: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  padding: clamp(4px, .8vmin, 8px);
  border: 1px solid rgba(140, 205, 255, .36);
  border-radius: 5px;
  background: rgba(0, 0, 0, .22);
}

.mini-hand.winner {
  border-color: rgba(255, 211, 79, .9);
  background: rgba(255, 211, 79, .16);
}

.mini-hand-top {
  display: flex;
  justify-content: space-between;
  gap: 5px;
  color: var(--muted);
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(.5rem, 1.1vmin, .72rem);
  font-weight: 800;
  line-height: 1;
}

.mini-hand-top strong {
  color: var(--gold);
}

.mini-card-row {
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 2px;
  margin: 4px 0;
}

.mini-card-row span,
.mini-card-row i {
  display: grid;
  place-items: center;
  min-height: 18px;
  border-radius: 3px;
  color: var(--black-card);
  background: var(--paper);
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(.46rem, 1.05vmin, .68rem);
  font-style: normal;
  font-weight: 1000;
  line-height: 1;
}

.mini-card-row small {
  font-size: .72em;
}

.mini-card-row .red {
  color: var(--red);
}

.mini-card-row i {
  background: rgba(180, 212, 255, .18);
}

.mini-hand p {
  min-height: 1em;
  overflow: hidden;
  color: var(--ink);
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: clamp(.48rem, 1.08vmin, .72rem);
  font-weight: 800;
  line-height: 1.05;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.controls {
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(116px, 1.25fr) repeat(2, minmax(70px, .65fr)) minmax(112px, 1fr) repeat(2, minmax(78px, .72fr));
  grid-auto-rows: minmax(0, 1fr);
  gap: clamp(5px, 1vmin, 10px);
  align-items: stretch;
  overflow: hidden;
  padding: clamp(6px, 1vmin, 12px);
  border: 2px solid var(--line);
  border-radius: 7px;
  background: linear-gradient(180deg, #1d2c6d, #060b2b);
}

label {
  min-width: 0;
  display: grid;
  gap: 3px;
  color: #e8efff;
  font-size: clamp(.54rem, 1.15vmin, .72rem);
  font-weight: 900;
  line-height: 1;
  text-transform: uppercase;
}

select,
.secondary-button,
.primary-button {
  min-width: 0;
  min-height: 0;
  border: 2px solid rgba(255, 255, 255, .44);
  border-radius: 6px;
  color: #fff;
  box-shadow: inset 0 1px rgba(255, 255, 255, .32), 0 4px 0 rgba(0, 0, 0, .42);
  text-transform: uppercase;
}

select {
  width: 100%;
  height: 100%;
  padding: 0 28px 0 8px;
  background: linear-gradient(180deg, #2c70d4, #093b83);
}

.primary-button {
  padding: 0 10px;
  color: var(--white);
  background: linear-gradient(180deg, #ff4758, var(--button-red) 62%, #710811);
  font-size: clamp(.92rem, 2.6vmin, 1.45rem);
  text-shadow: 0 2px #3b0308;
}

.secondary-button {
  padding: 0 8px;
  background: linear-gradient(180deg, #2990f0, var(--button-blue) 58%, #063c75);
  font-size: clamp(.68rem, 1.55vmin, .92rem);
}

.toggle {
  min-height: 0;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 7px;
  padding: 0 8px;
  border: 2px solid rgba(255, 255, 255, .44);
  border-radius: 6px;
  background: linear-gradient(180deg, #1f67bc, #082f75);
  box-shadow: inset 0 1px rgba(255, 255, 255, .28), 0 4px 0 rgba(0, 0, 0, .42);
}

.toggle input {
  flex: 0 0 auto;
  width: clamp(14px, 2.3vmin, 20px);
  height: clamp(14px, 2.3vmin, 20px);
  accent-color: var(--gold);
}

.toggle span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

dialog {
  width: min(420px, calc(100% - 28px));
  border: 2px solid var(--line);
  border-radius: 8px;
  color: var(--ink);
  background: linear-gradient(180deg, #113fad, #07103e);
  padding: 0;
}

dialog::backdrop {
  background: rgba(0, 0, 0, .72);
}

.dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px;
  border-bottom: 1px solid var(--line);
}

.dialog-header h2 {
  color: var(--gold);
  font-size: 1.1rem;
  text-transform: uppercase;
}

.dialog-header button {
  width: 38px;
  height: 38px;
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--ink);
  background: rgba(255, 255, 255, .08);
  font-size: 1.4rem;
}

.paytable-list {
  padding: 10px 16px 16px;
}

.paytable-list div {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  font-family: "Segoe UI", system-ui, sans-serif;
  font-weight: 800;
}

.paytable-list div:last-child {
  border-bottom: 0;
}

.paytable-list strong {
  color: var(--gold);
}

@keyframes pulse-win {
  from {
    filter: brightness(1);
  }
  to {
    filter: brightness(1.22);
  }
}

@keyframes multiplier-scan {
  from {
    background-position: -70px 0, 0 0, 0 0;
  }
  to {
    background-position: 70px 0, 0 0, 0 0;
  }
}

@keyframes multiplier-pop {
  from {
    transform: scale(.94);
  }
  to {
    transform: scale(1.03);
  }
}

@keyframes reveal-dot {
  from {
    opacity: .25;
    transform: translateY(1px);
  }
  to {
    opacity: 1;
    transform: translateY(-1px);
  }
}

@media (orientation: portrait) {
  .machine {
    grid-template-rows: clamp(72px, 16svh, 128px) clamp(34px, 6.5svh, 48px) minmax(0, 1fr) clamp(92px, 18svh, 118px);
  }

  .marquee {
    grid-template-columns: 1fr;
    gap: 5px;
    padding: 7px;
  }

  h1 {
    font-size: clamp(1.3rem, 7.2vw, 2.7rem);
  }

  .meters div {
    padding: 5px;
  }

  .pay-glass {
    grid-template-columns: 1fr;
  }

  .pay-title {
    display: none;
  }

  .paytable-strip {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }

  .paytable-strip div:nth-child(n+6) {
    display: none;
  }

  .screen {
    grid-template-rows: auto minmax(88px, 1fr) minmax(0, 2.3fr);
  }

  .status-row {
    grid-template-columns: 1fr;
    gap: 4px;
  }

  .main-hand {
    padding: 8px 6px 10px;
  }

  .card-row {
    grid-template-columns: repeat(5, minmax(0, min(18vw, 11svh)));
    gap: 4px;
  }

  .hands-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .controls {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .app {
    padding: 3px;
  }

  .machine {
    padding: 5px;
    border-width: 3px;
    gap: 4px;
  }

  .brand {
    text-align: center;
  }

  .message {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
  }

  .card {
    border-width: 2px;
    border-radius: 5px;
  }

  .card em {
    bottom: -8px;
    padding: 2px 5px;
  }

  .results-panel {
    padding-inline: 5px;
    padding-bottom: 5px;
  }

  .mini-hand {
    padding: 4px;
  }

  .mini-card-row {
    gap: 1px;
    margin: 3px 0;
  }

  .controls {
    padding: 5px;
    gap: 4px;
  }

  .toggle {
    padding: 0 5px;
  }
}

@media (max-height: 560px) {
  .machine {
    grid-template-rows: clamp(44px, 15svh, 70px) clamp(30px, 8svh, 38px) minmax(0, 1fr) clamp(46px, 13svh, 58px);
  }

  .eyebrow,
  .message,
  .mini-hand p {
    display: none;
  }

  .screen {
    grid-template-rows: auto minmax(58px, .8fr) minmax(0, 1.4fr);
  }

  .main-hand {
    padding-block: 5px 8px;
  }

  .card-row {
    grid-template-columns: repeat(5, minmax(34px, min(9vw, 9.5svh)));
    gap: 5px;
  }

  .controls label {
    gap: 1px;
  }

  .controls {
    grid-template-columns: minmax(94px, 1.25fr) repeat(5, minmax(54px, 1fr));
  }
}

engine.js

Poker rules and hand evaluation
const RANKS = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"];
const SUITS = ["spades", "hearts", "diamonds", "clubs"];

const SUIT_SYMBOLS = {
  spades: "\u2660",
  hearts: "\u2665",
  diamonds: "\u2666",
  clubs: "\u2663",
};

const PAYTABLE = {
  "Royal Flush": 800,
  "Straight Flush": 50,
  "Four of a Kind": 25,
  "Full House": 9,
  Flush: 6,
  Straight: 4,
  "Three of a Kind": 3,
  "Two Pair": 2,
  "Jacks or Better": 1,
  "No Win": 0,
};

const MULTIPLIERS = [2, 3, 4, 5, 8, 10];
const MULTIPLIER_TRIGGER_CHANCE = 0.1;

function createDeck() {
  return SUITS.flatMap((suit) =>
    RANKS.map((rank, index) => ({
      rank,
      suit,
      value: index + 2,
      id: `${rank}-${suit}`,
      symbol: SUIT_SYMBOLS[suit],
    })),
  );
}

function shuffle(cards, random = Math.random) {
  const deck = [...cards];
  for (let i = deck.length - 1; i > 0; i -= 1) {
    const j = Math.floor(random() * (i + 1));
    [deck[i], deck[j]] = [deck[j], deck[i]];
  }
  return deck;
}

function dealBaseHand(random = Math.random) {
  const deck = shuffle(createDeck(), random);
  return {
    hand: deck.slice(0, 5),
    deck: deck.slice(5),
  };
}

function drawFinalHands(baseHand, heldIndexes, handCount, random = Math.random) {
  const held = baseHand.filter((_, index) => heldIndexes.includes(index));
  const unavailable = new Set(baseHand.map((card) => card.id));

  return Array.from({ length: handCount }, (_, handIndex) => {
    const drawPool = shuffle(
      createDeck().filter((card) => !unavailable.has(card.id)),
      random,
    );
    const needed = 5 - held.length;
    const drawn = drawPool.slice(0, needed);
    const cards = mergeHeldAndDrawn(baseHand, heldIndexes, drawn);

    return {
      id: handIndex + 1,
      cards,
      drawn,
    };
  });
}

function mergeHeldAndDrawn(baseHand, heldIndexes, drawn) {
  let drawIndex = 0;
  return baseHand.map((card, index) => {
    if (heldIndexes.includes(index)) return card;
    const nextCard = drawn[drawIndex];
    drawIndex += 1;
    return nextCard;
  });
}

function evaluateHand(cards) {
  const values = cards.map((card) => card.value).sort((a, b) => a - b);
  const suits = cards.map((card) => card.suit);
  const counts = countBy(values);
  const countValues = Object.values(counts).sort((a, b) => b - a);
  const pairs = Object.entries(counts)
    .filter(([, count]) => count === 2)
    .map(([value]) => Number(value));

  const isFlush = suits.every((suit) => suit === suits[0]);
  const isStraight = detectStraight(values);
  const isRoyal = JSON.stringify(values) === JSON.stringify([10, 11, 12, 13, 14]);

  let name = "No Win";
  if (isFlush && isRoyal) name = "Royal Flush";
  else if (isFlush && isStraight) name = "Straight Flush";
  else if (countValues[0] === 4) name = "Four of a Kind";
  else if (countValues[0] === 3 && countValues[1] === 2) name = "Full House";
  else if (isFlush) name = "Flush";
  else if (isStraight) name = "Straight";
  else if (countValues[0] === 3) name = "Three of a Kind";
  else if (pairs.length === 2) name = "Two Pair";
  else if (pairs.some((value) => value >= 11 || value === 14)) name = "Jacks or Better";

  return {
    handName: name,
    basePayout: PAYTABLE[name],
    isWinner: PAYTABLE[name] > 0,
  };
}

function recommendHoldIndexes(cards) {
  if (!cards.length) return [];

  const evaluation = evaluateHand(cards);
  const byValue = groupIndexes(cards, (card) => card.value);
  const bySuit = groupIndexes(cards, (card) => card.suit);
  const pairs = Object.entries(byValue)
    .filter(([, indexes]) => indexes.length === 2)
    .map(([value, indexes]) => ({ value: Number(value), indexes }));
  const three = Object.values(byValue).find((indexes) => indexes.length === 3);
  const four = Object.values(byValue).find((indexes) => indexes.length === 4);

  if (["Royal Flush", "Straight Flush", "Full House", "Flush", "Straight"].includes(evaluation.handName)) {
    return [0, 1, 2, 3, 4];
  }
  if (four) return four;
  if (three) return three;
  if (pairs.length === 2) return pairs.flatMap((pair) => pair.indexes).sort((a, b) => a - b);
  if (pairs.length === 1) return pairs[0].indexes;

  const royalDraw = bestRoyalDraw(cards);
  if (royalDraw.length >= 4) return royalDraw;

  const straightFlushDraw = bestStraightDraw(cards, true);
  if (straightFlushDraw.length >= 4) return straightFlushDraw;

  const flushDraw = Object.values(bySuit)
    .filter((indexes) => indexes.length >= 4)
    .sort((a, b) => b.length - a.length)[0];
  if (flushDraw) return flushDraw;

  const straightDraw = bestStraightDraw(cards, false);
  if (straightDraw.length >= 4) return straightDraw;
  if (royalDraw.length >= 3) return royalDraw;

  return cards
    .map((card, index) => ({ card, index }))
    .filter(({ card }) => card.value >= 11 || card.value === 14)
    .sort((a, b) => b.card.value - a.card.value)
    .slice(0, 2)
    .map(({ index }) => index)
    .sort((a, b) => a - b);
}

function groupIndexes(items, keyFn) {
  return items.reduce((groups, item, index) => {
    const key = keyFn(item);
    groups[key] = groups[key] || [];
    groups[key].push(index);
    return groups;
  }, {});
}

function bestRoyalDraw(cards) {
  const royalValues = new Set([10, 11, 12, 13, 14]);
  return Object.values(groupIndexes(cards, (card) => card.suit))
    .map((indexes) => indexes.filter((index) => royalValues.has(cards[index].value)))
    .sort((a, b) => b.length - a.length)[0] || [];
}

function bestStraightDraw(cards, requireSameSuit) {
  const wheelsHigh = cards.map((card) => (card.value === 14 ? { ...card, value: 1 } : card));
  const candidates = straightWindows()
    .flatMap((windowValues) => {
      const sourceHands = requireSameSuit
        ? Object.values(groupIndexes(cards, (card) => card.suit))
        : [[0, 1, 2, 3, 4]];

      return sourceHands.map((indexes) => {
        const matchesByValue = indexes.reduce((matches, index) => {
          const values = [cards[index].value, wheelsHigh[index].value];
          const matchValue = values.find((value) => windowValues.includes(value));
          if (matchValue && !matches.has(matchValue)) matches.set(matchValue, index);
          return matches;
        }, new Map());
        return [...matchesByValue.values()];
      });
    })
    .filter((indexes) => indexes.length >= 3)
    .sort((a, b) => b.length - a.length);

  return candidates[0] || [];
}

function straightWindows() {
  return [
    [1, 2, 3, 4, 5],
    [2, 3, 4, 5, 6],
    [3, 4, 5, 6, 7],
    [4, 5, 6, 7, 8],
    [5, 6, 7, 8, 9],
    [6, 7, 8, 9, 10],
    [7, 8, 9, 10, 11],
    [8, 9, 10, 11, 12],
    [9, 10, 11, 12, 13],
    [10, 11, 12, 13, 14],
  ];
}

function countBy(items) {
  return items.reduce((counts, item) => {
    counts[item] = (counts[item] || 0) + 1;
    return counts;
  }, {});
}

function detectStraight(sortedValues) {
  const unique = [...new Set(sortedValues)];
  if (unique.length !== 5) return false;
  if (JSON.stringify(unique) === JSON.stringify([2, 3, 4, 5, 14])) return true;
  return unique.every((value, index) => index === 0 || value === unique[index - 1] + 1);
}

function rollMultiplier(isActive, random = Math.random) {
  if (!isActive || random() >= MULTIPLIER_TRIGGER_CHANCE) {
    return {
      active: false,
      value: 1,
    };
  }

  return {
    active: true,
    value: MULTIPLIERS[Math.floor(random() * MULTIPLIERS.length)],
  };
}

function scoreHands(finalHands, betPerHand, multiplier) {
  const results = finalHands.map((hand) => {
    const evaluation = evaluateHand(hand.cards);
    const baseWin = betPerHand * evaluation.basePayout;
    return {
      ...hand,
      ...evaluation,
      baseWin,
      finalWin: baseWin * multiplier.value,
    };
  });

  return {
    results,
    baseTotal: results.reduce((sum, result) => sum + result.baseWin, 0),
    finalTotal: results.reduce((sum, result) => sum + result.finalWin, 0),
  };
}

function calculateTotalBet({ handCount, betPerHand, multiplierEnabled }) {
  return handCount * betPerHand + (multiplierEnabled ? handCount : 0);
}

const PokerEngine = {
  RANKS,
  SUITS,
  SUIT_SYMBOLS,
  PAYTABLE,
  MULTIPLIERS,
  createDeck,
  shuffle,
  dealBaseHand,
  drawFinalHands,
  evaluateHand,
  recommendHoldIndexes,
  rollMultiplier,
  scoreHands,
  calculateTotalBet,
};

if (typeof window !== "undefined") {
  window.PokerEngine = PokerEngine;
}

app.js

Interface state and gameplay controls
(function () {
const Engine = window.PokerEngine;

const state = {
  phase: "idle",
  credits: 1000,
  lastWin: 0,
  baseHand: [],
  heldIndexes: [],
  finalResults: [],
  multiplier: { active: false, value: 1 },
  multiplierReveal: {
    spinning: false,
    display: "",
    timer: null,
  },
};

const els = {
  credits: document.querySelector("#credits"),
  currentBet: document.querySelector("#currentBet"),
  lastWin: document.querySelector("#lastWin"),
  stateLabel: document.querySelector("#stateLabel"),
  message: document.querySelector("#message"),
  multiplierBadge: document.querySelector("#multiplierBadge"),
  baseHand: document.querySelector("#baseHand"),
  finalHands: document.querySelector("#finalHands"),
  resultSummary: document.querySelector("#resultSummary"),
  paytableStrip: document.querySelector("#paytableStrip"),
  dealDrawButton: document.querySelector("#dealDrawButton"),
  betPerHand: document.querySelector("#betPerHand"),
  handCount: document.querySelector("#handCount"),
  paytableButton: document.querySelector("#paytableButton"),
  resetButton: document.querySelector("#resetButton"),
  paytableDialog: document.querySelector("#paytableDialog"),
  closePaytable: document.querySelector("#closePaytable"),
  paytableList: document.querySelector("#paytableList"),
};

const sound = {
  ctx: null,
  enabled: true,
};

function getAudioContext() {
  if (!sound.enabled) return null;
  if (!sound.ctx) {
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    if (!AudioContext) {
      sound.enabled = false;
      return null;
    }
    sound.ctx = new AudioContext();
  }
  if (sound.ctx.state === "suspended") sound.ctx.resume();
  return sound.ctx;
}

function playTone(frequency, duration, type = "square", gainValue = 0.06, delay = 0) {
  const ctx = getAudioContext();
  if (!ctx) return;

  const start = ctx.currentTime + delay;
  const osc = ctx.createOscillator();
  const gain = ctx.createGain();
  osc.type = type;
  osc.frequency.setValueAtTime(frequency, start);
  gain.gain.setValueAtTime(0.0001, start);
  gain.gain.exponentialRampToValueAtTime(gainValue, start + 0.012);
  gain.gain.exponentialRampToValueAtTime(0.0001, start + duration);
  osc.connect(gain);
  gain.connect(ctx.destination);
  osc.start(start);
  osc.stop(start + duration + 0.02);
}

function playSound(name) {
  if (name === "deal") {
    [220, 260, 300, 340, 380].forEach((freq, index) => playTone(freq, 0.045, "square", 0.045, index * 0.035));
  } else if (name === "hold") {
    playTone(520, 0.055, "triangle", 0.05);
  } else if (name === "draw") {
    [380, 330, 450, 410, 560].forEach((freq, index) => playTone(freq, 0.038, "sawtooth", 0.04, index * 0.028));
  } else if (name === "win") {
    [523, 659, 784, 1046].forEach((freq, index) => playTone(freq, 0.11, "triangle", 0.07, index * 0.08));
  } else if (name === "bigWin") {
    [392, 523, 659, 784, 1046, 1318].forEach((freq, index) => playTone(freq, 0.12, "triangle", 0.075, index * 0.07));
  } else if (name === "multiplierTick") {
    playTone(740, 0.035, "square", 0.035);
  } else if (name === "multiplierHit") {
    [440, 660, 880, 1320].forEach((freq, index) => playTone(freq, 0.105, "triangle", 0.075, index * 0.075));
  } else if (name === "multiplierMiss") {
    [260, 210].forEach((freq, index) => playTone(freq, 0.09, "sawtooth", 0.045, index * 0.075));
  } else {
    playTone(180, 0.04, "square", 0.035);
  }
}

function clearMultiplierReveal() {
  if (state.multiplierReveal.timer) {
    window.clearTimeout(state.multiplierReveal.timer);
  }
  state.multiplierReveal.timer = null;
  state.multiplierReveal.spinning = false;
  state.multiplierReveal.display = "";
}

function getSettings() {
  return {
    betPerHand: Number(els.betPerHand.value),
    handCount: Number(els.handCount.value),
    multiplierEnabled: true,
  };
}

function deal() {
  const settings = getSettings();
  const totalBet = Engine.calculateTotalBet(settings);

  if (state.credits < totalBet) {
    state.message = "Not enough credits for that bet. Lower the settings or reset credits.";
    render();
    return;
  }

  playSound("deal");
  const dealResult = Engine.dealBaseHand();
  state.phase = "dealt";
  state.credits -= totalBet;
  state.lastWin = 0;
  state.baseHand = dealResult.hand;
  state.heldIndexes = [];
  state.finalResults = [];
  state.multiplier = Engine.rollMultiplier(settings.multiplierEnabled);
  state.message = "Video Poker is choosing a multiplier...";
  render();
  startMultiplierReveal(settings.multiplierEnabled);
}

function draw() {
  if (state.multiplierReveal.spinning) return;
  const settings = getSettings();
  playSound("draw");
  const finalHands = Engine.drawFinalHands(state.baseHand, state.heldIndexes, settings.handCount);
  const scored = Engine.scoreHands(finalHands, settings.betPerHand, state.multiplier);

  state.phase = "result";
  state.finalResults = scored.results;
  state.lastWin = scored.finalTotal;
  state.credits += scored.finalTotal;
  state.message = buildResultMessage(scored);
  render();
  if (scored.finalTotal > 0) {
    playSound(scored.finalTotal >= settings.betPerHand * settings.handCount * 10 ? "bigWin" : "win");
  }
}

function buildResultMessage(scored) {
  if (scored.finalTotal === 0) return "No winning hands this round.";
  if (state.multiplier.active && scored.baseTotal > 0) {
    return `${scored.baseTotal} credits became ${scored.finalTotal} with a ${state.multiplier.value}x multiplier.`;
  }
  return `${scored.finalTotal} credits won.`;
}

function toggleHold(index) {
  if (state.phase !== "dealt" || state.multiplierReveal.spinning) return;
  if (state.heldIndexes.includes(index)) {
    state.heldIndexes = state.heldIndexes.filter((heldIndex) => heldIndex !== index);
  } else {
    state.heldIndexes = [...state.heldIndexes, index].sort((a, b) => a - b);
  }
  playSound("hold");
  renderBaseHand();
}

function resetGame() {
  clearMultiplierReveal();
  state.phase = "idle";
  state.credits = 1000;
  state.lastWin = 0;
  state.baseHand = [];
  state.heldIndexes = [];
  state.finalResults = [];
  state.multiplier = { active: false, value: 1 };
  state.message = "Credits reset. Choose your bet, then deal.";
  playSound("button");
  render();
}

function startMultiplierReveal(enabled) {
  clearMultiplierReveal();
  if (!enabled) return;

  state.multiplierReveal.spinning = true;
  els.dealDrawButton.disabled = true;
  const stops = [...Engine.MULTIPLIERS, "NO"];
  const totalTicks = 24 + Math.floor(Math.random() * 5);
  let tick = 0;

  function step() {
    const isLastTick = tick >= totalTicks;
    if (isLastTick) {
      state.multiplierReveal.spinning = false;
      state.multiplierReveal.display = "";
      state.message = state.multiplier.active
        ? `${state.multiplier.value}x Video Poker multiplier is active. ${buildDealAdviceMessage()}`
        : `No multiplier this deal. ${buildDealAdviceMessage()}`;
      render();
      playSound(state.multiplier.active ? "multiplierHit" : "multiplierMiss");
      return;
    }

    const suspenseIndex = tick % stops.length;
    const display = stops[suspenseIndex];
    state.multiplierReveal.display = display === "NO" ? "No Multiplier?" : `${display}x`;
    renderMultiplier();
    playSound("multiplierTick");
    tick += 1;

    const delay = 42 + Math.pow(tick / totalTicks, 2.6) * 170;
    state.multiplierReveal.timer = window.setTimeout(step, delay);
  }

  step();
}

function render() {
  const settings = getSettings();
  els.credits.textContent = state.credits;
  els.currentBet.textContent = Engine.calculateTotalBet(settings);
  els.lastWin.textContent = state.lastWin;
  els.stateLabel.textContent = state.phase === "idle" ? "Ready" : state.phase === "dealt" ? "Hold" : "Result";
  els.message.textContent = state.message || "Choose your bet, then deal.";

  els.dealDrawButton.textContent = state.multiplierReveal.spinning ? "Wait" : state.phase === "dealt" ? "Draw" : state.phase === "result" ? "Deal Again" : "Deal";
  els.dealDrawButton.disabled = state.multiplierReveal.spinning;
  const lockSettings = state.phase === "dealt";
  els.betPerHand.disabled = lockSettings;
  els.handCount.disabled = lockSettings;

  renderMultiplier();
  renderBaseHand();
  renderFinalHands();
  renderPaytable();
}

function renderMultiplier() {
  const label = els.multiplierBadge;
  label.className = "multiplier-badge";

  if (state.multiplierReveal.spinning) {
    label.innerHTML = `
      <span class="reveal-label">Video Poker</span>
      <strong>${state.multiplierReveal.display || "..."}</strong>
      <span class="reveal-dots"><i></i><i></i><i></i></span>
    `;
    label.classList.add("spinning");
  } else if (state.phase === "idle") {
    label.textContent = "Multiplier eligible";
    label.classList.add("eligible");
  } else if (state.multiplier.active) {
    label.textContent = `${state.multiplier.value}x Video Poker`;
    label.classList.add("hit");
  } else {
    label.textContent = "No multiplier this deal";
    label.classList.add("miss");
  }
}

function renderBaseHand() {
  els.baseHand.innerHTML = "";
  const cards = state.baseHand.length ? state.baseHand : Array.from({ length: 5 }, () => null);
  const advisedIndexes = state.phase === "dealt" && !state.multiplierReveal.spinning
    ? Engine.recommendHoldIndexes(state.baseHand)
    : [];

  cards.forEach((card, index) => {
    const isAdvised = advisedIndexes.includes(index);
    const button = document.createElement("button");
    button.type = "button";
    button.className = "card";
    if (!card) button.classList.add("back");
    if (card && isRed(card)) button.classList.add("red");
    if (isAdvised) button.classList.add("advised");
    if (state.heldIndexes.includes(index)) button.classList.add("held");
    button.disabled = state.phase !== "dealt" || state.multiplierReveal.spinning;
    button.setAttribute("aria-label", card ? `${card.rank} of ${card.suit}${isAdvised ? ", suggested hold" : ""}` : "Card back");
    button.innerHTML = card ? cardFace(card, state.heldIndexes.includes(index), isAdvised) : "";
    button.addEventListener("click", () => toggleHold(index));
    els.baseHand.appendChild(button);
  });
}

function renderFinalHands() {
  els.finalHands.innerHTML = "";

  if (!state.finalResults.length) {
    const settings = getSettings();
    for (let i = 0; i < settings.handCount; i += 1) {
      const placeholder = document.createElement("article");
      placeholder.className = "mini-hand placeholder";
      placeholder.innerHTML = `<span>Hand ${i + 1}</span><div class="mini-card-row">${Array.from({ length: 5 }, () => "<i></i>").join("")}</div>`;
      els.finalHands.appendChild(placeholder);
    }
    els.resultSummary.textContent = "Wins appear after draw";
    return;
  }

  const wins = state.finalResults.filter((result) => result.isWinner);
  els.resultSummary.textContent = `${wins.length} winning hand${wins.length === 1 ? "" : "s"} / ${state.finalResults.length}`;

  state.finalResults.forEach((result) => {
    const hand = document.createElement("article");
    hand.className = `mini-hand${result.isWinner ? " winner" : ""}`;
    hand.innerHTML = `
      <div class="mini-hand-top">
        <span>Hand ${result.id}</span>
        <strong>${result.finalWin}</strong>
      </div>
      <div class="mini-card-row">
        ${result.cards.map((card) => `<span class="${isRed(card) ? "red" : ""}">${card.rank}<small>${card.symbol}</small></span>`).join("")}
      </div>
      <p>${result.handName}</p>
    `;
    els.finalHands.appendChild(hand);
  });
}

function renderPaytable() {
  const rows = Object.entries(Engine.PAYTABLE)
    .filter(([, value]) => value > 0)
    .map(([name, value]) => `<div><span>${name}</span><strong>${value}</strong></div>`);

  els.paytableList.innerHTML = rows.join("");
  els.paytableStrip.innerHTML = rows.join("");
}

function buildDealAdviceMessage() {
  const evaluation = Engine.evaluateHand(state.baseHand);
  const advisedIndexes = Engine.recommendHoldIndexes(state.baseHand);
  const holdText = advisedIndexes.length
    ? "Starred cards are suggested holds."
    : "No strong hold is suggested.";
  return `Dealt: ${evaluation.handName}. ${holdText}`;
}

function cardFace(card, held, advised) {
  return `
    <span class="rank">${card.rank}</span>
    <span class="suit">${card.symbol}</span>
    <span class="rank bottom">${card.rank}</span>
    ${advised ? '<span class="advice-star" aria-hidden="true">*</span>' : ""}
    ${held ? "<em>Hold</em>" : ""}
  `;
}

function isRed(card) {
  return card.suit === "hearts" || card.suit === "diamonds";
}

els.dealDrawButton.addEventListener("click", () => {
  if (state.phase === "dealt") draw();
  else deal();
});

els.betPerHand.addEventListener("change", () => {
  playSound("button");
  render();
});
els.handCount.addEventListener("change", () => {
  playSound("button");
  render();
});
els.resetButton.addEventListener("click", resetGame);
els.paytableButton.addEventListener("click", () => {
  playSound("button");
  els.paytableDialog.showModal();
});
els.closePaytable.addEventListener("click", () => {
  playSound("button");
  els.paytableDialog.close();
});

render();
}());