Slot Machine
Grok 4.5 · legacy

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>