← Slot Machine results

Slot Machine

Grok 4.5 · legacy

Initial view of Slot Machine

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 26,217 bytes · SHA-256 ab8870150279
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lucky Reels Casino</title>
<style>
  :root {
    --gold: #ffd700;
    --gold-dark: #b8860b;
    --red: #c41e3a;
    --red-dark: #8b0000;
    --grid: #1a0a2e;
    --panel: #2d1b4e;
    --neon: #ff2d95;
    --cyan: #00f5ff;
    --cream: #fff8e7;
  }

  * { box-sizing: border-box; margin: 0; padding: 0; }

  body {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background:
      radial-gradient(ellipse at 20% 20%, #3d1a5c 0%, transparent 50%),
      radial-gradient(ellipse at 80% 80%, #1a3a5c 0%, transparent 50%),
      linear-gradient(160deg, #0d0221 0%, #1a0a2e 40%, #0a1628 100%);
    font-family: "Segoe UI", system-ui, sans-serif;
    color: var(--cream);
    overflow-x: hidden;
    padding: 16px;
  }

  body::before {
    content: "";
    position: fixed;
    inset: 0;
    background-image:
      radial-gradient(circle at 10% 20%, rgba(255, 45, 149, 0.08) 0 2px, transparent 3px),
      radial-gradient(circle at 90% 70%, rgba(0, 245, 255, 0.06) 0 2px, transparent 3px),
      radial-gradient(circle at 50% 50%, rgba(255, 215, 0, 0.04) 0 1px, transparent 2px);
    background-size: 80px 80px, 120px 120px, 40px 40px;
    pointer-events: none;
    z-index: 0;
  }

  .machine {
    position: relative;
    z-index: 1;
    width: min(440px, 100%);
    background: linear-gradient(180deg, #4a2068 0%, #2d1b4e 30%, #1f1238 100%);
    border-radius: 24px;
    padding: 20px 18px 24px;
    box-shadow:
      0 0 0 4px var(--gold-dark),
      0 0 0 8px var(--gold),
      0 0 0 12px var(--gold-dark),
      0 25px 60px rgba(0, 0, 0, 0.6),
      inset 0 1px 0 rgba(255, 255, 255, 0.15);
  }

  .machine::before {
    content: "";
    position: absolute;
    top: -6px; left: 20%; right: 20%;
    height: 10px;
    background: linear-gradient(90deg, transparent, var(--neon), var(--cyan), var(--neon), transparent);
    border-radius: 50%;
    filter: blur(4px);
    opacity: 0.7;
    animation: marquee 2s linear infinite;
  }

  @keyframes marquee {
    0%, 100% { opacity: 0.4; }
    50% { opacity: 1; }
  }

  .title {
    text-align: center;
    font-size: clamp(1.4rem, 5vw, 1.9rem);
    font-weight: 800;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    background: linear-gradient(180deg, #fff 0%, var(--gold) 45%, var(--gold-dark) 100%);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    text-shadow: 0 0 30px rgba(255, 215, 0, 0.35);
    margin-bottom: 4px;
    filter: drop-shadow(0 2px 0 #5a3a00);
  }

  .subtitle {
    text-align: center;
    font-size: 0.72rem;
    letter-spacing: 0.35em;
    color: var(--cyan);
    opacity: 0.85;
    margin-bottom: 16px;
    text-transform: uppercase;
  }

  .display {
    background: #0a0612;
    border-radius: 12px;
    padding: 10px 14px;
    margin-bottom: 14px;
    border: 2px solid #3d2a5c;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 8px;
    box-shadow: inset 0 4px 12px rgba(0, 0, 0, 0.5);
  }

  .stat {
    text-align: center;
  }

  .stat label {
    display: block;
    font-size: 0.65rem;
    letter-spacing: 0.15em;
    color: #9a8ab8;
    text-transform: uppercase;
    margin-bottom: 2px;
  }

  .stat value {
    display: block;
    font-size: 1.15rem;
    font-weight: 700;
    color: var(--gold);
    font-variant-numeric: tabular-nums;
  }

  .stat value.win-flash {
    color: #7dff9a;
    animation: pulse 0.4s ease 3;
  }

  @keyframes pulse {
    50% { transform: scale(1.15); filter: brightness(1.4); }
  }

  .window {
    background: linear-gradient(180deg, #12101a 0%, #0a0810 100%);
    border-radius: 16px;
    padding: 14px 12px;
    border: 3px solid #5a3d1a;
    box-shadow:
      inset 0 0 40px rgba(0, 0, 0, 0.8),
      0 0 20px rgba(255, 215, 0, 0.15);
    position: relative;
    margin-bottom: 16px;
  }

  .window::after {
    content: "";
    position: absolute;
    left: 0; right: 0;
    top: 50%;
    height: 2px;
    background: linear-gradient(90deg, transparent, rgba(255, 45, 149, 0.5), transparent);
    transform: translateY(-50%);
    pointer-events: none;
    z-index: 5;
  }

  .payline {
    position: absolute;
    left: 8px; right: 8px;
    top: 50%;
    height: 4px;
    margin-top: -2px;
    border: 1px dashed rgba(255, 45, 149, 0.4);
    border-left: none;
    border-right: none;
    z-index: 4;
    pointer-events: none;
  }

  .reels {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 8px;
    height: 210px;
    overflow: hidden;
    border-radius: 10px;
  }

  .reel {
    background: linear-gradient(180deg, #1e1a28 0%, #15121c 50%, #1e1a28 100%);
    border-radius: 8px;
    overflow: hidden;
    position: relative;
    border: 2px solid #3a3050;
    box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.6);
  }

  .reel-strip {
    position: absolute;
    left: 0; right: 0;
    top: 0;
    will-change: transform;
  }

  .symbol {
    height: 70px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.4rem;
    user-select: none;
    line-height: 1;
    position: relative;
  }

  .symbol .icon {
    width: 52px;
    height: 52px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 12px;
    font-size: 1.9rem;
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.35);
    transition: transform 0.15s;
  }

  .sym-cherry .icon { background: linear-gradient(145deg, #ff6b8a, #c41230); }
  .sym-lemon .icon  { background: linear-gradient(145deg, #ffe566, #e8b800); }
  .sym-orange .icon { background: linear-gradient(145deg, #ffb347, #e07b00); }
  .sym-plum .icon   { background: linear-gradient(145deg, #c79bff, #7b3fc9); }
  .sym-bell .icon   { background: linear-gradient(145deg, #ffeaa0, #d4a017); }
  .sym-bar .icon    { background: linear-gradient(145deg, #6ec6ff, #1565c0); font-weight: 900; font-size: 1.1rem; letter-spacing: -0.02em; color: #fff; text-shadow: 0 1px 2px #000; }
  .sym-seven .icon  { background: linear-gradient(145deg, #ff6b6b, #b71c1c); font-weight: 900; font-size: 1.6rem; color: #fff; text-shadow: 0 0 8px #ff0, 0 2px 2px #000; }
  .sym-diamond .icon{ background: linear-gradient(145deg, #a8ffff, #00bcd4); box-shadow: 0 0 12px rgba(0, 245, 255, 0.5), 0 3px 8px rgba(0,0,0,0.35); }

  .controls {
    display: flex;
    gap: 10px;
    align-items: stretch;
    margin-bottom: 12px;
  }

  .bet-group {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .bet-row {
    display: flex;
    gap: 6px;
    align-items: center;
  }

  .btn {
    border: none;
    cursor: pointer;
    font-family: inherit;
    font-weight: 700;
    border-radius: 10px;
    transition: transform 0.1s, filter 0.15s, box-shadow 0.15s;
    user-select: none;
  }

  .btn:active:not(:disabled) { transform: scale(0.96); }
  .btn:disabled { opacity: 0.45; cursor: not-allowed; }

  .btn-small {
    width: 40px;
    height: 40px;
    font-size: 1.3rem;
    background: linear-gradient(180deg, #5a3d7a, #3d2858);
    color: var(--cream);
    border: 2px solid #7a5a9a;
  }

  .btn-small:hover:not(:disabled) {
    filter: brightness(1.15);
    box-shadow: 0 0 12px rgba(255, 45, 149, 0.3);
  }

  .bet-display {
    flex: 1;
    text-align: center;
    background: #0a0612;
    border-radius: 10px;
    padding: 6px 4px;
    border: 2px solid #3d2a5c;
  }

  .bet-display label {
    display: block;
    font-size: 0.6rem;
    color: #9a8ab8;
    letter-spacing: 0.12em;
  }

  .bet-display span {
    font-size: 1.2rem;
    color: var(--gold);
    font-weight: 700;
  }

  .btn-spin {
    flex: 1.2;
    font-size: 1.35rem;
    letter-spacing: 0.15em;
    background: linear-gradient(180deg, #ff4d6d 0%, var(--red) 40%, var(--red-dark) 100%);
    color: #fff;
    border: 3px solid #ff8a9a;
    text-shadow: 0 2px 0 rgba(0, 0, 0, 0.3);
    box-shadow:
      0 6px 0 #6b0014,
      0 8px 20px rgba(196, 30, 58, 0.4);
    min-height: 86px;
  }

  .btn-spin:hover:not(:disabled) {
    filter: brightness(1.08);
    box-shadow:
      0 6px 0 #6b0014,
      0 8px 28px rgba(255, 45, 149, 0.55);
  }

  .btn-spin:active:not(:disabled) {
    box-shadow: 0 2px 0 #6b0014, 0 4px 10px rgba(196, 30, 58, 0.4);
    transform: translateY(4px);
  }

  .btn-spin.spinning {
    animation: spin-glow 0.5s ease infinite alternate;
  }

  @keyframes spin-glow {
    from { box-shadow: 0 6px 0 #6b0014, 0 0 15px rgba(255, 45, 149, 0.3); }
    to   { box-shadow: 0 6px 0 #6b0014, 0 0 30px rgba(0, 245, 255, 0.5); }
  }

  .msg {
    text-align: center;
    min-height: 28px;
    font-size: 0.95rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    color: var(--cyan);
    margin-bottom: 10px;
    transition: color 0.2s;
  }

  .msg.win  { color: #7dff9a; text-shadow: 0 0 12px rgba(125, 255, 154, 0.5); }
  .msg.big  { color: var(--gold); font-size: 1.1rem; animation: pulse 0.5s ease infinite; }
  .msg.lose { color: #c9b8d8; }

  .paytable {
    background: rgba(0, 0, 0, 0.35);
    border-radius: 10px;
    padding: 10px 12px;
    border: 1px solid #3d2a5c;
    font-size: 0.72rem;
  }

  .paytable h3 {
    text-align: center;
    font-size: 0.7rem;
    letter-spacing: 0.2em;
    color: var(--gold);
    margin-bottom: 8px;
    text-transform: uppercase;
  }

  .pays {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4px 12px;
  }

  .pay {
    display: flex;
    align-items: center;
    gap: 6px;
    color: #c9b8d8;
  }

  .pay .p-sym {
    font-size: 0.95rem;
    width: 1.4em;
    text-align: center;
  }

  .pay .p-mul {
    color: var(--gold);
    font-weight: 700;
    margin-left: auto;
  }

  .lever {
    position: absolute;
    right: -28px;
    top: 38%;
    width: 22px;
    height: 90px;
    display: none;
  }

  @media (min-width: 520px) {
    .lever {
      display: block;
    }
  }

  .lever-arm {
    width: 10px;
    height: 70px;
    background: linear-gradient(90deg, #888, #ddd, #888);
    margin: 0 auto;
    border-radius: 4px;
    transform-origin: bottom center;
    transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 2px 0 4px rgba(0,0,0,0.4);
  }

  .lever-arm.pulled { transform: rotate(25deg); }

  .lever-ball {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, #ff6b8a, var(--red-dark));
    margin: -4px auto 0;
    box-shadow: 0 2px 6px rgba(0,0,0,0.5);
    border: 2px solid #ff8a9a;
  }

  .lever-base {
    width: 18px;
    height: 16px;
    background: linear-gradient(180deg, #666, #333);
    margin: 0 auto;
    border-radius: 3px;
  }

  .confetti {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 100;
    overflow: hidden;
  }

  .particle {
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 2px;
    top: -10px;
    animation: fall linear forwards;
  }

  @keyframes fall {
    to {
      transform: translateY(100vh) rotate(720deg);
      opacity: 0;
    }
  }

  .mute-btn {
    position: absolute;
    top: 14px;
    right: 14px;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid #5a3d7a;
    color: var(--cream);
    font-size: 1rem;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 2;
  }

  .mute-btn:hover { background: rgba(0, 0, 0, 0.5); }
</style>
</head>
<body>
  <div class="machine" id="machine">
    <button class="mute-btn" id="muteBtn" title="Toggle sound" aria-label="Toggle sound">🔊</button>
    <div class="title">Lucky Reels</div>
    <div class="subtitle">★ Casino Royale ★</div>

    <div class="display">
      <div class="stat">
        <label>Credits</label>
        <value id="credits">100</value>
      </div>
      <div class="stat">
        <label>Last Win</label>
        <value id="lastWin">0</value>
      </div>
      <div class="stat">
        <label>Spins</label>
        <value id="spinCount">0</value>
      </div>
    </div>

    <div class="window">
      <div class="payline"></div>
      <div class="reels" id="reels">
        <div class="reel" data-reel="0"><div class="reel-strip" id="strip0"></div></div>
        <div class="reel" data-reel="1"><div class="reel-strip" id="strip1"></div></div>
        <div class="reel" data-reel="2"><div class="reel-strip" id="strip2"></div></div>
      </div>
    </div>

    <div class="msg" id="msg">Place your bet & spin!</div>

    <div class="controls">
      <div class="bet-group">
        <div class="bet-row">
          <button class="btn btn-small" id="betDown" aria-label="Decrease bet">−</button>
          <div class="bet-display">
            <label>Bet</label>
            <span id="betVal">1</span>
          </div>
          <button class="btn btn-small" id="betUp" aria-label="Increase bet">+</button>
        </div>
        <div class="bet-row">
          <button class="btn btn-small" id="betMin" style="flex:1;width:auto;font-size:0.7rem;">MIN</button>
          <button class="btn btn-small" id="betMax" style="flex:1;width:auto;font-size:0.7rem;">MAX</button>
        </div>
      </div>
      <button class="btn btn-spin" id="spinBtn">SPIN</button>
    </div>

    <div class="paytable">
      <h3>Paytable × Bet</h3>
      <div class="pays" id="pays"></div>
    </div>

    <div class="lever" id="lever" aria-hidden="true">
      <div class="lever-arm" id="leverArm">
        <div class="lever-ball"></div>
      </div>
      <div class="lever-base"></div>
    </div>
  </div>

  <div class="confetti" id="confetti"></div>

<script>
(function () {
  "use strict";

  const SYMBOLS = [
    { id: "cherry",  emoji: "🍒", cls: "sym-cherry",  weight: 28, mult: 2  },
    { id: "lemon",   emoji: "🍋", cls: "sym-lemon",   weight: 24, mult: 3  },
    { id: "orange",  emoji: "🍊", cls: "sym-orange",  weight: 20, mult: 4  },
    { id: "plum",    emoji: "🍇", cls: "sym-plum",    weight: 16, mult: 6  },
    { id: "bell",    emoji: "🔔", cls: "sym-bell",    weight: 12, mult: 10 },
    { id: "bar",     emoji: "BAR",cls: "sym-bar",     weight: 8,  mult: 15 },
    { id: "seven",   emoji: "7",  cls: "sym-seven",   weight: 5,  mult: 50 },
    { id: "diamond", emoji: "💎", cls: "sym-diamond", weight: 3,  mult: 100}
  ];

  const SYM_H = 70;
  const VISIBLE = 3;
  const STRIP_LEN = 28;
  const BETS = [1, 2, 5, 10, 20, 50];

  let credits = 100;
  let betIdx = 0;
  let spinning = false;
  let muted = false;
  let spins = 0;
  let audioCtx = null;

  const strips = [0, 1, 2].map(() => []);
  const stripEls = [
    document.getElementById("strip0"),
    document.getElementById("strip1"),
    document.getElementById("strip2")
  ];
  const creditsEl = document.getElementById("credits");
  const lastWinEl = document.getElementById("lastWin");
  const spinCountEl = document.getElementById("spinCount");
  const betValEl = document.getElementById("betVal");
  const msgEl = document.getElementById("msg");
  const spinBtn = document.getElementById("spinBtn");
  const muteBtn = document.getElementById("muteBtn");
  const leverArm = document.getElementById("leverArm");

  function ensureAudio() {
    if (!audioCtx) {
      audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    }
    if (audioCtx.state === "suspended") audioCtx.resume();
    return audioCtx;
  }

  function tone(freq, dur, type, gain, when) {
    if (muted) return;
    const ctx = ensureAudio();
    const t = (when || 0) + ctx.currentTime;
    const o = ctx.createOscillator();
    const g = ctx.createGain();
    o.type = type || "square";
    o.frequency.setValueAtTime(freq, t);
    g.gain.setValueAtTime(gain || 0.08, t);
    g.gain.exponentialRampToValueAtTime(0.001, t + dur);
    o.connect(g);
    g.connect(ctx.destination);
    o.start(t);
    o.stop(t + dur + 0.02);
  }

  function noiseBurst(dur, gain) {
    if (muted) return;
    const ctx = ensureAudio();
    const n = Math.floor(ctx.sampleRate * dur);
    const buf = ctx.createBuffer(1, n, ctx.sampleRate);
    const data = buf.getChannelData(0);
    for (let i = 0; i < n; i++) data[i] = (Math.random() * 2 - 1) * (1 - i / n);
    const src = ctx.createBufferSource();
    const g = ctx.createGain();
    const f = ctx.createBiquadFilter();
    f.type = "bandpass";
    f.frequency.value = 1200;
    src.buffer = buf;
    g.gain.value = gain || 0.12;
    src.connect(f);
    f.connect(g);
    g.connect(ctx.destination);
    src.start();
  }

  const SFX = {
    click() { tone(600, 0.05, "square", 0.05); },
    tick()  { tone(180 + Math.random() * 40, 0.04, "triangle", 0.04); },
    stop()  { tone(220, 0.08, "square", 0.07); tone(330, 0.1, "square", 0.04, 0.05); },
    spin()  {
      for (let i = 0; i < 6; i++) tone(120 + i * 30, 0.06, "sawtooth", 0.03, i * 0.03);
      noiseBurst(0.12, 0.06);
    },
    smallWin() {
      [523, 659, 784].forEach((f, i) => tone(f, 0.15, "sine", 0.08, i * 0.1));
    },
    bigWin() {
      [392, 523, 659, 784, 1046].forEach((f, i) => {
        tone(f, 0.22, "sine", 0.09, i * 0.12);
        tone(f * 1.5, 0.18, "triangle", 0.04, i * 0.12);
      });
    },
    jackpot() {
      for (let i = 0; i < 16; i++) {
        tone(300 + (i % 8) * 80, 0.12, "square", 0.07, i * 0.08);
        tone(400 + (i % 5) * 100, 0.1, "sine", 0.05, i * 0.08 + 0.04);
      }
    },
    lose() {
      tone(300, 0.15, "sawtooth", 0.05);
      tone(200, 0.2, "sawtooth", 0.04, 0.12);
    },
    coin() {
      tone(980, 0.08, "sine", 0.06);
      tone(1318, 0.12, "sine", 0.05, 0.06);
    },
    error() {
      tone(140, 0.18, "square", 0.07);
    }
  };

  function pickWeighted() {
    const total = SYMBOLS.reduce((s, x) => s + x.weight, 0);
    let r = Math.random() * total;
    for (const s of SYMBOLS) {
      r -= s.weight;
      if (r <= 0) return s;
    }
    return SYMBOLS[0];
  }

  function buildStrip(forcedCenter) {
    const strip = [];
    for (let i = 0; i < STRIP_LEN; i++) strip.push(pickWeighted());
    if (forcedCenter) {
      // Center position after stop is index (STRIP_LEN - 2) making middle visible row that symbol
      strip[STRIP_LEN - 2] = forcedCenter;
    }
    return strip;
  }

  function renderStrip(el, strip) {
    el.innerHTML = strip.map((s) => {
      return `<div class="symbol ${s.cls}"><div class="icon">${s.emoji}</div></div>`;
    }).join("");
  }

  function initStrips() {
    for (let r = 0; r < 3; r++) {
      strips[r] = buildStrip();
      renderStrip(stripEls[r], strips[r]);
      // Align so center symbol is mid-window: offset so row STRIP_LEN-2 is at center
      const y = -(STRIP_LEN - 2 - 1) * SYM_H;
      stripEls[r].style.transition = "none";
      stripEls[r].style.transform = `translateY(${y}px)`;
    }
  }

  function updateUI() {
    creditsEl.textContent = credits;
    betValEl.textContent = BETS[betIdx];
    spinCountEl.textContent = spins;
    spinBtn.disabled = spinning || credits < BETS[betIdx];
    document.getElementById("betUp").disabled = spinning || betIdx >= BETS.length - 1;
    document.getElementById("betDown").disabled = spinning || betIdx <= 0;
    document.getElementById("betMin").disabled = spinning;
    document.getElementById("betMax").disabled = spinning;
  }

  function setMsg(text, cls) {
    msgEl.textContent = text;
    msgEl.className = "msg" + (cls ? " " + cls : "");
  }

  function confettiBurst(count) {
    const box = document.getElementById("confetti");
    const colors = ["#ffd700", "#ff2d95", "#00f5ff", "#7dff9a", "#ff6b6b", "#c79bff"];
    for (let i = 0; i < count; i++) {
      const p = document.createElement("div");
      p.className = "particle";
      p.style.left = Math.random() * 100 + "%";
      p.style.background = colors[i % colors.length];
      p.style.animationDuration = (1.2 + Math.random() * 1.8) + "s";
      p.style.animationDelay = (Math.random() * 0.4) + "s";
      p.style.width = (6 + Math.random() * 8) + "px";
      p.style.height = p.style.width;
      box.appendChild(p);
      setTimeout(() => p.remove(), 3500);
    }
  }

  function centerSymbol(strip) {
    // With offset translateY(-(STRIP_LEN-2-1)*SYM_H), top row shown is index STRIP_LEN-3,
    // middle is STRIP_LEN-2, bottom is STRIP_LEN-1.
    return strip[STRIP_LEN - 2];
  }

  function animateReel(reelIdx, strip, duration) {
    return new Promise((resolve) => {
      const el = stripEls[reelIdx];
      const loops = 4 + reelIdx; // staggered extra rotations
      const finalY = -(STRIP_LEN - 2 - 1) * SYM_H;
      const startY = finalY + loops * STRIP_LEN * SYM_H;

      renderStrip(el, strip);
      el.style.transition = "none";
      el.style.transform = `translateY(${startY}px)`;

      // force reflow
      void el.offsetHeight;

      el.style.transition = `transform ${duration}ms cubic-bezier(0.12, 0.75, 0.15, 1)`;
      el.style.transform = `translateY(${finalY}px)`;

      // tick sounds while spinning
      const tickEvery = 80;
      let ticks = 0;
      const maxTicks = Math.floor(duration / tickEvery) - 2;
      const tickTimer = setInterval(() => {
        ticks++;
        SFX.tick();
        if (ticks >= maxTicks) clearInterval(tickTimer);
      }, tickEvery);

      setTimeout(() => {
        clearInterval(tickTimer);
        SFX.stop();
        el.style.transition = "none";
        resolve();
      }, duration + 30);
    });
  }

  function evaluate(results) {
    const [a, b, c] = results;
    if (a.id === b.id && b.id === c.id) {
      return { mult: a.mult, kind: "three", sym: a };
    }
    // two of a kind partial pays for high symbols
    if (a.id === b.id || b.id === c.id || a.id === c.id) {
      const twin = a.id === b.id ? a : b.id === c.id ? b : a;
      if (twin.mult >= 10) {
        return { mult: Math.max(1, Math.floor(twin.mult / 5)), kind: "two", sym: twin };
      }
    }
    // any two sevens or diamonds or any diamond + seven
    const specials = results.filter((s) => s.id === "seven" || s.id === "diamond");
    if (specials.length >= 2) {
      return { mult: 5, kind: "mix", sym: specials[0] };
    }
    return { mult: 0, kind: "none", sym: null };
  }

  async function spin() {
    if (spinning) return;
    ensureAudio();

    const bet = BETS[betIdx];
    if (credits < bet) {
      SFX.error();
      setMsg("Not enough credits!", "lose");
      return;
    }

    spinning = true;
    credits -= bet;
    spins++;
    lastWinEl.textContent = "0";
    lastWinEl.classList.remove("win-flash");
    setMsg("Good luck…", "");
    spinBtn.classList.add("spinning");
    leverArm.classList.add("pulled");
    setTimeout(() => leverArm.classList.remove("pulled"), 280);
    updateUI();
    SFX.spin();

    // Decide outcomes first
    const results = [pickWeighted(), pickWeighted(), pickWeighted()];
    // Slight bias boost if player is very low (keep game fun) – soft pity every 12+ dry spins
    // (no state tracked beyond credits – pure RNG as base)

    const durations = [1600, 2200, 2900];
    const newStrips = results.map((r) => buildStrip(r));
    newStrips.forEach((s, i) => { strips[i] = s; });

    await Promise.all(newStrips.map((s, i) => animateReel(i, s, durations[i])));

    const outcome = evaluate(results);
    const win = outcome.mult * bet;

    if (win > 0) {
      credits += win;
      lastWinEl.textContent = win;
      lastWinEl.classList.add("win-flash");
      if (outcome.mult >= 50) {
        SFX.jackpot();
        confettiBurst(60);
        setMsg(`💎 JACKPOT! ${outcome.sym.emoji} ×3 — won ${win}!`, "big");
      } else if (outcome.mult >= 10) {
        SFX.bigWin();
        confettiBurst(30);
        setMsg(`Big win! ${outcome.sym.emoji} — ${win} credits!`, "big");
      } else {
        SFX.smallWin();
        confettiBurst(12);
        setMsg(
          outcome.kind === "two"
            ? `Pair of ${outcome.sym.emoji} — won ${win}!`
            : outcome.kind === "mix"
              ? `Hot mix! — won ${win}`
              : `Winner! ${outcome.sym.emoji}${outcome.sym.emoji}${outcome.sym.emoji} +${win}`,
          "win"
        );
      }
      SFX.coin();
    } else {
      SFX.lose();
      setMsg("No luck — try again!", "lose");
    }

    if (credits === 0) {
      setTimeout(() => {
        setMsg("Busted! Here's 20 free credits 🎰", "win");
        credits = 20;
        SFX.coin();
        updateUI();
      }, 800);
    }

    spinning = false;
    spinBtn.classList.remove("spinning");
    updateUI();
  }

  // Paytable UI
  (function buildPaytable() {
    const el = document.getElementById("pays");
    el.innerHTML = SYMBOLS.slice().reverse().map((s) =>
      `<div class="pay"><span class="p-sym">${s.emoji}</span><span>×3</span><span class="p-mul">×${s.mult}</span></div>`
    ).join("");
  })();

  // Events
  spinBtn.addEventListener("click", spin);
  document.getElementById("lever").addEventListener("click", spin);

  document.getElementById("betUp").addEventListener("click", () => {
    if (spinning || betIdx >= BETS.length - 1) return;
    betIdx++;
    SFX.click();
    updateUI();
  });
  document.getElementById("betDown").addEventListener("click", () => {
    if (spinning || betIdx <= 0) return;
    betIdx--;
    SFX.click();
    updateUI();
  });
  document.getElementById("betMin").addEventListener("click", () => {
    if (spinning) return;
    betIdx = 0;
    SFX.click();
    updateUI();
  });
  document.getElementById("betMax").addEventListener("click", () => {
    if (spinning) return;
    // max affordable
    let i = BETS.length - 1;
    while (i > 0 && BETS[i] > credits) i--;
    betIdx = i;
    SFX.click();
    updateUI();
  });

  muteBtn.addEventListener("click", () => {
    muted = !muted;
    muteBtn.textContent = muted ? "🔇" : "🔊";
    if (!muted) SFX.click();
  });

  document.addEventListener("keydown", (e) => {
    if (e.code === "Space" || e.code === "Enter") {
      e.preventDefault();
      spin();
    } else if (e.code === "ArrowUp") {
      document.getElementById("betUp").click();
    } else if (e.code === "ArrowDown") {
      document.getElementById("betDown").click();
    }
  });

  initStrips();
  updateUI();
})();
</script>
</body>
</html>