grrr/index.html
2025-07-22 09:12:18 +00:00

273 lines
11 KiB
HTML

<!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>