diff --git a/.config/spicetify/Extensions/adblock.js b/.config/spicetify/Extensions/adblock.js
new file mode 100644
index 00000000..6220d04f
--- /dev/null
+++ b/.config/spicetify/Extensions/adblock.js
@@ -0,0 +1,59 @@
+//@ts-check
+
+// NAME: adblock
+// AUTHOR: CharlieS1103
+// DESCRIPTION: Block all audio and UI ads on Spotify
+
+///
+
+(function adblock() {
+ const { Platform} = Spicetify;
+ if (!(Platform)) {
+ setTimeout(adblock, 300)
+ return
+ }
+
+ var styleSheet = document.createElement("style")
+
+ styleSheet.innerHTML =
+ `
+ .MnW5SczTcbdFHxLZ_Z8j, .WiPggcPDzbwGxoxwLWFf, .ReyA3uE3K7oEz7PTTnAn, .main-leaderboardComponent-container, .sponsor-container, a.link-subtle.main-navBar-navBarLink.GKnnhbExo0U9l7Jz2rdc{
+ display: none !important;
+ }
+ `
+ document.body.appendChild(styleSheet)
+ delayAds()
+ var billboard = Spicetify.Platform.AdManagers.billboard.displayBillboard;
+ Spicetify.Platform.AdManagers.billboard.displayBillboard = function (arguments) {
+ Spicetify.Platform.AdManagers.billboard.finish()
+ // hook before call
+ var ret = billboard.apply(this, arguments);
+ // hook after call
+ console.log("Adblock.js: Billboard blocked! Leave a star!")
+ Spicetify.Platform.AdManagers.billboard.finish()
+ const observer = new MutationObserver((mutations, obs) => {
+ const billboardAd = document.getElementById('view-billboard-ad');
+ if (billboardAd) {
+ Spicetify.Platform.AdManagers.billboard.finish()
+ obs.disconnect();
+ return;
+ }
+ });
+
+ observer.observe(document, {
+ childList: true,
+ subtree: true
+ });
+ return ret;
+ };
+ function delayAds() {
+ console.log("Ads delayed: Adblock.js")
+ Spicetify.Platform.AdManagers.audio.audioApi.cosmosConnector.increaseStreamTime(-100000000000)
+ Spicetify.Platform.AdManagers.billboard.billboardApi.cosmosConnector.increaseStreamTime(-100000000000)
+ }
+ setInterval(delayAds, 720 *10000);
+
+
+})()
+
+
diff --git a/.config/spicetify/Extensions/catppuccin-macchiato.js b/.config/spicetify/Extensions/catppuccin-macchiato.js
new file mode 100644
index 00000000..b0c536c4
--- /dev/null
+++ b/.config/spicetify/Extensions/catppuccin-macchiato.js
@@ -0,0 +1,39 @@
+// Color map
+let colorPalette = {
+ rosewater: "#f4dbd6",
+ flamingo: "#f0c6c6",
+ pink: "#f5bde6",
+ maroon: "#ee99a0",
+ red: "#ed8796",
+ peach: "#f5a97f",
+ yellow: "#eed49f",
+ green: "#a6da95",
+ teal: "#8bd5ca",
+ blue: "#8aadf4",
+ sky: "#91d7e3",
+ lavender: "#b7bdf8",
+ white: "#d9e0ee"
+}
+
+// waitForElement borrowed from:
+// https://github.com/morpheusthewhite/spicetify-themes/blob/master/Dribbblish/dribbblish.js
+function waitForElement(els, func, timeout = 100) {
+ const queries = els.map(el => document.querySelector(el));
+ if (queries.every(a => a)) {
+ func(queries);
+ } else if (timeout > 0) {
+ setTimeout(waitForElement, 300, els, func, --timeout);
+ }
+}
+
+// Return the color label for a given hex color value
+function getKeyByValue(object, value) {
+ return Object.keys(object).find(key => object[key] === value.trim());
+}
+
+// Used to select matching equalizer-animated-COLOR.gif
+waitForElement([".Root"], (root) => {
+ let spiceEq = getComputedStyle(document.querySelector(":root")).getPropertyValue("--spice-equalizer");
+ let eqColor = getKeyByValue(colorPalette, spiceEq);
+ root[0].classList.add(`catppuccin-eq-${eqColor}`);
+});
diff --git a/.config/spicetify/Extensions/copyPlaylist.js b/.config/spicetify/Extensions/copyPlaylist.js
new file mode 100644
index 00000000..4616d75a
--- /dev/null
+++ b/.config/spicetify/Extensions/copyPlaylist.js
@@ -0,0 +1,654 @@
+// NAME: Copy Playlists
+// AUTHOR: einzigartigerName
+// DESCRIPTION: copy/combine playlist/queue directly in Spotify
+
+(function CopyPlaylist() {
+
+ const { CosmosAPI, BridgeAPI, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify
+ if (!(CosmosAPI || BridgeAPI)) {
+ setTimeout(CopyPlaylist, 1000);
+ return;
+ }
+
+ const STORAGE_KEY = "combine_buffer_spicetify"
+ const TOP_BTN_TOOLTIP = "Combine Playlists"
+ const MENU_BTN_CREATE_NEW = "Create Playlist"
+ const MENU_BTN_INSERT_BUFFER = "Copy to Buffer"
+
+ class PlaylistCollection {
+ constructor() {
+ const menu = createMenu()
+ this.container = menu.container
+ this.items = menu.menu
+ this.lastScroll = 0
+ this.container.onclick = () => {
+ this.storeScroll()
+ this.container.remove()
+ }
+ this.pattern
+ this.apply()
+ }
+
+ apply() {
+ this.items.textContent = '' // Remove all childs
+ this.items.append(createMenuItem("Create Playlist", () => highjackCreateDialog(mergePlaylists(this.pattern))))
+ this.items.append(createMenuItem("Clear Buffer", () => LIST.clearStorage()))
+
+ const select = createPatternSelect(this.filter);
+ select.onchange = (event) => {
+ this.pattern = event.srcElement.selectedIndex;
+ }
+ this.items.append(select);
+
+ const collection = this.getStorage();
+ collection.forEach((item) => this.items.append(new CardContainer(item)))
+ }
+
+ getStorage() {
+ const storageRaw = LocalStorage.get(STORAGE_KEY);
+ let storage = [];
+
+ if (storageRaw) {
+ storage = JSON.parse(storageRaw);
+ } else {
+ LocalStorage.set(STORAGE_KEY, "[]")
+ }
+
+ return storage;
+ }
+
+ addToStorage(data) {
+
+ /** @type {Object[]} */
+ const storage = this.getStorage();
+ storage.push(data);
+
+ LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
+ this.apply()
+ }
+
+ removeFromStorage(id) {
+ const storage = this.getStorage()
+ .filter(item => item.id !== id)
+
+ LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
+ this.apply()
+ }
+
+ clearStorage() {
+ LocalStorage.set(STORAGE_KEY, "[]");
+ this.apply()
+ }
+
+ moveItem(uri, direction) {
+ var storage = this.getStorage()
+
+ var from;
+
+ for (var i = 0; i < storage.length; i++) {
+ if (storage[i].uri === uri) {
+ from = i
+ break;
+ }
+ }
+ if (!from) { return }
+
+ var to = from + direction
+ if (to < 0 || to >= storage.length) { return }
+
+ var tmp = storage[from]
+ storage[from] = storage[to]
+ storage[to] = tmp
+
+ LocalStorage.set(STORAGE_KEY, JSON.stringify(storage));
+ this.apply()
+ }
+
+ changePosition(x, y) {
+ this.items.style.left = x + "px"
+ this.items.style.top = y + 10 + "px"
+ }
+
+ storeScroll() {
+ this.lastScroll = this.items.scrollTop
+ }
+
+ setScroll() {
+ this.items.scrollTop = this.lastScroll
+ }
+ }
+
+ /*
+ * Displays Stored Playlist
+ * {id, uri, name, tracks, imgUri, owner}
+ */
+ class CardContainer extends HTMLElement {
+ constructor(info) {
+ super()
+
+ this.innerHTML = `
+
+
+
+ ${info.imgUri ? `
+
+ ` : ""}
+
+
+
`
+
+ const up = this.querySelector(".order-controls-up")
+ up.onclick = (event) => {
+ LIST.moveItem(info.uri, -1)
+ event.stopPropagation()
+ }
+
+ const remove = this.querySelector(".order-controls-remove")
+ remove.onclick = (event) => {
+ LIST.removeFromStorage(info.id)
+ event.stopPropagation()
+ }
+
+ const down = this.querySelector(".order-controls-down")
+ down.onclick = (event) => {
+ LIST.moveItem(info.uri, +1)
+ event.stopPropagation()
+ }
+
+ const imageLink = this.querySelector(".card-image-link");
+ const infoLink = this.querySelector(".card-info-link");
+
+ if (imageLink) imageLink.addEventListener("click", ((e) => showPlaylist(e)));
+
+ if (infoLink) infoLink.addEventListener("click", ((e) => showPlaylist(e)));
+ }
+ }
+
+ customElements.define("combine-buffer-card-container", CardContainer)
+
+ const LIST = new PlaylistCollection()
+
+ // New Playlist Button
+ const playlistDialogButton = document.querySelector("#new-playlist-button-mount-point > div > button")
+ if (!playlistDialogButton) return;
+
+ document.querySelector("#view-browser-navigation-top-bar")
+ .append(createTopBarButton())
+
+ createPlaylistContextMenu().register()
+
+
+ /**************************************************************************
+ UI Building
+ **************************************************************************/
+ // If Queue Page add Buttons
+ const iframeInterval = setInterval(() => {
+ /** @type {HTMLIFrameElement} */
+ const currentIframe = document.querySelector("iframe.active");
+ if (!currentIframe ||
+ currentIframe.id !== "app-queue"
+ ) {
+ return;
+ }
+
+ const headers = currentIframe.contentDocument.querySelectorAll(
+ ".glue-page-header__buttons"
+ );
+
+ for (const e of headers) {
+ e.append(createQueueButton(
+ "Save as Playlist",
+ "Save the current Queue as a new Playlist",
+ () => {
+ let tracks = getQueueTracks();
+ highjackCreateDialog(tracks);
+ },
+ ));
+
+ e.append(createQueueButton(
+ "Copy into Buffer",
+ "Insert the current Queue into the Buffer",
+ () => { queueToBuffer() },
+ ));
+ }
+
+ if (headers.length > 0) clearInterval(iframeInterval);
+ }, 500)
+
+ // Creates the Main Menu
+ function createMenu() {
+ const container = document.createElement("div")
+ container.id = "combine-playlist-spicetify"
+ container.className = "context-menu-container"
+ container.style.zIndex = "1029"
+
+ const style = document.createElement("style")
+ style.textContent = `
+#combine-menu {
+ display: inline-block;
+ width: 33%;
+ max-height: 70%;
+ overflow: hidden auto;
+ padding: 10px
+}
+.combine-pattern {
+ margin-top: 7px;
+}
+.order-controls {
+ position: absolute;
+ right: 0;
+ padding: 0 5px 5px 0;
+ z-index: 3
+}
+.button.button-icon-only::before {
+ color: var(--modspotify_main_fg);
+}
+.order-controls-up {
+ position: relative;
+ top: 100%;
+}
+.order-controls-remove {
+ position: relative;
+ top: 50%;
+}
+.order-controls-down {
+ position: relative;
+ bottom: 100%;
+}
+.card-info-subtitle-owner {
+ color: var(--modspotify_secondary_fg);
+}
+.card-info-subtitle-tracks {
+ font-weight: lighter;
+ color: var(--modspotify_secondary_fg);
+}
+`
+
+ const menu = document.createElement("ul")
+ menu.id = "combine-menu"
+ menu.className = "context-menu"
+
+ container.append(style, menu)
+
+ return { container, menu }
+ }
+
+ // Creates a Button in the Combine Menu
+ function createMenuItem(name, callback) {
+ const item = document.createElement("div");
+ item.classList.add("item");
+ item.onclick = callback;
+ item.onmouseover = () => item.classList.add("hover");
+ item.onmouseleave = () => item.classList.remove("hover");
+
+ const text = document.createElement("span");
+ text.classList.add("text");
+ text.innerText = name;
+
+ item.append(text);
+
+ return item;
+ }
+
+ // Creates the SubMenu in Playlist Context
+ function createPlaylistContextMenu() {
+ var createFromCurrent = new Spicetify.ContextMenu.Item(
+ MENU_BTN_CREATE_NEW,
+ (uris) => {
+ if (uris.length === 1) {
+ fetchPlaylist(uris[0])
+ .then((buffer) => highjackCreateDialog(buffer.tracks))
+ .catch((err) => Spicetify.showNotification(`${err}`));
+ return;
+ } else {
+ Spicetify.showNotification("Unable to find Playlist URI")
+ }
+ },
+ (_) => true
+ )
+
+ var insertIntoBuffer = new Spicetify.ContextMenu.Item(
+ MENU_BTN_INSERT_BUFFER,
+ (uris) => {
+
+ if (uris.length === 1) {
+ fetchPlaylist(uris[0])
+ .then((buffer) => {LIST.addToStorage(buffer)})
+ .catch((err) => Spicetify.showNotification(`${err}`));
+ return;
+ }
+ },
+ (_) => true
+ )
+
+ return new Spicetify.ContextMenu.SubMenu(
+ "Copy Playlist",
+ [ createFromCurrent, insertIntoBuffer],
+ (uris) => {
+ if (uris.length === 1) {
+ const uriObj = Spicetify.URI.fromString(uris[0]);
+ switch (uriObj.type) {
+ case Spicetify.URI.Type.PLAYLIST:
+ case Spicetify.URI.Type.PLAYLIST_V2:
+ return true;
+ }
+ return false;
+ }
+ // Multiple Items selected.
+ return false;
+ }
+ )
+ }
+
+ // Creates the Button to View Merge Buffer
+ function createTopBarButton() {
+ const button = document.createElement("button")
+ button.classList.add("button", "spoticon-copy-16", "merge-button")
+ button.setAttribute("data-tooltip", TOP_BTN_TOOLTIP)
+ button.setAttribute("data-contextmenu", "")
+ button.setAttribute("data-uri", "spotify:special:copy")
+ button.onclick = () => {
+ const bound = button.getBoundingClientRect()
+ LIST.changePosition(bound.left, bound.top)
+ document.body.append(LIST.container)
+ LIST.setScroll()
+ }
+ return button
+ }
+
+ // Creates the Dropdown Menu for Merge Pattern
+ function createPatternSelect(defaultOpt = 0) {
+ const select = document.createElement("select");
+ select.className = "GlueDropdown combine-pattern";
+
+ const appendOpt = document.createElement("option");
+ appendOpt.text = "Append";
+
+ const shuffleOpt = document.createElement("option");
+ shuffleOpt.text = "Shuffle";
+
+ const alternateOpt = document.createElement("option");
+ alternateOpt.text = "Alternate";
+
+ select.onclick = (ev) => ev.stopPropagation();
+ select.append(appendOpt, shuffleOpt, alternateOpt);
+ select.options[defaultOpt].selected = true;
+
+ return select;
+ }
+
+ // Queue button
+ function createQueueButton(name, tooltip, callback) {
+ const b = document.createElement("button");
+ b.classList.add("button", "button-green");
+ b.innerText = name;
+ b.setAttribute("data-tooltip", tooltip);
+ b.onclick = callback;
+ return b;
+ }
+
+ // Highjack Spotifies 'New Playlist' Dialog
+ function highjackCreateDialog(tracks) {
+ playlistDialogButton.click()
+
+ var createButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container > button")
+ var buttonContainer = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__submit-button-container")
+
+ var highjackedButton = createButton.cloneNode(true)
+ highjackedButton.addEventListener("click", () => onCreateNewPlaylist(tracks))
+
+ window.addEventListener("keypress", (event) => {
+ if (event.code === `Enter`) {
+ // Cancel the default action, if needed
+ event.preventDefault();
+ // Trigger the button element with a click
+ createButton.click();
+ }
+ });
+
+ createButton.remove()
+ buttonContainer.insertAdjacentElement("afterbegin", highjackedButton)
+ }
+
+
+ /**************************************************************************
+ OnCLick Functions
+ **************************************************************************/
+ // Create a new Playlist from Inputs
+ function onCreateNewPlaylist(tracks) {
+ var exitButton = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__close-button > button");
+ var nameInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-name > input")
+ var descInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__playlist-description > textarea")
+ var imageInput = document.querySelector("body > div.Modal__portal > div > div > div > div.PlaylistAnnotationModal__content > div.PlaylistAnnotationModal__img-container > div > div.PlaylistAnnotationModal__img-holder > img")
+
+ var name = nameInput.value
+ if (!name) {
+ name = nameInput.getAttribute("placeholder")
+ }
+
+ var desc = descInput.value
+
+ var img;
+ if (imageInput) {
+ img = imageInput.getAttribute("src")
+ }
+
+ createPlaylist(name)
+ .then(res => addTracks(res.uri, tracks))
+ .then((_) => Spicetify.showNotification(`Created Playlist: "${name}"`))
+ .catch((err) => Spicetify.showNotification(`${err}`));
+
+ exitButton.click()
+
+ if (exitButton) {
+ exitButton.click()
+ }
+ }
+
+ // Get All Tracks in Queue and remove delimiter
+ function getQueueTracks() {
+ return Spicetify.Queue.next_tracks
+ .map((t) => t.uri)
+ .filter((t) => { return t != "spotify:delimiter"; })
+ }
+
+ // Copy the Queue to the Combine Buffer
+ function queueToBuffer() {
+ let tracks = getQueueTracks();
+
+ var date = new Date()
+ var year = new Intl.DateTimeFormat('en', { year: 'numeric' }).format(date);
+ var month = new Intl.DateTimeFormat('en', { month: 'short' }).format(date);
+ var day = new Intl.DateTimeFormat('en', { day: '2-digit' }).format(date);
+
+ const timeOptions = { hour: 'numeric', minute: 'numeric', hour12: false};
+ var time = new Intl.DateTimeFormat(`en`, timeOptions).format(date);
+
+ let queue = {
+ id: `spotify:queue-${date}`,
+ uri: `spotify:queue`,
+ name: "Queue",
+ imgUri: undefined,
+ tracks: tracks,
+ owner: `${time} - ${day} ${month} ${year}`,
+ }
+
+ LIST.addToStorage(queue);
+ }
+
+ // Show the clicked Playlist
+ async function showPlaylist(event) {
+ console.log(event)
+ }
+
+
+ /**************************************************************************
+ Merge Playlists
+ **************************************************************************/
+ // Merge all Playlists
+ function mergePlaylists(pattern) {
+ var tracks = LIST.getStorage().map((pl) => pl.tracks)
+
+ switch (pattern) {
+ case 1: return shuffle(tracks);
+ case 2: return alternate(tracks);
+ default: return append(tracks);
+ }
+ }
+
+ // Alternate Playlists
+ function alternate(arrays) {
+ var combined = []
+
+ while (arrays.length != 0) {
+ var current = arrays.shift()
+ if (current.length != 0) {
+ combined.push(current.shift())
+
+ if (current.length != 0) {
+ arrays.push(current)
+ }
+ }
+ }
+
+ return combined
+ }
+
+ // Shuffle all tracks using the Durstenfeld Shuffle
+ function shuffle(arrays) {
+ var combined = append(arrays)
+
+ for (var i = combined.length - 1; i > 0; i--) {
+ var j = Math.floor(Math.random() * (i + 1));
+ var temp = combined[i];
+ combined[i] = combined[j];
+ combined[j] = temp;
+ }
+
+ return combined;
+ }
+
+ // Simply Concat all Playlist
+ function append(arrays) {
+ var combined = []
+ arrays.forEach((arr) => combined = combined.concat(arr))
+
+ return combined;
+ }
+
+ /**************************************************************************
+ Calls to the CosmosAPI
+ **************************************************************************/
+ // Fetch all Track from Playlist URI
+ async function fetchPlaylist(uri) {
+ return await new Promise((resolve, reject) => {
+ Spicetify.BridgeAPI.cosmosJSON(
+ {
+ method: "GET",
+ uri: `sp://core-playlist/v1/playlist/${uri}/`,
+ body: {
+ policy: {
+ link: true,
+ },
+ },
+ },
+ (error, res) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ let id = `${uri}-${new Date()}`
+ let tracks = res.items.map((track) => track.link)
+ let img = res.playlist.picture
+ let name = res.playlist.name
+ let owner = res.playlist.owner.name
+
+ let playlist = {id: id, uri: uri, name: name, tracks: tracks, imgUri: img, owner: owner}
+
+ resolve(playlist);
+ }
+ );
+ });
+ }
+
+ // Create a new Playlist
+ async function createPlaylist(name) {
+ return await new Promise((resolve, reject) => {
+ Spicetify.BridgeAPI.cosmosJSON(
+ {
+ method: "POST",
+ uri: `sp://core-playlist/v1/rootlist`,
+ body: {
+ operation: "create",
+ playlist: !0,
+ before: "start",
+ name: name,
+ },
+ },
+ (error, res) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ resolve(res);
+ }
+ );
+ });
+ }
+
+ // add track list to playlist
+ async function addTracks(uri, tracks) {
+ return await new Promise((resolve, reject) => {
+ Spicetify.BridgeAPI.cosmosJSON(
+ {
+ method: "POST",
+ uri: `sp://core-playlist/v1/playlist/${uri}`,
+ body: {
+ operation: "add",
+ uris: tracks,
+ after: "end"
+ }
+ },
+ (error, res) => {
+ if (error) {
+ reject(error);
+ return;
+ }
+
+ resolve(res);
+ }
+ );
+ });
+ }
+})();
diff --git a/.config/spicetify/Extensions/fullScreen.js b/.config/spicetify/Extensions/fullScreen.js
new file mode 100644
index 00000000..fe6c5c50
--- /dev/null
+++ b/.config/spicetify/Extensions/fullScreen.js
@@ -0,0 +1,3087 @@
+// @ts-nocheck
+
+// NAME: Full Screen Mode
+// AUTHOR: daksh2k
+// DESCRIPTION: Fancy artwork and track status display.
+
+///
+
+const translations = {
+ "en-US": {
+ langName: "English",
+ context: {
+ queue: "Playing from queue",
+ track: "Playing track",
+ album: "Playing from album",
+ artist: "Playing from artist",
+ playlist: "Playing from playlist",
+ playlistFolder: "Playing from playlist folder",
+ search: "Playing from search",
+ searchDest: "Songs",
+ collection: "Playing from collection",
+ likedSongs: "Liked Songs",
+ trackRadio: "Playing from track radio",
+ artistRadio: "Playing from artist radio",
+ albumRadio: "Playing from album radio",
+ playlistRadio: "Playing from playlist radio",
+ },
+ upnext: "UP NEXT",
+ unknownArtist: "Artist (Unavailable)",
+ settings: {
+ switchToTV: "Switch to TV Mode",
+ switchToFullscreen: "Switch to Default Mode",
+ tvModeConfig: "TV Mode Configuration",
+ fullscreenConfig: "Full Screen Configuration",
+ exit: "Exit",
+
+ pluginSettings: "Plugin Settings",
+ fsHideOriginal: "Hide Stock Button(Spotify Premium)",
+ language: "Language",
+
+ lyricsHeader: "Lyrics Settings",
+ lyrics: "Lyrics",
+ lyricsAlignment: {
+ setting: "Lyrics Alignment",
+ left: "Left",
+ center: "Center",
+ right: "Right",
+ },
+ lyricsAnimationTempo: "Lyrics Animation Tempo",
+
+ generalHeader: "General Settings",
+ progressBar: "Progress Bar",
+ playerControls: "Player Controls",
+ trimTitle: "Trim Title",
+ trimTitleUpNext: "Trim Title(Up Next)",
+ showAlbum: {
+ setting: "Show Album",
+ never: "Never",
+ always: "Always",
+ date: "Show with Release Date",
+ },
+ showAllArtists: "Show All Artists",
+ icons: "Icons",
+ songChangeAnimation: "Song Change Animation",
+ fullscreen: "Fullscreen",
+
+ extraHeader: "Extra Functionality",
+ backgroundChoice: {
+ setting: "Background choice",
+ color: "Solid color",
+ artwork: "Artwork",
+ },
+ extraControls: "Extra Controls",
+ upnextDisplay: "Upnext Display",
+ contextDisplay: {
+ setting: "Context Display",
+ always: "Always",
+ never: "Never",
+ mouse: "On mouse movement",
+ },
+ volumeDisplay: {
+ setting: "Volume Bar Display",
+ always: "Always",
+ never: "Never",
+ mouse: "On mouse movement",
+ volume: "On volume change",
+ },
+
+ appearanceHeader: "Advanced/Appearance",
+ appearanceSubHeader: "Only change if you know what you are doing!",
+ backgroundColor: {
+ setting: "Color Choice on Colored Background",
+ vibrant: "Vibrant",
+ prominent: "Prominent",
+ desaturated: "Desaturated (recommended)",
+ lightVibrant: "Light Vibrant",
+ darkVibrant: "Dark Vibrant",
+ vibrantNonAlarming: "Vibrant Non Alarming",
+ },
+ themedButtons: "Themed Buttons",
+ themedIcons: "Themed Icons",
+ invertColors: {
+ setting: "Invert Colors",
+ never: "Never",
+ always: "Always",
+ auto: "Automatic (Based on BG)",
+ },
+ backAnimationTime: "Background Animation Time",
+ upnextScroll: {
+ setting: "Upnext Scroll Animation",
+ mq: "Marquee/Scrolling",
+ sp: "Spotify/Translating",
+ },
+ upnextTime: "Upnext Time to Show",
+ backgroundBlur: "Background Blur",
+ backgroundBrightness: "Background Brightness",
+
+ cgfReset: "Reset Config",
+ reload: "Reload Client",
+ },
+ tvBtnDesc: "TV Mode Display",
+ fullscreenBtnDesc: "Full Screen",
+ },
+ "it-IT": {
+ langName: "Italiano",
+ context: {
+ queue: "Riproduzione da coda",
+ track: "Riproduzione brano",
+ album: "Riproduzione album",
+ artist: "Riproduzione artista",
+ playlist: "Riproduzione playlist",
+ playlistFolder: "Riproduzione da cartella playlist",
+ search: "Riproduzione da ricerca",
+ searchDest: "Brani",
+ collection: "Riproduzione dalla libreria",
+ likedSongs: "Brani che ti piacciono",
+ trackRadio: "Radio dal brano",
+ artistRadio: "Radio da artista",
+ albumRadio: "Radio da album",
+ playlistRadio: "Radio da playlist",
+ },
+ upnext: "In coda",
+ unknownArtist: "Artista sconosciuto",
+ settings: {
+ switchToTV: "Passa a modalità TV",
+ switchToFullscreen: "Passa a modalità fullscreen",
+ tvModeConfig: "Modalità TV",
+ fullscreenConfig: "Modalità fullscreen",
+ exit: "Esci",
+
+ pluginSettings: "Impostazioni plugin",
+ fsHideOriginal: "Pulsante Nascondi Stock (Spotify Premium)",
+ language: "Lingua",
+
+ lyricsHeader: "Impostazioni testo",
+ lyrics: "Mostra testo",
+ lyricsAlignment: {
+ setting: "Allineamento testo",
+ left: "Sinistra",
+ center: "Centro",
+ right: "Destra",
+ },
+ lyricsAnimationTempo: "Durata animazione testo",
+
+ generalHeader: "Impostazioni generali",
+ progressBar: "Barra di avanzamento",
+ playerControls: "Controlli player",
+ trimTitle: "Accorcia titolo",
+ trimTitleUpNext: "Accorcia titolo in Up next",
+ showAlbum: {
+ setting: "Mostra album",
+ never: "Mai",
+ always: "Sempre",
+ date: "Mostra con data di uscita",
+ },
+ showAllArtists: "Mostra tutti gli artisti",
+ icons: "Icone",
+ songChangeAnimation: "Animazione cambio brano",
+ fullscreen: "Schermo intero",
+
+ extraHeader: "Funzionalità aggiuntive",
+ backgroundChoice: {
+ setting: "Scelta sfondo",
+ color: "Tinta unita",
+ artwork: "Artwork",
+ },
+ extraControls: "Controlli aggiuntivi",
+ upnextDisplay: "Mostra Up next",
+ contextDisplay: {
+ setting: "Mostra contesto",
+ always: "Sempre",
+ never: "Mai",
+ mouse: "Al movimento del mouse",
+ },
+ volumeDisplay: {
+ setting: "Mostra barra volume",
+ always: "Sempre",
+ never: "Mai",
+ mouse: "Al movimento del mouse",
+ volume: "Al cambio volume",
+ },
+
+ appearanceHeader: "Avanzato/Aspetto",
+ appearanceSubHeader: "Cambia solo se sai cosa stai facendo!",
+ backgroundColor: {
+ setting: "Colore su sfondo tinta unita",
+ vibrant: "Vibrant",
+ prominent: "Prominent",
+ desaturated: "Desaturato (raccomandato)",
+ lightVibrant: "Vibrant chiaro",
+ darkVibrant: "Vibrant scuro",
+ vibrantNonAlarming: "Vibrant delicato",
+ },
+ themedButtons: "Pulsanti a tema",
+ themedIcons: "Icone a tema",
+ invertColors: {
+ setting: "Inverti colori",
+ never: "Mai",
+ always: "Sempre",
+ auto: "Automatico (basato su sfondo)",
+ },
+ backAnimationTime: "Durata animazione sfondo",
+ upnextScroll: {
+ setting: "Scorrimento Up next",
+ mq: "Scorrimento",
+ sp: "Traslazione",
+ },
+ upnextTime: "Tempo per mostrare Up next",
+ backgroundBlur: "Sfocamento sfondo",
+ backgroundBrightness: "Luminosità sfondo",
+
+ cgfReset: "Resetta configurazione",
+ reload: "Ricarica client",
+ },
+ tvBtnDesc: "Modalità TV",
+ fullscreenBtnDesc: "Schermo intero",
+ },
+ "zh-CN": {
+ langName: "简体中文",
+ context: {
+ queue: "正在从队列播放",
+ track: "正在播放",
+ album: "正在从专辑播放",
+ artist: "正在从艺人播放",
+ playlist: "正在从歌单播放",
+ playlistFolder: "正在从歌单文件夹播放",
+ search: "正在从搜索结果播放",
+ searchDest: "歌曲",
+ collection: "正在从合辑播放",
+ likedSongs: "已点赞的歌曲",
+ trackRadio: "正在从歌曲电台播放",
+ artistRadio: "正在从艺人电台播放",
+ albumRadio: "正在从专辑电台播放",
+ playlistRadio: "正在从歌单电台播放",
+ },
+ upnext: "下一首",
+ unknownArtist: "未知歌手",
+ settings: {
+ switchToTV: "切换至电视模式",
+ switchToFullscreen: "切换至默认模式",
+ tvModeConfig: "电视模式配置",
+ fullscreenConfig: "全屏模式配置",
+ exit: "退出",
+
+ pluginSettings: "插件设置",
+ fsHideOriginal: "隐藏切换至原版按钮 (Spotify Premium)",
+ language: "语言",
+
+ lyricsHeader: "歌词设置",
+ lyrics: "歌词",
+ lyricsAlignment: {
+ setting: "歌词对齐方式",
+ left: "靠左",
+ center: "居中",
+ right: "靠右",
+ },
+ lyricsAnimationTempo: "歌词动画速度",
+
+ generalHeader: "通用设置",
+ progressBar: "播放进度条",
+ playerControls: "播放控制",
+ trimTitle: "缩短标题",
+ trimTitleUpNext: "缩短标题 (下一首提示)",
+ showAlbum: {
+ setting: "显示标题",
+ never: "永不",
+ always: "总是",
+ date: "与发布日期一起显示",
+ },
+ showAllArtists: "显示所有艺人",
+ icons: "显示图标",
+ songChangeAnimation: "切歌动画",
+ fullscreen: "全屏",
+
+ extraHeader: "额外功能",
+ backgroundChoice: {
+ setting: "背景",
+ color: "纯色",
+ artwork: "艺术画",
+ },
+ extraControls: "额外控件",
+ upnextDisplay: "下一首提示",
+ contextDisplay: {
+ setting: "内容来源显示",
+ always: "总是",
+ never: "从不",
+ mouse: "当鼠标移动时",
+ },
+ volumeDisplay: {
+ setting: "音量显示",
+ always: "总是",
+ never: "从不",
+ mouse: "当鼠标移动时",
+ volume: "当音量调节时",
+ },
+
+ appearanceHeader: "高级/外观",
+ appearanceSubHeader: "不要动除非你知道自己在做什么",
+ backgroundColor: {
+ setting: "纯色背景的颜色配置",
+ vibrant: "Vibrant",
+ prominent: "Prominent",
+ desaturated: "Desaturated (推荐)",
+ lightVibrant: "Light Vibrant",
+ darkVibrant: "Dark Vibrant",
+ vibrantNonAlarming: "Vibrant Non Alarming",
+ },
+ themedButtons: "主题色按钮",
+ themedIcons: "主题色图标",
+ invertColors: {
+ setting: "反转颜色",
+ never: "从不",
+ always: "总是",
+ auto: "自动(基于背景)",
+ },
+ backAnimationTime: "背景动画时间",
+ upnextScroll: {
+ setting: "下一首提示滚动",
+ mq: "Marquee/滚动",
+ sp: "Spotify/变换",
+ },
+ upnextTime: "下一首提示显示时间",
+ backgroundBlur: "背景模糊",
+ backgroundBrightness: "背景亮度",
+
+ cgfReset: "重置配置",
+ reload: "重新载入应用",
+ },
+ tvBtnDesc: "电视模式显示",
+ fullscreenBtnDesc: "全屏",
+ },
+};
+
+let INIT_RETRIES = 0;
+function fullScreen() {
+ const extraBar =
+ document.querySelector(".main-nowPlayingBar-right")?.childNodes[0] ||
+ document.querySelector(".ExtraControls") ||
+ document.querySelector(".ClYTTKGdd9KB7D9MXicj");
+ const topBar = document.querySelector(".main-topBar-historyButtons");
+ const { CosmosAsync, Keyboard, Player, Platform } = Spicetify;
+
+ let entriesToVerify = [topBar, extraBar, CosmosAsync, Keyboard, Player, Platform];
+
+ if (INIT_RETRIES > 50) {
+ entriesToVerify.forEach((entry) => {
+ if (!entry) {
+ console.error("Spicetify method not available. Report issue on GitHub or run Spicetify.test() to test.");
+ Spicetify.showNotification(`Error initializing "fullscreen.js" extension. Spicetify method not available. Report issue on GitHub.`);
+ }
+ });
+ return;
+ }
+ if (entriesToVerify.some((it) => !it)) {
+ setTimeout(fullScreen, 300);
+ INIT_RETRIES += 1;
+ return;
+ }
+ Spicetify.Keyboard.registerShortcut(
+ {
+ key: Spicetify.Keyboard.KEYS["T"],
+ ctrl: false,
+ shift: false,
+ alt: false,
+ },
+ openwithTV
+ );
+ Spicetify.Keyboard.registerShortcut(
+ {
+ key: Spicetify.Keyboard.KEYS["F"],
+ ctrl: false,
+ shift: false,
+ alt: false,
+ },
+ openwithDef
+ );
+
+ function openwithTV() {
+ if (!document.body.classList.contains("fsd-activated") || !CONFIG.tvMode || ACTIVE !== "tv") {
+ if (!CONFIG.tvMode || ACTIVE !== "tv") {
+ CONFIG["tvMode"] = true;
+ ACTIVE = "tv";
+ saveConfig();
+ render();
+ }
+ activate();
+ } else deactivate();
+ }
+
+ function openwithDef() {
+ if (!document.body.classList.contains("fsd-activated") || CONFIG.tvMode || ACTIVE !== "def") {
+ if (CONFIG.tvMode || ACTIVE !== "def") {
+ CONFIG["tvMode"] = false;
+ ACTIVE = "def";
+ saveConfig();
+ render();
+ }
+ activate();
+ } else deactivate();
+ }
+ const DEFAULTS = {
+ tv: {
+ lyricsDisplay: true,
+ lyricsAlignment: "right",
+ animationTempo: 0.2,
+ progressBarDisplay: false,
+ playerControls: false,
+ trimTitle: true,
+ trimTitleUpNext: true,
+ showAlbum: "d",
+ showAllArtists: true,
+ icons: true,
+ titleMovingIcon: false,
+ enableFade: true,
+ enableFullscreen: true,
+ extraControls: false,
+ upnextDisplay: true,
+ contextDisplay: "a",
+ volumeDisplay: "o",
+ themedButtons: true,
+ themedIcons: true,
+ invertColors: "n",
+ backAnimationTime: 0.4,
+ upNextAnim: "sp",
+ upnextTimeToShow: 45,
+ blurSize: 0,
+ backgroundBrightness: 0.4,
+ },
+ def: {
+ lyricsDisplay: true,
+ lyricsAlignment: "right",
+ animationTempo: 0.2,
+ progressBarDisplay: true,
+ playerControls: true,
+ trimTitle: true,
+ trimTitleUpNext: true,
+ showAlbum: "n",
+ showAllArtists: true,
+ icons: false,
+ titleMovingIcon: false,
+ enableFade: true,
+ enableFullscreen: true,
+ backgroundChoice: "a",
+ extraControls: true,
+ upnextDisplay: true,
+ contextDisplay: "m",
+ volumeDisplay: "o",
+ themedButtons: true,
+ themedIcons: false,
+ invertColors: "n",
+ backAnimationTime: 1,
+ upNextAnim: "sp",
+ upnextTimeToShow: 30,
+ coloredBackChoice: "DESATURATED",
+ blurSize: 24,
+ backgroundBrightness: 0.7,
+ },
+ tvMode: false,
+ locale: "en-US",
+ fsHideOriginal: false,
+ };
+ const CONFIG = getConfig();
+ if (localStorage.getItem("full-screen:inverted") === null) {
+ localStorage.setItem("full-screen:inverted", "{}");
+ }
+
+ const INVERTED = JSON.parse(localStorage.getItem("full-screen:inverted") ?? "{}");
+ let ACTIVE = CONFIG.tvMode ? "tv" : "def";
+ let LOCALE = CONFIG.locale;
+
+ const OFFLINESVG = `data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCI+CiAgPHJlY3Qgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiB4PSIwIiB5PSIwIiAvPgogIDxwYXRoIGZpbGw9IiNCM0IzQjMiIGQ9Ik0yNi4yNSAxNi4xNjJMMjEuMDA1IDEzLjEzNEwyMS4wMTIgMjIuNTA2QzIwLjU5NCAyMi4xOTIgMjAuMDgxIDIxLjk5OSAxOS41MTkgMjEuOTk5QzE4LjE0MSAyMS45OTkgMTcuMDE5IDIzLjEyMSAxNy4wMTkgMjQuNDk5QzE3LjAxOSAyNS44NzggMTguMTQxIDI2Ljk5OSAxOS41MTkgMjYuOTk5QzIwLjg5NyAyNi45OTkgMjIuMDE5IDI1Ljg3OCAyMi4wMTkgMjQuNDk5QzIyLjAxOSAyNC40MjIgMjIuMDA2IDE0Ljg2NyAyMi4wMDYgMTQuODY3TDI1Ljc1IDE3LjAyOUwyNi4yNSAxNi4xNjJaTTE5LjUxOSAyNS45OThDMTguNjkyIDI1Ljk5OCAxOC4wMTkgMjUuMzI1IDE4LjAxOSAyNC40OThDMTguMDE5IDIzLjY3MSAxOC42OTIgMjIuOTk4IDE5LjUxOSAyMi45OThDMjAuMzQ2IDIyLjk5OCAyMS4wMTkgMjMuNjcxIDIxLjAxOSAyNC40OThDMjEuMDE5IDI1LjMyNSAyMC4zNDYgMjUuOTk4IDE5LjUxOSAyNS45OThaIi8+Cjwvc3ZnPgo=`;
+
+ const style = document.createElement("style");
+
+ const container = document.createElement("div");
+ container.id = "full-screen-display";
+ container.classList.add("Video", "VideoPlayer--fullscreen", "VideoPlayer--landscape");
+
+ let cover,
+ back,
+ title,
+ artist,
+ album,
+ prog,
+ elaps,
+ durr,
+ play,
+ ctx_container,
+ ctx_icon,
+ ctx_source,
+ ctx_name,
+ fsd_myUp,
+ fsd_nextCover,
+ fsd_up_next_text,
+ fsd_next_tit_art,
+ fsd_next_tit_art_inner,
+ fsd_first_span,
+ fsd_second_span,
+ volumeContainer,
+ volumeCurr,
+ volumeBarInner,
+ volumeIcon,
+ playingIcon,
+ pausedIcon,
+ nextControl,
+ backControl,
+ heart,
+ shuffle,
+ repeat,
+ invertButton,
+ lyrics;
+ const nextTrackImg = new Image();
+ const artistImg = new Image();
+
+ function render() {
+ container.classList.toggle("lyrics-active", !!CONFIG[ACTIVE].lyricsDisplay);
+ if (!CONFIG[ACTIVE].lyricsDisplay || !CONFIG[ACTIVE].extraControls) container.classList.remove("lyrics-hide-force");
+ const styleBase = `
+#full-screen-display {
+ display: none;
+ z-index: 100;
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ cursor: default;
+ left: 0;
+ top: 0;
+ --transition-duration: .8s;
+ --transition-function: ease-in-out;
+ --main-color: 255,255,255;
+ --contrast-color: 0,0,0;
+ --primary-color: rgba(var(--main-color),1);
+ --secondary-color: rgba(var(--main-color),.7);
+ --tertiary-color: rgba(var(--main-color),.5);
+ --theme-color: 175,175,175;
+ --theme-background-color: rgba(175,175,175,.6);
+ --theme-hover-color: rgba(175,175,175,.3);
+ --theme-main-color: rgba(var(--theme-color),1);
+}
+#full-screen-display.themed-buttons{
+ --theme-background-color: rgba(var(--theme-color),.6);
+ --theme-hover-color: rgba(var(--theme-color),.3);
+}
+.unavailable{
+ color: var(--tertiary-color) !important;
+ pointer-events: none !important;
+ opacity: .5 !important;
+ background: transparent !important;
+}
+/*
+.unavailable::after{
+ content: "";
+ display: block;
+ background-color: #FFF;
+ bottom: 50%;
+ left: 0;
+ right: 0;
+ position: absolute;
+ transform: rotateZ(45deg);
+ width: 25px;
+ height: 1.5px;
+}
+.unavailable::before{
+ content: "";
+ display: block;
+ background-color: #FFF;
+ bottom: 50%;
+ left: 0;
+ right: 0;
+ position: absolute;
+ transform: rotateZ(-45deg);
+ width: 25px;
+ height: 1.5px;
+}
+*/
+@keyframes fadeUp{
+ 0%{
+ opacity:0;
+ transform:translateY(-10px)
+ }
+ to{
+ opacity:1;
+ transform:translateY(0)
+ }
+}
+@keyframes fadeDo{
+ 0%{
+ opacity:0;
+ transform:translateY(10px)
+ }
+ to{
+ opacity:1;
+ transform:translateY(0)
+ }
+}
+@keyframes fadeRi{
+ 0%{
+ opacity:0;
+ transform:translateX(10px)
+ }
+ to{
+ opacity:1;
+ transform:translateX(0)
+ }
+}
+@keyframes fadeLe{
+ 0%{
+ opacity:0;
+ transform:translateX(-10px)
+ }
+ to{
+ opacity:1;
+ transform:translateX(0)
+ }
+}
+.fade-do{
+ animation: fadeDo .5s cubic-bezier(.3, 0, 0, 1);
+}
+.fade-up{
+ animation: fadeUp .5s cubic-bezier(.3, 0, 0, 1);
+}
+.fade-ri{
+ animation: fadeRi .5s cubic-bezier(.3, 0, 0, 1);
+}
+.fade-le{
+ animation: fadeLe .5s cubic-bezier(.3, 0, 0, 1);
+}
+button.dot-after{
+ padding-bottom: 3px !important;
+}
+.dot-after:after{
+ background-color: currentColor;
+ border-radius: 50%;
+ bottom: 3px;
+ content: "";
+ display: block;
+ height: 4px;
+ left: 50%;
+ position: absolute;
+ transform: translateX(-50%);
+ width: 4px;
+}
+#fsd-ctx-container {
+ background-color: transparent;
+ color: var(--secondary-color);
+ position: fixed;
+ float: left;
+ top: 30px;
+ left: 50px;
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ text-align: left;
+ z-index: 50;
+ transition: all 1s ease-in-out;
+ opacity: 1;
+ max-width: 40%;
+}
+#fsd-ctx-details{
+ padding-left: 18px;
+ line-height: initial;
+ font-size: 18px;
+ overflow: hidden;
+}
+#fsd-ctx-icon{
+ width: 48px;
+ height : 48px;
+}
+#fsd-ctx-icon svg{
+ fill: var(--primary-color) !important;
+}
+#fsd-ctx-source{
+ text-transform: uppercase;
+}
+#fsd-ctx-name{
+ font-weight: 700;
+ font-size: 20px;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+}
+.ctx-no-name{
+ padding-bottom: 2px;
+ font-size: 24px;
+ font-weight: 600;
+}
+#fsd-upnext-container{
+ float: right;
+ width: 472px;
+ height: 102px;
+ max-width: 45%;
+ position: fixed;
+ top: 45px;
+ right: 60px;
+ display: flex;
+ border: 1px solid rgba(130,130,130,.7);
+ border-radius: 10px;
+ background-color: rgba(20,20,20,1);
+ flex-direction: row;
+ text-align: left;
+ z-index: 50;
+ transition: transform .8s ease-in-out;
+ transform: translateX(600px);
+}
+#fsd_next_art_image{
+ background-size: cover;
+ background-position: center;
+ width: 100px;
+ height: 100px;
+ border-radius: 9px 0 0 9px;
+}
+#fsd_next_details{
+ padding-left: 18px;
+ padding-top: 17px;
+ line-height: initial;
+ width: calc(100% - 115px);
+ color: rgba(255,255,255,1);
+ font-size: 19px;
+ overflow: hidden;
+}
+#fsd_next_tit_art{
+ padding-top: 9px;
+ font-size: 22px;
+ font-weight: 700;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+@keyframes fsd_cssmarquee {
+ 0% {
+ transform: translateX(0%);
+ }
+ 18% {
+ transform: translateX(0%);
+ }
+ 100% {
+ transform: translateX(var(--translate_width_fsd));
+ }
+}
+@keyframes fsd_translate {
+ 0%,10% {
+ transform: translateX(0%);
+ }
+ 50%,55% {
+ transform: translateX(var(--translate_width_fsd));
+ }
+ 100% {
+ transform: translateX(0%);
+ }
+}
+#fsd-volume-container{
+ position: fixed;
+ text-align: center;
+ background-color: transparent;
+ color: var(--primary-color);
+ float: left;
+ top: 30%;
+ left: 30px;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ z-index: 50;
+ height: 200px;
+ max-height: 33vh;
+ transition: transform .8s var(--transition-function);
+}
+#fsd-volume-container.v-hidden{
+ transform: translateX(-100px) scale(.1);
+}
+#fsd-volume-container:hover{
+ transform: translateX(0px) scale(1);
+}
+#fsd-volume-bar{
+ margin: 8px 0;
+ border-radius: 4px;
+ background-color: rgba(var(--main-color),.35);
+ overflow: hidden;
+ width: 8px;
+ height: 100%;
+ display: flex;
+ align-items: end;
+}
+#fsd-volume-bar-inner{
+ width: 100%;
+ border-radius: 4px;
+ background-color: var(--primary-color);
+ box-shadow: 4px 0 12px rgba(0, 0, 0, 0.8);
+ transition: height .2s var(--transition-function);
+}
+#fsd-volume-icon svg{
+ fill: var(--primary-color) !important;
+}
+.unavailable #fsd-volume-bar-inner{
+ height: 100%;
+ background-color: var(--tertiary-color);
+}
+#fsd-volume{
+ width: 50px;
+ font-size: 18px;
+}
+#fad-lyrics-plus-container{
+ transition: transform var(--transition-duration) var(--transition-function);
+ position: absolute;
+ right: -50px;
+ max-width: 50%;
+ top: 7.5vh;
+}
+.lyrics-unavailable #fad-lyrics-plus-container, .lyrics-hide-force #fad-lyrics-plus-container{
+ transform: translateX(1000px) scale3d(.1,.1,.1) rotate(45deg);
+}
+#fad-lyrics-plus-container .lyrics-lyricsContainer-LyricsContainer{
+ --lyrics-color-active: var(--primary-color) !important;
+ --lyrics-color-inactive: var(--tertiary-color) !important;
+ --lyrics-highlight-background: rgba(var(--contrast-color),.7) !important;
+ --lyrics-align-text: ${CONFIG[ACTIVE].lyricsAlignment} !important;
+ --animation-tempo: ${CONFIG[ACTIVE].animationTempo}s !important;
+ height: 85vh !important;
+}
+.lyrics-config-button{
+ margin-right: 20px;
+}
+#fsd-foreground {
+ position: relative;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--primary-color);
+ transition: all var(--transition-duration) var(--transition-function);
+}
+#fsd-art-image {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ padding-bottom: 100%;
+ border-radius: 8px;
+ background-size: cover;
+}
+#fsd-art-inner {
+ position: absolute;
+ left: 3%;
+ bottom: 0;
+ width: 94%;
+ height: 94%;
+ z-index: -1;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6) !important;
+ transform: translateZ(0);
+}
+#fsd-artist{
+ font-weight: 500;
+ color: var(--secondary-color);
+}
+#fsd-album{
+ font-weight: 400;
+ color: var(--tertiary-color);
+}
+.fsd-controls{
+ display: flex;
+ flex-direction: row;
+ column-gap: 10px;
+}
+#fsd-progress {
+ width: 100%;
+ height: 6px;
+ border-radius: 4px;
+ background-color: rgba(var(--main-color),.35);
+ overflow: hidden;
+}
+#fsd-progress-inner {
+ height: 100%;
+ border-radius: 4px;
+ background-color: var(--primary-color);
+ box-shadow: 4px 0 12px rgba(0, 0, 0, 0.8);
+}
+#fsd-elapsed, #fsd-duration{
+ min-width: 35px;
+ text-align: center;
+}
+#fsd-elapsed {
+ margin-right: 10px;
+}
+#fsd-duration {
+ margin-left: 10px;
+}
+#fsd-background {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ z-index: -2;
+}
+
+#full-screen-display .fs-button{
+ background: transparent;
+ border: 0;
+ border-radius: 8px;
+ color: var(--primary-color);
+ padding: 3px 5px 0 5px;
+ cursor: pointer;
+ position: relative;
+ transition: all .3s var(--transition-function), transform .1s var(--transition-function);
+}
+#full-screen-display .fs-button:hover{
+ transform: scale(1.2);
+ filter: saturate(1.5) contrast(1.5) !important;
+ background: var(--theme-hover-color);
+}
+#full-screen-display .fs-button.button-active{
+ background: var(--theme-background-color) !important;
+ filter: saturate(1.5) contrast(1.5) !important;
+}
+
+#fsd-foreground svg{
+ fill: var(--primary-color);
+ transition: all .3s var(--transition-function);
+}
+.themed-icons #fsd-foreground svg{
+ fill: var(--theme-main-color);
+ filter: saturate(1.8);
+}
+.themed-icons.themed-buttons .fs-button.button-active svg{
+ fill: var(--primary-color) !important;
+}
+.fsd-background-fade {
+ transition: background-image var(--fs-transition) linear;
+}
+body.fsd-activated #full-screen-display {
+ display: block;
+}
+.fsd-activated .Root__top-bar, .fsd-activated .Root__nav-bar, .fsd-activated .Root__main-view, .fsd-activated .Root__now-playing-bar{
+ visibility: hidden;
+ display: none;
+}
+.main-notificationBubbleContainer-NotificationBubbleContainer{
+ z-index: 1000;
+}`;
+
+ const styleChoices = [
+ `
+#full-screen-display,
+#full-screen-display.lyrics-unavailable,
+#full-screen-display.lyrics-hide-force{
+ --fsd-foreground-transform: 50%;
+ --fsd-art-max-width: 600px;
+ --fsd-items-max-width: 580px;
+ --fsd-title-size: 50px;
+ --fsd-sec-size: 28px;
+}
+#full-screen-display.lyrics-active :not(#full-screen-display.lyrics-unavailable *,#full-screen-display.lyrics-hide-force *){
+ --fsd-foreground-transform: 0px;
+ --fsd-art-max-width: 500px;
+ --fsd-items-max-width: 480px;
+ --fsd-title-size: 40px;
+ --fsd-sec-size: 23px;
+}
+
+#fsd-art, #fsd-details, #fsd-status, #fsd-progress-container{
+ transition: all var(--transition-duration) var(--transition-function);
+}
+
+#fsd-foreground {
+ transform: translateX(var(--fsd-foreground-transform));
+ width: 50%;
+ flex-direction: column;
+ text-align: center;
+}
+#fsd-art {
+ width: calc(100vh - 300px);
+ max-width: var(--fsd-art-max-width);
+ min-width: 300px;
+}
+
+#fsd-title{
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 3;
+ overflow: hidden;
+}
+#fsd-album, #fsd-artist{
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+}
+
+#fsd-progress-container {
+ width: 28vw;
+ max-width: var(--fsd-items-max-width);
+ display: flex;
+ align-items: center;
+}
+#fsd-details {
+ padding-top: 30px;
+ line-height: initial;
+ max-width: var(--fsd-items-max-width);
+ color: var(--primary-color);
+}
+#fsd-status {
+ display: flex;
+ width: 28vw;
+ max-width: var(--fsd-items-max-width);
+ align-items: center;
+ justify-content: space-between;
+ flex-direction: row;
+}
+#fsd-status.active {
+ margin: 5px auto 0;
+ gap: 10px;
+}
+
+#fsd-title {
+ font-size: var(--fsd-title-size);
+ font-weight: 900;
+ transition: all var(--transition-duration) var(--transition-function);
+}
+#fsd-artist,#fsd-album{
+ font-size: var(--fsd-sec-size);
+ transition: all var(--transition-duration) var(--transition-function);
+}
+@media (max-width: 900px), (max-height: 900px){
+ #fsd-title{
+ font-size: 35px;
+ font-weight: 600;
+ }
+}
+
+#fsd-title svg{
+ width: 35px;
+ height: 35px;
+}
+.lyrics-active #fsd-title svg{
+ width: 30px;
+ height: 30px;
+}
+.lyrics-unavailable #fsd-title svg,.lyrics-hide-force #fsd-title svg{
+ width: 35px;
+ height: 35px;
+}
+#playing-icon{
+ width: 28px !important;
+ height: 28px !important;
+ margin-right: 7px;
+}
+.lyrics-active #playing-icon{
+ margin-right: 2px;
+}
+.lyrics-unavailable #playing-icon,.lyrics-hide-force #playing-icon{
+ margin-right: 7px;
+}
+
+#fsd-artist svg, #fsd-album svg{
+ width: calc(var(--fsd-sec-size) - 6px);
+ height: calc(var(--fsd-sec-size) - 6px);
+ margin-right: 5px;
+}
+
+.fsd-controls {
+ margin-top: 10px;
+ margin-bottom: 5px;
+}
+.fsd-controls-left{
+ width: 30%;
+ justify-content: flex-start;
+}
+.fsd-controls-center{
+ width: 40%;
+ justify-content: center;
+ margin: 10px auto 5px;
+}
+.fsd-controls-right{
+ width: 30%;
+ justify-content: flex-end;
+}
+`,
+ `#fsd-background-image {
+ height: 100%;
+ background-size: cover;
+ filter: brightness(${CONFIG[ACTIVE].backgroundBrightness}) blur(${CONFIG[ACTIVE].blurSize}px);
+ background-position: center;
+ transform: translateZ(0);
+}
+
+#fsd-foreground {
+ flex-direction: row;
+ text-align: left;
+ justify-content: left;
+ align-items: flex-end;
+ position: absolute;
+ top: auto;
+ bottom: 75px;
+}
+.lyrics-active #fsd-foreground{
+ width: max-content;
+ max-width: 65%;
+}
+
+#fsd-art {
+ width: calc(100vw - 840px);
+ min-width: 180px;
+ max-width: 220px;
+ margin-left: 65px;
+}
+
+#fsd-progress-container {
+ width: 100%;
+ max-width: 450px;
+ display: flex;
+ align-items: center;
+}
+.fsd-controls + #fsd-progress-container{
+ padding-left: 10px;
+}
+#fsd-details {
+ padding-left: 30px;
+ line-height: initial;
+ width: 80%;
+ color: var(--primary-color);
+}
+#fsd-title, #fsd-album, #fsd-artist{
+ display: flex;
+ justify-content: flex-start;
+ align-items: baseline;
+ gap: 5px;
+}
+#fsd-title span, #fsd-album span, #fsd-artist span{
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+}
+#fsd-title span{
+ -webkit-line-clamp: 3;
+}
+#fsd-title svg, #fsd-artist svg, #fsd-album svg{
+ flex: 0 0 auto;
+}
+#fsd-album{
+ position: relative;
+}
+#fsd-album svg{
+ position: absolute;
+ top: 7px;
+}
+#fsd-album svg + span{
+ margin-left: 40px;
+}
+#fsd-title {
+ font-size: 62px;
+ font-weight: 900;
+}
+@media (max-width: 900px), (max-height: 800px){
+ #fsd-title{
+ font-size: 40px;
+ font-weight: 600;
+ }
+ #fsd-artist, #fsd-album {
+ font-size: 20px;
+ }
+}
+#fsd-artist, #fsd-album {
+ font-size: 28px;
+}
+#fsd-title svg{
+ width: 35px;
+ height: 45px;
+}
+#playing-icon{
+ width: 30px !important;
+ height: 40px !important;
+ margin-right: 5px;
+}
+#fsd-artist svg, #fsd-album svg {
+ margin-right: 15px;
+ width: 22px;
+ height: 22px;
+}
+#fsd-status {
+ display: flex;
+ flex-direction: row;
+ min-width: 450px;
+ max-width: 450px;
+ align-items: center;
+ justify-content: space-between;
+}
+#fsd-status.active {
+ column-gap: 10px;
+ margin: 10px 0;
+}
+`,
+ ];
+ const iconStyleChoices = [
+ `
+#fsd-title svg, #fsd-artist svg, #fsd-album svg {
+ display: none;
+}`,
+ `
+#fsd-title svg, #fsd-artist svg, #fsd-album svg {
+ transition: all var(--transition-duration) var(--transition-function);
+ display: inline-block;
+}`,
+ ];
+ Spicetify.Player.removeEventListener("songchange", updateInfo);
+ if (progressListener) clearInterval(progressListener);
+ Spicetify.Player.removeEventListener("onplaypause", updatePlayerControls);
+ Spicetify.Player.removeEventListener("onplaypause", updatePlayingIcon);
+ Spicetify.Player.origin._events.removeListener("update", updateExtraControls);
+ heartObserver.disconnect();
+
+ Spicetify.Player.origin._events.removeListener("queue_update", updateUpNext);
+ Spicetify.Player.origin._events.removeListener("update", updateUpNextShow);
+ window.removeEventListener("resize", updateUpNext);
+ upNextShown = false;
+
+ if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.removeListener("volume", updateVolume);
+ else Spicetify.Platform.PlaybackAPI._events.removeListener("volume", updateVolume);
+
+ if (origLoc !== "/lyrics-plus" && document.body.classList.contains("fsd-activated")) {
+ Spicetify.Platform.History.push(origLoc);
+ Spicetify.Platform.History.entries.splice(Spicetify.Platform.History.entries.length - 3, 2);
+ Spicetify.Platform.History.index = Spicetify.Platform.History.index > 0 ? Spicetify.Platform.History.index - 2 : -1;
+ Spicetify.Platform.History.length = Spicetify.Platform.History.length > 1 ? Spicetify.Platform.History.length - 2 : 0;
+ }
+ window.dispatchEvent(new Event("fad-request"));
+ window.removeEventListener("lyrics-plus-update", handleLyricsUpdate);
+
+ container.removeEventListener("mousemove", hideCursor);
+ container.removeEventListener("mousemove", hideContext);
+ container.removeEventListener("mousemove", hideVolume);
+
+ if (curTimer) clearTimeout(curTimer);
+ if (ctxTimer) clearTimeout(ctxTimer);
+ if (volTimer) clearTimeout(volTimer);
+
+ style.innerHTML = styleBase + styleChoices[CONFIG.tvMode ? 1 : 0] + iconStyleChoices[CONFIG[ACTIVE].icons ? 1 : 0];
+
+ container.innerHTML = `
+${
+ CONFIG.tvMode
+ ? ``
+ : ``
+}
+ ${
+ CONFIG[ACTIVE].contextDisplay !== "n"
+ ? `
+ `
+ : ""
+ }
+ ${
+ CONFIG[ACTIVE].upnextDisplay
+ ? `
+`
+ : ""
+ }
+${
+ CONFIG[ACTIVE].volumeDisplay !== "n"
+ ? `
+`
+ : ""
+}
+${CONFIG[ACTIVE].lyricsDisplay ? `` : ""}
+
+
+
+
+
+
+
+
+ ${
+ CONFIG[ACTIVE].showAlbum !== "n"
+ ? `
+ ${CONFIG[ACTIVE].icons ? `` : ""}
+
+
`
+ : ""
+ }
+
+ ${
+ CONFIG.tvMode && CONFIG[ACTIVE].playerControls && CONFIG[ACTIVE].extraControls
+ ? `${
+ CONFIG[ACTIVE].progressBarDisplay
+ ? `
`
+ : ""
+ }`
+ : ""
+ }
+
+ ${
+ CONFIG.tvMode
+ ? ""
+ : `${
+ CONFIG[ACTIVE].progressBarDisplay
+ ? `
`
+ : ""
+ }`
+ }
+
`;
+ if (CONFIG.tvMode) back = container.querySelector("#fsd-background-image");
+ else {
+ back = container.querySelector("canvas");
+ back && (back.width = window.innerWidth);
+ back && (back.height = window.innerHeight);
+ }
+ cover = container.querySelector("#fsd-art-image");
+ title = container.querySelector("#fsd-title span");
+ artist = container.querySelector("#fsd-artist span");
+ album = container.querySelector("#fsd-album span");
+
+ if (CONFIG[ACTIVE].contextDisplay !== "n") {
+ ctx_container = container.querySelector("#fsd-ctx-container");
+ ctx_icon = container.querySelector("#fsd-ctx-icon");
+ ctx_source = container.querySelector("#fsd-ctx-source");
+ ctx_name = container.querySelector("#fsd-ctx-name");
+ }
+ if (CONFIG[ACTIVE].upnextDisplay) {
+ fsd_myUp = container.querySelector("#fsd-upnext-container");
+ fsd_myUp.onclick = Spicetify.Player.next;
+ fsd_nextCover = container.querySelector("#fsd_next_art_image");
+ fsd_up_next_text = container.querySelector("#fsd_up_next_text");
+ fsd_next_tit_art = container.querySelector("#fsd_next_tit_art");
+ fsd_next_tit_art_inner = container.querySelector("#fsd_next_tit_art_inner");
+ fsd_first_span = container.querySelector("#fsd_first_span");
+ fsd_second_span = container.querySelector("#fsd_second_span");
+ }
+ if (CONFIG[ACTIVE].volumeDisplay !== "n") {
+ volumeContainer = container.querySelector("#fsd-volume-container");
+ volumeCurr = container.querySelector("#fsd-volume");
+ volumeBarInner = container.querySelector("#fsd-volume-bar-inner");
+ volumeIcon = container.querySelector("#fsd-volume-icon");
+ volumeIcon && (volumeIcon.onclick = Spicetify.Player.toggleMute);
+ }
+ if (CONFIG[ACTIVE].progressBarDisplay) {
+ prog = container.querySelector("#fsd-progress-inner");
+ durr = container.querySelector("#fsd-duration");
+ elaps = container.querySelector("#fsd-elapsed");
+ }
+ if (CONFIG[ACTIVE].icons) {
+ playingIcon = container.querySelector("#playing-icon");
+
+ //Clicking on playing icon disables it and remembers the config
+ playingIcon && (playingIcon.onclick = () => {
+ CONFIG[ACTIVE]["titleMovingIcon"] = false;
+ saveConfig();
+ playingIcon.classList.add("hidden");
+ pausedIcon.classList.remove("hidden");
+ });
+ pausedIcon = container.querySelector("#paused-icon");
+ pausedIcon && (pausedIcon.onclick = () => {
+ CONFIG[ACTIVE]["titleMovingIcon"] = true;
+ saveConfig();
+ playingIcon.classList.remove("hidden");
+ pausedIcon.classList.add("hidden");
+ updatePlayingIcon({ data: { is_paused: !Spicetify.Player.isPlaying() } });
+ });
+ }
+ if (CONFIG[ACTIVE].playerControls) {
+ play = container.querySelector("#fsd-play");
+ play && (play.onclick = () => {
+ fadeAnimation(play);
+ Spicetify.Player.togglePlay();
+ });
+ nextControl = container.querySelector("#fsd-next");
+ nextControl && (nextControl.onclick = () => {
+ fadeAnimation(nextControl, "fade-ri");
+ Spicetify.Player.next();
+ });
+ backControl = container.querySelector("#fsd-back");
+ backControl && (backControl.onclick = () => {
+ fadeAnimation(backControl, "fade-le");
+ Spicetify.Player.back();
+ });
+ }
+ if (CONFIG[ACTIVE].extraControls) {
+ heart = container.querySelector("#fsd-heart");
+ shuffle = container.querySelector("#fsd-shuffle");
+ repeat = container.querySelector("#fsd-repeat");
+
+ heart && (heart.onclick = () => {
+ fadeAnimation(heart);
+ Spicetify.Player.toggleHeart();
+ });
+ shuffle && (shuffle.onclick = () => {
+ fadeAnimation(shuffle);
+ Spicetify.Player.toggleShuffle();
+ });
+ repeat && (repeat.onclick = () => {
+ fadeAnimation(repeat);
+ Spicetify.Player.toggleRepeat();
+ });
+ if (CONFIG[ACTIVE].invertColors === "d") {
+ invertButton = container.querySelector("#fsd-invert");
+ invertButton && (invertButton.onclick = () => {
+ fadeAnimation(invertButton);
+ if (invertButton.classList.contains("button-active"))
+ invertButton.innerHTML = ``;
+ else
+ invertButton.innerHTML = ``;
+ invertButton.classList.toggle("button-active");
+ if (getComputedStyle(container).getPropertyValue("--main-color").startsWith("0")) {
+ container.style.setProperty("--main-color", "255,255,255");
+ container.style.setProperty("--contrast-color", "0,0,0");
+ if (!CONFIG.tvMode && CONFIG.def.backgroundChoice === "a")
+ INVERTED[Spicetify.Player.data.track?.metadata?.album_uri?.split(":")[2]] = false;
+ } else {
+ container.style.setProperty("--main-color", "0,0,0");
+ container.style.setProperty("--contrast-color", "255,255,255");
+ if (!CONFIG.tvMode && CONFIG.def.backgroundChoice === "a")
+ INVERTED[Spicetify.Player.data.track?.metadata?.album_uri?.split(":")[2]] = true;
+ }
+ localStorage.setItem("full-screen:inverted", JSON.stringify(INVERTED));
+ });
+ }
+ if (CONFIG[ACTIVE].lyricsDisplay) {
+ lyrics = container.querySelector("#fsd-lyrics");
+ lyrics && (lyrics.onclick = () => {
+ fadeAnimation(lyrics);
+ container.classList.toggle("lyrics-hide-force");
+ lyrics.classList.toggle("button-active");
+ lyrics.innerHTML =
+ container.classList.contains("lyrics-unavailable") || container.classList.contains("lyrics-hide-force")
+ ? ``
+ : ``;
+ });
+ }
+ }
+ }
+
+ const classes = ["video", "video-full-screen", "video-full-window", "video-full-screen--hide-ui", "fsd-activated"];
+
+ function fullScreenOn() {
+ if (!document.fullscreen) document.documentElement.requestFullscreen();
+ }
+
+ function fullScreenOff() {
+ if (document.fullscreen) document.exitFullscreen();
+ }
+
+ function getToken() {
+ return Spicetify.Platform.AuthorizationAPI._tokenProvider({
+ preferCached: true,
+ }).then((res) => res.accessToken);
+ }
+
+ async function getTrackInfo(id) {
+ return fetch(`https://api.spotify.com/v1/tracks/${id}`, {
+ headers: {
+ Authorization: `Bearer ${await getToken()}`,
+ },
+ }).then((res) => res.json());
+ // return Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/tracks/${id}`)
+ }
+
+ async function getAlbumInfo(id) {
+ return fetch(`https://api.spotify.com/v1/albums/${id}`, {
+ headers: {
+ Authorization: `Bearer ${await getToken()}`,
+ },
+ }).then((res) => res.json());
+ // return Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/albums/${id}`)
+ }
+
+ function getPlaylistInfo(uri) {
+ return Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}`);
+ }
+
+ async function getArtistInfo(id) {
+ return fetch(
+ `https://api-partner.spotify.com/pathfinder/v1/query?operationName=queryArtistOverview&variables=%7B%22uri%22%3A%22spotify%3Aartist%3A${id}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22d66221ea13998b2f81883c5187d174c8646e4041d67f5b1e103bc262d447e3a0%22%7D%7D`,
+ {
+ headers: {
+ Authorization: `Bearer ${await getToken()}`,
+ },
+ }
+ )
+ .then((res) => res.json())
+ .then((res) => res.data.artist);
+ // return Spicetify.CosmosAsync.get(`https://api-partner.spotify.com/pathfinder/v1/query?operationName=queryArtistOverview&variables=%7B%22uri%22%3A%22spotify%3Aartist%3A${id}%22%7D&extensions=%7B%22persistedQuery%22%3A%7B%22version%22%3A1%2C%22sha256Hash%22%3A%22d66221ea13998b2f81883c5187d174c8646e4041d67f5b1e103bc262d447e3a0%22%7D%7D`).then(res => res.data.artist)
+ }
+
+ async function searchArt(name) {
+ return fetch(`https://api.spotify.com/v1/search?q="${name}"&type=artist&limit=2`, {
+ headers: {
+ Authorization: `Bearer ${await getToken()}`,
+ },
+ }).then((res) => res.json());
+ // return Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/search?q="${name}"&type=artist&limit=2`)
+ }
+
+ // Add fade animation on button click
+ function fadeAnimation(ele, anim = "fade-do") {
+ ele.classList.remove(anim);
+ ele.classList.add(anim);
+ setTimeout(() => {
+ ele.classList.remove(anim);
+ }, 800);
+ }
+
+ // Utility function to add a observer with wait for element support
+ function addObserver(observer, selector, options) {
+ const ele = document.querySelector(selector);
+ if (!ele) {
+ setTimeout(() => {
+ addObserver(observer, selector, options);
+ }, 2000);
+ return;
+ }
+ observer.observe(ele, options);
+ }
+
+ // Converting hex to rgb
+ function hexToRgb(hex) {
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
+ let shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+ hex = hex.replace(shorthandRegex, function (m, r, g, b) {
+ return r + r + g + g + b + b;
+ });
+ let result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return result ? `${parseInt(result[1], 16)},${parseInt(result[2], 16)},${parseInt(result[3], 16)}` : null;
+ }
+
+ async function getImageAndLoad(meta) {
+ if (meta.artist_uri == null) return meta.image_xlarge_url;
+ let arUri = meta.artist_uri.split(":")[2];
+ if (meta.artist_uri.split(":")[1] === "local") {
+ let res = await searchArt(meta.artist_name).catch((err) => console.error(err));
+ arUri = res ? res.artists.items[0].id : "";
+ }
+ let artistInfo = await getArtistInfo(arUri).catch((err) => console.error(err));
+ return artistInfo?.visuals?.headerImage?.sources[0].url ?? meta.image_xlarge_url;
+ }
+
+ // Set the timeout to show upnext or hide when song ends
+ let upnextTimer,
+ upNextShown = false;
+
+ function updateUpNextShow() {
+ setTimeout(() => {
+ let timetogo = getShowTime();
+ if (upnextTimer) {
+ clearTimeout(upnextTimer);
+ upnextTimer = 0;
+ }
+ if (timetogo < 10) {
+ if (!upNextShown || fsd_myUp.style.transform !== "translateX(0px)") {
+ updateUpNext();
+ }
+ upNextShown = true;
+ } else {
+ fsd_myUp.style.transform = "translateX(600px)";
+ upNextShown = false;
+ if (!Spicetify.Player.origin._state.isPaused) {
+ upnextTimer = setTimeout(() => {
+ updateUpNext();
+ upNextShown = true;
+ }, timetogo);
+ }
+ }
+ }, 100);
+ }
+
+ // Return the total time left to show the upnext timer
+ function getShowTime() {
+ let showBefore = CONFIG[ACTIVE].upnextTimeToShow * 1000;
+ let dur = Spicetify.Player.data.duration;
+ let curProg = Spicetify.Player.getProgress();
+
+ if (dur - curProg <= showBefore) return -1;
+ else return dur - showBefore - curProg;
+ }
+
+ let colorsCache = [];
+ async function colorExtractor(uri) {
+ const presentInCache = colorsCache.filter(obj => obj.uri === uri)
+ if(presentInCache.length > 0) return presentInCache[0].colors;
+ const body = await Spicetify.CosmosAsync.get(`wg://colorextractor/v1/extract-presets?uri=${uri}&format=json`);
+ if (body.entries && body.entries.length) {
+ const list = {};
+ for (const color of body.entries[0].color_swatches) {
+ list[color.preset] = `#${color.color.toString(16).padStart(6, "0")}`;
+ }
+ if(colorsCache.length > 20) colorsCache.shift();
+ colorsCache.push({uri, colors: list});
+ return list;
+ }
+ throw "No colors returned.";
+ }
+
+ async function updateInfo() {
+ const meta = Spicetify.Player.data.track?.metadata;
+
+ if (CONFIG[ACTIVE].contextDisplay !== "n") updateContext().catch((err) => console.error("Error getting context: ", err));
+
+ // prepare title
+ let rawTitle = meta?.title;
+ if (CONFIG[ACTIVE].trimTitle) {
+ rawTitle = rawTitle
+ .replace(/\(.+?\)/g, "")
+ .replace(/\[.+?\]/g, "")
+ .replace(/\s\-\s.+?$/, "")
+ .trim();
+ if (!rawTitle) rawTitle = meta?.title;
+ }
+
+ // prepare artist
+ let artistName;
+ if (CONFIG[ACTIVE].showAllArtists) {
+ artistName = Object.keys(meta)
+ .filter((key) => key.startsWith("artist_name"))
+ .sort()
+ .map((key) => meta[key])
+ .join(", ");
+ } else {
+ artistName = meta?.artist_name;
+ }
+
+ // prepare album
+ let albumText;
+ if (CONFIG[ACTIVE].showAlbum !== "n") {
+ albumText = meta?.album_title || "";
+ if (album) album.innerText = albumText || "";
+ const albumURI = meta?.album_uri;
+ if (albumURI?.startsWith("spotify:album:") && CONFIG[ACTIVE].showAlbum === "d") {
+ getAlbumInfo(albumURI.replace("spotify:album:", ""))
+ .then((albumInfo) => {
+ if (!albumInfo?.release_date) throw Error("No release Date");
+ const albumDate = new Date(albumInfo.release_date);
+ const recentDate = new Date();
+ recentDate.setMonth(recentDate.getMonth() - 18);
+ const dateStr = albumDate.toLocaleString(
+ LOCALE,
+ albumDate > recentDate ? { year: "numeric", month: "short" } : { year: "numeric" }
+ );
+ albumText += " • " + dateStr.charAt(0).toUpperCase() + dateStr.slice(1);
+ if (album) album.innerText = albumText || "";
+ })
+ .catch((err) => console.error(err));
+ }
+ }
+
+ // prepare duration
+ let durationText;
+ if (CONFIG[ACTIVE].progressBarDisplay) {
+ durationText = Spicetify.Player.formatTime(meta?.duration);
+ }
+ const previousImg = nextTrackImg.cloneNode();
+
+ nextTrackImg.src = meta?.image_xlarge_url;
+
+ // Wait until next track image is downloaded then update UI text and images
+ nextTrackImg.onload = async () => {
+ if (!CONFIG.tvMode) {
+ updateMainColor(Spicetify.Player.data.track?.uri, meta);
+ updateThemeColor(Spicetify.Player.data.track?.uri);
+ if (CONFIG.def.backgroundChoice == "a") {
+ animateCanvas(previousImg, nextTrackImg);
+ } else {
+ animateColor(await getNextColor());
+ }
+ }
+ cover.style.backgroundImage = `url("${nextTrackImg.src}")`;
+ title.innerText = rawTitle || "";
+ artist.innerText = artistName || "";
+ if (durr) {
+ durr.innerText = durationText || "";
+ }
+ new Image().src = Spicetify?.Queue?.nextTracks[0]?.contextTrack?.metadata.image_xlarge_url;
+ };
+ nextTrackImg.onerror = () => {
+ // Placeholder
+ console.error("Check your Internet! Unable to load Image");
+ nextTrackImg.src = OFFLINESVG;
+ };
+ if (CONFIG.tvMode) {
+ artistImg.src = await getImageAndLoad(meta);
+ updateMainColor(artistImg.src, meta);
+ updateThemeColor(artistImg.src);
+ artistImg.onload = async () => {
+ back.style.backgroundImage = `url("${artistImg.src}")`;
+ let newurl = await getImageAndLoad(Spicetify?.Queue?.nextTracks[0]?.contextTrack?.metadata);
+ new Image().src = newurl;
+ };
+ }
+ }
+
+ async function getNextColor() {
+ let nextColor;
+ const imageColors = await colorExtractor(Spicetify.Player.data.track?.uri).catch((err) => console.warn(err));
+ if (!imageColors || !imageColors[CONFIG.def.coloredBackChoice]) nextColor = "#444444";
+ else nextColor = imageColors[CONFIG.def.coloredBackChoice];
+ return nextColor;
+ }
+ async function updateMainColor(imageURL, meta) {
+ switch (CONFIG[ACTIVE].invertColors) {
+ case "a":
+ container.style.setProperty("--main-color", "0,0,0");
+ container.style.setProperty("--contrast-color", "255,255,255");
+ break;
+ case "d":
+ let mainColor, contrastColor;
+ if (!CONFIG.tvMode && CONFIG.def.backgroundChoice === "a" && meta.album_uri.split(":")[2] in INVERTED) {
+ mainColor = INVERTED[meta.album_uri.split(":")[2]] ? "0,0,0" : "255,255,255";
+ } else {
+ let imageProminentColor;
+ const imageColors = await colorExtractor(imageURL).catch((err) => console.warn(err));
+ if (CONFIG.tvMode || CONFIG.def.backgroundChoice == "a") {
+ if (!imageColors?.PROMINENT) imageProminentColor = "0,0,0";
+ else imageProminentColor = hexToRgb(imageColors.PROMINENT);
+ } else {
+ if (!imageColors || !imageColors[CONFIG.def.coloredBackChoice]) imageProminentColor = hexToRgb("#444444");
+ else imageProminentColor = hexToRgb(imageColors[CONFIG.def.coloredBackChoice]);
+ }
+ const thresholdValue = 260 - CONFIG[ACTIVE].backgroundBrightness * 100;
+ const isLightBG =
+ Number(imageProminentColor?.split(",")[0]) * 0.299 +
+ Number(imageProminentColor?.split(",")[1]) * 0.587 +
+ Number(imageProminentColor?.split(",")[2]) * 0.114 >
+ thresholdValue;
+ mainColor = isLightBG && CONFIG[ACTIVE].backgroundBrightness > 0.3 ? "0,0,0" : "255,255,255";
+ contrastColor = isLightBG && CONFIG[ACTIVE].backgroundBrightness > 0.3 ? "255,255,255" : "0,0,0";
+ }
+ container.style.setProperty("--main-color", mainColor);
+ container.style.setProperty("--contrast-color", contrastColor ?? "0,0,0");
+ if (CONFIG[ACTIVE].extraControls) {
+ invertButton.classList.remove("button-active");
+ invertButton.innerHTML = ``;
+ }
+ break;
+ case "n":
+ default:
+ container.style.setProperty("--main-color", "255,255,255");
+ container.style.setProperty("--contrast-color", "0,0,0");
+ break;
+ }
+ }
+ //Set main theme color for the display
+ async function updateThemeColor(imageURL) {
+ if (
+ !(!CONFIG.tvMode && CONFIG.def.backgroundChoice == "c" && CONFIG.def.coloredBackChoice == "VIBRANT") &&
+ (CONFIG[ACTIVE].themedButtons || CONFIG[ACTIVE].themedIcons)
+ ) {
+ container.classList.toggle("themed-buttons", !!CONFIG[ACTIVE].themedButtons);
+ container.classList.toggle("themed-icons", !!CONFIG[ACTIVE].themedIcons);
+ let themeVibrantColor;
+ const artColors = await colorExtractor(imageURL).catch((err) => console.warn(err));
+ if (!artColors?.VIBRANT) themeVibrantColor = "175,175,175";
+ else themeVibrantColor = hexToRgb(artColors.VIBRANT);
+ container.style.setProperty("--theme-color", themeVibrantColor);
+ } else {
+ container.classList.remove("themed-buttons", "themed-icons");
+ container.style.setProperty("--theme-color", "175,175,175");
+ }
+ }
+
+ function handleLyricsUpdate(evt) {
+ if (evt.detail.isLoading) return;
+ container.classList.toggle("lyrics-unavailable", !(evt.detail.available && evt.detail?.synced?.length > 1));
+ if (CONFIG[ACTIVE].extraControls) {
+ lyrics.classList.toggle("hidden", container.classList.contains("lyrics-unavailable"));
+ }
+ }
+
+ let prevColor = "#000000";
+ async function animateColor(nextColor) {
+ const configTransitionTime = CONFIG[ACTIVE].backAnimationTime;
+ const { innerWidth: width, innerHeight: height } = window;
+ back.width = width;
+ back.height = height;
+
+ const ctx = back.getContext("2d");
+
+ if (!CONFIG[ACTIVE].enableFade) {
+ ctx.globalAlpha = 1;
+ ctx.fillStyle = nextColor;
+ ctx.fillRect(0, 0, width, height);
+ return;
+ }
+
+ let previousTimeStamp,
+ done = false,
+ start;
+ const animate = (timestamp) => {
+ if (start === undefined) start = timestamp;
+ const elapsed = timestamp - start;
+
+ if (previousTimeStamp !== timestamp) {
+ const factor = Math.min(elapsed / (configTransitionTime * 1000), 1.0);
+ ctx.globalAlpha = 1;
+ ctx.fillStyle = prevColor;
+ ctx.fillRect(0, 0, width, height);
+ ctx.globalAlpha = Math.sin((Math.PI / 2) * factor);
+ ctx.fillStyle = nextColor;
+ ctx.fillRect(0, 0, width, height);
+ if (factor === 1.0) done = true;
+ }
+ if (elapsed < configTransitionTime * 1000) {
+ previousTimeStamp = timestamp;
+ !done && requestAnimationFrame(animate);
+ } else {
+ prevColor = nextColor;
+ }
+ };
+
+ requestAnimationFrame(animate);
+ }
+
+ function animateCanvas(prevImg, nextImg) {
+ const configTransitionTime = CONFIG[ACTIVE].backAnimationTime;
+ const { innerWidth: width, innerHeight: height } = window;
+ back.width = width;
+ back.height = height;
+
+ const ctx = back.getContext("2d");
+ ctx.imageSmoothingEnabled = false;
+ ctx.filter = `brightness(${CONFIG[ACTIVE].backgroundBrightness}) blur(${CONFIG[ACTIVE].blurSize}px)`;
+ const blur = CONFIG[ACTIVE].blurSize;
+
+ const x = -blur * 2;
+
+ let y, dim;
+ if (width > height) {
+ dim = width;
+ y = x - (width - height) / 2;
+ } else {
+ dim = height;
+ y = x;
+ }
+ const size = dim + 4 * blur;
+
+ if (!CONFIG[ACTIVE].enableFade) {
+ ctx.globalAlpha = 1;
+ ctx.drawImage(nextImg, x, y, size, size);
+ return;
+ }
+
+ let prevTimeStamp,
+ start,
+ done = false;
+
+ const animate = (timestamp) => {
+ if (start === undefined) start = timestamp;
+
+ const elapsed = timestamp - start;
+
+ if (prevTimeStamp !== timestamp) {
+ const factor = Math.min(elapsed / (configTransitionTime * 1000), 1.0);
+ ctx.globalAlpha = 1;
+ ctx.drawImage(prevImg, x, y, size, size);
+ ctx.globalAlpha = Math.sin((Math.PI / 2) * factor);
+ ctx.drawImage(nextImg, x, y, size, size);
+ if (factor === 1.0) done = true;
+ }
+ if (elapsed < configTransitionTime * 1000) {
+ prevTimeStamp = timestamp;
+ !done && requestAnimationFrame(animate);
+ }
+ };
+
+ requestAnimationFrame(animate);
+ }
+
+ let prevUriObj;
+ async function getContext() {
+ let ctxIcon = "",
+ ctxSource,
+ ctxName;
+ if (Spicetify.Player.data.track?.provider === "queue") {
+ ctxIcon = ``;
+ ctxSource = translations[LOCALE].context.queue;
+ ctxName = "";
+ } else {
+ const uriObj = Spicetify.URI.fromString(Spicetify.Player.data.context_uri);
+ if (JSON.stringify(uriObj) === JSON.stringify(prevUriObj) && ctxSource != undefined && ctxName != undefined)
+ return [ctxIcon, ctxSource, ctxName];
+ prevUriObj = uriObj;
+ switch (uriObj.type) {
+ case Spicetify.URI.Type.TRACK:
+ ctxIcon = ``;
+ ctxSource = translations[LOCALE].context.track;
+ await getTrackInfo(uriObj._base62Id).then((meta) => (ctxName = `${meta.name} • ${meta.artists[0].name}`));
+ break;
+ case Spicetify.URI.Type.SEARCH:
+ ctxIcon = Spicetify.SVGIcons["search-active"];
+ ctxSource = translations[LOCALE].context.search;
+ ctxName = `"${uriObj.query}" in ${translations[LOCALE].context.searchDest}`;
+ break;
+ case Spicetify.URI.Type.COLLECTION:
+ ctxIcon = Spicetify.SVGIcons["heart-active"];
+ ctxSource = translations[LOCALE].context.collection;
+ ctxName = translations[LOCALE].context.likedSongs;
+ break;
+ case Spicetify.URI.Type.PLAYLIST_V2:
+ ctxIcon = Spicetify.SVGIcons["playlist"];
+ ctxSource = translations[LOCALE].context.playlist;
+ ctxName = Spicetify.Player.data.context_metadata?.context_description || "";
+ break;
+
+ case Spicetify.URI.Type.STATION:
+ case Spicetify.URI.Type.RADIO:
+ ctxIcon = ``;
+ const rType = uriObj.args[0];
+ switch (rType) {
+ case "album":
+ ctxSource = translations[LOCALE].context.albumRadio;
+ await getAlbumInfo(uriObj.args[1]).then((meta) => (ctxName = meta.name));
+ break;
+ case "track":
+ ctxSource = translations[LOCALE].context.trackRadio;
+ await getTrackInfo(uriObj.args[1]).then((meta) => (ctxName = `${meta.name} • ${meta.artists[0].name}`));
+ break;
+ case "artist":
+ ctxSource = translations[LOCALE].context.artistRadio;
+ await getArtistInfo(uriObj.args[1]).then((meta) => (ctxName = meta?.profile?.name));
+ break;
+ case "playlist":
+ case "playlist-v2":
+ ctxSource = translations[LOCALE].context.playlistRadio;
+ ctxIcon = ``;
+ await getPlaylistInfo("spotify:playlist:" + uriObj.args[1]).then((meta) => (ctxName = meta.playlist.name));
+ break;
+ default:
+ ctxName = "";
+ }
+ break;
+
+ case Spicetify.URI.Type.PLAYLIST:
+ ctxIcon = Spicetify.SVGIcons[uriObj.type];
+ ctxSource = translations[LOCALE].context.playlist;
+ ctxName = Spicetify.Player.data.context_metadata.context_description || "";
+ break;
+ case Spicetify.URI.Type.ALBUM:
+ ctxIcon = Spicetify.SVGIcons[uriObj.type];
+ ctxSource = translations[LOCALE].context.album;
+ ctxName = Spicetify.Player.data.context_metadata.context_description || "";
+ break;
+
+ case Spicetify.URI.Type.ARTIST:
+ ctxIcon = Spicetify.SVGIcons[uriObj.type];
+ ctxSource = translations[LOCALE].context.artist;
+ ctxName = Spicetify.Player.data.context_metadata.context_description || "";
+ break;
+
+ case Spicetify.URI.Type.FOLDER:
+ ctxIcon = Spicetify.SVGIcons["playlist-folder"];
+ ctxSource = translations[LOCALE].context.playlistFolder;
+ const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/rootlist`, {
+ policy: { folder: { rows: true, link: true, name: true } },
+ });
+ for (const item of res.rows) {
+ if (item.type === "folder" && item.link === Spicetify.Player.data.context_uri) {
+ ctxName = item.name;
+ break;
+ }
+ }
+ break;
+ default:
+ ctxSource = uriObj.type;
+ ctxName = Spicetify.Player.data?.context_metadata?.context_description || "";
+ }
+ }
+ return [ctxIcon, ctxSource, ctxName];
+ }
+
+ // Get the context and update it
+ async function updateContext() {
+ let ctxIcon, ctxSource, ctxName;
+ [ctxIcon, ctxSource, ctxName] = await getContext().catch((err) => console.error(err));
+ ctx_source.classList.toggle("ctx-no-name", !ctxName);
+
+ //Set default icon if no icon is returned
+ if (!ctxIcon) ctxIcon = Spicetify.SVGIcons.spotify;
+ ctx_icon.innerHTML = /^${ctxIcon}`
+ : ctxIcon;
+
+ //Only change the DOM if context is changed
+ if (ctx_source.innerText.toLowerCase() !== `${ctxSource}`.toLowerCase() || ctx_name.innerText.toLowerCase() !== ctxName.toLowerCase()) {
+ ctx_source.innerText = `${ctxSource}`;
+ ctx_name.innerText = ctxName;
+ if (CONFIG[ACTIVE].contextDisplay === "m") hideContext();
+ }
+ }
+
+ function updateUpNextInfo() {
+ fsd_up_next_text.innerText = translations[LOCALE].upnext.toUpperCase();
+ let metadata = {};
+ const queue_metadata = Spicetify.Queue.nextTracks[0];
+ if (queue_metadata) {
+ metadata = queue_metadata?.contextTrack?.metadata;
+ } else {
+ metadata["artist_name"] = "";
+ metadata["title"] = "";
+ }
+
+ let rawTitle = metadata.title;
+ if (CONFIG[ACTIVE].trimTitleUpNext) {
+ rawTitle = rawTitle
+ .replace(/\(.+?\)/g, "")
+ .replace(/\[.+?\]/g, "")
+ .replace(/\s\-\s.+?$/, "")
+ .trim();
+ if (!rawTitle) rawTitle = metadata.title;
+ }
+ const artistNameNext = Object.keys(metadata)
+ .filter((key) => key.startsWith("artist_name"))
+ .sort()
+ .map((key) => metadata[key])
+ .join(", ");
+
+ let next_artist;
+ if (artistNameNext) {
+ next_artist = artistNameNext;
+ } else {
+ next_artist = translations[LOCALE].unknownArtist;
+ }
+ const next_image = metadata.image_xlarge_url;
+ if (next_image) {
+ fsd_nextCover.style.backgroundImage = `url("${next_image}")`;
+ } else {
+ if (metadata.image_url) fsd_nextCover.style.backgroundImage = `url("${metadata.image_url}")`;
+ else {
+ fsd_nextCover.style.backgroundImage = `url("${OFFLINESVG}")`;
+ }
+ }
+ fsd_first_span.innerText = rawTitle + " • " + next_artist;
+ fsd_second_span.innerText = rawTitle + " • " + next_artist;
+ }
+
+ async function updateUpNext() {
+ if (
+ Spicetify.Player.data.duration - Spicetify.Player.getProgress() <= CONFIG[ACTIVE].upnextTimeToShow * 1000 + 50 &&
+ Spicetify.Queue?.nextTracks[0]?.contextTrack?.metadata?.title
+ ) {
+ await updateUpNextInfo();
+ fsd_myUp.style.transform = "translateX(0px)";
+ upNextShown = true;
+ let animTime;
+ if (fsd_second_span.offsetWidth > fsd_next_tit_art.offsetWidth - 2) {
+ switch (CONFIG[ACTIVE].upNextAnim) {
+ case "mq":
+ fsd_first_span.style.paddingRight = "80px";
+ animTime = 5000 * (fsd_first_span.offsetWidth / 400);
+ fsd_myUp.style.setProperty("--translate_width_fsd", `-${fsd_first_span.offsetWidth + 3.5}px`);
+ fsd_next_tit_art_inner.style.animation = "fsd_cssmarquee " + animTime + "ms linear 800ms infinite";
+ break;
+ case "sp":
+ default:
+ fsd_first_span.style.paddingRight = "0px";
+ fsd_second_span.innerText = "";
+ animTime = (fsd_first_span.offsetWidth - fsd_next_tit_art.offsetWidth - 2) / 0.05;
+ // animTime= 3000*(fsd_first_span.offsetWidth/fsd_next_tit_art.offsetWidth)
+ fsd_myUp.style.setProperty("--translate_width_fsd", `-${fsd_first_span.offsetWidth - fsd_next_tit_art.offsetWidth + 5}px`);
+ fsd_next_tit_art_inner.style.animation = `fsd_translate ${animTime > 1500 ? animTime : 1500}ms linear 800ms infinite`;
+ break;
+ }
+ } else {
+ fsd_first_span.style.paddingRight = "0px";
+ fsd_next_tit_art_inner.style.animation = "none";
+ fsd_second_span.innerText = "";
+ }
+ } else {
+ upNextShown = false;
+ fsd_myUp.style.transform = "translateX(600px)";
+ fsd_first_span.style.paddingRight = "0px";
+ fsd_next_tit_art_inner.style.animation = "none";
+ fsd_second_span.innerText = "";
+ }
+ }
+
+ let prevVolume = Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume;
+
+ function updateVolume(data) {
+ const volume = !data ? Spicetify.Player?.origin?._volume?._volume ?? Spicetify.Platform?.PlaybackAPI?._volume : data.data.volume;
+ if (volume !== prevVolume || !data) {
+ //Only update volume when there is a change or on initial fire
+ prevVolume = volume;
+ if (CONFIG[ACTIVE].volumeDisplay === "o" || CONFIG[ACTIVE].volumeDisplay === "m") {
+ volumeContainer.classList.remove("v-hidden");
+ }
+ volumeBarInner.style.height = volume * 100 + "%";
+ let currVol = Math.round(volume * 100) === -100 ? "%" : Math.round(volume * 100);
+ volumeCurr.innerText = currVol + "%";
+ volumeContainer.classList.toggle("unavailable", typeof currVol !== "number");
+ if (typeof currVol !== "number" || currVol > 60)
+ volumeIcon.innerHTML = ``;
+ else if (currVol > 30)
+ volumeIcon.innerHTML = ``;
+ else if (currVol > 0)
+ volumeIcon.innerHTML = ``;
+ else
+ volumeIcon.innerHTML = ``;
+ if (CONFIG[ACTIVE].volumeDisplay === "o" || CONFIG[ACTIVE].volumeDisplay === "m") {
+ hideVolume();
+ }
+ }
+ }
+
+ function updateProgress() {
+ const progress = Spicetify.Player.formatTime(Spicetify.Player.getProgress());
+ if (!Spicetify.Player.origin._state.isPaused || elaps.innerText !== progress) {
+ prog.style.width = Spicetify.Player.getProgressPercent() * 100 + "%";
+ elaps.innerText = progress;
+ }
+ }
+
+ function updatePlayingIcon({ data }) {
+ if (data.is_paused) {
+ pausedIcon.classList.remove("hidden");
+ playingIcon.classList.add("hidden");
+ } else {
+ pausedIcon.classList.toggle("hidden", CONFIG[ACTIVE].titleMovingIcon);
+ playingIcon.classList.toggle("hidden", !CONFIG[ACTIVE].titleMovingIcon);
+ }
+ }
+
+ function updatePlayerControls({ data }) {
+ fadeAnimation(play);
+ if (data.is_paused) {
+ play.innerHTML = ``;
+ } else {
+ play.innerHTML = ``;
+ }
+ }
+
+ let prevControlData = {
+ shuffle: Spicetify?.Player?.origin?._state?.shuffle,
+ repeat: Spicetify?.Player?.origin?._state?.repeat,
+ };
+
+ function updateExtraControls(data) {
+ data = !data ? Spicetify.Player.origin._state : data.data;
+ updateHeart();
+ if (prevControlData?.shuffle !== data?.shuffle) fadeAnimation(shuffle);
+ if (prevControlData?.repeat !== data?.repeat) fadeAnimation(repeat);
+ prevControlData = {
+ shuffle: data?.shuffle,
+ repeat: data?.repeat,
+ };
+ repeat.classList.toggle("dot-after", data?.repeat !== 0);
+ repeat.classList.toggle("button-active", data?.repeat !== 0);
+
+ shuffle.classList.toggle("dot-after", data?.shuffle);
+ shuffle.classList.toggle("button-active", data?.shuffle);
+ if (data?.repeat === 2) {
+ repeat.innerHTML = ``;
+ } else {
+ repeat.innerHTML = ``;
+ }
+ if (data.restrictions) {
+ shuffle.classList.toggle("unavailable", !data?.restrictions?.canToggleShuffle);
+ repeat.classList.toggle("unavailable", !data?.restrictions?.canToggleRepeatTrack && !data?.restrictions?.canToggleRepeatContext);
+ }
+ }
+
+ let prevHeartData = Spicetify?.Player?.origin?._state?.item?.metadata["collection.in_collection"];
+
+ function updateHeart() {
+ const meta = Spicetify?.Player?.origin?._state?.item;
+ heart.classList.toggle("unavailable", meta?.metadata["collection.can_add"] !== "true");
+ if (prevHeartData !== meta?.metadata["collection.in_collection"]) fadeAnimation(heart);
+ prevHeartData = meta?.metadata["collection.in_collection"];
+ if (meta?.metadata["collection.in_collection"] === "true" || Spicetify.Player.getHeart()) {
+ heart.innerHTML = ``;
+ heart.classList.add("button-active");
+ } else {
+ heart.innerHTML = ``;
+ heart.classList.remove("button-active");
+ }
+ }
+
+ let curTimer, ctxTimer, volTimer;
+
+ function hideCursor() {
+ if (curTimer) {
+ clearTimeout(curTimer);
+ curTimer = 0;
+ }
+ container.style.cursor = "default";
+ curTimer = setTimeout(() => (container.style.cursor = "none"), 2000);
+ }
+
+ function hideContext() {
+ if (ctxTimer) {
+ clearTimeout(ctxTimer);
+ ctxTimer = 0;
+ }
+ ctx_container.style.opacity = 1;
+ ctxTimer = setTimeout(() => (ctx_container.style.opacity = 0), 3000);
+ }
+
+ function hideVolume() {
+ if (volTimer) {
+ clearTimeout(volTimer);
+ volTimer = 0;
+ }
+ volumeContainer.classList.remove("v-hidden");
+ volTimer = setTimeout(() => volumeContainer.classList.add("v-hidden"), 3000);
+ }
+
+ let origLoc, progressListener;
+ const heartObserver = new MutationObserver(updateHeart);
+
+ function activate() {
+ container.style.setProperty("--fs-transition", `${CONFIG[ACTIVE].backAnimationTime}s`);
+ updateInfo();
+ Spicetify.Player.addEventListener("songchange", updateInfo);
+ container.addEventListener("mousemove", hideCursor);
+ hideCursor();
+ container.querySelector("#fsd-foreground").oncontextmenu = openConfig;
+ container.querySelector("#fsd-foreground").ondblclick = deactivate;
+ back.oncontextmenu = openConfig;
+ back.ondblclick = deactivate;
+ if (CONFIG[ACTIVE].contextDisplay === "m") {
+ container.addEventListener("mousemove", hideContext);
+ hideContext();
+ }
+ if (CONFIG[ACTIVE].upnextDisplay) {
+ updateUpNextShow();
+ Spicetify.Player.origin._events.addListener("queue_update", updateUpNext);
+ Spicetify.Player.origin._events.addListener("update", updateUpNextShow);
+ window.addEventListener("resize", updateUpNext);
+ }
+ if (CONFIG[ACTIVE].volumeDisplay !== "n") {
+ if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.addListener("volume", updateVolume);
+ else Spicetify.Platform.PlaybackAPI._events.addListener("volume", updateVolume);
+ updateVolume();
+ if (CONFIG[ACTIVE].volumeDisplay === "m") {
+ container.addEventListener("mousemove", hideVolume);
+ hideVolume();
+ }
+ }
+ if (CONFIG[ACTIVE].enableFade) {
+ cover.classList.add("fsd-background-fade");
+ if (CONFIG.tvMode) back.classList.add("fsd-background-fade");
+ } else {
+ cover.classList.remove("fsd-background-fade");
+ if (CONFIG.tvMode) back.classList.remove("fsd-background-fade");
+ }
+ if (CONFIG[ACTIVE].icons) {
+ updatePlayingIcon({ data: { is_paused: !Spicetify.Player.isPlaying() } });
+ Spicetify.Player.addEventListener("onplaypause", updatePlayingIcon);
+ }
+ if (CONFIG[ACTIVE].progressBarDisplay) {
+ updateProgress();
+ progressListener = setInterval(updateProgress, 500);
+ }
+ if (CONFIG[ACTIVE].playerControls) {
+ updatePlayerControls({ data: { is_paused: !Spicetify.Player.isPlaying() } });
+ Spicetify.Player.addEventListener("onplaypause", updatePlayerControls);
+ }
+ if (CONFIG[ACTIVE].extraControls) {
+ updateExtraControls();
+ addObserver(heartObserver, ".control-button-heart", { attributes: true, attributeFilter: ["aria-checked"] });
+ Spicetify.Player.origin._events.addListener("update", updateExtraControls);
+ }
+ document.body.classList.add(...classes);
+ if (CONFIG[ACTIVE].enableFullscreen) fullScreenOn();
+ else fullScreenOff();
+ document.querySelector(".Root__top-container")?.append(style, container);
+ if (CONFIG[ACTIVE].lyricsDisplay) {
+ window.addEventListener("lyrics-plus-update", handleLyricsUpdate);
+ origLoc = Spicetify.Platform.History.location.pathname;
+ if (origLoc !== "/lyrics-plus") {
+ Spicetify.Platform.History.push("/lyrics-plus");
+ }
+ window.dispatchEvent(new Event("fad-request"));
+ }
+ Spicetify.Keyboard.registerShortcut(
+ {
+ key: Spicetify.Keyboard.KEYS["F11"],
+ ctrl: false,
+ shift: false,
+ alt: false,
+ },
+ fsToggle
+ );
+ Spicetify.Keyboard.registerShortcut(
+ {
+ key: Spicetify.Keyboard.KEYS["ESCAPE"],
+ ctrl: false,
+ shift: false,
+ alt: false,
+ },
+ deactivate
+ );
+ }
+
+ function deactivate() {
+ Spicetify.Player.removeEventListener("songchange", updateInfo);
+ container.removeEventListener("mousemove", hideCursor);
+ if (CONFIG[ACTIVE].contextDisplay === "m") {
+ container.removeEventListener("mousemove", hideContext);
+ }
+ if (CONFIG[ACTIVE].upnextDisplay) {
+ upNextShown = false;
+ Spicetify.Player.origin._events.removeListener("queue_update", updateUpNext);
+ Spicetify.Player.origin._events.removeListener("update", updateUpNextShow);
+ window.removeEventListener("resize", updateUpNext);
+ }
+ if (CONFIG[ACTIVE].volumeDisplay !== "n") {
+ if (Spicetify.Platform?.PlaybackAPI === undefined) Spicetify.Player.origin._events.removeListener("volume", updateVolume);
+ else Spicetify.Platform.PlaybackAPI._events.removeListener("volume", updateVolume);
+ if (CONFIG[ACTIVE].volumeDisplay === "m") container.removeEventListener("mousemove", hideVolume);
+ }
+ if (CONFIG[ACTIVE].progressBarDisplay) {
+ clearInterval(progressListener);
+ }
+ if (CONFIG[ACTIVE].icons) {
+ Spicetify.Player.removeEventListener("onplaypause", updatePlayingIcon);
+ }
+ if (CONFIG[ACTIVE].playerControls) {
+ Spicetify.Player.removeEventListener("onplaypause", updatePlayerControls);
+ }
+ if (CONFIG[ACTIVE].extraControls) {
+ heartObserver.disconnect();
+ Spicetify.Player.origin._events.removeListener("update", updateExtraControls);
+ }
+ document.body.classList.remove(...classes);
+ upNextShown = false;
+ if (CONFIG[ACTIVE].enableFullscreen) {
+ fullScreenOff();
+ }
+ let popup = document.querySelector("body > generic-modal");
+ if (popup) popup.remove();
+ style.remove();
+ container.remove();
+ if (CONFIG[ACTIVE].lyricsDisplay) {
+ window.removeEventListener("lyrics-plus-update", handleLyricsUpdate);
+ if (origLoc !== "/lyrics-plus") {
+ Spicetify.Platform.History.push(origLoc);
+ Spicetify.Platform.History.entries.splice(Spicetify.Platform.History.entries.length - 3, 2);
+ Spicetify.Platform.History.index = Spicetify.Platform.History.index > 0 ? Spicetify.Platform.History.index - 2 : -1;
+ Spicetify.Platform.History.length = Spicetify.Platform.History.length > 1 ? Spicetify.Platform.History.length - 2 : 0;
+ }
+ window.dispatchEvent(new Event("fad-request"));
+ }
+ Spicetify.Keyboard._deregisterShortcut({
+ key: Spicetify.Keyboard.KEYS["F11"],
+ ctrl: false,
+ shift: false,
+ alt: false,
+ });
+ Spicetify.Keyboard._deregisterShortcut({
+ key: Spicetify.Keyboard.KEYS["ESCAPE"],
+ ctrl: false,
+ shift: false,
+ alt: false,
+ });
+ }
+
+ function fsToggle() {
+ if (CONFIG[ACTIVE].enableFullscreen) {
+ CONFIG[ACTIVE]["enableFullscreen"] = false;
+ saveConfig();
+ render();
+ activate();
+ } else {
+ CONFIG[ACTIVE]["enableFullscreen"] = true;
+ saveConfig();
+ render();
+ activate();
+ }
+ }
+
+ function getAvailableLanguages(translations) {
+ let languages = {};
+ for (const lang in translations) {
+ languages[lang] = translations[lang].langName;
+ }
+ return languages;
+ }
+
+ /**
+ * Merges keys from one object into another recursively.
+ */
+ function mergeDefaultsInConfig(defaultObject, objectToMergeIn) {
+ Object.keys(defaultObject).forEach((key) => {
+ if (objectToMergeIn[key] === undefined) {
+ objectToMergeIn[key] = defaultObject[key];
+ } else {
+ if (typeof defaultObject[key] === "object") {
+ mergeDefaultsInConfig(defaultObject[key], objectToMergeIn[key]);
+ }
+ }
+ });
+ }
+ function getConfig() {
+ try {
+ const parsed = JSON.parse(localStorage.getItem("full-screen-config") ?? "{}");
+ if (!!parsed && typeof parsed === "object") {
+ // mergeDefaultsInConfig(DEFAULTS, parsed);
+ Object.keys(DEFAULTS).forEach((key) => {
+ if (parsed[key] === undefined) {
+ parsed[key] = DEFAULTS[key];
+ } else {
+ if (typeof DEFAULTS[key] === "object") {
+ Object.keys(DEFAULTS[key])
+ .filter((subkey) => parsed[key][subkey] === undefined)
+ .forEach((subkey) => {
+ parsed[key][subkey] = DEFAULTS[key][subkey];
+ });
+ }
+ }
+ });
+ localStorage.setItem("full-screen-config", JSON.stringify(parsed));
+ return parsed;
+ }
+ throw "";
+ } catch {
+ localStorage.setItem("full-screen-config", JSON.stringify(DEFAULTS));
+ return DEFAULTS;
+ }
+ }
+
+ function saveConfig() {
+ localStorage.setItem("full-screen-config", JSON.stringify(CONFIG));
+ }
+
+ function saveOption(key, value) {
+ CONFIG[ACTIVE][key] = value;
+ saveConfig();
+ render();
+ if (document.body.classList.contains("fsd-activated")) activate();
+ }
+
+ // Saves an option independent from TV or Fullscreen mode
+ function saveGlobalOption(key, value) {
+ CONFIG[key] = value;
+ LOCALE = CONFIG.locale; // Update locale (avoids reloading client to apply setting)
+ saveConfig();
+ render();
+ if (document.body.classList.contains("fsd-activated")) activate();
+ }
+
+ function createAdjust(name, key, unit = "", defaultValue, step, min, max, onChange = (val) => {}) {
+ let value = key in CONFIG[ACTIVE] ? CONFIG[ACTIVE][key] : defaultValue;
+
+ function adjustValue(dir) {
+ let temp = value + dir * step;
+ if (temp < min) {
+ temp = min;
+ } else if (temp > max) {
+ temp = max;
+ }
+ value = Number(Number(temp).toFixed(step >= 1 ? 0 : 1));
+ container.querySelector(".adjust-value").innerText = `${value}${unit}`;
+ plus && plus.classList.toggle("disabled", value === max);
+ minus && minus.classList.toggle("disabled", value === min);
+ onChange(value);
+ }
+ const container = document.createElement("div");
+ container.innerHTML = `
+
+
+
+
+
${value}${unit}
+
+
+
+ `;
+ const minus = container.querySelector(".minus");
+ const plus = container.querySelector(".plus");
+ minus && minus.classList.toggle("disabled", value === min);
+ plus && plus.classList.toggle("disabled", value === max);
+ minus && (minus.onclick = () => adjustValue(-1));
+ plus && (plus.onclick = () => adjustValue(1));
+ return container;
+ }
+
+ function createOptions(name, options, defaultValue, callback) {
+ const container = document.createElement("div");
+ container.innerHTML = `
+
+
+
+
+
+
`;
+ const select = container.querySelector("select");
+ select && (select.value = defaultValue);
+ select && (select.onchange = (e) => {
+ callback(e?.target?.value);
+ });
+ return container;
+ }
+
+ function createToggle(name, key, callback = (a,b) => {}) {
+ const container = document.createElement("div");
+ container.innerHTML = `
+ `;
+
+ const toggle = container.querySelector("input");
+ toggle && (toggle.checked = CONFIG[ACTIVE][key]);
+ toggle && (toggle.onchange = (evt) => {
+ CONFIG[ACTIVE][key] = evt?.target?.checked;
+ saveConfig();
+ render();
+ callback(container, evt?.target?.checked);
+ if (document.body.classList.contains("fsd-activated")) {
+ activate();
+ }
+ });
+ return container;
+ }
+
+ function headerText(text, subtext = "") {
+ const container = document.createElement("div");
+ container.classList.add("subhead");
+ const listHeader = document.createElement("h2");
+ listHeader.innerText = text;
+ container.append(listHeader);
+ if (subtext) {
+ const listSub = document.createElement("i");
+ listSub.innerText = "(" + subtext + ")";
+ container.append(listSub);
+ }
+ return container;
+ }
+ let configContainer;
+
+ function openConfig(evt) {
+ evt?.preventDefault();
+ configContainer = document.createElement("div");
+ configContainer.id = "full-screen-config-container";
+ const style = document.createElement("style");
+ style.innerHTML = `
+ .GenericModal ::-webkit-scrollbar{
+ width: 7px;
+ }
+ .GenericModal ::-webkit-scrollbar-thumb{
+ border-radius: 2rem;
+ }
+ .main-trackCreditsModal-container{
+ height: 75vh;
+ max-height: 600px;
+ width: clamp(500px,50vw,600px);
+ }
+ .setting-row::after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+ .setting-row-but{
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ .subhead{
+ text-align: center;
+ padding: 5px 0;
+ }
+ .setting-row-but button{
+ margin: 5px 10px;
+ }
+ .setting-row .col {
+ display: flex;
+ padding: 10px 0;
+ align-items: center;
+ }
+ .setting-row .col.description {
+ float: left;
+ padding-right: 15px;
+ }
+ .setting-row .col.action {
+ float: right;
+ text-align: right;
+ }
+ .switch {
+ position: relative;
+ display: inline-block;
+ width: 44px;
+ height: 20px;
+ }
+ .switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+ }
+ .slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(150,150,150,.5);
+ transition: all .3s ease-in-out;
+ border-radius: 34px;
+ }
+ .slider:before {
+ position: absolute;
+ content: "";
+ height: 24px;
+ width: 24px;
+ left: -2px;
+ bottom: -2px;
+ background-color: #EEE;
+ transition: all .3s ease-in-out;
+ border-radius: 50%;
+ }
+ input:checked + .slider {
+ background-color: rgba(var(--spice-rgb-button-disabled),.6);
+ }
+ input:focus + .slider {
+ box-shadow: 0 0 1px rgba(var(--spice-rgb-button-disabled),.6);
+ }
+ input:checked + .slider:before {
+ transform: translateX(24px);
+ background-color: var(--spice-button);
+ filter: brightness(1.1) saturate(1.2);
+ }
+ #full-screen-config-container select {
+ color: var(--spice-text);
+ background: var(--spice-card);
+ border: none;
+ height: 32px;
+ }
+ #full-screen-config-container option {
+ color: var(--spice-text);
+ background: var(--spice-card);
+ }
+ button.switch {
+ align-items: center;
+ border: 0px;
+ border-radius: 50%;
+ background-color: rgba(var(--spice-rgb-shadow), 0.7);
+ color: var(--spice-text);
+ cursor: pointer;
+ margin-inline-start: 12px;
+ padding: 8px;
+ width: 32px;
+ height: 32px;
+ }
+ button.switch.disabled,
+ button.switch[disabled] {
+ color: rgba(var(--spice-rgb-text), 0.3);
+ pointer-events: none;
+ }
+ button.switch.small {
+ width: 22px;
+ height: 22px;
+ padding: 3px;
+ }
+ .main-buttons-button{
+ border: 2px solid transparent;
+ border-radius: 500px;
+ color: var(--spice-text);
+ cursor: pointer;
+ display: inline-block;
+ font-size: 12px;
+ font-weight: 700;
+ letter-spacing: 1.76px;
+ line-height: 18px;
+ padding: 8px 34px;
+ text-align: center;
+ text-transform: uppercase;
+ -webkit-transition: all 33ms cubic-bezier(.3,0,0,1);
+ transition: all 33ms cubic-bezier(.3,0,0,1);
+ white-space: nowrap;
+ will-change: transform;
+ }
+ .main-button-primary{
+ background-color: var(--spice-button);
+ }
+ .main-button-secondary{
+ background-color: transparent;
+ color: var(--spice-button);
+ border: 2px solid var(--spice-button);
+ }
+ #full-screen-config-container .adjust-value {
+ margin-inline: 12px;
+ width: 22px;
+ text-align: center;
+ }
+ `;
+ configContainer.append(
+ style,
+ (() => {
+ const container = document.createElement("div");
+ container.innerHTML = `
+
+
+
+
`;
+ container.querySelector("#mode-exit").onclick = deactivate;
+ container.querySelector("#mode-switch").onclick = () => {
+ CONFIG.tvMode ? openwithDef() : openwithTV();
+ document.querySelector("body > generic-modal")?.remove();
+ };
+ return document.body.classList.contains("fsd-activated") ? container : "";
+ })(),
+
+ headerText(translations[LOCALE].settings.pluginSettings),
+ createOptions(translations[LOCALE].settings.language, getAvailableLanguages(translations), CONFIG["locale"], (value) =>
+ saveGlobalOption("locale", value)
+ ),
+
+ headerText(translations[LOCALE].settings.lyricsHeader),
+ createToggle(translations[LOCALE].settings.lyrics, "lyricsDisplay", (row, status) => {
+ container.classList.remove("lyrics-unavailable");
+ let nextEle = row.nextElementSibling;
+ while (!nextEle.classList.contains("subhead")) {
+ nextEle.classList.toggle("hidden", !status);
+ nextEle = nextEle.nextElementSibling;
+ }
+ }),
+ createOptions(
+ translations[LOCALE].settings.lyricsAlignment.setting,
+ {
+ left: translations[LOCALE].settings.lyricsAlignment.left,
+ center: translations[LOCALE].settings.lyricsAlignment.center,
+ right: translations[LOCALE].settings.lyricsAlignment.right,
+ },
+ CONFIG[ACTIVE].lyricsAlignment,
+ (value) => saveOption("lyricsAlignment", value)
+ ),
+ createAdjust(translations[LOCALE].settings.lyricsAnimationTempo, "animationTempo", "s", 0.3, 0.1, 0, 1, (state) => {
+ CONFIG[ACTIVE]["animationTempo"] = state;
+ saveConfig();
+ render();
+ if (document.body.classList.contains("fsd-activated")) activate();
+ }),
+ headerText(translations[LOCALE].settings.generalHeader),
+ createToggle(translations[LOCALE].settings.progressBar, "progressBarDisplay"),
+ createToggle(translations[LOCALE].settings.playerControls, "playerControls"),
+ createToggle(translations[LOCALE].settings.trimTitle, "trimTitle"),
+ createToggle(translations[LOCALE].settings.trimTitleUpNext, "trimTitleUpNext"),
+ createOptions(
+ translations[LOCALE].settings.showAlbum.setting,
+ {
+ n: translations[LOCALE].settings.showAlbum.never,
+ a: translations[LOCALE].settings.showAlbum.always,
+ d: translations[LOCALE].settings.showAlbum.date,
+ },
+ CONFIG[ACTIVE].showAlbum,
+ (value) => saveOption("showAlbum", value)
+ ),
+ createToggle(translations[LOCALE].settings.showAllArtists, "showAllArtists"),
+ createToggle(translations[LOCALE].settings.icons, "icons"),
+ createToggle(translations[LOCALE].settings.songChangeAnimation, "enableFade"),
+ document.fullscreenEnabled ? createToggle(translations[LOCALE].settings.fullscreen, "enableFullscreen") : "",
+ headerText(translations[LOCALE].settings.extraHeader),
+ ACTIVE !== "tv"
+ ? createOptions(
+ translations[LOCALE].settings.backgroundChoice.setting,
+ {
+ c: translations[LOCALE].settings.backgroundChoice.color,
+ a: translations[LOCALE].settings.backgroundChoice.artwork,
+ },
+ CONFIG.def.backgroundChoice,
+ (value) => saveOption("backgroundChoice", value)
+ )
+ : "",
+ createToggle(translations[LOCALE].settings.extraControls, "extraControls"),
+ createToggle(translations[LOCALE].settings.upnextDisplay, "upnextDisplay"),
+ createOptions(
+ translations[LOCALE].settings.contextDisplay.setting,
+ {
+ n: translations[LOCALE].settings.contextDisplay.never,
+ m: translations[LOCALE].settings.contextDisplay.mouse,
+ a: translations[LOCALE].settings.contextDisplay.always,
+ },
+ CONFIG[ACTIVE].contextDisplay,
+ (value) => saveOption("contextDisplay", value)
+ ),
+ createOptions(
+ translations[LOCALE].settings.volumeDisplay.setting,
+ {
+ a: translations[LOCALE].settings.volumeDisplay.always,
+ n: translations[LOCALE].settings.volumeDisplay.never,
+ m: translations[LOCALE].settings.volumeDisplay.mouse,
+ o: translations[LOCALE].settings.volumeDisplay.volume,
+ },
+ CONFIG[ACTIVE].volumeDisplay,
+ (value) => saveOption("volumeDisplay", value)
+ ),
+ headerText(translations[LOCALE].settings.appearanceHeader, translations[LOCALE].settings.appearanceSubHeader),
+ ACTIVE !== "tv"
+ ? createOptions(
+ translations[LOCALE].settings.backgroundColor.setting,
+ {
+ VIBRANT: translations[LOCALE].settings.backgroundColor.vibrant,
+ PROMINENT: translations[LOCALE].settings.backgroundColor.prominent,
+ DESATURATED: translations[LOCALE].settings.backgroundColor.desaturated,
+ LIGHT_VIBRANT: translations[LOCALE].settings.backgroundColor.lightVibrant,
+ DARK_VIBRANT: translations[LOCALE].settings.backgroundColor.darkVibrant,
+ VIBRANT_NON_ALARMING: translations[LOCALE].settings.backgroundColor.vibrantNonAlarming,
+ },
+ CONFIG.def.coloredBackChoice,
+ (value) => saveOption("coloredBackChoice", value)
+ )
+ : "",
+ createToggle(translations[LOCALE].settings.themedButtons, "themedButtons"),
+ createToggle(translations[LOCALE].settings.themedIcons, "themedIcons"),
+ createOptions(
+ translations[LOCALE].settings.invertColors.setting,
+ {
+ n: translations[LOCALE].settings.invertColors.never,
+ a: translations[LOCALE].settings.invertColors.always,
+ d: translations[LOCALE].settings.invertColors.auto,
+ },
+ CONFIG[ACTIVE].invertColors,
+ (value) => saveOption("invertColors", value)
+ ),
+ createAdjust(translations[LOCALE].settings.backAnimationTime, "backAnimationTime", "s", 0.8, 0.1, 0, 5, (state) => {
+ CONFIG[ACTIVE]["backAnimationTime"] = state;
+ saveConfig();
+ render();
+ if (document.body.classList.contains("fsd-activated")) activate();
+ }),
+ createOptions(
+ translations[LOCALE].settings.upnextScroll.setting,
+ {
+ mq: translations[LOCALE].settings.upnextScroll.mq,
+ sp: translations[LOCALE].settings.upnextScroll.sp,
+ },
+ CONFIG[ACTIVE].upNextAnim,
+ (value) => saveOption("upNextAnim", value)
+ ),
+ createAdjust(translations[LOCALE].settings.upnextTime, "upnextTimeToShow", "s", 30, 1, 5, 60, (state) => {
+ CONFIG[ACTIVE]["upnextTimeToShow"] = state;
+ saveConfig();
+ updateUpNextShow();
+ // render()
+ // if (document.body.classList.contains('fsd-activated'))
+ // activate()
+ }),
+ createAdjust(translations[LOCALE].settings.backgroundBlur, "blurSize", "px", 20, 4, 0, 100, (state) => {
+ CONFIG[ACTIVE]["blurSize"] = state;
+ saveConfig();
+ render();
+ if (document.body.classList.contains("fsd-activated")) activate();
+ }),
+ createOptions(
+ translations[LOCALE].settings.backgroundBrightness,
+ {
+ 0: "0%",
+ 0.1: "10%",
+ 0.2: "20%",
+ 0.3: "30%",
+ 0.4: "40%",
+ 0.5: "50%",
+ 0.6: "60%",
+ 0.7: "70%",
+ 0.8: "80%",
+ 0.9: "90%",
+ 1: "100%",
+ },
+ CONFIG[ACTIVE].backgroundBrightness,
+ (value) => saveOption("backgroundBrightness", Number(value))
+ ),
+ (() => {
+ const container = document.createElement("div");
+ container.innerHTML = `
+
+
+
+
`;
+ container.querySelector("#reset-switch").onclick = () => {
+ CONFIG[ACTIVE] = DEFAULTS[ACTIVE];
+ saveConfig();
+ render();
+ if (document.body.classList.contains("fsd-activated")) {
+ activate();
+ }
+ configContainer = "";
+ setTimeout(openConfig, 5);
+ };
+ container.querySelector("#reload-switch").onclick = () => {
+ location.reload();
+ };
+ return container;
+ })()
+ );
+ Spicetify.PopupModal.display({
+ title: ACTIVE === "tv" ? translations[LOCALE].settings.tvModeConfig : translations[LOCALE].settings.fullscreenConfig,
+ content: configContainer,
+ });
+ }
+
+ // container.ondblclick = deactivatemenu
+
+ // Add Full Screen Button on bottom bar
+ const defButton = document.createElement("button");
+ defButton.classList.add("button", "fsd-button", "control-button", "InvalidDropTarget");
+ defButton.id = "fs-button";
+ defButton.setAttribute("title", translations[LOCALE].fullscreenBtnDesc);
+
+ defButton.innerHTML = ``;
+ defButton.onclick = openwithDef;
+
+ defButton.oncontextmenu = (evt) => {
+ evt.preventDefault();
+ ACTIVE = "def";
+ openConfig();
+ };
+
+ // if (CONFIG.fsHideOriginal) {
+ // if (extraBar.lastChild.classList.contains("control-button") || extraBar.lastChild.title == "Full screen") extraBar.lastChild.remove();
+ // }
+
+ extraBar?.append(defButton);
+
+ // Add TV Mode Button on top bar
+ const tvButton = document.createElement("button");
+ tvButton.classList.add("button", "tm-button", "main-topBar-button", "InvalidDropTarget");
+ tvButton.innerHTML = ``;
+ tvButton.id = "TV-button";
+ tvButton.setAttribute("title", translations[LOCALE].tvBtnDesc);
+
+ tvButton.onclick = openwithTV;
+
+ topBar?.append(tvButton);
+ tvButton.oncontextmenu = (evt) => {
+ evt.preventDefault();
+ ACTIVE = "tv";
+ openConfig();
+ };
+
+ render();
+}
+
+fullScreen();
diff --git a/.config/spicetify/Extensions/genre.js b/.config/spicetify/Extensions/genre.js
new file mode 100644
index 00000000..85c603bd
--- /dev/null
+++ b/.config/spicetify/Extensions/genre.js
@@ -0,0 +1,125 @@
+//
+
+(function Genre() {
+ const { CosmosAsync, Player } = Spicetify;
+
+ /**
+ * Fetch genre from artist
+ *
+ * @param artistURI {string}
+ * @return {Promise}
+ */
+ const fetchGenres = async (artistURI) => {
+ const res = await CosmosAsync.get(
+ `https://api.spotify.com/v1/artists/${artistURI}`
+ );
+ // noinspection JSUnresolvedVariable
+ return res.genres.slice(0, 3) // Only keep the first 3 genres
+ };
+
+ /**
+ * Fetch playlist from The Sound of Spotify for a given genre
+ * @param {String} genre
+ * @return {String|null}
+ */
+ const fetchSoundOfSpotifyPlaylist = async (genre) => {
+ const query = encodeURIComponent(`The Sound of ${genre}`);
+ // Check localStorage for playlist
+ const cached = localStorage.getItem(`everynoise:${query}`);
+ if (cached !== null) {
+ return cached;
+ }
+
+ // Search for playlist and check results for the everynoise account
+ const re = new RegExp(`^the sound of ${genre.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`, 'i');
+ const res = await CosmosAsync.get(`https://api.spotify.com/v1/search?q=${query}&type=playlist`)
+ for (const item of res.playlists.items) {
+ if (item.owner.id === "thesoundsofspotify" && re.test(item.name)) {
+ localStorage.setItem(`everynoise:${genre}`, item.uri);
+ return item.uri
+ }
+ }
+ return null;
+ };
+
+
+ // Store the current playback id
+ let playback = null;
+
+ /**
+ *
+ * @type {Node}
+ */
+ let genreContainer = null;
+
+ let infoContainer = document.querySelector('div.main-trackInfo-container');
+
+ /**
+ * Remove genre injection in the UI
+ */
+ const cleanInjection = () => {
+ if (genreContainer !== null) {
+ try {
+ infoContainer.removeChild(genreContainer);
+ } catch (e) {}
+ }
+ };
+
+ /**
+ * Inject genres to UI
+ */
+ const inject = () => {
+ Player.addEventListener('onprogress', async () => {
+ if (Player.data.track.metadata.hasOwnProperty('artist_uri')) {
+ // If the registered song isn't the same as the one currently being played then fetch genres
+ if (playback !== Player.data.playback_id) {
+ // Save the new track
+ playback = Player.data.playback_id;
+ const id = Player.data.track.metadata.artist_uri.split(':')[2];
+ const genres = await fetchGenres(id);
+
+ cleanInjection();
+
+ genreContainer = document.createElement('div');
+ // noinspection JSUndefinedPropertyAssignment
+ genreContainer.className = 'main-trackInfo-genres ellipsis-one-line main-type-finale';
+ // noinspection JSUnresolvedVariable
+ genreContainer.style.color = 'var(--spice-extratext)';
+
+ for (const i in genres) {
+ let element;
+ const uri = await fetchSoundOfSpotifyPlaylist(genres[i]);
+ if (uri !== null) {
+ element = document.createElement('a');
+ element.innerHTML = genres[i];
+ element.href = uri;
+ } else {
+ element = document.createElement('span');
+ }
+ element.innerHTML = genres[i];
+ element.style.fontSize ="11px";
+ genreContainer.appendChild(element);
+ if (i < genres.length-1) {
+ const separator = document.createElement('span');
+ separator.innerHTML = ', ';
+ genreContainer.appendChild(separator);
+ }
+ }
+
+ infoContainer = document.querySelector('div.main-trackInfo-container');
+ if(!infoContainer) cleanInjection();
+ infoContainer.appendChild(genreContainer);
+
+ }
+ } else {
+ cleanInjection();
+ }
+ });
+ };
+
+ if (!CosmosAsync) {
+ setTimeout(Genre, 500);
+ } else {
+ inject();
+ }
+})();
diff --git a/.config/spicetify/Extensions/hidePodcasts.js b/.config/spicetify/Extensions/hidePodcasts.js
new file mode 100644
index 00000000..6bc4a454
--- /dev/null
+++ b/.config/spicetify/Extensions/hidePodcasts.js
@@ -0,0 +1,5 @@
+var hidePodcasts=(()=>{var T=(e=>"undefined"!=typeof require?require:"undefined"!=typeof Proxy?new Proxy(e,{get:(e,t)=>("undefined"!=typeof require?require:e)[t]}):e)(function(e){if("react"===e)return Spicetify.React;if("react-dom"===e)return Spicetify.ReactDOM;if("undefined"!=typeof require)return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});function C(e){return(C="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function U(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,o=new Array(t);n":">",'"':""","'":"'","/":"/"};function W(e){return"string"==typeof e?e.replace(/[&<>"'\/]/g,function(e){return $[e]}):e}var h="undefined"!=typeof window&&window.navigator&&void 0===window.navigator.userAgentData&&window.navigator.userAgent&&-1s+c;)c++,u=a[l=i.slice(s,s+c).join(r)];if(void 0===u)return;if(null===u)return null;if(n.endsWith(l)){if("string"==typeof u)return u;if(l&&"string"==typeof u[l])return u[l]}var p=i.slice(s+c).join(r);return p?e(u,p,r):void 0}a=a[i[s]]}return a}}(this.data&&this.data[e]&&this.data[e][t],n,r)}},{key:"addResource",value:function(e,t,n,o){var r=4=p.maxReplaces)break}var n,o}),r}},{key:"nest",value:function(e,t){var n,o=this,r=2=this.maxParallelReads?void this.waitingReads.push({lng:o,ns:r,fcName:i,tried:s,wait:c,callback:l}):(this.readingCalls++,this.backend[i](o,r,function(e,t){var n;a.readingCalls--,0",">":">","'":"'","'":"'",""":'"',""":'"'," ":" "," ":" ","©":"©","©":"©","®":"®","®":"®","…":"…","…":"…","/":"/","/":"/"};function Ne(t,e){var n,o=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),o.push.apply(o,n)),o}function Ce(t){for(var e=1;e{t=localStorage.getItem(t);if(!t)return e;try{return JSON.parse(t)}catch(e){return t}}),Ye=(je.use(T).use(I).init({resources:{en:{translation:{menuTitle:"Hide podcasts",enabled:"Enabled",aggressiveMode:"Aggressive mode",searchPageShelfAriaLabel:"Browse all"}},fr:{translation:{menuTitle:"Masquer les balados",enabled:"Activé",aggressiveMode:"Mode agressif",searchPageShelfAriaLabel:"Paarcourir tout"}},de:{translation:{menuTitle:"Podcasts verstecken",enabled:"Aktiviert",aggressiveMode:"Aggressiver Modus",searchPageShelfAriaLabel:"Alles durchsuchen"}},it:{translation:{menuTitle:"Nascondi podcast",enabled:"Attivato",aggressiveMode:"Modalità aggressiva",searchPageShelfAriaLabel:"Sfoglia tutto"}}},detection:{order:["navigator","htmlTag"]},fallbackLng:"en",interpolation:{escapeValue:!1}}),"HidePodcastsEnabled"),Ge="HidePodcastsAggressiveMode";var Qe=async function(){const o=je["t"];let{Player:e,Menu:t,Platform:n}=Spicetify,r=document.querySelector(".main-view-container__scroll-node-child");for(;!(e&&t&&n&&r);)await new Promise(e=>setTimeout(e,100)),e=Spicetify.Player,t=Spicetify.Menu,n=Spicetify.Platform,r=document.querySelector(".main-view-container__scroll-node-child");let a=We(Ye,!0),i=We(Ge,!1);function s(){var e;e=a,document.body.classList.toggle("hide-podcasts-enabled",e);{const t=document.body;if(!t.classList.contains("hide-podcasts--style-injected")){const n=document.createElement("style");n.innerHTML=`.hide-podcasts-enabled .podcast-item {
+ display: none !important;
+ }.hide-podcasts-enabled .queue-tabBar-header a[href="/collection/podcasts"] {
+ display: none !important;
+ }`,t.appendChild(n),t.classList.add("hide-podcasts--style-injected")}}{const o=null==(e=document.querySelector('a[href="/collection/episodes"]'))?void 0:e.parentElement,r=(o&&o.classList.add("podcast-item"),document.querySelectorAll(".main-shelf-shelf")),i=(r.forEach(e=>{var t;0<[...e.querySelectorAll('.main-cardHeader-link[href^="/episode"]'),...e.querySelectorAll('.main-cardHeader-link[href^="/show"]')].length&&(t=e.getAttribute("aria-label"),console.log("Tagging carousel: "+t),e.classList.add("podcast-item"))}),document.querySelector('.x-categoryCard-CategoryCard[href="/genre/podcasts-web"]'));i&&(console.log("Tagging browsePodcastsCard: "+i),i.classList.add("podcast-item"))}}function c(t){const n=new MutationObserver(function(){var e;!r||(e="/search"===t?r.querySelector(`#searchPage .main-shelf-shelf[aria-label="${o("searchPageShelfAriaLabel")}"]`):r.querySelector("section"))&&(console.log(t,e),s(),i||n.disconnect())});n.observe(r,{childList:!0,subtree:!0})}new t.SubMenu(o("menuTitle"),[new t.Item(o("enabled"),a,e=>{a=!a,localStorage.setItem(Ye,a),e.setState(a),s()}),new t.Item(o("aggressiveMode"),i,e=>{i=!i,localStorage.setItem(Ge,i),e.setState(i),location.reload()})]).register(),c(n.History.location.pathname),n.History.listen(({pathname:e})=>{c(e)})};(async()=>{await Qe()})()})();
\ No newline at end of file
diff --git a/.config/spicetify/Extensions/historyShortcut.js b/.config/spicetify/Extensions/historyShortcut.js
new file mode 100644
index 00000000..a4c65228
--- /dev/null
+++ b/.config/spicetify/Extensions/historyShortcut.js
@@ -0,0 +1,127 @@
+// NAME: History Shortcut
+// AUTHOR: einzigartigerName
+// DESCRIPTION: Adds a Shortcut to your Listening History to the Sidebar
+
+(function HistoryShortcut() {
+
+ const { CosmosAPI, Player, LocalStorage, PlaybackControl, ContextMenu, URI } = Spicetify
+ if (!(CosmosAPI && Player && LocalStorage && PlaybackControl && ContextMenu && URI)) {
+ setTimeout(HistoryShortcut, 300)
+ return
+ }
+
+ const ITEM_LABEL = "History"
+
+ const HISTORY_DIV_CLASS = "SidebarListItem"
+ const HISTORY_DIV_CLASS_ACTIVE = "SidebarListItem SidebarListItem--is-active"
+
+ const HISTORY_ANKER_CLASS = "SidebarListItemLink SidebarListItemLink--tall spoticon-time-24"
+ const HISTORY_ANKER_CLASS_ACTIVE = "SidebarListItemLink SidebarListItemLink--is-highlighted SidebarListItemLink--tall spoticon-time-24"
+
+ let historyItem = createHistoyItem()
+ // Get Sidebar Lists
+ var topicList = document.querySelector("#view-navigation-bar > div > div.LeftSidebar__section > div > ul")
+ if (topicList) {
+ // Add to first in list
+ // On default layout this would be the Home/Browse/Radio List
+ topicList.appendChild(historyItem.div)
+ } else {
+ return
+ }
+
+ const toCheckMutate = document.getElementById('view-content');
+ const config = { attributes: true, childList: true, subtree: true };
+
+ let observerCallback = function(_, _) {
+ appQueue = document.getElementById("app-queue")
+ if (!appQueue){ return }
+
+ if (appQueue.getAttribute("class") === "active"
+ && appQueue.getAttribute("data-app-uri") === "spotify:app:queue:history"
+ ) {
+ onClickHistory()
+ } else {
+ onLeaveHistory()
+ }
+ };
+
+ let observer = new MutationObserver(observerCallback)
+ observer.observe(toCheckMutate, config)
+
+
+
+ // Deactivate Active Status for History Item
+ function onLeaveHistory() {
+ historyItem.div.setAttribute("class",HISTORY_DIV_CLASS)
+ historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS)
+ }
+
+ // Activate Active Status for History Item
+ function onClickHistory() {
+ historyItem.div.setAttribute("class", HISTORY_DIV_CLASS_ACTIVE)
+ historyItem.anker.setAttribute("class", HISTORY_ANKER_CLASS_ACTIVE)
+ }
+
+ // Construct the List Item
+ function createHistoyItem() {
+ /* List Item
+ *