diff --git a/now-playing/settings/settings.json b/now-playing/settings/settings.json index 4f0bcd2..d36f022 100644 --- a/now-playing/settings/settings.json +++ b/now-playing/settings/settings.json @@ -10,17 +10,37 @@ "value": "standard", "label": "Standard" }, + { + "value": "matte", + "label": "Matte (Light)" + }, + { + "value": "matte-dark", + "label": "Matte (Dark)" + }, { "value": "compact", - "label": "Compact" + "label": "Compact (Light)" + }, + { + "value": "compact-dark", + "label": "Compact (Dark)" }, { "value": "simple", "label": "Simple" }, + { + "value": "classic", + "label": "Classic" + }, { "value": "card", "label": "Card" + }, + { + "value": "vinyl", + "label": "Vinyl" } ], "defaultValue": "standard", diff --git a/now-playing/themes/card/style.css b/now-playing/themes/card/style.css index c1713af..cf24d8c 100644 --- a/now-playing/themes/card/style.css +++ b/now-playing/themes/card/style.css @@ -1,3 +1,8 @@ +:root { + --border-radius: 20px; + --component-radius: calc(var(--border-radius) * 0.75); +} + #main-wrapper { width: 500px; display: flex; @@ -12,7 +17,7 @@ /* 1. Tell it to maintain a 1:1 square ratio */ aspect-ratio: 1 / 1; overflow: hidden; - border-radius: 1em; + border-radius: var(--component-radius); position: relative; background-size: cover; background-position: center; @@ -46,7 +51,7 @@ flex: 1; display: flex; flex-direction: column; - border-radius: 1em; + border-radius: var(--border-radius); overflow: hidden; box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); } @@ -119,7 +124,7 @@ #progress-bar-track { width: 100%; height: 0.4em; - border-radius: 1em; + border-radius: 10000px; background-color: #ffffff3a; box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* Adds depth */ } @@ -127,7 +132,7 @@ #progress-bar-fill { height: 100%; width: 0%; /* JS will control this */ - border-radius: 1em; + border-radius: 10000px; background-color: var(--accent-color, #ffffff); transition: width 1s ease-in-out, background-color 1s ease-in-out; diff --git a/now-playing/themes/classic/index.html b/now-playing/themes/classic/index.html new file mode 100644 index 0000000..9d89656 --- /dev/null +++ b/now-playing/themes/classic/index.html @@ -0,0 +1,28 @@ +
+
+
+
+
+ +
+
+
+ +
+
 
+
 
