grrr/frustrationmeter/index.html

293 lines
8.2 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Dev Frustration Meter</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
font-family: sans-serif;
background-color: #0f172a;
color: white;
}
.key-indicator.active {
transform: scale(1.1);
background-color: #4f46e5;
box-shadow: 0 0 25px rgba(79, 70, 229, 0.7);
color: white;
}
/* Stick Figure Styles */
.stick-figure {
width: 120px;
height: 200px;
position: relative;
margin: 20px auto;
}
.head {
width: 50px;
height: 50px;
border-radius: 50%;
border: 2px solid white;
position: absolute;
left: 35px;
top: 0;
transition: all 0.3s ease;
}
.eye {
width: 8px;
height: 8px;
background: white;
border-radius: 50%;
position: absolute;
top: 15px;
}
.eye.left { left: 15px; }
.eye.right { right: 15px; }
.mouth {
width: 20px;
height: 10px;
border-bottom: 2px solid white;
position: absolute;
bottom: 10px;
left: 15px;
border-radius: 0 0 10px 10px;
transition: all 0.3s ease;
}
.body {
width: 4px;
height: 70px;
background: white;
position: absolute;
left: 58px;
top: 50px;
}
.arm {
width: 50px;
height: 4px;
background: white;
position: absolute;
top: 70px;
transform-origin: left center;
transition: all 0.3s ease;
}
.arm.left { left: 10px; transform: rotate(20deg); }
.arm.right { left: 60px; transform: rotate(-20deg); }
.leg {
width: 4px;
height: 60px;
background: white;
position: absolute;
top: 120px;
transform-origin: top center;
transition: all 0.3s ease;
}
.leg.left { left: 45px; transform: rotate(5deg); }
.leg.right { left: 70px; transform: rotate(-5deg); }
/* Anger states */
.angry .eye {
transform: scaleY(0.6);
border-radius: 50% 50% 0 0;
background: transparent;
border-top: 2px solid white;
}
.angry .mouth {
border-radius: 10px 10px 0 0;
border-top: 2px solid white;
border-bottom: none;
transform: scaleY(0.7);
}
.very-angry .eye {
transform: scale(0.8) rotate(45deg);
background: #ff4444;
border: 2px solid #ff4444;
}
.very-angry .mouth {
border: 2px solid #ff4444;
border-bottom: none;
border-radius: 10px 10px 0 0;
height: 15px;
background: #ff4444;
}
.very-angry .arm {
transform: rotate(0deg) !important;
top: 80px;
}
.very-angry .leg {
transform: rotate(0deg) !important;
}
.enraged .head {
background: #ff0000;
animation: shake 0.1s infinite;
}
.enraged .eye {
background: #ff0000;
border-color: #ff0000;
animation: blink 0.5s infinite;
}
.enraged .mouth {
border-color: #ff0000;
background: #ff0000;
animation: shout 0.3s infinite alternate;
}
.enraged .arm, .enraged .leg {
background: #ff0000;
animation: shake 0.05s infinite;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-2px); }
75% { transform: translateX(2px); }
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
@keyframes shout {
from { transform: scale(1); }
to { transform: scale(1.3); }
}
</style>
</head>
<body class="flex flex-col items-center justify-center min-h-screen p-4 text-center">
<h1 class="text-4xl font-bold text-indigo-400 mb-2">Dev Frustration Meter</h1>
<p class="text-gray-300 mb-6 text-lg">Hold <kbd class="bg-gray-700 px-2 py-1 rounded">.</kbd> to express your frustration</p>
<div id="statement" class="bg-gray-800 p-6 rounded-lg mb-4 max-w-xl text-xl text-yellow-300 shadow">
<!-- Tester quote will appear here -->
</div>
<!-- Animated Stick Figure -->
<div class="stick-figure" id="stick-figure">
<div class="head">
<div class="eye left"></div>
<div class="eye right"></div>
<div class="mouth"></div>
</div>
<div class="body"></div>
<div class="arm left"></div>
<div class="arm right"></div>
<div class="leg left"></div>
<div class="leg right"></div>
</div>
<div id="dot-display" class="text-indigo-300 text-4xl mb-2 h-20 overflow-hidden break-words"></div>
<div class="text-sm text-gray-400 mb-6">Dots: <span id="dot-count">0</span></div>
<div id="result" class="text-2xl font-bold mt-4 hidden"></div>
<button id="restart" class="mt-6 bg-indigo-600 hover:bg-indigo-700 px-6 py-2 rounded text-white hidden">Try Another</button>
<script>
const statements = [
{ text: "It works on my machine.", target: 18 },
{ text: "Have you tried restarting?", target: 12 },
{ text: "Looks like a you problem.", target: 25 },
{ text: "This is a feature, not a bug.", target: 20 },
{ text: "Can you reproduce it?", target: 10 },
{ text: "QA approved it yesterday.", target: 15 },
{ text: "Let's deploy on Friday evening.", target: 30 },
{ text: "I don't see an error message.", target: 14 }
];
const statementEl = document.getElementById('statement');
const dotDisplay = document.getElementById('dot-display');
const dotCountEl = document.getElementById('dot-count');
const resultEl = document.getElementById('result');
const restartBtn = document.getElementById('restart');
const stickFigure = document.getElementById('stick-figure');
let gameInterval = null;
let currentDots = 0;
let isHolding = false;
let currentTarget = 0;
function pickStatement() {
const s = statements[Math.floor(Math.random() * statements.length)];
statementEl.textContent = `"${s.text}"`;
currentTarget = s.target;
}
function updateAngerLevel(dots) {
stickFigure.className = 'stick-figure';
if (dots > currentTarget * 1.5) {
stickFigure.classList.add('enraged');
} else if (dots > currentTarget) {
stickFigure.classList.add('very-angry');
} else if (dots > currentTarget * 0.7) {
stickFigure.classList.add('angry');
}
}
function startPress() {
if (isHolding) return;
isHolding = true;
currentDots = 0;
dotDisplay.textContent = '';
dotCountEl.textContent = '0';
resultEl.classList.add('hidden');
restartBtn.classList.add('hidden');
stickFigure.className = 'stick-figure';
gameInterval = setInterval(() => {
currentDots++;
dotDisplay.textContent += '.';
dotCountEl.textContent = currentDots;
updateAngerLevel(currentDots);
}, 40);
}
function stopPress() {
if (!isHolding) return;
isHolding = false;
clearInterval(gameInterval);
judge();
}
function judge() {
const diff = Math.abs(currentTarget - currentDots);
let feedback = '';
if (diff === 0) feedback = "Perfect Frustration Match 😤✅";
else if (diff <= 3) feedback = "Nailed it 💯";
else if (diff <= 8) feedback = "Close enough 😅";
else if (currentDots > currentTarget) feedback = "Too harsh! 😬";
else feedback = "You went too easy... 😐";
resultEl.textContent = `${feedback} (${currentDots} vs expected ${currentTarget})`;
resultEl.classList.remove('hidden');
restartBtn.classList.remove('hidden');
}
function restartGame() {
dotDisplay.textContent = '';
dotCountEl.textContent = '0';
resultEl.textContent = '';
resultEl.classList.add('hidden');
restartBtn.classList.add('hidden');
pickStatement();
}
window.addEventListener('keydown', (e) => {
if (e.key === '.') {
e.preventDefault();
startPress();
}
});
window.addEventListener('keyup', (e) => {
if (e.key === '.') {
e.preventDefault();
stopPress();
}
});
restartBtn.addEventListener('click', restartGame);
// Start first round
pickStatement();
</script>
</body>
</html>