diff --git a/.github/workflows/empty-changelog.yaml b/.github/workflows/empty-changelog.yaml new file mode 100644 index 0000000..5f9a534 --- /dev/null +++ b/.github/workflows/empty-changelog.yaml @@ -0,0 +1,34 @@ +name: Empty CHANGELOG.md + +on: + release: + types: [published] + +jobs: + empty-changelog: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.event.repository.default_branch }} + + - name: Empty CHANGELOG.md + run: | + git config --global user.email "action@github.com" + git config --global user.name "github-actions" + + if [ -s CHANGELOG.md ]; then + echo "Comitting emptied CHANGELOG.md" + else + echo "CHANGELOG.md is already empty. skipping comitting" + exit 0 + fi + + rm CHANGELOG.md + touch CHANGELOG.md + + git add CHANGELOG.md + git commit -m "Empty CHANGELOG.md" + git push \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..e94f5b4 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,73 @@ +name: Release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version of Release (format: X.X.X)' + required: true + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Verify Input + run: | + [[ "${{ github.event.inputs.version }}" =~ ^[0-9]\.[0-9]\.[0-9]$ ]] && echo "Matches" && exit 0 || echo "Use versions like '1.2.3'" && exit 1 + + - name: Checkout + uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: Build Webpack + run: | + npm install + npm run build + echo "${{ github.event.inputs.version }}" > dist/VERSION + env: + DRIBBBLISH_VERSION: ${{ github.event.inputs.version }} + + - name: Zip Release + working-directory: dist + run: | + sudo apt-get install zip + zip -r DribbblishDynamic_v${{ github.event.inputs.version }}.zip * + mv DribbblishDynamic_v${{ github.event.inputs.version }}.zip .. + + - name: Read CHANGELOG.md + run: | + [ -s CHANGELOG.md ] && CHANGELOG=$(< CHANGELOG.md) || CHANGELOG="*Empty.*" + + echo "CHANGELOG<> $GITHUB_ENV + echo "$CHANGELOG" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + + - name: Upload Release + uses: softprops/action-gh-release@v1 + with: + fail_on_unmatched_files : true + files: DribbblishDynamic_v${{ github.event.inputs.version }}.zip + tag_name: ${{ github.event.inputs.version }} + draft: true + name: v${{ github.event.inputs.version }} + body: | + ## Changelog + ${{ env.CHANGELOG }} + + --- + ### Install / Update + #### Windows (PowerShell) + ```powershell + Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.ps1" | Invoke-Expression + ``` + #### Linux/MacOS (Bash) + ```bash + curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.sh | sh + ``` \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..04c01ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index c3d3691..2549d26 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,2 @@ -Vibrant.min.js \ No newline at end of file +Vibrant.min.js +dist/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 5b233cb..265236a 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,25 @@ # Dribbblish Dynamic ### Preview -![preview](showcase-images/preview.gif) + +img ## Features ### Resizable sidebar -img +img ### Customizable sidebar Rearrange icons positions, stick icons to header or hide unnecessary to save space. Turn on "Sidebar config" mode in Profile menu and hover on icon to show control buttons. After you finish customizing, turn off Config mode in Profile menu to save. -img +img ### Playlist Folder image Right click at folder and choose images for your playlist folder. Every image formats supported by Chrome can be used, but do keep image size small and in compressed format. -img +img ### Left/Right expanded cover In profile menu, toggle option "Right expanded cover" to change expaned current track cover image to left or right side, whereever you prefer. @@ -26,20 +27,30 @@ In profile menu, toggle option "Right expanded cover" to change expaned current ## Install / Update Make sure you are using spicetify >= v2.6.0 and Spotify >= v1.1.67. -Run these commands: - -### Windows -In **Powershell**: +### Windows (PowerShell) ```powershell Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.ps1" | Invoke-Expression ``` -### Linux and MacOS: -In **Bash**: +### Linux/MacOS (Bash) ```bash curl -fsSL https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/install.sh | sh ``` +### Manual Install +1. Download the latest [DribbblishDynamic_vX.X.X.zip](https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest) +2. Extract the files to your [Spicetify/Themes folder](https://github.com/khanhas/spicetify-cli/wiki/Customization#themes) +3. Copy `dribbblish-dynamic.js` to your [Spicetify/Extensions folder](https://github.com/khanhas/spicetify-cli/wiki/Extensions#installing) +4. Run: + ``` + spicetify config extensions dribbblish-dynamic.js + spicetify config current_theme DribbblishDynamic + spicetify config color_scheme base + spicetify config inject_css 1 replace_colors 1 overwrite_assets 1 + spicetify apply + ``` + +## IMPORTANT! From Spotify > v1.1.62, in sidebar, they use an adaptive render mechanic to actively show and hide items on scroll. It helps reducing number of items to render, hence there is significant performance boost if you have a large playlists collection. But the drawbacks is that item height is hard-coded, it messes up user interaction when we explicity change, in CSS, playlist item height bigger than original value. So you need to add these 2 lines in Patch section in config file: ```ini [Patch] @@ -50,30 +61,23 @@ xpui.js_repl_8008 = ,${1}56, ## Hide Window Controls Windows user, please edit your Spotify shortcut and add flag `--transparent-window-controls` after the Spotify.exe: To edit an taskbar shortcut, right click it, then right click Spotify in the list again. -![instruction1](showcase-images/windows-shortcut-instruction.png) + +img In addition to `--transparent-window-controls` you can set `Windows Top Bars` to `Solid` or `Transparent` to look like this: -![top-bars](showcase-images/top-bars.png) -Alternatively, you can use [`SpotifyNoControl.exe`](https://github.com/JulienMaille/SpotifyNoControl/files/6793911/SpotifyNoControl.zip), included in this theme package, to completely remove all windows controls and title menu (three dots at top left corner). Title menu still can be accessed via the Alt key. Closing and minimizing can be done via the right click menu at top window region. -`SpotifyNoControl.exe` could be used as Spotify launcher, it opens Spotify and hides controls right after. You can drag and drop it to your taskbar but make sure you unpin the original Spotify icon first. Alternatively you can make a shortcut for it and add to desktop or start menu. +img -![nocontrol](https://i.imgur.com/qdZyv1t.png) - -## Auto-uninstall -### Windows +## Uninstall +### Windows (PowerShell) ```powershell Invoke-WebRequest -UseBasicParsing "https://raw.githubusercontent.com/JulienMaille/dribbblish-dynamic-theme/master/uninstall.ps1" | Invoke-Expression ``` -## Manual uninstall -Remove the dribbblish script with the following commands - -``` -spicetify config extensions dribbblish.js- -spicetify config extensions dribbblish-dynamic.js- -``` -And remove Patch lines you added in config file earlier. Finally, run: -``` -spicetify apply -``` +### Manual Uninstall +1. Remove Patch lines you added in config file earlier. +2. Run: + ``` + spicetify config extensions dribbblish-dynamic.js- + spicetify apply + ``` \ No newline at end of file diff --git a/Vibrant.min.js b/Vibrant.min.js deleted file mode 100644 index d858a58..0000000 --- a/Vibrant.min.js +++ /dev/null @@ -1,23 +0,0 @@ -(function e$$0(x,z,l){function h(p,b){if(!z[p]){if(!x[p]){var a="function"==typeof require&&require;if(!b&&a)return a(p,!0);if(g)return g(p,!0);a=Error("Cannot find module '"+p+"'");throw a.code="MODULE_NOT_FOUND",a;}a=z[p]={exports:{}};x[p][0].call(a.exports,function(a){var b=x[p][1][a];return h(b?b:a)},a,a.exports,e$$0,x,z,l)}return z[p].exports}for(var g="function"==typeof require&&require,w=0;wg?1:0},sum:function(h,g){var l={};return h.reduce(g?function(h,b,a){l.index=a;return h+g.call(l,b)}:function(h,b){return h+b},0)},max:function(h,g){return Math.max.apply(null,g?l.map(h,g):h)}};A=function(){function h(f,c,a){return(f<<2*d)+(c<>e;m=f[1]>>e;r=f[2]>>e;a=h(b,m,r);c[a]=(c[a]||0)+1});return c} -function a(f,c){var a=1E6,b=0,m=1E6,d=0,q=1E6,n=0,h,k,l;f.forEach(function(c){h=c[0]>>e;k=c[1]>>e;l=c[2]>>e;hb&&(b=h);kd&&(d=k);ln&&(n=l)});return new w(a,b,m,d,q,n,c)}function n(a,c){function b(a){var f=a+"1";a+="2";var v,d,m,e;d=0;for(k=c[f];k<=c[a];k++)if(y[k]>n/2){m=c.copy();e=c.copy();v=k-c[f];d=c[a]-k;for(v=v<=d?Math.min(c[a]-1,~~(k+d/2)):Math.max(c[f],~~(k-1-v/2));!y[v];)v++;for(d=s[v];!d&&y[v-1];)d=s[--v];m[a]=v;e[f]=m[a]+1;return[m,e]}}if(c.count()){var d=c.r2- -c.r1+1,m=c.g2-c.g1+1,e=l.max([d,m,c.b2-c.b1+1]);if(1==c.count())return[c.copy()];var n=0,y=[],s=[],k,g,t,u,p;if(e==d)for(k=c.r1;k<=c.r2;k++){u=0;for(g=c.g1;g<=c.g2;g++)for(t=c.b1;t<=c.b2;t++)p=h(k,g,t),u+=a[p]||0;n+=u;y[k]=n}else if(e==m)for(k=c.g1;k<=c.g2;k++){u=0;for(g=c.r1;g<=c.r2;g++)for(t=c.b1;t<=c.b2;t++)p=h(g,k,t),u+=a[p]||0;n+=u;y[k]=n}else for(k=c.b1;k<=c.b2;k++){u=0;for(g=c.r1;g<=c.r2;g++)for(t=c.g1;t<=c.g2;t++)p=h(g,t,k),u+=a[p]||0;n+=u;y[k]=n}y.forEach(function(a,c){s[c]=n-a});return e== -d?b("r"):e==m?b("g"):b("b")}}var d=5,e=8-d;w.prototype={volume:function(a){if(!this._volume||a)this._volume=(this.r2-this.r1+1)*(this.g2-this.g1+1)*(this.b2-this.b1+1);return this._volume},count:function(a){var c=this.histo;if(!this._count_set||a){a=0;var b,d,n;for(b=this.r1;b<=this.r2;b++)for(d=this.g1;d<=this.g2;d++)for(n=this.b1;n<=this.b2;n++)index=h(b,d,n),a+=c[index]||0;this._count=a;this._count_set=!0}return this._count},copy:function(){return new w(this.r1,this.r2,this.g1,this.g2,this.b1, -this.b2,this.histo)},avg:function(a){var c=this.histo;if(!this._avg||a){a=0;var b=1<<8-d,n=0,e=0,g=0,q,l,s,k;for(l=this.r1;l<=this.r2;l++)for(s=this.g1;s<=this.g2;s++)for(k=this.b1;k<=this.b2;k++)q=h(l,s,k),q=c[q]||0,a+=q,n+=q*(l+0.5)*b,e+=q*(s+0.5)*b,g+=q*(k+0.5)*b;this._avg=a?[~~(n/a),~~(e/a),~~(g/a)]:[~~(b*(this.r1+this.r2+1)/2),~~(b*(this.g1+this.g2+1)/2),~~(b*(this.b1+this.b2+1)/2)]}return this._avg},contains:function(a){var c=a[0]>>e;gval=a[1]>>e;bval=a[2]>>e;return c>=this.r1&&c<=this.r2&& -gval>=this.g1&&gval<=this.g2&&bval>=this.b1&&bval<=this.b2}};p.prototype={push:function(a){this.vboxes.push({vbox:a,color:a.avg()})},palette:function(){return this.vboxes.map(function(a){return a.color})},size:function(){return this.vboxes.size()},map:function(a){for(var c=this.vboxes,b=0;bb[0]&&5>b[1]&&5>b[2]&&(a[0].color=[0,0,0]);var b=a.length-1,n=a[b].color;251d;)if(f=a.pop(),f.count()){var m=n(h,f);f=m[0];m=m[1];if(!f)break; -a.push(f);m&&(a.push(m),c++);if(c>=b)break;if(1E3c||256this.yiq?"#fff":"#000"};b.prototype.getBodyTextColor=function(){this._ensureTextColors();return 150>this.yiq?"#fff":"#000"};b.prototype._ensureTextColors=function(){if(!this.yiq)return this.yiq=(299*this.rgb[0]+587*this.rgb[1]+114*this.rgb[2])/1E3};return b}();window.Vibrant=g=function(){function b(a,b,d){this.swatches=w(this.swatches,this);var e,f, -c,g,p,m,r,q;"undefined"===typeof b&&(b=64);"undefined"===typeof d&&(d=5);p=new l(a);r=p.getImageData().data;m=p.getPixelCount();a=[];for(g=0;g=f&&s<=c&&m>=b&&m<=d&&!this.isAlreadySelected(k)&&(m=this.createComparisonValue(s,e,m,a, -k.getPopulation(),this.HighestPopulation),void 0===l||m>q))l=k,q=m;return l};b.prototype.createComparisonValue=function(a,b,d,e,f,c){return this.weightedMean(this.invertDiff(a,b),this.WEIGHT_SATURATION,this.invertDiff(d,e),this.WEIGHT_LUMA,f/c,this.WEIGHT_POPULATION)};b.prototype.invertDiff=function(a,b){return 1-Math.abs(a-b)};b.prototype.weightedMean=function(){var a,b,d,e,f,c;f=1<=arguments.length?p.call(arguments,0):[];for(a=d=b=0;ac&&(c+=1);1c?b:c<2/3?a+(b-a)*(2/3-c)*6:a};0===b?c=f=e=d:(b=0.5>d?d*(1+b):d+b-d*b,d=2*d-b,c=e(d,b,a+1/3),f=e(d,b,a),e=e(d,b,a-1/3));return[255*c,255*f,255*e]};return b}();window.CanvasImage=l=function(){function b(a){this.canvas= -document.createElement("canvas");this.context=this.canvas.getContext("2d");document.body.appendChild(this.canvas);this.width=this.canvas.width=a.width;this.height=this.canvas.height=a.height;this.context.drawImage(a,0,0,this.width,this.height)}b.prototype.clear=function(){return this.context.clearRect(0,0,this.width,this.height)};b.prototype.update=function(a){return this.context.putImageData(a,0,0)};b.prototype.getPixelCount=function(){return this.width*this.height};b.prototype.getImageData=function(){return this.context.getImageData(0, -0,this.width,this.height)};b.prototype.removeCanvas=function(){return this.canvas.parentNode.removeChild(this.canvas)};return b}()}).call(this)},{quantize:1}]},{},[2]); diff --git a/dribbblish-dynamic.js b/dribbblish-dynamic.js deleted file mode 100644 index a404422..0000000 --- a/dribbblish-dynamic.js +++ /dev/null @@ -1,453 +0,0 @@ -let current = "2.6.0"; - -/* Config settings */ - -DribbblishShared.config.register({ - area: "Animations & Transitions", - type: "slider", - key: "fadeDuration", - name: "Color Fade Duration", - description: "Select the duration of the color fading transition", - defaultValue: 0.5, - data: { - min: 0, - max: 10, - step: 0.1, - suffix: "s" - }, - onChange: (val) => document.documentElement.style.setProperty("--song-transition-speed", val + "s") -}); - -// waitForElement because Spicetify is not initialized at startup -waitForElement(["#main"], () => { - DribbblishShared.config.register({ - area: { name: "About", order: 999 }, - type: "button", - key: "aboutDribbblish", - name: "Info", - description: ` - OS: ${capitalizeFirstLetter(Spicetify.Platform.PlatformData.os_name)} v${Spicetify.Platform.PlatformData.os_version} - Spotify: v${Spicetify.Platform.PlatformData.event_sender_context_information?.client_version_string ?? Spicetify.Platform.PlatformData.client_version_triple} - Dribbblish: v${current} - `, - data: "Copy", - onChange: (val) => { - copyToClipboard(DribbblishShared.config.getOptions("aboutDribbblish").description); - Spicetify.showNotification("Copied Versions"); - } - }); -}); - -function capitalizeFirstLetter(string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} - -function copyToClipboard(text) { - var input = document.createElement("textarea"); - input.style.display = "fixed"; - input.innerHTML = text; - document.body.appendChild(input); - input.select(); - var result = document.execCommand("copy"); - document.body.removeChild(input); - return result; -} - -/* js */ -function getAlbumInfo(uri) { - return Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`); -} - -function isLight(hex) { - var [r, g, b] = hexToRgb(hex).map(Number); - const brightness = (r * 299 + g * 587 + b * 114) / 1000; - return brightness > 128; -} - -function hexToRgb(hex) { - var bigint = parseInt(hex.replace("#", ""), 16); - var r = (bigint >> 16) & 255; - var g = (bigint >> 8) & 255; - var b = bigint & 255; - return [r, g, b]; -} - -function rgbToHex([r, g, b]) { - const rgb = (r << 16) | (g << 8) | (b << 0); - return "#" + (0x1000000 + rgb).toString(16).slice(1); -} - -const LightenDarkenColor = (h, p) => - "#" + - [1, 3, 5] - .map((s) => parseInt(h.substr(s, 2), 16)) - .map((c) => parseInt((c * (100 + p)) / 100)) - .map((c) => (c < 255 ? c : 255)) - .map((c) => c.toString(16).padStart(2, "0")) - .join(""); - -function rgbToHsl([r, g, b]) { - (r /= 255), (g /= 255), (b /= 255); - var max = Math.max(r, g, b), - min = Math.min(r, g, b); - var h, - s, - l = (max + min) / 2; - if (max == min) { - h = s = 0; // achromatic - } else { - var d = max - min; - s = l > 0.5 ? d / (2 - max - min) : d / (max + min); - switch (max) { - case r: - h = (g - b) / d + (g < b ? 6 : 0); - break; - case g: - h = (b - r) / d + 2; - break; - case b: - h = (r - g) / d + 4; - break; - } - h /= 6; - } - return [h, s, l]; -} - -function hslToRgb([h, s, l]) { - var r, g, b; - if (s == 0) { - r = g = b = l; // achromatic - } else { - function hue2rgb(p, q, t) { - if (t < 0) t += 1; - if (t > 1) t -= 1; - if (t < 1 / 6) return p + (q - p) * 6 * t; - if (t < 1 / 2) return q; - if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; - return p; - } - var q = l < 0.5 ? l * (1 + s) : l + s - l * s; - var p = 2 * l - q; - r = hue2rgb(p, q, h + 1 / 3); - g = hue2rgb(p, q, h); - b = hue2rgb(p, q, h - 1 / 3); - } - return [r * 255, g * 255, b * 255]; -} - -function setLightness(hex, lightness) { - hsl = rgbToHsl(hexToRgb(hex)); - hsl[2] = lightness; - return rgbToHex(hslToRgb(hsl)); -} - -function parseComputedStyleColor(col) { - if (col.startsWith("#")) return col; - if (col.startsWith("rgb(")) - return rgbToHex( - col - .replace(/rgb|(|)/g, "") - .split(",") - .map((part) => Number(part.trim())) - ); -} - -// `parseComputedStyleColor()` beacuse "--spice-sidebar" is `rgb()` -let textColor = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue("--spice-text")); -let textColorBg = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue("--spice-main")); -let sidebarColor = parseComputedStyleColor(getComputedStyle(document.documentElement).getPropertyValue("--spice-sidebar")); - -function setRootColor(name, colHex) { - let root = document.documentElement; - if (root === null) return; - root.style.setProperty("--spice-" + name, colHex); - root.style.setProperty("--spice-rgb-" + name, hexToRgb(colHex).join(",")); -} - -function toggleDark(setDark) { - if (setDark === undefined) setDark = isLight(textColorBg); - - document.documentElement.style.setProperty("--is_light", setDark ? 0 : 1); - textColorBg = setDark ? "#0A0A0A" : "#FAFAFA"; - - setRootColor("main", textColorBg); - setRootColor("player", textColorBg); - setRootColor("card", setDark ? "#040404" : "#ECECEC"); - setRootColor("subtext", setDark ? "#EAEAEA" : "#3D3D3D"); - setRootColor("notification", setDark ? "#303030" : "#DDDDDD"); - - updateColors(textColor, sidebarColor, false); -} - -function checkDarkLightMode(colors) { - const theme = DribbblishShared.config.get("theme"); - if (theme == 2) { - // Based on Time - const start = 60 * parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[1]); - const end = 60 * parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[1]); - - const now = new Date(); - const time = 60 * now.getHours() + now.getMinutes(); - - if (end < start) dark = start <= time || time < end; - else dark = start <= time && time < end; - toggleDark(dark); - } else if (theme == 3) { - // Based on Color - if (colors && colors.length > 0) toggleDark(isLight(colors[0])); - } -} -// Run every Minute to check time and set dark / light mode -setInterval(checkDarkLightMode, 60000); - -DribbblishShared.config.register({ - area: "Theme", - type: "checkbox", - key: "dynamicColors", - name: "Dynamic", - description: "If the Theme's Color should be extracted from Albumart", - defaultValue: true, - onChange: (val) => updateColors(), - showChildren: (val) => !val, - children: [ - { - type: "color", - key: "colorOverride", - name: "Color", - description: "The Color of the Theme", - defaultValue: "#1ed760", - fireInitialChange: false, - onChange: (val) => updateColors() - } - ] -}); - -DribbblishShared.config.register({ - area: "Theme", - type: "select", - data: ["Dark", "Light", "Based on Time", "Based on Color"], - key: "theme", - name: "Theme", - description: "Select Dark / Bright mode", - defaultValue: 0, - showChildren: (val) => { - if (val == 2) return ["darkModeOnTime", "darkModeOffTime"]; - //if (val == 3) return [""]; - return false; - }, - onChange: (val) => { - switch (val) { - case 0: - toggleDark(true); - break; - case 1: - toggleDark(false); - break; - case 2: - checkDarkLightMode(); - break; - case 3: - checkDarkLightMode(); - break; - } - }, - children: [ - { - type: "time", - key: "darkModeOnTime", - name: "Dark Mode On Time", - description: "Beginning of Dark mode time", - defaultValue: "20:00", - fireInitialChange: false, - onChange: checkDarkLightMode - }, - { - type: "time", - key: "darkModeOffTime", - name: "Dark Mode Off Time", - description: "End of Dark mode time", - defaultValue: "06:00", - fireInitialChange: false, - onChange: checkDarkLightMode - } - ] -}); - -var currentColor; -var currentSideColor; - -function updateColors(textColHex, sideColHex, checkDarkMode = true) { - if (textColHex && sideColHex) { - currentColor = textColHex; - currentSideColor = sideColHex; - } else { - if (!(currentColor && currentSideColor)) return; // If `updateColors()` is called early these vars are undefined and would break - textColHex = currentColor; - sideColHex = currentSideColor; - } - - if (!DribbblishShared.config.get("dynamicColors")) { - const col = DribbblishShared.config.get("colorOverride"); - textColHex = col; - sideColHex = col; - } - - let isLightBg = isLight(textColorBg); - if (isLightBg) textColHex = LightenDarkenColor(textColHex, -15); // vibrant color is always too bright for white bg mode - - let darkColHex = LightenDarkenColor(textColHex, isLightBg ? 12 : -20); - let darkerColHex = LightenDarkenColor(textColHex, isLightBg ? 30 : -40); - let buttonBgColHex = setLightness(textColHex, isLightBg ? 0.9 : 0.14); - setRootColor("text", textColHex); - setRootColor("button", darkerColHex); - setRootColor("button-active", darkColHex); - setRootColor("selected-row", darkerColHex); - setRootColor("tab-active", buttonBgColHex); - setRootColor("button-disabled", buttonBgColHex); - setRootColor("sidebar", sideColHex); - - if (checkDarkMode) checkDarkLightMode([textColHex, sideColHex]); -} - -let nearArtistSpanText = ""; -let coverListenerInstalled = false; -async function songchange() { - try { - // warning popup - if (Spicetify.Platform.PlatformData.client_version_triple < "1.1.68") Spicetify.showNotification(`Your version of Spotify ${Spicetify.Platform.PlatformData.client_version_triple}) is un-supported`); - } catch (err) { - console.error(err); - } - - let album_uri = Spicetify.Player.data.track.metadata.album_uri; - let bgImage = Spicetify.Player.data.track.metadata.image_url; - if (bgImage === undefined) { - bgImage = "/images/tracklist-row-song-fallback.svg"; - textColor = "#509bf5"; - updateColors(textColor, textColor); - coverListenerInstalled = false; - } - if (!coverListenerInstalled) hookCoverChange(true); - - if (album_uri !== undefined && !album_uri.includes("spotify:show")) { - const albumInfo = await getAlbumInfo(album_uri.replace("spotify:album:", "")); - - let album_date = new Date(albumInfo.year, (albumInfo.month || 1) - 1, albumInfo.day || 0); - let recent_date = new Date(); - recent_date.setMonth(recent_date.getMonth() - 6); - album_date = album_date.toLocaleString("default", album_date > recent_date ? { year: "numeric", month: "short" } : { year: "numeric" }); - album_link = '' + Spicetify.Player.data.track.metadata.album_title + ""; - - nearArtistSpanText = album_link + " • " + album_date; - } else if (Spicetify.Player.data.track.uri.includes("spotify:episode")) { - // podcast - bgImage = bgImage.replace("spotify:image:", "https://i.scdn.co/image/"); - nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title; - } else if (Spicetify.Player.data.track.metadata.is_local == "true") { - // local file - nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title; - } else if (Spicetify.Player.data.track.provider == "ad") { - // ad - nearArtistSpanText = "advertisement"; - coverListenerInstalled = false; - return; - } else { - // When clicking a song from the homepage, songChange is fired with half empty metadata - // todo: retry only once? - setTimeout(songchange, 200); - } - - if (document.querySelector("#main-trackInfo-year") === null) { - waitForElement([".main-trackInfo-container"], (queries) => { - nearArtistSpan = document.createElement("div"); - nearArtistSpan.id = "main-trackInfo-year"; - nearArtistSpan.classList.add("main-trackInfo-artists", "ellipsis-one-line", "main-type-finale"); - nearArtistSpan.innerHTML = nearArtistSpanText; - queries[0].append(nearArtistSpan); - }); - } else { - nearArtistSpan.innerHTML = nearArtistSpanText; - } - document.documentElement.style.setProperty("--image_url", 'url("' + bgImage + '")'); -} - -Spicetify.Player.addEventListener("songchange", songchange); - -function pickCoverColor(img) { - if (!img.currentSrc.startsWith("spotify:")) return; - var swatches = new Vibrant(img, 5).swatches(); - lightCols = ["Vibrant", "DarkVibrant", "Muted", "LightVibrant"]; - darkCols = ["Vibrant", "LightVibrant", "Muted", "DarkVibrant"]; - - mainCols = isLight(textColorBg) ? lightCols : darkCols; - textColor = "#509bf5"; - for (var col in mainCols) - if (swatches[mainCols[col]]) { - textColor = swatches[mainCols[col]].getHex(); - break; - } - - sidebarColor = "#509bf5"; - for (var col in lightCols) - if (swatches[lightCols[col]]) { - sidebarColor = swatches[lightCols[col]].getHex(); - break; - } - updateColors(textColor, sidebarColor); -} - -waitForElement([".main-nowPlayingBar-left"], (queries) => { - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - if (mutation.removedNodes.length > 0) coverListenerInstalled = false; - }); - }); - observer.observe(queries[0], { childList: true }); -}); - -function hookCoverChange(pick) { - waitForElement([".cover-art-image"], (queries) => { - coverListenerInstalled = true; - var elem = queries.slice(-1)[0]; - if (pick && elem.complete && elem.naturalHeight !== 0) pickCoverColor(elem); - elem.addEventListener("load", function () { - try { - pickCoverColor(elem); - } catch (error) { - console.error(error); - setTimeout(pickCoverColor, 300, elem); - } - }); - }); -} - -(function Startup() { - if (!Spicetify.showNotification) { - setTimeout(Startup, 300); - return; - } - // Check latest release - fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest") - .then((response) => { - return response.json(); - }) - .then((data) => { - if (data.tag_name > current) { - upd = document.createElement("div"); - upd.innerText = `Theme UPD v${data.tag_name} avail.`; - upd.classList.add("ellipsis-one-line", "main-type-finale"); - upd.setAttribute("title", `Changes: ${data.name}`); - upd.style.setProperty("color", "var(--spice-button-active)"); - document.querySelector(".main-userWidget-box").append(upd); - document.querySelector(".main-userWidget-box").classList.add("update-avail"); - new Spicetify.Menu.Item("Update Dribbblish", false, () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/blob/main/README.md#install--update", "_blank")).register(); - } - }) - .catch((err) => { - // Do something for an error here - console.error(err); - }); -})(); - -document.documentElement.style.setProperty("--warning_message", " "); diff --git a/install.ps1 b/install.ps1 index 9436f5d..8a938f9 100644 --- a/install.ps1 +++ b/install.ps1 @@ -45,6 +45,7 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) { Write-Done $version = ($latest_release_json | ConvertFrom-Json).tag_name -replace "v", "" + $download_uri = ($latest_release_json | ConvertFrom-Json).assets[0].browser_download_url } # Check ~\spicetify-cli\Themes directory already exists @@ -57,7 +58,6 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) { # Download release. $zip_file = "${sp_dir}\${version}.zip" - $download_uri = "https://github.com/JulienMaille/dribbblish-dynamic-theme/archive/refs/tags/${version}.zip" Write-Part "DOWNLOADING "; Write-Emphasized $download_uri Invoke-WebRequest -Uri $download_uri -UseBasicParsing -OutFile $zip_file Write-Done @@ -65,7 +65,7 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) { # Extract theme from .zip file. Write-Part "EXTRACTING "; Write-Emphasized $zip_file Write-Part " into "; Write-Emphasized ${sp_dir}; - Expand-Archive -Path $zip_file -DestinationPath $sp_dir -Force + Expand-Archive -Path $zip_file -DestinationPath "${sp_dir}\dribbblish-dynamic-theme-${version}" -Force Write-Done # Remove .zip file. @@ -90,11 +90,9 @@ if ($PSVersionTable.PSVersion.Major -gt $PSMinVersion) { # Installing. Write-Part "INSTALLING"; cd $sp_dot_dir - Copy-Item dribbblish.js ..\..\Extensions Copy-Item dribbblish-dynamic.js ..\..\Extensions - Copy-Item Vibrant.min.js ..\..\Extensions - spicetify config extensions default-dynamic.js- - spicetify config extensions dribbblish.js extensions dribbblish-dynamic.js extensions Vibrant.min.js + spicetify config extensions default-dynamic.js- extensions dribbblish-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js- + spicetify config extensions dribbblish-dynamic.js spicetify config current_theme DribbblishDynamic spicetify config color_scheme base spicetify config inject_css 1 replace_colors 1 overwrite_assets 1 diff --git a/install.sh b/install.sh index b262732..f447b4f 100644 --- a/install.sh +++ b/install.sh @@ -10,13 +10,14 @@ if [ $# -eq 0 ]; then version=$( command curl -sSf "$latest_release_uri" | command grep -Eo "tag_name\": .*" | command grep -Eo "[0-9.]+" ) + download_uri=$( command curl -sSf "$latest_release_uri" | + command grep -Eo "browser_download_url\": .*" | + command grep -Eo "http.*?\.zip" ) if [ ! "$version" ]; then exit 1; fi else version="${1}" fi -download_uri="https://github.com/JulienMaille/dribbblish-dynamic-theme/archive/refs/tags/${version}.zip" - spicetify_install="${SPICETIFY_INSTALL:-$HOME/spicetify-cli/Themes}" if [ ! -d "$spicetify_install" ]; then @@ -31,7 +32,7 @@ curl --fail --location --progress-bar --output "$tar_file" "$download_uri" cd "$spicetify_install" echo "EXTRACTING $tar_file" -unzip -o "$tar_file" +unzip -d "$spicetify_install/dribbblish-dynamic-theme-${version}" -o "$tar_file" echo "REMOVING $tar_file" rm "$tar_file" @@ -49,10 +50,9 @@ cp -rf "$spicetify_install/dribbblish-dynamic-theme-${version}/." "$sp_dot_dir" echo "INSTALLING" cd "$(dirname "$(spicetify -c)")/Themes/DribbblishDynamic" mkdir -p ../../Extensions -cp dribbblish.js ../../Extensions/. cp dribbblish-dynamic.js ../../Extensions/. -cp Vibrant.min.js ../../Extensions/. -spicetify config extensions dribbblish.js extensions dribbblish-dynamic.js extensions Vibrant.min.js +spicetify config extensions default-dynamic.js- extensions dribbblish-dynamic.js- extensions dribbblish.js- extensions Vibrant.min.js- +spicetify config extensions dribbblish-dynamic.js spicetify config current_theme DribbblishDynamic spicetify config color_scheme base spicetify config inject_css 1 replace_colors 1 overwrite_assets 1 diff --git a/package.json b/package.json new file mode 100644 index 0000000..0e7baab --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "devDependencies": { + "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^9.0.1", + "css-minimizer-webpack-plugin": "^3.1.1", + "node-sass": "^6.0.1", + "sass-loader": "^12.2.0", + "webpack": "^5.58.2", + "webpack-cli": "^4.9.0" + }, + "scripts": { + "build": "webpack --mode=development" + }, + "dependencies": { + "chroma-js": "^2.1.2", + "jquery": "^3.6.0", + "node-vibrant": "3.1.4" + } +} diff --git a/showcase-images/customize-sidebar.png b/showcase-images/customize-sidebar.png new file mode 100644 index 0000000..aab9789 Binary files /dev/null and b/showcase-images/customize-sidebar.png differ diff --git a/showcase-images/playlist-folders.gif b/showcase-images/playlist-folders.gif new file mode 100644 index 0000000..5c16db4 Binary files /dev/null and b/showcase-images/playlist-folders.gif differ diff --git a/showcase-images/resize-sidebar.png b/showcase-images/resize-sidebar.png new file mode 100644 index 0000000..c47f557 Binary files /dev/null and b/showcase-images/resize-sidebar.png differ diff --git a/assets/glue-resources/fonts/GoogleSansDisplayMedium.woff2 b/src/assets/glue-resources/fonts/GoogleSansDisplayMedium.woff2 similarity index 100% rename from assets/glue-resources/fonts/GoogleSansDisplayMedium.woff2 rename to src/assets/glue-resources/fonts/GoogleSansDisplayMedium.woff2 diff --git a/assets/glue-resources/fonts/GoogleSansDisplayRegular.woff2 b/src/assets/glue-resources/fonts/GoogleSansDisplayRegular.woff2 similarity index 100% rename from assets/glue-resources/fonts/GoogleSansDisplayRegular.woff2 rename to src/assets/glue-resources/fonts/GoogleSansDisplayRegular.woff2 diff --git a/assets/glue-resources/fonts/Roboto.woff2 b/src/assets/glue-resources/fonts/Roboto.woff2 similarity index 100% rename from assets/glue-resources/fonts/Roboto.woff2 rename to src/assets/glue-resources/fonts/Roboto.woff2 diff --git a/assets/glue-resources/fonts/RobotoMedium.woff2 b/src/assets/glue-resources/fonts/RobotoMedium.woff2 similarity index 100% rename from assets/glue-resources/fonts/RobotoMedium.woff2 rename to src/assets/glue-resources/fonts/RobotoMedium.woff2 diff --git a/color.ini b/src/color.ini similarity index 96% rename from color.ini rename to src/color.ini index 437d5e0..ce40d6d 100644 --- a/color.ini +++ b/src/color.ini @@ -1,162 +1,162 @@ -[base] -text = FFFFFF -subtext = F0F0F0 -sidebar-text = FFFFFF -main = 000000 -sidebar = 1ed760 -player = 000000 -card = 000000 -shadow = 202020 -selected-row = 797979 -button = 1ed760 -button-active = 1ed760 -button-disabled = 535353 -tab-active = 166632 -notification = 1db954 -notification-error = e22134 -misc = BFBFBF - - -[white] -text = 363636 -subtext = 3D3D3D -sidebar-text = FFF9F4 -main = FFF9F4 -sidebar = FFA789 -player = FFF9F4 -card = FFF9F4 -shadow = d3d3d3 -selected-row = 6D6D6D -button = ff8367 -button-active = ff8367 -button-disabled = 535353 -tab-active = ffdace -notification = FFA789 -notification-error = e22134 -misc = BFBFBF - -[dark] -text = F0F0F0 -subtext = F0F0F0 -sidebar-text = 0a0e14 -main = 0a0e14 -sidebar = C2D935 -player = 0a0e14 -card = 0a0e14 -shadow = 202020 -selected-row = DEDEDE -button = C2D935 -button-active = C2D935 -button-disabled = 535353 -tab-active = 727d2b -notification = C2D935 -notification-error = e22134 -misc = BFBFBF - -[dracula] -text = f8f8f2 -subtext = f8f8f2 -sidebar-text = F0F0F0 -main = 44475a -sidebar = 6272a4 -player = 44475a -card = 6272a4 -shadow = 000000 -selected-row = bd93f9 -button = ffb86c -button-active = 8be9fd -button-disabled = 535353 -tab-active = 6272a4 -notification = bd93f9 -notification-error = e22134 -misc = BFBFBF - -[nord-light] -text = 2e3440 -subtext = 3b4252 -sidebar-text = ECEFF4 -main = ECEFF4 -sidebar = 5E81AC -player = ECEFF4 -card = ebcb8b -shadow = eceff4 -selected-row = 4c566a -button = 81a1c1 -button-active = 81a1c1 -button-disabled = c0c0c0 -tab-active = ebcb8b -notification = a3be8c -notification-error = bf616a -misc = BFBFBF - -[nord-dark] -text = ECEFF4 -subtext = E5E9F0 -sidebar-text = 434c5e -main = 2e3440 -sidebar = 88C0D0 -player = 2e3440 -card = 2e3440 -shadow = 2E3440 -selected-row = D8DEE9 -button = 81A1C1 -button-active = 81A1C1 -button-disabled = 434C5E -tab-active = 434C5E -notification = A3BE8C -notification-error = BF616A -misc = BFBFBF - -[purple] -text = f1eaff -subtext = f1eaff -sidebar-text = e0d0ff -main = 0A0E14 -sidebar = 6F3C89 -player = 0A0E14 -card = 0A0E14 -shadow = 3a2645 -selected-row = EBDFFF -button = c76af6 -button-active = 6F3C89 -button-disabled = 535353 -tab-active = 58306D -notification = ff9e00 -notification-error = f61379 -misc = DEDEDE - -[samourai] -text = ebdbb2 -subtext = ebdbb2 -sidebar-text = 461217 -main = 461217 -sidebar = ebdbb2 -player = 461217 -card = 461217 -shadow = 3a2645 -selected-row = 909090 -button = e7a52d -button-active = e7a52d -button-disabled = 535353 -tab-active = e7a52d -notification = e7a52d -notification-error = e22134 -misc = BFBFBF - -[beach-sunset] -text = FFFFFF -subtext = F0F0F0 -sidebar-text = F0F0F0 -main = 262626 -sidebar = bd3e3e -player = 262626 -card = 262626 -shadow = 000000 -selected-row = d1d6e2 -button = f1a84f -button-active = c98430 -button-disabled = 535353 -tab-active = f1a84f -notification = c98430 -notification-error = e22134 +[base] +text = FFFFFF +subtext = F0F0F0 +sidebar-text = FFFFFF +main = 000000 +sidebar = 1ed760 +player = 000000 +card = 000000 +shadow = 202020 +selected-row = 797979 +button = 1ed760 +button-active = 1ed760 +button-disabled = 535353 +tab-active = 166632 +notification = 1db954 +notification-error = e22134 +misc = BFBFBF + + +[white] +text = 363636 +subtext = 3D3D3D +sidebar-text = FFF9F4 +main = FFF9F4 +sidebar = FFA789 +player = FFF9F4 +card = FFF9F4 +shadow = d3d3d3 +selected-row = 6D6D6D +button = ff8367 +button-active = ff8367 +button-disabled = 535353 +tab-active = ffdace +notification = FFA789 +notification-error = e22134 +misc = BFBFBF + +[dark] +text = F0F0F0 +subtext = F0F0F0 +sidebar-text = 0a0e14 +main = 0a0e14 +sidebar = C2D935 +player = 0a0e14 +card = 0a0e14 +shadow = 202020 +selected-row = DEDEDE +button = C2D935 +button-active = C2D935 +button-disabled = 535353 +tab-active = 727d2b +notification = C2D935 +notification-error = e22134 +misc = BFBFBF + +[dracula] +text = f8f8f2 +subtext = f8f8f2 +sidebar-text = F0F0F0 +main = 44475a +sidebar = 6272a4 +player = 44475a +card = 6272a4 +shadow = 000000 +selected-row = bd93f9 +button = ffb86c +button-active = 8be9fd +button-disabled = 535353 +tab-active = 6272a4 +notification = bd93f9 +notification-error = e22134 +misc = BFBFBF + +[nord-light] +text = 2e3440 +subtext = 3b4252 +sidebar-text = ECEFF4 +main = ECEFF4 +sidebar = 5E81AC +player = ECEFF4 +card = ebcb8b +shadow = eceff4 +selected-row = 4c566a +button = 81a1c1 +button-active = 81a1c1 +button-disabled = c0c0c0 +tab-active = ebcb8b +notification = a3be8c +notification-error = bf616a +misc = BFBFBF + +[nord-dark] +text = ECEFF4 +subtext = E5E9F0 +sidebar-text = 434c5e +main = 2e3440 +sidebar = 88C0D0 +player = 2e3440 +card = 2e3440 +shadow = 2E3440 +selected-row = D8DEE9 +button = 81A1C1 +button-active = 81A1C1 +button-disabled = 434C5E +tab-active = 434C5E +notification = A3BE8C +notification-error = BF616A +misc = BFBFBF + +[purple] +text = f1eaff +subtext = f1eaff +sidebar-text = e0d0ff +main = 0A0E14 +sidebar = 6F3C89 +player = 0A0E14 +card = 0A0E14 +shadow = 3a2645 +selected-row = EBDFFF +button = c76af6 +button-active = 6F3C89 +button-disabled = 535353 +tab-active = 58306D +notification = ff9e00 +notification-error = f61379 +misc = DEDEDE + +[samourai] +text = ebdbb2 +subtext = ebdbb2 +sidebar-text = 461217 +main = 461217 +sidebar = ebdbb2 +player = 461217 +card = 461217 +shadow = 3a2645 +selected-row = 909090 +button = e7a52d +button-active = e7a52d +button-disabled = 535353 +tab-active = e7a52d +notification = e7a52d +notification-error = e22134 +misc = BFBFBF + +[beach-sunset] +text = FFFFFF +subtext = F0F0F0 +sidebar-text = F0F0F0 +main = 262626 +sidebar = bd3e3e +player = 262626 +card = 262626 +shadow = 000000 +selected-row = d1d6e2 +button = f1a84f +button-active = c98430 +button-disabled = 535353 +tab-active = f1a84f +notification = c98430 +notification-error = e22134 misc = BFBFBF \ No newline at end of file diff --git a/dribbblish.js b/src/js/ConfigMenu.js similarity index 50% rename from dribbblish.js rename to src/js/ConfigMenu.js index caa7bb5..210ab7e 100644 --- a/dribbblish.js +++ b/src/js/ConfigMenu.js @@ -1,6 +1,4 @@ -// Hide popover message -// document.getElementById("popover-container").style.height = 0; -class ConfigMenu { +export default class ConfigMenu { /** * @typedef {Object} DribbblishConfigItem * @property {"checkbox" | "select" | "button" | "slider" | "number" | "text" | "time" | "color"} type @@ -29,17 +27,20 @@ class ConfigMenu { /** * @callback showChildren + * @this {DribbblishConfigItem} * @param {any} value * @returns {Boolean | String[]} */ /** * @callback onAppended + * @this {DribbblishConfigItem} * @returns {void} */ /** * @callback onChange + * @this {DribbblishConfigItem} * @param {any} value * @returns {void} */ @@ -49,7 +50,7 @@ class ConfigMenu { constructor() { this.#config = {}; - this.configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => DribbblishShared.config.open()); + this.configButton = new Spicetify.Menu.Item("Dribbblish Settings", false, () => this.open()); this.configButton.register(); const container = document.createElement("div"); @@ -66,8 +67,8 @@ class ConfigMenu { `; document.body.appendChild(container); - document.querySelector(".dribbblish-config-close").addEventListener("click", () => DribbblishShared.config.close()); - document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => DribbblishShared.config.close()); + document.querySelector(".dribbblish-config-close").addEventListener("click", () => this.close()); + document.querySelector(".dribbblish-config-backdrop").addEventListener("click", () => this.close()); } open() { @@ -138,8 +139,8 @@ class ConfigMenu { .join("\n"); options._onChange = options.onChange; options.onChange = (val) => { - options._onChange(val); - const show = options.showChildren(val); + options._onChange.call(options, val); + const show = options.showChildren.call(options, val); options.children.forEach((child) => this.setHidden(child.key, Array.isArray(show) ? !show.includes(child.key) : !show)); }; options.children = options.children.map((child) => { @@ -147,7 +148,6 @@ class ConfigMenu { }); this.#config[options.key] = options; - this.#config[options.key].value = localStorage.getItem(`dribbblish:config:${options.key}`) ?? JSON.stringify(options.defaultValue); if (options.type == "checkbox") { const input = /* html */ ` @@ -165,17 +165,19 @@ class ConfigMenu { } else if (options.type == "select") { // Validate const val = this.get(options.key); - if (val < 0 || val > options.data.length - 1) this.set(options.key); + if (!Object.keys(options.data).includes(val)) this.reset(options.key); const input = /* html */ ` `; this.addInputHTML({ ...options, input }); document.getElementById(`dribbblish-config-input-${options.key}`).addEventListener("change", (e) => { - this.set(options.key, Number(e.target.value)); + this.set(options.key, e.target.value); options.onChange(this.get(options.key)); }); } else if (options.type == "button") { @@ -289,7 +291,7 @@ class ConfigMenu { options.children.forEach((child) => this.register(child)); - options.onAppended(); + options.onAppended.call(options); if (options.fireInitialChange) options.onChange(this.get(options.key)); } @@ -332,9 +334,12 @@ class ConfigMenu { * @returns {any} */ get(key, defaultValueOverride) { - const val = JSON.parse(this.#config[key].value ?? null); // Turn undefined into null because `JSON.parse()` dosen't like undefined - if (val == null) return defaultValueOverride ?? this.#config[key].defaultValue; - return val; + const val = JSON.parse(this.#config[key]?.storageCache ?? localStorage.getItem(`dribbblish:config:${key}`) ?? null); // Turn undefined into null because `JSON.parse()` dosen't like undefined + if (val == null || val?.type != this.#config[key]?.type) { + localStorage.removeItem(`dribbblish:config:${key}`); + return defaultValueOverride ?? this.#config[key].defaultValue; + } + return val.value; } /** @@ -343,10 +348,20 @@ class ConfigMenu { * @param {any} val */ set(key, val) { - this.#config[key].value = JSON.stringify(val); + val = { type: this.#config[key].type, value: val ?? this.#config[key].defaultValue }; + this.#config[key].storageCache = JSON.stringify(val); localStorage.setItem(`dribbblish:config:${key}`, JSON.stringify(val)); } + /** + * + * @param {String} key + */ + reset(key) { + delete this.#config[key].storageCache; + localStorage.removeItem(`dribbblish:config:${key}`); + } + /** * * @param {String} key @@ -361,334 +376,3 @@ class ConfigMenu { return this.#config[key]; } } - -class _DribbblishShared { - constructor() { - this.config = new ConfigMenu(); - } -} -const DribbblishShared = new _DribbblishShared(); - -DribbblishShared.config.register({ - type: "checkbox", - key: "rightBigCover", - name: "Right expanded cover", - description: "Have the expanded cover Image on the right instead of on the left", - defaultValue: true, - onChange: (val) => { - if (val) { - document.documentElement.classList.add("right-expanded-cover"); - } else { - document.documentElement.classList.remove("right-expanded-cover"); - } - } -}); - -DribbblishShared.config.register({ - type: "checkbox", - key: "roundSidebarIcons", - name: "Round Sidebar Icons", - description: "If the Sidebar Icons should be round instead of square", - defaultValue: false, - onChange: (val) => document.documentElement.style.setProperty("--sidebar-icons-border-radius", val ? "50%" : "var(--image-radius)") -}); - -DribbblishShared.config.register({ - area: "Animations & Transitions", - type: "checkbox", - key: "sidebarHoverAnimation", - name: "Sidebar Hover Animation", - description: "If the Sidebar Icons should have an animated background on hover", - defaultValue: true, - onChange: (val) => document.documentElement.style.setProperty("--sidebar-icons-hover-animation", val ? "1" : "0") -}); - -waitForElement(["#main"], () => { - DribbblishShared.config.register({ - type: "select", - data: ["None", "None (With Top Padding)", "Solid", "Transparent"], - key: "winTopBar", - name: "Windows Top Bar", - description: "Have different top Bars (or none at all)", - defaultValue: 0, - onChange: (val) => { - switch (val) { - case 0: - document.getElementById("main").setAttribute("top-bar", "none"); - break; - case 1: - document.getElementById("main").setAttribute("top-bar", "none-padding"); - break; - case 2: - document.getElementById("main").setAttribute("top-bar", "solid"); - break; - case 3: - document.getElementById("main").setAttribute("top-bar", "transparent"); - break; - } - } - }); - - DribbblishShared.config.register({ - type: "select", - data: ["Dribbblish", "Spotify"], - key: "playerControlsStyle", - name: "Player Controls Style", - description: "Style of the Player Controls. Selecting Spotify basically changes Play / Pause back to the center", - defaultValue: 0, - onChange: (val) => { - switch (val) { - case 0: - document.getElementById("main").setAttribute("player-controls", "dribbblish"); - break; - case 1: - document.getElementById("main").setAttribute("player-controls", "spotify"); - break; - } - } - }); - - DribbblishShared.config.register({ - area: "Ads", - type: "checkbox", - key: "hideAds", - name: "Hide Ads", - description: `Hide ads / premium features (see: SpotifyNoPremium)`, - defaultValue: false, - onAppended: () => { - document.styleSheets[0].insertRule(/* css */ ` - /* Remove upgrade button*/ - #main[hide-ads] .main-topBar-UpgradeButton { - display: none - } - `); - document.styleSheets[0].insertRule(/* css */ ` - /* Remove upgrade to premium button in user menu */ - #main[hide-ads] .main-contextMenu-menuItemButton[href="https://www.spotify.com/premium/"] { - display: none - } - `); - document.styleSheets[0].insertRule(/* css */ ` - /* Remove ad placeholder in main screen */ - #main[hide-ads] .main-leaderboardComponent-container { - display: none - } - `); - }, - onChange: (val) => document.getElementById("main").toggleAttribute("hide-ads", val) - }); -}); - -function waitForElement(els, func, timeout = 100) { - const queries = els.map((el) => document.querySelector(el)); - if (queries.every((a) => a)) { - func(queries); - } else if (timeout > 0) { - setTimeout(waitForElement, 300, els, func, --timeout); - } -} - -waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`], ([root, firstItem]) => { - const listElem = firstItem.parentElement; - root.classList.add("dribs-playlist-list"); - - /** Replace Playlist name with their pictures */ - function loadPlaylistImage() { - for (const item of listElem.children) { - let link = item.querySelector("a"); - if (!link) continue; - - let [_, app, uid] = link.pathname.split("/"); - let uri; - if (app === "playlist") { - uri = Spicetify.URI.playlistV2URI(uid); - } else if (app === "folder") { - const base64 = localStorage.getItem("dribbblish:folder-image:" + uid); - let img = link.querySelector("img"); - if (!img) { - img = document.createElement("img"); - img.classList.add("playlist-picture"); - link.prepend(img); - } - img.src = base64 || "/images/tracklist-row-song-fallback.svg"; - continue; - } - - Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, { policy: { picture: true } }).then((res) => { - const meta = res.metadata; - let img = link.querySelector("img"); - if (!img) { - img = document.createElement("img"); - img.classList.add("playlist-picture"); - link.prepend(img); - } - img.src = meta.picture || "/images/tracklist-row-song-fallback.svg"; - }); - } - } - - DribbblishShared.loadPlaylistImage = loadPlaylistImage; - loadPlaylistImage(); - - new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true }); -}); - -waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => { - function checkSidebarPlaylistScroll() { - const topDist = rootlist.getBoundingClientRect().top - document.querySelector("#spicetify-show-list:not(:empty), .main-rootlist-wrapper > :nth-child(2) > :first-child").getBoundingClientRect().top; - const bottomDist = document.querySelector(".main-rootlist-wrapper > :nth-child(2) > :last-child").getBoundingClientRect().bottom - rootlist.getBoundingClientRect().bottom; - - rootlist.classList.remove("no-top-shadow", "no-bottom-shadow"); - if (topDist < 10) rootlist.classList.add("no-top-shadow"); - if (bottomDist < 10) rootlist.classList.add("no-bottom-shadow"); - } - checkSidebarPlaylistScroll(); - - // Use Interval because scrolling takes a while and getBoundingClientRect() gets position at the moment of calling, so the interval keeps calling for 1s - let c = 0; - let interval; - rootlist.addEventListener("wheel", () => { - checkSidebarPlaylistScroll(); - c = 0; - if (interval == null) - interval = setInterval(() => { - if (c > 20) { - clearInterval(interval); - interval = null; - return; - } - - checkSidebarPlaylistScroll(); - c++; - }, 50); - }); -}); - -waitForElement([".Root__main-view"], ([mainView]) => { - const shadow = document.createElement("div"); - shadow.id = "dribbblish-back-shadow"; - mainView.prepend(shadow); -}); - -waitForElement([".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutResizer__resize-bar input"], ([resizer]) => { - const observer = new MutationObserver(updateVariable); - observer.observe(resizer, { attributes: true, attributeFilter: ["value"] }); - function updateVariable() { - let value = resizer.value; - if (value < 121) { - value = 72; - document.documentElement.classList.add("sidebar-hide-text"); - } else { - document.documentElement.classList.remove("sidebar-hide-text"); - } - document.documentElement.style.setProperty("--sidebar-width", value + "px"); - } - updateVariable(); -}); - -waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) => { - const observer = new ResizeObserver(updateVariable); - observer.observe(resizeHost); - function updateVariable([event]) { - document.documentElement.style.setProperty("--main-view-width", event.contentRect.width + "px"); - document.documentElement.style.setProperty("--main-view-height", event.contentRect.height + "px"); - if (event.contentRect.width < 700) { - document.documentElement.classList.add("minimal-player"); - } else { - document.documentElement.classList.remove("minimal-player"); - } - if (event.contentRect.width < 550) { - document.documentElement.classList.add("extra-minimal-player"); - } else { - document.documentElement.classList.remove("extra-minimal-player"); - } - } -}); - -(function Dribbblish() { - const progBar = document.querySelector(".playback-bar"); - const root = document.querySelector(".Root"); - - if (!Spicetify.Player.origin || !progBar || !root) { - setTimeout(Dribbblish, 300); - return; - } - - const progKnob = progBar.querySelector(".progress-bar__slider"); - - const tooltip = document.createElement("div"); - tooltip.className = "prog-tooltip"; - progKnob.append(tooltip); - - function updateProgTime(timeOverride) { - const newText = Spicetify.Player.formatTime(timeOverride || Spicetify.Player.getProgress()) + " / " + Spicetify.Player.formatTime(Spicetify.Player.getDuration()); - // To reduce DOM Updates when the Song is Paused - if (tooltip.innerText != newText) tooltip.innerText = newText; - } - const knobPosObserver = new MutationObserver((muts) => { - const progressPercentage = Number(getComputedStyle(document.querySelector(".progress-bar")).getPropertyValue("--progress-bar-transform").replace("%", "")) / 100; - updateProgTime(Spicetify.Player.getDuration() * progressPercentage); - }); - knobPosObserver.observe(document.querySelector(".progress-bar"), { - attributes: true, - attributeFilter: ["style"] - }); - Spicetify.Player.addEventListener("songchange", () => updateProgTime()); - updateProgTime(); - - Spicetify.CosmosAsync.sub("sp://connect/v1", (state) => { - const isExternal = state.devices.some((a) => a.is_active); - if (isExternal) { - root.classList.add("is-connectBarVisible"); - } else { - root.classList.remove("is-connectBarVisible"); - } - }); - - const filePickerForm = document.createElement("form"); - filePickerForm.setAttribute("aria-hidden", true); - filePickerForm.innerHTML = ''; - document.body.appendChild(filePickerForm); - /** @type {HTMLInputElement} */ - const filePickerInput = filePickerForm.childNodes[0]; - filePickerInput.accept = ["image/jpeg", "image/apng", "image/avif", "image/gif", "image/png", "image/svg+xml", "image/webp"].join(","); - - filePickerInput.onchange = () => { - if (!filePickerInput.files.length) return; - - const file = filePickerInput.files[0]; - const reader = new FileReader(); - reader.onload = (event) => { - const result = event.target.result; - const id = Spicetify.URI.from(filePickerInput.uri).id; - try { - localStorage.setItem("dribbblish:folder-image:" + id, result); - } catch { - Spicetify.showNotification("File too large"); - } - DribbblishShared.loadPlaylistImage?.call(); - }; - reader.readAsDataURL(file); - }; - - new Spicetify.ContextMenu.Item( - "Remove folder image", - ([uri]) => { - const id = Spicetify.URI.from(uri).id; - localStorage.removeItem("dribbblish:folder-image:" + id); - DribbblishShared.loadPlaylistImage?.call(); - }, - ([uri]) => Spicetify.URI.isFolder(uri), - "x" - ).register(); - new Spicetify.ContextMenu.Item( - "Choose folder image", - ([uri]) => { - filePickerInput.uri = uri; - filePickerForm.reset(); - filePickerInput.click(); - }, - ([uri]) => Spicetify.URI.isFolder(uri), - "edit" - ).register(); -})(); diff --git a/src/js/main.js b/src/js/main.js new file mode 100644 index 0000000..760089b --- /dev/null +++ b/src/js/main.js @@ -0,0 +1,672 @@ +import * as Vibrant from "node-vibrant"; +import chroma from "chroma-js"; +import $ from "jquery"; + +import ConfigMenu from "./ConfigMenu"; + +class _DribbblishShared { + constructor() { + this.config = new ConfigMenu(); + } +} +const DribbblishShared = new _DribbblishShared(); + +DribbblishShared.config.register({ + type: "checkbox", + key: "rightBigCover", + name: "Right expanded cover", + description: "Have the expanded cover Image on the right instead of on the left", + defaultValue: true, + onChange: (val) => $("html").toggleClass("right-expanded-cover", val) +}); + +DribbblishShared.config.register({ + type: "checkbox", + key: "roundSidebarIcons", + name: "Round Sidebar Icons", + description: "If the Sidebar Icons should be round instead of square", + defaultValue: false, + onChange: (val) => $("html").css("--sidebar-icons-border-radius", val ? "50%" : "var(--image-radius)") +}); + +DribbblishShared.config.register({ + area: "Animations & Transitions", + type: "checkbox", + key: "sidebarHoverAnimation", + name: "Sidebar Hover Animation", + description: "If the Sidebar Icons should have an animated background on hover", + defaultValue: true, + onChange: (val) => $("html").css("--sidebar-icons-hover-animation", val ? "1" : "0") +}); + +waitForElement(["#main"], () => { + DribbblishShared.config.register({ + type: "select", + data: { none: "None", "none-padding": "None (With Top Padding)", solid: "Solid", transparent: "Transparent" }, + key: "winTopBar", + name: "Windows Top Bar", + description: "Have different top Bars (or none at all)", + defaultValue: "none", + onChange: (val) => $("#main").attr("top-bar", val) + }); + + DribbblishShared.config.register({ + type: "select", + data: { dribbblish: "Dribbblish", spotify: "Spotify" }, + key: "playerControlsStyle", + name: "Player Controls Style", + description: "Style of the Player Controls. Selecting Spotify basically changes Play / Pause back to the center", + defaultValue: "dribbblish", + onChange: (val) => $("#main").attr("player-controls", val) + }); + + DribbblishShared.config.register({ + area: "Ads", + type: "checkbox", + key: "hideAds", + name: "Hide Ads", + description: `Hide ads / premium features (see: SpotifyNoPremium)`, + defaultValue: false, + onChange: (val) => $("#main").attr("hide-ads", val) + }); +}); + +function waitForElement(els, func, timeout = 100) { + const queries = els.map((el) => document.querySelector(el)); + if (queries.every((a) => a)) { + func(queries); + } else if (timeout > 0) { + setTimeout(waitForElement, 300, els, func, --timeout); + } +} + +waitForElement([`.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"]`, `.main-rootlist-rootlistPlaylistsScrollNode ul[tabindex="0"] li`], ([root, firstItem]) => { + const listElem = firstItem.parentElement; + root.classList.add("dribs-playlist-list"); + + /** Replace Playlist name with their pictures */ + function loadPlaylistImage() { + for (const item of listElem.children) { + let link = item.querySelector("a"); + if (!link) continue; + + let [_, app, uid] = link.pathname.split("/"); + let uri; + if (app === "playlist") { + uri = Spicetify.URI.playlistV2URI(uid); + } else if (app === "folder") { + const base64 = localStorage.getItem("dribbblish:folder-image:" + uid); + let img = link.querySelector("img"); + if (!img) { + img = document.createElement("img"); + img.classList.add("playlist-picture"); + link.prepend(img); + } + img.src = base64 || "/images/tracklist-row-song-fallback.svg"; + continue; + } + + Spicetify.CosmosAsync.get(`sp://core-playlist/v1/playlist/${uri.toURI()}/metadata`, { policy: { picture: true } }).then((res) => { + const meta = res.metadata; + let img = link.querySelector("img"); + if (!img) { + img = document.createElement("img"); + img.classList.add("playlist-picture"); + link.prepend(img); + } + img.src = meta.picture || "/images/tracklist-row-song-fallback.svg"; + }); + } + } + + DribbblishShared.loadPlaylistImage = loadPlaylistImage; + loadPlaylistImage(); + + new MutationObserver(loadPlaylistImage).observe(listElem, { childList: true }); +}); + +waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => { + function checkSidebarPlaylistScroll() { + const topDist = rootlist.getBoundingClientRect().top - document.querySelector("#spicetify-show-list:not(:empty), .main-rootlist-wrapper > :nth-child(2) > :first-child").getBoundingClientRect().top; + const bottomDist = document.querySelector(".main-rootlist-wrapper > :nth-child(2) > :last-child").getBoundingClientRect().bottom - rootlist.getBoundingClientRect().bottom; + + rootlist.classList.remove("no-top-shadow", "no-bottom-shadow"); + if (topDist < 10) rootlist.classList.add("no-top-shadow"); + if (bottomDist < 10) rootlist.classList.add("no-bottom-shadow"); + } + checkSidebarPlaylistScroll(); + + // Use Interval because scrolling takes a while and getBoundingClientRect() gets position at the moment of calling, so the interval keeps calling for 1s + let c = 0; + let interval; + rootlist.addEventListener("wheel", () => { + checkSidebarPlaylistScroll(); + c = 0; + if (interval == null) + interval = setInterval(() => { + if (c > 20) { + clearInterval(interval); + interval = null; + return; + } + + checkSidebarPlaylistScroll(); + c++; + }, 50); + }); +}); + +waitForElement([".Root__main-view"], ([mainView]) => { + const shadow = document.createElement("div"); + shadow.id = "dribbblish-back-shadow"; + mainView.prepend(shadow); +}); + +waitForElement([".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutResizer__resize-bar input"], ([resizer]) => { + const observer = new MutationObserver(updateVariable); + observer.observe(resizer, { attributes: true, attributeFilter: ["value"] }); + function updateVariable() { + let value = resizer.value; + if (value < 121) value = 72; + $("html").toggleClass("sidebar-hide-text", value < 121); + $("html").css("--sidebar-width", `${value}px`); + } + updateVariable(); +}); + +waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) => { + const observer = new ResizeObserver(updateVariable); + observer.observe(resizeHost); + function updateVariable([event]) { + $("html").css("--main-view-width", event.contentRect.width + "px"); + $("html").css("--main-view-height", event.contentRect.height + "px"); + $("html").toggleClass("minimal-player", event.contentRect.width < 700); + $("html").toggleClass("extra-minimal-player", event.contentRect.width < 550); + } +}); + +(function Dribbblish() { + const progBar = document.querySelector(".playback-bar"); + const root = document.querySelector(".Root"); + + if (!Spicetify.Player.origin || !progBar || !root) { + setTimeout(Dribbblish, 300); + return; + } + + const progKnob = progBar.querySelector(".progress-bar__slider"); + + const tooltip = document.createElement("div"); + tooltip.className = "prog-tooltip"; + progKnob.append(tooltip); + + function updateProgTime(timeOverride) { + const newText = Spicetify.Player.formatTime(timeOverride || Spicetify.Player.getProgress()) + " / " + Spicetify.Player.formatTime(Spicetify.Player.getDuration()); + // To reduce DOM Updates when the Song is Paused + if (tooltip.innerText != newText) tooltip.innerText = newText; + } + const knobPosObserver = new MutationObserver((muts) => { + const progressPercentage = Number($(".progress-bar").css("--progress-bar-transform").replace("%", "")) / 100; + updateProgTime(Spicetify.Player.getDuration() * progressPercentage); + }); + knobPosObserver.observe(document.querySelector(".progress-bar"), { + attributes: true, + attributeFilter: ["style"] + }); + Spicetify.Player.addEventListener("songchange", () => updateProgTime()); + updateProgTime(); + + Spicetify.CosmosAsync.sub("sp://connect/v1", (state) => { + const isExternal = state.devices.some((a) => a.is_active); + if (isExternal) { + root.classList.add("is-connectBarVisible"); + } else { + root.classList.remove("is-connectBarVisible"); + } + }); + + const filePickerForm = document.createElement("form"); + filePickerForm.setAttribute("aria-hidden", true); + filePickerForm.innerHTML = ''; + document.body.appendChild(filePickerForm); + /** @type {HTMLInputElement} */ + const filePickerInput = filePickerForm.childNodes[0]; + filePickerInput.accept = ["image/jpeg", "image/apng", "image/avif", "image/gif", "image/png", "image/svg+xml", "image/webp"].join(","); + + filePickerInput.onchange = () => { + if (!filePickerInput.files.length) return; + + const file = filePickerInput.files[0]; + const reader = new FileReader(); + reader.onload = (event) => { + const result = event.target.result; + const id = Spicetify.URI.from(filePickerInput.uri).id; + try { + localStorage.setItem("dribbblish:folder-image:" + id, result); + } catch { + Spicetify.showNotification("File too large"); + } + DribbblishShared.loadPlaylistImage?.call(); + }; + reader.readAsDataURL(file); + }; + + new Spicetify.ContextMenu.Item( + "Remove folder image", + ([uri]) => { + const id = Spicetify.URI.from(uri).id; + localStorage.removeItem("dribbblish:folder-image:" + id); + DribbblishShared.loadPlaylistImage?.call(); + }, + ([uri]) => Spicetify.URI.isFolder(uri), + "x" + ).register(); + new Spicetify.ContextMenu.Item( + "Choose folder image", + ([uri]) => { + filePickerInput.uri = uri; + filePickerForm.reset(); + filePickerInput.click(); + }, + ([uri]) => Spicetify.URI.isFolder(uri), + "edit" + ).register(); +})(); + +/* Config settings */ + +DribbblishShared.config.register({ + area: "Animations & Transitions", + type: "slider", + key: "fadeDuration", + name: "Color Fade Duration", + description: "Select the duration of the color fading transition", + defaultValue: 0.5, + data: { + min: 0, + max: 10, + step: 0.1, + suffix: "s" + }, + onChange: (val) => $("html").css("--song-transition-speed", `${val}s`) +}); + +// waitForElement because Spicetify is not initialized at startup +waitForElement(["#main"], () => { + DribbblishShared.config.register({ + area: { name: "About", order: 999 }, + type: "button", + key: "aboutDribbblish", + name: "Info", + description: ` + OS: ${capitalizeFirstLetter(Spicetify.Platform.PlatformData.os_name)} v${Spicetify.Platform.PlatformData.os_version} + Spotify: v${Spicetify.Platform.PlatformData.event_sender_context_information?.client_version_string ?? Spicetify.Platform.PlatformData.client_version_triple} + Dribbblish: v${process.env.DRIBBBLISH_VERSION} + `, + data: "Copy", + onChange: (val) => { + copyToClipboard(DribbblishShared.config.getOptions("aboutDribbblish").description); + Spicetify.showNotification("Copied Versions"); + } + }); +}); + +function capitalizeFirstLetter(string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} + +function copyToClipboard(text) { + var input = document.createElement("textarea"); + input.style.display = "fixed"; + input.innerHTML = text; + document.body.appendChild(input); + input.select(); + var result = document.execCommand("copy"); + document.body.removeChild(input); + return result; +} + +/* js */ +function getAlbumInfo(uri) { + return Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`); +} + +function isLight(hex) { + var [r, g, b] = chroma(hex).rgb().map(Number); + const brightness = (r * 299 + g * 587 + b * 114) / 1000; + return brightness > 128; +} + +// From: https://stackoverflow.com/a/13763063/12126879 +function getImageLightness(img) { + var colorSum = 0; + var canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + + var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + var data = imageData.data; + var r, g, b, avg; + + for (var x = 0, len = data.length; x < len; x += 4) { + r = data[x]; + g = data[x + 1]; + b = data[x + 2]; + + avg = Math.floor((r + g + b) / 3); + colorSum += avg; + } + + var brightness = Math.floor(colorSum / (img.width * img.height)); + return brightness; +} + +// parse to hex beacuse "--spice-sidebar" is `rgb()` +let textColor = chroma($("html").css("--spice-text")).hex(); +let textColorBg = chroma($("html").css("--spice-main")).hex(); +let sidebarColor = chroma($("html").css("--spice-sidebar")).hex(); + +function setRootColor(name, colHex) { + $("html").css(`--spice-${name}`, colHex); + $("html").css(`--spice-rgb-${name}`, chroma(colHex).rgb().join(",")); +} + +function toggleDark(setDark) { + if (setDark === undefined) setDark = isLight(textColorBg); + + $("html").css("--is_light", setDark ? 0 : 1); + textColorBg = setDark ? "#0A0A0A" : "#FAFAFA"; + + setRootColor("main", textColorBg); + setRootColor("player", textColorBg); + setRootColor("card", setDark ? "#040404" : "#ECECEC"); + setRootColor("subtext", setDark ? "#EAEAEA" : "#3D3D3D"); + setRootColor("notification", setDark ? "#303030" : "#DDDDDD"); + + updateColors(textColor, sidebarColor, false); +} + +function checkDarkLightMode(colors) { + const theme = DribbblishShared.config.get("theme"); + if (theme == "time") { + const start = 60 * parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOnTime").split(":")[1]); + const end = 60 * parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[0]) + parseInt(DribbblishShared.config.get("darkModeOffTime").split(":")[1]); + + const now = new Date(); + const time = 60 * now.getHours() + now.getMinutes(); + + let dark; + if (end < start) dark = start <= time || time < end; + else dark = start <= time && time < end; + toggleDark(dark); + } else if (theme == "color") { + if (colors && colors.length > 0) toggleDark(isLight(colors[0])); + } +} +// Run every Minute to check time and set dark / light mode +setInterval(checkDarkLightMode, 60000); + +DribbblishShared.config.register({ + area: "Theme", + type: "checkbox", + key: "dynamicColors", + name: "Dynamic", + description: "If the Theme's Color should be extracted from Albumart", + defaultValue: true, + onChange: (val) => updateColors(), + showChildren: (val) => !val, + children: [ + { + type: "color", + key: "colorOverride", + name: "Color", + description: "The Color of the Theme", + defaultValue: "#1ed760", + fireInitialChange: false, + onChange: (val) => updateColors() + } + ] +}); + +DribbblishShared.config.register({ + area: "Theme", + type: "select", + data: { dark: "Dark", light: "Light", time: "Based on Time", color: "Based on Color" }, + key: "theme", + name: "Theme", + description: "Select Dark / Bright mode", + defaultValue: "dark", + showChildren: (val) => { + if (val == 2) return ["darkModeOnTime", "darkModeOffTime"]; + //if (val == 3) return [""]; + return false; + }, + onChange: (val) => { + switch (val) { + case "dark": + toggleDark(true); + break; + case "light": + toggleDark(false); + break; + case "time": + checkDarkLightMode(); + break; + case "color": + checkDarkLightMode(); + break; + } + }, + children: [ + { + type: "time", + key: "darkModeOnTime", + name: "Dark Mode On Time", + description: "Beginning of Dark mode time", + defaultValue: "20:00", + fireInitialChange: false, + onChange: checkDarkLightMode + }, + { + type: "time", + key: "darkModeOffTime", + name: "Dark Mode Off Time", + description: "End of Dark mode time", + defaultValue: "06:00", + fireInitialChange: false, + onChange: checkDarkLightMode + } + ] +}); + +var currentColor; +var currentSideColor; + +function updateColors(textColHex, sideColHex, checkDarkMode = true) { + if (textColHex && sideColHex) { + currentColor = textColHex; + currentSideColor = sideColHex; + } else { + if (!(currentColor && currentSideColor)) return; // If `updateColors()` is called early these vars are undefined and would break + textColHex = currentColor; + sideColHex = currentSideColor; + } + + if (!DribbblishShared.config.get("dynamicColors")) { + const col = DribbblishShared.config.get("colorOverride"); + textColHex = col; + sideColHex = col; + } + + let isLightBg = isLight(textColorBg); + if (isLightBg) textColHex = chroma(textColHex).darken(0.15).hex(); // vibrant color is always too bright for white bg mode + + let darkColHex = chroma(textColHex) + .brighten(isLightBg ? 0.12 : -0.2) + .hex(); + let darkerColHex = chroma(textColHex) + .brighten(isLightBg ? 0.3 : -0.4) + .hex(); + let buttonBgColHex = chroma(textColHex) + .set("hsl.l", isLightBg ? 0.9 : 0.14) + .hex(); + setRootColor("text", textColHex); + setRootColor("button", darkerColHex); + setRootColor("button-active", darkColHex); + setRootColor("selected-row", darkerColHex); + setRootColor("tab-active", buttonBgColHex); + setRootColor("button-disabled", buttonBgColHex); + setRootColor("sidebar", sideColHex); + + if (checkDarkMode) checkDarkLightMode([textColHex, sideColHex]); +} + +let nearArtistSpan; +let nearArtistSpanText = ""; +let coverListenerInstalled = false; +async function songchange() { + try { + // warning popup + if (Spicetify.Platform.PlatformData.client_version_triple < "1.1.68") Spicetify.showNotification(`Your version of Spotify ${Spicetify.Platform.PlatformData.client_version_triple}) is un-supported`); + } catch (err) { + console.error(err); + } + + let album_uri = Spicetify.Player.data.track.metadata.album_uri; + let bgImage = Spicetify.Player.data.track.metadata.image_url; + if (bgImage === undefined) { + bgImage = "/images/tracklist-row-song-fallback.svg"; + textColor = "#509bf5"; + updateColors(textColor, textColor); + coverListenerInstalled = false; + } + if (!coverListenerInstalled) hookCoverChange(true); + + if (album_uri !== undefined && !album_uri.includes("spotify:show")) { + const albumInfo = await getAlbumInfo(album_uri.replace("spotify:album:", "")); + + let album_date = new Date(albumInfo.year, (albumInfo.month || 1) - 1, albumInfo.day || 0); + let recent_date = new Date(); + recent_date.setMonth(recent_date.getMonth() - 6); + album_date = album_date.toLocaleString("default", album_date > recent_date ? { year: "numeric", month: "short" } : { year: "numeric" }); + let album_link = '' + Spicetify.Player.data.track.metadata.album_title + ""; + + nearArtistSpanText = album_link + " • " + album_date; + } else if (Spicetify.Player.data.track.uri.includes("spotify:episode")) { + // podcast + bgImage = bgImage.replace("spotify:image:", "https://i.scdn.co/image/"); + nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title; + } else if (Spicetify.Player.data.track.metadata.is_local == "true") { + // local file + nearArtistSpanText = Spicetify.Player.data.track.metadata.album_title; + } else if (Spicetify.Player.data.track.provider == "ad") { + // ad + nearArtistSpanText = "advertisement"; + coverListenerInstalled = false; + return; + } else { + // When clicking a song from the homepage, songChange is fired with half empty metadata + // todo: retry only once? + setTimeout(songchange, 200); + } + + if (document.querySelector("#main-trackInfo-year") === null) { + waitForElement([".main-trackInfo-container"], (queries) => { + nearArtistSpan = document.createElement("div"); + nearArtistSpan.id = "main-trackInfo-year"; + nearArtistSpan.classList.add("main-trackInfo-artists", "ellipsis-one-line", "main-type-finale"); + nearArtistSpan.innerHTML = nearArtistSpanText; + queries[0].append(nearArtistSpan); + }); + } else { + nearArtistSpan.innerHTML = nearArtistSpanText; + } + + $("html").css("--image-url", `url("${bgImage}")`); +} + +Spicetify.Player.addEventListener("songchange", songchange); + +async function pickCoverColor(img) { + if (!img.currentSrc.startsWith("spotify:")) return; + + $("html").css("--image-brightness", getImageLightness(img) / 255); + + var swatches = await new Promise((resolve, reject) => new Vibrant(img, 5).getPalette().then(resolve).catch(reject)); + var lightCols = ["Vibrant", "DarkVibrant", "Muted", "LightVibrant"]; + var darkCols = ["Vibrant", "LightVibrant", "Muted", "DarkVibrant"]; + + var mainCols = isLight(textColorBg) ? lightCols : darkCols; + textColor = "#509bf5"; + for (var col in mainCols) + if (swatches[mainCols[col]]) { + textColor = swatches[mainCols[col]].getHex(); + break; + } + + sidebarColor = "#509bf5"; + for (var col in lightCols) + if (swatches[lightCols[col]]) { + sidebarColor = swatches[lightCols[col]].getHex(); + break; + } + updateColors(textColor, sidebarColor); +} + +waitForElement([".main-nowPlayingBar-left"], (queries) => { + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + if (mutation.removedNodes.length > 0) coverListenerInstalled = false; + }); + }); + observer.observe(queries[0], { childList: true }); +}); + +function hookCoverChange(pick) { + waitForElement([".cover-art-image"], (queries) => { + coverListenerInstalled = true; + var elem = queries.slice(-1)[0]; + if (pick && elem.complete && elem.naturalHeight !== 0) pickCoverColor(elem); + elem.addEventListener("load", function () { + try { + pickCoverColor(elem); + } catch (error) { + console.error(error); + setTimeout(pickCoverColor, 300, elem); + } + }); + }); +} + +hookCoverChange(false); + +// Check latest release +waitForElement([".main-userWidget-box"], ([userWidget]) => { + fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest") + .then((response) => { + return response.json(); + }) + .then((data) => { + const upd = document.createElement("div"); + upd.classList.add("ellipsis-one-line", "main-type-finale"); + upd.setAttribute("title", `Changes: ${data.name}`); + upd.style.setProperty("color", "var(--spice-button-active)"); + if (process.env.DRIBBBLISH_VERSION == "Dev") { + upd.innerText = "Dev version!"; + } else if (data.tag_name > process.env.DRIBBBLISH_VERSION) { + upd.innerText = `Theme UPD v${data.tag_name} avail.`; + new Spicetify.Menu.Item("Update Dribbblish", false, () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases/latest", "_blank")).register(); + } + userWidget.append(upd); + userWidget.classList.add("update-avail"); + }) + .catch((err) => { + // Do something for an error here + console.error(err); + }); +}); + +$("html").css("--warning_message", " "); diff --git a/src/styles/ConfigMenu.scss b/src/styles/ConfigMenu.scss new file mode 100644 index 0000000..c498a23 --- /dev/null +++ b/src/styles/ConfigMenu.scss @@ -0,0 +1,138 @@ +#dribbblish-config { + display: none; + z-index: 99999; + position: absolute; + inset: 0px; + align-items: center; + justify-content: center; + color: var(--spice-text); + + &[active] { + display: flex; + } + + .dribbblish-config-container { + z-index: 1; + position: relative; + width: clamp(500px, 50%, 650px); + background-color: rgba(var(--spice-rgb-main), 0.9); + backdrop-filter: blur(3px); + padding: 20px 15px; + border-radius: var(--main-corner-radius); + box-shadow: 0 0 10px 3px #0000003b; + display: flex; + gap: 5px; + flex-direction: column; + align-items: center; + justify-content: center; + + .dribbblish-config-close { + position: absolute; + top: 15px; + right: 15px; + } + + .dribbblish-config-areas { + display: flex; + width: 100%; + flex-direction: column; + gap: 16px; + max-height: 60vh; + overflow-y: auto; + padding: 0px 50px; + + .dribbblish-config-area { + display: flex; + flex-direction: column; + gap: 16px; + + &[collapsed] { + overflow: hidden; + min-height: 38px; //for some reason height alone isn't enough + height: 38px; + + > h2 svg { + transform: rotate(270deg); + } + } + + &:empty { + display: none; + } + + .dribbblish-config-area-header { + position: relative; + text-align: center; + height: 38px; + + svg { + position: absolute; + margin-left: 10px; + bottom: -2px; + color: var(--spice-text); + padding: 0px; + height: 100%; + stroke-width: 2px; + transform: rotate(90deg); + } + } + + .dribbblish-config-area-items { + display: flex; + flex-direction: column; + gap: 16px; + + .dribbblish-config-item { + position: relative; + width: 100%; + height: min-content; + display: grid; + grid-template-columns: 1fr auto; + grid-template-rows: auto auto; + gap: 5px 10px; + grid-template-areas: "header input" "description input"; + + &[parent] { + padding-left: 16px; + } + + &[hidden="true"] { + display: none; + } + + > { + .x-settings-title { + grid-area: header; + margin: 0px; + height: min-content; + position: relative; + bottom: 0px; + + &.no-desc { + bottom: -10px; + } + } + + .main-type-mesto { + grid-area: description; + height: min-content; + color: var(--spice-subtext); + } + + .x-settings-secondColumn { + grid-area: input; + } + } + } + } + } + } + } + + .dribbblish-config-backdrop { + position: absolute; + content: ""; + inset: 0px; + backdrop-filter: blur(3px) brightness(60%); + } +} diff --git a/src/styles/Inputs.scss b/src/styles/Inputs.scss new file mode 100644 index 0000000..3f7b958 --- /dev/null +++ b/src/styles/Inputs.scss @@ -0,0 +1,114 @@ +button.main-button-primary { + background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important; + + &:hover, + &:active { + background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important; + } + + span { + color: var(--spice-subtext) !important; + } +} + +input, +textarea { + background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important; + border-radius: 4px !important; + padding: 6px 10px 6px 48px; + color: var(--spice-text) !important; + + &:hover, + &:active { + background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important; + } + + &[type="range"] { + -webkit-appearance: none; + background: transparent; + padding: 0px; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + width: 16px; + height: 16px; + margin-top: -4px; + border-radius: 50%; + background-color: var(--spice-text); + + &:hover, + &:active { + filter: brightness(80%); + } + } + + &::after { + z-index: 9999; + content: attr(tooltip); + position: absolute; + min-width: 50px; + top: -10px; + left: 50%; + transform: translateX(calc(-50%)); + padding: 0 5px; + border-radius: 4px; + text-align: center; + color: var(--spice-sidebar-text); + background-color: var(--spice-button); + transition: opacity 0.25s ease; + opacity: 0; + } + + &:hover::after, + &:active::after { + opacity: 1; + } + + &:focus { + outline: none; + } + + &::-webkit-slider-runnable-track { + width: 100%; + height: 8px; + background-color: rgba(var(--spice-rgb-text), 0.2); + border-radius: 50vw; + } + } + + &[type="number"], + &[type="text"], + &[type="time"] { + height: 32px; + border: none; + border-radius: 4px !important; + padding: 0px 10px; + color: var(--spice-subtext) !important; + } + + &[type="time"] { + &::-webkit-calendar-picker-indicator { + filter: invert(calc(1 - var(--is_light))); + } + } + + &[type="color"] { + position: relative; + padding: 0px; + border: none; + + &::before { + z-index: -1; + content: ""; + position: absolute; + inset: -5px; + border-radius: 4px; + background-color: rgba(var(--spice-rgb-selected-row), 0.4); + } + + &:hover::before, + &:active::before { + background-color: rgba(var(--spice-rgb-selected-row), 0.6); + } + } +} diff --git a/src/styles/NoAds.scss b/src/styles/NoAds.scss new file mode 100644 index 0000000..364dbb0 --- /dev/null +++ b/src/styles/NoAds.scss @@ -0,0 +1,17 @@ +#main[hide-ads="true"] { + /* Remove upgrade button*/ + .main-topBar-UpgradeButton { + display: none; + } + + /* Remove upgrade to premium button in user menu */ + .main-contextMenu-menuItemButton[href="https://www.spotify.com/premium/"] + { + display: none; + } + + /* Remove ad placeholder in main screen */ + .main-leaderboardComponent-container { + display: none; + } +} diff --git a/user.css b/src/styles/main.scss similarity index 80% rename from user.css rename to src/styles/main.scss index d5eba74..8204044 100644 --- a/user.css +++ b/src/styles/main.scss @@ -1,1470 +1,1269 @@ -:root { - --bar-height: 70px; - --bar-cover-art-size: 40px; - --main-gap: 10px; - --main-corner-radius: 10px; - --scrollbar-vertical-size: 8px; - --cover-border-radius: 8px; - --playbar-movement-anim-speed: 0.5s; - --image-radius: 10px; - --sidebar-icons-border-radius: 50%; - --song-transition-speed: 3s; -} - -@font-face { - font-family: "Google Sans Display"; - src: url("glue-resources/fonts/GoogleSansDisplayRegular.woff2") format("woff2"); - font-style: normal; - font-weight: 400; -} - -@font-face { - font-family: "Google Sans Display"; - src: url("glue-resources/fonts/GoogleSansDisplayMedium.woff2") format("woff2"); - font-style: normal; - font-weight: 500; -} - -@font-face { - font-family: "Roboto"; - src: url("glue-resources/fonts/Roboto.woff2") format("woff2"); - font-style: normal; - font-weight: 400; -} - -@font-face { - font-family: "Roboto"; - src: url("glue-resources/fonts/RobotoMedium.woff2") format("woff2"); - font-style: normal; - font-weight: 500; -} - -/* smooth color transitions */ -@property --spice-sidebar { - syntax: ""; - initial-value: magenta; - inherits: true; -} - -@property --spice-main { - syntax: ""; - initial-value: magenta; - inherits: true; -} - -@property --spice-text { - syntax: ""; - initial-value: magenta; - inherits: true; -} - -@property --spice-button { - syntax: ""; - initial-value: magenta; - inherits: true; -} - -:root { - transition: all var(--song-transition-speed) linear; - transition-property: --spice-sidebar, --spice-main, --spice-text, --spice-button; -} - -body { - --glue-font-family: "Google Sans Display", "Roboto", spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif; - --info-font-family: "Roboto", spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif; - font-family: var(--glue-font-family); - letter-spacing: normal; -} - -.os-scrollbar-handle { - background-color: var(--spice-text) !important; - border-radius: calc(var(--scrollbar-vertical-size) / 2); -} - -.os-scrollbar-handle:hover { - filter: brightness(80%); -} - -::-webkit-scrollbar { - width: var(--scrollbar-vertical-size); -} - -::-webkit-scrollbar-thumb { - background-color: var(--spice-text) !important; - border-radius: calc(var(--scrollbar-vertical-size) / 2); -} - -::-webkit-scrollbar-thumb:hover { - filter: brightness(80%); -} - -.main-type-mesto, -.main-type-mestoBold, -.main-type-ballad, -.main-type-balladBold, -.main-type-canon { - font-family: var(--info-font-family); - letter-spacing: normal; -} - -.main-type-ballad { - font-weight: 500; -} - -.lyrics-lyricsContainer-LyricsLine { - font-family: var(--glue-font-family); - letter-spacing: -0.03em !important; -} - -.main-rootlist-rootlistDivider { - display: none; -} - -input { - background-color: unset !important; - border-radius: 0 !important; - padding: 6px 10px 6px 48px; - color: var(--spice-text) !important; -} - -input[type="range"] { - -webkit-appearance: none; - background: transparent; - padding: 0px; -} - -input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 16px; - height: 16px; - margin-top: -4px; - border-radius: 50%; - background-color: var(--spice-text); -} - -input[type="range"]::-webkit-slider-thumb:hover, -input[type="range"]::-webkit-slider-thumb:active { - filter: brightness(80%); -} - -input[type="range"]::after { - z-index: 9999; - content: attr(tooltip); - position: absolute; - min-width: 50px; - top: -10px; - left: 50%; - transform: translateX(calc(-50%)); - padding: 0 5px; - border-radius: 4px; - text-align: center; - color: var(--spice-sidebar-text); - background-color: var(--spice-button); - transition: opacity 0.25s ease; - opacity: 0; -} - -input[type="range"]:hover::after, -input[type="range"]:active::after { - opacity: 1; -} - -input[type="range"]:focus { - outline: none; -} - -input[type="range"]::-webkit-slider-runnable-track { - width: 100%; - height: 8px; - background-color: rgba(var(--spice-rgb-text), 0.2); - border-radius: 50vw; -} - -input[type="number"], -input[type="text"], -input[type="time"] { - height: 32px; - border: none; - border-radius: 4px !important; - padding: 0px 10px; - background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important; - color: var(--spice-subtext) !important; -} - -input[type="number"]:hover, -input[type="number"]:active, -input[type="text"]:hover, -input[type="text"]:active, -input[type="time"]:hover, -input[type="time"]:active { - background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important; -} - -input[type="time"]::-webkit-calendar-picker-indicator { - filter: invert(calc(1 - var(--is_light))); -} - -input[type="color"] { - position: relative; - padding: 0px; - border: none; -} - -input[type="color"]::before { - z-index: -1; - content: ""; - position: absolute; - inset: -5px; - border-radius: 4px; - background-color: rgba(var(--spice-rgb-selected-row), 0.4); -} - -input[type="color"]:hover::before, -input[type="color"]:active::before { - background-color: rgba(var(--spice-rgb-selected-row), 0.6); -} - -.x-searchInput-searchInputSearchIcon, -.x-searchInput-searchInputClearButton { - color: var(--spice-text) !important; -} - -.main-home-homeHeader, -.x-entityHeader-overlay, -.x-actionBarBackground-background, -.main-actionBarBackground-background, -.main-entityHeader-overlay, -.main-entityHeader-backgroundColor { - background-color: unset !important; - background-image: unset !important; -} - -.main-playButton-PlayButton.main-playButton-primary { - color: white; -} - -.connect-title, -.connect-header { - display: none; -} - -.connect-device-list { - margin: 0px -5px; -} - -/* Remove Topbar background colour */ -.main-topBar-background { - background-color: unset !important; -} -.main-topBar-overlay { - background-color: var(--spice-main); -} - -.main-entityHeader-shadow, -.main-contextMenu-menu, -.connect-device-list-container { - box-shadow: 0 4px 20px #21212130; -} - -.main-trackList-playingIcon { - image-rendering: pixelated; - filter: grayscale(1); -} - -.main-trackList-trackListRow:hover { - background-color: rgba(var(--spice-rgb-selected-row), 0.2) !important; -} - -.main-trackList-trackListRow:focus-within, -[aria-selected="true"] > .main-trackList-trackListRow { - background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important; -} - -span.artist-artistVerifiedBadge-badge svg > path:first-of-type { - fill: var(--spice-button); -} -span.artist-artistVerifiedBadge-badge svg > path:last-of-type { - fill: var(--spice-text); -} - -/* Full window artist background */ -.main-entityHeader-background.main-entityHeader-gradient { - opacity: 0.3; -} - -.main-entityHeader-container.main-entityHeader-withBackgroundImage, -.main-entityHeader-background, -.main-entityHeader-background.main-entityHeader-overlay:after { - height: 100vh; -} - -.main-entityHeader-withBackgroundImage .main-entityHeader-headerText { - justify-content: center; -} - -.main-entityHeader-container.main-entityHeader-nonWrapped.main-entityHeader-withBackgroundImage { - padding-left: 9%; -} - -.main-entityHeader-background.main-entityHeader-overlay:after { - background-image: linear-gradient(transparent, transparent), linear-gradient(var(--spice-main), var(--spice-main)); -} - -.artist-artistOverview-overview .main-entityHeader-withBackgroundImage h1 { - font-size: 120px !important; - line-height: 120px !important; -} - -.main-contextMenu-menu { - background-color: var(--spice-button); -} - -.main-contextMenu-menuHeading, -.main-contextMenu-menuItemButton, -.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):focus, -.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):hover { - color: var(--spice-main); -} - -.main-playPauseButton-button { - background-color: var(--spice-button); - color: white; -} - -/** Queue page header */ -.queue-queue-title, -.queue-playHistory-title { - color: var(--spice-text) !important; -} - -/** Artist page */ -.artist-artistOverview-heading { - color: var(--spice-text) !important; -} -.artist-artistAbout-content .artist-artistAbout-cityBlock div, -.artist-artistAbout-content .artist-artistAbout-stats div { - color: var(--spice-text) !important; -} -.artist-artistAbout-content div { - color: var(--spice-text) !important; -} - -/** Cards */ -.main-cardImage-imageWrapper { - background-color: transparent; - box-shadow: unset; - -webkit-box-shadow: unset; -} - -.main-cardImage-imagePlaceholder, -.main-cardImage-image { - border-radius: var(--cover-border-radius); -} - -.main-rootlist-rootlistDivider { - background-color: unset; -} - -.main-nowPlayingBar-nowPlayingBar { - height: var(--bar-height); -} - -.Root__top-bar { - border-radius: var(--main-corner-radius) var(--main-corner-radius) 0 0; -} - -.main-topBar-background { - border-radius: var(--main-corner-radius) var(--main-corner-radius) 0 0; -} - -.Root__main-view { - background-color: var(--spice-main); - border-radius: var(--main-corner-radius) var(--main-corner-radius) 0 0; - overflow: hidden; -} - -.main-buddyFeed-buddyFeed { - box-shadow: unset; - -webkit-box-shadow: unset; - z-index: 0; -} - -.main-buddyFeed-headerTitle, -.main-buddyFeed-activityMetadata .main-buddyFeed-username a { - color: var(--spice-sidebar-text); -} - -.main-buddyFeed-activityMetadata .main-buddyFeed-artistAndTrackName a, -.main-buddyFeed-activityMetadata .main-buddyFeed-artistAndTrackName span, -.main-buddyFeed-activityMetadata .main-buddyFeed-playbackContextLink, -.main-buddyFeed-activityMetadata .main-buddyFeed-timestamp { - color: rgba(var(--spice-rgb-sidebar-text), 0.8); -} - -.main-buddyFeed-buddyFeedRoot .main-avatar-avatar, -.main-buddyFeed-buddyFeedRoot .main-buddyFeed-overlay { - width: 32px !important; - height: 32px !important; - border-radius: var(--sidebar-icons-border-radius); -} - -.main-avatar-avatar > div { - width: 100% !important; - height: 100% !important; -} - -.view-homeShortcutsGrid-shortcut { - overflow: hidden; - background-color: rgba(var(--spice-rgb-selected-row), 0.4); -} - -.view-homeShortcutsGrid-shortcut:hover { - background-color: rgba(var(--spice-rgb-selected-row), 0.6); -} - -.cover-art, -.main-userWidget-box.update-avail, -.view-homeShortcutsGrid-shortcut, -:not(.view-homeShortcutsGrid-imageWrapper) > .main-image-image:not(.main-avatar-image) { - border-radius: var(--image-radius) !important; -} - -.main-avatar-image, -.main-userWidget-box:not(.update-avail), -.main-avatar-userIcon, -.view-homeShortcutsGrid-shortcutLink { - border-radius: var(--sidebar-icons-border-radius) !important; -} - -.main-userWidget-box { - background-color: transparent !important; -} - -.main-userWidget-box.update-avail { - backdrop-filter: invert(0.1); -} - -.main-avatar-avatar.main-avatar-withBadge:after { - box-shadow: 0 0 0 2px var(--spice-sidebar); - background-color: var(--spice-notification); - right: 0; -} - -.Root__now-playing-bar { - border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius); - background-color: unset; -} - -.main-nowPlayingBar-container { - border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius); - background-color: unset; - background: radial-gradient(ellipse at right 50% bottom -80px, rgba(var(--spice-rgb-sidebar), 0.55), var(--spice-main) 60%); - border-top: 0; - min-width: 518px; -} - -.main-buddyFeed-findFriendsButton { - color: var(--spice-sidebar-text); -} - -.main-connectBar-connectBar { - border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius); - border: 2px solid var(--spice-main); - border-top: 0; - background-color: var(--spice-button) !important; - color: var(--spice-text) !important; -} - -.Root__nav-bar { - height: 100%; - z-index: 1; - width: var(--sidebar-width) !important; -} - -.main-buddyFeed-buddyFeedRoot { - height: 100%; -} - -.main-buddyFeed-buddyFeedRoot .os-content { - padding-top: 0 !important; -} - -html, -.Root__nav-bar, -.main-buddyFeed-buddyFeed { - background-color: var(--spice-sidebar) !important; -} - -html { - background-position: center; - background-repeat: no-repeat; -} - -.Root__nav-bar .link-subtle, -.main-rootlist-rootlistItemLink:link, -.main-rootlist-rootlistItemLink:visited, -.main-rootlist-rootlistContent span, -.main-navBar-entryPoints span { - z-index: 999; - color: var(--spice-sidebar-text); -} - -.main-navBar-navBarItem svg { - width: 24px !important; - height: 24px !important; -} - -.main-navBar-navBarItem { - position: relative; - padding: 0px; -} - -#spicetify-show-list > * { - padding: 0 8px; -} - -.main-rootlist-statusIcons { - color: var(--spice-sidebar-text); - padding-right: 16px; -} - -.main-rootlist-statusIcons .main-playButton-button { - color: var(--spice-sidebar-text); -} - -.main-rootlist-expandArrow { - position: absolute; - top: 20px; - right: 12px; - width: 16px; - height: 16px; - color: var(--spice-sidebar-text) !important; - background-color: var(--spice-button); - border-radius: 50%; - box-shadow: 0 0 0 2px var(--spice-sidebar); - opacity: 0; -} - -li.GlueDropTarget:hover .main-rootlist-expandArrow { - opacity: 1; -} - -html:not(.sidebar-hide-text) .main-rootlist-expandArrow { - opacity: 1; -} - -.main-rootlist-expandArrow::before { - font-size: 10px; - padding-bottom: 3px; -} - -html.sidebar-hide-text .main-rootlist-expandArrow { - right: 4px; -} - -html.sidebar-hide-text .main-navBar-navBarItem span, -html.sidebar-hide-text .main-rootlist-rootlistContent span, -html.sidebar-hide-text .main-rootlist-rootlistItem span, -html.sidebar-hide-text .main-rootlist-statusIcons, -html.sidebar-hide-text .GlueDropTarget span { - display: none; -} - -.main-rootlist-rootlist { - margin-top: 0; -} - -.main-rootlist-rootlist::before, -.main-rootlist-rootlist::after { - z-index: 10; - position: absolute; - content: ""; - left: 0px; - right: 0px; - pointer-events: none; - transition: height 0.5s ease; -} - -.main-rootlist-rootlist.no-top-shadow::before { - height: 0px; -} - -.main-rootlist-rootlist.no-bottom-shadow::after { - height: 0px; -} - -.main-rootlist-rootlist::before { - top: 0px; - height: 5%; - background: linear-gradient(to bottom, var(--spice-sidebar) 10%, transparent); -} - -.main-rootlist-rootlist::after { - bottom: 0px; - height: 15%; - background: linear-gradient(to top, var(--spice-sidebar) 10%, transparent); -} - -.Root__nav-bar .os-scrollbar-vertical, -.main-buddyFeed-buddyFeedRoot .os-scrollbar-vertical { - display: none; -} - -.x-toggle-indicatorWrapper { - background-color: transparent; - backdrop-filter: invert(0.25); -} - -input:checked ~ .x-toggle-indicatorWrapper { - background-color: rgba(var(--spice-rgb-button), 0.4); -} - -input:hover:checked ~ .x-toggle-indicatorWrapper { - background-color: rgba(var(--spice-rgb-button), 0.5) !important; -} - -input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper { - background-color: transparent; - backdrop-filter: invert(0.4); -} - -/** */ -.main-topBar-historyButtons .main-topBar-button { - background-color: unset; - width: 24px; - height: 24px; -} - -.main-topBar-historyButtons svg { - color: var(--spice-button); - fill: var(--spice-button); -} - -.playback-bar__progress-time, -.playback-bar__progress-time-elapsed, -.main-playbackBarRemainingTime-container { - display: none; -} - -.playback-bar { - position: absolute; - width: var(--main-view-width); - left: var(--sidebar-width); - bottom: calc(var(--main-gap) + var(--bar-height) - 12px / 2); -} - -.Root.is-connectBarVisible .playback-bar { - bottom: calc(var(--main-gap) + var(--bar-height) + 24px - 12px / 2); -} - -.main-nowPlayingWidget-coverArt .cover-art { - width: var(--bar-cover-art-size) !important; - height: var(--bar-cover-art-size) !important; -} - -.player-controls__buttons { - margin-bottom: 0; - width: 192px; -} - -.progress-bar { - --progress-bar-height: 2px; - --fg-color: var(--spice-button); - --bg-color: rgba(var(--spice-rgb-text), 0.2); -} - -.progress-bar__slider { - display: block !important; - opacity: 0; - transition: opacity 0.2s ease; -} - -.progress-bar:hover .progress-bar__slider, -.progress-bar:active .progress-bar__slider { - opacity: 1; -} - -.progress-bar:not(:active) .x-progressBar-progressBarBg > div:first-child > div { - transition: transform var(--playbar-movement-anim-speed) ease; -} - -.progress-bar:not(:active) .progress-bar__slider { - transition-property: left, opacity; -} - -.playback-bar .prog-tooltip { - position: absolute; - min-width: 100px; - top: -35px; - left: 50%; - transform: translateX(calc(-50%)); - padding: 0 5px; - border-radius: 4px; - text-align: center; - color: var(--spice-text); - background-color: var(--spice-button); - pointer-events: none; -} - -.playback-bar:not(:active) .prog-tooltip { - transition: transform var(--playbar-movement-anim-speed) ease; -} - -.minimal-player .player-controls__buttons { - width: 120px; - gap: 0px; -} - -.minimal-player .player-controls__left, -.minimal-player .player-controls__right { - --button-size: 16px !important; - gap: 0px; -} - -.minimal-player .volume-bar { - flex: 0 1 70px; -} -.extra-minimal-player .player-controls__buttons { - width: 64px; -} -.extra-minimal-player .main-shuffleButton-button, -.extra-minimal-player .main-repeatButton-button, -.extra-minimal-player .ExtraControls__connect-device-picker, -.extra-minimal-player .volume-bar .progress-bar-wrapper { - display: none; -} -.extra-minimal-player .volume-bar { - flex: 0 0 32px; -} - -.main-trackInfo-name { - font-weight: 500; -} - -.main-topBar-topbarContent .main-playButton-PlayButton { - --size: 35px !important; -} - -.main-entityHeader-image { - border-radius: 5px; -} - -.main-entityHeader-metaDataText, -.main-duration-container { - color: var(--spice-subtext); -} - -/** Sidebar */ -.main-rootlist-rootlist .os-content { - padding: 0 0 8px 0 !important; -} - -.main-rootlist-rootlistDividerContainer { - display: none; -} - -.main-rootlist-rootlistItem a { - align-items: center; - border-radius: 4px; - display: flex; - height: 56px; - padding: 0 12px; -} - -img.playlist-picture { - width: 32px; - height: 32px; - flex: 0 0 32px; - background-size: cover; - background-position: center; - border-radius: var(--sidebar-icons-border-radius); -} - -.main-rootlist-rootlistItem a span { - margin-left: 24px; -} - -.main-rootlist-rootlistItem { - padding-left: calc(var(--indentation) * var(--left-sidebar-item-indentation-width)); - padding-right: 0; - transition: padding-left 0.5s ease; -} - -html.sidebar-hide-text .main-rootlist-rootlistItem { - padding: 0; -} - -.main-rootlist-dropIndicator { - background: var(--spice-selected-row); - height: 2px; -} - -.main-rootlist-rootlistPlaylistsScrollNode { - padding: 0px; -} - -.main-collectionLinkButton-icon, -.main-createPlaylistButton-icon { - margin: 0px; -} - -.main-navBar-navBarLink, -.main-collectionLinkButton-collectionLinkButton, -.main-createPlaylistButton-button { - gap: 24px; - height: 56px; -} - -li.GlueDropTarget { - padding: 0 8px; -} - -/** OS-specific window controls dodge */ -#main:not([top-bar^="none"]) .main-topBar-background { - -webkit-app-region: no-drag; -} - -#main:not([top-bar="none-padding"]) .main-navBar-navBar, -#main:not([top-bar="none-padding"]) .main-buddyFeed-header, -#main:not([top-bar="none-padding"]) .main-navBar-entryPoints { - padding-top: 8px !important; -} - -#main:not([top-bar^="none"]) { - padding-top: 31px; -} - -#main:not([top-bar^="none"])::before { - z-index: 999; - content: ""; - position: absolute; - top: 0px; - left: 0px; - right: 135px; - height: 31px; - background-color: rgba(0, 0, 0, 0.53); - -webkit-app-region: drag; - pointer-events: none; -} - -#main[top-bar="solid"]::before { - right: 0px; - background-color: black; -} - -#main[top-bar="none-padding"] .spotify__os--is-windows .main-navBar-navBar { - padding-top: 24px; -} - -#main[top-bar="none-padding"] .spotify__container--is-desktop:not(.fullscreen).spotify__os--is-windows .main-navBar-entryPoints { - padding-top: 22px; -} - -#main[top-bar="none-padding"] .spotify__os--is-windows .main-buddyFeed-header { - padding-top: 32px; -} - -#main[top-bar="none-padding"] .spotify__container--is-desktop.spotify__os--is-windows[dir="ltr"] .Root__top-bar + .main-buddyFeed-buddyFeedRoot .main-topBar-container { - padding-right: 167px; -} - -.main-topBar-container { - max-width: unset; -} - -/** Custom elements */ -.dribs-playlist-list { - padding-bottom: 86px; -} - -#dribbblish-back-shadow { - position: fixed; - width: var(--main-view-width); - height: calc(var(--main-view-height) + var(--bar-height)); - box-shadow: 0 0 10px 3px #0000003b; - border-radius: var(--main-corner-radius); - z-index: 2; - pointer-events: none; -} - -#dribbblish-config { - display: none; - z-index: 99999; - position: absolute; - inset: 0px; - align-items: center; - justify-content: center; - color: var(--spice-text); -} - -#dribbblish-config[active] { - display: flex; -} - -.dribbblish-config-container { - z-index: 1; - position: relative; - width: clamp(500px, 50%, 650px); - background-color: rgba(var(--spice-rgb-main), 0.9); - backdrop-filter: blur(3px); - padding: 20px 15px; - border-radius: var(--main-corner-radius); - box-shadow: 0 0 10px 3px #0000003b; - display: flex; - gap: 5px; - flex-direction: column; - align-items: center; - justify-content: center; -} - -.dribbblish-config-areas { - display: flex; - width: 100%; - flex-direction: column; - gap: 16px; - max-height: 60vh; - overflow-y: auto; - padding: 0px 50px; -} - -.dribbblish-config-area, -.dribbblish-config-area-items { - display: flex; - flex-direction: column; - gap: 16px; -} - -.dribbblish-config-area[collapsed] { - overflow: hidden; - min-height: 38px; /* for some reason height alone isn't enough */ - height: 38px; -} - -.dribbblish-config-area:empty { - display: none; -} - -.dribbblish-config-area > h2 { - position: relative; - text-align: center; - height: 38px; -} - -.dribbblish-config-area > h2 svg { - position: absolute; - margin-left: 10px; - bottom: -2px; - color: var(--spice-text); - padding: 0px; - height: 100%; - stroke-width: 2px; - transform: rotate(90deg); -} - -.dribbblish-config-area[collapsed] > h2 svg { - transform: rotate(270deg); -} - -.dribbblish-config-item { - position: relative; - width: 100%; - height: min-content; - display: grid; - grid-template-columns: 1fr auto; - grid-template-rows: auto auto; - gap: 5px 10px; - grid-template-areas: - "header input" - "description input"; -} - -.dribbblish-config-item[parent] { - padding-left: 16px; -} - -.dribbblish-config-item[hidden="true"] { - display: none; -} - -.dribbblish-config-item > .x-settings-title { - grid-area: header; - margin: 0px; - height: min-content; - position: relative; - bottom: 0px; -} - -.dribbblish-config-item > .x-settings-title.no-desc { - bottom: -10px; -} - -.dribbblish-config-item > .main-type-mesto { - grid-area: description; - height: min-content; - color: var(--spice-subtext); -} - -.dribbblish-config-item > .x-settings-secondColumn { - grid-area: input; -} - -.dribbblish-config-close { - position: absolute; - top: 15px; - right: 15px; -} - -.dribbblish-config-backdrop { - position: absolute; - content: ""; - inset: 0px; - backdrop-filter: blur(3px) brightness(60%); -} - -/** Rearrange player bar */ -.main-nowPlayingBar-left { - order: 1; - flex: 1; - width: auto; - min-width: 0 !important; -} - -.main-nowPlayingBar-center { - order: 0; - flex: 1; - width: auto; - min-width: unset !important; -} - -.main-nowPlayingBar-right { - order: 2; - flex: 1; - width: auto; - min-width: unset !important; -} - -.main-nowPlayingWidget-nowPlaying { - justify-content: center; -} - -.player-controls { - justify-content: flex-start; - flex-direction: row; -} - -.main-playPauseButton-button { - background-color: transparent; -} - -.main-playPauseButton-button svg { - width: 32px !important; - height: 32px !important; - color: var(--spice-button); -} - -/* Spotify style player bar */ -#main[player-controls="spotify"] .main-nowPlayingBar-left { - order: 0; -} - -#main[player-controls="spotify"] .main-nowPlayingBar-center { - order: 1; -} - -#main[player-controls="spotify"] .main-nowPlayingWidget-nowPlaying { - justify-content: left; -} - -#main[player-controls="spotify"] .player-controls { - justify-content: center; -} - -/** Main container */ -.contentSpacing, -.artist-artistDiscography-headerContainer { - padding-left: 64px; - padding-right: 64px; -} - -@media (min-width: 1024px) { - .contentSpacing, - .artist-artistDiscography-headerContainer { - padding-left: 128px; - padding-right: 128px; - } -} - -.main-collectionLinkButton-collectionLinkButton .main-collectionLinkButton-icon, -.main-collectionLinkButton-collectionLinkButton .main-collectionLinkButton-collectionLinkText, -.main-createPlaylistButton-button { - opacity: 1; -} - -.main-collectionLinkButton-collectionLinkText, -.main-createPlaylistButton-text, -.main-navBar-navBarLink > span { - font-size: 14px; - font-weight: 400; - letter-spacing: normal; - line-height: 20px; - text-transform: none; -} - -.main-likedSongsButton-likedSongsIcon, -.main-yourEpisodesButton-yourEpisodesIcon, -.main-createPlaylistButton-createPlaylistIcon { - background: unset !important; -} - -.main-createPlaylistButton-icon, -.main-collectionLinkButton-icon, -.main-createPlaylistButton-icon { - height: 40px; -} - -.main-likedSongsButton-likedSongsIcon svg, -.main-yourEpisodesButton-yourEpisodesIcon svg, -.main-createPlaylistButton-createPlaylistIcon svg { - fill: var(--spice-sidebar-text); - width: 32px; - height: 32px; -} -.main-yourEpisodesButton-yourEpisodesIcon svg path { - fill: var(--spice-sidebar-text); -} - -/** Grid */ -.Root__top-container { - grid-template-areas: - "nav-bar main-view buddy-feed" - "nav-bar now-playing-bar buddy-feed"; - padding: var(--main-gap) 0; -} - -html:not(.buddyfeed-visible) .Root__top-container { - padding-right: var(--main-gap); -} - -/** Minimal profile button */ -span.main-userWidget-displayName, -.main-userWidget-box svg { - display: none; -} - -/** Sidebar config */ -#dribs-hidden-list { - background-color: rgba(var(--spice-rgb-main), 0.3); -} - -#dribs-sidebar-config { - position: relative; - width: 100%; - height: 0; - display: flex; - justify-content: space-evenly; - align-items: center; - top: -30px; - left: 0; -} - -#dribs-sidebar-config button { - min-width: 60px; - border-radius: 3px; - background-color: var(--spice-main); - color: var(--spice-text); - border: 1px solid var(--spice-text); -} -#dribs-sidebar-config button:disabled { - color: var(--spice-button-disabled); -} - -.main-navBar-entryPoints { - --left-sidebar-padding-left: 24px; - --left-sidebar-padding-right: 24px; - - padding: 0px 8px; -} - -.main-navBar-navBar .main-rootlist-wrapper > div:nth-child(2), -.main-navBar-entryPoints, -#spicetify-show-list, -.main-rootlist-rootlistContent .os-content { - display: flex; - flex-direction: column; - gap: 5px; -} - -#spicetify-show-list:empty { - display: none; -} - -.main-rootlist-wrapper > div:nth-child(2) > li img, -.main-navBar-navBarLink > .icon { - z-index: 100; -} - -.main-collectionLinkButton-collectionLinkButton, -.main-createPlaylistButton-button { - position: relative; -} - -.main-navBar-navBarLink::before, -.main-collectionLinkButton-collectionLinkButton::before, -.main-createPlaylistButton-button::before, -.main-rootlist-rootlistItemLink::before { - content: ""; - position: absolute; - width: 100%; - inset: 0px; - left: -200%; - opacity: 0.4; - background-color: black; - border-radius: var(--image-radius); - pointer-events: none; - transition: all calc(var(--sidebar-icons-hover-animation) * 0.2s) ease; - transition-property: left, opacity; -} - -.main-navBar-navBarLink:hover::before, -.main-collectionLinkButton-collectionLinkButton:hover::before, -.main-createPlaylistButton-button:hover::before, -.main-rootlist-rootlistItemLink:hover::before, -.main-navBar-navBarLinkActive::before, -.main-collectionLinkButton-selected::before { - left: 0px; -} - -.main-navBar-navBarLinkActive:hover::before, -.main-collectionLinkButton-selected:hover::before { - opacity: 0.6; -} - -.main-rootlist-rootlist .main-navBar-navBarItem:hover::before { - margin: 0 8px; -} - -.main-navBar-navBarLinkActive, -.main-collectionLinkButton-selected { - background-color: transparent; -} - -.main-navBar-navBar a:hover, -.main-navBar-navBar a:hover span { - color: var(--spice-sidebar-text) !important; -} - -div.GlueDropTarget.personal-library { - padding: 0px; -} -div.GlueDropTarget.personal-library > * { - padding: 0 16px; - height: 56px; - border-radius: 4px; -} - -div.GlueDropTarget.personal-library > *.active { - background: transparent; -} - -/** Big cover, small cover */ -.main-coverSlotExpanded-container { - position: fixed; - z-index: 2; - width: 250px; - height: 250px; - bottom: calc(var(--main-gap) + var(--bar-height) + 10px); - left: calc(var(--sidebar-width) + 10px); -} - -.Root.is-connectBarVisible .main-coverSlotExpanded-container { - bottom: calc(var(--main-gap) + var(--bar-height) + 24px + 10px); -} - -html.right-expanded-cover .main-coverSlotExpanded-container { - right: calc(var(--main-gap) + 10px); - left: unset; -} - -html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container { - right: calc(var(--main-gap) + var(--buddy-feed-width) + 10px); - left: unset; -} - -.main-coverSlotExpanded-container img { - border-radius: 4px; -} - -.cover-art { - border-radius: 4px; -} - -.main-nowPlayingWidget-coverExpanded .main-coverSlotCollapsed-container { - opacity: 0; -} - -.main-nowPlayingWidget-coverExpanded { - transform: translateX(-27px); -} - -/** Mini dribbblish */ -.x-categoryCard-CategoryCard > div { - background-color: var(--spice-main); - width: calc(100% - 14px); - height: calc(100% - 6px); - margin: 3px 10px; - border-radius: 5px; -} - -.x-categoryCard-CategoryCard { - height: 100px; -} - -.x-categoryCard-image { - width: 50px !important; - height: 50px !important; -} - -.x-heroCategoryCard-HeroCategoryCard > div { - background-color: var(--spice-main); - width: calc(100% - 20px); - height: calc(100% - 6px); - margin: 3px 16px; - border-radius: 5px; -} - -.main-dropDown-dropDown, -.x-sortBox-sortDropdown { - background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important; - color: var(--spice-subtext); -} - -.main-dropDown-dropDown:hover, -.x-sortBox-sortDropdown:hover { - background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important; -} - -.connect-device-list-item:focus, -.connect-device-list-item:hover { - background-color: rgba(var(--spice-rgb-selected-row), 0.3); -} - -.bookmark-filter { - color: var(--spice-main) !important; -} - -/* 1.1.56 */ -.main-navBar-navBar { - width: var(--sidebar-width) !important; -} - -.main-entityHeader-container.main-entityHeader-nonWrapped { - padding-left: 64px; - padding-right: 64px; -} - -@media (min-width: 1024px) { - .main-entityHeader-container.main-entityHeader-nonWrapped { - padding-left: 128px; - padding-right: 128px; - } -} - -.main-userWidget-dropDownMenu > li > button { - color: rgba(var(--spice-rgb-selected-row), 0.7); - padding-left: 8px; - text-decoration: none; -} -.main-userWidget-dropDownMenu > li > button:hover, -.main-userWidget-dropDownMenu > li > button:focus { - color: var(--spice-text); -} - -.main-userWidget-dropDownMenu svg { - position: unset; -} -.main-userWidget-dropDownMenu > li svg { - position: absolute; -} -.main-buddyFeed-buddyFeed.main-buddyFeed-buddyFeed-expanded { - z-index: 4; -} - -.main-actionBar-ActionBarRow button:not(.main-playButton-primary) { - color: var(--spice-subtext); -} - -/* explicit icon */ -.main-tag-container { - background-color: var(--spice-text); -} -/* progressbar tooltip text color */ -.playback-bar .prog-tooltip { - color: var(--spice-sidebar-text) !important; -} - -/* edit button of CustomApps */ -.reddit-sort-container button.switch, -.new-releases-header button.switch, -.lyrics-tabBar-header button.switch { - background-color: rgba(var(--spice-rgb-subtext), 0.15) !important; - color: var(--spice-text); -} - -.reddit-sort-container button.switch:hover, -.new-releases-header button.switch:hover, -.lyrics-tabBar-header button.switch:hover { - background-color: rgba(var(--spice-rgb-subtext), 0.3) !important; -} - -.lyrics-lyricsContainer-LyricsBackground { - background: linear-gradient(180deg, transparent 0px, transparent 60px, var(--lyrics-color-background) 61px) !important; -} - -/* big cover opacity on hover */ -.main-coverSlotExpanded-container:hover .cover-art, -.main-coverSlotExpanded-container:hover img { - opacity: 0.5; -} - -.main-navBar-navBar a:hover, -.main-navBar-navBar a:hover span, -.main-buddyFeed-activityMetadata a:hover { - color: var(--spice-shadow) !important; -} - -.collection-collectionEntityHeroCard-likedSongs, -.collection-collectionEntityHeroCard-likedSongs .main-cardHeader-link, -.collection-collectionEntityHeroCard-likedSongs .collection-collectionEntityHeroCard-descriptionContainer, -.x-heroCategoryCard-heroTitle, -.main-rootlist-expandArrow:focus, -.main-rootlist-expandArrow:hover, -.main-rootlist-textWrapper:focus, -.main-rootlist-textWrapper:hover, -.main-contextMenu-menuHeading, -.main-contextMenu-menuItemButton, -.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):focus, -.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):hover { - color: var(--spice-sidebar-text) !important; -} - -/* translucent background cover */ -.Root__top-container::before { - content: ""; - background-image: var(--image_url); - background-repeat: no-repeat; - background-size: cover; - background-position: center center; - position: fixed; - display: block; - top: 0; - left: 0; - right: 0; - bottom: 0; - filter: blur(15px); - pointer-events: none; - backface-visibility: hidden; - will-change: transform; - opacity: calc(0.07 + 0.03 * var(--is_light, 0)); - z-index: +3; - transition: background-image var(--song-transition-speed) linear; -} +// SASS overwrites the CSS invert function so we overwrite it back +@function invert($v) { + @return #{"invert("}$v#{")"}; +} + +@import "Inputs"; +@import "ConfigMenu"; +@import "NoAds"; + +:root { + --bar-height: 70px; + --bar-cover-art-size: 40px; + --main-gap: 10px; + --main-corner-radius: 10px; + --scrollbar-vertical-size: 8px; + --cover-border-radius: 8px; + --playbar-movement-anim-speed: 0.5s; + --image-radius: 10px; + --sidebar-icons-border-radius: 50%; + --song-transition-speed: 3s; +} + +@font-face { + font-family: "Google Sans Display"; + src: url("glue-resources/fonts/GoogleSansDisplayRegular.woff2") format("woff2"); + font-style: normal; + font-weight: 400; +} + +@font-face { + font-family: "Google Sans Display"; + src: url("glue-resources/fonts/GoogleSansDisplayMedium.woff2") format("woff2"); + font-style: normal; + font-weight: 500; +} + +@font-face { + font-family: "Roboto"; + src: url("glue-resources/fonts/Roboto.woff2") format("woff2"); + font-style: normal; + font-weight: 400; +} + +@font-face { + font-family: "Roboto"; + src: url("glue-resources/fonts/RobotoMedium.woff2") format("woff2"); + font-style: normal; + font-weight: 500; +} + +/* smooth color transitions */ +@property --spice-sidebar { + syntax: ""; + initial-value: magenta; + inherits: true; +} + +@property --spice-main { + syntax: ""; + initial-value: magenta; + inherits: true; +} + +@property --spice-text { + syntax: ""; + initial-value: magenta; + inherits: true; +} + +@property --spice-button { + syntax: ""; + initial-value: magenta; + inherits: true; +} + +:root { + transition: all var(--song-transition-speed) linear; + transition-property: --spice-sidebar, --spice-main, --spice-text, --spice-button; +} + +body { + --glue-font-family: "Google Sans Display", "Roboto", spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif; + --info-font-family: "Roboto", spotify-circular, spotify-circular-cyrillic, spotify-circular-arabic, spotify-circular-hebrew, Helvetica Neue, helvetica, arial, Hiragino Kaku Gothic Pro, Meiryo, MS Gothic, sans-serif; + font-family: var(--glue-font-family); + letter-spacing: normal; +} + +.os-scrollbar-handle { + background-color: var(--spice-text) !important; + border-radius: calc(var(--scrollbar-vertical-size) / 2); +} + +.os-scrollbar-handle:hover { + filter: brightness(80%); +} + +::-webkit-scrollbar { + width: var(--scrollbar-vertical-size); +} + +::-webkit-scrollbar-thumb { + background-color: var(--spice-text) !important; + border-radius: calc(var(--scrollbar-vertical-size) / 2); +} + +::-webkit-scrollbar-thumb:hover { + filter: brightness(80%); +} + +.main-type-mesto, +.main-type-mestoBold, +.main-type-ballad, +.main-type-balladBold, +.main-type-canon { + font-family: var(--info-font-family); + letter-spacing: normal; +} + +.main-type-ballad { + font-weight: 500; +} + +.lyrics-lyricsContainer-LyricsLine { + font-family: var(--glue-font-family); + letter-spacing: -0.03em !important; +} + +.main-rootlist-rootlistDivider { + display: none; +} + +.x-searchInput-searchInputSearchIcon, +.x-searchInput-searchInputClearButton { + color: var(--spice-text) !important; +} + +.main-home-homeHeader, +.x-entityHeader-overlay, +.x-actionBarBackground-background, +.main-actionBarBackground-background, +.main-entityHeader-overlay, +.main-entityHeader-backgroundColor { + background-color: unset !important; + background-image: unset !important; +} + +.main-playButton-PlayButton.main-playButton-primary { + color: white; +} + +.connect-title, +.connect-header { + display: none; +} + +.connect-device-list { + margin: 0px -5px; +} + +/* Remove Topbar background colour */ +.main-topBar-background { + background-color: unset !important; +} +.main-topBar-overlay { + background-color: var(--spice-main); +} + +.main-entityHeader-shadow, +.main-contextMenu-menu, +.connect-device-list-container { + box-shadow: 0 4px 20px #21212130; +} + +.main-trackList-playingIcon { + image-rendering: pixelated; + filter: grayscale(1); +} + +.main-trackList-trackListRow:hover { + background-color: rgba(var(--spice-rgb-selected-row), 0.2) !important; +} + +.main-trackList-trackListRow:focus-within, +[aria-selected="true"] > .main-trackList-trackListRow { + background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important; +} + +span.artist-artistVerifiedBadge-badge svg > path:first-of-type { + fill: var(--spice-button); +} +span.artist-artistVerifiedBadge-badge svg > path:last-of-type { + fill: var(--spice-text); +} + +/* Full window artist background */ +.main-entityHeader-background.main-entityHeader-gradient { + opacity: 0.3; +} + +.main-entityHeader-container.main-entityHeader-withBackgroundImage, +.main-entityHeader-background, +.main-entityHeader-background.main-entityHeader-overlay:after { + height: 100vh; +} + +.main-entityHeader-withBackgroundImage .main-entityHeader-headerText { + justify-content: center; +} + +.main-entityHeader-container.main-entityHeader-nonWrapped.main-entityHeader-withBackgroundImage { + padding-left: 9%; +} + +.main-entityHeader-background.main-entityHeader-overlay:after { + background-image: linear-gradient(transparent, transparent), linear-gradient(var(--spice-main), var(--spice-main)); +} + +.artist-artistOverview-overview .main-entityHeader-withBackgroundImage h1 { + font-size: 120px !important; + line-height: 120px !important; +} + +.main-contextMenu-menu { + background-color: var(--spice-button); +} + +.main-contextMenu-menuHeading, +.main-contextMenu-menuItemButton, +.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):focus, +.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):hover { + color: var(--spice-main); +} + +.main-playPauseButton-button { + background-color: var(--spice-button); + color: white; +} + +/** Queue page header */ +.queue-queue-title, +.queue-playHistory-title { + color: var(--spice-text) !important; +} + +/** Artist page */ +.artist-artistOverview-heading { + color: var(--spice-text) !important; +} +.artist-artistAbout-content .artist-artistAbout-cityBlock div, +.artist-artistAbout-content .artist-artistAbout-stats div { + color: var(--spice-text) !important; +} +.artist-artistAbout-content div { + color: var(--spice-text) !important; +} + +/** Cards */ +.main-card-card { + border-radius: var(--main-corner-radius) !important; +} + +.main-cardImage-imageWrapper { + background-color: transparent; + box-shadow: unset; + -webkit-box-shadow: unset; +} + +.main-cardImage-imagePlaceholder, +.main-cardImage-image { + border-radius: var(--cover-border-radius); +} + +.main-rootlist-rootlistDivider { + background-color: unset; +} + +.main-nowPlayingBar-nowPlayingBar { + height: var(--bar-height); +} + +.Root__top-bar { + border-radius: var(--main-corner-radius) var(--main-corner-radius) 0 0; +} + +.main-topBar-background { + border-radius: var(--main-corner-radius) var(--main-corner-radius) 0 0; +} + +.Root__main-view { + background-color: var(--spice-main); + border-radius: var(--main-corner-radius) var(--main-corner-radius) 0 0; + overflow: hidden; +} + +.main-buddyFeed-buddyFeed { + box-shadow: unset; + -webkit-box-shadow: unset; + z-index: 0; +} + +.main-buddyFeed-headerTitle, +.main-buddyFeed-activityMetadata .main-buddyFeed-username a { + color: var(--spice-sidebar-text); +} + +.main-buddyFeed-activityMetadata .main-buddyFeed-artistAndTrackName a, +.main-buddyFeed-activityMetadata .main-buddyFeed-artistAndTrackName span, +.main-buddyFeed-activityMetadata .main-buddyFeed-playbackContextLink, +.main-buddyFeed-activityMetadata .main-buddyFeed-timestamp { + color: rgba(var(--spice-rgb-sidebar-text), 0.8); +} + +.main-buddyFeed-buddyFeedRoot .main-avatar-avatar, +.main-buddyFeed-buddyFeedRoot .main-buddyFeed-overlay { + width: 32px !important; + height: 32px !important; + border-radius: var(--sidebar-icons-border-radius); +} + +.main-avatar-avatar > div { + width: 100% !important; + height: 100% !important; +} + +.view-homeShortcutsGrid-shortcut { + overflow: hidden; + background-color: rgba(var(--spice-rgb-selected-row), 0.4); +} + +.view-homeShortcutsGrid-shortcut:hover { + background-color: rgba(var(--spice-rgb-selected-row), 0.6); +} + +.cover-art, +.main-userWidget-box.update-avail, +.view-homeShortcutsGrid-shortcut, +:not(.view-homeShortcutsGrid-imageWrapper) > .main-image-image:not(.main-avatar-image) { + border-radius: var(--image-radius) !important; +} + +.main-avatar-image, +.main-userWidget-box:not(.update-avail), +.main-avatar-userIcon, +.view-homeShortcutsGrid-shortcutLink { + border-radius: var(--sidebar-icons-border-radius) !important; +} + +.main-userWidget-box { + background-color: transparent !important; +} + +.main-userWidget-box.update-avail { + backdrop-filter: invert(0.1); + padding-right: 10px; +} + +.main-avatar-avatar.main-avatar-withBadge:after { + box-shadow: 0 0 0 2px var(--spice-sidebar); + background-color: var(--spice-notification); + right: 0; +} + +.Root__now-playing-bar { + border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius); + background-color: unset; +} + +.main-nowPlayingBar-container { + border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius); + background-color: unset; + background: radial-gradient(ellipse at right 50% bottom -80px, rgba(var(--spice-rgb-sidebar), 0.55), var(--spice-main) 60%); + border-top: 0; + min-width: 518px; +} + +.main-buddyFeed-findFriendsButton { + color: var(--spice-sidebar-text); +} + +.main-connectBar-connectBar { + border-radius: 0 0 var(--main-corner-radius) var(--main-corner-radius); + border: 2px solid var(--spice-main); + border-top: 0; + background-color: var(--spice-button) !important; + color: var(--spice-text) !important; +} + +.Root__nav-bar { + height: 100%; + z-index: 1; + width: var(--sidebar-width) !important; +} + +.main-buddyFeed-buddyFeedRoot { + height: 100%; +} + +.main-buddyFeed-buddyFeedRoot .os-content { + padding-top: 0 !important; +} + +html, +.Root__nav-bar, +.main-buddyFeed-buddyFeed { + background-color: var(--spice-sidebar) !important; +} + +html { + background-position: center; + background-repeat: no-repeat; +} + +.Root__nav-bar .link-subtle, +.main-rootlist-rootlistItemLink:link, +.main-rootlist-rootlistItemLink:visited, +.main-rootlist-rootlistContent span, +.main-navBar-entryPoints span { + z-index: 1; + color: var(--spice-sidebar-text); +} + +.main-navBar-navBarItem svg { + width: 24px !important; + height: 24px !important; +} + +.main-navBar-navBarItem { + position: relative; + padding: 0px; +} + +#spicetify-show-list > * { + padding: 0 8px; +} + +.main-rootlist-statusIcons { + z-index: 1; + inset: 0; + position: absolute; + margin-left: 12px; + display: flex; + align-items: center; + justify-content: flex-start; + pointer-events: none; +} + +.main-rootlist-statusIcons .main-playButton-button { + width: 32px; + height: 32px; + margin: 0px; + color: var(--spice-sidebar); + background-color: rgba(var(--spice-rgb-main), 0.7); + border-radius: var(--sidebar-icons-border-radius); +} + +.main-rootlist-expandArrow { + position: absolute; + top: 20px; + right: 12px; + width: 16px; + height: 16px; + color: var(--spice-sidebar-text) !important; + background-color: var(--spice-button); + border-radius: 50%; + box-shadow: 0 0 0 2px var(--spice-sidebar); + opacity: 0; +} + +li.GlueDropTarget:hover .main-rootlist-expandArrow { + opacity: 1; +} + +html:not(.sidebar-hide-text) .main-rootlist-expandArrow { + opacity: 1; +} + +.main-rootlist-expandArrow::before { + font-size: 10px; + padding-bottom: 3px; +} + +html.sidebar-hide-text .main-rootlist-expandArrow { + right: 4px; +} + +html.sidebar-hide-text .main-navBar-navBarItem span, +html.sidebar-hide-text .main-rootlist-rootlistContent span, +html.sidebar-hide-text .main-rootlist-rootlistItem span, +html.sidebar-hide-text .GlueDropTarget span { + display: none; +} + +.main-rootlist-rootlist { + margin-top: 0; +} + +.main-rootlist-rootlist::before, +.main-rootlist-rootlist::after { + z-index: 10; + position: absolute; + content: ""; + left: 0px; + right: 0px; + pointer-events: none; + transition: height 0.5s ease; +} + +.main-rootlist-rootlist.no-top-shadow::before { + height: 0px; +} + +.main-rootlist-rootlist.no-bottom-shadow::after { + height: 0px; +} + +.main-rootlist-rootlist::before { + top: 0px; + height: 5%; + background: linear-gradient(to bottom, var(--spice-sidebar) 10%, transparent); +} + +.main-rootlist-rootlist::after { + bottom: 0px; + height: 15%; + background: linear-gradient(to top, var(--spice-sidebar) 10%, transparent); +} + +.Root__nav-bar .os-scrollbar-vertical, +.main-buddyFeed-buddyFeedRoot .os-scrollbar-vertical { + display: none; +} + +.x-toggle-indicatorWrapper { + background-color: transparent; + backdrop-filter: invert(0.25); +} + +input:checked ~ .x-toggle-indicatorWrapper { + background-color: rgba(var(--spice-rgb-button), 0.4); +} + +input:hover:checked ~ .x-toggle-indicatorWrapper { + background-color: rgba(var(--spice-rgb-button), 0.5) !important; +} + +input:hover:not([disabled]):not(:active) ~ .x-toggle-indicatorWrapper { + background-color: transparent; + backdrop-filter: invert(0.4); +} + +/** */ +.main-topBar-historyButtons .main-topBar-button { + background-color: unset; + width: 24px; + height: 24px; +} + +.main-topBar-historyButtons svg { + color: var(--spice-button); + fill: var(--spice-button); +} + +.playback-bar__progress-time, +.playback-bar__progress-time-elapsed, +.main-playbackBarRemainingTime-container { + display: none; +} + +.playback-bar { + position: absolute; + width: var(--main-view-width); + left: var(--sidebar-width); + bottom: calc(var(--main-gap) + var(--bar-height) - 12px / 2); +} + +.Root.is-connectBarVisible .playback-bar { + bottom: calc(var(--main-gap) + var(--bar-height) + 24px - 12px / 2); +} + +.main-nowPlayingWidget-coverArt .cover-art { + width: var(--bar-cover-art-size) !important; + height: var(--bar-cover-art-size) !important; +} + +.player-controls__buttons { + margin-bottom: 0; + width: 192px; +} + +.progress-bar { + --progress-bar-height: 2px; + --fg-color: var(--spice-button); + --bg-color: rgba(var(--spice-rgb-text), 0.2); +} + +.progress-bar__slider { + display: block !important; + opacity: 0; + transition: opacity 0.2s ease; +} + +.progress-bar:hover .progress-bar__slider, +.progress-bar:active .progress-bar__slider { + opacity: 1; +} + +.progress-bar:not(:active) .x-progressBar-progressBarBg > div:first-child > div { + transition: transform var(--playbar-movement-anim-speed) ease; +} + +.progress-bar:not(:active) .progress-bar__slider { + transition-property: left, opacity; +} + +.playback-bar .prog-tooltip { + position: absolute; + min-width: 100px; + top: -35px; + left: 50%; + transform: translateX(calc(-50%)); + padding: 0 5px; + border-radius: 4px; + text-align: center; + color: var(--spice-text); + background-color: var(--spice-button); + pointer-events: none; +} + +.playback-bar:not(:active) .prog-tooltip { + transition: transform var(--playbar-movement-anim-speed) ease; +} + +.minimal-player .player-controls__buttons { + width: 120px; + gap: 0px; +} + +.minimal-player .player-controls__left, +.minimal-player .player-controls__right { + --button-size: 16px !important; + gap: 0px; +} + +.minimal-player .volume-bar { + flex: 0 1 70px; +} +.extra-minimal-player .player-controls__buttons { + width: 64px; +} +.extra-minimal-player .main-shuffleButton-button, +.extra-minimal-player .main-repeatButton-button, +.extra-minimal-player .ExtraControls__connect-device-picker, +.extra-minimal-player .volume-bar .progress-bar-wrapper { + display: none; +} +.extra-minimal-player .volume-bar { + flex: 0 0 32px; +} + +.main-trackInfo-name { + font-weight: 500; +} + +.main-topBar-topbarContent .main-playButton-PlayButton { + --size: 35px !important; +} + +.main-entityHeader-image { + border-radius: 5px; +} + +.main-entityHeader-metaDataText, +.main-duration-container { + color: var(--spice-subtext); +} + +/** Sidebar */ +.main-rootlist-rootlist .os-content { + padding: 0 0 8px 0 !important; +} + +.main-rootlist-rootlistDividerContainer { + display: none; +} + +.main-rootlist-rootlistItem a { + align-items: center; + border-radius: 4px; + display: flex; + height: 56px; + padding: 0 12px; +} + +img.playlist-picture { + width: 32px; + height: 32px; + flex: 0 0 32px; + background-size: cover; + background-position: center; + border-radius: var(--sidebar-icons-border-radius); +} + +.main-rootlist-rootlistItem a span { + margin-left: 24px; +} + +.main-rootlist-rootlistItem { + padding-left: calc(var(--indentation) * var(--left-sidebar-item-indentation-width)); + padding-right: 0; + transition: padding-left 0.5s ease; +} + +html.sidebar-hide-text .main-rootlist-rootlistItem { + padding: 0; +} + +.main-rootlist-dropIndicator { + background: var(--spice-selected-row); + height: 2px; +} + +.main-rootlist-rootlistPlaylistsScrollNode { + padding: 0px; +} + +.main-collectionLinkButton-icon, +.main-createPlaylistButton-icon { + margin: 0px; +} + +.main-navBar-navBarLink, +.main-collectionLinkButton-collectionLinkButton, +.main-createPlaylistButton-button { + gap: 24px; + height: 56px; +} + +li.GlueDropTarget { + padding: 0 8px; +} + +/** OS-specific window controls dodge */ +#main:not([top-bar^="none"]) { + .main-topBar-background, + .main-topBar-topbarContent { + -webkit-app-region: no-drag; + } +} + +#main:not([top-bar="none-padding"]) { + .main-navBar-navBar, + .main-buddyFeed-header, + .main-navBar-entryPoints { + padding-top: 8px !important; + } +} + +#main:not([top-bar^="none"]) { + padding-top: 31px; +} + +#main:not([top-bar^="none"])::before { + z-index: 999; + content: ""; + position: absolute; + top: 0px; + left: 0px; + right: 135px; + height: 31px; + background-color: rgba(0, 0, 0, 0.53); + -webkit-app-region: drag; + pointer-events: none; +} + +#main[top-bar="solid"]::before { + right: 0px; + background-color: black; +} + +#main[top-bar="none-padding"] .spotify__os--is-windows .main-navBar-navBar { + padding-top: 24px; +} + +#main[top-bar="none-padding"] .spotify__container--is-desktop:not(.fullscreen).spotify__os--is-windows .main-navBar-entryPoints { + padding-top: 22px; +} + +#main[top-bar="none-padding"] .spotify__os--is-windows .main-buddyFeed-header { + padding-top: 32px; +} + +#main[top-bar="none-padding"] .spotify__container--is-desktop.spotify__os--is-windows[dir="ltr"] .Root__top-bar + .main-buddyFeed-buddyFeedRoot .main-topBar-container { + padding-right: 167px; +} + +.main-topBar-container { + max-width: unset; +} + +/** Custom elements */ +.dribs-playlist-list { + padding-bottom: 86px; +} + +#dribbblish-back-shadow { + position: fixed; + width: var(--main-view-width); + height: calc(var(--main-view-height) + var(--bar-height)); + box-shadow: 0 0 10px 3px #0000003b; + border-radius: var(--main-corner-radius); + z-index: 2; + pointer-events: none; +} + +/** Rearrange player bar */ +.main-nowPlayingBar-left { + order: 1; + flex: 1; + width: auto; + min-width: 0 !important; +} + +.main-nowPlayingBar-center { + order: 0; + flex: 1; + width: auto; + min-width: unset !important; +} + +.main-nowPlayingBar-right { + order: 2; + flex: 1; + width: auto; + min-width: unset !important; +} + +.main-nowPlayingWidget-nowPlaying { + justify-content: center; +} + +.player-controls { + justify-content: flex-start; + flex-direction: row; +} + +.main-playPauseButton-button { + background-color: transparent; +} + +.main-playPauseButton-button svg { + width: 32px !important; + height: 32px !important; + color: var(--spice-button); +} + +/* Spotify style player bar */ +#main[player-controls="spotify"] .main-nowPlayingBar-left { + order: 0; +} + +#main[player-controls="spotify"] .main-nowPlayingBar-center { + order: 1; +} + +#main[player-controls="spotify"] .main-nowPlayingWidget-nowPlaying { + justify-content: left; +} + +#main[player-controls="spotify"] .player-controls { + justify-content: center; +} + +/** Main container */ +.contentSpacing, +.artist-artistDiscography-headerContainer { + padding-left: 64px; + padding-right: 64px; +} + +@media (min-width: 1024px) { + .contentSpacing, + .artist-artistDiscography-headerContainer { + padding-left: 128px; + padding-right: 128px; + } +} + +.main-collectionLinkButton-collectionLinkButton .main-collectionLinkButton-icon, +.main-collectionLinkButton-collectionLinkButton .main-collectionLinkButton-collectionLinkText, +.main-createPlaylistButton-button { + opacity: 1; +} + +.main-collectionLinkButton-collectionLinkText, +.main-createPlaylistButton-text, +.main-navBar-navBarLink > span { + font-size: 14px; + font-weight: 400; + letter-spacing: normal; + line-height: 20px; + text-transform: none; +} + +.main-likedSongsButton-likedSongsIcon, +.main-yourEpisodesButton-yourEpisodesIcon, +.main-createPlaylistButton-createPlaylistIcon { + background: unset !important; +} + +.main-createPlaylistButton-icon, +.main-collectionLinkButton-icon, +.main-createPlaylistButton-icon { + height: 40px; +} + +.main-likedSongsButton-likedSongsIcon svg, +.main-yourEpisodesButton-yourEpisodesIcon svg, +.main-createPlaylistButton-createPlaylistIcon svg { + fill: var(--spice-sidebar-text); + width: 32px; + height: 32px; +} +.main-yourEpisodesButton-yourEpisodesIcon svg path { + fill: var(--spice-sidebar-text); +} + +/** Grid */ +.Root__top-container { + grid-template-areas: + "nav-bar main-view buddy-feed" + "nav-bar now-playing-bar buddy-feed"; + padding: var(--main-gap) 0; +} + +html:not(.buddyfeed-visible) .Root__top-container { + padding-right: var(--main-gap); +} + +/** Minimal profile button */ +span.main-userWidget-displayName, +.main-userWidget-box svg { + display: none; +} + +/** Sidebar config */ +#dribs-hidden-list { + background-color: rgba(var(--spice-rgb-main), 0.3); +} + +#dribs-sidebar-config { + position: relative; + width: 100%; + height: 0; + display: flex; + justify-content: space-evenly; + align-items: center; + top: -30px; + left: 0; +} + +#dribs-sidebar-config button { + min-width: 60px; + border-radius: 3px; + background-color: var(--spice-main); + color: var(--spice-text); + border: 1px solid var(--spice-text); +} +#dribs-sidebar-config button:disabled { + color: var(--spice-button-disabled); +} + +.main-navBar-entryPoints { + --left-sidebar-padding-left: 24px; + --left-sidebar-padding-right: 24px; + + padding: 0px 8px; +} + +.main-navBar-navBar .main-rootlist-wrapper > div:nth-child(2), +.main-navBar-entryPoints, +#spicetify-show-list, +.main-rootlist-rootlistContent .os-content { + display: flex; + flex-direction: column; + gap: 5px; +} + +#spicetify-show-list:empty { + display: none; +} + +.main-rootlist-wrapper > div:nth-child(2) > li img, +.main-navBar-navBarLink > .icon { + z-index: 1; +} + +.main-collectionLinkButton-collectionLinkButton, +.main-createPlaylistButton-button { + position: relative; +} + +.main-navBar-navBarLink::before, +.main-collectionLinkButton-collectionLinkButton::before, +.main-createPlaylistButton-button::before, +.main-rootlist-rootlistItemLink::before { + content: ""; + position: absolute; + width: 100%; + inset: 0px; + left: -200%; + opacity: 0.4; + background-color: black; + border-radius: var(--image-radius); + pointer-events: none; + transition: all calc(var(--sidebar-icons-hover-animation) * 0.2s) ease; + transition-property: left, opacity; +} + +.main-navBar-navBarLink:hover::before, +.main-collectionLinkButton-collectionLinkButton:hover::before, +.main-createPlaylistButton-button:hover::before, +.main-rootlist-rootlistItemLink:hover::before, +.main-navBar-navBarLinkActive::before, +.main-collectionLinkButton-selected::before { + left: 0px; +} + +.main-navBar-navBarLinkActive:hover::before, +.main-collectionLinkButton-selected:hover::before { + opacity: 0.6; +} + +.main-rootlist-rootlist .main-navBar-navBarItem:hover::before { + margin: 0 8px; +} + +.main-navBar-navBarLinkActive, +.main-collectionLinkButton-selected { + background-color: transparent; +} + +.main-navBar-navBar a:hover, +.main-navBar-navBar a:hover span { + color: var(--spice-sidebar-text) !important; +} + +div.GlueDropTarget.personal-library { + padding: 0px; +} +div.GlueDropTarget.personal-library > * { + padding: 0 16px; + height: 56px; + border-radius: 4px; +} + +div.GlueDropTarget.personal-library > *.active { + background: transparent; +} + +/** Big cover, small cover */ +.main-coverSlotExpanded-container { + position: fixed; + z-index: 2; + width: 250px; + height: 250px; + bottom: calc(var(--main-gap) + var(--bar-height) + 10px); + left: calc(var(--sidebar-width) + 10px); +} + +.Root.is-connectBarVisible .main-coverSlotExpanded-container { + bottom: calc(var(--main-gap) + var(--bar-height) + 24px + 10px); +} + +html.right-expanded-cover .main-coverSlotExpanded-container { + right: calc(var(--main-gap) + 10px); + left: unset; +} + +html.right-expanded-cover.buddyfeed-visible .main-coverSlotExpanded-container { + right: calc(var(--main-gap) + var(--buddy-feed-width) + 10px); + left: unset; +} + +.main-coverSlotExpanded-container img { + border-radius: 4px; +} + +.cover-art { + border-radius: 4px; +} + +.main-nowPlayingWidget-coverExpanded .main-coverSlotCollapsed-container { + opacity: 0; + width: 10px; + pointer-events: none; +} + +.main-nowPlayingWidget-coverExpanded { + transform: translateX(-27px); +} + +/** Mini dribbblish */ +.x-categoryCard-CategoryCard > div { + background-color: var(--spice-main); + width: calc(100% - 14px); + height: calc(100% - 6px); + margin: 3px 10px; + border-radius: 5px; +} + +.x-categoryCard-CategoryCard { + height: 100px; +} + +.x-categoryCard-image { + width: 50px !important; + height: 50px !important; +} + +.x-heroCategoryCard-HeroCategoryCard > div { + background-color: var(--spice-main); + width: calc(100% - 20px); + height: calc(100% - 6px); + margin: 3px 16px; + border-radius: 5px; +} + +.main-dropDown-dropDown, +.x-sortBox-sortDropdown { + background-color: rgba(var(--spice-rgb-selected-row), 0.4) !important; + color: var(--spice-subtext); +} + +.main-dropDown-dropDown:hover, +.x-sortBox-sortDropdown:hover { + background-color: rgba(var(--spice-rgb-selected-row), 0.6) !important; +} + +.connect-device-list-item:focus, +.connect-device-list-item:hover { + background-color: rgba(var(--spice-rgb-selected-row), 0.3); +} + +.bookmark-filter { + color: var(--spice-main) !important; +} + +/* 1.1.56 */ +.main-navBar-navBar { + width: var(--sidebar-width) !important; +} + +.main-entityHeader-container.main-entityHeader-nonWrapped { + padding-left: 64px; + padding-right: 64px; +} + +@media (min-width: 1024px) { + .main-entityHeader-container.main-entityHeader-nonWrapped { + padding-left: 128px; + padding-right: 128px; + } +} + +.main-userWidget-dropDownMenu > li > button { + color: rgba(var(--spice-rgb-selected-row), 0.7); + padding-left: 8px; + text-decoration: none; +} +.main-userWidget-dropDownMenu > li > button:hover, +.main-userWidget-dropDownMenu > li > button:focus { + color: var(--spice-text); +} + +.main-userWidget-dropDownMenu svg { + position: unset; +} +.main-userWidget-dropDownMenu > li svg { + position: absolute; +} +.main-buddyFeed-buddyFeed.main-buddyFeed-buddyFeed-expanded { + z-index: 4; +} + +.main-actionBar-ActionBarRow button:not(.main-playButton-primary) { + color: var(--spice-subtext); +} + +/* explicit icon */ +.main-tag-container { + background-color: var(--spice-text); +} +/* progressbar tooltip text color */ +.playback-bar .prog-tooltip { + color: var(--spice-sidebar-text) !important; +} + +/* edit button of CustomApps */ +.reddit-sort-container button.switch, +.new-releases-header button.switch, +.lyrics-tabBar-header button.switch { + background-color: rgba(var(--spice-rgb-subtext), 0.15) !important; + color: var(--spice-text); +} + +.reddit-sort-container button.switch:hover, +.new-releases-header button.switch:hover, +.lyrics-tabBar-header button.switch:hover { + background-color: rgba(var(--spice-rgb-subtext), 0.3) !important; +} + +.lyrics-lyricsContainer-LyricsBackground { + background: linear-gradient(180deg, transparent 0px, transparent 60px, var(--lyrics-color-background) 61px) !important; +} + +/* big cover opacity on hover */ +.main-coverSlotExpanded-container:hover .cover-art, +.main-coverSlotExpanded-container:hover img { + opacity: 0.5; +} + +.collection-collectionEntityHeroCard-likedSongs, +.collection-collectionEntityHeroCard-likedSongs .main-cardHeader-link, +.collection-collectionEntityHeroCard-likedSongs .collection-collectionEntityHeroCard-descriptionContainer, +.x-heroCategoryCard-heroTitle, +.main-rootlist-expandArrow:focus, +.main-rootlist-expandArrow:hover, +.main-rootlist-textWrapper:focus, +.main-rootlist-textWrapper:hover, +.main-contextMenu-menuHeading, +.main-contextMenu-menuItemButton, +.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):focus, +.main-contextMenu-menuItemButton:not(.main-contextMenu-disabled):hover { + color: var(--spice-sidebar-text) !important; +} + +/* translucent background cover */ +.Root__top-container::before { + content: ""; + background-image: var(--image-url); + background-repeat: no-repeat; + background-size: cover; + background-position: center center; + position: fixed; + display: block; + top: 0; + left: 0; + right: 0; + bottom: 0; + filter: blur(15px); + pointer-events: none; + backface-visibility: hidden; + will-change: transform; + opacity: calc(0.07 + 0.03 * var(--is_light, 0)); + z-index: +3; + transition: background-image var(--song-transition-speed) linear; +} + +// Fix lyrics-plus having a scrollbar when top-bar is `solid` or `transparent` +.lyrics-lyricsContainer-LyricsContainer { + height: 100% !important; +} +.main-view-container__scroll-node-child { + height: 100%; +} + +canvas[width="250"][height="250"] { + display: none; +} diff --git a/uninstall.ps1 b/uninstall.ps1 index 3b4fd16..94abda4 100644 --- a/uninstall.ps1 +++ b/uninstall.ps1 @@ -1,4 +1,4 @@ -spicetify config current_theme " " extensions dribbblish.js- extensions dribbblish-dynamic.js- extensions Vibrant.min.js- +spicetify config current_theme " " extensions dribbblish-dynamic.js- $spicePath = spicetify -c | Split-Path $configFile = Get-Content "$spicePath\config-xpui.ini" diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..0c630d2 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,56 @@ +const webpack = require("webpack"); +const CopyPlugin = require("copy-webpack-plugin"); +const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const path = require("path"); + +/** @type {import('webpack').Configuration} */ +module.exports = { + entry: [path.resolve(__dirname, "./src/js/main.js"), path.resolve(__dirname, "./src/styles/main.scss")], + output: { + path: path.resolve(__dirname, "dist"), + filename: "dribbblish-dynamic.js" + }, + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + use: [] + }, + { + test: /\.scss$/, + exclude: /node_modules/, + type: "asset/resource", + generator: { + filename: "user.css" + }, + use: [ + { + loader: "sass-loader", + options: { + sourceMap: true + } + } + ] + } + ] + }, + devtool: "inline-source-map", + plugins: [ + new CleanWebpackPlugin({ + protectWebpackAssets: false, + cleanAfterEveryBuildPatterns: ["*.LICENSE.txt"] + }), + new webpack.DefinePlugin({ + "process.env.DRIBBBLISH_VERSION": JSON.stringify(process.env.DRIBBBLISH_VERSION || "Dev") + }), + new CopyPlugin({ + patterns: [{ from: "src/assets", to: "assets" }, { from: "src/color.ini" }] + }) + ], + optimization: { + minimize: true, + minimizer: [`...`, new CssMinimizerPlugin()] + } +};