From a4e78595bdc1f0c66d2c92d9a9438757a62e8227 Mon Sep 17 00:00:00 2001 From: nutty Date: Sat, 27 Jun 2026 04:51:28 +1000 Subject: [PATCH] Added "Simple" theme --- now-playing/script.js | 2 +- now-playing/settings/settings.json | 6 +- now-playing/style.css | 1 + now-playing/themes/simple/index.html | 11 ++ now-playing/themes/simple/script.js | 212 ++++++++++++++++++++++++++ now-playing/themes/simple/style.css | 209 +++++++++++++++++++++++++ now-playing/themes/standard/script.js | 5 +- 7 files changed, 441 insertions(+), 5 deletions(-) create mode 100644 now-playing/themes/simple/index.html create mode 100644 now-playing/themes/simple/script.js create mode 100644 now-playing/themes/simple/style.css diff --git a/now-playing/script.js b/now-playing/script.js index 13951ea..55a239d 100644 --- a/now-playing/script.js +++ b/now-playing/script.js @@ -19,7 +19,7 @@ const mainContainer = document.getElementById('main-container'); const theme = urlParams.get('theme') || 'standard'; const font = urlParams.get("font") || ""; const fontSize = GetIntParam("fontSize", 20); -const width = GetIntParam("width", 500); +const maxWidth = GetIntParam("maxWidth", 500); const albumArt = urlParams.get('albumArt') || 'show'; const showProgressBar = GetBooleanParam("showProgressBar", true); diff --git a/now-playing/settings/settings.json b/now-playing/settings/settings.json index d0dccaf..bc9aecd 100644 --- a/now-playing/settings/settings.json +++ b/now-playing/settings/settings.json @@ -43,9 +43,9 @@ "group": "Appearance" }, { - "id": "width", - "label": "Width", - "description": "", + "id": "maxWidth", + "label": "Max Width", + "description": "Set to 0 to utilize full browser width", "type": "number", "defaultValue": 500, "group": "Appearance" diff --git a/now-playing/style.css b/now-playing/style.css index 0433f92..ef982ed 100644 --- a/now-playing/style.css +++ b/now-playing/style.css @@ -10,4 +10,5 @@ body { justify-content: center; align-items: center; height: 100vh; /* Full viewport height */ + margin: 0.5em; } \ No newline at end of file diff --git a/now-playing/themes/simple/index.html b/now-playing/themes/simple/index.html new file mode 100644 index 0000000..07f1765 --- /dev/null +++ b/now-playing/themes/simple/index.html @@ -0,0 +1,11 @@ +
+
+
+
+
+ +
+
 
+
 
+
+
\ No newline at end of file diff --git a/now-playing/themes/simple/script.js b/now-playing/themes/simple/script.js new file mode 100644 index 0000000..bad07b6 --- /dev/null +++ b/now-playing/themes/simple/script.js @@ -0,0 +1,212 @@ +///////////////// +// GLOBAL VARS // +///////////////// + +let CurrentPlaybackStatus; +let CurrentSong; +let hideTimeout = null; + +/////////////////// +// 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 // +//////////////// + +if (maxWidth > 0) + mainWrapper.style.width = `${maxWidth}px`; +else + mainWrapper.style.width = `100%`; + +// Set album art style +switch (albumArt) +{ + case 'none': + albumArtContainer.style.display = 'none'; + break; + case 'show': + albumArtContainer.style.display = ''; + break; + case 'spinny': + break; +} + +// // Set progress bar visibility +// if (!showProgressBar) +// progressContainer.style.display = 'none'; + + + +//////////////////// +// CORE FUNCTIONS // +//////////////////// + +// Each theme must implement the following +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; + + // 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 + const newTrackKey = `${mediaProps.Title}|${mediaProps.Artist}`; + if (newTrackKey !== CurrentSong) { + ChangeTrack(mediaProps); // 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', mediaProps.AccentColor); + // } + } + 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) { + // 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(() => { + trackLabel.innerText = mediaProps.Title; + artistLabel.innerText = mediaProps.Artist; + + // Extract the image source string (use fallback if Windows has no art) + const newArtUrl = mediaProps.Base64Image; + const accent = mediaProps.AccentColor || "#ffffff"; + + // 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 = accent + "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 = accent + "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 = `${1.5 * height}px`; + albumArtContainer.style.width = `${1.5 * height}px`; +} + +SetAlbumArtSize(); \ No newline at end of file diff --git a/now-playing/themes/simple/style.css b/now-playing/themes/simple/style.css new file mode 100644 index 0000000..4a5797c --- /dev/null +++ b/now-playing/themes/simple/style.css @@ -0,0 +1,209 @@ +#main-wrapper { + width: 500px; + display: flex; + flex-direction: row; + /* This forces both containers to be the same height */ + align-items: stretch; + gap: 1em; + opacity: 0; + align-items: center; +} + +#album-art-container { + /* 1. Tell it to maintain a 1:1 square ratio */ + aspect-ratio: 1 / 1; + overflow: hidden; + border-radius: 0.25em; + position: relative; + background-size: cover; + background-position: center; +} + +#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; +} + +#art-layer { + z-index: 1; + transition: all 0.5s ease; +} + +#art-transition-layer { + z-index: 0; +} + +#song-info-container { + position: relative; + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +#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; +} + +#artist-label { + font-size: 0.8em; + font-weight: 300; + opacity: 0.6; +} + +.text-label { + white-space: nowrap; + overflow: hidden; + mask-image: linear-gradient(to right, black calc(100% - 1em), transparent 100%); + -webkit-mask-image: linear-gradient(to right, black calc(100% - 1em), transparent 100%); + transition: opacity 0.25s ease-in-out; +} + +@keyframes fade-in { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fade-out { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +}@keyframes slide-in-from-top { + 0% { + transform: translateY(-1em); + opacity: 0; + } + + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slide-in-from-top { + 0% { + transform: translateY(-1em); + opacity: 0; + } + + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slide-out-top { + 0% { + transform: translateY(0); + opacity: 1; + } + + 100% { + transform: translateY(-1em); + opacity: 0; + } +} + +@keyframes slide-in-from-bottom { + 0% { + transform: translateY(1em); + opacity: 0; + } + + 100% { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slide-out-bottom { + 0% { + transform: translateY(0); + opacity: 1; + } + + 100% { + transform: translateY(1em); + opacity: 0; + } +} + +@keyframes slide-in-from-left { + 0% { + transform: translateX(-1em); + opacity: 0; + } + + 100% { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slide-out-left { + 0% { + transform: translateX(0); + opacity: 1; + } + + 100% { + transform: translateX(-1em); + opacity: 0; + } +} + +@keyframes slide-in-from-right { + 0% { + transform: translateX(1em); + opacity: 0; + } + + 100% { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slide-out-right { + 0% { + transform: translateX(0); + opacity: 1; + } + + 100% { + transform: translateX(1em); + opacity: 0; + } +} \ No newline at end of file diff --git a/now-playing/themes/standard/script.js b/now-playing/themes/standard/script.js index e129b71..069da38 100644 --- a/now-playing/themes/standard/script.js +++ b/now-playing/themes/standard/script.js @@ -30,7 +30,10 @@ const durationLabel = document.getElementById('duration'); // PAGE SETUP // //////////////// -mainWrapper.style.width = `${width}px`; +if (maxWidth > 0) + mainWrapper.style.width = `${maxWidth}px`; +else + mainWrapper.style.width = `100%`; // Set album art style switch (albumArt)