add basic event functionality and a loader at startup

This commit is contained in:
Send_Nukez 2021-11-21 23:54:20 +01:00 committed by GitHub Action
parent 83b2801a36
commit f0a562f70a
11 changed files with 805 additions and 610 deletions

View file

@ -1,3 +1,6 @@
Added:
- A spinning loader at startup while spotify is not ready
Fixed: Fixed:
- Checking for update every 10 Minutes not working - Checking for update every 10 Minutes not working
- Background album art is cut off (#116) - Background album art is cut off (#116)

68
src/js/Dribbblish.js Normal file
View file

@ -0,0 +1,68 @@
import ConfigMenu from "./ConfigMenu";
import Info from "./Info";
import Loader from "./Loader";
export default class Dribbblish {
/**
* @typedef {"ready"} Event
*/
/**
* @callback listener
* @param {any} [data]
* @returns {void}
*/
/** @type {ConfigMenu} */
config;
/** @type {Info} */
info;
/** @type {Loader} */
loader;
/** @type {Object.<string, listener[]>} */
#listeners = {};
/** @type {Boolean} */
#ready = false;
constructor() {
this.config = new ConfigMenu();
this.info = new Info();
this.loader = new Loader();
const interval = setInterval(() => {
if (document.querySelector("#main") == null || Spicetify?.showNotification == undefined || !this.info.isReady()) return;
this.#ready = true;
this.emit("ready");
clearInterval(interval);
}, 200);
}
/**
* @param {Event} event
* @param {any} data
*/
emit(event, data) {
this.#listeners[event]?.forEach((listener) => listener(data));
}
/**
* @param {Event} event
* @param {listener} listener
*/
on(event, listener) {
this.#listeners[event] = [...(this.#listeners[event] ?? []), listener];
if (event == "ready" && this.#ready) listener();
}
/**
* @param {Event} event
* @param {listener} listener
*/
off(event, listener) {
this.#listeners = this.#listeners[event].filter((f) => f != listener);
}
}

View file

@ -43,6 +43,10 @@ export default class Info {
}); });
} }
isReady() {
return this.#ready;
}
/** /**
* @param {String} key * @param {String} key
* @param {DribbblishInfo} info * @param {DribbblishInfo} info

25
src/js/Loader.js Normal file
View file

@ -0,0 +1,25 @@
export default class Loader {
/** @type {HTMLDivElement} */
#container;
constructor() {
this.#container = document.createElement("div");
this.#container.id = "dribbblish-loader";
this.#container.innerHTML = `
<svg width="65px" height="65px" viewBox="0 0 66 66" xmlns="http://www.w3.org/2000/svg">
<circle fill="none" stroke-width="6" stroke-linecap="round" cx="33" cy="33" r="30"></circle>
</svg>
`;
document.body.appendChild(this.#container);
}
show(text) {
this.#container.setAttribute("text", text ?? "");
this.#container.setAttribute("active", "");
}
hide() {
this.#container.removeAttribute("active");
}
}

View file

@ -60,3 +60,17 @@ export function htmlToNode(htmlStr) {
div.innerHTML = htmlStr.trim(); div.innerHTML = htmlStr.trim();
return div.firstChild; return div.firstChild;
} }
export function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
export function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export function randomFromArray(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}

View file

@ -4,9 +4,8 @@ import chroma from "chroma-js";
import $ from "jquery"; import $ from "jquery";
import moment from "moment"; import moment from "moment";
import { waitForElement, copyToClipboard, capitalizeFirstLetter, getClosestToNum } from "./Util"; import { waitForElement, copyToClipboard, capitalizeFirstLetter, getClosestToNum, randomFromArray } from "./Util";
import ConfigMenu from "./ConfigMenu"; import { default as _Dribbblish } from "./Dribbblish";
import Info from "./Info";
import "./Folders"; import "./Folders";
import iconArrowDown from "icon/arrow-down"; import iconArrowDown from "icon/arrow-down";
@ -14,15 +13,20 @@ import iconCode from "icon/code";
import iconWifiSlash from "icon/wifi-slash"; import iconWifiSlash from "icon/wifi-slash";
import iconCog from "icon/cog"; import iconCog from "icon/cog";
const Dribbblish = {
config: new ConfigMenu(),
info: new Info()
};
const colorThief = new ColorThief();
// To expose to external scripts // To expose to external scripts
const Dribbblish = new _Dribbblish();
window.Dribbblish = Dribbblish; window.Dribbblish = Dribbblish;
Dribbblish.config.register({ const colorThief = new ColorThief();
// In the future maybe have some useful info here
const loadingHints = ["Getting things ready...", "Starting up...", "Just one moment..."];
Dribbblish.loader.show(randomFromArray(loadingHints));
Dribbblish.on("ready", () => {
setTimeout(() => Dribbblish.loader.hide(), 3000);
Dribbblish.config.register({
type: "checkbox", type: "checkbox",
key: "openSettingsInfo", key: "openSettingsInfo",
name: "Open Settings Icon", name: "Open Settings Icon",
@ -39,18 +43,18 @@ Dribbblish.config.register({
tooltip: "Open Dribbblish Settings", tooltip: "Open Dribbblish Settings",
onClick: () => Dribbblish.config.open() onClick: () => Dribbblish.config.open()
}) })
}); });
Dribbblish.config.register({ Dribbblish.config.register({
type: "checkbox", type: "checkbox",
key: "rightBigCover", key: "rightBigCover",
name: "Right expanded cover", name: "Right expanded cover",
description: "Have the expanded cover Image on the right instead of on the left", description: "Have the expanded cover Image on the right instead of on the left",
defaultValue: true, defaultValue: true,
onChange: (val) => $("html").toggleClass("right-expanded-cover", val) onChange: (val) => $("html").toggleClass("right-expanded-cover", val)
}); });
Dribbblish.config.register({ Dribbblish.config.register({
area: "Sidebar", area: "Sidebar",
type: "checkbox", type: "checkbox",
key: "roundSidebarIcons", key: "roundSidebarIcons",
@ -58,9 +62,9 @@ Dribbblish.config.register({
description: "If the Sidebar Icons should be round instead of square", description: "If the Sidebar Icons should be round instead of square",
defaultValue: false, defaultValue: false,
onChange: (val) => $("html").css("--sidebar-icons-border-radius", val ? "50vh" : "var(--image-radius)") onChange: (val) => $("html").css("--sidebar-icons-border-radius", val ? "50vh" : "var(--image-radius)")
}); });
Dribbblish.config.register({ Dribbblish.config.register({
area: "Animations & Transitions", area: "Animations & Transitions",
type: "checkbox", type: "checkbox",
key: "sidebarHoverAnimation", key: "sidebarHoverAnimation",
@ -68,9 +72,9 @@ Dribbblish.config.register({
description: "If the Sidebar Icons should have an animated background on hover", description: "If the Sidebar Icons should have an animated background on hover",
defaultValue: true, defaultValue: true,
onChange: (val) => $("html").css("--sidebar-icons-hover-animation", val ? "1" : "0") onChange: (val) => $("html").css("--sidebar-icons-hover-animation", val ? "1" : "0")
}); });
Dribbblish.config.register({ Dribbblish.config.register({
area: "Sidebar", area: "Sidebar",
type: "number", type: "number",
key: "sidebarGapLeft", key: "sidebarGapLeft",
@ -81,9 +85,9 @@ Dribbblish.config.register({
min: 0 min: 0
}, },
onChange: (val) => $("html").css("--sidebar-gap-left", `${val}px`) onChange: (val) => $("html").css("--sidebar-gap-left", `${val}px`)
}); });
Dribbblish.config.register({ Dribbblish.config.register({
area: "Sidebar", area: "Sidebar",
type: "number", type: "number",
key: "sidebarGapRight", key: "sidebarGapRight",
@ -94,9 +98,9 @@ Dribbblish.config.register({
min: 0 min: 0
}, },
onChange: (val) => $("html").css("--sidebar-gap-right", `${val}px`) onChange: (val) => $("html").css("--sidebar-gap-right", `${val}px`)
}); });
waitForElement([".main-nowPlayingBar-container"], ([container]) => { waitForElement([".main-nowPlayingBar-container"], ([container]) => {
Dribbblish.config.register({ Dribbblish.config.register({
area: "Playbar", area: "Playbar",
type: "checkbox", type: "checkbox",
@ -106,9 +110,8 @@ waitForElement([".main-nowPlayingBar-container"], ([container]) => {
defaultValue: true, defaultValue: true,
onChange: (val) => $(container).toggleClass("with-shadow", val) onChange: (val) => $(container).toggleClass("with-shadow", val)
}); });
}); });
waitForElement(["#main"], () => {
Dribbblish.config.register({ Dribbblish.config.register({
type: "select", type: "select",
data: { none: "None", "none-padding": "None (With Top Padding)", solid: "Solid", transparent: "Transparent" }, data: { none: "None", "none-padding": "None (With Top Padding)", solid: "Solid", transparent: "Transparent" },
@ -152,9 +155,8 @@ waitForElement(["#main"], () => {
defaultValue: false, defaultValue: false,
onChange: (val) => $("#main").attr("hide-ads", val) onChange: (val) => $("#main").attr("hide-ads", val)
}); });
});
waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => { waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(2) > :first-child", "#spicetify-show-list"], ([rootlist]) => {
function checkSidebarPlaylistScroll() { 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 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; const bottomDist = document.querySelector(".main-rootlist-wrapper > :nth-child(2) > :last-child").getBoundingClientRect().bottom - rootlist.getBoundingClientRect().bottom;
@ -183,15 +185,15 @@ waitForElement([".main-rootlist-rootlist", ".main-rootlist-wrapper > :nth-child(
c++; c++;
}, 50); }, 50);
}); });
}); });
waitForElement([".Root__main-view"], ([mainView]) => { waitForElement([".Root__main-view"], ([mainView]) => {
const shadow = document.createElement("div"); const shadow = document.createElement("div");
shadow.id = "dribbblish-back-shadow"; shadow.id = "dribbblish-back-shadow";
mainView.prepend(shadow); mainView.prepend(shadow);
}); });
waitForElement([".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutResizer__resize-bar input"], ([resizer]) => { waitForElement([".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutResizer__resize-bar input"], ([resizer]) => {
const observer = new MutationObserver(updateVariable); const observer = new MutationObserver(updateVariable);
observer.observe(resizer, { attributes: true, attributeFilter: ["value"] }); observer.observe(resizer, { attributes: true, attributeFilter: ["value"] });
function updateVariable() { function updateVariable() {
@ -201,9 +203,9 @@ waitForElement([".Root__nav-bar .LayoutResizer__input, .Root__nav-bar .LayoutRes
$("html").css("--sidebar-width", `${value}px`); $("html").css("--sidebar-width", `${value}px`);
} }
updateVariable(); updateVariable();
}); });
waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) => { waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) => {
const observer = new ResizeObserver(updateVariable); const observer = new ResizeObserver(updateVariable);
observer.observe(resizeHost); observer.observe(resizeHost);
function updateVariable([event]) { function updateVariable([event]) {
@ -212,9 +214,9 @@ waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) =>
$("html").toggleClass("minimal-player", event.contentRect.width < 700); $("html").toggleClass("minimal-player", event.contentRect.width < 700);
$("html").toggleClass("extra-minimal-player", event.contentRect.width < 550); $("html").toggleClass("extra-minimal-player", event.contentRect.width < 550);
} }
}); });
(function Dribbblish() { (function Dribbblish() {
const progBar = document.querySelector(".playback-bar"); const progBar = document.querySelector(".playback-bar");
const root = document.querySelector(".Root"); const root = document.querySelector(".Root");
@ -253,11 +255,11 @@ waitForElement([".Root__main-view .os-resize-observer-host"], ([resizeHost]) =>
root.classList.remove("is-connectBarVisible"); root.classList.remove("is-connectBarVisible");
} }
}); });
})(); })();
/* Config settings */ /* Config settings */
Dribbblish.config.register({ Dribbblish.config.register({
area: "Animations & Transitions", area: "Animations & Transitions",
type: "slider", type: "slider",
key: "fadeDuration", key: "fadeDuration",
@ -271,10 +273,8 @@ Dribbblish.config.register({
suffix: "s" suffix: "s"
}, },
onChange: (val) => $("html").css("--song-transition-speed", `${val}s`) onChange: (val) => $("html").css("--song-transition-speed", `${val}s`)
}); });
// waitForElement because Spicetify is not initialized at startup
waitForElement(["#main"], () => {
Dribbblish.config.registerArea({ name: "About", order: 999 }); Dribbblish.config.registerArea({ name: "About", order: 999 });
Dribbblish.config.register({ Dribbblish.config.register({
@ -380,20 +380,19 @@ waitForElement(["#main"], () => {
data: "Open", data: "Open",
onChange: () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases", "_blank") onChange: () => window.open("https://github.com/JulienMaille/dribbblish-dynamic-theme/releases", "_blank")
}); });
});
/* js */ /* js */
async function getAlbumRelease(uri) { async function getAlbumRelease(uri) {
const info = await Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`); const info = await Spicetify.CosmosAsync.get(`hm://album/v1/album-app/album/${uri}/desktop`);
return { year: info.year, month: (info.month ?? 1) - 1, day: info.day ?? 1 }; return { year: info.year, month: (info.month ?? 1) - 1, day: info.day ?? 1 };
} }
function isLight(hex) { function isLight(hex) {
return chroma(hex).luminance() > 0.5; return chroma(hex).luminance() > 0.5;
} }
// From: https://stackoverflow.com/a/13763063/12126879 // From: https://stackoverflow.com/a/13763063/12126879
function getImageLightness(img) { function getImageLightness(img) {
var colorSum = 0; var colorSum = 0;
var canvas = document.createElement("canvas"); var canvas = document.createElement("canvas");
canvas.width = img.width; canvas.width = img.width;
@ -417,17 +416,17 @@ function getImageLightness(img) {
var brightness = Math.floor(colorSum / (img.width * img.height)); var brightness = Math.floor(colorSum / (img.width * img.height));
return brightness; return brightness;
} }
// parse to hex because "--spice-sidebar" is `rgb()` // parse to hex because "--spice-sidebar" is `rgb()`
let textColorBg = chroma($("html").css("--spice-main")).hex(); let textColorBg = chroma($("html").css("--spice-main")).hex();
function setRootColor(name, color) { function setRootColor(name, color) {
$("html").css(`--spice-${name}`, chroma(color).hex()); $("html").css(`--spice-${name}`, chroma(color).hex());
$("html").css(`--spice-rgb-${name}`, chroma(color).rgb().join(",")); $("html").css(`--spice-rgb-${name}`, chroma(color).rgb().join(","));
} }
function toggleDark(setDark) { function toggleDark(setDark) {
if (setDark === undefined) setDark = isLight(textColorBg); if (setDark === undefined) setDark = isLight(textColorBg);
$("html").css("--is_light", setDark ? 0 : 1); $("html").css("--is_light", setDark ? 0 : 1);
@ -440,9 +439,9 @@ function toggleDark(setDark) {
setRootColor("notification", setDark ? "#303030" : "#DDDDDD"); setRootColor("notification", setDark ? "#303030" : "#DDDDDD");
updateColors(false); updateColors(false);
} }
function checkDarkLightMode() { function checkDarkLightMode() {
const theme = Dribbblish.config.get("theme"); const theme = Dribbblish.config.get("theme");
if (theme == "time") { if (theme == "time") {
const start = 60 * parseInt(Dribbblish.config.get("darkModeOnTime").split(":")[0]) + parseInt(Dribbblish.config.get("darkModeOnTime").split(":")[1]); const start = 60 * parseInt(Dribbblish.config.get("darkModeOnTime").split(":")[0]) + parseInt(Dribbblish.config.get("darkModeOnTime").split(":")[1]);
@ -456,12 +455,12 @@ function checkDarkLightMode() {
else dark = start <= time && time < end; else dark = start <= time && time < end;
toggleDark(dark); toggleDark(dark);
} }
} }
// Run every Minute to check time and set dark / light mode // Run every Minute to check time and set dark / light mode
setInterval(checkDarkLightMode, 60000); setInterval(checkDarkLightMode, 60000);
Dribbblish.config.register({ Dribbblish.config.register({
area: "Theme", area: "Theme",
type: "select", type: "select",
key: "colorSelectionAlgorithm", key: "colorSelectionAlgorithm",
@ -537,12 +536,13 @@ Dribbblish.config.register({
] ]
} }
] ]
}); });
Dribbblish.config.register({ Dribbblish.config.register({
area: "Theme", area: "Theme",
type: "select", type: "select",
data: { dark: "Dark", light: "Light", time: "Based on Time" }, data: { dark: "Dark", light: "Light", time: "Based on Time" },
order: -1,
key: "theme", key: "theme",
name: "Theme", name: "Theme",
description: "Select Dark / Bright mode", description: "Select Dark / Bright mode",
@ -584,9 +584,9 @@ Dribbblish.config.register({
onChange: checkDarkLightMode onChange: checkDarkLightMode
} }
] ]
}); });
function updateColors(checkDarkMode = true, sideColHex) { function updateColors(checkDarkMode = true, sideColHex) {
if (sideColHex == undefined) return registerCoverListener(); if (sideColHex == undefined) return registerCoverListener();
let isLightBg = isLight(textColorBg); let isLightBg = isLight(textColorBg);
@ -616,9 +616,9 @@ function updateColors(checkDarkMode = true, sideColHex) {
setRootColor("sidebar-text", isLight(sideColHex) ? "#000000" : "#FFFFFF"); setRootColor("sidebar-text", isLight(sideColHex) ? "#000000" : "#FFFFFF");
if (checkDarkMode) checkDarkLightMode([textColHex, sideColHex]); if (checkDarkMode) checkDarkLightMode([textColHex, sideColHex]);
} }
async function songchange() { async function songchange() {
if (!document.querySelector(".main-trackInfo-container")) return setTimeout(songchange, 300); if (!document.querySelector(".main-trackInfo-container")) return setTimeout(songchange, 300);
try { try {
@ -674,11 +674,12 @@ async function songchange() {
$("html").css("--image-url", `url("${bgImage}")`); $("html").css("--image-url", `url("${bgImage}")`);
registerCoverListener(); registerCoverListener();
} }
Spicetify.Player.addEventListener("songchange", songchange); Spicetify.Player.addEventListener("songchange", songchange);
songchange();
async function pickCoverColor(img) { async function pickCoverColor(img) {
if (!img.currentSrc.startsWith("spotify:")) return; if (!img.currentSrc.startsWith("spotify:")) return;
$("html").css("--image-brightness", getImageLightness(img) / 255); $("html").css("--image-brightness", getImageLightness(img) / 255);
@ -718,10 +719,10 @@ async function pickCoverColor(img) {
} }
updateColors(false, color); updateColors(false, color);
} }
var coverListener; var coverListener;
function registerCoverListener() { function registerCoverListener() {
const img = document.querySelector(".main-image-image.cover-art-image"); const img = document.querySelector(".main-image-image.cover-art-image");
if (!img) return setTimeout(registerCoverListener, 250); // Check if image exists if (!img) return setTimeout(registerCoverListener, 250); // Check if image exists
if (!img.complete) return img.addEventListener("load", registerCoverListener); // Check if image is loaded if (!img.complete) return img.addEventListener("load", registerCoverListener); // Check if image is loaded
@ -741,11 +742,11 @@ function registerCoverListener() {
attributes: true, attributes: true,
attributeFilter: ["src"] attributeFilter: ["src"]
}); });
} }
registerCoverListener(); registerCoverListener();
// Check latest release every 10m // Check latest release every 10m
function checkForUpdate() { function checkForUpdate() {
fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest") fetch("https://api.github.com/repos/JulienMaille/dribbblish-dynamic-theme/releases/latest")
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
@ -754,13 +755,13 @@ function checkForUpdate() {
Dribbblish.info.set("dev", isDev ? { tooltip: "Dev build", icon: iconCode } : null); Dribbblish.info.set("dev", isDev ? { tooltip: "Dev build", icon: iconCode } : null);
}) })
.catch(console.error); .catch(console.error);
} }
setInterval(checkForUpdate, 10 * 60 * 1000); setInterval(checkForUpdate, 10 * 60 * 1000);
checkForUpdate(); checkForUpdate();
// Show "Offline info" // Show "Offline info"
window.addEventListener("offline", () => window.addEventListener("offline", () =>
Dribbblish.info.set("offline", { Dribbblish.info.set("offline", {
tooltip: "Offline", tooltip: "Offline",
icon: iconWifiSlash, icon: iconWifiSlash,
@ -770,7 +771,8 @@ window.addEventListener("offline", () =>
bg: "#ff2323" bg: "#ff2323"
} }
}) })
); );
window.addEventListener("online", () => Dribbblish.info.remove("offline")); window.addEventListener("online", () => Dribbblish.info.remove("offline"));
});
$("html").css("--warning_message", " "); $("html").css("--warning_message", " ");

