Files

197 lines
6.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Tapdown vs CPU — Cyber Sale Edition</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-slate-950 text-white min-h-screen flex flex-col">
<!-- Top bar -->
<header class="max-w-5xl mx-auto w-full px-4 py-4 flex items-center justify-between">
<div class="flex items-center gap-2">
<span class="text-sm text-slate-400">Player:</span>
<span class="font-semibold">{{ name }}</span>
<span class="mx-2 opacity-50">·</span>
<span class="text-sm text-slate-400">Persona:</span>
<span class="font-semibold">{{ persona }}</span>
</div>
<div class="flex items-center gap-2">
<label class="text-sm opacity-80">Difficulty</label>
<select id="difficulty" class="text-black p-1 rounded">
<option value="easy">Easy</option>
<option value="normal" selected>Normal</option>
<option value="hard">Hard</option>
<option value="insane">Insane</option>
</select>
</div>
</header>
<!-- Arena -->
<main class="max-w-5xl mx-auto w-full px-4 flex-1">
<div class="mb-3 text-center text-slate-300 text-sm">
Beat the <b>Super Saver</b> bot — then browse Cyber Sale deals!
</div>
<div class="relative h-44 rounded-2xl bg-slate-900/60 border border-slate-800 overflow-hidden">
<div class="absolute inset-y-0 left-1/2 w-0.5 bg-slate-700"></div>
<div class="absolute inset-y-0 left-0 w-1 bg-emerald-500"></div>
<div class="absolute inset-y-0 right-0 w-1 bg-cyan-500"></div>
<div id="puck" class="absolute top-1/2 -translate-y-1/2 w-6 h-6 rounded-full bg-white shadow"></div>
</div>
<p id="status" class="mt-3 text-center text-lg"></p>
<div class="grid grid-cols-2 gap-4 mt-6">
<button id="tapLeft" class="text-2xl py-12 rounded-xl bg-emerald-600 active:scale-95 font-semibold">YOU TAP</button>
<button id="tapRight" class="text-2xl py-12 rounded-xl bg-cyan-700 opacity-60 cursor-not-allowed font-semibold" disabled>CPU</button>
</div>
<div class="mt-6 flex items-center justify-center gap-3">
<button id="startBtn" class="bg-emerald-500 hover:bg-emerald-600 px-5 py-2 rounded-lg font-semibold">Start</button>
<button id="resetBtn" class="bg-slate-800 hover:bg-slate-700 px-5 py-2 rounded-lg font-semibold">Reset</button>
<a id="shopBtn" href="/"
class="bg-slate-900 border border-slate-700 px-5 py-2 rounded-lg font-semibold">Back</a>
</div>
<!-- Cyber Sale CTA under the arena -->
<div class="mt-8 text-center">
<a href="{{ url_for('index') }}" class="text-slate-300 underline decoration-dotted">Cyber Sale details & QR on the promo page</a>
</div>
</main>
<footer class="max-w-5xl mx-auto w-full px-4 py-6 text-center text-slate-400 text-xs">
Tip: Holding the button auto-taps. Bursts & fatigue make the bot feel human.
</footer>
<script>
/* --- Pull persona from template to lightly tweak the feel --- */
const PERSONA = "{{ persona }}";
/* --- Physics constants --- */
const EDGE = 100.0;
const BASE_IMPULSE = 7.0;
let FRICTION = 0.92;
const TICK_HZ = 60;
/* Persona perk (very light-touch) */
let leftImpulseMult = 1.0;
if (PERSONA === "Speed Demon") leftImpulseMult = 1.02;
if (PERSONA === "Tank") FRICTION = 0.91;
/* --- Game state --- */
let puck = 0.0, vel = 0.0, running = false;
let lastTapLeft = 0, lastTapRight = 0;
let MIN_TAP_MS = 85;
/* --- CPU model --- */
const DIFF = {
easy: { baseHz: 4.8, burstHz: 7.2, burstProb: 0.20, whiff: 0.08, friction: 0.92 },
normal: { baseHz: 6.6, burstHz: 9.2, burstProb: 0.28, whiff: 0.05, friction: 0.92 },
hard: { baseHz: 8.2, burstHz: 11.5, burstProb: 0.35, whiff: 0.035, friction: 0.91 },
insane: { baseHz: 9.8, burstHz: 13.5, burstProb: 0.45, whiff: 0.02, friction: 0.905 },
};
let cpuCfg = DIFF.normal;
let cpuBurstUntil = 0;
let cpuNextTapAt = 0;
/* --- UI elements --- */
const statusEl = document.getElementById("status");
const puckEl = document.getElementById("puck");
const leftBtn = document.getElementById("tapLeft");
const startBtn = document.getElementById("startBtn");
const resetBtn = document.getElementById("resetBtn");
const diffSel = document.getElementById("difficulty");
/* --- Helpers --- */
function flash(msg){ statusEl.textContent = msg; setTimeout(()=>statusEl.textContent="", 1200); }
function setDifficulty(key){
cpuCfg = DIFF[key] || DIFF.normal;
FRICTION = cpuCfg.friction;
}
diffSel.onchange = () => { setDifficulty(diffSel.value); flash(`Difficulty: ${diffSel.value.toUpperCase()}`); };
/* --- Controls --- */
leftBtn.addEventListener("pointerdown", (e) => {
e.preventDefault(); tap("left");
const t = setInterval(()=> tap("left"), 100);
const stop = ()=>{ clearInterval(t); window.removeEventListener("pointerup", stop); leftBtn.removeEventListener("pointerleave", stop); };
window.addEventListener("pointerup", stop, { once: true });
leftBtn.addEventListener("pointerleave", stop, { once: true });
});
startBtn.onclick = startMatch;
resetBtn.onclick = resetMatch;
/* --- Game functions --- */
function startMatch(){
resetMatch();
running = true;
scheduleCpuTap(performance.now());
flash("Fight!");
}
function resetMatch(){
running = false; vel = 0; puck = 0; updatePuck(puck);
statusEl.textContent = "";
}
function tap(side){
if(!running) return;
const now = performance.now();
if(side === "left"){
if(now - lastTapLeft < MIN_TAP_MS) return;
lastTapLeft = now;
vel -= BASE_IMPULSE * leftImpulseMult;
} else {
if(now - lastTapRight < MIN_TAP_MS) return;
lastTapRight = now;
vel += BASE_IMPULSE;
}
}
/* --- CPU scheduling --- */
function scheduleCpuTap(now){
if(now > cpuBurstUntil && Math.random() < cpuCfg.burstProb){
cpuBurstUntil = now + (300 + Math.random()*400);
}
const inBurst = now < cpuBurstUntil;
const hz = inBurst ? cpuCfg.burstHz : cpuCfg.baseHz;
const intervalMs = 1000 * ( -Math.log(1 - Math.random()) / hz );
cpuNextTapAt = now + intervalMs;
}
function maybeCpuTap(now){
if(now < cpuNextTapAt) return;
if(Math.random() > cpuCfg.whiff) tap("right");
scheduleCpuTap(now);
}
/* --- Loop --- */
function tick(){
const dt = 1.0 / TICK_HZ;
if(running){
vel *= FRICTION;
puck += vel * dt * 6.0;
const now = performance.now();
maybeCpuTap(now);
if(puck <= -EDGE || puck >= EDGE){
running = false;
statusEl.textContent = puck <= -EDGE ? "YOU WIN! 🥳" : "CPU WINS! 🤖";
}
}
updatePuck(puck);
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
function updatePuck(p){
const track = document.querySelector(".relative.h-44");
const w = track.clientWidth;
const x = (p / 100) * (w/2 - 10);
puckEl.style.left = `calc(50% + ${x}px)`;
}
/* init */
setDifficulty("normal");
</script>
</body>
</html>