mirror of
https://github.com/nuttylmao/nutty.gg.git
synced 2026-09-19 04:00:59 -04:00
Created a "Variant" system — themes are now able to inherit from base themes
This commit is contained in:
+29
-3
@@ -82,14 +82,27 @@ switch (textAlignment)
|
|||||||
// Load the theme
|
// Load the theme
|
||||||
async function LoadTheme()
|
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();
|
const html = await response.text();
|
||||||
|
|
||||||
mainContainer.innerHTML = html;
|
mainContainer.innerHTML = html;
|
||||||
|
|
||||||
// Swap the CSS file
|
// Swap the CSS file
|
||||||
const link = document.getElementById('theme-style');
|
const link = document.getElementById('theme-style');
|
||||||
link.href = `./themes/${theme}/style.css`;
|
link.href = `./themes/${baseTheme}/style.css`;
|
||||||
|
|
||||||
// Load the theme-specific JS
|
// Load the theme-specific JS
|
||||||
// We remove the old script tag and add a new one
|
// We remove the old script tag and add a new one
|
||||||
@@ -97,9 +110,22 @@ async function LoadTheme()
|
|||||||
if (oldScript) oldScript.remove();
|
if (oldScript) oldScript.remove();
|
||||||
|
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.src = `./themes/${theme}/script.js`;
|
script.src = `./themes/${baseTheme}/script.js`;
|
||||||
script.id = 'theme-script';
|
script.id = 'theme-script';
|
||||||
document.body.appendChild(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();
|
LoadTheme();
|
||||||
|
|||||||
@@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": "compact",
|
"value": "compact",
|
||||||
"label": "Compact (Light)"
|
"label": "Compact"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": "compact-dark",
|
"value": "compact",
|
||||||
"label": "Compact (Dark)"
|
"label": "Compact (Inverted)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"value": "simple",
|
"value": "simple",
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
|
|
||||||
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
// 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
|
// '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 = "";
|
trackLabel.style.opacity = "";
|
||||||
artistLabel.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
|
// 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
|
// '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
|
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
background-blend-mode: overlay; /* Or 'soft-light' for a subtler effect */
|
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 */
|
transform: scale(1.1); /* Slightly zoom in to cover edges when blurred */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
|
|
||||||
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
// 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
|
// '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 = "";
|
trackLabel.style.opacity = "";
|
||||||
artistLabel.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
|
// 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
|
// '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
|
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
background-blend-mode: overlay; /* Or 'soft-light' for a subtler effect */
|
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 */
|
transform: scale(1.1); /* Slightly zoom in to cover edges when blurred */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"base-theme": "compact"
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@ const songLabelBackground = document.getElementById('song-label-background');
|
|||||||
// PAGE SETUP //
|
// PAGE SETUP //
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
|
// This theme has variants
|
||||||
|
const themeVariant = window.ThemeVariant ? window.ThemeVariant : '';
|
||||||
|
|
||||||
// Set property visibility
|
// Set property visibility
|
||||||
trackLabel.style.display = showPrimary ? '' : 'none';
|
trackLabel.style.display = showPrimary ? '' : 'none';
|
||||||
artistLabel.style.display = showSecondary ? '' : '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)
|
// Fade in the overlay (shows the new text)
|
||||||
songLabel.style.opacity = "0";
|
songLabel.style.opacity = "0";
|
||||||
songLabelBackground.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)
|
// Extract the image source string (use fallback if Windows has no art)
|
||||||
|
|
||||||
// Set the pill color
|
// Set the pill color
|
||||||
songInfoContainer.style.background = accentColor.DarkMuted + "80";
|
switch (themeVariant) {
|
||||||
progressBar.style.background = accentColor.LightVibrant;
|
case "compact-inverted":
|
||||||
songLabel.style.color = accentColor.DarkVibrant;
|
songInfoContainer.style.backgroundColor = accentColorPalette.LightVibrant;
|
||||||
songLabelBackground.style.color = accentColor.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 = "";
|
songLabel.style.opacity = "";
|
||||||
songLabelBackground.style.opacity = "";
|
songLabelBackground.style.opacity = "";
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"base-theme": "standard"
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"base-theme": "standard"
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
|
}
|
||||||
@@ -22,6 +22,9 @@ const durationLabel = document.getElementById('duration');
|
|||||||
// PAGE SETUP //
|
// PAGE SETUP //
|
||||||
////////////////
|
////////////////
|
||||||
|
|
||||||
|
// This theme has variants
|
||||||
|
const themeVariant = window.ThemeVariant ? window.ThemeVariant : '';
|
||||||
|
|
||||||
// Set property visibility
|
// Set property visibility
|
||||||
trackLabel.style.display = showPrimary ? '' : 'none';
|
trackLabel.style.display = showPrimary ? '' : 'none';
|
||||||
artistLabel.style.display = showSecondary ? '' : 'none';
|
artistLabel.style.display = showSecondary ? '' : 'none';
|
||||||
@@ -36,6 +39,15 @@ else
|
|||||||
if (!showProgressBar)
|
if (!showProgressBar)
|
||||||
progressContainer.style.display = 'none';
|
progressContainer.style.display = 'none';
|
||||||
|
|
||||||
|
// Theme specific setup
|
||||||
|
switch (themeVariant) {
|
||||||
|
case "matte":
|
||||||
|
case "matte-dark":
|
||||||
|
backgroundLayer.style.display = 'none';
|
||||||
|
backgroundTransitionLayer.style.display = 'none';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
////////////////////
|
////////////////////
|
||||||
@@ -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
|
// 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
|
// Only proceed if the player state is actively playing audio
|
||||||
if (CurrentPlaybackStatus == PlaybackStatus.PLAYING)
|
if (CurrentPlaybackStatus == PlaybackStatus.PLAYING) {
|
||||||
{
|
|
||||||
const newTrackKey = `${mediaProps.Title}|${mediaProps.Artist}`;
|
const newTrackKey = `${mediaProps.Title}|${mediaProps.Artist}`;
|
||||||
if (newTrackKey !== CurrentSong) {
|
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
|
CurrentSong = newTrackKey; // Update the tracker with the string key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Update the progress info
|
// 3. Update the progress info
|
||||||
if (timelineProps)
|
if (timelineProps) {
|
||||||
{
|
|
||||||
// Parse the Windows timestamp into a JavaScript time object
|
// Parse the Windows timestamp into a JavaScript time object
|
||||||
const lastUpdateAnchor = Date.parse(timelineProps.LastUpdatedTime.replace(' ', 'T'));
|
const lastUpdateAnchor = Date.parse(timelineProps.LastUpdatedTime.replace(' ', 'T'));
|
||||||
|
|
||||||
@@ -116,10 +126,18 @@ async function UpdatePlayerState(data) {
|
|||||||
progressPercent = Math.min(100, Math.max(0, progressPercent));
|
progressPercent = Math.min(100, Math.max(0, progressPercent));
|
||||||
progressBarFill.style.width = `${progressPercent}%`;
|
progressBarFill.style.width = `${progressPercent}%`;
|
||||||
progressBarFill.style.setProperty('--accent-color', accentColorPalette.LightVibrant);
|
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);
|
SetVisibility(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +169,7 @@ function SetVisibility(visible) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ChangeTrack(mediaProps, tintColor) {
|
async function ChangeTrack(mediaProps, accentColorPalette) {
|
||||||
// Fade in the overlay (shows the new text)
|
// Fade in the overlay (shows the new text)
|
||||||
trackLabel.style.opacity = "0";
|
trackLabel.style.opacity = "0";
|
||||||
artistLabel.style.opacity = "0";
|
artistLabel.style.opacity = "0";
|
||||||
@@ -163,16 +181,33 @@ async function ChangeTrack(mediaProps, tintColor) {
|
|||||||
trackLabel.innerText = swapArtistTrack ? mediaProps.Artist : mediaProps.Title;
|
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)
|
// Extract the image source string (use fallback if Windows has no art)
|
||||||
const newArtUrl = mediaProps.Thumbnail;
|
const newArtUrl = mediaProps.Thumbnail;
|
||||||
|
|
||||||
// Set the image
|
// 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}')`;
|
backgroundLayer.style.backgroundImage = `url('${newArtUrl}')`;
|
||||||
albumArtLayer.style.backgroundImage = `url('${newArtUrl}')`;
|
albumArtLayer.style.backgroundImage = `url('${newArtUrl}')`;
|
||||||
|
|
||||||
// Apply a tint: Use 30% opacity of the accent color + a dark overlay for contrast
|
// 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
|
// '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 = "";
|
trackLabel.style.opacity = "";
|
||||||
artistLabel.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
|
// 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
|
// '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
|
SetVisibility(true); // Show the overlay for a few seconds if autoHide is enabled
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||||||
gap: 1.5em;
|
gap: 1.5em;
|
||||||
padding: 0.75em 1em;
|
padding: 0.75em 1em;
|
||||||
|
transition: all 0.25s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The background now fills the frame */
|
/* The background now fills the frame */
|
||||||
@@ -74,7 +75,7 @@
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
background-blend-mode: overlay; /* Or 'soft-light' for a subtler effect */
|
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 */
|
transform: scale(1.1); /* Slightly zoom in to cover edges when blurred */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user