Skip to content

Instantly share code, notes, and snippets.

@rndmcnlly
Last active January 29, 2026 23:01
Show Gist options
  • Select an option

  • Save rndmcnlly/61d8c753906c966329cbf67d14ed915e to your computer and use it in GitHub Desktop.

Select an option

Save rndmcnlly/61d8c753906c966329cbf67d14ed915e to your computer and use it in GitHub Desktop.
<!-- Vibe-coded with Gambit v1.4 (https://bayleaf.chat/?model=gambit) -->
<!-- PITCH TIMER: Because every second of their attention is precious -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>⏱️ PITCH TIMER</title>
<style>
/* === TWEAK THESE === */
:root {
--color-safe: #00ff88;
--color-warn: #ffaa00;
--color-danger: #ff3366;
--color-overtime: #ff0000;
--bg-dark: #0a0a0f;
--ring-size: min(70vmin, 400px);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: var(--bg-dark);
color: white;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
overflow: hidden;
user-select: none;
}
.timer-container {
position: relative;
width: var(--ring-size);
height: var(--ring-size);
}
.timer-ring {
width: 100%;
height: 100%;
border-radius: 50%;
background: conic-gradient(
var(--current-color, var(--color-safe)) calc(var(--progress, 1) * 360deg),
#222 0deg
);
display: flex;
align-items: center;
justify-content: center;
position: relative;
box-shadow:
0 0 60px color-mix(in srgb, var(--current-color, var(--color-safe)) 40%, transparent),
inset 0 0 80px rgba(0,0,0,0.8);
transition: box-shadow 0.3s;
}
.timer-ring::before {
content: '';
position: absolute;
width: 85%;
height: 85%;
background: var(--bg-dark);
border-radius: 50%;
}
.time-display {
position: relative;
z-index: 1;
font-size: clamp(3rem, 15vmin, 8rem);
font-weight: 200;
font-variant-numeric: tabular-nums;
letter-spacing: -0.02em;
text-shadow: 0 0 30px currentColor;
}
.controls {
margin-top: 2rem;
display: flex;
gap: 1rem;
flex-wrap: wrap;
justify-content: center;
}
button {
padding: 0.8rem 2rem;
font-size: 1.1rem;
font-weight: 600;
border: 2px solid #444;
border-radius: 2rem;
background: #1a1a24;
color: white;
cursor: pointer;
transition: all 0.2s;
}
button:hover { background: #2a2a3a; border-color: #666; transform: scale(1.05); }
button:active { transform: scale(0.98); }
button.primary { border-color: var(--color-safe); color: var(--color-safe); }
/* === WARNING STATES === */
.warn-10 .timer-ring { animation: pulse-warn 1s ease-in-out infinite; }
.warn-5 .timer-ring { animation: pulse-danger 0.5s ease-in-out infinite; }
.warn-5 .time-display { animation: shake 0.1s linear infinite; }
@keyframes pulse-warn {
0%, 100% { box-shadow: 0 0 60px var(--color-warn), inset 0 0 80px rgba(0,0,0,0.8); }
50% { box-shadow: 0 0 100px var(--color-warn), 0 0 150px var(--color-warn), inset 0 0 80px rgba(0,0,0,0.8); }
}
@keyframes pulse-danger {
0%, 100% { box-shadow: 0 0 80px var(--color-danger), inset 0 0 80px rgba(0,0,0,0.8); }
50% { box-shadow: 0 0 120px var(--color-danger), 0 0 200px var(--color-danger), inset 0 0 80px rgba(0,0,0,0.5); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-3px) rotate(-1deg); }
75% { transform: translateX(3px) rotate(1deg); }
}
/* === OVERTIME CHAOS === */
.overtime { animation: overtime-bg 0.3s linear infinite; }
.overtime .timer-ring {
animation: overtime-ring 0.2s linear infinite;
filter: saturate(1.5);
}
.overtime .time-display { color: var(--color-overtime); }
@keyframes overtime-bg {
0%, 100% { background: var(--bg-dark); }
50% { background: #1a0000; }
}
@keyframes overtime-ring {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.02); }
}
/* === SKULL EXPLOSION === */
.skull-container {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 100;
}
.skull {
position: absolute;
font-size: 4rem;
animation: skull-explode 2s ease-out forwards;
filter: drop-shadow(0 0 20px var(--color-overtime));
}
@keyframes skull-explode {
0% {
transform: translate(-50%, -50%) scale(0) rotate(0deg);
opacity: 1;
}
20% { transform: translate(-50%, -50%) scale(1.5) rotate(20deg); }
100% {
transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.5) rotate(var(--rot));
opacity: 0;
}
}
.status {
position: fixed;
top: 1rem;
font-size: 0.9rem;
color: #666;
}
.duration-hint {
position: fixed;
bottom: 1rem;
font-size: 0.8rem;
color: #444;
}
</style>
</head>
<body>
<!-- Design question: Does dramatic time pressure improve or hurt pitch delivery? -->
<div class="status" id="status"></div>
<div class="timer-container">
<div class="timer-ring" id="ring">
<div class="time-display" id="display">1:30</div>
</div>
</div>
<div class="controls">
<button class="primary" id="startBtn">▶ START</button>
<button id="resetBtn">↺ RESET</button>
<button id="fullscreenBtn">⛶ FULLSCREEN</button>
</div>
<div class="skull-container" id="skulls"></div>
<div class="duration-hint">Tip: Add #duration=60 to URL for different times</div>
<script>
// ============================================
// === CONFIGURATION (edit these constants) ===
// ============================================
const DEFAULT_DURATION = 90; // seconds
const WARN_THRESHOLD_1 = 10; // yellow warning
const WARN_THRESHOLD_2 = 5; // red danger
const SKULL_COUNT = 12; // skulls in explosion
// Parse URL hash for custom duration
function getDuration() {
const hash = location.hash.slice(1);
const match = hash.match(/duration=(\d+)/);
return match ? parseInt(match[1], 10) : DEFAULT_DURATION;
}
// ============================================
// === STATE ===
// ============================================
let duration = getDuration();
let remaining = duration;
let running = false;
let interval = null;
let wakeLock = null;
let exploded = false;
const display = document.getElementById('display');
const ring = document.getElementById('ring');
const startBtn = document.getElementById('startBtn');
const resetBtn = document.getElementById('resetBtn');
const fullscreenBtn = document.getElementById('fullscreenBtn');
const status = document.getElementById('status');
const skulls = document.getElementById('skulls');
// ============================================
// === RENDERING ===
// ============================================
function formatTime(sec) {
const negative = sec < 0;
const abs = Math.abs(sec);
const m = Math.floor(abs / 60);
const s = abs % 60;
return (negative ? '-' : '') + m + ':' + String(s).padStart(2, '0');
}
function render() {
display.textContent = formatTime(remaining);
const progress = Math.max(0, remaining / duration);
ring.style.setProperty('--progress', progress);
// Color states
let color;
if (remaining <= 0) color = 'var(--color-overtime)';
else if (remaining <= WARN_THRESHOLD_2) color = 'var(--color-danger)';
else if (remaining <= WARN_THRESHOLD_1) color = 'var(--color-warn)';
else color = 'var(--color-safe)';
ring.style.setProperty('--current-color', color);
// Body classes for animations
document.body.classList.remove('warn-10', 'warn-5', 'overtime');
if (remaining <= 0) document.body.classList.add('overtime');
else if (remaining <= WARN_THRESHOLD_2) document.body.classList.add('warn-5');
else if (remaining <= WARN_THRESHOLD_1) document.body.classList.add('warn-10');
}
// ============================================
// === SKULL EXPLOSION ===
// ============================================
function explodeSkulls() {
if (exploded) return;
exploded = true;
skulls.innerHTML = '';
for (let i = 0; i < SKULL_COUNT; i++) {
const skull = document.createElement('div');
skull.className = 'skull';
skull.textContent = '💀';
skull.style.left = '50%';
skull.style.top = '50%';
skull.style.setProperty('--dx', (Math.random() - 0.5) * 600 + 'px');
skull.style.setProperty('--dy', (Math.random() - 0.5) * 600 + 'px');
skull.style.setProperty('--rot', (Math.random() - 0.5) * 720 + 'deg');
skull.style.animationDelay = (i * 0.05) + 's';
skulls.appendChild(skull);
}
}
// ============================================
// === TIMER CONTROL ===
// ============================================
function tick() {
remaining--;
render();
if (remaining === 0) explodeSkulls();
}
function start() {
if (running) {
// Pause
running = false;
clearInterval(interval);
startBtn.textContent = '▶ RESUME';
releaseWakeLock();
} else {
// Start/Resume
running = true;
interval = setInterval(tick, 1000);
startBtn.textContent = '⏸ PAUSE';
requestWakeLock();
}
}
function reset() {
running = false;
clearInterval(interval);
duration = getDuration();
remaining = duration;
exploded = false;
skulls.innerHTML = '';
startBtn.textContent = '▶ START';
document.body.classList.remove('warn-10', 'warn-5', 'overtime');
releaseWakeLock();
render();
}
// ============================================
// === FULLSCREEN & WAKE LOCK ===
// ============================================
function toggleFullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {});
} else {
document.exitFullscreen();
}
}
async function requestWakeLock() {
try {
if ('wakeLock' in navigator) {
wakeLock = await navigator.wakeLock.request('screen');
status.textContent = '🔒 Screen wake lock active';
}
} catch (e) {
status.textContent = '⚠️ Wake lock unavailable';
}
}
function releaseWakeLock() {
if (wakeLock) {
wakeLock.release();
wakeLock = null;
status.textContent = '';
}
}
// Re-acquire wake lock on visibility change
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible' && running) {
requestWakeLock();
}
});
// Listen for hash changes
window.addEventListener('hashchange', reset);
// ============================================
// === EVENT BINDINGS ===
// ============================================
startBtn.addEventListener('click', start);
resetBtn.addEventListener('click', reset);
fullscreenBtn.addEventListener('click', toggleFullscreen);
// Spacebar to start/pause
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') { e.preventDefault(); start(); }
if (e.code === 'KeyR') reset();
if (e.code === 'KeyF') toggleFullscreen();
});
// Initialize
render();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment