diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx deleted file mode 100644 index 32c78c364..000000000 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import "./spotifyStyles.css"; - -import { Settings } from "@api/Settings"; -import { classNameFactory } from "@api/Styles"; -import { Flex } from "@components/Flex"; -import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; -import { debounce } from "@shared/debounce"; -import { openImageModal } from "@utils/discord"; -import { classes, copyWithToast } from "@utils/misc"; -import { ContextMenuApi, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; - -import { SeekBar } from "./SeekBar"; -import { SpotifyStore, Track } from "./SpotifyStore"; - -const cl = classNameFactory("vc-spotify-"); - -function msToHuman(ms: number) { - const minutes = ms / 1000 / 60; - const m = Math.floor(minutes); - const s = Math.floor((minutes - m) * 60); - return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; -} - -function Svg(path: string, label: string) { - return () => ( - - - - ); -} - -// KraXen's icons :yesyes: -// from https://fonts.google.com/icons?icon.style=Rounded&icon.set=Material+Icons -// older material icon style, but still really good -const PlayButton = Svg("M8 6.82v10.36c0 .79.87 1.27 1.54.84l8.14-5.18c.62-.39.62-1.29 0-1.69L9.54 5.98C8.87 5.55 8 6.03 8 6.82z", "play"); -const PauseButton = Svg("M8 19c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2v10c0 1.1.9 2 2 2zm6-12v10c0 1.1.9 2 2 2s2-.9 2-2V7c0-1.1-.9-2-2-2s-2 .9-2 2z", "pause"); -const SkipPrev = Svg("M7 6c.55 0 1 .45 1 1v10c0 .55-.45 1-1 1s-1-.45-1-1V7c0-.55.45-1 1-1zm3.66 6.82l5.77 4.07c.66.47 1.58-.01 1.58-.82V7.93c0-.81-.91-1.28-1.58-.82l-5.77 4.07c-.57.4-.57 1.24 0 1.64z", "previous"); -const SkipNext = Svg("M7.58 16.89l5.77-4.07c.56-.4.56-1.24 0-1.63L7.58 7.11C6.91 6.65 6 7.12 6 7.93v8.14c0 .81.91 1.28 1.58.82zM16 7v10c0 .55.45 1 1 1s1-.45 1-1V7c0-.55-.45-1-1-1s-1 .45-1 1z", "next"); -const Repeat = Svg("M7 7h10v1.79c0 .45.54.67.85.35l2.79-2.79c.2-.2.2-.51 0-.71l-2.79-2.79c-.31-.31-.85-.09-.85.36V5H6c-.55 0-1 .45-1 1v4c0 .55.45 1 1 1s1-.45 1-1V7zm10 10H7v-1.79c0-.45-.54-.67-.85-.35l-2.79 2.79c-.2.2-.2.51 0 .71l2.79 2.79c.31.31.85.09.85-.36V19h11c.55 0 1-.45 1-1v-4c0-.55-.45-1-1-1s-1 .45-1 1v3z", "repeat"); -const Shuffle = Svg("M10.59 9.17L6.12 4.7c-.39-.39-1.02-.39-1.41 0-.39.39-.39 1.02 0 1.41l4.46 4.46 1.42-1.4zm4.76-4.32l1.19 1.19L4.7 17.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0L17.96 7.46l1.19 1.19c.31.31.85.09.85-.36V4.5c0-.28-.22-.5-.5-.5h-3.79c-.45 0-.67.54-.36.85zm-.52 8.56l-1.41 1.41 3.13 3.13-1.2 1.2c-.31.31-.09.85.36.85h3.79c.28 0 .5-.22.5-.5v-3.79c0-.45-.54-.67-.85-.35l-1.19 1.19-3.13-3.14z", "shuffle"); - -function Button(props: React.ButtonHTMLAttributes) { - return ( - - ); -} - -function CopyContextMenu({ name, path }: { name: string; path: string; }) { - const copyId = `spotify-copy-${name}`; - const openId = `spotify-open-${name}`; - - return ( - FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} - aria-label={`Spotify ${name} Menu`} - > - copyWithToast("https://open.spotify.com" + path)} - icon={LinkIcon} - /> - SpotifyStore.openExternal(path)} - icon={OpenExternalIcon} - /> - - ); -} - -function makeContextMenu(name: string, path: string) { - return (e: React.MouseEvent) => - ContextMenuApi.openContextMenu(e, () => ); -} - -function Controls() { - const [isPlaying, shuffle, repeat] = useStateFromStores( - [SpotifyStore], - () => [SpotifyStore.isPlaying, SpotifyStore.shuffle, SpotifyStore.repeat] - ); - - const [nextRepeat, repeatClassName] = (() => { - switch (repeat) { - case "off": return ["context", "repeat-off"] as const; - case "context": return ["track", "repeat-context"] as const; - case "track": return ["off", "repeat-track"] as const; - default: throw new Error(`Invalid repeat state ${repeat}`); - } - })(); - - // the 1 is using position absolute so it does not make the button jump around - return ( - - - - - - - - ); -} - -const seek = debounce((v: number) => { - SpotifyStore.seek(v); -}); - -function SpotifySeekBar() { - const { duration } = SpotifyStore.track!; - - const [storePosition, isSettingPosition, isPlaying] = useStateFromStores( - [SpotifyStore], - () => [SpotifyStore.mPosition, SpotifyStore.isSettingPosition, SpotifyStore.isPlaying] - ); - - const [position, setPosition] = useState(storePosition); - - useEffect(() => { - if (isPlaying && !isSettingPosition) { - setPosition(SpotifyStore.position); - const interval = setInterval(() => { - setPosition(p => p + 1000); - }, 1000); - - return () => clearInterval(interval); - } - }, [storePosition, isSettingPosition, isPlaying]); - - const onChange = (v: number) => { - if (isSettingPosition) return; - setPosition(v); - seek(v); - }; - - return ( -
- - {msToHuman(position)} - - - - {msToHuman(duration)} - -
- ); -} - - -function AlbumContextMenu({ track }: { track: Track; }) { - const volume = useStateFromStores([SpotifyStore], () => SpotifyStore.volume); - - return ( - FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })} - aria-label="Spotify Album Menu" - > - SpotifyStore.openExternal(`/album/${track.album.id}`)} - icon={OpenExternalIcon} - /> - openImageModal(track.album.image)} - icon={ImageIcon} - /> - ( - SpotifyStore.setVolume(v))} - /> - )} - /> - - ); -} - -function makeLinkProps(name: string, condition: unknown, path: string) { - if (!condition) return {}; - - return { - role: "link", - onClick: () => SpotifyStore.openExternal(path), - onContextMenu: makeContextMenu(name, path) - } satisfies React.HTMLAttributes; -} - -function Info({ track }: { track: Track; }) { - const img = track?.album?.image; - - const [coverExpanded, setCoverExpanded] = useState(false); - - const i = ( - <> - {img && ( - Album Image setCoverExpanded(!coverExpanded)} - onContextMenu={e => { - ContextMenuApi.openContextMenu(e, () => ); - }} - /> - )} - - ); - - if (coverExpanded && img) - return ( -
- {i} -
- ); - - return ( -
- {i} -
- - {track.name} - - {track.artists.some(a => a.name) && ( - - by  - {track.artists.map((a, i) => ( - - - {a.name} - - {i !== track.artists.length - 1 && {", "}} - - ))} - - )} - {track.album.name && ( - - on  - - {track.album.name} - - - )} -
-
- ); -} - -export function Player() { - const track = useStateFromStores( - [SpotifyStore], - () => SpotifyStore.track, - null, - (prev, next) => prev?.id ? (prev.id === next?.id) : prev?.name === next?.name - ); - - const device = useStateFromStores( - [SpotifyStore], - () => SpotifyStore.device, - null, - (prev, next) => prev?.id === next?.id - ); - - const isPlaying = useStateFromStores([SpotifyStore], () => SpotifyStore.isPlaying); - const [shouldHide, setShouldHide] = useState(false); - - // Hide player after 5 minutes of inactivity - - React.useEffect(() => { - setShouldHide(false); - if (!isPlaying) { - const timeout = setTimeout(() => setShouldHide(true), 1000 * 60 * 5); - return () => clearTimeout(timeout); - } - }, [isPlaying]); - - if (!track || !device?.is_active || shouldHide) - return null; - - const exportTrackImageStyle = { - "--vc-spotify-track-image": `url(${track?.album?.image?.url || ""})`, - } as React.CSSProperties; - - return ( -
- - - -
- ); -} diff --git a/src/plugins/spotifyControls/SeekBar.ts b/src/plugins/spotifyControls/SeekBar.ts deleted file mode 100644 index 8d6c8a30c..000000000 --- a/src/plugins/spotifyControls/SeekBar.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2025 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { LazyComponent } from "@utils/lazyReact"; -import { Slider } from "@webpack/common"; - -export const SeekBar = LazyComponent(() => { - const SliderClass = Slider.$$vencordGetWrappedComponent(); - - // Discord's Slider does not update `state.value` when `props.initialValue` changes if state.value is not nullish. - // We extend their class and override their `getDerivedStateFromProps` to update the value - return class SeekBar extends SliderClass { - static getDerivedStateFromProps(props: any, state: any) { - const newState = super.getDerivedStateFromProps!(props, state); - if (newState) { - newState.value = props.initialValue; - } - - return newState; - } - }; -}); diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts deleted file mode 100644 index 65ee2c154..000000000 --- a/src/plugins/spotifyControls/SpotifyStore.ts +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { Settings } from "@api/Settings"; -import { findByProps, findByPropsLazy, proxyLazyWebpack } from "@webpack"; -import { Flux, FluxDispatcher } from "@webpack/common"; - -export interface Track { - id: string; - name: string; - duration: number; - isLocal: boolean; - album: { - id: string; - name: string; - image: { - height: number; - width: number; - url: string; - }; - }; - artists: { - id: string; - href: string; - name: string; - type: string; - uri: string; - }[]; -} - -interface PlayerState { - accountId: string; - track: Track | null; - volumePercent: number, - isPlaying: boolean, - repeat: boolean, - position: number, - context?: any; - device?: Device; - - // added by patch - actual_repeat: Repeat; - shuffle: boolean; -} - -interface Device { - id: string; - is_active: boolean; -} - -type Repeat = "off" | "track" | "context"; - -// Don't wanna run before Flux and Dispatcher are ready! -export const SpotifyStore = proxyLazyWebpack(() => { - // For some reason ts hates extends Flux.Store - const { Store } = Flux; - - const SpotifySocket = findByProps("getActiveSocketAndDevice"); - const SpotifyAPI = findByPropsLazy("vcSpotifyMarker"); - - const API_BASE = "https://api.spotify.com/v1/me/player"; - - class SpotifyStore extends Store { - public mPosition = 0; - public _start = 0; - - public track: Track | null = null; - public device: Device | null = null; - public isPlaying = false; - public repeat: Repeat = "off"; - public shuffle = false; - public volume = 0; - - public isSettingPosition = false; - - public openExternal(path: string) { - const url = Settings.plugins.SpotifyControls.useSpotifyUris || Vencord.Plugins.isPluginEnabled("OpenInApp") - ? "spotify:" + path.replaceAll("/", (_, idx) => idx === 0 ? "" : ":") - : "https://open.spotify.com" + path; - - VencordNative.native.openExternal(url); - } - - // Need to keep track of this manually - public get position(): number { - let pos = this.mPosition; - if (this.isPlaying) { - pos += Date.now() - this._start; - } - return pos; - } - - public set position(p: number) { - this.mPosition = p; - this._start = Date.now(); - } - - prev() { - this._req("post", "/previous"); - } - - next() { - this._req("post", "/next"); - } - - setVolume(percent: number) { - this._req("put", "/volume", { - query: { - volume_percent: Math.round(percent) - } - - }).then(() => { - this.volume = percent; - this.emitChange(); - }); - } - - setPlaying(playing: boolean) { - this._req("put", playing ? "/play" : "/pause"); - } - - setRepeat(state: Repeat) { - this._req("put", "/repeat", { - query: { state } - }); - } - - setShuffle(state: boolean) { - this._req("put", "/shuffle", { - query: { state } - }).then(() => { - this.shuffle = state; - this.emitChange(); - }); - } - - seek(ms: number) { - if (this.isSettingPosition) return Promise.resolve(); - - this.isSettingPosition = true; - - return this._req("put", "/seek", { - query: { - position_ms: Math.round(ms) - } - }).catch((e: any) => { - console.error("[VencordSpotifyControls] Failed to seek", e); - this.isSettingPosition = false; - }); - } - - _req(method: "post" | "get" | "put", route: string, data: any = {}) { - if (this.device?.is_active) - (data.query ??= {}).device_id = this.device.id; - - const { socket } = SpotifySocket.getActiveSocketAndDevice(); - return SpotifyAPI[method](socket.accountId, socket.accessToken, { - url: API_BASE + route, - ...data - }); - } - } - - const store = new SpotifyStore(FluxDispatcher, { - SPOTIFY_PLAYER_STATE(e: PlayerState) { - store.track = e.track; - store.device = e.device ?? null; - store.isPlaying = e.isPlaying ?? false; - store.volume = e.volumePercent ?? 0; - store.repeat = e.actual_repeat || "off"; - store.shuffle = e.shuffle ?? false; - store.position = e.position ?? 0; - store.isSettingPosition = false; - store.emitChange(); - }, - SPOTIFY_SET_DEVICES({ devices }: { devices: Device[]; }) { - store.device = devices.find(d => d.is_active) ?? devices[0] ?? null; - store.emitChange(); - } - }); - - return store; -}); diff --git a/src/plugins/spotifyControls/hoverOnly.css b/src/plugins/spotifyControls/hoverOnly.css deleted file mode 100644 index 8051cc42c..000000000 --- a/src/plugins/spotifyControls/hoverOnly.css +++ /dev/null @@ -1,16 +0,0 @@ -.vc-spotify-button-row { - height: 0; - opacity: 0; - pointer-events: none; - transition: 0.2s; - transition-property: height; -} - -#vc-spotify-player:hover .vc-spotify-button-row { - opacity: 1; - height: 32px; - pointer-events: auto; - - /* only transition opacity on show to prevent clipping */ - transition-property: height, opacity; -} diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx deleted file mode 100644 index add54025e..000000000 --- a/src/plugins/spotifyControls/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { Settings } from "@api/Settings"; -import { disableStyle, enableStyle } from "@api/Styles"; -import ErrorBoundary from "@components/ErrorBoundary"; -import { Devs } from "@utils/constants"; -import definePlugin, { OptionType } from "@utils/types"; - -import hoverOnlyStyle from "./hoverOnly.css?managed"; -import { Player } from "./PlayerComponent"; - -function toggleHoverControls(value: boolean) { - (value ? enableStyle : disableStyle)(hoverOnlyStyle); -} - -export default definePlugin({ - name: "SpotifyControls", - description: "Adds a Spotify player above the account panel", - authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000, Devs.nin0dev], - options: { - hoverControls: { - description: "Show controls on hover", - type: OptionType.BOOLEAN, - default: false, - onChange: v => toggleHoverControls(v) - }, - useSpotifyUris: { - type: OptionType.BOOLEAN, - description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms", - default: false - }, - previousButtonRestartsTrack: { - type: OptionType.BOOLEAN, - description: "Restart currently playing track when pressing the previous button if playtime is >3s", - default: true - } - }, - patches: [ - { - // HEY THOR UPDATE YOUTUBE MUSIC CONTROLS IF THIS SHIT CHANGES TY :) - find: "this.isCopiedStreakGodlike", - replacement: { - // react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah }) - match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,hidePrivateData:)/, - // react.jsx(WrapperComponent, { VencordOriginal: AccountPanel, ... - replace: "$self.PanelWrapper,{VencordOriginal:$1," - } - }, - { - find: ".PLAYER_DEVICES", - replacement: [{ - // Adds POST and a Marker to the SpotifyAPI (so we can easily find it) - match: /get:(\i)\.bind\(null,(\i\.\i)\.get\)/, - replace: "post:$1.bind(null,$2.post),vcSpotifyMarker:1,$&" - }, - { - // Spotify Connect API returns status 202 instead of 204 when skipping tracks. - // Discord rejects 202 which causes the request to send twice. This patch prevents this. - match: /202===\i\.status/, - replace: "false", - }] - }, - { - find: 'repeat:"off"!==', - replacement: [ - { - // Discord doesn't give you shuffle state and the repeat kind, only a boolean - match: /repeat:"off"!==(\i),/, - replace: "shuffle:arguments[2]?.shuffle_state??false,actual_repeat:$1,$&" - }, - { - match: /(?<=artists.filter\(\i=>).{0,10}\i\.id\)&&/, - replace: "" - } - ] - }, - ], - - start: () => toggleHoverControls(Settings.plugins.SpotifyControls.hoverControls), - - PanelWrapper({ VencordOriginal, ...props }) { - return ( - <> - ( -
-

Failed to render Spotify Modal :(

-

Check the console for errors

-
- )} - > - -
- - - - ); - } -}); diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css deleted file mode 100644 index e9dfeffde..000000000 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ /dev/null @@ -1,229 +0,0 @@ -#vc-spotify-player { - padding: 12px; - background: var(--bg-overlay-floating, var(--background-base-low, var(--background-base-lower-alt))); - margin: 0; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - border-bottom: 1px solid var(--border-subtle); - - /* so custom themes can easily change it */ - --vc-spotify-green: var(--spotify, #1db954); - --vc-spotify-green-90: color-mix(in hsl, var(--vc-spotify-green), transparent 90%); - --vc-spotify-green-80: color-mix(in hsl, var(--vc-spotify-green), transparent 80%); -} - -.theme-light #vc-spotify-player { - background: var(--bg-overlay-3, var(--background-base-lower-alt)); -} - -.theme-dark #vc-spotify-player { - background: var(--bg-overlay-1, var(--background-base-lower-alt)); -} - -.vc-spotify-button { - margin: 0 2px; - border-radius: var(--radius-sm); - background: none; - color: var(--interactive-normal); - padding: 0; - width: 32px; - height: 32px; - display: flex; - justify-content: center; - align-items: center; -} - -.vc-spotify-button:hover { - color: var(--interactive-hover); - background-color: var(--background-modifier-selected); -} - -.vc-spotify-button-icon { - height: 24px; - width: 24px; -} - -.vc-spotify-shuffle .vc-spotify-button-icon, -.vc-spotify-repeat .vc-spotify-button-icon { - width: 22px; - height: 22px; -} - -/* .vc-spotify-button:hover { - filter: brightness(1.3); -} */ - -.vc-spotify-repeat-context, -.vc-spotify-repeat-track, -.vc-spotify-shuffle-on { - background-color: var(--vc-spotify-green-90); -} - -.vc-spotify-repeat-context:hover, -.vc-spotify-repeat-track:hover, -.vc-spotify-shuffle-on:hover { - background-color: var(--vc-spotify-green-80); -} - -.vc-spotify-tooltip-text { - overflow: hidden; - white-space: nowrap; - padding-right: 0.2em; - max-width: 100%; - margin: unset; -} - -.vc-spotify-repeat-1 { - font-size: 70%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); -} - -.vc-spotify-button-row { - justify-content: center; - margin-top: 14px; -} - -.vc-spotify-secondary-song-info { - font-size: 12px; -} - -.vc-spotify-song-info-prefix { - display: none; -} - -#vc-spotify-info-wrapper { - display: flex; - flex-direction: row; - align-items: center; - height: 3em; - gap: 0.5em; -} - -#vc-spotify-album-image { - height: 90%; - object-fit: contain; - border-radius: 3px; - transition: filter 0.2s; -} - -#vc-spotify-album-image:hover { - filter: brightness(1.2); - cursor: pointer; -} - -#vc-spotify-album-expanded-wrapper #vc-spotify-album-image { - width: 100%; - object-fit: contain; -} - -#vc-spotify-titles { - display: flex; - flex-direction: column; - padding: 0.2rem; - align-items: flex-start; - place-content: flex-start center; - overflow: hidden; -} - -#vc-spotify-song-title { - color: var(--header-primary); - font-size: 14px; - font-weight: 600; -} - -.vc-spotify-ellipoverflow { - white-space: nowrap; - overflow: hidden; - width: 100%; - text-overflow: ellipsis; -} - -.vc-spotify-artist, -.vc-spotify-album { - font-size: 12px; - text-decoration: none; - color: var(--header-primary); -} - -.vc-spotify-comma { - color: var(--header-secondary); -} - -.vc-spotify-artist[role="link"]:hover, -#vc-spotify-album-title[role="link"]:hover, -#vc-spotify-song-title[role="link"]:hover { - text-decoration: underline; - cursor: pointer; -} - -#vc-spotify-progress-bar { - position: relative; - color: var(--text-default); - width: 100%; - margin: 0.5em 0; - margin-bottom: 5px; -} - -#vc-spotify-progress-bar > [class^="slider"] { - flex-grow: 1; - width: 100%; - padding: 0 !important; -} - -#vc-spotify-progress-bar > [class^="slider"] [class^="bar"] { - height: 3px !important; - top: calc(12px - 4px / 2 + var(--bar-offset)); -} - -#vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] { - background-color: var(--interactive-active); -} - -#vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] { - background-color: var(--vc-spotify-green); -} - -#vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] { - /* these importants are necessary, it applies a width and height through inline styles */ - height: 16px !important; - width: 16px !important; - margin-top: calc(17px/-2 + var(--bar-offset)/2); - margin-left: -0.5px; - background-color: var(--interactive-active); - border-color: var(--interactive-normal); - color: var(--interactive-normal); - opacity: 0; - transition: opacity 0.1s; -} - -#vc-spotify-progress-bar:hover > [class^="slider"] [class^="grabber"] { - opacity: 1; -} - -#vc-spotify-progress-text { - margin: 0; -} - -.vc-spotify-progress-time { - font-size: 12px; - top: 10px; - position: absolute; - margin-top: 8px; - font-family: var(--font-code); -} - -.vc-spotify-time-left { - left: 0; -} - -.vc-spotify-time-right { - right: 0; -} - -.vc-spotify-fallback { - padding: 0.5em; - color: var(--text-default); -} \ No newline at end of file