function parsePace(paceStr) { const parts = paceStr.split(':'); if (parts.length !== 2) return null; const minutes = parseInt(parts[0]); const seconds = parseInt(parts[1]); if (isNaN(minutes) || isNaN(seconds)) return null; return minutes * 60 + seconds; } // Helper to convert seconds back to MM:SS string function formatPaceString(totalSeconds) { const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; return `${minutes}:${seconds.toString().padStart(2, '0')}`; } function formatTime(seconds) { const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = Math.round(seconds % 60); // Format total time as H:MM:SS return `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } function formatPace(paceSeconds) { const totalSeconds = Math.round(paceSeconds); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; // Format pace as M:SS return `${minutes}:${seconds.toString().padStart(2, '0')}`; } // --- NEW INCREMENT HANDLERS --- function handlePaceIncrement(targetId, direction) { const input = document.getElementById(targetId); let currentSeconds = parsePace(input.value) || 0; const increment = 5; // 5 second increments currentSeconds = Math.max(5, currentSeconds + (direction * increment)); // Min pace of 5 seconds input.value = formatPaceString(currentSeconds); calculate(); } function handleDistanceIncrement(targetId, direction) { const input = document.getElementById(targetId); let currentDistance = parseFloat(input.value) || 0; const increment = 25; // 25 meter increments currentDistance = Math.max(5, currentDistance + (direction * increment)); // Min distance of 5 meters input.value = currentDistance; calculate(); } function handleTimeIncrement(targetId, direction) { const input = document.getElementById(targetId); let currentTime = parseFloat(input.value) || 0; const increment = 10; // 10 second increments currentTime = Math.max(0, currentTime + (direction * increment)); input.value = currentTime.toString(); calculate(); } // --- END NEW INCREMENT HANDLERS --- function calculate() { const totalDistance = parseFloat(document.getElementById('totalDistance').value); const runPaceInput = document.getElementById('runPace'); const walkPaceInput = document.getElementById('walkPace'); const fastFinishPaceInput = document.getElementById('fastFinishPace'); // 🛑 Validate all pace inputs before proceeding with calculations if (!runPaceInput.checkValidity() || !walkPaceInput.checkValidity() || !fastFinishPaceInput.checkValidity()) { // Clear the result section if inputs are invalid document.getElementById('totalTime').textContent = '--'; document.getElementById('averagePace').textContent = '--'; document.getElementById('distancePerCycle').textContent = '--'; document.getElementById('fastFinishInfo').style.display = 'none'; return; } const runPaceStr = runPaceInput.value; const runTime = parseFloat(document.getElementById('runTime').value); const walkPaceStr = walkPaceInput.value; const walkTime = parseFloat(document.getElementById('walkTime').value); const fastFinishEnabled = document.getElementById('fastFinishEnabled').value; const fastFinishDistanceValue = parseFloat(document.getElementById('fastFinishDistance').value); const fastFinishPaceStr = fastFinishPaceInput.value; const runPaceSeconds = parsePace(runPaceStr); const walkPaceSeconds = parsePace(walkPaceStr); if (!runPaceSeconds || !walkPaceSeconds || isNaN(runTime) || isNaN(walkTime) || runPaceSeconds <= 0 || walkPaceSeconds <= 0 || runTime < 0 || walkTime < 0) { document.getElementById('totalTime').textContent = '--'; document.getElementById('averagePace').textContent = '--'; document.getElementById('distancePerCycle').textContent = '--'; document.getElementById('fastFinishInfo').style.display = 'none'; return; } let targetDistance = totalDistance; let fastFinishTime = 0; let fastFinishDistance = 0; // Handle fast finish if (fastFinishEnabled === 'yes' && !isNaN(fastFinishDistanceValue) && fastFinishDistanceValue > 0) { const fastFinishPaceSeconds = parsePace(fastFinishPaceStr); if (fastFinishPaceSeconds && fastFinishPaceSeconds > 0) { fastFinishDistance = fastFinishDistanceValue; fastFinishTime = fastFinishDistance * fastFinishPaceSeconds; targetDistance = totalDistance - fastFinishDistance; // Ensure the main run/walk calculation distance isn't negative if (targetDistance < 0) targetDistance = 0; } } let totalTime = 0; let cycleDistance = 0; let runDistance = 0; let walkDistance = 0; // Handle run/walk distance if (targetDistance > 0) { runDistance = runTime / runPaceSeconds; walkDistance = walkTime / walkPaceSeconds; cycleDistance = runDistance + walkDistance; const cycleTime = runTime + walkTime; // Handle the zero-cycle case to prevent division by zero if (cycleDistance > 0) { const completeCycles = Math.floor(targetDistance / cycleDistance); const remainingDistance = targetDistance - (completeCycles * cycleDistance); totalTime = completeCycles * cycleTime; if (remainingDistance > 0) { if (remainingDistance <= runDistance) { totalTime += remainingDistance * runPaceSeconds; } else { // Complete run segment and partial walk segment totalTime += runTime; const walkDistanceNeeded = remainingDistance - runDistance; totalTime += walkDistanceNeeded * walkPaceSeconds; } } } } // Final total time (including fast finish) totalTime += fastFinishTime; // --- Update Results --- document.getElementById('runDistance').textContent = `${(1000* runDistance).toFixed(0)} m`; document.getElementById('walkDistance').textContent = `${(1000* walkDistance).toFixed(0)} m`; // 1. Total Time const resultText = document.getElementById('totalDistance').selectedOptions[0].innerText; document.getElementById('finishText').textContent = `${resultText} Finish Time`; document.getElementById('totalTime').textContent = formatTime(totalTime); // 2. Overall Average Pace if (totalDistance > 0 && totalTime > 0) { const averagePaceSeconds = totalTime / totalDistance; document.getElementById('averagePace').textContent = `${formatPace(averagePaceSeconds)} /km`; } else { document.getElementById('averagePace').textContent = `--`; } // 3. Distance per Cycle document.getElementById('distancePerCycle').textContent = `${cycleDistance.toFixed(3)} km`; // Show/hide fast finish info const fastFinishInfo = document.getElementById('fastFinishInfo'); if (fastFinishEnabled === 'yes' && fastFinishDistance > 0 && fastFinishTime > 0) { fastFinishInfo.style.display = 'flex'; // Use flex to match .result-item document.getElementById('fastFinishDetails').textContent = `${fastFinishDistance.toFixed(3)} km in ${formatTime(fastFinishTime)}`; } else { fastFinishInfo.style.display = 'none'; } document.getElementById('result').classList.add('show'); } function setupEventListeners() { // Select elements that trigger calculation (change event) const changeInputs = [ 'totalDistance', 'fastFinishEnabled', 'fastFinishDistance' ]; // Select elements that trigger calculation (input event) const inputInputs = [ 'runPace', 'walkPace', 'fastFinishPace', 'runTime', 'walkTime' ]; // Attach 'change' listeners changeInputs.forEach(id => { document.getElementById(id)?.addEventListener('change', calculate); }); // Attach 'input' listeners inputInputs.forEach(id => { document.getElementById(id)?.addEventListener('input', calculate); }); // --- NEW STEPPER LISTENERS --- // Pace buttons (+/- 5 seconds) document.querySelectorAll('.pace-up').forEach(button => { button.addEventListener('click', () => { handlePaceIncrement(button.dataset.target, 1); // 1 = up }); }); document.querySelectorAll('.pace-down').forEach(button => { button.addEventListener('click', () => { handlePaceIncrement(button.dataset.target, -1); // -1 = down }); }); // Distance buttons (+/- 25 meters) document.querySelectorAll('.dist-up').forEach(button => { button.addEventListener('click', () => { handleDistanceIncrement(button.dataset.target, 1); // 1 = up }); }); document.querySelectorAll('.dist-down').forEach(button => { button.addEventListener('click', () => { handleDistanceIncrement(button.dataset.target, -1); // -1 = down }); }); // Time buttons (+/- 10 seconds) document.querySelectorAll('.time-up').forEach(button => { button.addEventListener('click', () => { handleTimeIncrement(button.dataset.target, 1); // 1 = up }); }); document.querySelectorAll('.time-down').forEach(button => { button.addEventListener('click', () => { handleTimeIncrement(button.dataset.target, -1); // -1 = down }); }); // --- END NEW STEPPER LISTENERS --- } // Setup listeners first, then run initial calculation setupEventListeners(); calculate();