From 3e058b54335ff25cc1e4a6333f3f03777e5b11cd Mon Sep 17 00:00:00 2001 From: nutty Date: Tue, 30 Jun 2026 02:58:57 +1000 Subject: [PATCH] =?UTF-8?q?Created=20a=20"Variant"=20system=20=E2=80=94=20?= =?UTF-8?q?themes=20are=20now=20able=20to=20inherit=20from=20base=20themes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- now-playing/script.js | 32 +++++++++- now-playing/settings/settings.json | 6 +- now-playing/themes/card/script.js | 4 +- now-playing/themes/card/style.css | 2 +- now-playing/themes/classic/script.js | 4 +- now-playing/themes/classic/style.css | 2 +- .../themes/compact-inverted/inherits.json | 3 + now-playing/themes/compact-inverted/style.css | 0 now-playing/themes/compact/script.js | 23 +++++-- now-playing/themes/matte-dark/inherits.json | 3 + now-playing/themes/matte-dark/style.css | 17 +++++ now-playing/themes/matte/inherits.json | 3 + now-playing/themes/matte/style.css | 17 +++++ now-playing/themes/standard/script.js | 63 ++++++++++++++----- now-playing/themes/standard/style.css | 3 +- 15 files changed, 150 insertions(+), 32 deletions(-) create mode 100644 now-playing/themes/compact-inverted/inherits.json create mode 100644 now-playing/themes/compact-inverted/style.css create mode 100644 now-playing/themes/matte-dark/inherits.json create mode 100644 now-playing/themes/matte-dark/style.css create mode 100644 now-playing/themes/matte/inherits.json create mode 100644 now-playing/themes/matte/style.css diff --git a/now-playing/script.js b/now-playing/script.js index 94cbadd..bf57c3b 100644 --- a/now-playing/script.js +++ b/now-playing/script.js @@ -82,14 +82,27 @@ switch (textAlignment) // Load the theme async function LoadTheme() { - const response = await fetch(`./themes/${theme}/index.html`); + // Check if the theme inherits from a base theme + let baseTheme = theme; + try { + const response = await fetch(`./themes/${theme}/inherits.json`); + if (response.ok) { + const config = await response.json(); + baseTheme = config['base-theme']; + } + } catch (e) { + // No inheritance file found, assume it is a base theme + } + + // Fetch the HTML structure from the base theme folder + const response = await fetch(`./themes/${baseTheme}/index.html`); const html = await response.text(); mainContainer.innerHTML = html; // Swap the CSS file const link = document.getElementById('theme-style'); - link.href = `./themes/${theme}/style.css`; + link.href = `./themes/${baseTheme}/style.css`; // Load the theme-specific JS // We remove the old script tag and add a new one @@ -97,9 +110,22 @@ async function LoadTheme() if (oldScript) oldScript.remove(); const script = document.createElement('script'); - script.src = `./themes/${theme}/script.js`; + script.src = `./themes/${baseTheme}/script.js`; script.id = 'theme-script'; document.body.appendChild(script); + + // If the base theme != theme, that means this is a variant, so load the variant's CSS + if (baseTheme != theme) + { + // Load the variant CSS + const baseLink = document.createElement('link'); + baseLink.rel = 'stylesheet'; + baseLink.className = 'theme-style'; // Use class for easy batch removal + baseLink.href = `./themes/${theme}/style.css`; + document.head.appendChild(baseLink); + + window.ThemeVariant = theme; + } } LoadTheme(); diff --git a/now-playing/settings/settings.json b/now-playing/settings/settings.json index d36f022..c6c58c3 100644 --- a/now-playing/settings/settings.json +++ b/now-playing/settings/settings.json @@ -20,11 +20,11 @@ }, { "value": "compact", - "label": "Compact (Light)" + "label": "Compact" }, { - "value": "compact-dark", - "label": "Compact (Dark)" + "value": "compact", + "label": "Compact (Inverted)" }, { "value": "simple", diff --git a/now-playing/themes/card/script.js b/now-playing/themes/card/script.js index cf131ac..3263bb6 100644 --- a/now-playing/themes/card/script.js +++ b/now-playing/themes/card/script.js @@ -172,7 +172,7 @@ async function ChangeTrack(mediaProps, tintColor) { // 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 + // backgroundLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex trackLabel.style.opacity = ""; artistLabel.style.opacity = ""; @@ -186,7 +186,7 @@ async function ChangeTrack(mediaProps, tintColor) { // 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 + // 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); diff --git a/now-playing/themes/card/style.css b/now-playing/themes/card/style.css index cf24d8c..748d119 100644 --- a/now-playing/themes/card/style.css +++ b/now-playing/themes/card/style.css @@ -70,7 +70,7 @@ background-repeat: no-repeat; background-blend-mode: overlay; /* Or 'soft-light' for a subtler effect */ - filter: blur(5px) brightness(0.3); + filter: blur(20px) brightness(0.3); transform: scale(1.1); /* Slightly zoom in to cover edges when blurred */ } diff --git a/now-playing/themes/classic/script.js b/now-playing/themes/classic/script.js index 335c365..3d86bf4 100644 --- a/now-playing/themes/classic/script.js +++ b/now-playing/themes/classic/script.js @@ -172,7 +172,7 @@ async function ChangeTrack(mediaProps, tintColor) { // 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 + // backgroundLayer.style.backgroundColor = tintColor + "80"; // 80 is 50% opacity in hex trackLabel.style.opacity = ""; artistLabel.style.opacity = ""; @@ -186,7 +186,7 @@ async function ChangeTrack(mediaProps, tintColor) { // 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 + // 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); diff --git a/now-playing/themes/classic/style.css b/now-playing/themes/classic/style.css index 0637f70..bdc5c85 100644 --- a/now-playing/themes/classic/style.css +++ b/now-playing/themes/classic/style.css @@ -69,7 +69,7 @@ background-repeat: no-repeat; background-blend-mode: overlay; /* Or 'soft-light' for a subtler effect */ - filter: blur(5px) brightness(0.3) saturate(1.2); + filter: blur(10px) brightness(0.3) saturate(1.2); transform: scale(1.1); /* Slightly zoom in to cover edges when blurred */ } diff --git a/now-playing/themes/compact-inverted/inherits.json b/now-playing/themes/compact-inverted/inherits.json new file mode 100644 index 0000000..9bd2662 --- /dev/null +++ b/now-playing/themes/compact-inverted/inherits.json @@ -0,0 +1,3 @@ +{ + "base-theme": "compact" +} \ No newline at end of file diff --git a/now-playing/themes/compact-inverted/style.css b/now-playing/themes/compact-inverted/style.css new file mode 100644 index 0000000..e69de29 diff --git a/now-playing/themes/compact/script.js b/now-playing/themes/compact/script.js index ce8fd6d..1922dbd 100644 --- a/now-playing/themes/compact/script.js +++ b/now-playing/themes/compact/script.js @@ -19,6 +19,9 @@ const songLabelBackground = document.getElementById('song-label-background'); // PAGE SETUP // //////////////// +// This theme has variants +const themeVariant = window.ThemeVariant ? window.ThemeVariant : ''; + // Set property visibility trackLabel.style.display = showPrimary ? '' : 'none'; artistLabel.style.display = showSecondary ? '' : 'none'; @@ -146,7 +149,7 @@ function SetVisibility(visible) { } } -async function ChangeTrack(mediaProps, accentColor) { +async function ChangeTrack(mediaProps, accentColorPalette) { // Fade in the overlay (shows the new text) songLabel.style.opacity = "0"; songLabelBackground.style.opacity = "0"; @@ -161,10 +164,20 @@ async function ChangeTrack(mediaProps, accentColor) { // Extract the image source string (use fallback if Windows has no art) // Set the pill color - songInfoContainer.style.background = accentColor.DarkMuted + "80"; - progressBar.style.background = accentColor.LightVibrant; - songLabel.style.color = accentColor.DarkVibrant; - songLabelBackground.style.color = accentColor.LightVibrant; + switch (themeVariant) { + case "compact-inverted": + songInfoContainer.style.backgroundColor = accentColorPalette.LightVibrant; + progressBar.style.background = `color-mix(in srgb, ${accentColorPalette.DarkMuted}, black 60%)`; + songLabel.style.color = accentColorPalette.LightVibrant; + songLabelBackground.style.color = accentColorPalette.DarkVibrant; + break; + default: + songInfoContainer.style.backgroundColor = `color-mix(in srgb, ${accentColorPalette.DarkMuted}, black 60%)`; + progressBar.style.background = accentColorPalette.LightVibrant; + songLabel.style.color = accentColorPalette.DarkVibrant; + songLabelBackground.style.color = accentColorPalette.LightVibrant; + break; + } songLabel.style.opacity = ""; songLabelBackground.style.opacity = ""; diff --git a/now-playing/themes/matte-dark/inherits.json b/now-playing/themes/matte-dark/inherits.json new file mode 100644 index 0000000..19733b4 --- /dev/null +++ b/now-playing/themes/matte-dark/inherits.json @@ -0,0 +1,3 @@ +{ + "base-theme": "standard" +} \ No newline at end of file diff --git a/now-playing/themes/matte-dark/style.css b/now-playing/themes/matte-dark/style.css new file mode 100644 index 0000000..bef8978 --- /dev/null +++ b/now-playing/themes/matte-dark/style.css @@ -0,0 +1,17 @@ +#track-label { + font-weight: 800; +} + +#artist-label { + font-weight: 500; + opacity: 0.7; +} + +.progress-text { + font-weight: 500; + opacity: 0.7; +} + +#progress-bar-track { + box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); /* Adds depth */ +} \ No newline at end of file diff --git a/now-playing/themes/matte/inherits.json b/now-playing/themes/matte/inherits.json new file mode 100644 index 0000000..19733b4 --- /dev/null +++ b/now-playing/themes/matte/inherits.json @@ -0,0 +1,3 @@ +{ + "base-theme": "standard" +} \ No newline at end of file diff --git a/now-playing/themes/matte/style.css b/now-playing/themes/matte/style.css new file mode 100644 index 0000000..bef8978 --- /dev/null +++ b/now-playing/themes/matte/style.css @@ -0,0 +1,17 @@ +#track-label { + font-weight: 800; +} + +#artist-label { + font-weight: 500; + opacity: 0.7; +} + +.progress-text { + font-weight: 500; + opacity: 0.7; +} + +#progress-bar-track { + box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); /* Adds depth */ +} \ No newline at end of file diff --git a/now-playing/themes/standard/script.js b/now-playing/themes/standard/script.js index 6bb7812..6c498a0 100644 --- a/now-playing/themes/standard/script.js +++ b/now-playing/themes/standard/script.js @@ -22,6 +22,9 @@ const durationLabel = document.getElementById('duration'); // PAGE SETUP // //////////////// +// This theme has variants +const themeVariant = window.ThemeVariant ? window.ThemeVariant : ''; + // Set property visibility trackLabel.style.display = showPrimary ? '' : 'none'; artistLabel.style.display = showSecondary ? '' : 'none'; @@ -36,6 +39,15 @@ else if (!showProgressBar) progressContainer.style.display = 'none'; +// Theme specific setup +switch (themeVariant) { + case "matte": + case "matte-dark": + backgroundLayer.style.display = 'none'; + backgroundTransitionLayer.style.display = 'none'; + break; +} + //////////////////// @@ -64,7 +76,7 @@ async function UpdatePlayerState(data) { 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); @@ -79,18 +91,16 @@ async function UpdatePlayerState(data) { // 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) - { + if (CurrentPlaybackStatus == PlaybackStatus.PLAYING) { const newTrackKey = `${mediaProps.Title}|${mediaProps.Artist}`; if (newTrackKey !== CurrentSong) { - ChangeTrack(mediaProps, accentColorPalette.LightVibrant); // Now trigger your cross-fade logic here! + ChangeTrack(mediaProps, accentColorPalette); // Now trigger your cross-fade logic here! CurrentSong = newTrackKey; // Update the tracker with the string key } } // 3. Update the progress info - if (timelineProps) - { + if (timelineProps) { // Parse the Windows timestamp into a JavaScript time object const lastUpdateAnchor = Date.parse(timelineProps.LastUpdatedTime.replace(' ', 'T')); @@ -116,10 +126,18 @@ async function UpdatePlayerState(data) { progressPercent = Math.min(100, Math.max(0, progressPercent)); progressBarFill.style.width = `${progressPercent}%`; progressBarFill.style.setProperty('--accent-color', accentColorPalette.LightVibrant); + switch (themeVariant) { + case "matte": + progressBarFill.style.setProperty('--accent-color', accentColorPalette.DarkVibrant); + break; + case "matte-dark": + default: + progressBarFill.style.setProperty('--accent-color', accentColorPalette.LightVibrant); + break; + } } } - else - { + else { SetVisibility(false); } } @@ -139,7 +157,7 @@ function SetVisibility(visible) { if (visible) { mainWrapper.style.animation = `${showAnimation} 0.5s ease-out forwards`; - + // Only set a new timer if autoHide is enabled if (autoHide) { hideTimeout = setTimeout(() => { @@ -151,28 +169,45 @@ function SetVisibility(visible) { } } -async function ChangeTrack(mediaProps, tintColor) { +async function ChangeTrack(mediaProps, accentColorPalette) { // 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; + artistLabel.innerText = swapArtistTrack ? mediaProps.Title : mediaProps.Artist; + + switch (themeVariant) { + case "matte": + document.body.style.color = accentColorPalette.DarkVibrant; + break; + case "matte-dark": + document.body.style.color = accentColorPalette.LightVibrant; + break; + } // Extract the image source string (use fallback if Windows has no art) const newArtUrl = mediaProps.Thumbnail; // Set the image + switch (themeVariant) { + case "matte": + songInfoContainer.style.backgroundColor = accentColorPalette.LightVibrant; + break; + case "matte-dark": + songInfoContainer.style.backgroundColor = `color-mix(in srgb, ${accentColorPalette.DarkMuted}, black 60%)`; + break; + } 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 + // backgroundLayer.style.backgroundColor = accentColorPalette.LightVibrant + "80"; // 80 is 50% opacity in hex trackLabel.style.opacity = ""; artistLabel.style.opacity = ""; @@ -186,7 +221,7 @@ async function ChangeTrack(mediaProps, tintColor) { // 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 + // backgroundTransitionLayer.style.backgroundColor = accentColorPalette.LightVibrant + "80"; // 80 is 50% opacity in hex SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled }, 250); diff --git a/now-playing/themes/standard/style.css b/now-playing/themes/standard/style.css index 89200c8..79c7b95 100644 --- a/now-playing/themes/standard/style.css +++ b/now-playing/themes/standard/style.css @@ -58,6 +58,7 @@ box-shadow: 0 0 5px rgba(0, 0, 0, 0.5); gap: 1.5em; padding: 0.75em 1em; + transition: all 0.25s ease-in-out; } /* The background now fills the frame */ @@ -74,7 +75,7 @@ background-repeat: no-repeat; background-blend-mode: overlay; /* Or 'soft-light' for a subtler effect */ - filter: blur(5px) brightness(0.3) saturate(1.2); + filter: blur(10px) brightness(0.3) saturate(1.2); transform: scale(1.1); /* Slightly zoom in to cover edges when blurred */ }