“`html
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>20-Min Legs & Back Circuit Timer</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=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@500;700;800&display=swap” rel=”stylesheet”>
<script>
tailwind.config = {
darkMode: ‘class’,
theme: {
extend: {
fontFamily: {
sans: [‘”Plus Jakarta Sans”‘, ‘sans-serif’],
mono: [‘”JetBrains Mono”‘, ‘monospace’],
}
}
}
}
</script>
<style>
.progress-ring__circle {
transition: stroke-dashoffset 0.2s linear, stroke 0.3s ease;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.3; transform: scale(1); }
50% { opacity: 0.6; transform: scale(1.05); }
}
.pulse-glow {
animation: pulseGlow 2.5s infinite ease-in-out;
}
</style>
</head>
<body class=”bg-slate-950 text-slate-100 font-sans min-h-screen flex flex-col antialiased selection:bg-emerald-500 selection:text-black”>
<!– Audio Unlock Notification Banner for iOS/Safari –>
<div id=”unlockBanner” class=”bg-gradient-to-r from-emerald-600 via-teal-600 to-cyan-600 text-slate-950 px-4 py-2.5 text-xs sm:text-sm font-semibold flex items-center justify-between shadow-lg”>
<div class=”flex items-center gap-2 max-w-2xl mx-auto flex-1 justify-center”>
<svg class=”w-4 h-4 shrink-0″ fill=”currentColor” viewBox=”0 0 20 20″>
<path fill-rule=”evenodd” d=”M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z” clip-rule=”evenodd”></path>
</svg>
<span>Tap <strong>”Test Voice”</strong> or <strong>”Start”</strong> to unlock high-clarity voice announcements on mobile & desktop.</span>
</div>
<button id=”dismissUnlockBtn” class=”text-slate-900 hover:text-black font-bold text-lg leading-none px-2″>×</button>
</div>
<!– Header –>
<header class=”w-full border-b border-slate-800 bg-slate-900/80 backdrop-blur-md sticky top-0 z-30″>
<div class=”max-w-5xl mx-auto px-4 h-16 flex items-center justify-between”>
<div class=”flex items-center space-x-3″>
<div class=”h-9 w-9 rounded-xl bg-gradient-to-tr from-emerald-500 to-teal-400 flex items-center justify-center shadow-lg shadow-emerald-500/20 text-slate-950 font-bold”>
<svg class=”w-5 h-5″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2.5″ d=”M13 10V3L4 14h7v7l9-11h-7z” />
</svg>
</div>
<div>
<h1 class=”font-bold text-sm sm:text-base leading-tight text-white flex items-center gap-2″>
20-Min Legs & Back
<span class=”text-[10px] font-bold px-2 py-0.5 rounded-full bg-emerald-500/10 text-emerald-400 border border-emerald-500/30″>CIRCUIT</span>
</h1>
<p class=”text-[11px] text-slate-400 font-medium”>4 Rounds • 5 Stations • 45s Work / 15s Rest</p>
</div>
</div>
<!– Header Action Controls –>
<div class=”flex items-center space-x-2″>
<!– Direct Quick Test Voice button –>
<button id=”testVoiceBtn” class=”px-2.5 sm:px-3 py-1.5 rounded-xl border border-emerald-500/40 bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-300 text-xs font-semibold flex items-center gap-1.5 transition-all active:scale-95″>
<svg class=”w-4 h-4 text-emerald-400″ fill=”currentColor” viewBox=”0 0 20 20″>
<path d=”M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z”></path>
</svg>
<span class=”hidden sm:inline”>Test Voice</span>
<span class=”sm:hidden”>Test</span>
</button>
<!– Voice Toggle –>
<button id=”voiceToggleBtn” title=”Toggle Voice Prompts” class=”p-2 sm:px-3 sm:py-1.5 rounded-xl border border-slate-700 bg-slate-800/80 hover:bg-slate-700 text-xs font-semibold text-emerald-400 flex items-center gap-1.5 transition-all”>
<svg id=”voiceIconOn” class=”w-4 h-4″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4m-4-8a3 3 0 01-3-3V5a3 3 0 116 0v6a3 3 0 01-3 3z”></path>
</svg>
<svg id=”voiceIconOff” class=”w-4 h-4 hidden text-slate-500″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2″ />
</svg>
<span id=”voiceLabel” class=”hidden sm:inline”>Voice ON</span>
</button>
<!– Chime Toggle –>
<button id=”soundToggleBtn” title=”Toggle Beep Chimes” class=”p-2 sm:px-3 sm:py-1.5 rounded-xl border border-slate-700 bg-slate-800/80 hover:bg-slate-700 text-xs font-semibold text-slate-300 flex items-center gap-1.5 transition-all”>
<svg id=”soundIconOn” class=”w-4 h-4″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z” />
</svg>
<svg id=”soundIconOff” class=”w-4 h-4 hidden text-slate-500″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z M17 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2″ />
</svg>
<span id=”soundLabel” class=”hidden sm:inline”>Beeps ON</span>
</button>
<!– Overview Drawer Open –>
<button id=”openOverviewBtn” title=”Full Workout Routine” class=”p-2 sm:px-3 sm:py-1.5 rounded-xl border border-slate-700 bg-slate-800/80 hover:bg-slate-700 text-xs font-semibold text-slate-300 flex items-center gap-1.5 transition-all”>
<svg class=”w-4 h-4 text-emerald-400″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M4 6h16M4 12h16M4 18h7″ />
</svg>
<span class=”hidden sm:inline”>Routine</span>
</button>
</div>
</div>
</header>
<!– Workout Status & Timings Sub-Bar –>
<div class=”bg-slate-900/40 border-b border-slate-800 py-2.5 px-4″>
<div class=”max-w-4xl mx-auto flex flex-wrap items-center justify-between gap-3 text-xs sm:text-sm”>
<div class=”flex items-center space-x-2″>
<span class=”text-slate-400 font-medium”>Round:</span>
<div class=”flex items-center gap-1.5″ id=”roundPillContainer”></div>
</div>
<div class=”flex items-center gap-4 font-mono text-xs”>
<div>
<span class=”text-slate-400″>Elapsed: </span>
<span id=”elapsedDisplay” class=”font-bold text-slate-200″>00:00</span>
</div>
<span class=”text-slate-700″>|</span>
<div>
<span class=”text-slate-400″>Remaining: </span>
<span id=”remainingDisplay” class=”font-bold text-emerald-400″>20:00</span>
</div>
</div>
</div>
</div>
<!– Main Body Content –>
<main class=”flex-1 max-w-4xl w-full mx-auto p-4 sm:p-6 flex flex-col justify-between items-center gap-6″>
<!– Active State Pill –>
<div id=”phaseBadge” class=”w-full max-w-md py-2 px-4 rounded-full text-center font-extrabold uppercase tracking-widest text-xs sm:text-sm bg-slate-800 border border-slate-700 text-slate-300 transition-all duration-300″>
READY TO WORKOUT
</div>
<!– Center Circular Timer –>
<div class=”relative flex items-center justify-center my-1 sm:my-3″>
<div id=”ambientGlow” class=”absolute w-64 h-64 sm:w-80 sm:h-80 rounded-full blur-3xl transition-all duration-500 bg-slate-800/20 pulse-glow pointer-events-none”></div>
<div class=”relative w-64 h-64 sm:w-80 sm:h-80 flex items-center justify-center”>
<svg class=”w-full h-full transform” viewBox=”0 0 280 280″>
<circle cx=”140″ cy=”140″ r=”120″ class=”text-slate-800/80″ stroke-width=”12″ stroke=”currentColor” fill=”transparent” />
<circle id=”timerCircle” cx=”140″ cy=”140″ r=”120″ stroke-width=”12″ stroke=”currentColor” fill=”transparent” stroke-linecap=”round” class=”text-emerald-500 progress-ring__circle” stroke-dasharray=”753.98″ stroke-dashoffset=”0″ />
</svg>
<div class=”absolute flex flex-col items-center justify-center text-center select-none px-4″>
<span id=”countdownNumber” class=”font-mono text-5xl sm:text-6xl font-extrabold text-white tracking-tight drop-shadow-md”>
45
</span>
<span id=”phaseSubtitle” class=”text-xs uppercase tracking-widest text-emerald-400 font-bold mt-1″>
WORK INTERVAL
</span>
<span id=”voiceSpeakingBadge” class=”text-[11px] font-semibold text-emerald-300 opacity-0 transition-opacity mt-1 flex items-center gap-1″>
<span class=”inline-block w-2 h-2 rounded-full bg-emerald-400 animate-ping”></span>
Voice active
</span>
</div>
</div>
</div>
<!– Active Exercise Information Card –>
<div class=”w-full max-w-xl bg-slate-900/90 border border-slate-800 rounded-2xl p-5 shadow-2xl backdrop-blur-sm”>
<div class=”flex items-start justify-between gap-3″>
<div>
<span id=”stationNumberTag” class=”text-[11px] font-bold text-emerald-400 uppercase tracking-wider bg-emerald-500/10 px-2 py-0.5 rounded-md border border-emerald-500/20″>
Station 1 of 5
</span>
<h2 id=”exerciseNameHeading” class=”text-xl sm:text-2xl font-extrabold text-white mt-1.5 tracking-tight”>
Goblet Squats
</h2>
</div>
<div id=”muscleGroupTags” class=”flex flex-wrap gap-1.5 justify-end”></div>
</div>
<!– Coach Form Cue –>
<div class=”mt-4 p-3 rounded-xl bg-slate-950/70 border border-slate-800 flex items-start space-x-2.5″>
<svg class=”w-5 h-5 text-emerald-400 shrink-0 mt-0.5″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z” />
</svg>
<p id=”exerciseFormCue” class=”text-xs sm:text-sm text-slate-300 leading-relaxed font-normal”>
Hold dumbbell close to sternum. Hinge hips back, sink down to parallel, and drive up through full feet.
</p>
</div>
<!– Up Next Station Preview –>
<div class=”mt-3.5 pt-3 border-t border-slate-800 flex items-center justify-between text-xs text-slate-400″>
<span class=”font-medium text-slate-500″>UP NEXT:</span>
<span id=”upNextPreviewText” class=”font-semibold text-slate-300 truncate max-w-[260px]”>
Bent-Over Dumbbell Rows
</span>
</div>
</div>
<!– Primary Control Buttons –>
<div class=”w-full max-w-md flex items-center justify-center gap-3 sm:gap-4 my-2″>
<!– Previous Station –>
<button id=”prevBtn” title=”Previous Station” class=”p-3.5 rounded-2xl bg-slate-800/90 hover:bg-slate-700 active:scale-95 text-slate-300 border border-slate-700 transition shadow”>
<svg class=”w-5 h-5″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2.5″ d=”M11 19l-7-7 7-7m8 14l-7-7 7-7″ />
</svg>
</button>
<!– Play / Pause –>
<button id=”playPauseBtn” class=”flex-1 max-w-[200px] py-4 px-6 rounded-2xl bg-gradient-to-r from-emerald-500 to-teal-500 hover:from-emerald-400 hover:to-teal-400 active:scale-95 text-slate-950 font-extrabold text-base sm:text-lg flex items-center justify-center gap-2 shadow-xl shadow-emerald-500/25 transition”>
<svg id=”playSvg” class=”w-6 h-6 fill-current” viewBox=”0 0 24 24″>
<polygon points=”5 3 19 12 5 21 5 3″></polygon>
</svg>
<svg id=”pauseSvg” class=”w-6 h-6 hidden fill-current” viewBox=”0 0 24 24″>
<rect x=”6″ y=”4″ width=”4″ height=”16″></rect>
<rect x=”14″ y=”4″ width=”4″ height=”16″></rect>
</svg>
<span id=”playPauseText”>Start Workout</span>
</button>
<!– Next Station –>
<button id=”nextBtn” title=”Skip to Next Station” class=”p-3.5 rounded-2xl bg-slate-800/90 hover:bg-slate-700 active:scale-95 text-slate-300 border border-slate-700 transition shadow”>
<svg class=”w-5 h-5″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2.5″ d=”M13 5l7 7-7 7M5 5l7 7-7 7″ />
</svg>
</button>
<!– Reset –>
<button id=”resetBtn” title=”Reset Workout” class=”p-3.5 rounded-2xl bg-slate-800/90 hover:bg-rose-950/40 hover:text-rose-400 hover:border-rose-900/60 active:scale-95 text-slate-400 border border-slate-700 transition shadow”>
<svg class=”w-5 h-5″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15″ />
</svg>
</button>
</div>
<!– Voice Settings Dropdown Selector –>
<div class=”w-full max-w-xl bg-slate-900/60 border border-slate-800/80 rounded-xl p-3 flex flex-col sm:flex-row items-center justify-between gap-3 text-xs”>
<div class=”flex items-center gap-2 text-slate-300″>
<svg class=”w-4 h-4 text-emerald-400″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2″ d=”M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4″></path>
</svg>
<span class=”font-medium text-slate-400″>Coaching Voice:</span>
</div>
<div class=”flex items-center gap-2 w-full sm:w-auto”>
<select id=”voiceSelect” class=”bg-slate-800 text-slate-200 border border-slate-700 rounded-lg px-2.5 py-1.5 focus:outline-none focus:ring-1 focus:ring-emerald-500 text-xs w-full sm:w-64 truncate”>
<option value=””>Loading browser voices…</option>
</select>
<button id=”testSelectedVoiceBtn” class=”px-2 py-1.5 bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 rounded-lg shrink-0 font-medium text-xs”>
Speak
</button>
</div>
</div>
</main>
<!– Overview Drawer –>
<aside id=”overviewDrawer” class=”fixed inset-y-0 right-0 w-full sm:w-96 bg-slate-900/95 border-l border-slate-800 shadow-2xl p-6 flex flex-col justify-between z-50 transform translate-x-full transition-transform duration-300 backdrop-blur-xl”>
<div>
<div class=”flex items-center justify-between pb-4 border-b border-slate-800″>
<div>
<h3 class=”font-bold text-lg text-white”>20-Min Workout Plan</h3>
<p class=”text-xs text-slate-400″>5 Exercises • 4 Continuous Rounds</p>
</div>
<button id=”closeDrawerBtn” class=”p-2 rounded-xl text-slate-400 hover:text-white hover:bg-slate-800″>×</button>
</div>
<div class=”mt-4 space-y-3 overflow-y-auto max-h-[calc(100vh-220px)] pr-1″ id=”drawerCardsList”></div>
</div>
<div class=”pt-4 border-t border-slate-800 text-xs text-slate-400″>
<div class=”text-slate-300 font-semibold mb-1″>Workout Formula:</div>
45s High Intensity Work + 15s Transition Rest. Stay upright, maintain core brace, and keep movements deliberate!
</div>
</aside>
<!– Overlay Backdrop for Drawer –>
<div id=”drawerOverlay” class=”fixed inset-0 bg-slate-950/70 backdrop-blur-xs z-40 hidden transition-opacity”></div>
<!– Completed Modal Overlay –>
<div id=”completionModal” class=”fixed inset-0 bg-slate-950/90 z-50 flex items-center justify-center p-4 hidden”>
<div class=”bg-slate-900 border border-emerald-500/40 rounded-3xl p-6 sm:p-8 max-w-md w-full text-center shadow-2xl”>
<div class=”w-16 h-16 rounded-full bg-emerald-500/20 text-emerald-400 mx-auto flex items-center justify-center mb-4 ring-8 ring-emerald-500/10″>
<svg class=”w-8 h-8″ fill=”none” stroke=”currentColor” viewBox=”0 0 24 24″>
<path stroke-linecap=”round” stroke-linejoin=”round” stroke-width=”2.5″ d=”M5 13l4 4L19 7″ />
</svg>
</div>
<h2 class=”text-2xl sm:text-3xl font-black text-white”>Workout Finished!</h2>
<p class=”text-sm text-slate-300 mt-2″>20 minutes of intense legs and back work completed. Outstanding job!</p>
<div class=”grid grid-cols-2 gap-3 my-5″>
<div class=”bg-slate-950/80 p-3 rounded-xl border border-slate-800″>
<div class=”text-[10px] text-slate-400 uppercase font-bold”>Total Time</div>
<div class=”text-lg font-bold font-mono text-emerald-400″>20:00</div>
</div>
<div class=”bg-slate-950/80 p-3 rounded-xl border border-slate-800″>
<div class=”text-[10px] text-slate-400 uppercase font-bold”>Rounds</div>
<div class=”text-lg font-bold font-mono text-emerald-400″>4 of 4</div>
</div>
</div>
<button id=”modalRestartBtn” class=”w-full py-3.5 px-6 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-bold transition”>
Restart Workout
</button>
</div>
</div>
<!– JAVASCRIPT APPLICATION LOGIC –>
<script>
// — WORKOUT CONFIGURATION —
const WORKOUT_STATIONS = [
{
id: 1,
name: “Goblet Squats”,
target: [“Quads”, “Glutes”],
cue: “Hold dumbbell tight to sternum. Hinge hips back, drop into a parallel squat, and drive forcefully through mid-foot.”
},
{
id: 2,
name: “Bent-Over Dumbbell Rows”,
target: [“Lats”, “Rhomboids”, “Upper Back”],
cue: “Hinge at 45° with a flat spine. Pull dumbbells back toward hips, driving elbows to ceiling and pinching shoulder blades.”
},
{
id: 3,
name: “Romanian Deadlifts (RDLs)”,
target: [“Hamstrings”, “Glutes”, “Lower Back”],
cue: “Soft bend in knees, push hips straight back into the wall behind you. Shave the shins with dumbbells, squeeze glutes at lockout.”
},
{
id: 4,
name: “Alternating Reverse Lunges”,
target: [“Quads”, “Glutes”, “Balance”],
cue: “Step backward with control into two 90-degree knee angles. Keep torso upright and drive up through the front lead heel.”
},
{
id: 5,
name: “Renegade Rows”,
target: [“Mid-Back”, “Lats”, “Core Stability”],
cue: “Set wide feet in high plank. Lock hips perfectly level with the floor; row one dumbbell at a time without swaying.”
