alpha_base_3
This commit is contained in:
parent
01b9e110c3
commit
81a9931032
@ -274,10 +274,20 @@ body {
|
||||
100% { color: inherit; }
|
||||
}
|
||||
|
||||
@keyframes blink-theme {
|
||||
0% { color: inherit; }
|
||||
50% { color: var(--accent-color); }
|
||||
100% { color: inherit; }
|
||||
}
|
||||
|
||||
.timer-alert {
|
||||
animation: blink-red 0.5s infinite;
|
||||
}
|
||||
|
||||
.timer-finish-theme {
|
||||
animation: blink-theme 0.5s infinite;
|
||||
}
|
||||
|
||||
/* Settings Switches */
|
||||
.form-check-input:checked {
|
||||
background-color: var(--accent-color);
|
||||
@ -357,4 +367,11 @@ body.dark-mode .text-muted {
|
||||
/* Saved Timers Hover Effect */
|
||||
#saved-timers-list .card-precise:hover {
|
||||
border-color: var(--success-color);
|
||||
}
|
||||
|
||||
/* Color input styling */
|
||||
.participant-color-input {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -26,6 +26,7 @@ const app = {
|
||||
|
||||
// Relay State
|
||||
currentParticipant: 1,
|
||||
participantNames: [],
|
||||
|
||||
// Custom State
|
||||
customActivities: [
|
||||
@ -53,7 +54,7 @@ const app = {
|
||||
'countdown': { title: 'Countdown Timer', allowPause: true, hasLaps: false, forceCountdown: true, canSave: true },
|
||||
'stopwatch': { title: 'Stopwatch', allowPause: false, hasLaps: false, canToggleCountdown: false, canSave: true },
|
||||
'lap': { title: 'Lap Timer', allowPause: false, hasLaps: true, canToggleCountdown: false },
|
||||
'relay': { title: 'Relay Timer', allowPause: true, hasRelay: true, canToggleCountdown: false },
|
||||
'relay': { title: 'Relay Timer', allowPause: false, hasRelay: true, canToggleCountdown: false },
|
||||
'custom': { title: 'Custom Timer', allowPause: true, isCustom: true, canToggleCountdown: true, canSave: true }
|
||||
},
|
||||
|
||||
@ -78,6 +79,7 @@ const app = {
|
||||
btnNext: document.getElementById('btn-next'),
|
||||
btnSaveMain: document.getElementById('btn-save-main'),
|
||||
btnSaveBuilder: document.getElementById('btn-save-session-builder'),
|
||||
btnResetBuilder: document.getElementById('btn-reset-builder'),
|
||||
|
||||
formatSelect: document.getElementById('format-select'),
|
||||
listTitle: document.getElementById('list-title'),
|
||||
@ -92,7 +94,12 @@ const app = {
|
||||
inputS: document.getElementById('input-s'),
|
||||
|
||||
relayConfig: document.getElementById('relay-config'),
|
||||
relayParticipantCountBox: document.getElementById('relay-participant-count-box'),
|
||||
participantCount: document.getElementById('participant-count'),
|
||||
participantNamesContainer: document.getElementById('participant-names-container'),
|
||||
relayActiveParticipantContainer: document.getElementById('relay-active-participant-container'),
|
||||
relayActiveName: document.getElementById('relay-active-name'),
|
||||
btnRelaySplit: document.getElementById('btn-relay-split'),
|
||||
|
||||
customBuilder: document.getElementById('custom-builder'),
|
||||
activityList: document.getElementById('activity-list'),
|
||||
@ -102,6 +109,7 @@ const app = {
|
||||
btnDeleteSaved: document.getElementById('btn-delete-saved'),
|
||||
|
||||
// Settings / Options
|
||||
optionsDropdown: document.getElementById('optionsDropdown'),
|
||||
optCountdownContainer: document.getElementById('opt-countdown-container'),
|
||||
customOptions: document.getElementById('custom-options'),
|
||||
restAlertContainer: document.getElementById('rest-alert-container'),
|
||||
@ -115,6 +123,8 @@ const app = {
|
||||
alertPreEndSec: document.getElementById('alert-pre-end-seconds'),
|
||||
alertCompletion: document.getElementById('alert-completion'),
|
||||
|
||||
preFinishSection: document.getElementById('pre-finish-section'),
|
||||
|
||||
brandLink: document.getElementById('brand-link'),
|
||||
darkModeToggle: document.getElementById('dark-mode-toggle')
|
||||
},
|
||||
@ -153,9 +163,14 @@ const app = {
|
||||
this.el.btnReset.addEventListener('click', () => this.resetTimer());
|
||||
this.el.btnLap.addEventListener('click', () => this.recordLap());
|
||||
this.el.btnNext.addEventListener('click', () => this.recordRelaySplit());
|
||||
this.el.btnRelaySplit.addEventListener('click', () => this.recordRelaySplit());
|
||||
this.el.btnSaveMain.addEventListener('click', () => this.saveCurrentTimer());
|
||||
this.el.btnSaveBuilder.addEventListener('click', () => this.saveCurrentTimer());
|
||||
|
||||
if (this.el.btnResetBuilder) {
|
||||
this.el.btnResetBuilder.addEventListener('click', () => this.resetCustomBuilder());
|
||||
}
|
||||
|
||||
this.el.brandLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
this.switchView('landing');
|
||||
@ -166,6 +181,8 @@ const app = {
|
||||
this.el.settingIsCountdown.addEventListener('change', () => this.resetTimer());
|
||||
this.el.lapSort.addEventListener('change', () => this.renderHistory());
|
||||
|
||||
this.el.participantCount.addEventListener('input', () => this.renderParticipantInputs());
|
||||
|
||||
this.el.savedTimersDropdown.addEventListener('change', (e) => {
|
||||
if (e.target.value) {
|
||||
this.loadTimer(e.target.value);
|
||||
@ -226,7 +243,7 @@ const app = {
|
||||
// UI Adjustments
|
||||
this.el.btnPause.classList.toggle('d-none', !mode.allowPause);
|
||||
this.el.btnLap.classList.toggle('d-none', !mode.hasLaps);
|
||||
this.el.btnNext.classList.toggle('d-none', !mode.hasRelay);
|
||||
this.el.btnNext.classList.add('d-none'); // Removed from timer entirely for all modes
|
||||
this.el.btnSaveMain.classList.toggle('d-none', !mode.canSave || mode.isCustom);
|
||||
|
||||
this.el.timerSideColumn.classList.toggle('d-none', !mode.hasLaps && !mode.hasRelay && !mode.isCustom);
|
||||
@ -235,17 +252,37 @@ const app = {
|
||||
this.el.customBuilder.classList.remove('d-none');
|
||||
this.el.historySection.classList.add('d-none');
|
||||
this.el.savedTimersContainer.classList.remove('d-none');
|
||||
this.el.relayConfig.classList.add('d-none');
|
||||
} else if (mode.hasRelay) {
|
||||
this.el.customBuilder.classList.add('d-none');
|
||||
this.el.relayConfig.classList.remove('d-none');
|
||||
this.el.historySection.classList.remove('d-none');
|
||||
this.el.savedTimersContainer.classList.add('d-none');
|
||||
|
||||
// Move history to main column immediately for Relay (separate from participants)
|
||||
this.el.lapResultsContainer.classList.remove('d-none');
|
||||
this.el.lapResultsContainer.appendChild(this.el.historySection);
|
||||
this.el.lapSort.classList.remove('d-none');
|
||||
} else {
|
||||
this.el.customBuilder.classList.add('d-none');
|
||||
this.el.relayConfig.classList.add('d-none');
|
||||
this.el.historySection.classList.remove('d-none');
|
||||
this.el.savedTimersContainer.classList.toggle('d-none', !mode.canSave);
|
||||
}
|
||||
|
||||
this.el.countdownInputs.classList.toggle('d-none', modeKey !== 'countdown');
|
||||
this.el.relayConfig.classList.toggle('d-none', !mode.hasRelay);
|
||||
this.el.customOptions.classList.toggle('d-none', !mode.isCustom);
|
||||
this.el.restAlertContainer.classList.toggle('d-none', !mode.isCustom);
|
||||
this.el.activeActivityName.classList.add('d-none');
|
||||
this.el.relayActiveParticipantContainer.classList.add('d-none');
|
||||
|
||||
// Options Dropdown visibility logic
|
||||
this.el.optionsDropdown.classList.toggle('d-none', modeKey === 'time-watch');
|
||||
|
||||
// Pre-finish section visibility logic
|
||||
const noPreFinishModes = ['stopwatch', 'lap', 'relay'];
|
||||
this.el.preFinishSection.classList.toggle('d-none', noPreFinishModes.includes(modeKey));
|
||||
|
||||
this.el.lapSort.classList.add('d-none');
|
||||
|
||||
// Options Box adjustments
|
||||
@ -267,7 +304,8 @@ const app = {
|
||||
this.el.listHead.innerHTML = '<tr><th style="width: 80px;">Lap</th><th>Split Time</th><th>Delta</th><th class="text-end">Notes</th></tr>';
|
||||
} else if (mode.hasRelay) {
|
||||
this.el.listTitle.textContent = 'Relay Splits';
|
||||
this.el.listHead.innerHTML = '<tr><th style="width: 120px;">Participant</th><th>Total Time</th><th>Split</th><th class="text-end">Status</th></tr>';
|
||||
this.el.listHead.innerHTML = '<tr><th>Participant</th><th>Start Time</th><th>Split Time</th><th class="text-end">Total Time</th></tr>';
|
||||
this.renderParticipantInputs();
|
||||
} else if (mode.isCustom) {
|
||||
this.el.listTitle.textContent = 'Activities';
|
||||
this.el.listHead.innerHTML = '<tr><th>Activity</th><th>Duration</th><th class="text-end">Status</th></tr>';
|
||||
@ -277,6 +315,25 @@ const app = {
|
||||
this.updateDisplay();
|
||||
},
|
||||
|
||||
renderParticipantInputs() {
|
||||
const count = Math.min(12, Math.max(1, parseInt(this.el.participantCount.value) || 1));
|
||||
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];
|
||||
html += `
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text fs-tiny">#${i}</span>
|
||||
<input type="text" class="form-control form-control-precise participant-name-input" data-index="${i}" placeholder="Participant Name">
|
||||
<input type="color" class="form-control form-control-color form-control-precise p-1 participant-color-input" data-index="${i}" value="${defaultColor}" style="width: 40px; height: 31px;">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
this.el.participantNamesContainer.innerHTML = html;
|
||||
},
|
||||
|
||||
handleStartClick() {
|
||||
if (!this.el.sessionTitle.value.trim()) {
|
||||
if (this.currentMode === 'custom') {
|
||||
@ -348,6 +405,7 @@ const app = {
|
||||
}
|
||||
this.countdownStartValue = totalMs;
|
||||
this.elapsedTime = totalMs;
|
||||
this.el.countdownInputs.classList.add('d-none');
|
||||
}
|
||||
|
||||
if (mode.isCustom && !this.isPaused) {
|
||||
@ -371,6 +429,14 @@ const app = {
|
||||
this.updateCurrentActivityStatus('in-progress');
|
||||
}
|
||||
|
||||
if (mode.hasRelay && !this.isPaused) {
|
||||
this.currentParticipant = 1;
|
||||
this.history = [];
|
||||
this.el.relayActiveParticipantContainer.classList.remove('d-none');
|
||||
this.el.relayParticipantCountBox.classList.add('d-none');
|
||||
this.updateRelayActiveDisplay();
|
||||
}
|
||||
|
||||
if (this.isPaused) {
|
||||
const offset = (isCountdownMode) ? (this.countdownStartValue - this.elapsedTime) : this.elapsedTime;
|
||||
this.startTime = now - offset;
|
||||
@ -388,6 +454,7 @@ const app = {
|
||||
this.el.btnReset.disabled = true;
|
||||
this.el.sessionTitle.disabled = true;
|
||||
this.el.mainTimer.classList.remove('timer-finish');
|
||||
this.el.mainTimer.classList.remove('timer-finish-theme');
|
||||
this.el.mainTimer.classList.remove('timer-alert');
|
||||
|
||||
// Lock inputs
|
||||
@ -395,6 +462,8 @@ const app = {
|
||||
if (this.el.inputM) this.el.inputM.disabled = true;
|
||||
if (this.el.inputS) this.el.inputS.disabled = true;
|
||||
if (this.el.participantCount) this.el.participantCount.disabled = true;
|
||||
document.querySelectorAll('.participant-name-input').forEach(i => i.disabled = true);
|
||||
document.querySelectorAll('.participant-color-input').forEach(i => i.disabled = true);
|
||||
|
||||
this.tick();
|
||||
},
|
||||
@ -428,6 +497,11 @@ const app = {
|
||||
this.el.btnStop.disabled = true;
|
||||
this.el.btnReset.disabled = false;
|
||||
this.el.mainTimer.classList.remove('timer-alert');
|
||||
this.el.relayActiveParticipantContainer.classList.add('d-none');
|
||||
|
||||
if (this.currentMode === 'countdown') {
|
||||
this.el.countdownInputs.classList.remove('d-none');
|
||||
}
|
||||
|
||||
if (this.currentMode === 'lap') {
|
||||
this.recordLap();
|
||||
@ -436,7 +510,10 @@ const app = {
|
||||
this.el.lapResultsContainer.appendChild(this.el.historySection);
|
||||
this.el.lapSort.classList.remove('d-none');
|
||||
} else if (this.currentMode === 'relay') {
|
||||
this.recordRelaySplit(true);
|
||||
if (wasRunning) this.recordRelaySplit(true);
|
||||
// Relay splits panel is already under controls in setMode, just ensure sorting is visible
|
||||
this.el.lapResultsContainer.classList.remove('d-none');
|
||||
this.el.lapSort.classList.remove('d-none');
|
||||
} else if (this.currentMode === 'custom' && wasRunning) {
|
||||
this.updateCurrentActivityStatus('Canceled');
|
||||
}
|
||||
@ -454,39 +531,76 @@ const app = {
|
||||
|
||||
this.el.listBody.innerHTML = '';
|
||||
this.el.mainTimer.classList.remove('timer-finish');
|
||||
this.el.mainTimer.classList.remove('timer-finish-theme');
|
||||
this.el.mainTimer.classList.remove('timer-alert');
|
||||
|
||||
this.el.btnReset.disabled = true;
|
||||
this.el.btnStart.disabled = false;
|
||||
this.el.btnPause.disabled = true;
|
||||
this.el.btnStop.disabled = true;
|
||||
this.el.btnNext.disabled = false;
|
||||
this.el.btnRelaySplit.disabled = false;
|
||||
this.el.activeActivityName.classList.add('d-none');
|
||||
this.el.relayActiveParticipantContainer.classList.add('d-none');
|
||||
this.el.relayParticipantCountBox.classList.remove('d-none');
|
||||
this.el.sessionTitle.disabled = false;
|
||||
|
||||
if (this.el.inputH) this.el.inputH.disabled = false;
|
||||
if (this.el.inputM) this.el.inputM.disabled = false;
|
||||
if (this.el.inputS) this.el.inputS.disabled = false;
|
||||
if (this.el.inputH) {
|
||||
this.el.inputH.disabled = false;
|
||||
if (this.currentMode === 'countdown') this.el.inputH.value = '';
|
||||
}
|
||||
if (this.el.inputM) {
|
||||
this.el.inputM.disabled = false;
|
||||
if (this.currentMode === 'countdown') this.el.inputM.value = '';
|
||||
}
|
||||
if (this.el.inputS) {
|
||||
this.el.inputS.disabled = false;
|
||||
if (this.currentMode === 'countdown') this.el.inputS.value = '';
|
||||
}
|
||||
|
||||
if (this.currentMode === 'countdown' || this.currentMode === 'stopwatch') {
|
||||
this.el.savedTimersDropdown.value = '';
|
||||
this.el.sessionTitle.value = '';
|
||||
if (this.currentMode) this.sessionTitles[this.currentMode] = '';
|
||||
}
|
||||
|
||||
if (this.el.participantCount) this.el.participantCount.disabled = false;
|
||||
document.querySelectorAll('.participant-name-input').forEach(i => i.disabled = false);
|
||||
document.querySelectorAll('.participant-color-input').forEach(i => i.disabled = false);
|
||||
|
||||
if (this.currentMode === 'countdown') {
|
||||
this.el.countdownInputs.classList.remove('d-none');
|
||||
}
|
||||
|
||||
// Move history back to side column default
|
||||
this.el.timerSideColumn.querySelector('.card-precise').appendChild(this.el.historySection);
|
||||
this.el.lapResultsContainer.classList.add('d-none');
|
||||
this.el.lapSort.classList.add('d-none');
|
||||
|
||||
// Transition: History -> Builder (if custom)
|
||||
if (this.currentMode === 'custom') {
|
||||
this.el.customBuilder.classList.remove('d-none');
|
||||
this.el.historySection.classList.add('d-none');
|
||||
this.el.savedTimersContainer.classList.remove('d-none');
|
||||
// Ensure history section is back in side column if it was moved (though only lap moves it)
|
||||
this.el.timerSideColumn.querySelector('.card-precise').appendChild(this.el.historySection);
|
||||
}
|
||||
|
||||
if (this.currentMode === 'lap') {
|
||||
// Move history back to side column
|
||||
this.el.timerSideColumn.querySelector('.card-precise').appendChild(this.el.historySection);
|
||||
this.el.lapResultsContainer.classList.add('d-none');
|
||||
this.el.lapSort.classList.add('d-none');
|
||||
} else if (this.currentMode === 'relay') {
|
||||
// Put it back under timer for relay even after reset
|
||||
this.el.lapResultsContainer.classList.remove('d-none');
|
||||
this.el.lapResultsContainer.appendChild(this.el.historySection);
|
||||
this.el.lapSort.classList.remove('d-none');
|
||||
}
|
||||
|
||||
this.updateDisplay();
|
||||
},
|
||||
|
||||
resetCustomBuilder() {
|
||||
this.customActivities = [{ name: 'Activity 1', duration: 60000, isRest: false }];
|
||||
this.el.sessionTitle.value = '';
|
||||
if (this.currentMode) this.sessionTitles[this.currentMode] = '';
|
||||
this.el.savedTimersDropdown.value = '';
|
||||
this.renderCustomBuilder();
|
||||
this.resetTimer();
|
||||
},
|
||||
|
||||
tick() {
|
||||
if (!this.isRunning || this.isPaused) return;
|
||||
|
||||
@ -599,8 +713,13 @@ const app = {
|
||||
|
||||
updateDisplay() {
|
||||
const ms = this.elapsedTime;
|
||||
const format = this.el.formatSelect.value;
|
||||
let format = this.el.formatSelect.value;
|
||||
|
||||
if (this.currentMode === 'time-watch') {
|
||||
if (format === 'hh:mm:ss.ms') format = 'hh:mm:ss';
|
||||
if (format === 'seconds.ms') format = 'seconds';
|
||||
}
|
||||
|
||||
this.el.mainTimer.textContent = this.formatTime(ms, format);
|
||||
|
||||
// Only show sub-timer if format is not milliseconds as requested
|
||||
@ -656,6 +775,24 @@ const app = {
|
||||
this.renderHistory();
|
||||
},
|
||||
|
||||
updateRelayActiveDisplay() {
|
||||
const maxParticipants = parseInt(this.el.participantCount.value) || 1;
|
||||
if (this.currentParticipant > maxParticipants) {
|
||||
this.el.relayActiveParticipantContainer.classList.add('d-none');
|
||||
return;
|
||||
}
|
||||
|
||||
const nameInput = document.querySelector(`.participant-name-input[data-index="${this.currentParticipant}"]`);
|
||||
const name = nameInput ? (nameInput.value.trim() || `P${this.currentParticipant}`) : `P${this.currentParticipant}`;
|
||||
const colorInput = document.querySelector(`.participant-color-input[data-index="${this.currentParticipant}"]`);
|
||||
const color = colorInput ? colorInput.value : '#3b82f6';
|
||||
|
||||
this.el.relayActiveName.textContent = name;
|
||||
this.el.relayActiveName.style.color = color;
|
||||
this.el.btnRelaySplit.disabled = false;
|
||||
this.el.btnRelaySplit.textContent = 'Split';
|
||||
},
|
||||
|
||||
recordRelaySplit(isFinal = false) {
|
||||
const now = this.elapsedTime;
|
||||
const lastTime = this.history.length > 0 ? this.history[this.history.length - 1].time : 0;
|
||||
@ -664,28 +801,44 @@ const app = {
|
||||
|
||||
if (this.currentParticipant > maxParticipants && !isFinal) return;
|
||||
|
||||
// Get name and color
|
||||
const nameInput = document.querySelector(`.participant-name-input[data-index="${this.currentParticipant}"]`);
|
||||
let name = nameInput ? (nameInput.value.trim() || `P${this.currentParticipant}`) : `P${this.currentParticipant}`;
|
||||
const colorInput = document.querySelector(`.participant-color-input[data-index="${this.currentParticipant}"]`);
|
||||
const color = colorInput ? colorInput.value : '#3b82f6';
|
||||
|
||||
// If it's the last participant, mark as final but keep their name
|
||||
if (this.currentParticipant === maxParticipants) {
|
||||
isFinal = true;
|
||||
}
|
||||
|
||||
this.history.push({
|
||||
id: `P${this.currentParticipant}`,
|
||||
id: name,
|
||||
color: color,
|
||||
startTime: lastTime,
|
||||
time: now,
|
||||
delta: delta,
|
||||
status: isFinal ? 'DONE' : 'SPLIT'
|
||||
});
|
||||
|
||||
this.el.btnRelaySplit.disabled = true;
|
||||
|
||||
if (!isFinal) {
|
||||
this.currentParticipant++;
|
||||
if (this.currentParticipant > maxParticipants) {
|
||||
this.stopTimer();
|
||||
}
|
||||
this.updateRelayActiveDisplay();
|
||||
} else {
|
||||
this.stopTimer();
|
||||
this.el.btnRelaySplit.disabled = true;
|
||||
}
|
||||
|
||||
this.renderHistory();
|
||||
},
|
||||
|
||||
renderHistory() {
|
||||
if (this.currentMode === 'lap') {
|
||||
let sortedHistory = this.history.slice();
|
||||
const sortVal = this.el.lapSort.value;
|
||||
let sortedHistory = this.history.slice();
|
||||
const sortVal = this.el.lapSort.value;
|
||||
|
||||
if (this.currentMode === 'lap' || this.currentMode === 'relay') {
|
||||
if (sortVal === 'best') {
|
||||
sortedHistory.sort((a, b) => a.delta - b.delta);
|
||||
} else if (sortVal === 'worse') {
|
||||
@ -699,27 +852,27 @@ const app = {
|
||||
if (h.delta < minDelta) minDelta = h.delta;
|
||||
});
|
||||
|
||||
this.el.listBody.innerHTML = sortedHistory.map(h => `
|
||||
<tr class="${h.delta === minDelta ? 'table-success' : ''}">
|
||||
<td class="font-tabular fw-bold">#${h.id}</td>
|
||||
<td class="font-tabular">${this.formatTime(h.time, 'hh:mm:ss.ms')}</td>
|
||||
<td class="font-tabular text-muted">+${this.formatTime(h.delta, 'seconds.ms')}</td>
|
||||
<td class="text-end">
|
||||
${h.delta === minDelta ? '<span class="badge-best">BEST</span>' : ''}
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} else if (this.currentMode === 'relay') {
|
||||
this.el.listBody.innerHTML = this.history.slice().reverse().map(h => `
|
||||
<tr>
|
||||
<td class="fw-bold">${h.id}</td>
|
||||
<td class="font-tabular">${this.formatTime(h.time, 'hh:mm:ss.ms')}</td>
|
||||
<td class="font-tabular text-muted">${this.formatTime(h.delta, 'seconds.ms')}</td>
|
||||
<td class="text-end">
|
||||
<span class="badge ${h.status === 'DONE' ? 'bg-danger' : 'bg-primary'} font-tabular" style="font-size: 0.6rem;">${h.status}</span>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
if (this.currentMode === 'relay') {
|
||||
this.el.listBody.innerHTML = sortedHistory.map(h => `
|
||||
<tr>
|
||||
<td class="font-tabular fw-bold" style="border-left: 4px solid ${h.color}; padding-left: 10px;">${h.id}</td>
|
||||
<td class="font-tabular">${this.formatTime(h.startTime || 0, 'hh:mm:ss.ms')}</td>
|
||||
<td class="font-tabular text-primary">${this.formatTime(h.delta, 'hh:mm:ss.ms')}</td>
|
||||
<td class="text-end font-tabular fw-bold">${this.formatTime(h.time, 'hh:mm:ss.ms')}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} else {
|
||||
this.el.listBody.innerHTML = sortedHistory.map(h => `
|
||||
<tr class="${h.delta === minDelta && h.delta > 0 ? 'table-success' : ''}">
|
||||
<td class="font-tabular fw-bold">${h.id}</td>
|
||||
<td class="font-tabular">${this.formatTime(h.time, 'hh:mm:ss.ms')}</td>
|
||||
<td class="font-tabular text-muted">+${this.formatTime(h.delta, 'seconds.ms')}</td>
|
||||
<td class="text-end">
|
||||
${h.delta === minDelta && h.delta > 0 ? '<span class="badge-best">BEST</span>' : ''}
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
}
|
||||
} else if (this.currentMode === 'custom') {
|
||||
this.el.listBody.innerHTML = this.history.slice().reverse().map(h => {
|
||||
let badgeClass = 'bg-success';
|
||||
@ -884,11 +1037,7 @@ const app = {
|
||||
alert('Session saved successfully!');
|
||||
|
||||
if (this.currentMode === 'custom') {
|
||||
this.customActivities = [{ name: 'Activity 1', duration: 60000, isRest: false }];
|
||||
this.el.sessionTitle.value = '';
|
||||
if (this.currentMode) this.sessionTitles[this.currentMode] = '';
|
||||
this.renderCustomBuilder();
|
||||
this.resetTimer();
|
||||
this.resetCustomBuilder();
|
||||
}
|
||||
} else {
|
||||
alert('Error saving session: ' + result.error);
|
||||
@ -997,7 +1146,11 @@ const app = {
|
||||
},
|
||||
|
||||
playCompletionAlert() {
|
||||
this.el.mainTimer.classList.add('timer-finish');
|
||||
this.el.mainTimer.classList.add('timer-finish-theme');
|
||||
setTimeout(() => {
|
||||
this.el.mainTimer.classList.remove('timer-finish-theme');
|
||||
}, 3000);
|
||||
|
||||
if (this.el.alertCompletion.checked) {
|
||||
this.beep(880, 0.5);
|
||||
setTimeout(() => this.beep(1100, 0.5), 200);
|
||||
|
||||
38
index.php
38
index.php
@ -158,7 +158,7 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<div class="alert-settings-box">
|
||||
<label class="d-block small text-muted text-uppercase tracking-wider mb-2 border-bottom pb-1">Sound Alerts</label>
|
||||
|
||||
<div class="mb-3">
|
||||
<div id="pre-start-section" class="mb-3">
|
||||
<div class="form-check form-switch mb-1">
|
||||
<input class="form-check-input" type="checkbox" id="alert-pre-start">
|
||||
<label class="form-check-label fw-bold small" for="alert-pre-start">Pre-start</label>
|
||||
@ -169,7 +169,7 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div id="pre-finish-section" class="mb-3">
|
||||
<div class="form-check form-switch mb-1">
|
||||
<input class="form-check-input" type="checkbox" id="alert-pre-end" checked>
|
||||
<label class="form-check-label fw-bold small" for="alert-pre-end">Pre-finish</label>
|
||||
@ -187,7 +187,7 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch">
|
||||
<div id="completion-section" class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="alert-completion" checked>
|
||||
<label class="form-check-label fw-bold small" for="alert-completion">Completion Sound</label>
|
||||
</div>
|
||||
@ -199,7 +199,7 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
|
||||
<div class="timer-workspace-container">
|
||||
|
||||
<!-- Side Column (History / Builder) - LEFT -->
|
||||
<!-- Side Column (History / Builder / Relay Config) - LEFT -->
|
||||
<div id="timer-side-column" class="timer-side-column d-none">
|
||||
<div class="card card-precise p-4 h-100">
|
||||
|
||||
@ -238,6 +238,7 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
<div class="d-flex gap-1">
|
||||
<button id="btn-save-session-builder" class="btn btn-outline-success btn-sm btn-precise">Save</button>
|
||||
<button id="btn-reset-builder" class="btn btn-outline-dark btn-sm btn-precise">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -246,6 +247,18 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Relay Configuration Section -->
|
||||
<div id="relay-config" class="d-none text-start">
|
||||
<h4 class="fs-6 fw-bold text-uppercase tracking-widest mb-3 border-bottom pb-2">Participants</h4>
|
||||
<div id="relay-participant-count-box" class="mb-3">
|
||||
<label class="small text-muted mb-1 d-block">Participant Count (Max 12)</label>
|
||||
<input type="number" id="participant-count" class="form-control form-control-precise" value="4" min="1" max="12" style="width: 80px;">
|
||||
</div>
|
||||
<div id="participant-names-container" class="d-flex flex-column gap-2">
|
||||
<!-- Name inputs injected here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -269,6 +282,14 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
|
||||
<div id="active-activity-name" class="text-primary d-none text-center mt-2">Activity Name</div>
|
||||
|
||||
<!-- Relay Active Participant Container -->
|
||||
<div id="relay-active-participant-container" class="d-none mt-3 p-3 border rounded bg-light">
|
||||
<div class="d-flex align-items-center justify-content-center gap-3">
|
||||
<span id="relay-active-name" class="fw-bold fs-5">Participant Name</span>
|
||||
<button id="btn-relay-split" class="btn btn-primary btn-sm btn-precise">Split</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timer-display font-tabular mb-2" id="main-timer">00:00:00.000</div>
|
||||
@ -299,15 +320,6 @@ $projectImage = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<button id="btn-delete-saved" class="btn btn-outline-danger btn-precise">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mode Specific Inputs (Other) -->
|
||||
<div class="d-flex justify-content-center flex-wrap gap-3 mb-5 mt-5">
|
||||
<div id="relay-config" class="d-none text-start">
|
||||
<label class="small text-muted mb-1 d-block">Participant Count</label>
|
||||
<input type="number" id="participant-count" class="form-control form-control-precise" value="4" min="1" style="width: 100px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user