View file

@ -27,12 +27,12 @@
backdrop-filter: blur(3px); backdrop-filter: blur(3px);
padding: 20px 15px; padding: 20px 15px;
border-radius: var(--main-corner-radius); border-radius: var(--main-corner-radius);
box-shadow: 0 0 10px 3px #0000003b;
display: flex; display: flex;
gap: 5px; gap: 5px;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@include spiceShadow();
.dribbblish-config-close { .dribbblish-config-close {
position: absolute; position: absolute;

View file

@ -16,6 +16,7 @@
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border-radius: var(--main-corner-radius); border-radius: var(--main-corner-radius);
box-shadow: 0px 0px 8px spiceColor("subtext", 0.1, 0.1); box-shadow: 0px 0px 8px spiceColor("subtext", 0.1, 0.1);
@include spiceShadow();
} }
.main-contextMenu-menuItem { .main-contextMenu-menuItem {

73
src/styles/Loader.scss Normal file
View file

@ -0,0 +1,73 @@
// From https://codepen.io/mrrocks/pen/EiplA
@use "sass:math";
$offset: 187;
$duration: 1.4s;
#dribbblish-loader {
z-index: 999999;
position: fixed;
inset: 0px;
display: flex;
align-items: center;
justify-content: center;
pointer-events: none;
opacity: 0;
transition: opacity 1s ease-in;
&[active] {
opacity: 1;
pointer-events: all;
}
&::before {
content: "";
position: absolute;
inset: 0px;
color: spiceColor("subtext");
background-color: spiceColor("main", 0.9, -0.1);
backdrop-filter: blur(10px);
}
&::after {
content: attr(text);
position: absolute;
bottom: 40%;
@include spiceFont("glue", 32px, "Bold");
}
svg {
animation: rotator $duration linear infinite;
circle {
stroke: spiceColor("sidebar");
stroke-dasharray: $offset;
stroke-dashoffset: 0;
transform-origin: center;
animation: dash $duration ease-in-out infinite;
}
}
}
@keyframes rotator {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(270deg);
}
}
@keyframes dash {
0% {
stroke-dashoffset: $offset;
}
50% {
stroke-dashoffset: math.div($offset, 4);
transform: rotate(135deg);
}
100% {
stroke-dashoffset: $offset;
transform: rotate(450deg);
}
}

View file

@ -2,3 +2,7 @@
@function lightOffset($n, $offset) { @function lightOffset($n, $offset) {
@return calc($n + $offset * var(--is_light)); @return calc($n + $offset * var(--is_light));
} }
@mixin spiceShadow() {
box-shadow: 0px 0px 8px spiceColor("subtext", 0.1, 0.1);
}

View file

@ -12,6 +12,7 @@
@import "NoAds"; @import "NoAds";
@import "Markdown"; @import "Markdown";
@import "Info"; @import "Info";
@import "Loader";
:root { :root {
--bar-height: 70px; --bar-height: 70px;