init
This commit is contained in:
commit
75f77c14cd
272
index.html
Normal file
272
index.html
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>grr - The Timing Game</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;900&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
/* Custom styles for the game */
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
.key-indicator {
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
box-shadow: 0 0 15px rgba(255, 255, 255, 0);
|
||||||
|
}
|
||||||
|
.key-indicator.active {
|
||||||
|
transform: scale(1.1);
|
||||||
|
background-color: #4f46e5; /* Indigo 600 */
|
||||||
|
color: white;
|
||||||
|
box-shadow: 0 0 25px rgba(79, 70, 229, 0.7);
|
||||||
|
}
|
||||||
|
#dot-display {
|
||||||
|
word-break: break-all;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.fade-in {
|
||||||
|
animation: fadeIn 0.5s ease-in-out;
|
||||||
|
}
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-900 text-white flex items-center justify-center min-h-screen antialiased select-none">
|
||||||
|
|
||||||
|
<div id="game-container" class="w-full max-w-4xl mx-auto p-4 md:p-8 text-center">
|
||||||
|
|
||||||
|
<!-- Game Title -->
|
||||||
|
<h1 class="text-5xl md:text-7xl font-black text-indigo-400 mb-4">grr</h1>
|
||||||
|
<p class="text-gray-400 mb-8 text-lg">The Long-Press Timing Game</p>
|
||||||
|
|
||||||
|
<!-- Instructions and Status -->
|
||||||
|
<div id="status-container" class="min-h-[80px] flex flex-col items-center justify-center mb-6">
|
||||||
|
<p id="instructions" class="text-xl md:text-2xl text-gray-300 transition-opacity duration-300">
|
||||||
|
Hold down the <kbd id="dot-key-indicator" class="key-indicator inline-block bg-gray-700 rounded-md px-3 py-1 font-mono text-lg">.</kbd> key to begin
|
||||||
|
</p>
|
||||||
|
<p id="backspace-instructions" class="text-xl md:text-2xl text-gray-300 transition-opacity duration-300 opacity-0 absolute">
|
||||||
|
Now hold <kbd id="backspace-key-indicator" class="key-indicator inline-block bg-gray-700 rounded-md px-3 py-1 font-mono text-lg">Backspace</kbd> to match the count
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Counter Display -->
|
||||||
|
<div class="relative mb-6">
|
||||||
|
<div id="counter-display" class="text-7xl md:text-9xl font-extrabold text-white transition-transform duration-200">0</div>
|
||||||
|
<div id="target-counter" class="absolute top-0 right-0 -mt-4 -mr-2 bg-indigo-500 text-white text-xs font-bold px-2 py-1 rounded-full opacity-0 transition-opacity duration-300"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dot Display Area -->
|
||||||
|
<div class="bg-gray-800 rounded-lg p-4 min-h-[120px] w-full text-left text-indigo-300 shadow-inner">
|
||||||
|
<p id="dot-display"></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Result Screen -->
|
||||||
|
<div id="result-screen" class="absolute inset-0 bg-gray-900 bg-opacity-90 flex-col items-center justify-center hidden">
|
||||||
|
<div class="text-center p-8 rounded-lg fade-in">
|
||||||
|
<h2 id="result-title" class="text-5xl font-bold mb-4"></h2>
|
||||||
|
<p class="text-xl text-gray-400 mb-2">Your score vs. Target</p>
|
||||||
|
<p id="result-details" class="text-3xl font-mono mb-8"></p>
|
||||||
|
<button id="restart-button" class="bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-8 rounded-lg text-xl transition-transform transform hover:scale-105 focus:outline-none focus:ring-4 focus:ring-indigo-500">
|
||||||
|
Play Again
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// DOM Element References
|
||||||
|
const instructionsEl = document.getElementById('instructions');
|
||||||
|
const backspaceInstructionsEl = document.getElementById('backspace-instructions');
|
||||||
|
const counterDisplayEl = document.getElementById('counter-display');
|
||||||
|
const dotDisplayEl = document.getElementById('dot-display');
|
||||||
|
const resultScreenEl = document.getElementById('result-screen');
|
||||||
|
const resultTitleEl = document.getElementById('result-title');
|
||||||
|
const resultDetailsEl = document.getElementById('result-details');
|
||||||
|
const restartButton = document.getElementById('restart-button');
|
||||||
|
const dotKeyIndicator = document.getElementById('dot-key-indicator');
|
||||||
|
const backspaceKeyIndicator = document.getElementById('backspace-key-indicator');
|
||||||
|
const targetCounterEl = document.getElementById('target-counter');
|
||||||
|
|
||||||
|
// Game State
|
||||||
|
let gameState = 'waiting_for_dot'; // 'waiting_for_dot', 'dot_pressing', 'waiting_for_backspace', 'backspace_pressing', 'game_over'
|
||||||
|
let dotCount = 0;
|
||||||
|
let backspaceCount = 0;
|
||||||
|
let currentCount = 0;
|
||||||
|
let gameInterval = null;
|
||||||
|
let isKeyPressed = false;
|
||||||
|
const PRESS_INTERVAL = 40; // milliseconds, controls speed of dot generation/deletion
|
||||||
|
|
||||||
|
// --- Game Logic Functions ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the dot generation phase
|
||||||
|
*/
|
||||||
|
function startDotPress() {
|
||||||
|
if (gameState !== 'waiting_for_dot' || isKeyPressed) return;
|
||||||
|
isKeyPressed = true;
|
||||||
|
gameState = 'dot_pressing';
|
||||||
|
instructionsEl.textContent = 'Release when you want to stop';
|
||||||
|
dotKeyIndicator.classList.add('active');
|
||||||
|
|
||||||
|
gameInterval = setInterval(() => {
|
||||||
|
currentCount++;
|
||||||
|
dotDisplayEl.textContent += '.';
|
||||||
|
counterDisplayEl.textContent = currentCount;
|
||||||
|
}, PRESS_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends the dot generation phase and sets up for backspace
|
||||||
|
*/
|
||||||
|
function stopDotPress() {
|
||||||
|
if (gameState !== 'dot_pressing') return;
|
||||||
|
isKeyPressed = false;
|
||||||
|
clearInterval(gameInterval);
|
||||||
|
dotCount = currentCount;
|
||||||
|
gameState = 'waiting_for_backspace';
|
||||||
|
dotKeyIndicator.classList.remove('active');
|
||||||
|
|
||||||
|
if (dotCount === 0) {
|
||||||
|
restartGame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show target count
|
||||||
|
targetCounterEl.textContent = `TARGET: ${dotCount}`;
|
||||||
|
targetCounterEl.style.opacity = '1';
|
||||||
|
|
||||||
|
// Transition instructions
|
||||||
|
instructionsEl.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
backspaceInstructionsEl.style.opacity = '1';
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the backspace phase
|
||||||
|
*/
|
||||||
|
function startBackspacePress() {
|
||||||
|
if (gameState !== 'waiting_for_backspace' || isKeyPressed) return;
|
||||||
|
isKeyPressed = true;
|
||||||
|
gameState = 'backspace_pressing';
|
||||||
|
backspaceKeyIndicator.classList.add('active');
|
||||||
|
|
||||||
|
gameInterval = setInterval(() => {
|
||||||
|
if (currentCount > 0) {
|
||||||
|
currentCount--;
|
||||||
|
backspaceCount++;
|
||||||
|
dotDisplayEl.textContent = dotDisplayEl.textContent.slice(0, -1);
|
||||||
|
counterDisplayEl.textContent = currentCount;
|
||||||
|
} else {
|
||||||
|
// Stop automatically if all dots are cleared
|
||||||
|
stopBackspacePress();
|
||||||
|
}
|
||||||
|
}, PRESS_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends the backspace phase and shows the result
|
||||||
|
*/
|
||||||
|
function stopBackspacePress() {
|
||||||
|
if (gameState !== 'backspace_pressing') return;
|
||||||
|
isKeyPressed = false;
|
||||||
|
clearInterval(gameInterval);
|
||||||
|
gameState = 'game_over';
|
||||||
|
backspaceKeyIndicator.classList.remove('active');
|
||||||
|
showResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates and displays the final score
|
||||||
|
*/
|
||||||
|
function showResult() {
|
||||||
|
const diff = Math.abs(dotCount - backspaceCount);
|
||||||
|
let title = '';
|
||||||
|
|
||||||
|
if (diff === 0) {
|
||||||
|
title = 'Perfect Match!';
|
||||||
|
resultTitleEl.className = 'text-5xl font-bold mb-4 text-green-400';
|
||||||
|
} else if (diff <= 2) {
|
||||||
|
title = 'Incredible!';
|
||||||
|
resultTitleEl.className = 'text-5xl font-bold mb-4 text-yellow-400';
|
||||||
|
} else if (diff <= 5) {
|
||||||
|
title = 'So Close!';
|
||||||
|
resultTitleEl.className = 'text-5xl font-bold mb-4 text-yellow-500';
|
||||||
|
} else if (diff <= 10) {
|
||||||
|
title = 'Not Bad!';
|
||||||
|
resultTitleEl.className = 'text-5xl font-bold mb-4 text-orange-500';
|
||||||
|
} else {
|
||||||
|
title = 'Try Again!';
|
||||||
|
resultTitleEl.className = 'text-5xl font-bold mb-4 text-red-500';
|
||||||
|
}
|
||||||
|
|
||||||
|
resultTitleEl.textContent = title;
|
||||||
|
resultDetailsEl.textContent = `${backspaceCount} / ${dotCount}`;
|
||||||
|
resultScreenEl.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the game to its initial state
|
||||||
|
*/
|
||||||
|
function restartGame() {
|
||||||
|
gameState = 'waiting_for_dot';
|
||||||
|
dotCount = 0;
|
||||||
|
backspaceCount = 0;
|
||||||
|
currentCount = 0;
|
||||||
|
isKeyPressed = false;
|
||||||
|
|
||||||
|
clearInterval(gameInterval);
|
||||||
|
|
||||||
|
counterDisplayEl.textContent = '0';
|
||||||
|
dotDisplayEl.textContent = '';
|
||||||
|
resultScreenEl.style.display = 'none';
|
||||||
|
|
||||||
|
targetCounterEl.style.opacity = '0';
|
||||||
|
|
||||||
|
backspaceInstructionsEl.style.opacity = '0';
|
||||||
|
setTimeout(() => {
|
||||||
|
instructionsEl.textContent = 'Hold down the . key to begin';
|
||||||
|
instructionsEl.style.opacity = '1';
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// --- Event Listeners ---
|
||||||
|
|
||||||
|
window.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === '.') {
|
||||||
|
e.preventDefault();
|
||||||
|
startDotPress();
|
||||||
|
}
|
||||||
|
if (e.key === 'Backspace') {
|
||||||
|
e.preventDefault();
|
||||||
|
startBackspacePress();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keyup', (e) => {
|
||||||
|
if (e.key === '.') {
|
||||||
|
e.preventDefault();
|
||||||
|
stopDotPress();
|
||||||
|
}
|
||||||
|
if (e.key === 'Backspace') {
|
||||||
|
e.preventDefault();
|
||||||
|
stopBackspacePress();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
restartButton.addEventListener('click', restartGame);
|
||||||
|
|
||||||
|
// Initial setup
|
||||||
|
restartGame();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user