diff --git a/jam-ui/src/components/client/JKSessionMetronomePlayer.css b/jam-ui/src/components/client/JKSessionMetronomePlayer.css index 541d77b9d..c9a9ba925 100644 --- a/jam-ui/src/components/client/JKSessionMetronomePlayer.css +++ b/jam-ui/src/components/client/JKSessionMetronomePlayer.css @@ -1,25 +1,25 @@ .metronome-player-popup { - padding: 24px; + padding: 20px 24px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", sans-serif; - background-color: #ffffff; - min-width: 450px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + background-color: #f5f5f5; + min-width: 380px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); } .metronome-player-header { display: flex; justify-content: space-between; align-items: center; - margin-bottom: 24px; - padding-bottom: 16px; - border-bottom: 2px solid #dee2e6; + margin-bottom: 20px; + padding-bottom: 12px; + border-bottom: 1px solid #d0d0d0; } .metronome-player-header h3 { margin: 0; - font-size: 1.4rem; - font-weight: 700; - color: #212529; + font-size: 1rem; + font-weight: 400; + color: #666; } .metronome-close-btn { @@ -50,56 +50,87 @@ .metronome-player-controls { display: flex; flex-direction: column; - gap: 24px; + gap: 0; } -/* Playback controls (Play/Stop buttons) */ -.metronome-playback-controls { +/* Main content area with buttons and form side by side */ +.metronome-main-content { display: flex; - gap: 12px; - padding-bottom: 16px; - border-bottom: 1px solid #dee2e6; + gap: 16px; + align-items: flex-start; + margin-bottom: 20px; } -.metronome-play-btn, -.metronome-stop-btn { - flex: 1; - padding: 12px 20px; - font-size: 1rem; - font-weight: 600; - border-radius: 6px; +/* Playback buttons - circular icons */ +.metronome-playback-buttons { + display: flex; + flex-direction: column; + gap: 8px; +} + +.metronome-icon-btn { + width: 40px; + height: 40px; + border-radius: 50%; + border: 2px solid #007bff; + background-color: #fff; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; transition: all 0.2s; - border: none; + padding: 0; } -.metronome-play-btn:disabled { - opacity: 0.5; - cursor: not-allowed; +.metronome-icon-btn:hover:not(.disabled) { + background-color: #007bff; + color: #fff; } -.metronome-stop-btn:disabled { - opacity: 0.5; +.metronome-icon-btn.disabled { + opacity: 0.3; cursor: not-allowed; + border-color: #ccc; +} + +.metronome-play-icon .play-icon { + font-size: 14px; + color: #007bff; + margin-left: 2px; +} + +.metronome-icon-btn:hover:not(.disabled) .play-icon { + color: #fff; +} + +.metronome-stop-icon .stop-icon { + font-size: 12px; + color: #007bff; +} + +.metronome-icon-btn:hover:not(.disabled) .stop-icon { + color: #fff; } /* Form controls with labels on left, inputs on right */ .metronome-form-controls { display: flex; flex-direction: column; - gap: 16px; + gap: 12px; + flex: 1; } .metronome-form-row { display: grid; - grid-template-columns: 120px 1fr; + grid-template-columns: 80px 1fr; align-items: center; - gap: 16px; + gap: 12px; } .metronome-form-label { - font-weight: 600; - font-size: 0.9rem; - color: #495057; + font-weight: 400; + font-size: 0.875rem; + color: #666; text-align: right; margin: 0; } @@ -108,25 +139,36 @@ .metronome-close-container { display: flex; justify-content: center; - padding-top: 16px; - border-top: 1px solid #dee2e6; + padding-top: 20px; + border-top: none; } .metronome-close-button { - min-width: 120px; - padding: 10px 24px; - font-size: 0.9rem; - font-weight: 600; - border-radius: 6px; + min-width: 100px; + padding: 8px 32px; + font-size: 0.875rem; + font-weight: 400; + border-radius: 4px; + border: 1px solid #d0d0d0; + background-color: #fff; + color: #333; + cursor: pointer; + transition: all 0.2s; +} + +.metronome-close-button:hover { + background-color: #f8f8f8; + border-color: #999; } .metronome-error { - padding: 10px; - background-color: #f8d7da; - color: #721c24; - border: 1px solid #f5c6cb; + padding: 8px 12px; + background-color: #fff3cd; + color: #856404; + border: 1px solid #ffeaa7; border-radius: 4px; - font-size: 0.875rem; + font-size: 0.8rem; + margin-bottom: 12px; } /* Legacy styles - no longer used but kept for backwards compatibility */ @@ -144,30 +186,32 @@ } .metronome-select { - padding: 10px 14px; - border: 2px solid #ced4da; - border-radius: 6px; - font-size: 0.95rem; + padding: 6px 10px; + border: 1px solid #d0d0d0; + border-radius: 4px; + font-size: 0.875rem; background-color: #fff; cursor: pointer; transition: all 0.2s; width: 100%; + color: #333; } -.metronome-select:hover { - border-color: #007bff; +.metronome-select:hover:not(:disabled) { + border-color: #999; } .metronome-select:focus { outline: none; border-color: #007bff; - box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.15); + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.1); } .metronome-select:disabled { - background-color: #e9ecef; - opacity: 0.6; - cursor: not-allowed; + background-color: #f5f5f5; + color: #666; + cursor: default; + border-color: #e0e0e0; } /* Legacy checkbox and actions styles - removed in redesign */ diff --git a/jam-ui/src/components/client/JKSessionMetronomePlayer.js b/jam-ui/src/components/client/JKSessionMetronomePlayer.js index 68f396f99..8729d92a5 100644 --- a/jam-ui/src/components/client/JKSessionMetronomePlayer.js +++ b/jam-ui/src/components/client/JKSessionMetronomePlayer.js @@ -148,6 +148,10 @@ const JKSessionMetronomePlayer = ({ cricket: cricket ? 1 : 0 }); + // Stop any current playback first + await jamClient.SessionStopPlay(); + + // Start the metronome await jamClient.SessionOpenMetronome( bpm, METRO_SOUND_LOOKUP[sound], @@ -175,7 +179,9 @@ const JKSessionMetronomePlayer = ({ try { console.log('[Metronome] Stopping metronome'); - await jamClient.SessionCloseMetronome(); + // Use SessionStopPlay to pause metronome without destroying it + // This allows Play to work again without reinitializing + await jamClient.SessionStopPlay(); setIsPlaying(false); console.log('[Metronome] Metronome stopped successfully'); @@ -211,83 +217,79 @@ const JKSessionMetronomePlayer = ({ )} - {/* Play/Stop buttons */} -
- - -
- - {/* Form layout: labels on left, controls on right */} -
- {/* Sound selector */} -
- - + + +
- {/* Tempo (BPM) selector */} -
- - -
+ {/* Form layout: labels on left, controls on right */} +
+ {/* Feature row - always shows "Metronome" */} +
+ + +
- {/* Meter selector */} -
- - + {/* Sound selector */} +
+ + +
+ + {/* Tempo (BPM) selector */} +
+ + +
{/* Close button centered at bottom */}
- +
); diff --git a/jam-ui/src/components/client/JKSessionScreen.js b/jam-ui/src/components/client/JKSessionScreen.js index f1f0b950e..f20dc3885 100644 --- a/jam-ui/src/components/client/JKSessionScreen.js +++ b/jam-ui/src/components/client/JKSessionScreen.js @@ -1037,10 +1037,11 @@ const JKSessionScreen = () => { // Inform server about metronome opening (like legacy SessionStore) await openMetronome({ id: currentSession.id }); - // Initialize metronome track (creates mixers) but stop immediately so no audio plays + // Initialize metronome track (creates mixers) then stop playback // This allows the track to appear in the UI while audio remains stopped + // Use SessionStopPlay instead of SessionCloseMetronome to preserve metronome state await jamClient.SessionOpenMetronome(bpm, soundName, meter, 0); - await jamClient.SessionCloseMetronome(); + await jamClient.SessionStopPlay(); // Update local metronome state to show popup immediately // Metronome track will be visible but audio is stopped (user must click Play) diff --git a/jam-ui/src/components/common/WindowPortal.js b/jam-ui/src/components/common/WindowPortal.js index 8ed02f2b6..dc113ba73 100644 --- a/jam-ui/src/components/common/WindowPortal.js +++ b/jam-ui/src/components/common/WindowPortal.js @@ -68,6 +68,21 @@ const WindowPortal = ({ newWindow.document.body.style.backgroundColor = '#f8f9fa'; newWindow.document.body.style.overflow = 'hidden'; + // Copy all stylesheets from parent window to popup + const stylesheets = Array.from(document.querySelectorAll('link[rel="stylesheet"], style')); + stylesheets.forEach(sheet => { + if (sheet.tagName === 'LINK') { + const newLink = newWindow.document.createElement('link'); + newLink.rel = 'stylesheet'; + newLink.href = sheet.href; + newWindow.document.head.appendChild(newLink); + } else if (sheet.tagName === 'STYLE') { + const newStyle = newWindow.document.createElement('style'); + newStyle.textContent = sheet.textContent; + newWindow.document.head.appendChild(newStyle); + } + }); + // Add window ID for identification if (windowId) { newWindow.windowId = windowId;