diff --git a/assets/css/custom.css b/assets/css/custom.css index 1dd57f4..c3b7c60 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -11,11 +11,31 @@ --font-mono: 'JetBrains Mono', 'Fira Code', monospace; } +body.dark-mode { + --bg-color: #0f172a; + --surface-color: #1e293b; + --text-primary: #f8fafc; + --text-secondary: #94a3b8; + --border-color: #334155; + --accent-color: #60a5fa; + --accent-hover: #93c5fd; +} + body { background-color: var(--bg-color); color: var(--text-primary); font-family: 'Inter', system-ui, -apple-system, sans-serif; -webkit-font-smoothing: antialiased; + transition: background-color 0.3s ease, color 0.3s ease; +} + +.navbar { + background-color: var(--bg-color) !important; + border-color: var(--border-color) !important; +} + +.navbar-brand { + color: var(--text-primary) !important; } .font-tabular { @@ -23,6 +43,25 @@ body { font-family: var(--font-mono); } +/* Dark Mode Toggle */ +.dark-mode-toggle { + width: 40px; + height: 40px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid var(--border-color); + background: var(--surface-color); + cursor: pointer; + transition: all 0.2s ease; +} + +.dark-mode-toggle:hover { + border-color: var(--accent-color); + color: var(--accent-color); +} + /* Landing Clock */ .hero-clock { font-size: clamp(4rem, 15vw, 10rem); @@ -38,11 +77,12 @@ body { border: 1px solid var(--border-color); border-radius: 4px; transition: all 0.2s ease; + color: var(--text-primary); } .card-precise:hover { border-color: var(--accent-color); - background: #fff; + background: var(--bg-color); box-shadow: 0 4px 12px rgba(0,0,0,0.03); } @@ -69,6 +109,10 @@ body { } /* Table */ +.table { + color: var(--text-primary); +} + .table-precise { font-size: 0.875rem; } @@ -82,6 +126,10 @@ body { border-bottom: 2px solid var(--border-color); } +.table-precise td { + border-bottom: 1px solid var(--border-color); +} + .badge-best { background-color: var(--success-color); color: white; @@ -100,6 +148,19 @@ body { } /* Forms */ +.form-control, .form-select { + background-color: var(--surface-color); + color: var(--text-primary); + border-color: var(--border-color); +} + +.form-control:focus, .form-select:focus { + background-color: var(--bg-color); + color: var(--text-primary); + border-color: var(--accent-color); + box-shadow: none; +} + .form-control-precise { border-radius: 4px; border: 1px solid var(--border-color); @@ -107,13 +168,8 @@ body { padding: 0.4rem 0.75rem; } -.form-control-precise:focus { - border-color: var(--accent-color); - box-shadow: none; -} - #session-title { - background-color: var(--surface-color); + background-color: transparent; border-width: 0 0 2px 0; border-radius: 0; padding-left: 0; @@ -133,8 +189,21 @@ body { } .activity-row { + background: var(--bg-color) !important; + border-color: var(--border-color) !important; transition: transform 0.2s ease; box-shadow: 0 2px 4px rgba(0,0,0,0.02); + cursor: grab; +} + +.activity-row:active { + cursor: grabbing; +} + +.activity-row.dragging { + opacity: 0.5; + background: var(--border-color) !important; + transform: scale(0.98); } .activity-row:hover { @@ -142,10 +211,33 @@ body { box-shadow: 0 4px 8px rgba(0,0,0,0.05); } +.activity-row.rest-activity { + border-left: 4px solid var(--accent-color) !important; + font-size: 0.8rem; + opacity: 0.9; + padding: 0.5rem 1rem !important; +} + +.activity-row.rest-activity .form-control { + font-size: 0.8rem; +} + +.activity-row.rest-activity .btn { + font-size: 0.65rem; + padding: 0.2rem 0.4rem; +} + +.activity-row.rest-activity .fs-tiny { + font-size: 0.6rem; +} + /* Options Dropdown */ .dropdown-menu { - box-shadow: 0 10px 25px rgba(0,0,0,0.1) !important; + background-color: var(--surface-color); + color: var(--text-primary); + box-shadow: 0 10px 25px rgba(0,0,0,0.2) !important; border: 1px solid var(--border-color); + z-index: 1050; } .dropdown-header { @@ -161,6 +253,12 @@ body { .fs-tiny { font-size: 0.65rem; } .cursor-pointer { cursor: pointer; } +#active-activity-name { + letter-spacing: 0.02em; + font-size: 1.1rem; + font-weight: 600; +} + /* Alerts / Finishes */ .timer-finish { animation: flash 1s infinite; @@ -176,4 +274,40 @@ body { .form-check-input:checked { background-color: var(--accent-color); border-color: var(--accent-color); +} + +/* Layout Adjustments */ +.timer-workspace-container { + display: flex; + flex-wrap: wrap; + gap: 2rem; + justify-content: center; +} + +.timer-main-column { + flex: 1; + min-width: 320px; + max-width: 800px; + order: 2; /* Main on right on desktop */ +} + +.timer-side-column { + width: 350px; + order: 1; /* History on left on desktop */ +} + +@media (max-width: 1200px) { + .timer-side-column { + width: 100%; + max-width: 800px; + margin: 0 auto; + order: 2; /* Move history back to bottom on mobile */ + } + .timer-main-column { + order: 1; /* Main timer on top on mobile */ + } +} + +footer { + border-color: var(--border-color) !important; } \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index ec5d31f..b4359ae 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -6,6 +6,7 @@ * - Display Formats * - Lap, Relay Split & Custom Activity Logic * - Sound Alerts & Session Management + * - Dark Mode & UI Layout */ const app = { @@ -15,6 +16,7 @@ const app = { isRunning: false, isPaused: false, isPreStarting: false, + isDarkMode: false, startTime: 0, elapsedTime: 0, pausedTime: 0, @@ -27,10 +29,11 @@ const app = { // Custom State customActivities: [ - { name: 'Activity 1', duration: 60000 }, - { name: 'Activity 2', duration: 120000 } + { name: 'Activity 1', duration: 60000, isRest: false }, + { name: 'Rest', duration: 30000, isRest: true } ], currentActivityIndex: 0, + draggedItemIndex: null, // Audio Helpers audioCtx: null, @@ -56,6 +59,7 @@ const app = { timerTitle: document.getElementById('timer-title'), activeActivityName: document.getElementById('active-activity-name'), sessionTitle: document.getElementById('session-title'), + timerSideColumn: document.getElementById('timer-side-column'), btnStart: document.getElementById('btn-start'), btnPause: document.getElementById('btn-pause'), @@ -65,7 +69,6 @@ const app = { btnNext: document.getElementById('btn-next'), formatSelect: document.getElementById('format-select'), - listContainer: document.getElementById('list-container'), listTitle: document.getElementById('list-title'), listHead: document.getElementById('list-head'), listBody: document.getElementById('list-body'), @@ -84,24 +87,48 @@ const app = { // Settings / Options optCountdownContainer: document.getElementById('opt-countdown-container'), + customOptions: document.getElementById('custom-options'), + restAlertContainer: document.getElementById('rest-alert-container'), settingIsCountdown: document.getElementById('setting-is-countdown'), + settingRestDuration: document.getElementById('setting-rest-duration'), + alertRestPreEnd: document.getElementById('alert-rest-pre-end'), + alertPreStart: document.getElementById('alert-pre-start'), alertPreStartSec: document.getElementById('alert-pre-start-seconds'), alertPreEnd: document.getElementById('alert-pre-end'), alertPreEndSec: document.getElementById('alert-pre-end-seconds'), alertCompletion: document.getElementById('alert-completion'), - brandLink: document.getElementById('brand-link') + brandLink: document.getElementById('brand-link'), + darkModeToggle: document.getElementById('dark-mode-toggle') }, init() { console.log('Timer App Initializing...'); + this.initDarkMode(); this.updateLandingClock(); this.bindEvents(); this.renderCustomBuilder(); this.resetTimer(); }, + initDarkMode() { + this.isDarkMode = localStorage.getItem('darkMode') === 'true'; + this.applyDarkMode(); + }, + + applyDarkMode() { + document.body.classList.toggle('dark-mode', this.isDarkMode); + document.getElementById('moon-icon').classList.toggle('d-none', this.isDarkMode); + document.getElementById('sun-icon').classList.toggle('d-none', !this.isDarkMode); + }, + + toggleDarkMode() { + this.isDarkMode = !this.isDarkMode; + localStorage.setItem('darkMode', this.isDarkMode); + this.applyDarkMode(); + }, + bindEvents() { this.el.btnStart.addEventListener('click', () => this.handleStartClick()); this.el.btnPause.addEventListener('click', () => this.pauseTimer()); @@ -116,6 +143,7 @@ const app = { this.switchView('landing'); }); + this.el.darkModeToggle.addEventListener('click', () => this.toggleDarkMode()); this.el.formatSelect.addEventListener('change', () => this.updateDisplay()); this.el.settingIsCountdown.addEventListener('change', () => this.resetTimer()); }, @@ -157,10 +185,12 @@ const app = { this.el.btnLap.classList.toggle('d-none', !mode.hasLaps); this.el.btnNext.classList.toggle('d-none', !mode.hasRelay); - this.el.listContainer.classList.toggle('d-none', !mode.hasLaps && !mode.hasRelay && !mode.isCustom); + this.el.timerSideColumn.classList.toggle('d-none', !mode.hasLaps && !mode.hasRelay && !mode.isCustom); this.el.countdownInputs.classList.toggle('d-none', modeKey !== 'countdown'); this.el.relayConfig.classList.toggle('d-none', !mode.hasRelay); this.el.customBuilder.classList.toggle('d-none', !mode.isCustom); + 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'); // Options Box adjustments @@ -169,7 +199,7 @@ const app = { this.el.settingIsCountdown.checked = true; this.el.settingIsCountdown.disabled = true; } else if (modeKey === 'custom') { - this.el.settingIsCountdown.checked = false; // Default to count up as requested + this.el.settingIsCountdown.checked = false; // Default to count up this.el.settingIsCountdown.disabled = false; } else { this.el.settingIsCountdown.checked = false; @@ -178,14 +208,14 @@ const app = { // Update List Header if (mode.hasLaps) { - this.el.listTitle.textContent = 'Laps'; + this.el.listTitle.textContent = 'Laps Recorded'; this.el.listHead.innerHTML = '