cuber-timer / index.html
irraju's picture
Add your twitter handle irraju at the bottom - Follow Up Deployment
b48ae64 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cuber Timer</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&family=Roboto+Mono:wght@400;700&display=swap');
.touch-indicator {
position: absolute;
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
opacity: 0.7;
}
.touch-indicator-outer {
border: 2px solid rgba(255, 255, 255, 0.5);
background: rgba(255, 255, 255, 0.1);
}
.touch-indicator-inner {
background: rgba(255, 255, 255, 0.3);
}
body {
font-family: 'Roboto Mono', monospace;
background-color: #1a202c;
color: white;
height: 100vh;
overflow: hidden;
}
.timer-display {
font-family: 'Fira Code', monospace;
font-size: 30vw;
letter-spacing: -0.05em;
font-weight: 700;
}
@media (max-width: 640px) {
.timer-display {
font-size: 35vw;
}
}
.instructions {
max-width: 600px;
}
#previous-times::-webkit-scrollbar {
width: 4px;
}
#previous-times::-webkit-scrollbar-track {
background: transparent;
}
#previous-times::-webkit-scrollbar-thumb {
background-color: rgba(255,255,255,0.2);
border-radius: 2px;
}
</style>
</head>
<body class="flex flex-col items-center justify-center p-4 relative">
<div id="touch-indicators"></div>
<div class="text-center mb-8">
<h1 class="text-3xl md:text-4xl font-bold mb-2 text-blue-400">CUBER TIMER</h1>
<p class="text-gray-400 mb-6">Press any two non-adjacent keys or touch points to start/stop</p>
<div class="timer-display font-mono mb-8" id="timer">0.00</div>
<div class="text-gray-400 text-xl mb-4" id="last-time"></div>
</div>
<div class="fixed right-0 top-0 h-full w-32 bg-gray-900 bg-opacity-30 p-4 overflow-y-auto" id="previous-times"></div>
<div class="text-gray-500 text-sm">
<p>Keys pressed: <span id="pressed-keys" class="font-mono">None</span></p>
<p class="mt-2">Status: <span id="status" class="font-semibold text-yellow-400">Waiting to start</span></p>
</div>
<script>
// QWERTY keyboard layout adjacency map
const adjacencyMap = {
'`': ['1', 'Tab'],
'1': ['`', '2', 'q'],
'2': ['1', '3', 'q', 'w'],
'3': ['2', '4', 'w', 'e'],
'4': ['3', '5', 'e', 'r'],
'5': ['4', '6', 'r', 't'],
'6': ['5', '7', 't', 'y'],
'7': ['6', '8', 'y', 'u'],
'8': ['7', '9', 'u', 'i'],
'9': ['8', '0', 'i', 'o'],
'0': ['9', '-', 'o', 'p'],
'-': ['0', '=', 'p', '['],
'=': ['-', 'Backspace', '[', ']'],
'q': ['1', '2', 'w', 'a', 'Tab', 'CapsLock'],
'w': ['2', '3', 'q', 'e', 'a', 's'],
'e': ['3', '4', 'w', 'r', 's', 'd'],
'r': ['4', '5', 'e', 't', 'd', 'f'],
't': ['5', '6', 'r', 'y', 'f', 'g'],
'y': ['6', '7', 't', 'u', 'g', 'h'],
'u': ['7', '8', 'y', 'i', 'h', 'j'],
'i': ['8', '9', 'u', 'o', 'j', 'k'],
'o': ['9', '0', 'i', 'p', 'k', 'l'],
'p': ['0', '-', 'o', '[', 'l', ';'],
'a': ['q', 'w', 's', 'z', 'CapsLock', 'Shift'],
's': ['w', 'e', 'a', 'd', 'z', 'x'],
'd': ['e', 'r', 's', 'f', 'x', 'c'],
'f': ['r', 't', 'd', 'g', 'c', 'v'],
'g': ['t', 'y', 'f', 'h', 'v', 'b'],
'h': ['y', 'u', 'g', 'j', 'b', 'n'],
'j': ['u', 'i', 'h', 'k', 'n', 'm'],
'k': ['i', 'o', 'j', 'l', 'm', ','],
'l': ['o', 'p', 'k', ';', ',', '.'],
'z': ['a', 's', 'x', 'Shift'],
'x': ['s', 'd', 'z', 'c'],
'c': ['d', 'f', 'x', 'v'],
'v': ['f', 'g', 'c', 'b'],
'b': ['g', 'h', 'v', 'n'],
'n': ['h', 'j', 'b', 'm'],
'm': ['j', 'k', 'n', ','],
',': ['k', 'l', 'm', '.'],
'.': ['l', ';', ',', '/'],
'/': [';', '.', 'Shift']
};
// State variables
let timer = null;
let startTime = 0;
let currentTime = 0;
let running = false;
let pressedKeys = new Set();
let touchPoints = new Map();
let lastTime = 0;
let previousTimes = [];
let recordTime = Infinity;
// DOM elements
const timerDisplay = document.getElementById('timer');
const pressedKeysDisplay = document.getElementById('pressed-keys');
const statusDisplay = document.getElementById('status');
// Format time to 2 decimal places
function formatTime(ms) {
return (ms / 1000).toFixed(2);
}
// Update the timer display
function updateTimer() {
currentTime = Date.now() - startTime;
timerDisplay.textContent = formatTime(currentTime);
}
// Calculate distance between two points
function getDistance(x1, y1, x2, y2) {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
}
// Check if two touch points are far enough apart
function areTouchesFarEnough(touches) {
if (touches.length < 2) return false;
// Get all pairs of touches and check if any are at least 100px apart
for (let i = 0; i < touches.length; i++) {
for (let j = i + 1; j < touches.length; j++) {
const t1 = touches[i];
const t2 = touches[j];
if (getDistance(t1.clientX, t1.clientY, t2.clientX, t2.clientY) > 100) {
return true;
}
}
}
return false;
}
// Update touch indicators
function updateTouchIndicators() {
const container = document.getElementById('touch-indicators');
container.innerHTML = '';
touchPoints.forEach((pos, identifier) => {
// Outer circle
const outer = document.createElement('div');
outer.className = 'touch-indicator touch-indicator-outer';
outer.style.width = '60px';
outer.style.height = '60px';
outer.style.left = `${pos.x}px`;
outer.style.top = `${pos.y}px`;
// Inner circle
const inner = document.createElement('div');
inner.className = 'touch-indicator touch-indicator-inner';
inner.style.width = '30px';
inner.style.height = '30px';
inner.style.left = `${pos.x}px`;
inner.style.top = `${pos.y}px`;
container.appendChild(outer);
container.appendChild(inner);
});
}
// Check if two keys are non-adjacent
function areKeysNonAdjacent(key1, key2) {
// Convert to lowercase for case-insensitive comparison
key1 = key1.toLowerCase();
key2 = key2.toLowerCase();
// If either key isn't in our adjacency map, assume they're not adjacent
if (!adjacencyMap[key1] || !adjacencyMap[key2]) {
return true;
}
// Check if key2 is adjacent to key1
return !adjacencyMap[key1].includes(key2);
}
// Check if current pressed keys meet the start/stop condition
function checkStartStopCondition() {
const keys = Array.from(pressedKeys);
// Need at least two keys pressed
if (keys.length < 2) return false;
// Check all pairs of pressed keys to find at least one non-adjacent pair
for (let i = 0; i < keys.length; i++) {
for (let j = i + 1; j < keys.length; j++) {
if (areKeysNonAdjacent(keys[i], keys[j])) {
return true;
}
}
}
return false;
}
// Start the timer
function startTimer() {
if (running) return;
startTime = Date.now();
timer = setInterval(updateTimer, 10);
running = true;
statusDisplay.textContent = 'Running';
statusDisplay.className = 'font-semibold text-green-400';
}
// Stop the timer
function stopTimer() {
if (!running) return;
clearInterval(timer);
running = false;
lastTime = currentTime;
statusDisplay.textContent = 'Stopped';
statusDisplay.className = 'font-semibold text-red-400';
// Add to previous times
previousTimes.push(lastTime);
// Update record if this is the fastest time
if (lastTime < recordTime) {
recordTime = lastTime;
}
// Update displays
updateLastTimeDisplay();
updatePreviousTimesDisplay();
// Animate the timer display
timerDisplay.classList.add('animate-pulse');
setTimeout(() => {
timerDisplay.classList.remove('animate-pulse');
}, 1000);
}
// Update last time display
function updateLastTimeDisplay() {
document.getElementById('last-time').textContent = `Last: ${formatTime(lastTime)}`;
}
// Update previous times display
function updatePreviousTimesDisplay() {
const container = document.getElementById('previous-times');
container.innerHTML = '';
// Sort times (fastest first)
const sortedTimes = [...previousTimes].sort((a, b) => a - b);
sortedTimes.forEach(time => {
const timeElement = document.createElement('div');
timeElement.textContent = formatTime(time);
timeElement.className = 'py-1 text-right text-sm';
// Highlight record time
if (time === recordTime) {
timeElement.className += ' text-yellow-400 font-bold';
}
container.appendChild(timeElement);
});
}
// Handle key down events
function handleKeyDown(e) {
// Ignore modifier keys
if (['Control', 'Shift', 'Alt', 'Meta', 'CapsLock', 'Tab', 'Backspace'].includes(e.key)) {
return;
}
// Add key to pressed keys set
pressedKeys.add(e.key);
// Update pressed keys display
pressedKeysDisplay.textContent = Array.from(pressedKeys).join(', ');
// Check if we should start/stop the timer
if (checkStartStopCondition()) {
if (running) {
stopTimer();
} else {
startTimer();
}
}
}
// Handle key up events
function handleKeyUp(e) {
// Remove key from pressed keys set
pressedKeys.delete(e.key);
// Update pressed keys display
pressedKeysDisplay.textContent = pressedKeys.size > 0 ? Array.from(pressedKeys).join(', ') : 'None';
// If no keys are pressed and timer was just started, change status
if (pressedKeys.size === 0 && running) {
statusDisplay.textContent = 'Solving...';
statusDisplay.className = 'font-semibold text-blue-400';
}
}
// Touch event handlers
function handleTouchStart(e) {
e.preventDefault();
// Add all new touches
Array.from(e.changedTouches).forEach(touch => {
touchPoints.set(touch.identifier, {
x: touch.clientX,
y: touch.clientY
});
});
updateTouchIndicators();
// Check if we should start/stop the timer
if (areTouchesFarEnough(Array.from(e.touches))) {
if (running) {
stopTimer();
} else {
startTimer();
}
}
}
function handleTouchMove(e) {
e.preventDefault();
// Update touch positions
Array.from(e.changedTouches).forEach(touch => {
if (touchPoints.has(touch.identifier)) {
touchPoints.set(touch.identifier, {
x: touch.clientX,
y: touch.clientY
});
}
});
updateTouchIndicators();
}
function handleTouchEnd(e) {
e.preventDefault();
// Remove ended touches
Array.from(e.changedTouches).forEach(touch => {
touchPoints.delete(touch.identifier);
});
updateTouchIndicators();
}
// Initialize
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
document.addEventListener('touchstart', handleTouchStart, { passive: false });
document.addEventListener('touchmove', handleTouchMove, { passive: false });
document.addEventListener('touchend', handleTouchEnd, { passive: false });
// Reset timer when clicking on it
timerDisplay.addEventListener('click', () => {
if (!running) {
timerDisplay.textContent = '0.00';
lastTime = 0;
document.getElementById('last-time').textContent = '';
statusDisplay.textContent = 'Waiting to start';
statusDisplay.className = 'font-semibold text-yellow-400';
previousTimes = [];
recordTime = Infinity;
document.getElementById('previous-times').innerHTML = '';
}
});
</script>
<div class="fixed bottom-4 left-0 right-0 text-center text-gray-500 text-sm">
<a href="https://twitter.com/irraju" target="_blank" class="hover:text-blue-400">@irraju</a>
</div>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=irraju/cuber-timer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>