Created a "Variant" system — themes are now able to inherit from base themes

This commit is contained in:
nutty
2026-06-30 02:58:57 +10:00
parent 534a328edf
commit 3e058b5433
15 changed files with 150 additions and 32 deletions
+29 -3
View File
@@ -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();
+3 -3
View File
@@ -20,11 +20,11 @@
},
{
"value": "compact",
"label": "Compact (Light)"
"label": "Compact"
},
{
"value": "compact-dark",
"label": "Compact (Dark)"
"value": "compact",
"label": "Compact (Inverted)"
},
{
"value": "simple",
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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 */
}
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -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 */
}
@@ -0,0 +1,3 @@
{
"base-theme": "compact"
}
+18 -5
View File
@@ -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 = "";
@@ -0,0 +1,3 @@
{
"base-theme": "standard"
}
+17
View File
@@ -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 */
}
+3
View File
@@ -0,0 +1,3 @@
{
"base-theme": "standard"
}
+17
View File
@@ -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 */
}
+49 -14
View File
@@ -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);
+2 -1
View File
@@ -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 */
}