diff --git a/changelog.js b/changelog.js index b8da734..a516651 100644 --- a/changelog.js +++ b/changelog.js @@ -1,7 +1,21 @@ // Version tracking -export const APP_VERSION = '1.0.26'; +export const APP_VERSION = '1.0.27'; export const CHANGELOG = { + '1.0.27': { + date: '2026-02-03', + changes: { + features: [ + 'Revised accelerate-time (skip-ahead) flow: skipped preset days are styled as past, hidden when not current, and correctly numbered when starting a new day with Wake Up', + 'Add keyboard shortcut for Accelerate Time', + ], + improvements: [ + 'Sound choices in settings are now applied correctly; sound loading is more reliable', + 'Nominations countdown resets when starting a new day; wake-up countdown shortened to 6 seconds', + 'Timer display no longer flickers when reloading', + ], + }, + }, '1.0.26': { date: '2026-02-03', changes: { diff --git a/cypress/e2e/02-first-time-user.cy.js b/cypress/e2e/02-first-time-user.cy.js index a82cc4a..0f21d26 100644 --- a/cypress/e2e/02-first-time-user.cy.js +++ b/cypress/e2e/02-first-time-user.cy.js @@ -39,9 +39,8 @@ describe('First Time User Flow', () => { cy.get('#startBtn .button-text').should('have.text', '⏰ Wake Up!'); cy.get('#startBtn').should('not.be.disabled'); cy.get('#resetBtn').should('have.text', 'πŸ”„ Reset Day').and('be.disabled'); - cy.get('#accelerateBtn') - .should('have.text', '⏩ Accelerate Time') - .and('be.disabled'); + cy.get('#accelerateBtn .button-text').should('have.text', '⏩ Accelerate Time'); + cy.get('#accelerateBtn').should('be.disabled'); // Verify navigation buttons exist cy.get('#settingsBtn').should('exist'); diff --git a/cypress/e2e/05-timer-behavior.cy.js b/cypress/e2e/05-timer-behavior.cy.js index 0ee517f..68fd164 100644 --- a/cypress/e2e/05-timer-behavior.cy.js +++ b/cypress/e2e/05-timer-behavior.cy.js @@ -11,6 +11,10 @@ describe('Timer Behavior', () => { // Settings dialog should be open on fresh start cy.get('#settingsDialog').should('be.visible'); + // Turn OFF effects (trigger change so playSoundEffects is updated before save) + cy.get('.tab-button[data-tab="effects"]').click(); + cy.get('#playSoundEffects').uncheck().trigger('change'); + // Save and Close settings cy.get('#closeSettings').click(); cy.get('#settingsDialog').should('not.be.visible'); @@ -49,6 +53,11 @@ describe('Timer Behavior', () => { cy.visit('/'); cy.get('#settingsDialog').should('be.visible'); + + // Turn OFF effects (trigger change so playSoundEffects is updated before save) + cy.get('.tab-button[data-tab="effects"]').click(); + cy.get('#playSoundEffects').uncheck().trigger('change'); + cy.get('#closeSettings').click(); cy.get('#settingsDialog').should('not.be.visible'); diff --git a/cypress/e2e/07-keyboard-shortcuts.cy.js b/cypress/e2e/07-keyboard-shortcuts.cy.js index 06ed2df..a8271d8 100644 --- a/cypress/e2e/07-keyboard-shortcuts.cy.js +++ b/cypress/e2e/07-keyboard-shortcuts.cy.js @@ -21,6 +21,7 @@ describe('Keyboard Shortcuts', () => { cy.get('#shortcutSettings').should('exist'); cy.get('#shortcutWakeUp').should('exist'); cy.get('#shortcutReset').should('exist'); + cy.get('#shortcutAccelerate').should('exist'); cy.get('#shortcutFullscreen').should('exist'); cy.get('#shortcutInfo').should('exist'); @@ -28,6 +29,7 @@ describe('Keyboard Shortcuts', () => { cy.get('#shortcutSettings').should('have.value', 'q'); cy.get('#shortcutWakeUp').should('have.value', 'Space'); cy.get('#shortcutReset').should('have.value', 'r'); + cy.get('#shortcutAccelerate').should('have.value', 'a'); cy.get('#shortcutFullscreen').should('have.value', 'f'); cy.get('#shortcutInfo').should('have.value', 'i'); @@ -53,6 +55,7 @@ describe('Keyboard Shortcuts', () => { // Verify other inputs are disabled during recording cy.get('#shortcutSettings').should('be.disabled'); cy.get('#shortcutReset').should('be.disabled'); + cy.get('#shortcutAccelerate').should('be.disabled'); cy.get('#shortcutFullscreen').should('be.disabled'); cy.get('#shortcutInfo').should('be.disabled'); @@ -66,6 +69,7 @@ describe('Keyboard Shortcuts', () => { // Verify other inputs are re-enabled cy.get('#shortcutSettings').should('not.be.disabled'); cy.get('#shortcutReset').should('not.be.disabled'); + cy.get('#shortcutAccelerate').should('not.be.disabled'); cy.get('#shortcutFullscreen').should('not.be.disabled'); cy.get('#shortcutInfo').should('not.be.disabled'); }); @@ -161,6 +165,7 @@ describe('Keyboard Shortcuts', () => { cy.get('#shortcutSettings').should('have.value', 'q'); cy.get('#shortcutWakeUp').should('have.value', 'Space'); cy.get('#shortcutReset').should('have.value', 'r'); + cy.get('#shortcutAccelerate').should('have.value', 'a'); cy.get('#shortcutFullscreen').should('have.value', 'f'); cy.get('#shortcutInfo').should('have.value', 'i'); }); @@ -182,6 +187,7 @@ describe('Keyboard Shortcuts', () => { cy.get('#shortcutSettings').should('have.value', ''); cy.get('#shortcutWakeUp').should('have.value', ''); cy.get('#shortcutReset').should('have.value', ''); + cy.get('#shortcutAccelerate').should('have.value', ''); cy.get('#shortcutFullscreen').should('have.value', ''); cy.get('#shortcutInfo').should('have.value', ''); }); @@ -245,10 +251,10 @@ describe('Keyboard Shortcuts', () => { cy.get('.shortcuts-container').should('have.css', 'grid-template-columns'); // Verify all shortcut items are present - cy.get('.shortcut-item').should('have.length', 5); + cy.get('.shortcut-item').should('have.length', 6); - // Verify the 5th item spans both columns - cy.get('.shortcut-item:nth-child(5)').should( + // Verify the 6th item spans both columns + cy.get('.shortcut-item:nth-child(6)').should( 'have.css', 'grid-column', '1 / -1' diff --git a/cypress/e2e/08-preset-skips.cy.js b/cypress/e2e/08-preset-skips.cy.js new file mode 100644 index 0000000..655b2a4 --- /dev/null +++ b/cypress/e2e/08-preset-skips.cy.js @@ -0,0 +1,172 @@ +describe('Preset skips and accelerate behaviour', () => { + beforeEach(() => { + cy.clearLocalStorage(); + }); + + it('keeps all days while hiding only appropriate skipped presets', () => { + // Load page + cy.visit('/'); + + // Settings dialog open on first load + cy.get('#settingsDialog').should('be.visible'); + + // Game settings: 12 players + cy.get('#playerCount').clear().type('12'); + + // Turn OFF effects (trigger change so playSoundEffects is updated before save) + cy.get('.tab-button[data-tab="effects"]').click(); + cy.get('#playSoundEffects').uncheck().trigger('change'); + + // Turn OFF music (trigger change so playMusic is updated before save) + cy.get('.tab-button[data-tab="music"]').click(); + cy.get('#playMusic').uncheck().trigger('change'); + + // Start new game + cy.get('#startNewGame').click(); + cy.get('#settingsDialog').should('not.be.visible'); + + // Capture the initial set of visible day labels (Day 1..Day N for this player count) + let initialDayLabels = []; + cy.get('#clocktowerPresets .clocktower-btn:visible .day').then(($spans) => { + const labels = Array.from($spans, (el) => el.textContent.trim()); + initialDayLabels = labels.filter((t) => t.startsWith('Day ')); + expect(initialDayLabels.length).to.be.greaterThan(0); + }); + + // Helper: assert visible day labels stay the same set as at the start + const expectSameDaySet = () => { + cy.get('#clocktowerPresets .clocktower-btn:visible .day').then( + ($spans) => { + const labels = Array.from($spans, (el) => el.textContent.trim()); + const dayLabels = labels.filter((t) => t.startsWith('Day ')); + expect(dayLabels).to.deep.equal(initialDayLabels); + } + ); + }; + + // Initially we should have a stable set of Day presets + expectSameDaySet(); + + // Select the Day 4 preset while on Day 1 + cy.contains('#clocktowerPresets .clocktower-btn .day', 'Day 4') + .closest('button') + .click(); + + // First three presets visible and marked as skipped (πŸ’€) + cy.get('#clocktowerPresets .clocktower-btn') + .filter(':visible') + .eq(0) + .should('have.class', 'skipped-day'); + cy.get('#clocktowerPresets .clocktower-btn') + .filter(':visible') + .eq(1) + .should('have.class', 'skipped-day'); + cy.get('#clocktowerPresets .clocktower-btn') + .filter(':visible') + .eq(2) + .should('have.class', 'skipped-day'); + + // Still have the same visible day presets + expectSameDaySet(); + + // Day 4 preset click already started the timer; accelerate is enabled + // Use Accelerate Time (click then confirm) + cy.get('#accelerateBtn', { timeout: 20000 }).should('not.be.disabled'); + cy.get('#accelerateBtn').click(); + cy.get('#accelerateBtn').click(); + + // Wait for accelerated day to complete (Wake Up active again) + cy.contains('#startBtn .button-text', '⏰ Wake Up!', { + timeout: 20000, + }).should('be.visible'); + + // After day end: no visible skipped presets, same Day set + cy.get('#clocktowerPresets .clocktower-btn.skipped-day:visible').should( + 'have.length', + 0 + ); + expectSameDaySet(); + + // In dusk with Wake Up active, click Day 4 for the next day + cy.contains('#clocktowerPresets .clocktower-btn .day', 'Day 4') + .closest('button') + .click(); + + // Now on Day 2: pattern Day 1, πŸ’€, πŸ’€, Day 2, Day 3..Day 9 + cy.get('#clocktowerPresets .clocktower-btn') + .filter(':visible') + .then(($btns) => { + const labels = Array.from($btns, (btn) => + btn.querySelector('.day').textContent.trim() + ); + const classes = Array.from($btns, (btn) => btn.className); + + expect(labels[0]).to.equal('Day 1'); + expect(classes[1]).to.include('skipped-day'); + expect(classes[2]).to.include('skipped-day'); + expect(labels[3]).to.equal('Day 2'); + // The remaining visible day labels should still include all later days + const remainingDays = labels + .slice(3) + .filter((t) => t.startsWith('Day ')) + .map((t) => Number(t.replace('Day ', ''))); + expect(remainingDays).to.include.members([3, 4, 5, 6, 7, 8, 9]); + }); + + // Day 4 preset click already started the timer; accelerate is enabled + cy.get('#accelerateBtn', { timeout: 20000 }).should('not.be.disabled'); + cy.get('#accelerateBtn').click(); + cy.get('#accelerateBtn').click(); + + cy.contains('#startBtn .button-text', '⏰ Wake Up!', { + timeout: 20000, + }).should('be.visible'); + + // After second day end: no skulls, same Day set + cy.get('#clocktowerPresets .clocktower-btn.skipped-day:visible').should( + 'have.length', + 0 + ); + expectSameDaySet(); + + // In dusk again, click Day 4 for the next day + cy.contains('#clocktowerPresets .clocktower-btn .day', 'Day 4') + .closest('button') + .click(); + + // Now on Day 3: Day 1, Day 2, πŸ’€, Day 3..Day 9 + cy.get('#clocktowerPresets .clocktower-btn') + .filter(':visible') + .then(($btns) => { + const labels = Array.from($btns, (btn) => + btn.querySelector('.day').textContent.trim() + ); + const classes = Array.from($btns, (btn) => btn.className); + + expect(labels[0]).to.equal('Day 1'); + expect(labels[1]).to.equal('Day 2'); + expect(classes[2]).to.include('skipped-day'); + expect(labels[3]).to.equal('Day 3'); + const remainingDays = labels + .slice(3) + .filter((t) => t.startsWith('Day ')); + expect(remainingDays.length).to.be.greaterThan(0); + }); + + // Day 4 preset click already started the timer; accelerate is enabled + cy.get('#accelerateBtn', { timeout: 20000 }).should('not.be.disabled'); + cy.get('#accelerateBtn').click(); + cy.get('#accelerateBtn').click(); + + cy.contains('#startBtn .button-text', '⏰ Wake Up!', { + timeout: 20000, + }).should('be.visible'); + + // After third day end: no skulls, same Day set + cy.get('#clocktowerPresets .clocktower-btn.skipped-day:visible').should( + 'have.length', + 0 + ); + expectSameDaySet(); + }); +}); diff --git a/index.html b/index.html index 400f13c..c31f036 100644 --- a/index.html +++ b/index.html @@ -31,6 +31,11 @@ +
-
+ -
Normal
-
+
00:00
- +
-
+
+ Timer controls
-
+
@@ -313,9 +318,15 @@
- - +
@@ -645,6 +656,18 @@

Keyboard Shortcuts

/> +
+ +