[
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: Bug Report\nabout: Create a report to help me resolve issues\ntitle: \"[Extension Name Here][BUG]\"\nlabels: bug\nassignees: huhridge\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Console Errors**\nTo check the console, run `spicetify enable-devtools` in the terminal, and press <kbd>Ctrl</kbd>/<kbd>Cmd</kbd>+<kbd>Shift</kbd>+<kbd>i</kbd> and check the console.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS:\n - Spotify Version:\n - Spicetify Version:\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: Feature Request\nabout: Suggest an idea for this project\ntitle: \"[REQ]\"\nlabels: enhancement\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is.\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".gitignore",
    "content": "\n.DS_Store\n.DS_Store\n.DS_Store\n"
  },
  {
    "path": "README.md",
    "content": "# huh-spicetify-extensions\n\nCollection of my spicetify extensions\n\n🌟 Like it? Gimme some love!  \n[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)\n\n## Full App Display modified\n\nFilename : `fullAppDisplayMod.js`\n\nMinimal album cover art display with beautiful blur effect background. Activating button locates in top bar. While in display mode, double click anywhere to exit. Right click anywhere to open setting menu. Now also includes lyrics if `lyrics-plus` custom app installed.\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAppDisplayModified/previews/preview.gif)\n\n<details>\n <summary>Screenshots</summary>\n \n* Album Art\n  <details>\n  <summary></summary>\n\n  <img width=\"1440\" alt=\"Album Art Background\" src=\"https://user-images.githubusercontent.com/67046436/146332984-d45231e2-f193-43cb-b1b0-2456edf9ce29.png\">\n  </details>\n \n  \n* Colorful Background(doesn't work offline)\n  <details>\n  <summary></summary>\n\n  <img width=\"1440\" alt=\"Colorful Background\" src=\"https://user-images.githubusercontent.com/67046436/146333067-c6d6694e-82a7-4948-bd56-3b31eb50ac7d.png\">\n  </details>\n \n</details>\n\nFor more information: [fullAppDisplayMod README](/fullAppDisplayModified/README.md)\n\n## skipStats\n\nFilename : `skipStats.js`\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/preview.jpg)\n\nExtension to track your skips!\n\n-   Tracks your skips when listening to playlists or albums!\n-   Displays the data in a readable manner\n-   Auto-skip songs over a certain value of skips\n\nFor more information: [skipStats README](/skipStats/README.md)\n\n## List Playlists with Song\n\nFilename : `listPlaylistsWithSong.js`\n\nAdds context menu button to view playlists in your library that contain the selected song.\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview.gif)\n\n### To use:\n\nRight Click on selected song, and click \"List playlists with this Song\".\n\n![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview1.jpg)\n\n#### Note:\n\n~~Currently, doesn't work on the currently playing song(like in the bottom bar pictured below), finding a workaround.~~\nNow it works, fixed by yours truly!\n\n## Go to Song\n\nFilename : `goToSong.js`\n\nGo to the currrently playing song in a playlist **/or/** currently playing playlist.\n\n### To use:\n\n-   Currently playing playlist: Go to Profile > GoToSong > Choose \"Go To Song in current Playlist\"\n-   ![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview1.jpg)\n\n-   Any Playlist: Right Click on the Playlist, and choose \"Go to Currently Playing Song\"\n-   ![Preview2](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview2.jpg)\n\n### Note:\n\nYou may need to adjust your delay if it's giving an error, follow the instructions in the popup.\n\n![Adjust](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/adjust.jpg)\n\n## playlistIntersection\n\nFilename : `playlistIntersection.js`\n\nAdds context menu buttons to see\n\n-   songs in common between two playlists\n-   songs only present in one playlist\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/both.jpg)\n\n### To use:\n\nCheck the full readme: [playlistIntersection readme](/playlistIntersection/README.md)\n\n## Display full Album date\n\nFilename : `fullAlbumDate.js`\n\nDisplay full album date instead of just year\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAlbumDate/preview.jpg)\n"
  },
  {
    "path": "fullAlbumDate/README.md",
    "content": "# Display full Album date\nFilename : `fullAlbumDate.js`\nDisplay full album date instead of just year\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAlbumDate/preview.jpg)\n\n## More\n🌟 Like it? Gimme some love!    \n[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)\n"
  },
  {
    "path": "fullAlbumDate/fullAlbumDate.js",
    "content": "(async function fullAlbumDate() {\n    if (!Spicetify.Platform?.History || !Spicetify.CosmosAsync || !Spicetify.Locale) {\n        setTimeout(fullAlbumDate, 300);\n        return;\n    }\n\n    const { CosmosAsync, Locale } = Spicetify;\n    const { History } = Spicetify.Platform;\n\n    async function getAlbumDate(uri) {\n        const albumInfo = await CosmosAsync.get(`https://api.spotify.com/v1/albums/${uri}`);\n        const albumDate = new Date(albumInfo.release_date);\n        return Locale.formatRelativeTime( albumDate );\n    }\n\n    function replaceDate(newDate) {\n        const dateElement =\n            document.querySelector(\".main-entityHeader-divider.main-type-mesto\") ??\n            document.querySelector(\".main-entityHeader-metaData span:nth-last-child(2)\");\n        if (!dateElement) {\n            setTimeout(replaceDate, 100, newDate);\n            return;\n        }\n        dateElement.textContent = newDate;\n    }\n\n    async function setDate() {\n        const { pathname } = History.location;\n        if (pathname.startsWith(\"/album/\")) {\n            const uri = pathname.split(\"/\")[2];\n            const newDate = await getAlbumDate(uri);\n            replaceDate(newDate);\n        }\n    }\n\n    setDate();\n    History.listen(setDate);\n})();\n"
  },
  {
    "path": "fullAppDisplayModified/README.md",
    "content": "# Full App Display modified\nFilename : `fullAppDisplayMod.js`\nMinimal album cover art display with beautiful blur effect background. Activating button locates in top bar. While in display mode, double click anywhere to exit. Right click anywhere to open setting menu. Now also includes lyrics if `lyrics-plus` custom app installed.\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/fullAppDisplayModified/previews/preview.gif)\n\nScreenshots:\n* Album Art\n <img width=\"1440\" alt=\"Album Art Background\" src=\"https://user-images.githubusercontent.com/67046436/146332984-d45231e2-f193-43cb-b1b0-2456edf9ce29.png\">\n* Colorful Background(doesn't work offline)\n <img width=\"1440\" alt=\"Colorful Background\" src=\"https://user-images.githubusercontent.com/67046436/146333067-c6d6694e-82a7-4948-bd56-3b31eb50ac7d.png\">\nColorful background changes text color if the color is too light,\n<img width=\"1440\" alt=\"Colorful Background(dynamic text)\" src=\"https://user-images.githubusercontent.com/67046436/146333271-9cd687e6-cb87-4c89-ba02-a21654596514.png\">\n\nSettings:\n(May look a bit different depending on your theme)\n<img width=\"1440\" alt=\"Screenshot 2021-12-16 at 14 03 04\" src=\"https://user-images.githubusercontent.com/67046436/146336169-7aa6ecba-e475-49fa-a111-b3407268d1ce.png\">\n\n## More\n🌟 Like it? Gimme some love!    \n[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)\n"
  },
  {
    "path": "fullAppDisplayModified/fullAppDisplayMod.js",
    "content": "// @ts-check\n// NAME: Full App Display\n// AUTHOR: khanhas\n// VERSION: 1.2\n// DESCRIPTION: Fancy artwork and track status display.\n\n/// <reference path=\"../globals.d.ts\" />\n\n(function FullAppDisplay() {\n    if (!Spicetify.Keyboard || !Spicetify.React || !Spicetify.ReactDOM || !Spicetify.Platform) {\n        setTimeout(FullAppDisplay, 200);\n        return;\n    }\n\n    const { React: react, ReactDOM: reactDOM } = Spicetify;\n    const { useState, useEffect } = react;\n\n    const CONFIG = getConfig();\n\n    if (\n        !CONFIG[\"colorChoice\"] ||\n        CONFIG[\"colorChoice\"] == \"colorDark\" ||\n        CONFIG[\"colorChoice\"] == \"colorLight\" ||\n        CONFIG[\"colorChoice\"] == \"colorRaw\"\n    ) {\n        CONFIG[\"colorChoice\"] = \"LIGHT_VIBRANT\";\n        saveConfig();\n    }\n\n    if (!CONFIG[\"version\"]) {\n        CONFIG[\"version\"] = \"1.2\";\n        saveConfig();\n    }\n\n    let updateVisual;\n    let nextUri, prevColor, nextColor, finImage;\n    let isHidden = false;\n    let time;\n\n    const style = document.createElement(\"style\");\n    const styleBase = `\n#full-app-display {\n    display: none;\n    position: fixed;\n    width: 100%;\n    height: 100%;\n    cursor: default;\n    left: 0;\n    top: 0;\n}\n#fad-header {\n    position: fixed;\n    width: 100%;\n    height: 80px;\n    -webkit-app-region: drag;\n}\n#fad-body {\n    height: 100vh;\n}\n#fad-foreground {\n    position: relative;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    transform: scale(var(--fad-scale));\n    transition: all 1s ease;\n}\n#fad-art-image {\n    position: relative;\n    width: 100%;\n    height: 100%;\n    padding-bottom: 100%;\n    border-radius: 15px;\n    background-size: cover;\n}\n#fad-art-inner {\n    position: absolute;\n    left: 3%;\n    bottom: 0;\n    width: 94%;\n    height: 94%;\n    z-index: -1;\n    backface-visibility: hidden;\n    transform: translateZ(0);\n    filter: blur(6px);\n    backdrop-filter: blur(6px);\n    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6) !important;\n}\n#fad-progress-container {\n    width: 100%;\n    display: flex;\n    align-items: center;\n}\n#fad-progress {\n    width: 100%;\n    height: 6px;\n    margin: 6px 0 6px;\n    border-radius: 6px;\n    background-color: #ffffff50;\n    overflow: hidden;\n}\n#fad-progress-inner {\n    height: 100%;\n    transition: width 100ms ease;\n    border-radius: 6px;\n    background-color: #ffffff;\n    box-shadow: 4px 0 12px rgba(0, 0, 0, 0.8) !important;\n}\n#fad-volume {\n    width: 4rem;\n    height: 16rem;\n    position: fixed;\n    left: 1rem;\n    transition: opacity ease 350ms;\n}\n#fad-volume:hover {\n    opacity: 1 !important;\n}\n#fad-volicon {\n    height: fit-content;\n    display: grid;\n    justify-content: start;\n}\n#fad-volbar {\n    display: flex;\n    height: 200px;\n    width: 8px;\n    border-radius: 6px;\n    overflow: hidden;\n    background-color: #ffffff50;\n    justify-content: center;\n    padding-top: 10px;\n    align-items: end;\n    margin-left: 27.5px;\n    margin-top: 10px;\n    position: absolute;\n}\n#fad-volbar-inner {\n    width: 8px;\n    border-radius: 6px;\n    background-color: rgb(255, 255, 255);\n    transition: height 0.5s ease 0s;\n}\n#fad-duration {\n    margin-left: 10px;\n}\n#fad-background {\n    position: absolute;\n    left: 0;\n    top: 0;\n    width: 100%;\n    height: 100%;\n    z-index: -2;\n}\nbody.fad-activated #full-app-display {\n    display: block\n}\n.fad-background-fade {\n    transition: background-image 1s linear;\n}\nbody.video-full-screen.video-full-screen--hide-ui {\n    cursor: auto;\n}\n#fad-controls button, #fad-extracontrols button {\n    background-color: transparent;\n    border: 0;\n    color: currentColor;\n    padding: 0 5px;\n    pointer-events: auto;\n}\n#fad-controls button:hover, #fad-extracontrols button:hover, #fad-volicon button:hover{\n    transform: scale(1.1);\n}\n#fad-artist svg, #fad-album svg {\n    display: inline-block;\n}\n#fad-upnext {\n    position: absolute;\n    width: 399px;\n    height: 90px;\n    display: flex;\n    border-radius: 10px;\n    animation: textchange 0.5s forwards;\n    transition: clip-path 0.4s;\n}\n#fad-upnext-image {\n    width: 64px;\n    align-self: center;\n    margin-left: 13px;\n    height: 64px;\n    background-size: cover;\n    background-position: center;\n    border-radius: 5px;\n    box-shadow: 0 4px 8px rgb(0 0 0 / 30%) !important;\n    z-index: 0;\n}\n#fad-upnext-blur {\n    position: absolute;\n    backdrop-filter: blur(15px) brightness(0.6);\n    background-color: rgb(255,255,255,0.1);\n    width: 100%;\n    height: 100%;\n    border-radius: 10px;\n}\n#fad-upnext-details{\n    margin-left: 15px;\n    align-self: center;\n    font-size: 14.5px;\n    display: flex;\n    flex-direction: column;\n    align-items: flex-start;\n    color: #ffffff;\n    overflow: hidden;\n    white-space: nowrap;\n}\n#scroll-queue{\n    position: absolute;\n    width: 399px;\n    height: 90px;\n    color: transparent;\n    z-index: 2;\n}\n.dont-scale{\n    transform: scale(calc(1/(var(--fad-scale))));\n}\n.dot-after:after{\n    background-color: currentColor;\n    border-radius: 50%;\n    bottom: 3px;\n    content: \"\";\n    display: block;\n    height: 4px;\n    left: 50%;\n    position: relative;\n    transform: translateX(-50%);\n    width: 4px;\n}\n.crossed-out:after{\n    background-color: currentColor;\n    bottom: 24px;\n    transform: rotate(45deg);\n    transform-origin: 0 0;\n    content: \"\";\n    display: block;\n    height: 27px;\n    left: 18px;\n    position: relative;\n    width: 0.5px;\n}\n::-webkit-scrollbar {\n    width: 8px;\n}\n\n.fad-grad-image{\n    position: absolute;\n    filter: blur(40px) brightness(0.60);\n    border-radius: 100em;\n    animation: rotategrad 50s linear infinite 1s;\n}\n\n@keyframes textchange {\n    0%{\n        opacity: 0;\n    }   \n    30%{\n        opacity: 0.3;\n    }\n    60%{\n        opacity: 0.6;\n    }\n    90%{\n        opacity: 0.9;\n    }\n  }\n\n@keyframes rotategrad {\n    0% {\n        transform: rotate(18deg);\n    }\n    100% {\n        transform: rotate(378deg);\n    }\n}\n`;\n\n    const styleChoices = [\n        `\n#fad-foreground {\n    flex-direction: row;\n    text-align: left;\n}\n#fad-art {\n    width: calc(100vw - 840px);\n    min-width: 200px;\n    max-width: 340px;\n}\n#fad-details {\n    padding-left: 40px;\n    line-height: initial;\n    max-width: 70%;\n    color: #FFFFFF;\n    filter: invert(0);\n}\n#fad-title {\n    font-size: 87px;\n    font-weight: 900;\n}\n#fad-artist, #fad-album {\n    font-size: 54px;\n    font-weight: 400;\n}\n#fad-artist svg, #fad-album svg {\n    margin-right: 5px;\n}\n#fad-status {\n    display: flex;\n    min-width: 400px;\n    max-width: 400px;\n    align-items: center;\n}\n#fad-status.active {\n    margin-top: 20px;\n}\n#fad-controls {\n    display: flex;\n    margin-right: 10px;\n    z-index: 0;\n}\n#fad-extracontrols {\n    height: 28px;\n    display: flex;\n}\n#fad-elapsed {\n    min-width: 52px;\n}`,\n        `\n#fad-art {\n    width: calc(100vh - 400px);\n    max-width: 340px;\n}\n#fad-foreground {\n    flex-direction: column;\n    text-align: center;\n}\n#fad-details {\n    padding-top: 40px;\n    line-height: initial;\n    max-width: 70%;\n    color: #FFFFFF;\n    filter: invert(0);\n}\n#fad-title {\n    line-height: 1;\n    font-size: 54px;\n    font-weight: 900;\n}\n#fad-artist, #fad-album {\n    font-size: 33px;\n    font-weight: 400;\n}\n#fad-artist svg, #fad-album svg {\n    width: 25px;\n    height: 25px;\n    margin-right: 5px;\n}\n#fad-status {\n    display: flex;\n    min-width: 400px;\n    max-width: 400px;\n    align-items: center;\n    flex-direction: column;\n}\n#fad-status.active {\n    margin: 20px auto 0;\n}\n#fad-controls {\n    margin-top: 20px;\n    order: 2;\n    z-index: 0;\n}\n#fad-extracontrols {\n    order: 3;\n    width: 100%;\n    height: 28px;\n    display: flex;\n}\n#fad-elapsed {\n    min-width: 56px;\n    margin-right: 10px;\n    text-align: right;\n}`,\n    ];\n\n    const lyricsPlusBase = `\n#fad-body {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n}\n#fad-foreground {\n    padding: 0 50px 0 100px;\n    width: 50vw;\n}\n#fad-lyrics-plus-container {\n    position: relative;\n    width: 50vw;\n}\n.lyrics-lyricsContainer-LyricsContainer.fad-enabled .lyrics-config-button-container {\n    display: none;\n}\n`;\n    const lyricsPlusStyleChoices = [\n        `\n#fad-title {\n    font-size: 4vw;\n}\n#fad-artist, #fad-album {\n    font-size: 2.5vw;\n    font-weight: 400;\n}\n#fad-art {\n    max-width: 210px;\n    margin-left: 50px;\n}`,\n        `\n#fad-title {\n    font-size: 3.9vw;\n}\n#fad-artist, #fad-album {\n    font-size: 2.5vw;\n    font-weight: 400;\n}\n        `,\n    ];\n\n    const verticalMonitorStyle = [\n        `\n#fad-body {\n    grid-template-columns: none;\n}\n#fad-foreground, #fad-lyrics-plus-container {\n    width: 100%;\n    height: 50vh;\n}\n#fad-foreground {\n    padding: 0 50px 0;\n}\n.lyrics-lyricsContainer-LyricsContainer.fad-enabled {\n    height: 50vh;\n    --lyrics-align-text: center !important;\n}\n#fad-volume {\n    top: 15vh;\n}\n        `,\n    ];\n    updateStyle();\n\n    function displayUpdate() {\n        let updateText = react.createElement(\n            \"p\",\n            {\n                className: \"fad-update\",\n            },\n            `\n             This update brings:\n             `,\n            react.createElement(\"li\", {}, \"Added seekable progress bar: Now you can seek songs from FAD itself, click on it to seek!\"),\n            react.createElement(\"li\", {}, \"Added show only on hover mode for volume bar (change in settings)\"),\n            react.createElement(\"li\", {}, \"Bug Fixes: Reworked the upnext and queue function, to account for the scale.\")\n        );\n        Spicetify.PopupModal.display({\n            title: \"What's New with FullAppDisplayMod\",\n            content: updateText,\n            isLarge: true,\n        });\n    }\n\n    if (CONFIG[\"version\"] == \"1.1\") {\n        displayUpdate();\n        CONFIG[\"version\"] = \"1.2\";\n        saveConfig();\n    }\n\n    async function fetchColors(uri) {\n        let colors = {};\n\n        try {\n            const body = await Spicetify.CosmosAsync.get(`https://spclient.wg.spotify.com/colorextractor/v1/extract-presets?uri=${uri}&format=json`);\n            for (const color of body.entries[0].color_swatches) {\n                colors[color.preset] = `#${color.color.toString(16).padStart(6, \"0\")}`;\n            }\n        } catch {\n            colors = {\n                DARK_VIBRANT: \"#000000\",\n                DESATURATED: \"#000000\",\n                LIGHT_VIBRANT: \"#000000\",\n                PROMINENT: \"#000000\",\n                VIBRANT: \"#000000\",\n                VIBRANT_NON_ALARMING: \"#000000\",\n            };\n        }\n        return colors;\n    }\n\n    function lightnessColor(hex) {\n        var result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n        let r = parseInt(result[1], 16);\n        let g = parseInt(result[2], 16);\n        let b = parseInt(result[3], 16);\n        return (Math.max(r, g, b) + Math.min(r, g, b)) / 2;\n    }\n\n    const DisplayIcon = ({ icon, size }) => {\n        return react.createElement(\"svg\", {\n            width: size,\n            height: size,\n            viewBox: \"0 0 16 16\",\n            fill: \"currentColor\",\n            dangerouslySetInnerHTML: {\n                __html: icon,\n            },\n        });\n    };\n\n    const SubInfo = ({ text, id, icon }) => {\n        return react.createElement(\n            \"div\",\n            {\n                id,\n            },\n            CONFIG.icons && react.createElement(DisplayIcon, { icon, size: 35 }),\n            react.createElement(\"span\", null, text)\n        );\n    };\n\n    const ButtonIcon = ({ icon, onClick, className = null, style = null, onMouseEnter = null, onMouseLeave = null }) => {\n        return react.createElement(\n            \"button\",\n            {\n                className,\n                style,\n                onClick,\n                onMouseEnter,\n                onMouseLeave,\n            },\n            react.createElement(DisplayIcon, { icon, size: 20 })\n        );\n    };\n\n    const ProgressBar = () => {\n        const [value, setValue] = useState(Spicetify.Player.getProgress());\n        useEffect(() => {\n            const update = ({ data }) => {\n                setValue(data);\n            };\n            Spicetify.Player.addEventListener(\"onprogress\", update);\n            // @ts-ignore\n            return () => Spicetify.Player.removeEventListener(\"onprogress\", update);\n        });\n        const duration = Spicetify.Platform.PlayerAPI._state.duration;\n        return react.createElement(\n            \"div\",\n            { id: \"fad-progress-container\" },\n            react.createElement(\"span\", { id: \"fad-elapsed\" }, Spicetify.Player.formatTime(value)),\n            react.createElement(\n                \"div\",\n                {\n                    id: \"fad-progress\",\n                    onClick: (e) => {\n                        e.persist();\n                        console.log(e);\n                        let coors = document.querySelector(\"#fad-progress\").getBoundingClientRect();\n                        let temp = (e.pageX - coors.x) / coors.width;\n                        console.log(temp);\n                        Spicetify.Player.seek(temp);\n                        setTimeout(console.log(Spicetify.Player.getProgressPercent()), 200);\n                        document.querySelector(\"#fad-progress-inner\").style.width = `${temp}%`;\n                    },\n                },\n                react.createElement(\"div\", {\n                    id: \"fad-progress-inner\",\n                    style: {\n                        width: (value / duration) * 100 + \"%\",\n                    },\n                })\n            ),\n            react.createElement(\"span\", { id: \"fad-duration\" }, Spicetify.Player.formatTime(duration))\n        );\n    };\n\n    // @ts-ignore\n    const VolumeBar = () => {\n        // @ts-ignore\n        const [value, setValue] = useState(Spicetify.Platform.PlaybackAPI._volume);\n        let isHover = false;\n        if (CONFIG[\"volumeBar\"] == \"onlyHover\") {\n            isHover = true;\n        }\n        useEffect(() => {\n            const update = ({ data }) => {\n                setValue(data.volume);\n            };\n            Spicetify.Platform.PlaybackAPI._events.addListener(\"volume\", update);\n            return () => Spicetify.Platform.PlaybackAPI._events.removeListener(\"volume\", update);\n        });\n        return react.createElement(\n            \"div\",\n            {\n                id: \"fad-volume\",\n                style: {\n                    top: `${(window.innerHeight - 256) / 2}px`,\n                    opacity: isHover ? 0 : 1,\n                },\n                onWheel: (event) => {\n                    let dir = event.deltaY < 0 ? 1 : -1;\n                    let temp = parseInt(document.querySelector(\"#fad-volbar-inner\").style.height) / 2 + dir * 1;\n                    if (temp < 0) {\n                        temp = 0;\n                    } else if (temp > 100) {\n                        temp = 100;\n                    }\n                    Spicetify.Player.setVolume(temp / 100);\n                    document.querySelector(\"#fad-volbar-inner\").style.height = `${2 * temp}px`;\n                },\n            },\n            react.createElement(\n                \"div\",\n                { id: \"fad-volicon\" },\n                react.createElement(ButtonIcon, {\n                    style: {\n                        marginLeft: \"18px\",\n                        backgroundColor: \"transparent\",\n                        border: \"0\",\n                        color: \"white\",\n                        padding: \"0 5px\",\n                        pointerEvents: \"auto\",\n                    },\n                    // @ts-ignore\n                    icon: Spicetify.Player.getMute() ? Spicetify.SVGIcons[\"volume-off\"] : Spicetify.SVGIcons[\"volume\"],\n                    onClick: () => {\n                        if (!Spicetify.Player.getMute()) {\n                            document.querySelector(\"#fad-volicon svg\").innerHTML = Spicetify.SVGIcons[\"volume-off\"];\n                            document.querySelector(\"#fad-volbar-inner\").style.height = `0px`;\n                            Spicetify.Player.toggleMute();\n                        } else {\n                            Spicetify.Player.toggleMute();\n                            document.querySelector(\"#fad-volicon svg\").innerHTML = Spicetify.SVGIcons[\"volume\"];\n                            setTimeout(() => {\n                                document.querySelector(\"#fad-volbar-inner\").style.height = `${Spicetify.Player.getVolume() * 200}px`;\n                            }, 200);\n                        }\n                    },\n                })\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    id: \"fad-volbar\",\n                    onClick: (e) => {\n                        let temp = 200 - e.nativeEvent.layerY;\n                        if (temp < 0) {\n                            temp = 0;\n                        }\n                        Spicetify.Player.setVolume(temp / 200);\n                        document.querySelector(\"#fad-volbar-inner\").style.height = `${temp}px`;\n                    },\n                },\n                react.createElement(\"div\", {\n                    id: \"fad-volbar-inner\",\n                    style: {\n                        height: `${value * 200}px`,\n                    },\n                })\n            )\n        );\n    };\n\n    const upNext = async ({ index, queue }) => {\n        let meta,\n            uri,\n            bottom = -100,\n            right = 0,\n            color,\n            context,\n            invertDetails = false,\n            invertWhole = false,\n            isContext = false,\n            isColor = false;\n\n        let deets = document.querySelector(\"#fad-details\");\n        const coor = deets.getBoundingClientRect();\n        let scale = CONFIG[\"scale\"];\n        if (coor.bottom + scale * 90 + 10 > window.innerHeight) {\n            bottom = bottom + (coor.bottom + 90 * scale + 10 - window.innerHeight) / scale + 10;\n            right = -409;\n        }\n\n        if (Spicetify.Player.getRepeat() == 2) {\n            uri = Spicetify.Player.data.item.uri;\n            meta = Spicetify.Player.data.item.metadata;\n        } else {\n            // @ts-ignore\n            uri = Spicetify.Queue.nextTracks[index].contextTrack.uri;\n            // @ts-ignore\n            meta = Spicetify.Queue.nextTracks[index].contextTrack.metadata;\n        }\n\n        let artistNames = Object.keys(meta)\n            .filter((key) => key.startsWith(\"artist_name\"))\n            .sort()\n            .map((key) => meta[key])\n            .join(\" • \");\n        //@ts-ignore\n        if (Spicetify.Queue.nextTracks[index].provider == \"context\") {\n            isContext = true;\n            context = Spicetify.Player.data.context.metadata.context_description;\n            if (!context) {\n                const uriObj = Spicetify.URI.fromString(Spicetify.Player.data.context.uri);\n                switch (uriObj.type) {\n                    case Spicetify.URI.Type.SEARCH:\n                        context = `Search`;\n                        break;\n                    case Spicetify.URI.Type.COLLECTION:\n                        context = \"Liked Songs\";\n                        break;\n                    case Spicetify.URI.Type.STATION:\n                    case Spicetify.URI.Type.RADIO:\n                        // @ts-ignore\n                        const rType = uriObj.args[0];\n                        context = `${rType} radio`;\n                        break;\n                    case Spicetify.URI.Type.FOLDER:\n                        context = \"Playlist Folder\";\n                        break;\n                    default:\n                        context = \"unknown\";\n                }\n            }\n        }\n\n        if (CONFIG[\"optionBackground\"] === \"colorText\") {\n            isColor = true;\n            color = await fetchColors(uri);\n            color = color[CONFIG[\"colorChoice\"]];\n            const luma =\n                parseInt(color.substring(1, 3), 16) * 0.2126 +\n                parseInt(color.substring(3, 5), 16) * 0.7152 +\n                parseInt(color.substring(5, 7), 16) * 0.0722;\n            if (luma > 180) {\n                invertDetails = true;\n            }\n            if (deets.style.filter == \"invert(1)\") {\n                invertWhole = true;\n            }\n        }\n\n        return react.createElement(\n            \"div\",\n            {\n                id: \"fad-upnext\",\n                style: {\n                    bottom: queue ? \"\" : `${bottom}px`,\n                    right: queue ? \"\" : `${right}px`,\n                    backgroundColor: isColor ? color : \"\",\n                    backgroundImage: isColor ? \"\" : `url(${meta.image_url})`,\n                    backgroundPosition: isColor ? \"\" : \"center\",\n                    backgroundRepeat: isColor ? \"\" : \"no-repeat\",\n                    backgroundSize: isColor ? \"\" : \"cover\",\n                    border: isColor ? \"\" : \"2px solid\",\n                    borderColor: isColor ? \"\" : \"white\",\n                    clipPath: queue ? (index == 0 ? \"inset(0px 0px 0px)\" : \"inset(90px 0px 0px)\") : \"\",\n                    filter: invertWhole ? \"invert(1)\" : \"invert(0)\",\n                },\n                ref: (el) => !queue && el && el.style.setProperty(\"box-shadow\", \"0 0 8px rgb(0 0 0 / 30%)\", \"important\"),\n            },\n            !isColor &&\n                react.createElement(\"div\", {\n                    id: \"fad-upnext-blur\",\n                }),\n            react.createElement(\"div\", {\n                id: \"fad-upnext-image\",\n                style: {\n                    backgroundImage: `url(${meta.image_url})`,\n                },\n            }),\n            react.createElement(\n                \"div\",\n                {\n                    id: \"fad-upnext-details\",\n                    style: { filter: invertDetails ? \"invert(1)\" : \"invert(0)\" },\n                },\n                react.createElement(\n                    \"p\",\n                    {\n                        id: \"fad-upnext-provider\",\n                        style: {\n                            fontWeight: \"700\",\n                            overflow: \"hidden\",\n                            textOverflow: \"ellipsis\",\n                            maxWidth: \"300px\",\n                        },\n                    },\n                    queue\n                        ? isContext\n                            ? `Track No.${index + 1} in Queue from ${context}`\n                            : `Track No.${index + 1} from Queue`\n                        : isContext\n                        ? `Next From ${context}:`\n                        : \"Next in Queue:\"\n                    // isContext && react.createElement(\"em\", {}, `${context}:`)\n                ),\n                react.createElement(\n                    \"div\",\n                    {\n                        id: \"fad-upnext-title\",\n                        style: {\n                            fontSize: \"18px\",\n                            fontWeight: \"900\",\n                            overflow: \"hidden\",\n                            textOverflow: \"ellipsis\",\n                            maxWidth: \"300px\",\n                        },\n                    },\n                    meta.title\n                ),\n                react.createElement(\n                    \"div\",\n                    {\n                        id: \"fad-upnext-artist\",\n                        style: {\n                            fontWeight: \"500\",\n                            overflow: \"hidden\",\n                            textOverflow: \"ellipsis\",\n                            maxWidth: \"300px\",\n                        },\n                    },\n                    artistNames\n                )\n            )\n        );\n    };\n\n    const PlayerControls = () => {\n        const [value, setValue] = useState(Spicetify.Player.isPlaying());\n        let timer;\n        useEffect(() => {\n            const update = () => setValue(Spicetify.Player.isPlaying());\n            Spicetify.Player.addEventListener(\"onplaypause\", update);\n            // @ts-ignore\n            return () => Spicetify.Player.removeEventListener(\"onplaypause\", update);\n        });\n        return react.createElement(\n            \"div\",\n            { id: \"fad-controls\" },\n            react.createElement(ButtonIcon, {\n                // @ts-ignore\n                icon: Spicetify.SVGIcons[\"skip-back\"],\n                onClick: Spicetify.Player.back,\n            }),\n            react.createElement(ButtonIcon, {\n                // @ts-ignore\n                icon: Spicetify.SVGIcons[value ? \"pause\" : \"play\"],\n                onClick: Spicetify.Player.togglePlay,\n            }),\n            react.createElement(ButtonIcon, {\n                // @ts-ignore\n                icon: Spicetify.SVGIcons[\"skip-forward\"],\n                onClick: Spicetify.Player.next,\n                onMouseEnter: async () => {\n                    timer = setTimeout(async () => {\n                        let cont = document.createElement(\"div\");\n                        cont.id = \"fad-upnext-container\";\n                        let fore = document.querySelector(\"#fad-details\");\n                        fore.append(cont);\n                        reactDOM.render(await upNext({ index: 0, queue: false }), cont);\n                    }, 450);\n                },\n                onMouseLeave: () => {\n                    let cont = document.querySelectorAll(\"#fad-upnext-container\");\n                    for (const con of cont) {\n                        con.remove();\n                    }\n                    clearTimeout(timer);\n                },\n            })\n        );\n    };\n\n    const ExtraPlayerControls = () => {\n        const [isShuffle, setShuffle] = useState(Spicetify.Player.getShuffle());\n        const [isRepeat, setRepeat] = useState(Spicetify.Player.getRepeat());\n        const [isHeart, setHeart] = useState(Spicetify.Player.getHeart());\n        const [isPodcast, setPodcast] = useState(Spicetify.URI.isEpisode(Spicetify.Player.data.item.uri));\n        useEffect(() => {\n            const update = ({ data }) => {\n                data.item.metadata[\"collection.in_collection\"] == \"true\" ? setHeart(true) : setHeart(false);\n\n                setPodcast(Spicetify.URI.isEpisode(Spicetify.Player.data.item.uri));\n                // @ts-ignore\n                const state = Spicetify.Player.origin._state;\n                if (!state.restrictions?.canToggleShuffle) {\n                    setShuffle(undefined);\n                }\n                if (!state.restrictions?.canToggleRepeatContext || !state.restrictions?.canToggleRepeatTrack) {\n                    setRepeat(undefined);\n                }\n            };\n            Spicetify.Player.addEventListener(\"songchange\", update);\n            // @ts-ignore\n            return () => Spicetify.Player.removeEventListener(\"songchange\", update);\n        });\n        return react.createElement(\n            \"div\",\n            {\n                id: \"fad-extracontrols\",\n                style: {\n                    marginTop: CONFIG.vertical ? (CONFIG.enableControl ? \"-25px\" : \"10px\") : CONFIG.enableControl ? \"-25px\" : \"\",\n                    width: CONFIG.vertical ? (CONFIG.enableControl ? \"100%\" : \"\") : CONFIG.enableControl ? \"360px\" : \"\",\n                    alignSelf: !CONFIG.vertical && CONFIG.enableControl ? \"baseline\" : \"\",\n                },\n            },\n            react.createElement(ButtonIcon, {\n                // @ts-ignore\n                className: isShuffle\n                    ? \"dot-after\"\n                    : // @ts-ignore\n                    !Spicetify.Player.origin._state.restrictions?.canToggleShuffle || isShuffle == undefined\n                    ? \"crossed-out\"\n                    : \"\",\n                style: {\n                    marginLeft: CONFIG.vertical ? \"18px\" : \"\",\n                },\n                // @ts-ignore\n                icon: Spicetify.SVGIcons[\"shuffle\"],\n                onClick: () => {\n                    Spicetify.Player.toggleShuffle();\n                    setShuffle(!isShuffle);\n                },\n            }),\n            react.createElement(ButtonIcon, {\n                // @ts-ignore\n                className: isRepeat\n                    ? \"dot-after\"\n                    : // @ts-ignore\n                    !Spicetify.Player.origin._state.restrictions?.canToggleRepeatContext ||\n                      // @ts-ignore\n                      !Spicetify.Player.origin._state.restrictions?.canToggleRepeatTrack ||\n                      isRepeat == undefined\n                    ? \"crossed-out\"\n                    : \"\",\n                // @ts-ignore\n                icon: Spicetify.SVGIcons[isRepeat == 2 ? \"repeat-once\" : \"repeat\"],\n                onClick: () => {\n                    Spicetify.Player.toggleRepeat();\n                    setRepeat((isRepeat + 1) % 3);\n                },\n            }),\n            react.createElement(ButtonIcon, {\n                // @ts-ignore\n                icon: isPodcast\n                    ? Spicetify.SVGIcons[isHeart ? \"check-alt-fill\" : \"plus-alt\"]\n                    : Spicetify.SVGIcons[isHeart ? \"heart-active\" : \"heart\"],\n                style: {\n                    marginLeft: CONFIG.vertical || CONFIG.enableControl ? \"auto\" : \"\",\n                    marginRight: !CONFIG.vertical && !CONFIG.enableControl ? \"10px\" : \"\",\n                },\n                onClick: () => {\n                    Spicetify.Player.toggleHeart();\n                    setHeart(!isHeart);\n                },\n            }),\n            CONFIG.enableControl &&\n                react.createElement(ButtonIcon, {\n                    // @ts-ignore\n                    icon: '<path d=\"M2 2v5l4.33-2.5L2 2zm0 12h14v-1H2v1zm0-4h14V9H2v1zm7-5v1h7V5H9z\"></path>',\n                    className: \"fad-queue-button\",\n                    // @ts-ignore\n                    // @ts-ignore\n                    onClick: async (e) => {\n                        let ele = document.querySelector(\".fad-queue-button\");\n                        if (ele.classList.contains(\"dot-after\")) {\n                            let cont = document.querySelector(\"#fad-queue-container\");\n                            cont.remove();\n                            ele.classList.remove(\"dot-after\");\n                            return;\n                        }\n                        ele.classList.add(\"dot-after\");\n\n                        let body = document.querySelector(\"#fad-body\");\n\n                        let noticont = document.createElement(\"div\");\n                        noticont.className = \"main-notificationBubbleContainer-NotificationBubbleContainer\";\n                        let notitext = document.createElement(\"div\");\n                        notitext.className = \"main-notificationBubble-NotificationBubble main-notificationBubble-isNotice\";\n                        notitext.innerText = \"Generating queue...\";\n                        noticont.append(notitext);\n                        body.append(noticont);\n                        setTimeout(function () {\n                            noticont.remove();\n                        }, 1000);\n\n                        const next = await upNext({ index: 0, queue: false });\n                        const bottom = next.props.style.bottom;\n                        const right = next.props.style.right;\n\n                        CONFIG[\"viewing\"] = 0;\n\n                        let tracks = [];\n                        for (var i = 0; i < 10; i++) {\n                            try {\n                                tracks.push(await upNext({ index: i, queue: true }));\n                            } catch {\n                                break;\n                            }\n                        }\n\n                        let scroll = react.createElement(\n                            \"div\",\n                            {\n                                id: \"scroll-queue\",\n                                style: {\n                                    bottom: bottom,\n                                    right: right,\n                                    borderRadius: \"10px\",\n                                    boxShadow: \"0 0 8px rgb(0 0 0 / 30%)\",\n                                },\n                                onWheel: (e) => {\n                                    var now = Date.now();\n                                    if (time !== -1 && now - time < 1000) return;\n                                    time = now;\n\n                                    if (e.deltaY > 0) {\n                                        if (CONFIG[\"viewing\"] == tracks.length - 1) {\n                                            return;\n                                        }\n                                        CONFIG[\"viewing\"] += 1;\n                                        var item = document.querySelector(\"#scroll-queue\").childNodes[CONFIG[\"viewing\"]];\n                                        // @ts-ignore\n                                        item.style.clipPath = \"inset(0px 0px 0px)\";\n                                    } else {\n                                        if (CONFIG[\"viewing\"] == 0) {\n                                            return;\n                                        }\n                                        var item = document.querySelector(\"#scroll-queue\").childNodes[CONFIG[\"viewing\"]];\n                                        // @ts-ignore\n                                        item.style.clipPath = \"inset(90px 0px 0px)\";\n                                        CONFIG[\"viewing\"] -= 1;\n                                    }\n                                },\n                            },\n                            tracks\n                        );\n                        let fore = document.querySelector(\"#fad-details\");\n                        let wrapper = document.createElement(\"div\");\n                        wrapper.id = \"fad-queue-container\";\n                        fore.append(wrapper);\n                        reactDOM.render(scroll, wrapper);\n                    },\n                })\n        );\n    };\n\n    class FAD extends react.Component {\n        constructor(props) {\n            super(props);\n\n            this.state = {\n                title: \"\",\n                artist: \"\",\n                album: \"\",\n                cover: \"\",\n            };\n            this.currTrackImg = new Image();\n            this.nextTrackImg = new Image();\n            this.mousetrap = new Spicetify.Mousetrap();\n        }\n\n        async getAlbumDate(uri) {\n            const id = uri.replace(\"spotify:album:\", \"\");\n            // const albumInfo = await Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${id}/desktop`);\n\n            // const albumDate = new Date(albumInfo.year, (albumInfo.month || 1) - 1, albumInfo.day || 0);\n            // hermes protocol deprecated 1.1.81 onwards\n            const albumInfo = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/albums/${id}`);\n            const albumDate = new Date(albumInfo.release_date);\n            const recentDate = new Date();\n            recentDate.setMonth(recentDate.getMonth() - 6);\n            return albumDate.toLocaleString(\"default\", albumDate > recentDate ? { year: \"numeric\", month: \"short\" } : { year: \"numeric\" });\n        }\n\n        async fetchInfo() {\n            const meta = Spicetify.Player.data.item.metadata;\n            const prevUri = nextUri;\n            nextUri = Spicetify.Player.data.item.uri;\n            const uriFinal = nextUri.split(\":\")[2];\n            let isLocalOrEpisode =\n                Spicetify.URI.isLocalTrack(Spicetify.Player.data.item.uri) || Spicetify.URI.isEpisode(Spicetify.Player.data.item.uri);\n\n            if (!isLocalOrEpisode) {\n                const ximage = await Spicetify.CosmosAsync.get(\"https://api.spotify.com/v1/tracks/\" + uriFinal);\n                let images = ximage.album.images;\n                for (const image of images) {\n                    if (image.height == 640) {\n                        finImage = image.url;\n                    }\n                }\n                updateStyle();\n            } else {\n                finImage = meta.image_xlarge_url;\n                style.innerHTML =\n                    styleBase +\n                    styleChoices[CONFIG.vertical ? 1 : 0] +\n                    (window.innerHeight > window.innerWidth && CONFIG.verticalMonitor ? verticalMonitorStyle : \"\");\n            }\n\n            // prepare title\n            let rawTitle = meta.title;\n            if (CONFIG[\"trimTitle\"] === \"justFeat\") {\n                rawTitle = rawTitle\n                    .replace(/-\\s+(feat|with|ft).*/i, \"\")\n                    .replace(/(\\(|\\[)(feat|with|ft)\\.?\\s+.*(\\)|\\])/i, \"\")\n                    .trim();\n            } else if (CONFIG[\"trimTitle\"] === \"trimEvery\") {\n                rawTitle = rawTitle\n                    .replace(/\\(.+?\\)/g, \"\")\n                    .replace(/\\[.+?\\]/g, \"\")\n                    .replace(/\\s\\-\\s.+?$/, \"\")\n                    .replace(/,.+?$/, \"\")\n                    .trim();\n            }\n\n            // prepare artist\n            let artistName;\n            if (CONFIG.showAllArtists) {\n                artistName = Object.keys(meta)\n                    .filter((key) => key.startsWith(\"artist_name\"))\n                    .sort()\n                    .map((key) => meta[key])\n                    .join(\", \");\n            } else {\n                artistName = meta.artist_name;\n            }\n\n            // prepare album\n            let albumText = meta.album_title || \"\";\n            if (CONFIG.showAlbum) {\n                const albumURI = meta.album_uri;\n                if (albumURI?.startsWith(\"spotify:album:\")) {\n                    albumText += \" • \" + (await this.getAlbumDate(albumURI));\n                }\n            }\n\n            //          if (meta.image_xlarge_url === this.currTrackImg.src) {\n            if (finImage === this.currTrackImg.src) {\n                this.setState({\n                    title: rawTitle || \"\",\n                    artist: artistName || \"\",\n                    album: albumText || \"\",\n                });\n                if (CONFIG[\"optionBackground\"] === \"colorText\" && !isLocalOrEpisode) {\n                    this.animateCanvasColor(prevUri, prevUri);\n                } else if (CONFIG[\"optionBackground\"] === \"static\" && !isLocalOrEpisode) {\n                    this.animateCanvasColor(prevUri, prevUri, true);\n                } else if (CONFIG[\"optionBackground\"] === \"albumart\") {\n                    this.animateCanvas(this.currTrackImg, this.currTrackImg);\n                }\n                return;\n            }\n\n            if (isHidden) {\n                isHidden = false;\n                updateStyle();\n            }\n\n            // TODO: Pre-load next track\n            // Wait until next track image is downloaded then update UI text and images\n            const previousImg = this.currTrackImg.cloneNode();\n            this.currTrackImg.src = finImage;\n            this.currTrackImg.onload = () => {\n                const bgImage = this.currTrackImg.src;\n                if (CONFIG[\"optionBackground\"] === \"colorText\" && !isLocalOrEpisode) {\n                    this.animateCanvasColor(prevUri, nextUri);\n                } else if (CONFIG[\"optionBackground\"] === \"static\" && !isLocalOrEpisode) {\n                    this.animateCanvasColor(prevUri, nextUri, true);\n                } else if (CONFIG[\"optionBackground\"] === \"albumart\") {\n                    this.animateCanvas(previousImg, this.currTrackImg);\n                }\n                if (CONFIG.enableFade) {\n                    this.deets.style.animation = \"\";\n                    void this.deets.offsetWidth;\n                    this.deets.style.animation = \"textchange 1s forwards\";\n                }\n                this.setState({\n                    title: rawTitle || \"\",\n                    artist: artistName || \"\",\n                    album: albumText || \"\",\n                    cover: bgImage,\n                });\n\n                if (CONFIG.lyricsPlus) {\n                    autoHideLyrics();\n                }\n            };\n            this.currTrackImg.onerror = () => {\n                // Placeholder\n                this.currTrackImg.src =\n                    \"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCI+CiAgPHJlY3Qgc3R5bGU9ImZpbGw6I2ZmZmZmZiIgd2lkdGg9IjQwIiBoZWlnaHQ9IjQwIiB4PSIwIiB5PSIwIiAvPgogIDxwYXRoIGZpbGw9IiNCM0IzQjMiIGQ9Ik0yNi4yNSAxNi4xNjJMMjEuMDA1IDEzLjEzNEwyMS4wMTIgMjIuNTA2QzIwLjU5NCAyMi4xOTIgMjAuMDgxIDIxLjk5OSAxOS41MTkgMjEuOTk5QzE4LjE0MSAyMS45OTkgMTcuMDE5IDIzLjEyMSAxNy4wMTkgMjQuNDk5QzE3LjAxOSAyNS44NzggMTguMTQxIDI2Ljk5OSAxOS41MTkgMjYuOTk5QzIwLjg5NyAyNi45OTkgMjIuMDE5IDI1Ljg3OCAyMi4wMTkgMjQuNDk5QzIyLjAxOSAyNC40MjIgMjIuMDA2IDE0Ljg2NyAyMi4wMDYgMTQuODY3TDI1Ljc1IDE3LjAyOUwyNi4yNSAxNi4xNjJaTTE5LjUxOSAyNS45OThDMTguNjkyIDI1Ljk5OCAxOC4wMTkgMjUuMzI1IDE4LjAxOSAyNC40OThDMTguMDE5IDIzLjY3MSAxOC42OTIgMjIuOTk4IDE5LjUxOSAyMi45OThDMjAuMzQ2IDIyLjk5OCAyMS4wMTkgMjMuNjcxIDIxLjAxOSAyNC40OThDMjEuMDE5IDI1LjMyNSAyMC4zNDYgMjUuOTk4IDE5LjUxOSAyNS45OThaIi8+Cjwvc3ZnPgo=\";\n            };\n        }\n\n        animateCanvas(prevImg, nextImg) {\n            const { innerWidth: width, innerHeight: height } = window;\n            this.back.width = width;\n            this.back.height = height;\n            const dim = width > height ? width : height;\n\n            this.deets.style.filter = \"invert(0)\";\n            if (!(CONFIG[\"volumeBar\"] === \"disable\")) {\n                document.querySelector(\"#fad-volume\").style.filter = \"invert(0)\";\n            }\n\n            if (CONFIG.lyricsPlus) {\n                this.lyrics.style.setProperty(\"--lyrics-color-active\", \"#ffffff\");\n                this.lyrics.style.setProperty(\"--lyrics-color-inactive\", \"#ffffff50\");\n            }\n\n            const ctx = this.back.getContext(\"2d\");\n            ctx.imageSmoothingEnabled = false;\n            ctx.filter = `blur(30px) brightness(0.6)`;\n            const blur = 30;\n\n            if (!CONFIG.enableFade) {\n                ctx.globalAlpha = 1;\n                width > height\n                    ? ctx.drawImage(nextImg, -blur * 2, -blur * 2 - (width - height) / 2, dim + 4 * blur, dim + 4 * blur)\n                    : ctx.drawImage(nextImg, -blur * 2 - (height - width) / 2, -blur * 2, dim + 4 * blur, dim + 4 * blur);\n                return;\n            }\n\n            let factor = 0.0;\n            const animate = () => {\n                ctx.globalAlpha = 1;\n                width > height\n                    ? ctx.drawImage(prevImg, -blur * 2, -blur * 2 - (width - height) / 2, dim + 4 * blur, dim + 4 * blur)\n                    : ctx.drawImage(prevImg, -blur * 2 - (height - width) / 2, -blur * 2, dim + 4 * blur, dim + 4 * blur);\n                ctx.globalAlpha = Math.sin((Math.PI / 2) * factor);\n                width > height\n                    ? ctx.drawImage(nextImg, -blur * 2, -blur * 2 - (width - height) / 2, dim + 4 * blur, dim + 4 * blur)\n                    : ctx.drawImage(nextImg, -blur * 2 - (height - width) / 2, -blur * 2, dim + 4 * blur, dim + 4 * blur);\n\n                if (factor < 1.0) {\n                    factor += 0.016;\n                    requestAnimationFrame(animate);\n                }\n            };\n\n            requestAnimationFrame(animate);\n        }\n\n        async animateCanvasColor(prevUri, nextUri, isStatic = false) {\n            const { innerWidth: width, innerHeight: height } = window;\n            const ctx = this.back.getContext(\"2d\");\n\n            if (isStatic) {\n                if (ctx.fillStyle == CONFIG[\"staticColor\"]) {\n                    return;\n                } else {\n                    ctx.filter = \"brightness(1)\";\n                    ctx.imageSmoothingEnabled = false;\n                    ctx.globalAlpha = 1;\n                    ctx.fillStyle = CONFIG[\"staticColor\"];\n                    ctx.fillRect(0, 0, width, height);\n                    return;\n                }\n            }\n\n            prevColor = await fetchColors(prevUri);\n            nextColor = await fetchColors(nextUri);\n\n            this.back.width = width;\n            this.back.height = height;\n\n            CONFIG[\"color\"] = nextColor;\n            saveConfig();\n\n            this.deets.style.filter = \"invert(0)\";\n\n            if (!(CONFIG[\"volumeBar\"] === \"disable\")) {\n                document.querySelector(\"#fad-volume\").style.filter = \"invert(0)\";\n            }\n\n            if (CONFIG.lyricsPlus) {\n                this.lyrics.style.setProperty(\"--lyrics-color-active\", \"#ffffff\");\n                this.lyrics.style.setProperty(\"--lyrics-color-inactive\", \"#ffffff50\");\n            }\n\n            prevColor = prevColor[CONFIG[\"colorChoice\"]];\n            nextColor = nextColor[CONFIG[\"colorChoice\"]];\n            const luma =\n                parseInt(nextColor.substring(1, 3), 16) * 0.2126 +\n                parseInt(nextColor.substring(3, 5), 16) * 0.7152 +\n                parseInt(nextColor.substring(5, 7), 16) * 0.0722;\n\n            console.log(nextColor);\n            if (luma > 180) {\n                this.deets.style.filter = \"invert(1)\";\n\n                if (!(CONFIG[\"volumeBar\"] === \"disable\")) {\n                    document.querySelector(\"#fad-volume\").style.filter = \"invert(1)\";\n                }\n                if (CONFIG.lyricsPlus) {\n                    this.lyrics.style.setProperty(\"--lyrics-color-active\", \"#000000\");\n                    this.lyrics.style.setProperty(\"--lyrics-color-inactive\", \"#00000050\");\n                }\n            }\n\n            if (!CONFIG.enableFade) {\n                ctx.globalAlpha = 1;\n                ctx.fillStyle = nextColor;\n                ctx.fillRect(0, 0, width, height);\n                return;\n            }\n\n            let factor = 0.0;\n            const animate = () => {\n                ctx.globalAlpha = 1;\n                ctx.fillStyle = prevColor;\n                ctx.fillRect(0, 0, width, height);\n                ctx.globalAlpha = Math.sin((Math.PI / 2) * factor);\n                ctx.fillStyle = nextColor;\n                ctx.fillRect(0, 0, width, height);\n\n                if (factor < 1.0) {\n                    factor += 0.016;\n                    requestAnimationFrame(animate);\n                }\n            };\n            requestAnimationFrame(animate);\n        }\n\n        componentDidMount() {\n            this.updateInfo = this.fetchInfo.bind(this);\n            Spicetify.Player.addEventListener(\"songchange\", this.updateInfo);\n            this.updateInfo();\n\n            updateVisual = () => {\n                updateStyle();\n                this.fetchInfo();\n            };\n\n            this.onQueueChange = async (queue) => {\n                queue = queue.data;\n                let nextTrack;\n                if (queue.queued.length) {\n                    nextTrack = queue.queued[0];\n                } else {\n                    nextTrack = queue.nextUp[0];\n                }\n                this.nextTrackImg.src = nextTrack.metadata.image_xlarge_url;\n            };\n\n            const scaleLimit = { min: 0.1, max: 4, step: 0.05 };\n            this.onScaleChange = (event) => {\n                if (!event.ctrlKey) return;\n                let dir = event.deltaY < 0 ? 1 : -1;\n                let temp = (CONFIG[\"scale\"] || 1) + dir * scaleLimit.step;\n                if (temp < scaleLimit.min) {\n                    temp = scaleLimit.min;\n                } else if (temp > scaleLimit.max) {\n                    temp = scaleLimit.max;\n                }\n                CONFIG[\"scale\"] = temp;\n                saveConfig();\n                updateVisual();\n            };\n\n            Spicetify.Platform.PlayerAPI._events.addListener(\"queue_update\", this.onQueueChange);\n            this.mousetrap.bind(\"esc\", deactivate);\n            window.dispatchEvent(new Event(\"fad-request\"));\n        }\n\n        componentWillUnmount() {\n            Spicetify.Player.removeEventListener(\"songchange\", this.updateInfo);\n            Spicetify.Platform.PlayerAPI._events.removeListener(\"queue_update\", this.onQueueChange);\n            this.mousetrap.unbind(\"esc\");\n        }\n\n        render() {\n            return react.createElement(\n                \"div\",\n                {\n                    id: \"full-app-display\",\n                    className: \"Video VideoPlayer--fullscreen VideoPlayer--landscape\",\n                    onDoubleClick: deactivate,\n                    onContextMenu: openConfig,\n                },\n                !(CONFIG[\"optionBackground\"] === \"grad\") &&\n                    react.createElement(\"canvas\", {\n                        id: \"fad-background\",\n                        ref: (el) => (this.back = el),\n                    }),\n                CONFIG[\"optionBackground\"] === \"grad\" &&\n                    react.createElement(\n                        \"div\",\n                        { id: \"fad-gradient-background\" },\n                        react.createElement(\"img\", {\n                            src: this.state.cover,\n                            className: \"fad-grad-image\",\n                            style: {\n                                right: \"-15%\",\n                                top: \"-20%\",\n                                zIndex: 10,\n                                transform: \"scale(2)\",\n                            },\n                        }),\n                        react.createElement(\"img\", {\n                            src: this.state.cover,\n                            className: \"fad-grad-image\",\n                            style: {\n                                left: \"-5%\",\n                                bottom: \"-10%\",\n                                transform: \"scale(1.5)\",\n                                zIndex: 1,\n                                animationDirection: \"reverse\",\n                            },\n                        }),\n                        react.createElement(\"img\", {\n                            src: this.state.cover,\n                            className: \"fad-grad-image\",\n                            style: {\n                                width: \"200%\",\n                                right: \"-50%\",\n                                top: \"-33%\",\n                                filter: \"blur(69px) brightness(0.6)\",\n                                zIndex: 0,\n                                animationDirection: \"reverse\",\n                            },\n                        })\n                    ),\n                react.createElement(\"div\", { id: \"fad-header\" }),\n                react.createElement(\n                    \"div\",\n                    { id: \"fad-body\" },\n                    react.createElement(\n                        \"div\",\n                        {\n                            id: \"fad-foreground\",\n                            style: {\n                                \"--fad-scale\": CONFIG[\"scale\"] || 1,\n                                zIndex: 20,\n                            },\n                            ref: (el) => {\n                                if (!el) return;\n                                el.onmousewheel = this.onScaleChange;\n                            },\n                        },\n                        react.createElement(\n                            \"div\",\n                            { id: \"fad-art\" },\n                            react.createElement(\n                                \"div\",\n                                {\n                                    id: \"fad-art-image\",\n                                    className: CONFIG.enableFade && \"fad-background-fade\",\n                                    style: {\n                                        backgroundImage: `url(\"${this.state.cover}\")`,\n                                    },\n                                },\n                                react.createElement(\"div\", { id: \"fad-art-inner\" })\n                            )\n                        ),\n                        react.createElement(\n                            \"div\",\n                            { id: \"fad-details\", ref: (el) => (this.deets = el) },\n                            react.createElement(\"div\", { id: \"fad-title\" }, this.state.title),\n                            react.createElement(SubInfo, {\n                                id: \"fad-artist\",\n                                text: this.state.artist,\n                                // @ts-ignore\n                                icon: Spicetify.SVGIcons.artist,\n                            }),\n                            CONFIG.showAlbum &&\n                                react.createElement(SubInfo, {\n                                    id: \"fad-album\",\n                                    text: this.state.album,\n                                    // @ts-ignore\n                                    icon: Spicetify.SVGIcons.album,\n                                }),\n                            react.createElement(\n                                \"div\",\n                                {\n                                    id: \"fad-status\",\n                                    className: (CONFIG.enableControl || CONFIG.enableProgress) && \"active\",\n                                    style: {\n                                        flexDirection: !CONFIG.vertical && CONFIG.enableControl && CONFIG.enableExtraControl ? \"column\" : \"\",\n                                    },\n                                },\n                                CONFIG.enableControl && react.createElement(PlayerControls),\n                                CONFIG.enableExtraControl && react.createElement(ExtraPlayerControls),\n                                CONFIG.enableProgress && react.createElement(ProgressBar)\n                            )\n                        )\n                    ),\n                    // @ts-ignore\n                    !(CONFIG[\"volumeBar\"] === \"disable\") && react.createElement(VolumeBar),\n                    CONFIG.lyricsPlus &&\n                        react.createElement(\"div\", {\n                            id: \"fad-lyrics-plus-container\",\n                            style: {\n                                \"--lyrics-color-active\": \"#ffffff\",\n                                \"--lyrics-color-inactive\": \"#ffffff50\",\n                            },\n                            ref: (el) => (this.lyrics = el),\n                        })\n                )\n            );\n        }\n    }\n\n    const classes = [\"video\", \"video-full-screen\", \"video-full-window\", \"video-full-screen--hide-ui\", \"fad-activated\"];\n\n    const container = document.createElement(\"div\");\n    container.id = \"fad-main\";\n    let lastApp;\n\n    async function toggleFullscreen() {\n        if (CONFIG.enableFullscreen) {\n            await document.documentElement.requestFullscreen();\n            // @ts-ignore\n        } else if (document.webkitIsFullScreen) {\n            await document.exitFullscreen();\n        }\n    }\n\n    async function activate() {\n        await toggleFullscreen();\n\n        document.body.classList.add(...classes);\n        document.body.append(style, container);\n        reactDOM.render(react.createElement(FAD), container);\n\n        requestLyricsPlus();\n    }\n\n    function deactivate() {\n        // @ts-ignore\n        if (CONFIG.enableFullscreen || document.webkitIsFullScreen) {\n            document.exitFullscreen();\n        }\n        document.body.classList.remove(...classes);\n        reactDOM.unmountComponentAtNode(container);\n        style.remove();\n        container.remove();\n        window.dispatchEvent(new Event(\"fad-request\"));\n\n        if (lastApp && lastApp !== \"/lyrics-plus\") {\n            Spicetify.Platform.History.push(lastApp);\n        }\n    }\n\n    function toggleFad() {\n        if (document.body.classList.contains(\"fad-activated\")) {\n            deactivate();\n        } else {\n            activate();\n        }\n    }\n\n    function updateStyle() {\n        style.innerHTML =\n            styleBase +\n            styleChoices[CONFIG.vertical ? 1 : 0] +\n            (checkLyricsPlus() && CONFIG.lyricsPlus && !isHidden\n                ? lyricsPlusBase +\n                  lyricsPlusStyleChoices[CONFIG.vertical ? 1 : 0] +\n                  (window.innerHeight > window.innerWidth && CONFIG.verticalMonitor ? verticalMonitorStyle : \"\")\n                : \"\");\n    }\n\n    function checkLyricsPlus() {\n        return Spicetify.Config?.custom_apps?.includes(\"lyrics-plus\") || !!document.querySelector(\"a[href='/lyrics-plus']\");\n    }\n\n    function autoHideLyrics() {\n        // @ts-ignore\n        if (!document.querySelector(\"#fad-lyrics-plus-container\").innerText) {\n            setTimeout(autoHideLyrics, 100);\n        } else {\n            // @ts-ignore\n            if (document.querySelector(\"#fad-lyrics-plus-container\").innerText == \"(• _ • )\") {\n                isHidden = true;\n                updateStyle();\n            }\n        }\n    }\n\n    function requestLyricsPlus() {\n        if (CONFIG.lyricsPlus && checkLyricsPlus()) {\n            lastApp = Spicetify.Platform.History.location.pathname;\n            if (lastApp !== \"/lyrics-plus\") {\n                Spicetify.Platform.History.push(\"/lyrics-plus\");\n            }\n        }\n        window.dispatchEvent(new Event(\"fad-request\"));\n        autoHideLyrics();\n    }\n\n    function getConfig() {\n        try {\n            const parsed = JSON.parse(Spicetify.LocalStorage.get(\"full-app-display-config\") || \"{}\");\n            if (parsed && typeof parsed === \"object\") {\n                return parsed;\n            }\n            throw \"\";\n        } catch {\n            Spicetify.LocalStorage.set(\"full-app-display-config\", \"{}\");\n            return {};\n        }\n    }\n\n    function saveConfig() {\n        Spicetify.LocalStorage.set(\"full-app-display-config\", JSON.stringify(CONFIG));\n    }\n\n    const ConfigItem = ({ name, field, func, disabled = false }) => {\n        const [value, setValue] = useState(CONFIG[field]);\n        return react.createElement(\n            \"div\",\n            { className: \"setting-row\" },\n            react.createElement(\"label\", { className: \"col description\" }, name),\n            react.createElement(\n                \"div\",\n                { className: \"col action\" },\n                react.createElement(\n                    \"button\",\n                    {\n                        className: \"switch\" + (value ? \"\" : \" disabled\"),\n                        disabled,\n                        onClick: () => {\n                            const state = !value;\n                            CONFIG[field] = state;\n                            setValue(state);\n                            saveConfig();\n                            func();\n                        },\n                    },\n                    // @ts-ignore\n                    react.createElement(DisplayIcon, { icon: Spicetify.SVGIcons.check, size: 16 })\n                )\n            )\n        );\n    };\n\n    const ConfigSelection = ({ name, field, options, def, func }) => {\n        const [value, setValue] = useState(CONFIG[field] ?? def);\n        return react.createElement(\n            \"div\",\n            { className: \"setting-row\" },\n            react.createElement(\"label\", { className: \"col description\" }, name),\n            react.createElement(\n                \"div\",\n                { className: \"col action\" },\n                react.createElement(\n                    \"select\",\n                    {\n                        value,\n                        onChange: (e) => {\n                            setValue(e.target.value);\n                            CONFIG[field] = e.target.value;\n                            saveConfig();\n                            func();\n                        },\n                    },\n                    Object.keys(options).map((item) =>\n                        react.createElement(\n                            \"option\",\n                            {\n                                value: item,\n                            },\n                            options[item]\n                        )\n                    )\n                )\n            )\n        );\n    };\n\n    const ConfigInput = ({ name, field, func, isColor = false }) => {\n        const [value, setValue] = useState(CONFIG[field]);\n        return react.createElement(\n            \"div\",\n            { className: \"setting-row\" },\n            react.createElement(\"label\", { className: \"col description\" }, name),\n            react.createElement(\n                \"div\",\n                { className: \"col action\" },\n                react.createElement(\"input\", {\n                    type: isColor ? \"color\" : \"\",\n                    value,\n                    className: \"input\",\n                    onChange: (e) => {\n                        setValue(e.target.value);\n                        CONFIG[field] = e.target.value;\n                        saveConfig();\n                        func();\n                    },\n                })\n            )\n        );\n    };\n\n    const ConfigHotkey = ({ name, field, def, onChange = () => {} }) => {\n        const [value, setValue] = useState(CONFIG[field] ?? def);\n        const [trap] = useState(new Spicetify.Mousetrap());\n\n        function record() {\n            trap.handleKey = (character, modifiers, e) => {\n                if (e.type == \"keydown\") {\n                    const sequence = [...new Set([...modifiers, character])];\n                    if (sequence.length === 1 && sequence[0] === \"esc\") {\n                        onChange(\"\");\n                        setValue(\"\");\n                        return;\n                    }\n                    setValue(sequence.join(\"+\"));\n                }\n            };\n        }\n\n        function finishRecord() {\n            trap.handleKey = () => {};\n            onChange(value);\n        }\n\n        return react.createElement(\n            \"div\",\n            {\n                className: \"setting-row\",\n            },\n            react.createElement(\n                \"label\",\n                {\n                    className: \"col description\",\n                },\n                name\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"col action\",\n                },\n                react.createElement(\"input\", {\n                    value,\n                    onFocus: record,\n                    onBlur: finishRecord,\n                })\n            )\n        );\n    };\n\n    const colorRow = ({ name, color }) => {\n        let originalColor;\n        const modal = document.getElementsByTagName(\"generic-modal\");\n        return react.createElement(\n            \"div\",\n            { className: \"color-row\" },\n            react.createElement(\"label\", { className: \"col description\" }, name),\n            react.createElement(\n                \"div\",\n                { className: \"col action\" },\n                react.createElement(\"div\", {\n                    className: \"col color\",\n                    style: {\n                        height: \"20px\",\n                        width: \"20px\",\n                        border: \"2px solid black\",\n                        clear: \"both\",\n                        backgroundColor: CONFIG[\"color\"][color],\n                    },\n                    // @ts-ignore\n                    // @ts-ignore\n                    onMouseEnter: (e) => {\n                        originalColor = CONFIG[\"colorChoice\"];\n                        CONFIG[\"colorChoice\"] = color;\n                        // @ts-ignore\n                        modal[0].style.opacity = 0.37;\n                        updateVisual();\n                    },\n                    // @ts-ignore\n                    // @ts-ignore\n                    onMouseLeave: (e) => {\n                        CONFIG[\"colorChoice\"] = originalColor;\n                        // @ts-ignore\n                        modal[0].style.opacity = 1;\n                        updateVisual();\n                    },\n                    // @ts-ignore\n                    // @ts-ignore\n                    onClick: (e) => {\n                        CONFIG[\"colorChoice\"] = color;\n                        updateVisual();\n                        // @ts-ignore\n                        modal[0].style.opacity = 1;\n                        Spicetify.PopupModal.hide();\n                    },\n                })\n            )\n        );\n    };\n\n    function openColor(event) {\n        event.preventDefault();\n        const style = react.createElement(\"style\", {\n            dangerouslySetInnerHTML: {\n                __html: `\n.color-row::after {\n    content: \"\";\n    display: table;\n    clear: both;\n}\n.color-row .col {\n    display: flex;\n    padding: 10px 0;\n    align-items: center;\n}\n.color-row .col.description {\n    float: left;\n    padding-right: 15px;\n}\n.color-row .col.action {\n    float: right;\n    text-align: right;\n}\n`,\n            },\n        });\n        let colorContainer = react.createElement(\n            \"div\",\n            null,\n            style,\n            react.createElement(colorRow, { name: \"Dark Vibrant\", color: \"DARK_VIBRANT\" }),\n            react.createElement(colorRow, { name: \"Desaturated\", color: \"DESATURATED\" }),\n            react.createElement(colorRow, { name: \"Light Vibrant\", color: \"LIGHT_VIBRANT\" }),\n            react.createElement(colorRow, { name: \"Vibrant\", color: \"VIBRANT\" }),\n            react.createElement(colorRow, { name: \"Vibrant(NA)\", color: \"VIBRANT_NON_ALARMING\" })\n        );\n        Spicetify.PopupModal.display({\n            title: \"Color Display (Hover to preview)\",\n            content: colorContainer,\n        });\n    }\n\n    function openConfig(event) {\n        try {\n            event.preventDefault();\n        } catch {}\n        const style = react.createElement(\"style\", {\n            dangerouslySetInnerHTML: {\n                __html: `\n.setting-row::after {\n    content: \"\";\n    display: table;\n    clear: both;\n}\n.setting-row .col {\n    display: flex;\n    padding: 10px 0;\n    align-items: center;\n}\n.setting-row .col.description {\n    float: left;\n    padding-right: 15px;\n}\n.setting-row .col.action {\n    float: right;\n    text-align: right;\n}\n.setting-row .col.action input {\n    padding-left: 10px;\n}\nbutton.switch {\n    align-items: center;\n    border: 0px;\n    border-radius: 50%;\n    background-color: rgba(var(--spice-rgb-shadow), .7);\n    color: var(--spice-text);\n    cursor: pointer;\n    display: flex;\n    margin-inline-start: 12px;\n    padding: 8px;\n}\nbutton.switch.disabled,\nbutton.switch[disabled] {\n    color: rgba(var(--spice-rgb-text), .3);\n}\nselect {\n    color: var(--spice-text);\n    background: rgba(var(--spice-rgb-shadow), .7);\n    border: 0;\n    height: 32px;\n}\n`,\n            },\n        });\n        let configContainer = react.createElement(\n            \"div\",\n            null,\n            style,\n            react.createElement(ConfigItem, {\n                name: checkLyricsPlus() ? \"Enable Lyrics Plus integration\" : \"Unable to find Lyrics Plus\",\n                field: \"lyricsPlus\",\n                func: () => {\n                    updateVisual();\n                    requestLyricsPlus();\n                    openConfig();\n                },\n                disabled: !checkLyricsPlus(),\n            }),\n            react.createElement(ConfigSelection, {\n                name: \"Background\",\n                field: \"optionBackground\",\n                options: {\n                    albumart: \"Album Art\",\n                    colorText: \"Colorful background\",\n                    static: \"Static Color\",\n                    // grad: \"Gradient\",\n                },\n                func: updateVisual,\n            }),\n            CONFIG[\"optionBackground\"] == \"static\" &&\n                react.createElement(ConfigInput, {\n                    name: \"Select static color:\",\n                    field: \"staticColor\",\n                    func: () => {\n                        const ctx = document.getElementById(\"fad-background\")?.getContext(\"2d\");\n                        ctx.filter = \"brightness(1)\";\n                        ctx.imageSmoothingEnabled = false;\n                        ctx.globalAlpha = 1;\n                        ctx.fillStyle = CONFIG[\"staticColor\"];\n                        ctx.fillRect(0, 0, window.innerWidth, window.innerHeight);\n                    },\n                    isColor: true,\n                }),\n            react.createElement(ConfigItem, { name: \"Enable progress bar\", field: \"enableProgress\", func: updateVisual }),\n            react.createElement(ConfigSelection, {\n                name: \"Enable volume bar\",\n                field: \"volumeBar\",\n                options: {\n                    disable: \"Disable\",\n                    onlyHover: \"Show on hover only\",\n                    alwaysOn: \"Show always\",\n                },\n                func: updateVisual,\n            }),\n            react.createElement(ConfigItem, { name: \"Enable controls\", field: \"enableControl\", func: updateVisual }),\n            react.createElement(ConfigItem, { name: \"Enable extra controls\", field: \"enableExtraControl\", func: updateVisual }),\n            react.createElement(ConfigSelection, {\n                name: \"Trim title\",\n                field: \"trimTitle\",\n                options: {\n                    dontTrim: \"Don't trim title\",\n                    justFeat: \"Trim just feat. and with \",\n                    trimEvery: \"Trim Everything\",\n                },\n                func: updateVisual,\n            }),\n            react.createElement(ConfigItem, { name: \"Show album\", field: \"showAlbum\", func: updateVisual }),\n            react.createElement(ConfigItem, { name: \"Show all artists\", field: \"showAllArtists\", func: updateVisual }),\n            react.createElement(ConfigItem, { name: \"Show icons\", field: \"icons\", func: updateVisual }),\n            react.createElement(ConfigItem, { name: \"Vertical mode\", field: \"vertical\", func: updateVisual }),\n            CONFIG.lyricsPlus &&\n                window.innerHeight > window.innerWidth &&\n                react.createElement(ConfigItem, { name: \"Vertical Monitor Mode\", field: \"verticalMonitor\", func: updateStyle }),\n            react.createElement(ConfigItem, { name: \"Enable fullscreen\", field: \"enableFullscreen\", func: toggleFullscreen }),\n            react.createElement(ConfigItem, { name: \"Enable song change animation\", field: \"enableFade\", func: updateVisual }),\n            react.createElement(ConfigHotkey, {\n                name: \"FAD hotkey: \",\n                field: \"hotkey\",\n                def: \"alt+f\",\n                onChange: (key) => {\n                    CONFIG[\"hotkey\"] = key;\n                    saveConfig();\n                    Spicetify.Mousetrap.bind(key, toggleFad);\n                },\n            }),\n            react.createElement(ConfigItem, { name: \"Enable development features\", field: \"enableDev\", func: openConfig }),\n\n            CONFIG.enableDev &&\n                react.createElement(ConfigSelection, {\n                    name: \"Color Choice (Press F6 for colors)\",\n                    field: \"colorChoice\",\n                    options: {\n                        DARK_VIBRANT: \"Dark Vibrant\",\n                        DESATURATED: \"Desaturated\",\n                        LIGHT_VIBRANT: \"Light Vibrant\",\n                        VIBRANT: \"Vibrant\",\n                        VIBRANT_NON_ALARMING: \"Vibrant(NA)\",\n                    },\n                    def: \"LIGHT_VIBRANT\",\n                    func: updateVisual,\n                })\n        );\n        Spicetify.PopupModal.display({\n            title: \"Full App Display\",\n            content: configContainer,\n        });\n    }\n\n    // Add activator on top bar\n    new Spicetify.Topbar.Button(\n        \"Full App Display\",\n        // @ts-ignore\n        `<svg role=\"img\" height=\"16\" width=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">${Spicetify.SVGIcons.projector}</svg>`,\n        activate\n    );\n\n    Spicetify.Mousetrap.bind(CONFIG[\"hotkey\"] ?? \"alt+f\", toggleFad);\n    Spicetify.Mousetrap.bind(\"f6\", openColor);\n})();\n"
  },
  {
    "path": "goToSong/README.md",
    "content": "# Go to Song\nFilename : `goToSong.js`\n\nGo to the currrently playing song in a playlist **/or/** currently playing playlist.\n\n## To use:\n\n* Currently playing playlist: Go to Profile > GoToSong > Choose \"Go To Song in current Playlist\" \n\n![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview1.jpg)\n\n* Any Playlist: Right Click on the Playlist, and choose \"Go to Currently Playing Song\"\n\n![Preview2](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/preview2.jpg)\n\n## Note:\nYou may need to adjust your delay if it's giving an error, follow the instructions in the popup.\n\n![Adjust](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/goToSong/adjust.jpg)\n\n## More\n🌟 Like it? Gimme some love!    \n[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)\n\n"
  },
  {
    "path": "goToSong/goToSong.js",
    "content": "// @ts-check\n// NAME: goToSong\n// AUTHOR: huhridge\n// DESCRIPTION: Go to currently playing song in playlists.\n/// <reference path=\"../globals.d.ts\" />\n\n(function goToSong(){\n    const { Player, Menu, LocalStorage, Platform, ContextMenu, URI, React: react, ReactDOM: reactDOM} = Spicetify\n    let tracks, index, playlisturi, curruri;\n\n    if (!(Player && Menu && LocalStorage && Platform)) {\n        setTimeout(goToSong, 1000)\n        return\n    }\n\n    function delay(delayInms) {\n        return new Promise(resolve => {\n        setTimeout(() => {\n            resolve(2);\n        }, delayInms);\n        });\n    }\n\n    if (!Spicetify.LocalStorage.get(\"goToDelay\")){\n        Spicetify.LocalStorage.set(\"goToDelay\", '200')\n    }\n\n    const ConfigInput = ({ name, lkey}) => {\n        const [value, setValue] = react.useState(Spicetify.LocalStorage.get(\"goToDelay\"));\n\n        const setValueCallback = react.useCallback(\n            (event) => {\n                const value = event.target.value;\n                setValue(value);\n                Spicetify.LocalStorage.set(lkey, value)\n            },\n            [value]\n        );\n    \n        return react.createElement(\n            \"div\",\n            {\n                className: \"setting-row\",\n            },\n            react.createElement(\n                \"label\",\n                {\n                    className: \"col description\",\n                },\n                name\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"col action\",\n                },\n                react.createElement(\"input\", {\n                    type: \"number\",\n                    value,\n                    onChange: setValueCallback,\n                })\n            )\n        );\n    };\n\n    function shouldDisplayGoTo(uris) {\n        if (uris.length > 1) {\n            return false;\n        }\n\n        const uri = uris[0];\n        const uriObj = Spicetify.URI.fromString(uri);\n        if (uriObj.type == Spicetify.URI.Type.PLAYLIST || uriObj.type == Spicetify.URI.Type.PLAYLIST_V2) {\n            return true;\n        }\n        return false;\n    }\n\n    async function scrollSong(playlisturi) {\n        tracks = await Spicetify.Platform.PlaylistAPI.getContents(playlisturi)\n        curruri = Spicetify.Player.data.item.uri\n        for (var i=0; i < tracks.items.length; i++){\n            if (tracks.items[i].uri == curruri){\n                break;\n            }\n        }\n        if (i == tracks.items.length){\n            Spicetify.showNotification(\"Song not in Playlist.\")\n            return;\n        }\n        const playlisturl = '/playlist/' + playlisturi.split(':')[2]\n        if (!(Spicetify.Platform.History.location.pathname == playlisturl)){\n            Spicetify.Platform.History.push(playlisturl)\n            await delay(1000)\n        }\n        if ((i+2)<58 && i == (tracks.items.length-1)){\n            document.querySelector(`[aria-rowindex=\"${i+1}\"]`).click()\n            await delay(1)\n            document.querySelector(`[aria-rowindex=\"${i+2}\"]`).click()\n            return;\n        }\n        if ((i+2)<58){\n            document.querySelector(`[aria-rowindex=\"${i+2}\"]`).click()\n        }\n        else{\n            try {\n                for (var j=57; j < tracks.items.length; j += 28){\n                    document.querySelector(`[aria-rowindex=\"${j}\"]`).click()\n                    const delayms = Spicetify.LocalStorage.get('goToDelay')\n                    await delay(Number(delayms))\n                    if (Math.abs(j-(i+2)) < 28){\n                        break\n                    }\n                }\n                document.querySelector(`[aria-rowindex=\"${i+2}\"]`).click()\n            }\n            catch(err){\n                Spicetify.showNotification(err + '    Try to Adjust your delay in Profile > GoToSong > Set Delay')\n            }\n\n        }\n    }\n\n    async function gotoCurrPlay() { \n        if (Spicetify.Player.data.context_uri.startsWith('spotify:playlist:')){\n            playlisturi = Spicetify.Player.data.context_uri\n            await scrollSong(playlisturi)\n        }\n        else {\n            Spicetify.showNotification('The song currently played is not part of a playlist.')            \n        }\n    }\n\n    async function gotoselectedPlay(uris){    \n        await scrollSong(uris[0])\n    }\n\n    let configContent = react.createElement(ConfigInput, {name: \"Set Delay(in ms) (200 default)\", lkey: \"goToDelay\"})\n\n\n    const goTocurrPlay = new Spicetify.Menu.Item(\n        \"Go To Song in Playlist\",\n        false,\n        gotoCurrPlay,\n    )\n\n    const goToConfig = new Spicetify.Menu.Item(\"Set Delay\", false, () =>{\n        Spicetify.PopupModal.display({\n            title: \"Set Delay\",\n            content: configContent,\n        })\n    })\n\n    new Spicetify.Menu.SubMenu(\"GoToSong\", [goTocurrPlay,goToConfig]).register();\n    \n    new Spicetify.ContextMenu.Item(\"Go To Currently Playing Song\", gotoselectedPlay, shouldDisplayGoTo).register();\n\n})();\n\n"
  },
  {
    "path": "listPlaylistsWithSong/README.md",
    "content": "# List Playlists with Song\nFilename : `listPlaylistsWithSong.js`\n\nAdds context menu button to view playlists in your library that contain the selected song.\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview.gif)\n\n## To use:\n\nRight Click on selected song, and click \"List playlists with this Song\".\n\n![Preview1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/listPlaylistsWithSong/preview1.jpg)\n\n\n### Note:\n~~Currently, doesn't work on the currently playing song(like in the bottom bar), finding a workaround.~~\nNow it shows up, fixed by your truly!\n\n\n## More\n🌟 Like it? Gimme some love!    \n[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)\n"
  },
  {
    "path": "listPlaylistsWithSong/listPlaylistsWithSong.js",
    "content": "//@ts-check\r\n// NAME: ListPlaylistsWithSong\r\n// AUTHOR: huhridge (based on elijaholmos's version)\r\n// DESCRIPTION: Adds context menu button to view playlists in your library that contain the selected song\r\n/// <reference path=\"../globals.d.ts\" />\r\n\r\n(async function listPlaylistsWithSong() {\r\n    const { Player, Menu, LocalStorage, Platform, React: react, ReactDOM: reactDOM } = Spicetify;\r\n\r\n    if (!(Player && Menu && LocalStorage && Platform)) {\r\n        setTimeout(listPlaylistsWithSong, 1000);\r\n        return;\r\n    }\r\n\r\n    function delay(delayInms) {\r\n        return new Promise((resolve) => {\r\n            setTimeout(() => {\r\n                resolve(2);\r\n            }, delayInms);\r\n        });\r\n    }\r\n\r\n    // const user = await Spicetify.Platform.UserAPI.getUser()\r\n\r\n    async function recursivePlaylistFolder(folder) {\r\n        //to get every playlist no matter how deep, thanks to elijaholmos for reminding me, else i would have forgotten it.\r\n        let playlists = [];\r\n        for (const playlist of folder) {\r\n            if (playlist.type == \"playlist\") {\r\n                if ((playlist.isCollaborative || playlist.isOwnedBySelf || playlist.canAdd) && playlist.totalLength > 0) {\r\n                    let image;\r\n                    try {\r\n                        image = !playlist.images[0]\r\n                            ? (await Spicetify.Platform.PlaylistAPI.getMetadata(playlist.uri)).images[0].url\r\n                            : playlist.images[0].url;\r\n                    } catch {\r\n                        image = \"\";\r\n                    }\r\n                    playlists.push({\r\n                        uri: playlist.uri,\r\n                        title: playlist.name,\r\n                        desc: playlist.description,\r\n                        isCollab: playlist.isCollaborative || playlist.canAdd,\r\n                        noOfSongs: playlist.totalLength,\r\n                        created: playlist.addedAt.toLocaleString(\"default\", { year: \"numeric\", month: \"short\", day: \"numeric\" }),\r\n                        image: image,\r\n                    });\r\n                }\r\n            } else if (playlist.type == \"folder\") {\r\n                playlists.push(...(await recursivePlaylistFolder(playlist.items)));\r\n            }\r\n        }\r\n        return playlists;\r\n    }\r\n\r\n    async function getUserLibrary() {\r\n        let playlistsToCheck = Array();\r\n        const userContents = await Spicetify.Platform.RootlistAPI.getContents();\r\n        for (const playlist of userContents.items) {\r\n            if (playlist.type == \"playlist\") {\r\n                if ((playlist.isCollaborative || playlist.isOwnedBySelf || playlist.canAdd) && playlist.totalLength > 0) {\r\n                    let image;\r\n                    try {\r\n                        image = !playlist.images[0]\r\n                            ? (await Spicetify.Platform.PlaylistAPI.getMetadata(playlist.uri)).images[0].url\r\n                            : playlist.images[0].url;\r\n                    } catch {\r\n                        image = \"\";\r\n                    }\r\n                    playlistsToCheck.push({\r\n                        uri: playlist.uri,\r\n                        title: playlist.name,\r\n                        desc: playlist.description,\r\n                        isCollab: playlist.isCollaborative || playlist.canAdd,\r\n                        noOfSongs: playlist.totalLength,\r\n                        created: playlist.addedAt.toLocaleString(\"default\", { year: \"numeric\", month: \"short\", day: \"numeric\" }),\r\n                        image: image,\r\n                    });\r\n                }\r\n            } else if (playlist.type == \"folder\") {\r\n                playlistsToCheck.push(...(await recursivePlaylistFolder(playlist.items)));\r\n            }\r\n        }\r\n        return playlistsToCheck;\r\n    }\r\n\r\n    async function checkPlaylist(playlist, songUri) {\r\n        var songFound = false;\r\n        let addedAtDate;\r\n        const tracks = await Spicetify.Platform.PlaylistAPI.getContents(playlist.uri);\r\n        for (var i = 0; i < tracks.items.length; i++) {\r\n            if (tracks.items[i].uri == songUri) {\r\n                songFound = true;\r\n                addedAtDate = new Date(tracks.items[i].addedAt).toLocaleString(\"default\", { year: \"numeric\", month: \"short\", day: \"numeric\" });\r\n                break;\r\n            }\r\n        }\r\n        if (songFound) {\r\n            playlist.index = i + 1;\r\n            playlist.songAddedAt = addedAtDate;\r\n            return playlist;\r\n        } else {\r\n            return false;\r\n        }\r\n    }\r\n\r\n    const playlistCard = ({ playlist }) => {\r\n        let isDesc = false;\r\n        if (playlist.desc) {\r\n            isDesc = true;\r\n        }\r\n        return react.createElement(\r\n            \"div\",\r\n            {\r\n                className: \"contentSpacing main-entityHeader-container main-entityHeader-nonWrapped main-trackList-trackListHeaderRow\",\r\n                style: {\r\n                    minHeight: \"280px\",\r\n                    marginLeft: \"2%\",\r\n                    marginRight: \"2%\",\r\n                    justifyContent: \"left\",\r\n                },\r\n            },\r\n            react.createElement(\r\n                \"div\",\r\n                { className: \"main-entityHeader-imageContainer\" },\r\n                react.createElement(\"img\", {\r\n                    className: \"main-image-image\",\r\n                    src: playlist.image,\r\n                    style: {\r\n                        height: \"inherit\",\r\n                    },\r\n                })\r\n            ),\r\n            react.createElement(\r\n                \"div\",\r\n                { className: \"main-entityHeader-headerText\" },\r\n                react.createElement(\r\n                    \"h2\",\r\n                    { className: \"main-entityHeader-subtitle main-entityHeader-small main-entityHeader-uppercase main-entityHeader-bold\" },\r\n                    playlist.isCollab ? \"Collaborative Playlist\" : \"Playlist\"\r\n                ),\r\n                react.createElement(\r\n                    \"h1\",\r\n                    {\r\n                        className: \"main-entityHeader-title main-type-bass\",\r\n                        style: {\r\n                            padding: \"0.08em 0px\",\r\n                            visibility: \"visible\",\r\n                            width: \"100%\",\r\n                            fontSize: \"9vmin\",\r\n                            lineHeight: \"9vmin\",\r\n                        },\r\n                    },\r\n                    react.createElement(\r\n                        \"a\",\r\n                        {\r\n                            href: playlist.uri,\r\n                            draggable: \"false\",\r\n                        },\r\n                        playlist.title\r\n                    )\r\n                ),\r\n                isDesc &&\r\n                    react.createElement(\"h2\", { className: \"main-entityHeader-subtitle main-entityHeader-gray main-type-viola\" }, playlist.desc),\r\n                react.createElement(\r\n                    \"span\",\r\n                    { className: \"main-entityHeader-metaData main-type-mesto\" },\r\n                    `${playlist.created} • ${playlist.noOfSongs} songs`\r\n                )\r\n            )\r\n        );\r\n    };\r\n\r\n    async function listPlaylists(uris) {\r\n        // getting playlists to display\r\n        const allPlaylists = await getUserLibrary();\r\n        const playlistsFound = [];\r\n        for (var playlist of allPlaylists) {\r\n            const playlistRes = await checkPlaylist(playlist, uris[0]);\r\n            if (playlistRes) {\r\n                playlistsFound.push(playlistRes);\r\n            }\r\n        }\r\n        if (playlistsFound.length == 0) {\r\n            Spicetify.showNotification(\"Song is not in any of your playlists.\");\r\n            return;\r\n        }\r\n\r\n        // getting song data to prepare elements\r\n        const songmeta = await Spicetify.CosmosAsync.get(\"https://api.spotify.com/v1/tracks/\" + uris[0].split(\":\")[2]);\r\n        Spicetify.Platform.History.push(`/album/${songmeta.album.uri.split(\":\")[2]}?highlight=${uris[0]}`);\r\n        await delay(2000); // waiting to load.\r\n\r\n        // modifying album page and saving info card and song row\r\n        let songRow = document.querySelector(`[aria-selected=\"true\"]`);\r\n        let section = document.querySelector(`[data-testid=\"album-page\"]`);\r\n\r\n        // if (songmeta.album.total_tracks == 1) {\r\n        //     songRow = document.querySelector(`[aria-rowindex=\"2\"]`).cloneNode(true);\r\n        // } else {\r\n        //     songRow = document.querySelector(`[aria-rowindex=\"3\"]`).cloneNode(true);\r\n        //     songRow.childNodes[0].childNodes[1].childNodes[0].childNodes[0].innerText = songmeta.name;\r\n        //     if (songmeta.artists.length > 1) {\r\n        //         let artists = \"\";\r\n        //         for (const artist of songmeta.artists) {\r\n        //             artists = artists.concat(artist.name, \", \");\r\n        //         }\r\n        //         artists = artists.slice(0, -2);\r\n        //         if (songmeta.explicit) {\r\n        //             songRow.childNodes[0].childNodes[1].childNodes[0].childNodes[2].innerHTML = artists;\r\n        //         } else {\r\n        //             songRow.childNodes[0].childNodes[1].childNodes[0].childNodes[1].innerHTML = artists;\r\n        //         }\r\n        //     }\r\n        // }\r\n        let info = document.querySelector(`[data-testid=\"album-page\"] > div`).cloneNode(true);\r\n        info.classList.add(\"main-trackList-trackListHeaderRow\");\r\n        section.innerHTML = \"\"; //wiping all other elements\r\n\r\n        await delay(200); // waiting for the topbar text to appear, to remove it\r\n        document\r\n            .querySelector(\".main-topBar-topbarContent.main-entityHeader-topbarContent.main-entityHeader-topbarContentFadeIn\")\r\n            .classList.remove(\"main-entityHeader-topbarContentFadeIn\");\r\n\r\n        section.appendChild(info);\r\n        // creating the heading\r\n        let appearsIn = document.createElement(\"h1\");\r\n        appearsIn.className = \"main-type-bass main-trackList-trackListHeaderRow\";\r\n        appearsIn.style.fontSize = \"48px\";\r\n        appearsIn.style.lineHeight = \"60px\";\r\n        appearsIn.style.paddingLeft = \"10px\";\r\n        appearsIn.style.height = \"auto\";\r\n        appearsIn.innerText = `Appears In ${playlistsFound.length}/${allPlaylists.length} of your playlists:`;\r\n        section.appendChild(appearsIn); //adding it\r\n        // modifying info card\r\n        let infoText = section.childNodes[0].childNodes[5];\r\n        infoText.childNodes[0].innerText = \"SONG\";\r\n        infoText.childNodes[1].childNodes[0].style.fontSize = \"8vmin\";\r\n        infoText.childNodes[1].childNodes[0].style.lineHeight = \"8vmin\";\r\n        infoText.childNodes[1].childNodes[0].innerText = songmeta.name;\r\n        if (songmeta.album.total_tracks > 1) {\r\n            let albumText = infoText.childNodes[0].cloneNode();\r\n            albumText.classList.remove(\"main-entityHeader-uppercase\", \"main-entityHeader-small\");\r\n            albumText.style = \"margin-top: 0px; margin-bottom: 8px\";\r\n            albumText.innerText = `Track ${songmeta.track_number} / ${songmeta.album.total_tracks} • ${songmeta.album.name}`;\r\n            infoText.childNodes[1].appendChild(albumText);\r\n            infoText.childNodes[2].childNodes[2].innerText = `1 song, ${parseInt(songmeta.duration_ms / 1000 / 60)} min ${parseInt(\r\n                (songmeta.duration_ms / 1000) % 60\r\n            )} sec`;\r\n        }\r\n\r\n        // preparing song row element\r\n        let songImage = document.createElement(\"img\"); //getting small album art and adding it\r\n        songImage.className = \"main-image-image main-trackList-rowImage\";\r\n        songImage.src = songmeta.album.images.at(-1).url;\r\n        songImage.width = 40;\r\n        songImage.height = 40;\r\n\r\n        songRow.childNodes[0].classList.remove(\"main-trackList-selected\");\r\n        songRow.childNodes[0].style = \"grid-template-columns: [index] 30px [first] 4fr [var1] 3fr [last] minmax(240px,2fr);\";\r\n        songRow.childNodes[0].childNodes[1].insertBefore(songImage, songRow.childNodes[0].childNodes[1].firstChild);\r\n        songRow.childNodes[0].childNodes[2].childNodes[0].style.width = \"fit-content\";\r\n        songRow.childNodes[0].childNodes[2].childNodes[0].innerText = songmeta.album.name;\r\n        songRow.childNodes[0].childNodes[3].childNodes[1].classList.remove(\"main-trackList-rowDuration\");\r\n        if (songRow.childNodes[0].classList.contains(\"main-trackList-active\")) {\r\n            let spanIndex = document.createElement(\"span\"); //adding index in playlist to song row\r\n            spanIndex.classList.add(\"main-trackList-number\", \"main-type-ballad\");\r\n\r\n            songRow.childNodes[0].childNodes[0].childNodes[0].childNodes[0].remove();\r\n            songRow.childNodes[0].childNodes[0].childNodes[0].appendChild(spanIndex);\r\n        }\r\n\r\n        // finally rendering the playlists\r\n        for (const playlist of playlistsFound) {\r\n            let preElement = document.createElement(\"div\");\r\n            preElement.classList.add(\"main-trackList-trackListHeaderRow\");\r\n            preElement.style.height = \"auto\";\r\n            section.append(preElement);\r\n\r\n            const playlist_card = react.createElement(playlistCard, { playlist: playlist });\r\n            reactDOM.render(playlist_card, preElement);\r\n\r\n            songRow.childNodes[0].childNodes[0].childNodes[0].childNodes[0].innerText = playlist.index;\r\n            songRow.childNodes[0].childNodes[3].childNodes[1].innerText = playlist.songAddedAt;\r\n            // finally adding song row\r\n            preElement.appendChild(songRow.cloneNode(true));\r\n        }\r\n    }\r\n\r\n    new Spicetify.ContextMenu.Item(\r\n        \"List playlists with this Song\",\r\n        listPlaylists,\r\n        (uris) => {\r\n            if (uris.length != 1) return false;\r\n            return Spicetify.URI.fromString(uris[0]).type == Spicetify.URI.Type.TRACK;\r\n        },\r\n        \"search\"\r\n    ).register();\r\n})();\r\n"
  },
  {
    "path": "manifest.json",
    "content": "[\n    {\n        \"name\": \"Full App Display Modified\",\n        \"description\": \"View your music at a glance.\",\n        \"preview\": \"fullAppDisplayModified/previews/preview.gif\",\n        \"main\": \"fullAppDisplayModified/fullAppDisplayMod.js\",\n        \"readme\": \"fullAppDisplayModified/README.md\"\n    },\n    {\n        \"name\" : \"SkipStats\",\n        \"description\" : \"See your skipping stats in playlists and albums!\",\n        \"preview\" : \"skipStats/preview.jpg\",\n        \"main\" : \"skipStats/skipStats.js\",\n        \"readme\" : \"skipStats/README.md\"\n    },\n    {\n        \"name\": \"listPlaylistsWithSong\",\n        \"description\": \"Adds context menu button to view playlists in your library that contain the selected song\",\n        \"preview\": \"listPlaylistsWithSong/preview1.jpg\",\n        \"main\": \"listPlaylistsWithSong/listPlaylistsWithSong.js\",\n        \"readme\": \"listPlaylistsWithSong/README.md\"\n    },\n    {\n        \"name\": \"GoToSong\",\n        \"description\": \"Go to currently playing song in playlist\",\n        \"preview\": \"goToSong/preview1.jpg\",\n        \"main\": \"goToSong/goToSong.js\",\n        \"readme\": \"goToSong/README.md\"\n    },\n    {\n        \"name\": \"playlistIntersection\",\n        \"description\": \"Adds context menu buttons to see songs in common between two playlists.\",\n        \"preview\": \"playlistIntersection/preview.jpg\",\n        \"main\": \"playlistIntersection/playlistIntersection.js\",\n        \"readme\": \"playlistIntersection/README.md\"\n    },\n    {\n        \"name\": \"Display full Album date\",\n        \"description\": \"Display the full album date instead of just year\",\n        \"preview\": \"fullAlbumDate/preview.jpg\",\n        \"main\": \"fullAlbumDate/fullAlbumDate.js\",\n        \"readme\": \"fullAlbumDate/README.md\"\n    }\n]\n"
  },
  {
    "path": "playlistIntersection/README.md",
    "content": "# playlistIntersection\nFilename : `playlistIntersection.js`\n\nAdds context menu buttons to see \n* songs in common between two playlists\n* songs only present in one playlist\n\nand convert them to a playlist.\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/both.jpg)\n\n## To use:\n\n* Right Click on desired playlist, and click \"Select for Intersection\".\n\n![Step 1](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/preview.jpg)\n\n* Right Click on a second playlist, and click \"Compare with Selected Playlist\".\n\n![Step 2](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/compare.jpg)\n\n* If you want to clear your first selection, Go to profile, and click \"Clear Selection from Intersection\".\n\n![Clear](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/clear.jpg)\n\n### Note: \nAfter comparing, please wait for all the songs to load, and then change the mode or it **will** malfunction.\n\n## Convert to Playlist:\n![Playlist](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/convert.jpg)\n\nClicking this button will turn the displayed tracks into a playlist.\n\n## Changing Modes:\n\n![Mode Selection](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/modeselection.jpg)\n\nClicking this button, will cycle through the following modes:\n* Intersection: This mode displays the song common in both the playlists. This is the default mode and will always open.\n\n![Intersection](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/modeinter.jpg)\n![Intersection Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/both.jpg)\n* Songs only in Playlist 1: This displays the song present only in the first playlist.\n\n![First Only](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/firstonly.jpg)\n![First Only Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/first.jpg)\n* Songs only in Playlist 2: This displays the song present only in the second playlist.\n\n![Second Only](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/secondonly.jpg)\n![Second Only Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/playlistIntersection/second.jpg)\n\n\n## More\n🌟 Like it? Gimme some love!    \n[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)\n"
  },
  {
    "path": "playlistIntersection/playlistIntersection.js",
    "content": "//@ts-check\n// NAME: playlistIntersection\n// AUTHOR: huhridge\n// DESCRIPTION: Adds context menu buttons to see songs in common between two playlists.\n/// <reference types=\"react\" />\n/// <reference path=\"../globals.d.ts\" />\n\n(async function playlistIntersecter() {\n    const { Player, Menu, LocalStorage, Platform, ReactDOM: reactDOM } = Spicetify;\n    let play1, play2, name1, name2, commonTracks;\n    let renderedTracks;\n\n    /** @type {React} */\n    const react = Spicetify.React;\n\n    if (!(Player && Menu && LocalStorage && Platform)) {\n        setTimeout(playlistIntersecter, 1000);\n        return;\n    }\n\n    function delay(delayInms) {\n        return new Promise((resolve) => {\n            setTimeout(() => {\n                resolve(2);\n            }, delayInms);\n        });\n    }\n\n    function trackIntersection(track1, track2) {\n        var inter = new Array();\n        var uri1 = new Array();\n\n        for (i = 0; i < track1.length; i++) {\n            uri1.push(track1[i].uri);\n        }\n\n        for (var i = 0; i < track2.length; i++) {\n            var index1 = uri1.indexOf(track2[i].uri);\n            if (index1 >= 0) {\n                track2[i].index1 = index1;\n                track2[i].index2 = i;\n                inter.push(track2[i]);\n            }\n        }\n        return inter;\n    }\n\n    const playlistInfo = (playlist, isLeft) => {\n        let isDesc = false;\n        if (playlist.description) {\n            isDesc = true;\n        }\n        return react.createElement(\n            \"div\",\n            {\n                className: \"contentSpacing main-entityHeader-container main-entityHeader-nonWrapped\",\n                style: {\n                    marginTop: \"1.5%\",\n                    marginBottom: \"1.5%\",\n                    justifyContent: isLeft ? \"left\" : \"start\",\n                    paddingLeft: isLeft ? \"32px\" : \"16px\",\n                    paddingRight: isLeft ? \"16px\" : \"32px\",\n                    borderRight: isLeft ? \"1px solid rgba(255,255,255,.1)\" : \"\",\n                },\n            },\n            react.createElement(\n                \"div\",\n                {\n                    draggable: false,\n                    style: {\n                        alignSelf: \"center\",\n                        position: \"relative\",\n                        height: \"232px\",\n                        minWidth: \"232px\",\n                        width: \"232px\",\n                        marginInlineEnd: \"24px\",\n                    },\n                },\n                react.createElement(\"img\", {\n                    className: \"main-image-image main-entityHeader-shadow\",\n                    style: {\n                        height: \"100%\",\n                        width: \"100%\",\n                    },\n                    src: playlist.images[0].url,\n                })\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-entityHeader-headerText\",\n                    style: {\n                        justifyContent: \"center\",\n                    },\n                },\n                react.createElement(\n                    \"h2\",\n                    {\n                        className: \"main-entityHeader-subtitle main-entityHeader-small main-entityHeader-uppercase main-entityHeader-bold\",\n                    },\n                    playlist.isCollaborative ? \"Collaborative Playlist\" : \"Playlist\"\n                ),\n                react.createElement(\n                    \"span\",\n                    {\n                        className: \"main-entityHeader-title\",\n                    },\n                    react.createElement(\n                        \"h1\",\n                        {\n                            dir: \"auto\",\n                            className: \"main-type-bass\",\n                            style: {\n                                padding: \"0.08em 0px\",\n                                visibility: \"visible\",\n                                width: \"100%\",\n                                lineHeight: \"3vw\",\n                                letterSpacing: \"-0.04em\",\n                                fontWeight: \"900\",\n                            },\n                            ref: (el) => el && el.style.setProperty(\"font-size\", \"2.5vw\", \"important\"),\n                        },\n                        react.createElement(\n                            \"a\",\n                            {\n                                href: playlist.uri,\n                                draggable: \"false\",\n                            },\n                            playlist.name\n                        )\n                    )\n                ),\n                isDesc &&\n                    react.createElement(\n                        \"h2\",\n                        { className: \"main-entityHeader-subtitle main-entityHeader-gray main-type-viola\" },\n                        playlist.description\n                    ),\n                react.createElement(\n                    \"div\",\n                    {\n                        className: \"main-entityHeader-metaData\",\n                    },\n                    react.createElement(\n                        \"span\",\n                        {\n                            className: \"main-type-mesto\",\n                        },\n                        react.createElement(\n                            \"a\",\n                            {\n                                href: playlist.owner.uri,\n                            },\n                            playlist.owner.displayName\n                        ),\n                        ` • ${playlist.totalLength} songs`\n                    )\n                )\n            )\n        );\n    };\n\n    const songRowHeader = () => {\n        return react.createElement(\n            \"div\",\n            {\n                className: \"main-trackList-trackListRowGrid\",\n                role: \"row\",\n                \"aria-rowindex\": \"1\",\n                style: {\n                    marginTop: \"10px\",\n                    marginBottom: \"8px\",\n                },\n                ref: (el) =>\n                    el && el.style.setProperty(\"grid-template-columns\", \"[index] 1fr [first] 6fr [var1] 6fr [var2] 1fr [last] 1fr\", \"important\"),\n            },\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionIndex\",\n                    role: \"columnheader\",\n                    \"aria-colindex\": \"1\",\n                },\n                \"#\"\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionStart\",\n                    role: \"columnheader\",\n                    \"aria-colindex\": \"2\",\n                },\n                react.createElement(\n                    \"span\",\n                    {\n                        className: \"standalone-ellipsis-one-line main-type-minuet\",\n                    },\n                    \"title\"\n                )\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionVariable\",\n                    role: \"columnheader\",\n                    \"aria-colindex\": \"3\",\n                },\n                react.createElement(\n                    \"span\",\n                    {\n                        className: \"standalone-ellipsis-one-line main-type-minuet\",\n                    },\n                    \"album\"\n                )\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionVariable\",\n                    role: \"columnheader\",\n                    \"aria-colindex\": \"4\",\n                },\n                react.createElement(\n                    \"svg\",\n                    {\n                        role: \"img\",\n                        height: \"16\",\n                        width: \"16\",\n                        fill: \"var(--spice-subtext)\",\n                        viewBox: \"0 0 16 16\",\n                    },\n                    react.createElement(\"path\", {\n                        d: \"M7.999 3h-1v5h3V7h-2V3zM7.5 0a7.5 7.5 0 100 15 7.5 7.5 0 000-15zm0 14C3.916 14 1 11.084 1 7.5S3.916 1 7.5 1 14 3.916 14 7.5 11.084 14 7.5 14z\",\n                    }),\n                    react.createElement(\"path\", {\n                        fill: \"none\",\n                        d: \"M16 0v16H0V0z\",\n                    })\n                )\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionIndex\",\n                    role: \"columnheader\",\n                    \"aria-colindex\": \"5\",\n                },\n                \"#\"\n            )\n        );\n    };\n\n    const songRow = (song, i, display = \"both\") => {\n        let artists = \"\";\n        for (const artist of song.artists) {\n            artists = artists.concat(artist.name, \", \");\n        }\n        let displayOne = true;\n        let displayTwo = true;\n        artists = artists.slice(0, -2);\n        if (display == \"1\") {\n            displayTwo = false;\n        } else if (display == \"2\") {\n            displayOne = false;\n        }\n        return react.createElement(\n            \"div\",\n            {\n                className: \"main-trackList-trackListRow main-trackList-trackListRowGrid\",\n                draggable: true,\n                role: \"presentation\",\n                \"aria-rowindex\": `${i}`,\n                ref: (el) =>\n                    el && el.style.setProperty(\"grid-template-columns\", \"[index] 1fr [first] 6fr [var1] 6fr [var2] 1fr [last] 1fr\", \"important\"),\n                onDoubleClick: () => {\n                    Spicetify.Player.playUri(song.uri);\n                },\n            },\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionStart\",\n                    role: \"gridcell\",\n                    style: {\n                        justifySelf: \"end\",\n                    },\n                    \"aria-colindex\": \"1\",\n                },\n                displayOne &&\n                    react.createElement(\n                        \"div\",\n                        {\n                            className: \"main-trackList-rowMarker\",\n                        },\n                        react.createElement(\n                            \"span\",\n                            {\n                                className: \"main-trackList-number main-type-ballad\",\n                            },\n                            song.index1 + 1\n                        )\n                    )\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionStart\",\n                    role: \"gridcell\",\n                    \"aria-colindex\": \"2\",\n                },\n                react.createElement(\"img\", {\n                    className: \"main-image-image main-trackList-rowImage\",\n                    draggable: false,\n                    width: \"40\",\n                    height: \"40\",\n                    src: song.album.images[1].url,\n                }),\n                react.createElement(\n                    \"div\",\n                    {\n                        className: \"main-trackList-rowMainContent\",\n                    },\n                    react.createElement(\n                        \"div\",\n                        {\n                            className: \"main-trackList-rowTitle standalone-ellipsis-one-line main-type-ballad\",\n                            dir: \"auto\",\n                        },\n                        song.name\n                    ),\n                    song.isExplicit &&\n                        react.createElement(\n                            \"span\",\n                            {\n                                className: \"main-trackList-rowBadges main-type-ballad\",\n                                style: {\n                                    color: \"var(--spice-subtext)\",\n                                },\n                            },\n                            react.createElement(\n                                \"span\",\n                                {\n                                    className: \"main-tag-container\",\n                                    title: \"Explicit\",\n                                },\n                                \"E\"\n                            )\n                        ),\n                    react.createElement(\n                        \"span\",\n                        {\n                            className: \"main-trackList-rowSubTitle standalone-ellipsis-one-line main-type-mesto\",\n                            style: {\n                                color: \"var(--spice-subtext)\",\n                            },\n                        },\n                        artists\n                    )\n                )\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionVariable\",\n                    role: \"gridcell\",\n                    \"aria-colindex\": \"3\",\n                },\n                react.createElement(\n                    \"a\",\n                    {\n                        draggable: true,\n                        className: \"standalone-ellipsis-one-line main-type-mesto\",\n                        href: song.album.uri,\n                    },\n                    song.album.name\n                )\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionVariable\",\n                    role: \"gridcell\",\n                    \"aria-colindex\": \"4\",\n                },\n                react.createElement(\n                    \"span\",\n                    {\n                        className: \"main-type-mesto\",\n                        style: {\n                            color: \"var(--spice-subtext)\",\n                        },\n                    },\n                    `${parseInt(song.duration.milliseconds / 1000 / 60)}:${parseInt((song.duration.milliseconds / 1000) % 60).toLocaleString(\n                        undefined,\n                        {\n                            minimumIntegerDigits: 2,\n                            useGrouping: false,\n                        }\n                    )}`\n                )\n            ),\n            react.createElement(\n                \"div\",\n                {\n                    className: \"main-trackList-rowSectionEnd\",\n                    role: \"gridcell\",\n                    \"aria-colindex\": \"5\",\n                    style: {\n                        justifyContent: \"end\",\n                    },\n                },\n                displayTwo &&\n                    react.createElement(\n                        \"div\",\n                        {\n                            className: \"main-trackList-rowMarker\",\n                        },\n                        react.createElement(\n                            \"span\",\n                            {\n                                className: \"main-trackList-number main-type-ballad\",\n                            },\n                            song.index2 + 1\n                        )\n                    )\n            )\n        );\n    };\n\n    const interHeading = () => {\n        return react.createElement(\n            \"div\",\n            {\n                className: \"interHeading\",\n                style: {\n                    display: \"flex\",\n                    justifyContent: \"flex-start\",\n                    borderBottom: \"1px solid rgba(255,255,255,.1)\",\n                },\n            },\n            react.createElement(\"h1\", {\n                className: \"main-type-bass main-trackList-trackListHeaderRow\",\n                style: {\n                    fontSize: \"30px\",\n                    height: \"100%\",\n                    lineHeight: \"60px\",\n                    paddingLeft: \"10px\",\n                    justifySelf: \"flex-start\",\n                    border: \"none\",\n                },\n            }),\n            react.createElement(\n                \"button\",\n                {\n                    className: \"changeMode main-topBar-button\",\n                    title: \"Change Mode\",\n                    style: {\n                        display: \"inline-flex\",\n                        alignSelf: \"center\",\n                        width: \"32px\",\n                        height: \"32px\",\n                        marginLeft: \"auto\",\n                        marginRight: \"16px\",\n                        borderWidth: \"0px\",\n                        borderRadius: \"5px\",\n                        alignItems: \"center\",\n                        justifyContent: \"center\",\n                    },\n                    ref: (el) => el && el.style.setProperty(\"background-color\", \"var(--spice-button)\", \"important\"),\n                    onClick: () => {\n                        let ele = document.querySelector(\".changeMode.main-topBar-button\");\n                        if (LocalStorage.get(\"spicetify-intermode\") == \"Intersect\" && Spicetify.LocalStorage.get(\"spicetify-interorder\") == \"0\") {\n                            LocalStorage.set(\"spicetify-intermode\", \"exceptIntersect\");\n                            Spicetify.LocalStorage.set(\"spicetify-interorder\", \"1\");\n                            ele.style.backgroundColor = \"var(--spice-button-active)\";\n                            let d = ele.firstChild.firstChild.getAttribute(\"d\");\n                            d = d + \"M4.318 10.836A.5.5 0 006.089 11.548.5.5 0 004.318 10.836Z\";\n                            ele.firstChild.firstChild.setAttribute(\"d\", d);\n                            exceptIntersect();\n                        } else if (\n                            LocalStorage.get(\"spicetify-intermode\") == \"exceptIntersect\" &&\n                            Spicetify.LocalStorage.get(\"spicetify-interorder\") == \"1\"\n                        ) {\n                            Spicetify.LocalStorage.set(\"spicetify-interorder\", \"2\");\n                            ele.firstChild.innerHTML = Spicetify.SVGIcons.copy;\n                            let d = ele.firstChild.firstChild.getAttribute(\"d\");\n                            d = d + \"M10.423 3.806A.5.5 0 0011.411 5.234.5.5 0 0010.423 3.806Z\";\n                            ele.firstChild.firstChild.setAttribute(\"d\", d);\n                            exceptIntersect();\n                        } else {\n                            LocalStorage.set(\"spicetify-intermode\", \"Intersect\");\n                            Spicetify.LocalStorage.set(\"spicetify-interorder\", \"0\");\n                            ele.firstChild.innerHTML = Spicetify.SVGIcons.copy;\n                            ele.style.backgroundColor = \"var(--spice-button)\";\n                            renderIntersect();\n                        }\n                    },\n                },\n                react.createElement(\"svg\", {\n                    role: \"img\",\n                    width: \"16\",\n                    height: \"16\",\n                    fill: \"black\",\n                    viewBox: \"0 0 16 16\",\n                    class: \"modeSVG\",\n                    dangerouslySetInnerHTML: {\n                        __html: Spicetify.SVGIcons.copy,\n                    },\n                })\n            ),\n            react.createElement(\n                \"button\",\n                {\n                    className: \"convertPlaylist main-topBar-button\",\n                    title: \"Convert to Playlist\",\n                    style: {\n                        display: \"inline-flex\",\n                        alignSelf: \"center\",\n                        width: \"32px\",\n                        height: \"32px\",\n                        marginRight: \"16px\",\n                        borderWidth: \"0px\",\n                        borderRadius: \"5px\",\n                        alignItems: \"center\",\n                        justifyContent: \"center\",\n                    },\n                    ref: (el) => el && el.style.setProperty(\"background-color\", \"var(--spice-button)\", \"important\"),\n                    onClick: () => {\n                        convertToPlaylist();\n                    },\n                },\n                react.createElement(\"svg\", {\n                    role: \"img\",\n                    width: \"16\",\n                    height: \"16\",\n                    fill: \"black\",\n                    viewBox: \"0 0 16 16\",\n                    class: \"playlistSVG\",\n                    dangerouslySetInnerHTML: {\n                        __html: Spicetify.SVGIcons.playlist,\n                    },\n                })\n            )\n        );\n    };\n\n    async function intersect() {\n        play1 = LocalStorage.get(\"spicetify-interplaylist1\");\n        play2 = LocalStorage.get(\"spicetify-interplaylist2\");\n        LocalStorage.set(\"spicetify-intermode\", \"Intersect\");\n        LocalStorage.set(\"spicetify-interorder\", \"0\");\n        LocalStorage.remove(\"spicetify-interplaylist1\");\n        LocalStorage.remove(\"spicetify-interplaylist2\");\n\n        const tracks1 = (await Spicetify.Platform.PlaylistAPI.getContents(play1)).items;\n        const tracks2 = (await Spicetify.Platform.PlaylistAPI.getContents(play2)).items;\n\n        commonTracks = trackIntersection(tracks1, tracks2);\n\n        if (commonTracks.length == 0) {\n            Spicetify.showNotification(\"No common tracks between the playlists.\");\n            return;\n        }\n\n        const meta1 = await Spicetify.Platform.PlaylistAPI.getMetadata(play1);\n        name1 = meta1.name;\n        const meta2 = await Spicetify.Platform.PlaylistAPI.getMetadata(play2);\n        name2 = meta2.name;\n\n        Spicetify.Platform.History.push(`/playlist/${meta1.uri.split(\":\")[2]}`);\n        await delay(1000);\n\n        let section = document.querySelector(`[data-testid=\"playlist-page\"]`);\n        section.innerHTML = \"\";\n\n        await delay(200); // waiting for the topbar text to appear, to remove it\n        document\n            .querySelector(\".main-topBar-topbarContent.main-entityHeader-topbarContent.main-entityHeader-topbarContentFadeIn\")\n            .classList.remove(\"main-entityHeader-topbarContentFadeIn\");\n        let style = document.createElement(\"style\");\n        style.innerHTML = `\n.main-type-mesto {\n    font-size: 14px;\n    font-weight: 400;\n    letter-spacing: normal;\n    line-height: 16px;\n    text-transform: none\n}\n.main-type-bass {\n    font-size: 96px;\n    font-weight: 900;\n    letter-spacing: -.04em;\n    line-height: 96px;\n    text-transform: none\n}\n.main-type-minuet {\n    font-size: 12px;\n    font-weight: 400;\n    letter-spacing: .1em;\n    line-height: 16px;\n    text-transform: uppercase\n}\n.main-type-ballad {\n    font-size: 16px;\n    font-weight: 400;\n    letter-spacing: normal;\n    line-height: 24px;\n    text-transform: none\n}\n        `;\n        section.append(style);\n        let playContainer = document.createElement(\"div\");\n        playContainer.classList.add(\"main-trackList-trackListHeaderRow\");\n        playContainer.style.display = \"flex\";\n        playContainer.style.borderBottom = \"1px solid rgba(255,255,255,.1)\";\n        playContainer.style.height = \"100%\"\n        section.append(playContainer);\n\n        const playele1 = playlistInfo(meta1, true);\n        const playele2 = playlistInfo(meta2, false);\n\n        let container = document.querySelector(\".main-trackList-trackListHeaderRow\");\n        let preElement1 = document.createElement(\"div\");\n        preElement1.style.width = \"50%\";\n        container.append(preElement1);\n        reactDOM.render(playele1, preElement1);\n        let preElement2 = document.createElement(\"div\");\n        preElement2.style.width = \"50%\";\n        container.append(preElement2);\n        reactDOM.render(playele2, preElement2);\n\n        let headingWrapper = document.createElement(\"div\");\n        section.append(headingWrapper);\n        let heading = interHeading();\n        reactDOM.render(heading, headingWrapper);\n\n        let songHeader = document.createElement(\"div\");\n        songHeader.className = \"main-trackList-trackListHeaderRow\";\n        songHeader.style.height = \"100%\"\n        songHeader.style.background = \"var(--spice-main)\";\n        songHeader.style.borderBottom = \"1px solid rgba(255,255,255,.1)\";\n        songHeader.style.marginBottom = \"8px\";\n        songHeader.style.position = \"sticky\";\n        songHeader.style.top = \"64px\";\n        songHeader.style.zIndex = \"2\";\n        section.append(songHeader);\n        const trackRowHeader = await songRowHeader();\n        reactDOM.render(trackRowHeader, songHeader);\n        renderIntersect();\n    }\n\n    async function renderIntersect() {\n        let section = document.querySelector(`[data-testid=\"playlist-page\"]`);\n        let songContainer;\n        try {\n            songContainer = document.querySelector(\".interSongContainer\");\n            songContainer.innerHTML = \"\";\n        } catch {\n            songContainer = document.createElement(\"div\");\n            songContainer.className = \"interSongContainer\";\n            section.append(songContainer);\n        }\n        let heading1 = document.querySelector(\".main-type-bass.main-trackList-trackListHeaderRow\");\n        if (commonTracks.length == 1) {\n            heading1.innerText = `${commonTracks.length} song appears in both of the playlists: `;\n        } else {\n            heading1.innerText = `${commonTracks.length} songs appear in both of the playlists: `;\n        }\n        //rendering songs\n        let i = 2;\n        renderedTracks = commonTracks;\n        for (const track of commonTracks) {\n            let preElement = document.createElement(\"div\");\n            songContainer.append(preElement);\n            const trackRow = songRow(track, i);\n            reactDOM.render(trackRow, preElement);\n            i += 1;\n        }\n    }\n\n    async function exceptIntersect() {\n        const toDisplay = LocalStorage.get(\"spicetify-interorder\");\n        let tracks = [];\n        let isOne, meta;\n        if (toDisplay == \"1\") {\n            tracks = (await Spicetify.Platform.PlaylistAPI.getContents(play1)).items;\n            meta = await Spicetify.Platform.PlaylistAPI.getMetadata(play1);\n            for (var i = 0; i < tracks.length; i++) {\n                tracks[i].index1 = i;\n            }\n            isOne = true;\n        } else {\n            tracks = (await Spicetify.Platform.PlaylistAPI.getContents(play2)).items;\n            meta = await Spicetify.Platform.PlaylistAPI.getMetadata(play2);\n            for (var i = 0; i < tracks.length; i++) {\n                tracks[i].index2 = i;\n            }\n            isOne = false;\n        }\n        for (const track of commonTracks) {\n            isOne ? delete tracks[track.index1] : delete tracks[track.index2];\n        }\n        let heading = document.querySelector(\".main-type-bass.main-trackList-trackListHeaderRow\");\n        if (tracks.length == 1) {\n            heading.innerText = `${tracks.length} song exists only in ${meta.name}`;\n        } else {\n            heading.innerText = `${tracks.length - commonTracks.length} songs exist only in ${meta.name}`;\n        }\n\n        let container = document.querySelector(\".interSongContainer\");\n        container.innerHTML = \"\";\n\n        renderedTracks = tracks;\n        let j = 2;\n        for (const track of tracks) {\n            if (!track) {\n                continue;\n            }\n            let preElement = document.createElement(\"div\");\n            container.append(preElement);\n            const trackRow = songRow(track, j, toDisplay);\n            reactDOM.render(trackRow, preElement);\n            j += 1;\n        }\n    }\n\n    async function convertToPlaylist() {\n        let songUris = renderedTracks\n            .filter(track => track) // Ensure no null tracks\n            .map(track => track.uri);\n\n        let playname;\n        if (LocalStorage.get(\"spicetify-intermode\") == \"Intersect\") {\n            playname = `${name1} ∩ ${name2}`;\n        } else {\n            if (LocalStorage.get(\"spicetify-interorder\") == \"1\") {\n                playname = `${name1} ∅ ${name2}`;\n            } else {\n                playname = `${name2} ∅ ${name1}`;\n            }\n        }\n\n        const response = await Spicetify.CosmosAsync.post(\n            \"https://api.spotify.com/v1/me/playlists\", {\n                name: playname,\n                public: false\n            }\n        );\n\n        if (!response || !response.id) {\n            Spicetify.showNotification(\"Failed to create playlist.\");\n            return;\n        }\n        const uri = response.id\n        await delay(100);\n        while (songUris.length) {\n            const b = songUris.splice(0, 100);\n            Spicetify.CosmosAsync.post(\"https://api.spotify.com/v1/playlists/\" + uri + \"/tracks\", {\n                uris: b,\n            });\n        }\n        Spicetify.showNotification(`Succesfully created playlist ${playname}`);\n    }\n\n    const clearSelection = new Spicetify.Menu.Item(\"Clear Selection from Intersection\", false, (self) => {\n        LocalStorage.remove(\"spicetify-interplaylist1\");\n        self.deregister();\n    });\n\n    new Spicetify.ContextMenu.Item(\n        \"Select for Intersection\",\n        (uris) => {\n            LocalStorage.set(\"spicetify-interplaylist1\", uris[0]);\n            clearSelection.register();\n        },\n        (uris) => {\n            if (uris.length > 1) {\n                return false;\n            }\n            if (LocalStorage.get(\"spicetify-interplaylist1\")) {\n                return false;\n            }\n            const uriObj = Spicetify.URI.fromString(uris[0]);\n            if (uriObj.type == Spicetify.URI.Type.PLAYLIST || uriObj.type == Spicetify.URI.Type.PLAYLIST_V2) {\n                return true;\n            }\n            return false;\n        },\n        \"copy\"\n    ).register();\n\n    new Spicetify.ContextMenu.Item(\n        \"Compare with Selected Playlist\",\n        (uris) => {\n            LocalStorage.set(\"spicetify-interplaylist2\", uris[0]);\n            clearSelection.deregister();\n            intersect();\n        },\n        (uris) => {\n            if (uris.length > 1) {\n                return false;\n            }\n            if (!LocalStorage.get(\"spicetify-interplaylist1\")) {\n                return false;\n            }\n            if (uris[0] == LocalStorage.get(\"spicetify-interplaylist1\")) {\n                return false;\n            }\n            const uriObj = Spicetify.URI.fromString(uris[0]);\n            if (uriObj.type == Spicetify.URI.Type.PLAYLIST || uriObj.type == Spicetify.URI.Type.PLAYLIST_V2) {\n                return true;\n            }\n            return false;\n        },\n        \"copy\"\n    ).register();\n})();\n"
  },
  {
    "path": "skipStats/README.md",
    "content": "# skipStats\n\nFilename : `skipStats.js`\n\n![Preview](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/preview.jpg)\n\nExtension to track your skips!\n\n-   Tracks your skips when listening to playlists or albums!\n-   Displays the data in a readable manner\n-   Auto-skip songs over a certain value of skips\n\n## To use:\n\n-   To see the skips in current playlist/album: Click on the profile and select \"See Skips for current playlist/album\".\n\n![profilemenu](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/skip_profilemenu.jpg)\n\n-   Right Click on a playlist or album, and click \"See Skip Stats\".\n\n![playlist](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/skip_playlist.jpg)\n\n## Config\n\n-   Set the auto skip limit: Go to profile menu and click on \"Auto-Skip\".\n\n![autoskip](https://raw.githubusercontent.com/huhridge/huh-spicetify-extensions/main/skipStats/autoskip.jpg)\n\n-   Reset Stats: Go to profile menu and click on the option accordingly.\n\n## Install\n\nCopy `skipStats.js` into your [Spicetify](https://github.com/khanhas/spicetify-cli) extensions directory:\n| **Platform** | **Path** |\n|------------|-----------------------------------------------------------------------------------|\n| **Linux** | `~/.config/spicetify/Extensions` or `$XDG_CONFIG_HOME/.config/spicetify/Extensions/` |\n| **MacOS** | `~/.config/spicetify/Extensions` or `$SPICETIFY_CONFIG/Extensions` |\n| **Windows** | `%userprofile%\\.spicetify\\Extensions\\` |\n\nAfter putting the extension file into the correct folder, run the following command to install the extension:\n\n```\nspicetify config extensions skipStats.js\nspicetify apply\n```\n\nNote: Using the `config` command to add the extension will always append the file name to the existing extensions list. It does not replace the whole key's value.\n\nOr you can manually edit your `config-xpui.ini` file. Add your desired extension filenames in the extensions key, separated them by the | character.\nExample:\n\n```ini\n[AdditionalOptions]\n...\nextensions = autoSkipExplicit.js|shuffle+.js|trashbin.js|skipStats.js\n```\n\n🌟 Like it? Gimme some love!  \n[![Github Stars badge](https://img.shields.io/github/stars/huhridge/huh-spicetify-extensions?logo=github&style=social)](https://github.com/huhridge/huh-spicetify-extensions/)\n"
  },
  {
    "path": "skipStats/skipStats.js",
    "content": "// @ts-check\n// NAME: Skip Stats\n// AUTHOR: huhridge\n// DESCRIPTION: Tracks skipping stats in playlists and albums.\n//TODO: Add liked songs.\n/// <reference path=\"../globals.d.ts\" />\n\n(function skipStats() {\n    if (!Spicetify.React || !Spicetify.ReactDOM || !Spicetify.Platform) {\n        setTimeout(skipStats, 200);\n        return;\n    }\n\n    const { LocalStorage, Platform, ReactDOM: reactDOM, React: react } = Spicetify;\n\n    let progress;\n\n    if (!Spicetify.LocalStorage.get(\"autoSkipThreshold\")) {\n        Spicetify.LocalStorage.set(\"autoSkipThreshold\", \"0\");\n    }\n\n    if (!LocalStorage.get(\"skipData\")) {\n        LocalStorage.set(\"skipData\", JSON.stringify({}));\n    }\n\n    setInterval(() => {\n        progress = Spicetify.Player.getProgressPercent();\n    }, 500);\n\n    Spicetify.Player.addEventListener(\"songchange\", trackSkips);\n\n    async function trackSkips() {\n        if (progress < 0.95) {\n            await delay(200);\n            //@ts-ignore\n            let song_key = Spicetify.Queue.prevTracks.slice(-1)[0].contextTrack.uri;\n            let skipData = JSON.parse(LocalStorage.get(\"skipData\"));\n            if (!skipData[song_key]) {\n                skipData[song_key] = 1;\n            } else {\n                skipData[song_key] += 1;\n            }\n            LocalStorage.set(\"skipData\", JSON.stringify(skipData));\n        }\n        //auto skip\n        let skipData = JSON.parse(LocalStorage.get(\"skipData\"));\n        let thresh = Number(Spicetify.LocalStorage.get(\"autoSkipThreshold\"));\n        if (thresh > 0 && skipData[Spicetify.Player.data.item.uri] >= thresh) {\n            Spicetify.Player.next();\n            Spicetify.showNotification(\"The track was auto-skipped due to being skipped too many times.\");\n        }\n        LocalStorage.set(\"skipData\", JSON.stringify(skipData));\n    }\n\n    function resetSkips(mode, uri = \"\") {\n        if (mode === \"all\") {\n            LocalStorage.set(\"skipData\", JSON.stringify({}));\n            Spicetify.showNotification(\"Resetted all skip data!\");\n        } else if (mode === \"current\") {\n            let skipData = JSON.parse(LocalStorage.get(\"skipData\"));\n            skipData[Spicetify.Player.data.item.uri] = 0;\n            LocalStorage.set(\"skipData\", JSON.stringify(skipData));\n            Spicetify.showNotification(\"Resetted skip data for current track!\");\n        } else if (mode === \"context\") {\n            let skipData = JSON.parse(LocalStorage.get(\"skipData\"));\n            skipData[uri] = 0;\n            LocalStorage.set(\"skipData\", JSON.stringify(skipData));\n            Spicetify.showNotification(\"Resetted skip data for selected track!\");\n        }\n    }\n\n    async function seeStats(uri) {\n        const uriObj = Spicetify.URI.fromString(uri);\n        let tracks;\n        switch (uriObj.type) {\n            case Spicetify.URI.Type.PLAYLIST:\n            case Spicetify.URI.Type.PLAYLIST_V2:\n                tracks = await fetchPlaylist(uri);\n                break;\n            case Spicetify.URI.Type.ALBUM:\n                tracks = await fetchAlbum(uri);\n                break;\n            case Spicetify.URI.Type.COLLECTION:\n                tracks = await fetchCollection();\n                break;\n        }\n        let skipData = JSON.parse(LocalStorage.get(\"skipData\"));\n        tracks = tracks.filter((item) => Boolean(skipData[item.uri]));\n        if (tracks.length == 0) {\n            Spicetify.showNotification(\"No Skipping Data found!\");\n            return;\n        }\n        tracks.forEach((item) => {\n            item.skips = skipData[item.uri];\n        });\n        tracks.sort((a, b) => b.skips - a.skips);\n        const skipTable = statTable({ tracks: tracks });\n        // @ts-ignore\n        Spicetify.PopupModal.display({ title: \"SkipStats\", content: skipTable, isLarge: true });\n    }\n\n    function delay(delayInms) {\n        return new Promise((resolve) => {\n            setTimeout(() => {\n                resolve(2);\n            }, delayInms);\n        });\n    }\n\n    const fetchPlaylist = async (uri) => {\n        const res = await Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri}/rows`);\n        return res.rows.map((item, index) => ({\n            uri: item.link,\n            title: item.name,\n            index: index + 1,\n            album: item.album.name,\n            artists: item.artists.map((item) => item.name).join(\", \"),\n        }));\n    };\n\n    const fetchCollection = async () => {\n        const res = await Spicetify.CosmosAsync.get(\"sp://core-collection/unstable/@/list/tracks/all?responseFormat=protobufJson\");\n        return res.item.map((item) => ({\n            uri: item.trackMetadata.link,\n            title: item.trackMetadata.name,\n            index: item.index + 1,\n            album: item.trackMetadata.album.name,\n            artists: item.trackMetadata.artist.map((item) => item.name).join(\", \"),\n        }));\n    };\n\n    const fetchAlbum = async (uri) => {\n        const arg = uri.split(\":\")[2];\n        const res = await Spicetify.CosmosAsync.get(`https://api.spotify.com/v1/albums/${arg}`);\n        return res.tracks.items.map((item) => ({\n            uri: item.uri,\n            title: item.name,\n            index: item.track_number,\n            album: res.name,\n            artists: item.artists.map((item) => item.name).join(\", \"),\n        }));\n    };\n\n    const statTable = ({ tracks }) => {\n        const headers = [\"Title\", \"Album\", \"Artists\", \"Skips\"];\n        let thresh = Number(Spicetify.LocalStorage.get(\"autoSkipThreshold\"));\n        let isThresh = Boolean(thresh);\n        const style = react.createElement(\"style\", {\n            dangerouslySetInnerHTML: {\n                __html: `\ndiv[aria-label=\"SkipStats\"] > div {\n    min-width: max-content;\n}\n.styled-table {\n    margin: 25px 0;\n    font-size: 0.9em;\n    font-family: sans-serif;\n    min-width: 400px;\n    box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);\n    overflow: hidden;\n    border-radius: 8px;\n}\n.styled-table thead tr {\n    background-color: var(--spice-sidebar);\n    color: var(--spice-sidebar-text);\n    text-align: left;\n    border-bottom: thin solid #dddddd;\n}\ntbody td:nth-child(4) {\n    text-align: center;\n}\n.styled-table th,\n.styled-table td {\n    padding: 12px 15px;\n    width: 1%;\n    white-space: nowrap;\n}\n.styled-table tbody tr {\n    border-bottom: thin solid #dddddd;\n}\ntd.auto-skip {\n    color: var(--spice-button-active);\n}\n                `,\n            },\n        });\n        return react.createElement(\n            \"table\",\n            { className: \"styled-table\" },\n            style,\n            react.createElement(\n                \"thead\",\n                null,\n                react.createElement(\n                    \"tr\",\n                    null,\n                    headers.map((item) => react.createElement(\"th\", null, item))\n                )\n            ),\n            react.createElement(\n                \"tbody\",\n                null,\n                tracks.map((track) =>\n                    react.createElement(\n                        \"tr\",\n                        {\n                            \"data-id\": track.uri,\n                        },\n                        // react.createElement(\"td\", null, track.index),\n                        react.createElement(\"td\", null, track.title),\n                        react.createElement(\"td\", null, track.album),\n                        react.createElement(\"td\", null, track.artists),\n                        react.createElement(\n                            \"td\",\n                            {\n                                className: isThresh && track.skips >= thresh ? \"auto-skip\" : \"\",\n                            },\n                            track.skips\n                        )\n                    )\n                )\n            )\n        );\n    };\n\n    const Config = ({ name, lkey }) => {\n        const [value, setValue] = react.useState(Spicetify.LocalStorage.get(lkey));\n\n        const setValueCallback = react.useCallback(\n            (event) => {\n                const value = event.target.value;\n                setValue(value);\n                Spicetify.LocalStorage.set(lkey, value);\n            },\n            [value]\n        );\n\n        const style = react.createElement(\"style\", {\n            dangerouslySetInnerHTML: {\n                __html: `\n.setting-row::after {\n    content: \"\";\n    display: table;\n    clear: both;\n}\n.setting-row .col {\n    padding: 16px 0 4px;\n    align-items: center;\n}\n.setting-row .col.description {\n    float: left;\n    padding-right: 15px;\n    cursor: default;\n}\n.setting-row .col.action {\n    float: right;\n    display: flex;\n    justify-content: flex-end;\n    align-items: center;\n}\n.col.action input {\n    width: 100%;\n    margin-top: 10px;\n    padding: 0 5px;\n    height: 32px;\n    border: 0;\n    color: var(--spice-text);\n    background-color: initial;\n    border-bottom: 1px solid var(--spice-text);\n}\n`,\n            },\n        });\n\n        return react.createElement(\n            \"div\",\n            {\n                className: \"skip-stats-config-container\",\n            },\n            style,\n            react.createElement(\n                \"div\",\n                {\n                    className: \"setting-row\",\n                },\n                react.createElement(\n                    \"label\",\n                    {\n                        className: \"col description\",\n                    },\n                    name\n                ),\n                react.createElement(\n                    \"div\",\n                    {\n                        className: \"col action\",\n                    },\n                    react.createElement(\"input\", {\n                        type: \"number\",\n                        value,\n                        onChange: setValueCallback,\n                    })\n                )\n            )\n        );\n    };\n\n    const currentSkips = new Spicetify.Menu.Item(\"See Skips for current playlist/album\", false, async () => {\n        const uriObj = Spicetify.URI.fromString(Spicetify.Player.data.context_uri);\n        switch (uriObj.type) {\n            case Spicetify.URI.Type.PLAYLIST:\n            case Spicetify.URI.Type.PLAYLIST_V2:\n            case Spicetify.URI.Type.ALBUM:\n            case Spicetify.URI.Type.COLLECTION:\n                await seeStats(Spicetify.Player.data.context_uri);\n                break;\n            default:\n                throw Spicetify.showNotification(\"Unsupported context type! Please use for a playlist or an album only\");\n        }\n    });\n\n    const autoSkip = new Spicetify.Menu.Item(\"Auto-Skip\", false, () => {\n        Spicetify.PopupModal.display({\n            title: \"Auto-Skip Threshold\",\n            content: react.createElement(Config, { name: \"Auto-Skip after this many skips (0 for off)\", lkey: \"autoSkipThreshold\" }),\n        });\n    });\n\n    const resetStatsAll = new Spicetify.Menu.Item(\"Reset all stats\", false, () => {\n        resetSkips(\"all\");\n    });\n\n    const resetStatsCurrent = new Spicetify.Menu.Item(\"Reset stats for current track\", false, () => {\n        resetSkips(\"current\");\n    });\n\n    new Spicetify.Menu.SubMenu(\"skipStats\", [currentSkips, autoSkip, resetStatsAll, resetStatsCurrent]).register();\n\n    new Spicetify.ContextMenu.Item(\n        \"See Skip Stats\",\n        async (uris) => {\n            await seeStats(uris[0]);\n        },\n        (uris) => {\n            if (uris.length > 1) {\n                return false;\n            }\n            const uriObj = Spicetify.URI.fromString(uris[0]);\n            switch (uriObj.type) {\n                case Spicetify.URI.Type.PLAYLIST:\n                case Spicetify.URI.Type.PLAYLIST_V2:\n                case Spicetify.URI.Type.ALBUM:\n                    // case Spicetify.URI.Type.COLLECTION:\n                    return true;\n            }\n            return false;\n        },\n        \"skip-forward\"\n    ).register();\n\n    new Spicetify.ContextMenu.Item(\n        \"Reset Skips for this track\",\n        (uris) => {\n            resetSkips(\"context\", uris[0]);\n        },\n        (uris) => {\n            if (uris.length != 1) return false;\n            return Spicetify.URI.fromString(uris[0]).type == Spicetify.URI.Type.TRACK;\n        }\n    ).register();\n})();\n"
  }
]