🎮ArcadeLab

Cosmic Luck Simulator

by PhantomOtter56
798 lines12.8 KB
▶ Play
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>Cosmic Luck Simulator</title>

<style>
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}

html,body{
 margin:0;
 min-height:100%;
 font-family:Arial,sans-serif;
 color:white;
 background:
 radial-gradient(circle at 50% 15%,#302b70,#11142f 45%,#050612 100%);
}

body{overflow-x:hidden}

button{
 font:inherit;
 touch-action:manipulation;
}

header{
 height:70px;
 display:flex;
 align-items:center;
 justify-content:space-between;
 padding:0 18px;
 background:#08091dcc;
 border-bottom:1px solid #ffffff18;
 position:sticky;
 top:0;
 z-index:10;
 backdrop-filter:blur(10px);
}

.logo{
 font-size:clamp(19px,5vw,30px);
 font-weight:900;
}

.logo span{color:#9c7cff}

.money{
 background:#ffffff12;
 border:1px solid #ffffff12;
 padding:10px 15px;
 border-radius:14px;
 font-weight:bold;
 font-size:18px;
}

main{
 width:min(900px,94%);
 margin:auto;
 padding:30px 0 60px;
}

h1{
 text-align:center;
 font-size:clamp(35px,9vw,65px);
 margin:10px 0;
 background:linear-gradient(90deg,#fff,#9c8aff,#65eaff);
 -webkit-background-clip:text;
 color:transparent;
}

.subtitle{
 text-align:center;
 color:#a9acd0;
}

.spinnerWrap{
 display:flex;
 justify-content:center;
 align-items:center;
 position:relative;
 margin:35px 0 25px;
}

.pointer{
 position:absolute;
 top:-20px;
 z-index:5;
 font-size:40px;
 filter:drop-shadow(0 3px 5px #000);
}

.spinner{
 width:min(72vw,350px);
 height:min(72vw,350px);
 border-radius:50%;
 padding:11px;
 background:conic-gradient(
 #ff3838,
 #ff9d38,
 #fff038,
 #48ff67,
 #38ddff,
 #6370ff,
 #b33cff,
 #ff3cae,
 #ff3838
 );
 box-shadow:0 0 45px #7660ff66;
 transition:transform 2.2s cubic-bezier(.12,.78,.15,1);
}

.spinnerInside{
 width:100%;
 height:100%;
 border-radius:50%;
 display:flex;
 flex-direction:column;
 align-items:center;
 justify-content:center;
 background:radial-gradient(circle,#292953,#090a1c);
 border:3px solid #ffffff18;
}

.dice{
 font-size:65px;
}

.spinLabel{
 font-size:27px;
 font-weight:900;
}

.result{
 text-align:center;
 min-height:135px;
}

.chance{
 font-size:clamp(45px,12vw,75px);
 font-weight:1000;
}

.name{
 font-size:clamp(20px,5vw,30px);
 font-weight:900;
 letter-spacing:1px;
}

.reward{
 color:#6dff96;
 font-weight:bold;
 margin-top:8px;
}

#roll{
 display:block;
 width:min(500px,95%);
 margin:10px auto 30px;
 min-height:72px;
 border:0;
 border-radius:20px;
 color:white;
 background:linear-gradient(135deg,#6547ff,#a05cff);
 font-size:30px;
 font-weight:1000;
 box-shadow:0 12px 35px #6345ff55;
 transition:transform .1s,filter .1s;
}

#roll:active{
 transform:scale(.95);
}

#roll:disabled{
 opacity:.5;
}

.stats{
 display:grid;
 grid-template-columns:repeat(3,1fr);
 gap:12px;
}

.stat{
 background:#ffffff0d;
 border:1px solid #ffffff10;
 border-radius:17px;
 padding:18px 10px;
 text-align:center;
}

.statValue{
 font-size:25px;
 font-weight:900;
}

.statLabel{
 color:#969abd;
 font-size:12px;
 margin-top:5px;
}

h2{
 text-align:center;
 margin-top:40px;
}

.shop{
 display:grid;
 grid-template-columns:repeat(3,1fr);
 gap:13px;
}

.card{
 background:#ffffff0d;
 border:1px solid #ffffff10;
 border-radius:18px;
 padding:20px;
 text-align:center;
}

.card h3{
 margin:0 0 8px;
}

.card p{
 color:#a5a8c6;
 min-height:38px;
}

.buy{
 width:100%;
 padding:13px;
 border:0;
 border-radius:12px;
 background:#29255b;
 color:white;
 font-weight:bold;
}

.buy:disabled{
 opacity:.4;
}

.rarities{
 margin-top:40px;
 display:grid;
 grid-template-columns:repeat(2,1fr);
 gap:10px;
}

.rarity{
 display:flex;
 justify-content:space-between;
 padding:14px;
 border-radius:12px;
 background:#ffffff0a;
}

.rarity strong{
 font-size:14px;
}

.rarity span{
 color:#aaaed0;
}

#popup{
 position:fixed;
 inset:0;
 display:flex;
 align-items:center;
 justify-content:center;
 pointer-events:none;
 z-index:50;
}

#popupText{
 font-size:clamp(35px,10vw,80px);
 font-weight:1000;
 text-align:center;
 opacity:0;
 transform:scale(.5);
}

.pop{
 animation:pop .9s ease forwards;
}

@keyframes pop{
 0%{opacity:0;transform:scale(.5)}
 25%{opacity:1;transform:scale(1.15)}
 70%{opacity:1;transform:scale(1)}
 100%{opacity:0;transform:scale(1.35)}
}

.particle{
 position:fixed;
 width:8px;
 height:8px;
 border-radius:50%;
 pointer-events:none;
 z-index:100;
 animation:fly 900ms ease-out forwards;
}

@keyframes fly{
 from{
  transform:translate(0,0);
  opacity:1;
 }
 to{
  transform:translate(var(--x),var(--y));
  opacity:0;
 }
}

@media(max-width:700px){
 .stats{grid-template-columns:1fr}
 .shop{grid-template-columns:1fr}
 .rarities{grid-template-columns:1fr}
 main{padding-top:20px}
}
</style>
</head>

<body>

<header>
 <div class="logo">COSMIC<span>LUCK</span> 🎲</div>
 <div class="money">💰 <span id="money">100</span></div>
</header>

<main>

<h1>LUCK SIMULATOR</h1>
<p class="subtitle">
 Roll your luck and discover the rarest percentages!
</p>

<div class="spinnerWrap">
 <div class="pointer">▼</div>

 <div class="spinner" id="spinner">
  <div class="spinnerInside">
   <div class="dice">🎲</div>
   <div class="spinLabel">ROLL</div>
  </div>
 </div>
</div>

<div class="result">
 <div class="chance" id="chance">?</div>
 <div class="name" id="name">READY?</div>
 <div class="reward" id="reward">
  Tap ROLL to begin!
 </div>
</div>

<button id="roll">🎲 ROLL</button>

<div class="stats">

 <div class="stat">
  <div class="statValue" id="rolls">0</div>
  <div class="statLabel">TOTAL ROLLS</div>
 </div>

 <div class="stat">
  <div class="statValue" id="luck">1.00x</div>
  <div class="statLabel">LUCK</div>
 </div>

 <div class="stat">
  <div class="statValue" id="best">—</div>
  <div class="statLabel">BEST</div>
 </div>

</div>

<h2>🍀 LUCK SHOP</h2>

<div class="shop">

 <div class="card">
  <h3>🍀 Lucky Boost</h3>
  <p>+0.25x Luck</p>
  <button class="buy" data-cost="100" data-add="0.25">
   💰 100
  </button>
 </div>

 <div class="card">
  <h3>✨ Super Luck</h3>
  <p>+0.75x Luck</p>
  <button class="buy" data-cost="500" data-add="0.75">
   💰 500
  </button>
 </div>

 <div class="card">
  <h3>🌌 Cosmic Luck</h3>
  <p>+2.00x Luck</p>
  <button class="buy" data-cost="2500" data-add="2">
   💰 2,500
  </button>
 </div>

</div>

<h2>📖 RARITY INDEX</h2>

<div class="rarities">

 <div class="rarity"><strong>COMMON</strong><span>100%</span></div>
 <div class="rarity"><strong>LUCKY</strong><span>90%</span></div>
 <div class="rarity"><strong>UNUSUAL</strong><span>75%</span></div>
 <div class="rarity"><strong>RARE</strong><span>60%</span></div>
 <div class="rarity"><strong>FORTUNE</strong><span>50%</span></div>
 <div class="rarity"><strong>EPIC</strong><span>40%</span></div>
 <div class="rarity"><strong>LEGENDARY</strong><span>30%</span></div>
 <div class="rarity"><strong>MYTHIC</strong><span>20%</span></div>
 <div class="rarity"><strong>DIVINE</strong><span>10%</span></div>
 <div class="rarity"><strong>CELESTIAL</strong><span>5%</span></div>
 <div class="rarity"><strong>GALACTIC</strong><span>3%</span></div>
 <div class="rarity"><strong>UNIVERSAL</strong><span>2%</span></div>
 <div class="rarity" style="grid-column:1/-1">
  <strong>🌌 COSMIC PERCENT</strong>
  <span>1%</span>
 </div>

</div>

</main>

<div id="popup">
 <div id="popupText"></div>
</div>

<script>
"use strict";

/* =========================
   GAME DATA
========================= */

const rarities = [
 {chance:100,name:"COMMON",reward:5},
 {chance:90,name:"LUCKY",reward:10},
 {chance:75,name:"UNUSUAL",reward:20},
 {chance:60,name:"RARE",reward:35},
 {chance:50,name:"FORTUNE",reward:55},
 {chance:40,name:"EPIC",reward:80},
 {chance:30,name:"LEGENDARY",reward:130},
 {chance:20,name:"MYTHIC",reward:220},
 {chance:10,name:"DIVINE",reward:500},
 {chance:5,name:"CELESTIAL",reward:1000},
 {chance:3,name:"GALACTIC",reward:2500},
 {chance:2,name:"UNIVERSAL",reward:6000},
 {chance:1,name:"COSMIC PERCENT",reward:15000}
];

/* =========================
   SAVE DATA
========================= */

let money = Number(localStorage.getItem("cosmic_money"));

if(!Number.isFinite(money)){
 money = 100;
}

let rolls = Number(localStorage.getItem("cosmic_rolls"));

if(!Number.isFinite(rolls)){
 rolls = 0;
}

let luck = Number(localStorage.getItem("cosmic_luck"));

if(!Number.isFinite(luck)){
 luck = 1;
}

let best = Number(localStorage.getItem("cosmic_best"));

if(!Number.isFinite(best)){
 best = 101;
}

let spinning = false;
let rotation = 0;

/* =========================
   ELEMENTS
========================= */

const moneyEl = document.getElementById("money");
const rollsEl = document.getElementById("rolls");
const luckEl = document.getElementById("luck");
const bestEl = document.getElementById("best");

const spinner = document.getElementById("spinner");
const rollButton = document.getElementById("roll");

const chanceEl = document.getElementById("chance");
const nameEl = document.getElementById("name");
const rewardEl = document.getElementById("reward");

const popupText = document.getElementById("popupText");

/* =========================
   SAVE
========================= */

function saveGame(){

 localStorage.setItem("cosmic_money",money);
 localStorage.setItem("cosmic_rolls",rolls);
 localStorage.setItem("cosmic_luck",luck);
 localStorage.setItem("cosmic_best",best);

}

/* =========================
   UI
========================= */

function updateUI(){

 moneyEl.textContent =
  Math.floor(money).toLocaleString();

 rollsEl.textContent =
  rolls.toLocaleString();

 luckEl.textContent =
  luck.toFixed(2) + "x";

 bestEl.textContent =
  best === 101 ? "—" : best + "%";

 document.querySelectorAll(".buy").forEach(button=>{

  const cost = Number(button.dataset.cost);

  button.disabled =
   spinning || money < cost;

 });

}

/* =========================
   ROLL SELECTION
========================= */

function chooseRoll(){

 /*
   Higher luck increases the chance
   of reaching the rarer entries.
 */

 const weights = rarities.map(item=>{

  const rarityPower =
   (101 - item.chance) + 1;

  return Math.pow(rarityPower,luck);

 });

 let total = 0;

 for(const weight of weights){
  total += weight;
 }

 let random = Math.random() * total;

 for(let i=0;i<rarities.length;i++){

  random -= weights[i];

  if(random <= 0){
   return rarities[i];
  }

 }

 return rarities[0];

}

/* =========================
   POPUP
========================= */

function popup(text){

 popupText.textContent = text;

 popupText.classList.remove("pop");

 /*
   Force the browser to notice the class
   being removed before adding it again.
 */

 void popupText.offsetWidth;

 popupText.classList.add("pop");

}

/* =========================
   PARTICLES
========================= */

function particles(){

 for(let i=0;i<35;i++){

  const p = document.createElement("div");

  p.className = "particle";

  p.style.left =
   window.innerWidth / 2 + "px";

  p.style.top =
   window.innerHeight / 2 + "px";

  p.style.setProperty(
   "--x",
   ((Math.random()-.5)*500) + "px"
  );

  p.style.setProperty(
   "--y",
   ((Math.random()-.5)*500) + "px"
  );

  document.body.appendChild(p);

  setTimeout(()=>{
   p.remove();
  },900);

 }

}

/* =========================
   MAIN ROLL
========================= */

function doRoll(){

 if(spinning){
  return;
 }

 spinning = true;

 rollButton.disabled = true;

 chanceEl.textContent = "...";
 nameEl.textContent = "SPINNING...";
 rewardEl.textContent = "Good luck!";

 const result = chooseRoll();

 /*
   Make the spinner rotate several full times.
 */

 rotation +=
  1800 +
  Math.floor(Math.random()*900);

 spinner.style.transform =
  "rotate(" + rotation + "deg)";

 /*
   Wait for the visual spin to finish.
 */

 setTimeout(()=>{

  rolls++;

  const earned =
   Math.max(1,Math.floor(result.reward * luck));

  money += earned;

  if(result.chance < best){
   best = result.chance;
  }

  chanceEl.textContent =
   result.chance + "%";

  nameEl.textContent =
   result.name;

  rewardEl.textContent =
   "+💰 " + earned.toLocaleString();

  popup(
   result.chance +
   "% " +
   result.name
  );

  if(result.chance <= 10){
   particles();
  }

  saveGame();

  spinning = false;

  updateUI();

 },2300);

}

/* =========================
   BUTTON EVENTS
========================= */

/*
   Using addEventListener means the
   button's click/tap handler is
   attached directly after the page
   has loaded.
*/

rollButton.addEventListener(
 "click",
 doRoll
);

/* =========================
   SHOP
========================= */

document.querySelectorAll(".buy").forEach(button=>{

 button.addEventListener("click",()=>{

  if(spinning){
   return;
  }

  const cost =
   Number(button.dataset.cost);

  const add =
   Number(button.dataset.add);

  if(money < cost){
   return;
  }

  money -= cost;

  luck += add;

  saveGame();

  updateUI();

  popup(
   "🍀 +" +
   add.toFixed(2) +
   "x LUCK!"
  );

 });

});

/* =========================
   START
========================= */

updateUI();

</script>

</body>
</html>

Game Source: Cosmic Luck Simulator

Creator: PhantomOtter56

Libraries: none

Complexity: complex (798 lines, 12.8 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: cosmic-luck-simulator-phantomotter56" to link back to the original. Then publish at arcadelab.ai/publish.