+ +
+
+
+
+
+ +
+
0:00
+
0:00
+
+
+
+
+
\ No newline at end of file diff --git a/now-playing/themes/classic/script.js b/now-playing/themes/classic/script.js new file mode 100644 index 0000000..335c365 --- /dev/null +++ b/now-playing/themes/classic/script.js @@ -0,0 +1,204 @@ +/////////////////// +// PAGE ELEMENTS // +/////////////////// + +const mainWrapper = document.getElementById('main-wrapper'); +const albumArtContainer = document.getElementById('album-art-container'); +const songInfoContainer = document.getElementById('song-info-container'); +const albumArtLayer = document.getElementById('album-art-layer'); +const albumArtTransition = document.getElementById('album-art-transition-layer'); +const backgroundLayer = document.getElementById('background-layer'); +const backgroundTransitionLayer = document.getElementById('background-transition-layer'); +const trackLabel = document.getElementById('track-label'); +const artistLabel = document.getElementById('artist-label'); +const progressContainer = document.getElementById('progress-container'); +const progressBarFill = document.getElementById('progress-bar-fill'); +const currentTimeLabel = document.getElementById('current-time'); +const durationLabel = document.getElementById('duration'); + + + +//////////////// +// PAGE SETUP // +//////////////// + +// Set property visibility +trackLabel.style.display = showPrimary ? '' : 'none'; +artistLabel.style.display = showSecondary ? '' : 'none'; + +// Set container width +if (maxWidth > 0) + mainWrapper.style.width = `${maxWidth}px`; +else + mainWrapper.style.width = `100%`; + +// Set progress bar visibility +if (!showProgressBar) + progressContainer.style.display = 'none'; + + + +//////////////////// +// CORE FUNCTIONS // +//////////////////// + +// Each theme must implement the following +async function UpdatePlayerState(data) { + // Check if the user has provided a target application in the settings + const isFiltering = targetApplication && targetApplication.trim() !== ""; + + // Search for the session that matches the target application + const sessionToFind = isFiltering ? targetApplication : data.current_session_id; + let targetSession = data.sessions.find(s => { + // If filtering, check if the source_app_id matches the user's string + if (isFiltering) { + return s.source_app_id.toLowerCase() === targetApplication.toLowerCase(); + } + // Otherwise, match the system's current active session ID + return s.source_app_id === sessionToFind; + }); + + // If a target session was found, update the state of the widget + if (targetSession) { + // Extract the relevant properties from the session + const playbackInfo = targetSession.playback_info; + const mediaProps = targetSession.media_properties; + const timelineProps = targetSession.timeline_properties; + + // Calcualte an accent color + const accentColorPalette = await GetAccentPalette(mediaProps.Thumbnail); + + // 1. Check if playback status has changed and update visibility accordingly + if (playbackInfo.PlaybackStatus !== CurrentPlaybackStatus) { + if (playbackInfo.PlaybackStatus === PlaybackStatus.PLAYING) + SetVisibility(true); + else + SetVisibility(false); + CurrentPlaybackStatus = playbackInfo.PlaybackStatus; + } + + // 2. Check if the track name/artist have changed - this is our indicator that the next track has loaded + // Only proceed if the player state is actively playing audio + if (CurrentPlaybackStatus == PlaybackStatus.PLAYING) + { + const newTrackKey = `${mediaProps.Title}|${mediaProps.Artist}`; + if (newTrackKey !== CurrentSong) { + ChangeTrack(mediaProps, accentColorPalette.LightVibrant); // Now trigger your cross-fade logic here! + CurrentSong = newTrackKey; // Update the tracker with the string key + } + } + + // 3. Update the progress info + if (timelineProps) + { + // Parse the Windows timestamp into a JavaScript time object + const lastUpdateAnchor = Date.parse(timelineProps.LastUpdatedTime.replace(' ', 'T')); + + // Calculate the drift (i.e. how many milliseconds have passed since Windows last updated) + const driftMs = Date.now() - lastUpdateAnchor; + + // Add that drift to the reported Position + // Only add drift if the status is PLAYING + const isPlaying = (targetSession.playback_info.PlaybackStatus === PlaybackStatus.PLAYING); + const currentPositionMs = isPlaying && timelineProps.EndTime > 0 ? timelineProps.Position + driftMs : timelineProps.Position; + + // Update the label using your naming convention + currentTimeLabel.innerText = + ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(currentPositionMs); + + durationLabel.innerText = + ConvertMillisecondsToMinutesSoThatItLooksBetterOnTheOverlay(timelineProps.EndTime); + + // Set progressbar + // Ensure we don't divide by zero or exceed 100% + const durationMs = timelineProps.EndTime; + let progressPercent = durationMs > 0 ? (currentPositionMs / durationMs) * 100 : 0; + progressPercent = Math.min(100, Math.max(0, progressPercent)); + progressBarFill.style.width = `${progressPercent}%`; + progressBarFill.style.setProperty('--accent-color', accentColorPalette.LightVibrant); + } + } + else + { + SetVisibility(false); + } +} + + + +////////////////////// +// HELPER FUNCTIONS // +////////////////////// + +function SetVisibility(visible) { + // Always clear any pending hide timers whenever we change visibility + if (hideTimeout) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + + if (visible) { + mainWrapper.style.animation = `${showAnimation} 0.5s ease-out forwards`; + + // Only set a new timer if autoHide is enabled + if (autoHide) { + hideTimeout = setTimeout(() => { + SetVisibility(false); + }, displayDuration * 1000); + } + } else { + mainWrapper.style.animation = `${hideAnimation} 0.5s ease-out forwards`; + } +} + +async function ChangeTrack(mediaProps, tintColor) { + // Fade in the overlay (shows the new text) + trackLabel.style.opacity = "0"; + artistLabel.style.opacity = "0"; + backgroundLayer.style.opacity = "0"; + albumArtLayer.style.opacity = "0"; + + // Wait for fade (0.5s), then swap the real text and hide overlay + setTimeout(async () => { + trackLabel.innerText = swapArtistTrack ? mediaProps.Artist : mediaProps.Title; + artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist; + + // Extract the image source string (use fallback if Windows has no art) + const newArtUrl = mediaProps.Thumbnail; + + // Set the image + backgroundLayer.style.backgroundImage = `url('${newArtUrl}')`; + albumArtLayer.style.backgroundImage = `url('${newArtUrl}')`; + + // Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast + // 'rgba(0,0,0,0.6)' ensures the text stays readable + backgroundLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex + + trackLabel.style.opacity = ""; + artistLabel.style.opacity = ""; + backgroundLayer.style.opacity = ""; + albumArtLayer.style.opacity = ""; + + setTimeout(() => { + // Set the image + backgroundTransitionLayer.style.backgroundImage = `url('${newArtUrl}')`; + albumArtTransition.style.backgroundImage = `url('${newArtUrl}')`; + + // Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast + // 'rgba(0,0,0,0.6)' ensures the text stays readable + backgroundTransitionLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex + + SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled + }, 250); + }, 250); +} + +function SetAlbumArtSize() { + const height = songInfoContainer.offsetHeight; + albumArtContainer.style.height = `${height}px`; + albumArtContainer.style.width = `${height}px`; +} + +requestAnimationFrame(() => { + SetAlbumArtSize(); +}); \ No newline at end of file diff --git a/now-playing/themes/classic/style.css b/now-playing/themes/classic/style.css new file mode 100644 index 0000000..0637f70 --- /dev/null +++ b/now-playing/themes/classic/style.css @@ -0,0 +1,172 @@ +:root { + --border-radius: 20px; +} + +#main-wrapper { + width: 500px; + display: flex; + flex-direction: row; + /* This forces both containers to be the same height */ + align-items: stretch; + gap: 0.5em; + opacity: 0; +} + +#album-art-container { + /* 1. Tell it to maintain a 1:1 square ratio */ + aspect-ratio: 1 / 1; + overflow: hidden; + border-radius: var(--border-radius); + position: relative; + background-size: cover; + background-position: center; + display: var(--show-album-art); +} + +#album-art-layer, +#album-art-transition-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center; + transition: opacity 0.5s ease; +} + +#album-art-layer { + z-index: 1; + transition: all 0.5s ease; +} + +#album-art-transition-layer { + z-index: 0; +} + +#song-info-container { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + border-radius: var(--border-radius); + overflow: hidden; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); +} + +/* The background now fills the frame */ +#background-layer, +#background-transition-layer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1; + background-size: cover; + background-position: center center; + background-repeat: no-repeat; + + background-blend-mode: overlay; /* Or 'soft-light' for a subtler effect */ + filter: blur(5px) brightness(0.3) saturate(1.2); + transform: scale(1.1); /* Slightly zoom in to cover edges when blurred */ +} + +#background-layer { + transition: all 0.5s ease; +} + +#background-transition-layer { + z-index: 0; +} + +/* The content sits on top */ +#song-info-wrapper { + position: relative; /* Keeps it in the document flow */ + z-index: 2; /* Sits above the background-layer */ + + padding: 1em 1.75em; + display: flex; + flex-direction: column; + gap: 0.3em; +} + +#track-label { + font-size: 1em; + font-weight: 600; + text-align: var(--text-alignment); +} + +#artist-label { + font-size: 0.8em; + font-weight: 300; + opacity: 0.6; + text-align: var(--text-alignment); +} + +.text-label { + white-space: nowrap; + overflow: hidden; + mask-image: var(--trailing-fade); + -webkit-mask-image: var(--trailing-fade); + transition: opacity 0.25s ease-in-out; +} + +#progress-container { + display: flex; + flex-direction: column; + gap: 0.2em; + margin-top: 0.2em; +} + +#progress-bar-track { + width: 100%; + height: 0.4em; + border-radius: 10000px; + background-color: #ffffff3a; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* Adds depth */ +} + +#progress-bar-fill { + height: 100%; + width: 0%; /* JS will control this */ + border-radius: 10000px; + background-color: var(--accent-color, #ffffff); + transition: width 1s ease-in-out, background-color 1s ease-in-out; + + /* This makes the handle track the end of the bar */ + position: relative; +} + +#progress-bar-fill::after { + content: ''; + position: absolute; + right: -6px; /* Adjust to center the circle on the end of the bar */ + top: 50%; + transform: translateY(-50%); + + width: 0.8em; /* Diameter of your circle */ + height: 0.8em; + background-color: var(--accent-color, #ffffff); + border-radius: 50%; + filter: brightness(1.4); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* Adds depth */ + + /* Optional: Hide it if the width is too small */ + opacity: 0; + transition: all 1s ease-in-out; +} + +/* Show the handle when the progress bar is active */ +#progress-bar-track:hover #progress-bar-fill::after, +#progress-bar-fill:not([style*="width: 0%"])::after { + opacity: 1; +} + +#time-container { + display: flex; + justify-content: space-between; + font-size: 0.7em; + opacity: 0.5; +} \ No newline at end of file diff --git a/now-playing/themes/simple/style.css b/now-playing/themes/simple/style.css index 5a879ef..8d046ad 100644 --- a/now-playing/themes/simple/style.css +++ b/now-playing/themes/simple/style.css @@ -1,3 +1,7 @@ +:root { + --border-radius: 10px; +} + #main-wrapper { width: 500px; display: flex; @@ -13,7 +17,7 @@ /* 1. Tell it to maintain a 1:1 square ratio */ aspect-ratio: 1 / 1; overflow: hidden; - border-radius: 0.25em; + border-radius: var(--border-radius); position: relative; background-size: cover; background-position: center; diff --git a/now-playing/themes/standard/index.html b/now-playing/themes/standard/index.html index 9d89656..3ec4a50 100644 --- a/now-playing/themes/standard/index.html +++ b/now-playing/themes/standard/index.html @@ -1,26 +1,27 @@
-
-
-
-
-
+
+
+
+
+
+
 
 
+
0:00
+
0:00
-
0:00
-
0:00
diff --git a/now-playing/themes/standard/script.js b/now-playing/themes/standard/script.js index 335c365..6bb7812 100644 --- a/now-playing/themes/standard/script.js +++ b/now-playing/themes/standard/script.js @@ -193,12 +193,12 @@ async function ChangeTrack(mediaProps, tintColor) { }, 250); } -function SetAlbumArtSize() { - const height = songInfoContainer.offsetHeight; - albumArtContainer.style.height = `${height}px`; - albumArtContainer.style.width = `${height}px`; -} +// function SetAlbumArtSize() { +// const height = songInfoContainer.offsetHeight; +// albumArtContainer.style.height = `${height}px`; +// albumArtContainer.style.width = `${height}px`; +// } -requestAnimationFrame(() => { - SetAlbumArtSize(); -}); \ No newline at end of file +// requestAnimationFrame(() => { +// SetAlbumArtSize(); +// }); \ No newline at end of file diff --git a/now-playing/themes/standard/style.css b/now-playing/themes/standard/style.css index 83931c1..89200c8 100644 --- a/now-playing/themes/standard/style.css +++ b/now-playing/themes/standard/style.css @@ -1,3 +1,8 @@ +:root { + --border-radius: 20px; + --component-radius: calc(var(--border-radius) * 0.75); +} + #main-wrapper { width: 500px; display: flex; @@ -10,13 +15,15 @@ #album-art-container { /* 1. Tell it to maintain a 1:1 square ratio */ + height: 100%; aspect-ratio: 1 / 1; overflow: hidden; - border-radius: 1em; + border-radius: var(--component-radius); position: relative; background-size: cover; background-position: center; display: var(--show-album-art); + flex-shrink: 0; } #album-art-layer, @@ -44,11 +51,13 @@ position: relative; flex: 1; display: flex; - flex-direction: column; - justify-content: center; - border-radius: 1em; + flex-direction: row; + align-items: center; + border-radius: var(--border-radius); overflow: hidden; box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); + gap: 1.5em; + padding: 0.75em 1em; } /* The background now fills the frame */ @@ -82,10 +91,15 @@ position: relative; /* Keeps it in the document flow */ z-index: 2; /* Sits above the background-layer */ - padding: 1em 1.75em; + /* padding: 1em 1.75em; */ display: flex; flex-direction: column; gap: 0.3em; + + margin: 0.5em 0em; + + flex-grow: 1; + min-width: 0; } #track-label { @@ -111,9 +125,11 @@ #progress-container { display: flex; - flex-direction: column; + flex-direction: row; + align-items: center; gap: 0.2em; margin-top: 0.2em; + gap: 0.5em; } #progress-bar-track { @@ -135,34 +151,15 @@ position: relative; } -#progress-bar-fill::after { - content: ''; - position: absolute; - right: -6px; /* Adjust to center the circle on the end of the bar */ - top: 50%; - transform: translateY(-50%); - - width: 0.8em; /* Diameter of your circle */ - height: 0.8em; - background-color: var(--accent-color, #ffffff); - border-radius: 50%; - filter: brightness(1.4); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); /* Adds depth */ - - /* Optional: Hide it if the width is too small */ - opacity: 0; - transition: all 1s ease-in-out; -} - -/* Show the handle when the progress bar is active */ -#progress-bar-track:hover #progress-bar-fill::after, -#progress-bar-fill:not([style*="width: 0%"])::after { - opacity: 1; -} - -#time-container { +/* #time-container { display: flex; justify-content: space-between; font-size: 0.7em; opacity: 0.5; + display: none; +} */ + +.progress-text { + font-size: 0.7em; + opacity: 0.5; } \ No newline at end of file