diff --git a/config/data.py b/config/data.py index 0df7c23..fad426f 100644 --- a/config/data.py +++ b/config/data.py @@ -10,7 +10,7 @@ gi.require_version("Gtk", "3.0") APP_NAME = "modus" APP_NAME_CAP = "Modus" -ALLOWED_PLAYERS = ["vlc", "mpv", "cmus", "firefox", "spotify", "chromium"] +ALLOWED_PLAYERS = ["vlc", "cmus", "firefox", "spotify", "chromium"] NOTIF_POS_KEY = "notif_pos" NOTIF_POS_DEFAULT = "Top" diff --git a/config/hypr/colors.conf b/config/hypr/colors.conf index 7697e90..12c8608 100644 --- a/config/hypr/colors.conf +++ b/config/hypr/colors.conf @@ -1,27 +1,27 @@ -$wallpaper = /home/sankalp/Pictures/wallpapers/110.jpg -$background = 171216 -$foreground = ebdfe6 +$wallpaper = /home/sankalp/Pictures/wallpapers/77.jpg +$background = 121318 +$foreground = e2e2e9 -$primary = eeb4ea -$secondary = d9bfd4 -$tertiary = f6b8aa -$surface = 171216 -$surface_bright = 3e373c -$outline = 998d96 +$primary = afc6ff +$secondary = bfc6dc +$tertiary = dfbbde +$surface = 121318 +$surface_bright = 38393e +$outline = 8f9099 $error = ff5945 $shadow = 000000 $red = ffb2b9 -$green = b7d085 -$yellow = dec56e -$blue = cebdfe -$magenta = efb3e8 +$green = 95d5a7 +$yellow = b8cf84 +$blue = b7c4ff +$magenta = e4b7f3 $cyan = 82d3e2 $white = 82d3e0 $red_dim = ff4c5c -$green_dim = 89ad42 -$yellow_dim = bb9b2b -$blue_dim = 8359fc -$magenta_dim = dd5fcf +$green_dim = 4cb86a +$yellow_dim = 8baa43 +$blue_dim = 5170ff +$magenta_dim = c360e4 $cyan_dim = 30b5ce diff --git a/example_player.py b/example_player.py deleted file mode 100644 index 4fd3652..0000000 --- a/example_player.py +++ /dev/null @@ -1,357 +0,0 @@ -from fabric.widgets.box import Box -from fabric.widgets.label import Label -from fabric.widgets.centerbox import CenterBox -from fabric.widgets.button import Button -from fabric.widgets.stack import Stack - -from player_service import PlayerManager, PlayerService -from wiggle_bar import WigglyWidget - -import icons as icons -import info as info - -from loguru import logger - - -class Player(Box): - def __init__(self, player, **kwargs): - super().__init__(style_classes="player", orientation="v", **kwargs) - - if not info.VERTICAL: - # vertical class binding from unknown source - self.remove_style_class("vertical") - - self._player = PlayerService(player=player) - - self.duration = 0.0 - - self._player.connect("pause", self.on_pause) - self._player.connect("play", self.on_play) - self._player.connect("meta-change", self.on_metadata) - self._player.connect("shuffle-toggle", self.on_shuffle) - self._player.connect("track-position", self.on_update_track_position) - - self.player_name = Label( - name=player.props.player_name, - style_classes="player-icon", - markup=getattr(icons, player.props.player_name, icons.disc), - ) - - self.set_style( - f"background-image:url('{info.HOME_DIR}/.cache/walls/low_rez.png')" - ) - - self.song = Label( - name="song", - label="song", - justification="left", - h_align="start", - max_chars_width=10, - ) - self.artist = Label( - name="artist", label="artist", justification="left", h_align="start" - ) - self.music = Box( - name="music", - orientation="v", - h_expand=True, - v_expand=True, - children=[self.song, self.artist], - ) - - self.play_pause_button = Button( - name="pause-button", - child=Label(name="pause-label", markup=icons.play), - style_classes="pause-track", - tooltip_text="Exit", - on_clicked=lambda b, *_: self.handle_play_pause(player), - ) - - self.shuffle_button = Button( - name="shuffle-button", - child=Label(name="shuffle", markup=icons.shuffle), - on_clicked=lambda b, *_: self.handle_shuffle(b, player), - ) - - self.wiggly = WigglyWidget() - self.wiggly.connect("on-seek", self.on_seek) - self.gtk_wrapper = Box( - orientation="v", - h_expand=True, - v_expand=True, - h_align="fill", - v_align="fill", - children=self.wiggly, - ) - - self.children = [ - Box(name="source", h_expand=True, v_expand=True, children=self.player_name), - CenterBox( - name="details", - # h_expand=True, - # v_expand=True, - start_children=self.music, - end_children=self.play_pause_button if not info.VERTICAL else [], - ), - Box( - name="controls", - # h_expand=True, - # v_expand=True, - style_classes="horizontal" if not info.VERTICAL else "vertical", - spacing=5, - children=[ - Button( - name="prev-button", - child=Label(name="play-previous", markup=icons.previous), - on_clicked=lambda b, *_: self.handle_prev(player), - ), - CenterBox( - name="progress-container", - h_expand=True, - v_expand=True, - orientation="v", - center_children=[self.gtk_wrapper], - ), - Button( - name="next-button", - child=Label(name="play-next", markup=icons.next), - on_clicked=lambda b, *_: self.handle_next(player), - ), - self.shuffle_button, - ] - if not info.VERTICAL - else [ - CenterBox( - orientation="v", - h_expand=True, - start_children=[ - CenterBox( - h_expand=True, - v_expand=True, - v_align="end", - start_children=[ - Button( - name="prev-button", - child=Label( - name="play-previous", markup=icons.previous - ), - on_clicked=lambda b, *_: self.handle_prev( - player - ), - ), - Button( - name="next-button", - child=Label( - name="play-next", markup=icons.next - ), - on_clicked=lambda b, *_: self.handle_next( - player - ), - ), - self.shuffle_button, - ], - end_children=self.play_pause_button, - ) - ], - end_children=CenterBox( - name="progress-container", - h_expand=True, - v_expand=True, - orientation="v", - center_children=[self.gtk_wrapper], - ), - ) - ], - ), - ] - - self.on_metadata(self._player, metadata=player.props.metadata, player=player) - - def on_update_track_position(self, sender, pos, dur): - self.duration = dur - self.wiggly.update_value_from_signal(pos / dur) - - def on_seek(self, sender, ratio): - pos = ratio * self.duration # duration in seconds - print(f"Seeking to {pos:.2f}s") - self._player.set_position(int(pos)) - - def on_metadata(self, sender, metadata, player): - keys = metadata.keys() - if "xesam:artist" in keys and "xesam:title" in keys: - _max_chars = 43 if not info.VERTICAL else 30 - song_title = metadata["xesam:title"] - if len(song_title) > _max_chars: - song_title = song_title[: _max_chars - 1] + "…" - self.song.set_label(song_title) - - artist_list = metadata["xesam:artist"] - artist_name = artist_list[0] if artist_list else "Unknown Artist" - if len(artist_name) > _max_chars: - artist_name = artist_name[: _max_chars - 1] + "…" - self.artist.set_label(artist_name) - if "mpris:artUrl" in keys: - self.set_style(f"background-image:url('{metadata['mpris:artUrl']}')") - - if ( - player.props.playback_status.value_name - == "PLAYERCTL_PLAYBACK_STATUS_PLAYING" - ): - self.on_play(self._player) - - if player.props.shuffle == True: - self.shuffle_button.get_child().set_markup(icons.disable_shuffle) - self.shuffle_button.get_child().set_name("disable-shuffle") - else: - self.shuffle_button.get_child().set_markup(icons.shuffle) - self.shuffle_button.get_child().set_name("shuffle") - - def on_pause(self, sender): - self.play_pause_button.get_child().set_markup(icons.play) - self.play_pause_button.get_child().set_name("pause-label") - self.wiggly.dragging = True - self.wiggly.update_amplitude(True) - self.wiggly.pause = True - self.play_pause_button.add_style_class("pause-track") - - def on_play(self, sender): - self.play_pause_button.get_child().set_markup(icons.pause) - self.play_pause_button.get_child().set_name("play-label") - self.wiggly.pause = False - self.wiggly.dragging = False - self.wiggly.update_amplitude(False) - self.play_pause_button.remove_style_class("pause-track") - - def on_shuffle(self, sender, player, status): - print("callback status", status) - if status == False: - self.shuffle_button.get_child().set_markup(icons.shuffle) - self.shuffle_button.get_child().set_name("shuffle") - else: - self.shuffle_button.get_child().set_markup(icons.disable_shuffle) - self.shuffle_button.get_child().set_name("disable-shuffle") - - self.shuffle_button.get_child().set_style("color: white") - - def handle_next(self, player): - player.next() - - def handle_prev(self, player): - player.previous() - - def handle_play_pause(self, player): - is_playing = self.play_pause_button.get_child().get_name() == "play-label" - - def _set_play_ui(): - self.play_pause_button.get_child().set_markup(icons.pause) - self.play_pause_button.remove_style_class("pause-track") - self.play_pause_button.get_child().set_name("pause-label") - - def _set_pause_ui(): - self.play_pause_button.get_child().set_markup(icons.play) - self.play_pause_button.add_style_class("pause-track") - self.play_pause_button.get_child().set_name("play-label") - - if is_playing: - _set_pause_ui() - else: - _set_play_ui() - - try: - player.play_pause() - except Exception as e: - # revert if signal failed - if is_playing: - _set_pause_ui() - else: - _set_play_ui() - logger.warning("Failed to toggle playback:", e) - - def handle_shuffle(self, shuffle_button, player): - print("shuffle", player.props.shuffle) - if player.props.shuffle == False: - player.set_shuffle(True) - print("setting to true", player.props.player_name) - else: - player.set_shuffle(False) - shuffle_button.get_child().set_style("color: var(--outline)") - - -class PlayerContainer(Box): - def __init__(self, **kwargs): - super().__init__(name="player-container", orientation="v", **kwargs) - - self.manager = PlayerManager() - self.manager.connect("new-player", self.new_player) - self.manager.connect("player-vanish", self.on_player_vanish) - self.stack = Stack( - name="player-container", - transition_type="crossfade", - transition_duration=100, - children=[], - ) - - self.player_stack = Stack( - name="player-stack", - transition_type="crossfade", - transition_duration=100, - children=[], - ) - - self.player_switch_container = CenterBox( - name="player-switch-container", - orientation="h", - style_classes="horizontal-player" - if not info.VERTICAL - else "vertical-player", - center_children=[], - ) - self.children = [self.stack, self.player_switch_container] - self.player = [] - self.manager.init_all_players() - - def new_player(self, manager, player): - print(player.props.player_name, "new player") - print(player) - new_player = Player(player=player) - new_player.gtk_wrapper.queue_draw() - new_player.set_name(player.props.player_name) - self.player.append(new_player) - print("stacking dis bitvch") - self.stack.add_named(new_player, player.props.player_name) - - self.player_switch_container.add_center( - Button( - name=player.props.player_name, - style_classes="player-button", - on_clicked=lambda b: self.switch_player(player.props.player_name, b), - ) - ) - self.update_player_list() - - def switch_player(self, player_name, button): - self.stack.set_visible_child_name(player_name) - - for btn in self.player_switch_container.center_children: - btn.remove_style_class("active") - button.add_style_class("active") - - def on_player_vanish(self, manager, player): - for player_instance in self.player: - if player_instance.get_name() == player.props.player_name: - self.stack.remove(player_instance) - self.player.remove(player_instance) - for btn in self.player_switch_container.center_children: - if btn.get_name() == player_instance.get_name(): - self.player_switch_container.remove_center(btn) - self.update_player_list() - break - - def update_player_list(self): - curr = self.stack.get_visible_child() - for btn in self.player_switch_container.center_children: - print(btn.get_name()) - if btn.get_name() == curr.get_name(): - btn.add_style_class("active") - else: - btn.remove_style_class("active") diff --git a/modules/dock/components/music_player.py b/modules/dock/components/music_player.py index 03e3a7f..3346ad6 100644 --- a/modules/dock/components/music_player.py +++ b/modules/dock/components/music_player.py @@ -10,13 +10,13 @@ import config.data as data import utils.icons as icons from services.mpris import PlayerManager, PlayerService from utils.wayland import WaylandWindow as Window -from player import PlayerContainer +from modules.dock.components.player import PlayerContainer class MusicPlayerPopup(Window): """Popup window that shows the full music player interface""" - def __init__(self, **kwargs): + def __init__(self, dock_component=None, **kwargs): # Get dock position for proper anchoring dock_position = data.DOCK_POSITION popup_anchor = self._get_popup_anchor(dock_position) @@ -34,14 +34,24 @@ class MusicPlayerPopup(Window): **kwargs, ) + self.dock_component = dock_component self.player_container = PlayerContainer() self.add(self.player_container) + # Connect player container events to sync with dock + if self.dock_component: + self.player_container.connect("active-player-changed", self.on_active_player_changed) + # Connect to escape key to close self.connect("key-press-event", self.on_key_press) self.connect("button-press-event", self.on_button_press) self.set_can_focus(True) + def on_active_player_changed(self, container, player): + """Handle active player change from player container""" + if self.dock_component and player: + self.dock_component.set_current_player(player) + def on_key_press(self, widget, event): """Handle key press events""" if event.keyval == Gdk.KEY_Escape: @@ -140,11 +150,18 @@ class MusicPlayer(Box): # Set initial visibility self.update_visibility() + # Set up periodic check for active player + from gi.repository import GLib + self.active_player_check_timeout = GLib.timeout_add_seconds(2, self.check_active_player) + # Connect to destroy signal for cleanup self.connect("destroy", self.on_destroy) def on_destroy(self, widget): """Clean up popup when component is destroyed""" + if hasattr(self, 'active_player_check_timeout'): + from gi.repository import GLib + GLib.source_remove(self.active_player_check_timeout) if self.popup: self.popup.destroy() self.popup = None @@ -156,6 +173,11 @@ class MusicPlayer(Box): # If this is our first player, make it current if not self.current_player: self.set_current_player(player) + else: + # If the new player is playing, switch to it + if (player.props.playback_status.value_name == "PLAYERCTL_PLAYBACK_STATUS_PLAYING" and + self.current_player.props.playback_status.value_name != "PLAYERCTL_PLAYBACK_STATUS_PLAYING"): + self.set_current_player(player) self.update_visibility() @@ -171,13 +193,93 @@ class MusicPlayer(Box): self.current_player = None self.current_player_service = None - # Try to find another active player - # This is a simple approach - in a real implementation you might want more sophisticated logic + # Try to find another player with media content + if hasattr(self.manager, '_manager') and hasattr(self.manager._manager, 'props'): + for player_name in self.manager._manager.props.player_names: + try: + from gi.repository import Playerctl + candidate_player = Playerctl.Player.new_from_name(player_name) + + # Check if this player has media content + if (hasattr(candidate_player, 'props') and + hasattr(candidate_player.props, 'metadata')): + metadata = candidate_player.props.metadata + if metadata and len(metadata.keys()) > 0: + keys = metadata.keys() + if ("xesam:title" in keys or + "xesam:artist" in keys or + "mpris:length" in keys): + print(f"[DEBUG] Switching to player with media: {candidate_player.props.player_name}") + self.set_current_player(candidate_player) + break + except Exception as e: + print(f"[DEBUG] Error checking candidate player {player_name}: {e}") + continue self.update_visibility() + def get_playing_player(self): + """Find a currently playing player from the manager""" + try: + if hasattr(self.manager, '_manager') and hasattr(self.manager._manager, 'props'): + for player_name in self.manager._manager.props.player_names: + try: + from gi.repository import Playerctl + player = Playerctl.Player.new_from_name(player_name) + if (hasattr(player, 'props') and + hasattr(player.props, 'playback_status') and + player.props.playback_status.value_name == "PLAYERCTL_PLAYBACK_STATUS_PLAYING"): + print(f"[DEBUG] Found playing player: {player.props.player_name}") + return player + except Exception as e: + print(f"[DEBUG] Error checking player {player_name}: {e}") + continue + except Exception as e: + print(f"[DEBUG] Error in get_playing_player: {e}") + return None + + def check_active_player(self): + """Periodically check for the currently playing player and switch if needed""" + try: + # Find the currently playing player + playing_player = self.get_playing_player() + + if playing_player: + # If we found a playing player and it's different from current, switch to it + if (not self.current_player or + self.current_player.props.player_name != playing_player.props.player_name): + print(f"[DEBUG] Switching to playing player: {playing_player.props.player_name}") + self.set_current_player(playing_player) + elif self.current_player: + # If no player is playing, check if current player is paused + if (self.current_player.props.playback_status.value_name != "PLAYERCTL_PLAYBACK_STATUS_PLAYING"): + # Current player is paused, see if there's another player available + if hasattr(self.manager, '_manager') and hasattr(self.manager._manager, 'props'): + for player_name in self.manager._manager.props.player_names: + try: + from gi.repository import Playerctl + player = Playerctl.Player.new_from_name(player_name) + if player.props.player_name != self.current_player.props.player_name: + # Switch to a different available player + print(f"[DEBUG] Switching to available player: {player.props.player_name}") + self.set_current_player(player) + break + except: + continue + + # Update visibility based on current state + self.update_visibility() + + except Exception as e: + print(f"[DEBUG] Error in check_active_player: {e}") + + # Return True to continue the timeout + return True + def set_current_player(self, player): """Set the current active player""" + print(f"[DEBUG] Setting current player to: {player.props.player_name}") + # Disconnect from previous player if self.current_player_service: try: @@ -238,6 +340,11 @@ class MusicPlayer(Box): if hasattr(self, '_current_track_info'): self.event_box.set_tooltip_text(f"{self._current_track_info} (Playing)") + # Trigger immediate check for active player when play state changes + self.check_active_player() + # Update visibility when playback state changes + self.update_visibility() + def on_pause(self, service): """Handle pause state""" # Always use generic music icon when paused @@ -247,6 +354,11 @@ class MusicPlayer(Box): if hasattr(self, '_current_track_info'): self.event_box.set_tooltip_text(f"{self._current_track_info} (Paused)") + # Trigger immediate check for active player when pause state changes + self.check_active_player() + # Update visibility when playback state changes + self.update_visibility() + def on_metadata(self, service, metadata, player): """Handle metadata changes""" # Update tooltip with current track info @@ -276,6 +388,9 @@ class MusicPlayer(Box): self._current_track_info = "Music Player" self.event_box.set_tooltip_text("Music Player") + # Update visibility when metadata changes (media content appears/disappears) + self.update_visibility() + def update_playback_state(self): """Update the visual state based on current playback""" if not self.current_player: @@ -296,7 +411,7 @@ class MusicPlayer(Box): else: # Create popup on-demand if it doesn't exist if not self.popup: - self.popup = MusicPlayerPopup() + self.popup = MusicPlayerPopup(dock_component=self) self.popup.show_popup() def on_scroll(self, widget, event): @@ -320,11 +435,47 @@ class MusicPlayer(Box): return True + def has_running_players(self): + """Check if there are any players that actually have media content running""" + try: + if hasattr(self.manager, '_manager') and hasattr(self.manager._manager, 'props'): + for player_name in self.manager._manager.props.player_names: + try: + from gi.repository import Playerctl + player = Playerctl.Player.new_from_name(player_name) + + # Check if player has metadata indicating actual media content + if hasattr(player, 'props') and hasattr(player.props, 'metadata'): + metadata = player.props.metadata + if metadata and len(metadata.keys()) > 0: + # Check for essential metadata that indicates real media + keys = metadata.keys() + if ("xesam:title" in keys or + "xesam:artist" in keys or + "mpris:length" in keys): + print(f"[DEBUG] Found player with media content: {player.props.player_name}") + return True + + # Also check if player is currently playing (even without full metadata) + if (hasattr(player, 'props') and + hasattr(player.props, 'playback_status') and + player.props.playback_status.value_name == "PLAYERCTL_PLAYBACK_STATUS_PLAYING"): + print(f"[DEBUG] Found playing player: {player.props.player_name}") + return True + + except Exception as e: + print(f"[DEBUG] Error checking player {player_name}: {e}") + continue + except Exception as e: + print(f"[DEBUG] Error in has_running_players: {e}") + + return False + def update_visibility(self): - """Update component visibility based on available players""" - # Show component only if there are active players - has_players = self.current_player is not None - self.set_visible(has_players) + """Update component visibility based on players with actual media content""" + # Show component only if there are players with actual media content + has_media_players = self.has_running_players() + self.set_visible(has_media_players) # Hide in vertical mode if user preference is set if data.VERTICAL and hasattr(data, "DOCK_COMPONENTS_VISIBILITY"): diff --git a/player.py b/modules/dock/components/player.py similarity index 84% rename from player.py rename to modules/dock/components/player.py index 8577ad4..01b2290 100644 --- a/player.py +++ b/modules/dock/components/player.py @@ -3,6 +3,7 @@ from fabric.widgets.label import Label from fabric.widgets.centerbox import CenterBox from fabric.widgets.button import Button from fabric.widgets.stack import Stack +from fabric.core.service import Signal from services.mpris import PlayerManager, PlayerService from utils.wiggle_bar import WigglyWidget @@ -42,7 +43,7 @@ class Player(Box): # Use a fallback background image self.set_style( - f"background-image:url('{data.HOME_DIR}/.cache/walls/low_rez.png')" + f"background-image:url('{data.HOME_DIR}/.current.wall')" ) self.song = Label( @@ -197,6 +198,22 @@ class Player(Box): if "mpris:artUrl" in keys: self.set_style(f"background-image:url('{metadata['mpris:artUrl']}')") + elif "xesam:url" in keys and "youtube.com" in metadata["xesam:url"]: + # Simple YouTube thumbnail extraction for Firefox + import re + url = metadata["xesam:url"] + match = re.search(r'(?:v=|/)([a-zA-Z0-9_-]{11})', url) + if match: + video_id = match.group(1) + thumbnail_url = f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg" + self.set_style(f"background-image:url('{thumbnail_url}')") + print(f"Using YouTube thumbnail: {thumbnail_url}") + else: + # Fallback to default wallpaper + self.set_style(f"background-image:url('{data.HOME_DIR}/.current.wall')") + else: + # Fallback to default wallpaper when no artwork is available + self.set_style(f"background-image:url('{data.HOME_DIR}/.current.wall')") if ( player.props.playback_status.value_name @@ -283,6 +300,9 @@ class Player(Box): class PlayerContainer(Box): + @Signal + def active_player_changed(self, player) -> None: ... + def __init__(self, **kwargs): super().__init__(name="player-container", orientation="v", **kwargs) @@ -316,6 +336,8 @@ class PlayerContainer(Box): ) self.children = [self.stack, self.player_switch_container] self.player = [] + self.player_objects = {} # Map player names to player objects + self.active_player = None # Track the currently active player self.manager.init_all_players() def new_player(self, manager, player): @@ -325,6 +347,7 @@ class PlayerContainer(Box): new_player.gtk_wrapper.queue_draw() new_player.set_name(player.props.player_name) self.player.append(new_player) + self.player_objects[player.props.player_name] = player # Store the player object print("stacking player") self.stack.add_named(new_player, player.props.player_name) @@ -335,6 +358,12 @@ class PlayerContainer(Box): on_clicked=lambda b: self.switch_player(player.props.player_name, b), ) ) + + # If this is the first player, make it active + if not self.active_player: + self.active_player = player + self.active_player_changed(player) + self.update_player_list() def switch_player(self, player_name, button): @@ -344,17 +373,38 @@ class PlayerContainer(Box): btn.remove_style_class("active") button.add_style_class("active") + # Get the player object and emit signal + if player_name in self.player_objects: + self.active_player = self.player_objects[player_name] + self.active_player_changed(self.active_player) + def on_player_vanish(self, manager, player): + player_name = player.props.player_name + for player_instance in self.player: - if player_instance.get_name() == player.props.player_name: + if player_instance.get_name() == player_name: self.stack.remove(player_instance) self.player.remove(player_instance) for btn in self.player_switch_container.center_children: - if btn.get_name() == player_instance.get_name(): + if btn.get_name() == player_name: self.player_switch_container.remove_center(btn) - self.update_player_list() break + # Clean up player objects dictionary + if player_name in self.player_objects: + del self.player_objects[player_name] + + # If the active player vanished, switch to another one + if self.active_player and self.active_player.props.player_name == player_name: + self.active_player = None + # Try to find another player to make active + if self.player_objects: + first_player_name = next(iter(self.player_objects)) + self.active_player = self.player_objects[first_player_name] + self.active_player_changed(self.active_player) + + self.update_player_list() + def update_player_list(self): curr = self.stack.get_visible_child() if curr: diff --git a/styles/components.css b/styles/components.css index 3aa6fc9..9d0621f 100644 --- a/styles/components.css +++ b/styles/components.css @@ -1,8 +1,18 @@ +#music-player{ + background-color: var(--surface); + padding: 4px; +} + +#music-player.invert { + border-radius: 12px; +} + #player-circle { padding: 4px; color: var(--surface-bright); border: 3px solid var(--primary); } + /* * ── language indicator ──────────────────────────────────────────────── */