diff --git a/assets/js/main.js b/assets/js/main.js
index 45bb0c7..e1e75b2 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -27,6 +27,9 @@ const app = {
// Relay State
currentParticipant: 1,
participantNames: [],
+ relayParticipantStartTime: 0,
+ relayParticipantElapsed: 0,
+ relayColors: ['#3b82f6', '#10b981', '#ef4444', '#f59e0b', '#8b5cf6', '#ec4899', '#06b6d4', '#f97316', '#14b8a6', '#6366f1', '#a855f7', '#d946ef'],
// Custom State
customActivities: [
@@ -63,6 +66,7 @@ const app = {
landingClock: document.getElementById('landing-clock'),
mainTimer: document.getElementById('main-timer'),
subTimer: document.getElementById('sub-timer'),
+ relayParticipantTimer: document.getElementById('relay-participant-timer'),
viewLanding: document.getElementById('view-landing'),
viewTimer: document.getElementById('view-timer'),
timerTitle: document.getElementById('timer-title'),
@@ -135,6 +139,7 @@ const app = {
init() {
console.log('Timer App Initializing...');
this.initDarkMode();
+ this.shuffleRelayColors();
this.updateLandingClock();
this.bindEvents();
this.renderCustomBuilder();
@@ -143,6 +148,13 @@ const app = {
this.bindKeyboardShortcuts();
},
+ shuffleRelayColors() {
+ for (let i = this.relayColors.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [this.relayColors[i], this.relayColors[j]] = [this.relayColors[j], this.relayColors[i]];
+ }
+ },
+
bindKeyboardShortcuts() {
document.addEventListener('keydown', (e) => {
if (this.currentView !== 'timer' || !this.currentMode) return;
@@ -155,10 +167,18 @@ const app = {
if (e.key === ' ' || e.code === 'Space') {
e.preventDefault();
- if (!this.isRunning || this.isPaused) {
- this.handleStartClick();
+ if (this.currentMode === 'lap') {
+ if (!this.isRunning || this.isPaused) {
+ this.handleStartClick();
+ } else {
+ this.recordLap();
+ }
} else {
- this.pauseTimer();
+ if (!this.isRunning || this.isPaused) {
+ this.handleStartClick();
+ } else {
+ this.pauseTimer();
+ }
}
} else if (e.key === 'Escape' || e.code === 'Escape') {
e.preventDefault();
@@ -232,7 +252,15 @@ const app = {
}
});
- this.el.toggleDisplayMode.addEventListener('change', () => this.updateDisplay());
+ this.el.toggleDisplayMode.addEventListener('change', () => {
+ const label = this.el.displayModeContainer.querySelector('label');
+ if (this.el.toggleDisplayMode.checked) {
+ label.textContent = 'Activity Progress';
+ } else {
+ label.textContent = 'Timer Progress';
+ }
+ this.updateDisplay();
+ });
},
updateLandingClock() {
@@ -286,6 +314,10 @@ const app = {
this.el.savedTimersContainer.classList.remove('d-none');
this.el.relayConfig.classList.add('d-none');
this.el.displayModeContainer.classList.remove('d-none');
+
+ // Set initial label based on toggle state
+ const label = this.el.displayModeContainer.querySelector('label');
+ label.textContent = this.el.toggleDisplayMode.checked ? 'Activity Progress' : 'Timer Progress';
} else if (mode.hasRelay) {
this.el.customBuilder.classList.add('d-none');
this.el.relayConfig.classList.remove('d-none');
@@ -310,6 +342,7 @@ const app = {
this.el.restAlertContainer.classList.toggle('d-none', !mode.isCustom);
this.el.activeActivityName.classList.add('d-none');
this.el.relayActiveParticipantContainer.classList.add('d-none');
+ this.el.relayParticipantTimer.classList.add('d-none');
// Options Dropdown visibility logic
this.el.optionsDropdown.classList.toggle('d-none', modeKey === 'time-watch');
@@ -367,9 +400,8 @@ const app = {
this.el.participantCount.value = count;
let html = '';
- const colors = ['#3b82f6', '#10b981', '#ef4444', '#f59e0b', '#8b5cf6', '#ec4899', '#06b6d4', '#f97316', '#14b8a6', '#6366f1', '#a855f7', '#d946ef'];
for (let i = 1; i <= count; i++) {
- const defaultColor = colors[(i - 1) % colors.length];
+ const defaultColor = this.relayColors[(i - 1) % this.relayColors.length];
html += `
#${i}
@@ -482,13 +514,23 @@ const app = {
this.el.relayActiveParticipantContainer.classList.remove('d-none');
this.el.relayParticipantCountBox.classList.add('d-none');
this.updateRelayActiveDisplay();
+ this.relayParticipantStartTime = now;
+ this.relayParticipantElapsed = 0;
+ this.el.relayParticipantTimer.classList.add('d-none');
}
if (this.isPaused) {
const offset = (isCountdownMode) ? (this.countdownStartValue - this.elapsedTime) : this.elapsedTime;
this.startTime = now - offset;
+
+ if (mode.hasRelay) {
+ this.relayParticipantStartTime = now - this.relayParticipantElapsed;
+ }
} else {
this.startTime = now;
+ if (mode.hasRelay) {
+ this.relayParticipantStartTime = now;
+ }
}
this.isRunning = true;
@@ -520,6 +562,9 @@ const app = {
pauseTimer() {
if (!this.isRunning || this.isPaused) return;
+
+ const mode = this.modes[this.currentMode];
+ if (mode && !mode.allowPause) return;
this.isPaused = true;
this.pausedTime = this.elapsedTime;
@@ -551,6 +596,7 @@ const app = {
this.el.btnReset.disabled = false;
this.el.mainTimer.classList.remove('timer-alert');
this.el.relayActiveParticipantContainer.classList.add('d-none');
+ this.el.relayParticipantTimer.classList.add('d-none');
if (this.currentMode === 'countdown') {
this.el.countdownInputs.classList.remove('d-none');
@@ -584,6 +630,8 @@ const app = {
this.currentParticipant = 1;
this.currentActivityIndex = 0;
this.lastPlayedSecond = -1;
+ this.relayParticipantStartTime = 0;
+ this.relayParticipantElapsed = 0;
this.el.listBody.innerHTML = '';
this.el.mainTimer.classList.remove('timer-finish');
@@ -598,6 +646,7 @@ const app = {
this.el.btnRelaySplit.disabled = false;
this.el.activeActivityName.classList.add('d-none');
this.el.relayActiveParticipantContainer.classList.add('d-none');
+ this.el.relayParticipantTimer.classList.add('d-none');
this.el.relayParticipantCountBox.classList.remove('d-none');
this.el.sessionTitle.disabled = false;
@@ -697,6 +746,10 @@ const app = {
} else {
// Standard count up
this.elapsedTime = now - this.startTime;
+
+ if (mode.hasRelay) {
+ this.relayParticipantElapsed = now - this.relayParticipantStartTime;
+ }
}
this.updateDisplay();
@@ -782,6 +835,10 @@ const app = {
this.el.mainTimer.textContent = this.formatTime(ms, format);
}
this.el.subTimer.classList.add('d-none');
+
+ if (this.currentMode === 'relay' && !this.el.relayParticipantTimer.classList.contains('d-none')) {
+ this.el.relayParticipantTimer.textContent = this.formatTime(this.relayParticipantElapsed, 'hh:mm:ss.ms');
+ }
},
getTotalProgress() {
@@ -854,6 +911,7 @@ const app = {
const maxParticipants = parseInt(this.el.participantCount.value) || 1;
if (this.currentParticipant > maxParticipants) {
this.el.relayActiveParticipantContainer.classList.add('d-none');
+ this.el.relayParticipantTimer.classList.add('d-none');
return;
}
@@ -882,6 +940,11 @@ const app = {
const colorInput = document.querySelector(`.participant-color-input[data-index="${this.currentParticipant}"]`);
const color = colorInput ? colorInput.value : '#3b82f6';
+ // Show secondary timer after first split
+ if (this.history.length >= 0) {
+ this.el.relayParticipantTimer.classList.remove('d-none');
+ }
+
// If it's the last participant, mark as final but keep their name
if (this.currentParticipant === maxParticipants) {
isFinal = true;
@@ -901,6 +964,8 @@ const app = {
if (!isFinal) {
this.currentParticipant++;
this.updateRelayActiveDisplay();
+ this.relayParticipantStartTime = performance.now();
+ this.relayParticipantElapsed = 0;
} else {
this.stopTimer();
this.el.btnRelaySplit.disabled = true;
@@ -1283,4 +1348,4 @@ const app = {
};
// Start the app
-document.addEventListener('DOMContentLoaded', () => app.init());
\ No newline at end of file
+document.addEventListener('DOMContentLoaded', () => app.init());
diff --git a/index.php b/index.php
index 236aba3..a746724 100644
--- a/index.php
+++ b/index.php
@@ -293,13 +293,14 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
00:00:00
+ 00:00:00.00
00:00:00
-
+