${track.title}
@@ -160,12 +159,12 @@ document.addEventListener('DOMContentLoaded', () => {
try {
const tempEl = document.createElement('div');
tempEl.textContent = `Downloading: ${contextTrack.title}...`;
- tempEl.style.cssText = 'position:fixed;bottom:20px;right:20px;background:var(--card);padding:1rem;border-radius:var(--radius);border:1px solid var(--border);z-index:9999;';
+ tempEl.style.cssText = 'position:fixed;bottom:100px;right:20px;background:var(--card);padding:1rem 1.5rem;border-radius:var(--radius);border:1px solid var(--border);z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,0.5);';
document.body.appendChild(tempEl);
await api.downloadTrack(contextTrack.id, QUALITY, filename);
- tempEl.textContent = `Downloaded: ${contextTrack.title}`;
+ tempEl.textContent = `✓ Downloaded: ${contextTrack.title}`;
setTimeout(() => tempEl.remove(), 3000);
} catch (error) {
const errorMsg = error.message === RATE_LIMIT_ERROR_MESSAGE
@@ -178,6 +177,19 @@ document.addEventListener('DOMContentLoaded', () => {
contextMenu.style.display = 'none';
});
+ const performSearch = debounce((query) => {
+ if (query) {
+ window.location.hash = `#search/${encodeURIComponent(query)}`;
+ }
+ }, 300);
+
+ searchInput.addEventListener('input', (e) => {
+ const query = e.target.value.trim();
+ if (query.length > 2) {
+ performSearch(query);
+ }
+ });
+
searchForm.addEventListener('submit', e => {
e.preventDefault();
const query = searchInput.value.trim();
@@ -219,18 +231,41 @@ document.addEventListener('DOMContentLoaded', () => {
playPauseBtn.innerHTML = SVG_PLAY;
});
+ let isSeeking = false;
+ let wasPlaying = false;
+
const seek = (bar, fill, event, setter) => {
const rect = bar.getBoundingClientRect();
const position = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width));
setter(position);
};
+ progressBar.addEventListener('mousedown', () => {
+ isSeeking = true;
+ wasPlaying = !audioPlayer.paused;
+ if (wasPlaying) audioPlayer.pause();
+ });
+
+ document.addEventListener('mouseup', (e) => {
+ if (isSeeking) {
+ seek(progressBar, progressFill, e, position => {
+ if (!isNaN(audioPlayer.duration)) {
+ audioPlayer.currentTime = position * audioPlayer.duration;
+ if (wasPlaying) audioPlayer.play();
+ }
+ });
+ isSeeking = false;
+ }
+ });
+
progressBar.addEventListener('click', e => {
- seek(progressBar, progressFill, e, position => {
- if (!isNaN(audioPlayer.duration)) {
- audioPlayer.currentTime = position * audioPlayer.duration;
- }
- });
+ if (!isSeeking) {
+ seek(progressBar, progressFill, e, position => {
+ if (!isNaN(audioPlayer.duration)) {
+ audioPlayer.currentTime = position * audioPlayer.duration;
+ }
+ });
+ }
});
volumeBar.addEventListener('click', e => {
diff --git a/js/player.js b/js/player.js
index 14a5264..5365cab 100644
--- a/js/player.js
+++ b/js/player.js
@@ -1,4 +1,3 @@
-//js/player.js
import { REPEAT_MODE, SVG_PLAY, SVG_PAUSE, formatTime } from './utils.js';
export class Player {
@@ -12,12 +11,61 @@ export class Player {
this.currentQueueIndex = -1;
this.shuffleActive = false;
this.repeatMode = REPEAT_MODE.OFF;
+ this.preloadCache = new Map();
+ this.preloadAbortController = null;
}
setQuality(quality) {
this.quality = quality;
}
+ async preloadNextTracks() {
+ if (this.preloadAbortController) {
+ this.preloadAbortController.abort();
+ }
+
+ this.preloadAbortController = new AbortController();
+ const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
+ const tracksToPreload = [];
+
+ for (let i = 1; i <= 2; i++) {
+ const nextIndex = this.currentQueueIndex + i;
+ if (nextIndex < currentQueue.length) {
+ tracksToPreload.push({ track: currentQueue[nextIndex], index: nextIndex });
+ }
+ }
+
+ for (const { track, index } of tracksToPreload) {
+ if (this.preloadCache.has(track.id)) continue;
+
+ try {
+ const streamUrl = await this.api.getStreamUrl(track.id, this.quality);
+
+ if (this.preloadAbortController.signal.aborted) break;
+
+ fetch(streamUrl, {
+ signal: this.preloadAbortController.signal,
+ method: 'GET',
+ mode: 'cors',
+ cache: 'default'
+ }).then(response => {
+ if (response.ok) {
+ this.preloadCache.set(track.id, streamUrl);
+ }
+ }).catch(err => {
+ if (err.name !== 'AbortError') {
+ console.debug('Preload failed for:', track.title);
+ }
+ });
+
+ } catch (error) {
+ if (error.name !== 'AbortError') {
+ console.debug('Failed to get stream URL for preload:', track.title);
+ }
+ }
+ }
+ }
+
async playTrackFromQueue() {
const currentQueue = this.shuffleActive ? this.shuffledQueue : this.queue;
if (this.currentQueueIndex < 0 || this.currentQueueIndex >= currentQueue.length) {
@@ -27,7 +75,7 @@ export class Player {
const track = currentQueue[this.currentQueueIndex];
document.querySelector('.now-playing-bar .cover').src =
- this.api.getCoverUrl(track.album?.cover, '1280');
+ this.api.getCoverUrl(track.album?.cover, '160');
document.querySelector('.now-playing-bar .title').textContent = track.title;
document.querySelector('.now-playing-bar .artist').textContent = track.artist?.name || 'Unknown Artist';
document.title = `${track.title} • ${track.artist?.name || 'Unknown'}`;
@@ -36,9 +84,19 @@ export class Player {
this.updateMediaSession(track);
try {
- const streamUrl = await this.api.getStreamUrl(track.id, this.quality);
+ let streamUrl;
+
+ if (this.preloadCache.has(track.id)) {
+ streamUrl = this.preloadCache.get(track.id);
+ } else {
+ streamUrl = await this.api.getStreamUrl(track.id, this.quality);
+ }
+
this.audio.src = streamUrl;
await this.audio.play();
+
+ this.preloadNextTracks();
+
} catch (error) {
console.error(`Could not get track URL for: ${track.title}`, error);
document.querySelector('.now-playing-bar .title').textContent = `Error: ${track.title}`;
@@ -111,6 +169,9 @@ export class Player {
this.queue = [...this.originalQueueBeforeShuffle];
this.currentQueueIndex = this.queue.findIndex(t => t.id === currentTrack?.id);
}
+
+ this.preloadCache.clear();
+ this.preloadNextTracks();
}
toggleRepeat() {
@@ -122,6 +183,7 @@ export class Player {
this.queue = tracks;
this.currentQueueIndex = startIndex;
this.shuffleActive = false;
+ this.preloadCache.clear();
}
addToQueue(track) {
@@ -141,34 +203,34 @@ export class Player {
});
}
-updateMediaSession(track) {
- if (!('mediaSession' in navigator)) return;
-
- const artwork = [];
- const sizes = ['1280'];
-
- const coverId = track.album?.cover;
-
- if (coverId) {
- sizes.forEach(size => {
- const url = this.api.getCoverUrl(coverId, size);
- artwork.push({
- src: url,
- sizes: `${size}x${size}`,
- type: 'image/jpeg'
+ updateMediaSession(track) {
+ if (!('mediaSession' in navigator)) return;
+
+ const artwork = [];
+ const sizes = ['96', '128', '192', '256', '384', '512'];
+
+ const coverId = track.album?.cover;
+
+ if (coverId) {
+ sizes.forEach(size => {
+ const url = this.api.getCoverUrl(coverId, size);
+ artwork.push({
+ src: url,
+ sizes: `${size}x${size}`,
+ type: 'image/jpeg'
+ });
});
+ }
+
+ navigator.mediaSession.metadata = new MediaMetadata({
+ title: track.title || 'Unknown Title',
+ artist: track.artist?.name || 'Unknown Artist',
+ album: track.album?.title || 'Unknown Album',
+ artwork: artwork.length > 0 ? artwork : undefined
});
- }
-
- navigator.mediaSession.metadata = new MediaMetadata({
- title: track.title || 'Unknown Title',
- artist: track.artist?.name || 'Unknown Artist',
- album: track.album?.title || 'Unknown Album',
- artwork: artwork.length > 0 ? artwork : undefined
- });
- navigator.mediaSession.playbackState = this.audio.paused ? 'paused' : 'playing';
-}
+ navigator.mediaSession.playbackState = this.audio.paused ? 'paused' : 'playing';
+ }
updateMediaSessionPlaybackState() {
if ('mediaSession' in navigator) {
diff --git a/js/storage.js b/js/storage.js
index 2861dc4..af1c5d9 100644
--- a/js/storage.js
+++ b/js/storage.js
@@ -3,12 +3,12 @@ export const apiSettings = {
STORAGE_KEY: 'monochrome-api-instances',
defaultInstances: [
'https://hifi.prigoana.com',
- 'https://tidal.401658.xyz',
'https://hund.qqdl.site',
'https://katze.qqdl.site',
'https://maus.qqdl.site',
'https://vogel.qqdl.site',
- 'https://wolf.qqdl.site'
+ 'https://wolf.qqdl.site',
+ 'https://tidal.401658.xyz'
],
getInstances() {
diff --git a/js/ui.js b/js/ui.js
index cd46506..bdd841a 100644
--- a/js/ui.js
+++ b/js/ui.js
@@ -1,4 +1,4 @@
-import { formatTime, createPlaceholder, trackDataStore } from './utils.js';
+import { formatTime, createPlaceholder, trackDataStore, hasExplicitContent } from './utils.js';
import { recentActivityManager } from './storage.js';
export class UIRenderer {
@@ -6,17 +6,25 @@ export class UIRenderer {
this.api = api;
}
+ createExplicitBadge() {
+ return '
E';
+ }
+
createTrackItemHTML(track, index, showCover = false) {
const playIconSmall = '
';
- const trackNumberHTML = `
${showCover ? playIconSmall : index + 1}
`;
+ const trackNumberHTML = `
${showCover ? playIconSmall : index + 1}
`;
+ const explicitBadge = hasExplicitContent(track) ? this.createExplicitBadge() : '';
return `
${trackNumberHTML}
- ${showCover ? `
})
` : ''}
+ ${showCover ? `
})
` : ''}
-
${track.title}
+
+ ${track.title}
+ ${explicitBadge}
+
${track.artist?.name ?? 'Unknown Artist'}
@@ -26,10 +34,13 @@ export class UIRenderer {
}
createAlbumCardHTML(album) {
+ const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : '';
return `
-
- ${album.title}
+
+
})
+
+ ${album.title} ${explicitBadge}
Album • ${album.artist?.name ?? ''}
`;
@@ -38,7 +49,9 @@ export class UIRenderer {
createArtistCardHTML(artist) {
return `
-
+
+
})
+
${artist.name}
Artist
@@ -71,19 +84,6 @@ export class UIRenderer {
`;
}
- createSkeletonDetailHeader(isArtist = false) {
- return `
-
- `;
- }
-
createSkeletonTracks(count = 5, showCover = false) {
return `
${Array(count).fill(0).map(() => this.createSkeletonTrack(showCover)).join('')}
`;
}
@@ -93,10 +93,20 @@ export class UIRenderer {
}
renderListWithTracks(container, tracks, showCover) {
- container.innerHTML = tracks.map((track, i) =>
+ const fragment = document.createDocumentFragment();
+ const tempDiv = document.createElement('div');
+
+ tempDiv.innerHTML = tracks.map((track, i) =>
this.createTrackItemHTML(track, i, showCover)
).join('');
+ while (tempDiv.firstChild) {
+ fragment.appendChild(tempDiv.firstChild);
+ }
+
+ container.innerHTML = '';
+ container.appendChild(fragment);
+
tracks.forEach(track => {
const element = container.querySelector(`[data-track-id="${track.id}"]`);
if (element) trackDataStore.set(element, track);
@@ -156,7 +166,6 @@ export class UIRenderer {
let finalAlbums = albumsResult.items;
if (finalArtists.length === 0 && finalTracks.length > 0) {
- console.log('Using fallback: extracting artists from tracks');
const artistMap = new Map();
finalTracks.forEach(track => {
if (track.artist && !artistMap.has(track.artist.id)) {
@@ -174,7 +183,6 @@ export class UIRenderer {
}
if (finalAlbums.length === 0 && finalTracks.length > 0) {
- console.log('Using fallback: extracting albums from tracks');
const albumMap = new Map();
finalTracks.forEach(track => {
if (track.album && !albumMap.has(track.album.id)) {
@@ -231,9 +239,12 @@ export class UIRenderer {
try {
const { album, tracks } = await this.api.getAlbum(albumId);
- imageEl.src = this.api.getCoverUrl(album.cover);
+ imageEl.src = this.api.getCoverUrl(album.cover, '640');
imageEl.style.backgroundColor = '';
- titleEl.textContent = album.title;
+
+ const explicitBadge = hasExplicitContent(album) ? this.createExplicitBadge() : '';
+ titleEl.innerHTML = `${album.title} ${explicitBadge}`;
+
metaEl.innerHTML =
`By
${album.artist.name} • ${new Date(album.releaseDate).getFullYear()}`;
@@ -274,7 +285,7 @@ export class UIRenderer {
try {
const artist = await this.api.getArtist(artistId);
- imageEl.src = this.api.getArtistPictureUrl(artist.picture, '750');
+ imageEl.src = this.api.getArtistPictureUrl(artist.picture, '640');
imageEl.style.backgroundColor = '';
nameEl.textContent = artist.name;
metaEl.textContent = `${artist.popularity} popularity`;
diff --git a/js/utils.js b/js/utils.js
index 8ad14a7..efc61a6 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -1,4 +1,3 @@
-//js/utils.js
export const QUALITY = 'LOSSLESS';
export const REPEAT_MODE = {
@@ -139,4 +138,20 @@ export const deriveTrackQuality = (track) => {
return pickBestQuality(candidates);
};
-export const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
\ No newline at end of file
+export const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
+
+export const hasExplicitContent = (item) => {
+ return item?.explicit === true || item?.explicitLyrics === true;
+};
+
+export const debounce = (func, wait) => {
+ let timeout;
+ return function executedFunction(...args) {
+ const later = () => {
+ clearTimeout(timeout);
+ func(...args);
+ };
+ clearTimeout(timeout);
+ timeout = setTimeout(later, wait);
+ };
+};
\ No newline at end of file
diff --git a/styles.css b/styles.css
index 153a736..1738929 100644
--- a/styles.css
+++ b/styles.css
@@ -1,1368 +1,1395 @@
- /*styles.css*/
- :root {
- --background: #000;
- --foreground: #fafafa;
- --card: #111;
- --card-foreground: #fafafa;
- --primary: #fafafa;
- --primary-foreground: #111;
- --secondary: #27272a;
- --secondary-foreground: #fafafa;
- --muted: #27272a;
- --muted-foreground: #a1a1aa;
- --border: #27272a;
- --input: #27272a;
- --ring: #fafafa;
- --radius: .5rem;
- --highlight: #4ade80;
- --active-highlight: var(--highlight);
- --spacing-xs: 0.5rem;
- --spacing-sm: 0.75rem;
- --spacing-md: 1rem;
- --spacing-lg: 1.5rem;
- --spacing-xl: 2rem;
- --spacing-2xl: 3rem;
- }
-
- *, *::before, *::after {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- }
-
- html {
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- }
-
- body {
- background-color: var(--background);
- color: var(--foreground);
- font-family: 'Inter', sans-serif;
- overflow: hidden;
- }
- img {
- max-width: 100%;
- display: block;
- background-color: var(--muted);
- color: transparent;
- font-size: 0;
- border: none;
- }
-
- a {
- color: inherit;
- text-decoration: none;
- }
-
- .app-container {
- display: grid;
- height: 100vh;
- grid-template-columns: 280px 1fr;
- grid-template-rows: 1fr auto;
- grid-template-areas:
- "sidebar main"
- "player player";
- }
-
- .sidebar {
- grid-area: sidebar;
- background-color: var(--background);
- border-right: 1px solid var(--border);
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 2rem;
- transition: transform .3s ease-in-out;
- z-index: 2000;
- }
-
- .main-content {
- grid-area: main;
- overflow-y: auto;
- padding: var(--spacing-xl);
- }
-
- .now-playing-bar {
- grid-area: player;
- background-color: #050505;
- border-top: 1px solid var(--border);
- padding: var(--spacing-md) var(--spacing-lg);
- display: grid;
- grid-template-columns: 1fr 2fr 1fr;
- align-items: center;
- gap: var(--spacing-xl);
- }
-
- .sidebar-logo {
- display: flex;
- align-items: center;
- gap: .75rem;
- font-size: 1.1rem;
- font-weight: 600;
- margin-bottom: 1rem;
- }
-
- .sidebar-logo svg {
- width: 15px;
- height: 15px;
- }
-
- .sidebar-nav ul {
- list-style: none;
- }
-
- .sidebar-nav .nav-item a {
- display: flex;
- align-items: center;
- gap: .75rem;
- padding: .75rem;
- border-radius: var(--radius);
- color: var(--muted-foreground);
- text-decoration: none;
- font-weight: 500;
- transition: all .2s ease-in-out;
- cursor: pointer;
- }
-
- .sidebar-nav .nav-item a:hover {
- background-color: var(--secondary);
- color: var(--foreground);
- }
-
- .sidebar-nav .nav-item a.active {
- background-color: var(--primary);
- color: var(--primary-foreground);
- }
-
- .sidebar-nav .nav-item a svg {
- width: 20px;
- height: 20px;
- }
-
- .page {
- display: none;
- }
-
- .page.active {
- display: block;
- animation: fadeIn .3s ease-in-out;
- }
-
- @keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(8px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
- }
-
- .main-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: var(--spacing-xl);
- gap: var(--spacing-md);
- }
-
- .hamburger-menu {
- display: none;
- background: transparent;
- border: none;
- color: var(--foreground);
- cursor: pointer;
- padding: 0.5rem;
- }
-
- .search-bar {
- position: relative;
- width: 100%;
- max-width: 400px;
- }
-
- .search-bar svg {
- position: absolute;
- left: .75rem;
- top: 50%;
- transform: translateY(-50%);
- color: var(--muted-foreground);
- width: 20px;
- height: 20px;
- }
-
- .search-bar input {
- width: 100%;
- padding: .75rem .75rem .75rem 2.5rem;
- background-color: var(--input);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--foreground);
- font-size: 1rem;
- }
-
- .search-bar input:focus {
- outline: none;
- border-color: var(--ring);
- }
-
- .content-section {
- margin-bottom: var(--spacing-2xl);
- }
-
- .section-title {
- font-size: 1.75rem;
- font-weight: 700;
- margin-bottom: var(--spacing-lg);
- }
-
- .search-tabs {
- display: flex;
- gap: var(--spacing-xs);
- margin-bottom: var(--spacing-lg);
- border-bottom: 1px solid var(--border);
- }
-
- .search-tab {
- background: transparent;
- border: none;
- color: var(--muted-foreground);
- padding: var(--spacing-sm) var(--spacing-lg);
- cursor: pointer;
- font-size: 1rem;
- font-weight: 500;
- border-bottom: 2px solid transparent;
- transition: all 0.2s;
- }
-
- .search-tab:hover {
- color: var(--foreground);
- }
-
- .search-tab.active {
- color: var(--foreground);
- border-bottom-color: var(--highlight);
- }
-
- .search-tab-content {
- display: none;
- }
-
- .search-tab-content.active {
- display: block;
- animation: fadeIn 0.3s ease-in-out;
- }
-
- .card-grid {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
- gap: var(--spacing-lg);
- }
-
- .card {
- display: block;
- background-color: var(--card);
- border-radius: var(--radius);
- padding: 1rem;
- transition: background-color .2s ease-in-out;
- }
-
- .card:hover {
- background-color: var(--secondary);
- }
-
- .card-image {
- width: 100%;
- aspect-ratio: 1/1;
- background-color: var(--muted);
- border-radius: calc(var(--radius) - 4px);
- margin-bottom: 1rem;
- object-fit: cover;
- }
-
- .card.artist .card-image {
- border-radius: 50%;
- }
-
- .card-title {
- font-weight: 600;
- margin-bottom: .25rem;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .card-subtitle {
- font-size: .9rem;
- color: var(--muted-foreground);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .track-list {
- display: flex;
- flex-direction: column;
- gap: 2px;
- }
-
- .track-list .track-list-header {
- display: grid;
- grid-template-columns: 40px 1fr auto;
- align-items: center;
- gap: var(--spacing-md);
- padding: var(--spacing-sm) var(--spacing-sm);
- color: var(--muted-foreground);
- font-size: .9rem;
- border-bottom: 1px solid var(--border);
- margin-bottom: var(--spacing-xs);
- }
-
- .track-list .track-list-header .duration-header {
- justify-self: flex-end;
- }
-
- .track-item {
- display: grid;
- grid-template-columns: 40px 1fr auto;
- align-items: center;
- gap: var(--spacing-md);
- padding: var(--spacing-sm);
- border-radius: var(--radius);
- cursor: pointer;
- transition: background-color .2s ease-in-out;
- }
-
- .track-item:hover {
- background-color: var(--secondary);
- }
-
- .track-item .track-number {
- color: var(--muted-foreground);
- text-align: center;
- font-size: .9rem;
- }
-
- .track-item-info {
- display: flex;
- align-items: center;
- gap: var(--spacing-md);
- min-width: 0;
- }
-
- .track-item-cover {
- width: 40px;
- height: 40px;
- background-color: var(--muted);
- border-radius: 4px;
- object-fit: cover;
- flex-shrink: 0;
- }
-
- .track-item-details {
- min-width: 0;
- }
-
- .track-item-details .title {
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .track-item-details .artist {
- font-size: .9rem;
- color: var(--muted-foreground);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .track-item-duration {
- font-size: .9rem;
- color: var(--muted-foreground);
- justify-self: flex-end;
- }
-
- .track-item.playing .track-number,
- .track-item.playing .track-item-details .title {
- color: var(--highlight);
- }
-
- .detail-header {
- display: flex;
- align-items: flex-end;
- gap: var(--spacing-xl);
- margin-bottom: var(--spacing-2xl);
- padding-bottom: var(--spacing-xl);
- }
-
- .detail-header-image {
- width: 200px;
- height: 200px;
- flex-shrink: 0;
- background-color: var(--muted);
- border-radius: var(--radius);
- object-fit: cover;
- box-shadow: 0 10px 30px rgba(0, 0, 0, .5);
- transition: opacity 0.3s ease-in-out;
- }
-
- .detail-header-image.loading {
- opacity: 0.3;
- }
-
- .detail-header-image.artist {
- border-radius: 50%;
- }
-
- .detail-header-info .type {
- font-weight: 600;
- margin-bottom: .5rem;
- }
-
- .detail-header-info .title {
- font-size: 4rem;
- font-weight: 800;
- line-height: 1.1;
- }
-
- .detail-header-info .meta {
- color: var(--muted-foreground);
- margin-top: 1rem;
- }
-
- .settings-list {
- max-width: 800px;
- }
-
- .setting-item {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: var(--spacing-lg) 0;
- border-bottom: 1px solid var(--border);
- gap: var(--spacing-lg);
- }
-
- .setting-item .info {
- display: flex;
- flex-direction: column;
- }
-
- .setting-item .label {
- font-weight: 500;
- }
-
- .setting-item .description {
- font-size: .9rem;
- color: var(--muted-foreground);
- }
-
- .setting-item select {
- background-color: var(--input);
- color: var(--foreground);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: 0.5rem;
- }
-
- .toggle-switch {
- position: relative;
- display: inline-block;
- width: 40px;
- height: 24px;
- }
-
- .toggle-switch input {
+:root {
+ --background: #000;
+ --foreground: #fafafa;
+ --card: #111;
+ --card-foreground: #fafafa;
+ --primary: #fafafa;
+ --primary-foreground: #111;
+ --secondary: #27272a;
+ --secondary-foreground: #fafafa;
+ --muted: #27272a;
+ --muted-foreground: #a1a1aa;
+ --border: #27272a;
+ --input: #27272a;
+ --ring: #fafafa;
+ --radius: .5rem;
+ --highlight: #ffffff;
+ --active-highlight: var(--highlight);
+ --explicit-badge: #fafafa;
+ --spacing-xs: 0.5rem;
+ --spacing-sm: 0.75rem;
+ --spacing-md: 1rem;
+ --spacing-lg: 1.5rem;
+ --spacing-xl: 2rem;
+ --spacing-2xl: 3rem;
+}
+
+*, *::before, *::after {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+html {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+body {
+ background-color: var(--background);
+ color: var(--foreground);
+ font-family: 'Inter', sans-serif;
+ overflow: hidden;
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ background-color: var(--muted);
+ color: transparent;
+ font-size: 0;
+ border: none;
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.app-container {
+ display: grid;
+ height: 100vh;
+ grid-template-columns: 280px 1fr;
+ grid-template-rows: 1fr auto;
+ grid-template-areas:
+ "sidebar main"
+ "player player";
+}
+
+.sidebar {
+ grid-area: sidebar;
+ background-color: var(--background);
+ border-right: 1px solid var(--border);
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+ transition: transform .3s cubic-bezier(0.4, 0, 0.2, 1);
+ z-index: 2000;
+}
+
+.main-content {
+ grid-area: main;
+ overflow-y: auto;
+ padding: var(--spacing-xl);
+ scroll-behavior: smooth;
+}
+
+.now-playing-bar {
+ grid-area: player;
+ background-color: #050505;
+ border-top: 1px solid var(--border);
+ padding: var(--spacing-md) var(--spacing-lg);
+ display: grid;
+ grid-template-columns: 1fr 2fr 1fr;
+ align-items: center;
+ gap: var(--spacing-xl);
+}
+
+.sidebar-logo {
+ display: flex;
+ align-items: center;
+ gap: .75rem;
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin-bottom: 1rem;
+}
+
+.sidebar-logo svg {
+ width: 15px;
+ height: 15px;
+}
+
+.sidebar-nav ul {
+ list-style: none;
+}
+
+.sidebar-nav .nav-item a {
+ display: flex;
+ align-items: center;
+ gap: .75rem;
+ padding: .75rem;
+ border-radius: var(--radius);
+ color: var(--muted-foreground);
+ text-decoration: none;
+ font-weight: 500;
+ transition: all .2s cubic-bezier(0.4, 0, 0.2, 1);
+ cursor: pointer;
+}
+
+.sidebar-nav .nav-item a:hover {
+ background-color: var(--secondary);
+ color: var(--foreground);
+}
+
+.sidebar-nav .nav-item a.active {
+ background-color: var(--primary);
+ color: var(--primary-foreground);
+}
+
+.sidebar-nav .nav-item a svg {
+ width: 20px;
+ height: 20px;
+}
+
+.page {
+ display: none;
+}
+
+.page.active {
+ display: block;
+ animation: fadeIn .25s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+@keyframes fadeIn {
+ from {
opacity: 0;
- width: 0;
- height: 0;
+ transform: translateY(4px);
}
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
- .slider {
- position: absolute;
- cursor: pointer;
+.main-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: var(--spacing-xl);
+ gap: var(--spacing-md);
+}
+
+.hamburger-menu {
+ display: none;
+ background: transparent;
+ border: none;
+ color: var(--foreground);
+ cursor: pointer;
+ padding: 0.5rem;
+ border-radius: var(--radius);
+ transition: background-color .2s;
+}
+
+.hamburger-menu:hover {
+ background-color: var(--secondary);
+}
+
+.search-bar {
+ position: relative;
+ width: 100%;
+ max-width: 400px;
+}
+
+.search-bar svg {
+ position: absolute;
+ left: .75rem;
+ top: 50%;
+ transform: translateY(-50%);
+ color: var(--muted-foreground);
+ width: 20px;
+ height: 20px;
+ pointer-events: none;
+}
+
+.search-bar input {
+ width: 100%;
+ padding: .75rem .75rem .75rem 2.5rem;
+ background-color: var(--input);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--foreground);
+ font-size: 1rem;
+ transition: border-color .2s;
+}
+
+.search-bar input:focus {
+ outline: none;
+ border-color: var(--ring);
+}
+
+.content-section {
+ margin-bottom: var(--spacing-2xl);
+}
+
+.section-title {
+ font-size: 1.75rem;
+ font-weight: 700;
+ margin-bottom: var(--spacing-lg);
+}
+
+.search-tabs {
+ display: flex;
+ gap: var(--spacing-xs);
+ margin-bottom: var(--spacing-lg);
+ border-bottom: 1px solid var(--border);
+}
+
+.search-tab {
+ background: transparent;
+ border: none;
+ color: var(--muted-foreground);
+ padding: var(--spacing-sm) var(--spacing-lg);
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 500;
+ border-bottom: 2px solid transparent;
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.search-tab:hover {
+ color: var(--foreground);
+}
+
+.search-tab.active {
+ color: var(--foreground);
+ border-bottom-color: var(--highlight);
+}
+
+.search-tab-content {
+ display: none;
+}
+
+.search-tab-content.active {
+ display: block;
+ animation: fadeIn 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.card-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+ gap: var(--spacing-lg);
+}
+
+.card {
+ display: block;
+ background-color: var(--card);
+ border-radius: var(--radius);
+ padding: 1rem;
+ transition: all .2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.card:hover {
+ background-color: var(--secondary);
+ transform: translateY(-2px);
+}
+
+.card-image-wrapper {
+ position: relative;
+ width: 100%;
+ margin-bottom: 1rem;
+}
+
+.card-image {
+ width: 100%;
+ aspect-ratio: 1/1;
+ background-color: var(--muted);
+ border-radius: calc(var(--radius) - 4px);
+ object-fit: cover;
+}
+
+.card.artist .card-image {
+ border-radius: 50%;
+}
+
+.card-image-wrapper .explicit-badge {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+}
+
+.card-title {
+ font-weight: 600;
+ margin-bottom: .25rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.card-subtitle {
+ font-size: .9rem;
+ color: var(--muted-foreground);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.explicit-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background-color: var(--explicit-badge);
+ color: rgb(0, 0, 0);
+ font-size: 0.65rem;
+ font-weight: 700;
+ padding: 0.15rem 0.35rem;
+ border-radius: 2px;
+ margin-left: 0.5rem;
+ vertical-align: middle;
+ line-height: 1;
+}
+
+.track-list {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.track-list .track-list-header {
+ display: grid;
+ grid-template-columns: 40px 1fr auto;
+ align-items: center;
+ gap: var(--spacing-md);
+ padding: var(--spacing-sm) var(--spacing-sm);
+ color: var(--muted-foreground);
+ font-size: .9rem;
+ border-bottom: 1px solid var(--border);
+ margin-bottom: var(--spacing-xs);
+}
+
+.track-list .track-list-header .duration-header {
+ justify-self: flex-end;
+}
+
+.track-item {
+ display: grid;
+ grid-template-columns: 40px 1fr auto;
+ align-items: center;
+ gap: var(--spacing-md);
+ padding: var(--spacing-sm);
+ border-radius: var(--radius);
+ cursor: pointer;
+ transition: all .2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.track-item:hover {
+ background-color: var(--secondary);
+}
+
+.track-item .track-number {
+ color: var(--muted-foreground);
+ text-align: center;
+ font-size: .9rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.track-item-info {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+ min-width: 0;
+}
+
+.track-item-cover {
+ width: 40px;
+ height: 40px;
+ background-color: var(--muted);
+ border-radius: 4px;
+ object-fit: cover;
+ flex-shrink: 0;
+}
+
+.track-item-details {
+ min-width: 0;
+}
+
+.track-item-details .title {
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: flex;
+ align-items: center;
+}
+
+.track-item-details .artist {
+ font-size: .9rem;
+ color: var(--muted-foreground);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.track-item-duration {
+ font-size: .9rem;
+ color: var(--muted-foreground);
+ justify-self: flex-end;
+}
+
+.track-item.playing .track-number,
+.track-item.playing .track-item-details .title {
+ color: var(--highlight);
+}
+
+.detail-header {
+ display: flex;
+ align-items: flex-end;
+ gap: var(--spacing-xl);
+ margin-bottom: var(--spacing-2xl);
+ padding-bottom: var(--spacing-xl);
+}
+
+.detail-header-image {
+ width: 200px;
+ height: 200px;
+ flex-shrink: 0;
+ background-color: var(--muted);
+ border-radius: var(--radius);
+ object-fit: cover;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, .5);
+ transition: opacity 0.3s ease-in-out;
+}
+
+.detail-header-image.loading {
+ opacity: 0.3;
+}
+
+.detail-header-image.artist {
+ border-radius: 50%;
+}
+
+.detail-header-info .type {
+ font-weight: 600;
+ margin-bottom: .5rem;
+}
+
+.detail-header-info .title {
+ font-size: 4rem;
+ font-weight: 800;
+ line-height: 1.1;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.detail-header-info .meta {
+ color: var(--muted-foreground);
+ margin-top: 1rem;
+}
+
+.settings-list {
+ max-width: 800px;
+}
+
+.setting-item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: var(--spacing-lg) 0;
+ border-bottom: 1px solid var(--border);
+ gap: var(--spacing-lg);
+}
+
+.setting-item .info {
+ display: flex;
+ flex-direction: column;
+}
+
+.setting-item .label {
+ font-weight: 500;
+}
+
+.setting-item .description {
+ font-size: .9rem;
+ color: var(--muted-foreground);
+}
+
+.setting-item select {
+ background-color: var(--input);
+ color: var(--foreground);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: 0.5rem;
+}
+
+.toggle-switch {
+ position: relative;
+ display: inline-block;
+ width: 40px;
+ height: 24px;
+}
+
+.toggle-switch input {
+ opacity: 0;
+ width: 0;
+ height: 0;
+}
+
+.slider {
+ position: absolute;
+ cursor: pointer;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: var(--secondary);
+ transition: .3s cubic-bezier(0.4, 0, 0.2, 1);
+ border-radius: 24px;
+}
+
+.slider:before {
+ position: absolute;
+ content: "";
+ height: 16px;
+ width: 16px;
+ left: 4px;
+ bottom: 4px;
+ background-color: var(--foreground);
+ transition: .3s cubic-bezier(0.4, 0, 0.2, 1);
+ border-radius: 50%;
+}
+
+input:checked + .slider {
+ background-color: var(--primary);
+}
+
+input:checked + .slider:before {
+ transform: translateX(16px);
+ background-color: var(--primary-foreground);
+}
+
+.btn-secondary {
+ padding: 0.5rem 1rem;
+ background-color: var(--secondary);
+ color: var(--foreground);
+ border: none;
+ border-radius: var(--radius);
+ cursor: pointer;
+ font-weight: 500;
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.btn-secondary:hover {
+ background-color: var(--muted);
+}
+
+.btn-secondary:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.now-playing-bar .track-info {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ min-width: 0;
+}
+
+.track-info .cover {
+ width: 56px;
+ height: 56px;
+ border-radius: 4px;
+ background-color: var(--muted);
+ object-fit: cover;
+ flex-shrink: 0;
+}
+
+.track-info .details {
+ min-width: 0;
+}
+
+.track-info .details .title {
+ font-weight: 500;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.track-info .details .artist {
+ font-size: .8rem;
+ color: var(--muted-foreground);
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.player-controls {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: var(--spacing-sm);
+}
+
+.player-controls .buttons {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+}
+
+.player-controls .buttons button {
+ background: transparent;
+ border: none;
+ color: var(--muted-foreground);
+ cursor: pointer;
+ transition: all .2s cubic-bezier(0.4, 0, 0.2, 1);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ position: relative;
+}
+
+.player-controls .buttons button:hover {
+ color: var(--foreground);
+ background-color: var(--secondary);
+}
+
+.player-controls .buttons button.active {
+ color: var(--active-highlight);
+}
+
+.player-controls .buttons button#repeat-btn.repeat-one::after {
+ content: '1';
+ position: absolute;
+ font-size: 0.6rem;
+ font-weight: bold;
+ bottom: 4px;
+ right: 6px;
+}
+
+.player-controls .buttons .play-pause-btn {
+ background-color: var(--primary);
+ color: var(--primary-foreground);
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.player-controls .buttons .play-pause-btn:hover {
+ transform: scale(1.05);
+ background-color: var(--primary);
+ color: var(--primary-foreground);
+}
+
+.player-controls .progress-container {
+ width: 100%;
+ max-width: 500px;
+ display: flex;
+ align-items: center;
+ gap: .75rem;
+ font-size: .8rem;
+ color: var(--muted-foreground);
+}
+
+.progress-bar {
+ flex-grow: 1;
+ height: 4px;
+ background-color: var(--secondary);
+ border-radius: 2px;
+ cursor: pointer;
+ position: relative;
+}
+
+.progress-bar .progress-fill {
+ width: 0;
+ height: 100%;
+ background-color: var(--foreground);
+ border-radius: 2px;
+ transition: width .1s linear;
+ position: relative;
+}
+
+.progress-bar:hover .progress-fill {
+ background-color: var(--highlight);
+}
+
+.progress-bar:hover .progress-fill::after {
+ content: '';
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 12px;
+ height: 12px;
+ background-color: var(--highlight);
+ border-radius: 50%;
+}
+
+.volume-controls {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: .75rem;
+}
+
+.volume-controls button {
+ background: transparent;
+ border: none;
+ color: var(--muted-foreground);
+ cursor: pointer;
+ transition: color .2s;
+ padding: 0.5rem;
+ border-radius: var(--radius);
+}
+
+.volume-controls button:hover {
+ color: var(--foreground);
+ background-color: var(--secondary);
+}
+
+.volume-controls .volume-bar {
+ width: 100px;
+ height: 4px;
+ background-color: var(--secondary);
+ border-radius: 2px;
+ cursor: pointer;
+ position: relative;
+}
+
+.volume-controls .volume-bar .volume-fill {
+ width: 70%;
+ height: 100%;
+ background-color: var(--foreground);
+ border-radius: 2px;
+}
+
+.volume-controls .volume-bar:hover .volume-fill {
+ background-color: var(--highlight);
+}
+
+.volume-controls .volume-bar:hover .volume-fill::after {
+ content: '';
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 12px;
+ height: 12px;
+ background-color: var(--highlight);
+ border-radius: 50%;
+}
+
+#context-menu {
+ display: none;
+ position: absolute;
+ background-color: var(--card);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ padding: .5rem;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
+ z-index: 1000;
+ min-width: 160px;
+}
+
+#context-menu ul {
+ list-style: none;
+}
+
+#context-menu li {
+ padding: .5rem .75rem;
+ cursor: pointer;
+ border-radius: 4px;
+ transition: background-color .2s cubic-bezier(0.4, 0, 0.2, 1);
+ font-size: 0.9rem;
+}
+
+#context-menu li:hover {
+ background-color: var(--secondary);
+}
+
+#queue-modal-overlay {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, .7);
+ backdrop-filter: blur(4px);
+ z-index: 1000;
+ justify-content: center;
+ align-items: center;
+ animation: fadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#queue-modal {
+ background-color: var(--card);
+ width: 90%;
+ max-width: 500px;
+ max-height: 80vh;
+ border-radius: var(--radius);
+ display: flex;
+ flex-direction: column;
+ box-shadow: 0 20px 60px rgba(0, 0, 0, .8);
+ animation: scaleIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+@keyframes scaleIn {
+ from {
+ transform: scale(0.95);
+ opacity: 0;
+ }
+ to {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+#queue-modal-header {
+ padding: 1rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 1px solid var(--border);
+}
+
+#queue-modal-header h3 {
+ margin: 0;
+}
+
+#queue-modal-header button {
+ background: transparent;
+ border: none;
+ color: var(--muted-foreground);
+ font-size: 1.5rem;
+ cursor: pointer;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: var(--radius);
+ transition: all .2s;
+}
+
+#queue-modal-header button:hover {
+ background-color: var(--secondary);
+ color: var(--foreground);
+}
+
+#queue-list {
+ overflow-y: auto;
+ padding: .5rem;
+}
+
+.placeholder-text {
+ padding: 2rem 1rem;
+ color: var(--muted-foreground);
+ text-align: center;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: .5; }
+}
+
+.placeholder-text.loading {
+ animation: pulse 1.5s infinite ease-in-out;
+}
+
+#api-instance-manager {
+ margin-top: 1rem;
+ padding-top: 1rem;
+ border-top: 1px solid var(--border);
+}
+
+#api-instance-list {
+ list-style: none;
+ margin-bottom: 1rem;
+}
+
+#api-instance-list li {
+ display: flex;
+ align-items: center;
+ gap: .75rem;
+ padding: .75rem;
+ background-color: var(--secondary);
+ border-radius: var(--radius);
+ margin-bottom: .5rem;
+}
+
+#api-instance-list li .instance-url {
+ flex-grow: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ font-size: .9rem;
+}
+
+#api-instance-list li .controls {
+ display: flex;
+ gap: .5rem;
+}
+
+#api-instance-list li button {
+ background: transparent;
+ border: none;
+ color: var(--muted-foreground);
+ cursor: pointer;
+ padding: 4px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all .2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+#api-instance-list li button:hover {
+ color: var(--foreground);
+ background-color: var(--muted);
+}
+
+#api-instance-list li button:disabled {
+ opacity: .3;
+ cursor: not-allowed;
+}
+
+#add-instance-form {
+ display: flex;
+ gap: .75rem;
+}
+
+#add-instance-form input {
+ flex-grow: 1;
+ padding: .5rem .75rem;
+ background-color: var(--input);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--foreground);
+}
+
+#add-instance-form button {
+ padding: .5rem 1rem;
+ background-color: var(--primary);
+ color: var(--primary-foreground);
+ border: none;
+ border-radius: var(--radius);
+ cursor: pointer;
+ font-weight: 500;
+ transition: opacity .2s;
+}
+
+#add-instance-form button:hover {
+ opacity: .9;
+}
+
+#sidebar-overlay {
+ display: none;
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, .5);
+ z-index: 1999;
+ backdrop-filter: blur(2px);
+}
+
+#about-section {
+ margin-top: 2rem;
+ padding-top: 2rem;
+ border-top: 1px solid var(--border);
+}
+
+.about-content {
+ padding: 1rem 0;
+}
+
+.about-description {
+ color: var(--foreground);
+ line-height: 1.6;
+ margin-bottom: 1.5rem;
+}
+
+.about-features,
+.about-tech {
+ margin-bottom: 1.5rem;
+}
+
+.about-features h4,
+.about-tech h4 {
+ font-size: 1rem;
+ font-weight: 600;
+ margin-bottom: 0.75rem;
+ color: var(--foreground);
+}
+
+.about-features ul {
+ list-style: none;
+ padding: 0;
+}
+
+.about-features li {
+ padding: 0.5rem 0;
+ padding-left: 1.5rem;
+ position: relative;
+ color: var(--foreground);
+ line-height: 1.5;
+}
+
+.about-features li::before {
+ content: "✓";
+ position: absolute;
+ left: 0;
+ color: var(--highlight);
+ font-weight: bold;
+}
+
+.about-tech p {
+ color: var(--muted-foreground);
+ font-family: 'Courier New', monospace;
+ font-size: 0.9rem;
+}
+
+.about-links {
+ display: flex;
+ gap: 1rem;
+ margin: 1.5rem 0;
+ flex-wrap: wrap;
+}
+
+.github-link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.75rem 1.25rem;
+ background-color: var(--card);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ color: var(--foreground);
+ text-decoration: none;
+ font-weight: 500;
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.github-link:hover {
+ background-color: var(--secondary);
+ border-color: var(--highlight);
+ transform: translateY(-2px);
+}
+
+.github-link svg {
+ flex-shrink: 0;
+}
+
+.about-footer {
+ margin-top: 2rem;
+ padding-top: 1.5rem;
+ border-top: 1px solid var(--border);
+}
+
+.about-footer p {
+ margin: 0.5rem 0;
+ font-size: 0.9rem;
+}
+
+.about-footer .version {
+ color: var(--foreground);
+ font-weight: 600;
+}
+
+.about-footer .license {
+ color: var(--muted-foreground);
+}
+
+.about-footer .disclaimer {
+ color: var(--muted-foreground);
+ font-size: 0.8rem;
+ font-style: italic;
+ margin-top: 1rem;
+ padding: 0.75rem;
+ background-color: var(--secondary);
+ border-radius: var(--radius);
+ border-left: 3px solid var(--muted-foreground);
+}
+
+.skeleton {
+ background: linear-gradient(
+ 90deg,
+ var(--secondary) 0%,
+ var(--muted) 50%,
+ var(--secondary) 100%
+ );
+ background-size: 200% 100%;
+ animation: skeleton-loading 1.5s ease-in-out infinite;
+ border-radius: var(--radius);
+}
+
+@keyframes skeleton-loading {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
+}
+
+.skeleton-track {
+ display: grid;
+ grid-template-columns: 40px 1fr auto;
+ align-items: center;
+ gap: var(--spacing-md);
+ padding: var(--spacing-sm);
+ margin-bottom: 2px;
+}
+
+.skeleton-track-number {
+ width: 24px;
+ height: 20px;
+ margin: 0 auto;
+}
+
+.skeleton-track-info {
+ display: flex;
+ align-items: center;
+ gap: var(--spacing-md);
+ min-width: 0;
+}
+
+.skeleton-track-cover {
+ width: 40px;
+ height: 40px;
+ flex-shrink: 0;
+ border-radius: 4px;
+}
+
+.skeleton-track-details {
+ flex: 1;
+ min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: var(--spacing-xs);
+}
+
+.skeleton-track-title {
+ height: 16px;
+ width: 60%;
+ max-width: 200px;
+}
+
+.skeleton-track-artist {
+ height: 14px;
+ width: 40%;
+ max-width: 150px;
+}
+
+.skeleton-track-duration {
+ width: 40px;
+ height: 14px;
+}
+
+.skeleton-card {
+ background-color: var(--card);
+ border-radius: var(--radius);
+ padding: var(--spacing-md);
+}
+
+.skeleton-card-image {
+ width: 100%;
+ aspect-ratio: 1/1;
+ margin-bottom: var(--spacing-md);
+ border-radius: calc(var(--radius) - 4px);
+}
+
+.skeleton-card.artist .skeleton-card-image {
+ border-radius: 50%;
+}
+
+.skeleton-card-title {
+ height: 18px;
+ width: 80%;
+ margin-bottom: var(--spacing-xs);
+}
+
+.skeleton-card-subtitle {
+ height: 14px;
+ width: 60%;
+}
+
+.skeleton-container {
+ width: 100%;
+}
+
+@media (max-width: 1024px) {
+ .app-container {
+ grid-template-columns: 240px 1fr;
+ }
+
+ .card-grid {
+ grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+ gap: var(--spacing-md);
+ }
+
+ .detail-header-info .title {
+ font-size: 3rem;
+ }
+
+ .main-content {
+ padding: var(--spacing-lg);
+ }
+}
+
+@media (max-width: 768px) {
+ .app-container {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto 1fr auto;
+ grid-template-areas:
+ "header"
+ "main"
+ "player";
+ }
+
+ .main-content {
+ padding: var(--spacing-md);
+ grid-area: main;
+ }
+
+ .main-header {
+ grid-area: header;
+ padding: var(--spacing-md) var(--spacing-md) 0 var(--spacing-md);
+ margin-bottom: var(--spacing-md);
+ }
+
+ .sidebar {
+ position: fixed;
top: 0;
left: 0;
- right: 0;
- bottom: 0;
- background-color: var(--secondary);
- transition: .4s;
- border-radius: 24px;
+ height: 100%;
+ transform: translateX(-100%);
+ box-shadow: 0 0 20px rgba(0, 0, 0, .5);
}
-
- .slider:before {
- position: absolute;
- content: "";
- height: 16px;
- width: 16px;
- left: 4px;
- bottom: 4px;
- background-color: var(--foreground);
- transition: .4s;
- border-radius: 50%;
+
+ .sidebar.is-open {
+ transform: translateX(0);
}
-
- input:checked + .slider {
- background-color: var(--primary);
+
+ .hamburger-menu {
+ display: block;
}
-
- input:checked + .slider:before {
- transform: translateX(16px);
- background-color: var(--primary-foreground);
+
+ #sidebar-overlay.is-visible {
+ display: block;
}
-
- .btn-secondary {
- padding: 0.5rem 1rem;
- background-color: var(--secondary);
- color: var(--foreground);
- border: none;
- border-radius: var(--radius);
- cursor: pointer;
- font-weight: 500;
- transition: background-color 0.2s;
+
+ .search-bar {
+ max-width: none;
}
-
- .btn-secondary:hover {
- background-color: var(--muted);
+
+ .content-section {
+ margin-bottom: var(--spacing-xl);
}
-
- .btn-secondary:disabled {
- opacity: 0.5;
- cursor: not-allowed;
+
+ .section-title {
+ font-size: 1.5rem;
+ margin-bottom: var(--spacing-md);
}
-
- .now-playing-bar .track-info {
- display: flex;
- align-items: center;
- gap: 1rem;
- min-width: 0;
+
+ .card-grid {
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: var(--spacing-md);
}
-
- .track-info .cover {
- width: 56px;
- height: 56px;
- border-radius: 4px;
- background-color: var(--muted);
- object-fit: cover;
- flex-shrink: 0;
- }
-
- .track-info .details {
- min-width: 0;
- }
-
- .track-info .details .title {
- font-weight: 500;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .track-info .details .artist {
- font-size: .8rem;
- color: var(--muted-foreground);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .player-controls {
- display: flex;
+
+ .detail-header {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--spacing-lg);
+ padding-bottom: var(--spacing-md);
+ margin-bottom: var(--spacing-lg);
+ }
+
+ .detail-header-image {
+ width: 150px;
+ height: 150px;
+ }
+
+ .detail-header-info .title {
+ font-size: 2rem;
+ line-height: 1.2;
+ }
+
+ .now-playing-bar {
+ grid-template-columns: 1fr;
+ grid-template-rows: auto auto;
+ gap: var(--spacing-md);
+ padding: var(--spacing-md);
+ height: auto;
+ }
+
+ .now-playing-bar .track-info {
+ grid-column: 1;
+ grid-row: 1;
+ width: 100%;
+ justify-content: flex-start;
+ }
+
+ .track-info .cover {
+ width: 48px;
+ height: 48px;
+ }
+
+ .track-info .details {
+ max-width: calc(100% - 64px);
+ }
+
+ .now-playing-bar .player-controls {
+ grid-column: 1;
+ grid-row: 2;
+ width: 100%;
flex-direction: column;
- align-items: center;
gap: var(--spacing-sm);
}
-
- .player-controls .buttons {
- display: flex;
- align-items: center;
- gap: var(--spacing-md);
- }
-
- .player-controls .buttons button {
- background: transparent;
- border: none;
- color: var(--muted-foreground);
- cursor: pointer;
- transition: all .2s;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 32px;
- height: 32px;
- border-radius: 50%;
- position: relative;
- }
-
- .player-controls .buttons button:hover {
- color: var(--foreground);
- background-color: var(--secondary);
- }
-
- .player-controls .buttons button.active {
- color: var(--active-highlight);
- }
-
- .player-controls .buttons button#repeat-btn.repeat-one::after {
- content: '1';
- position: absolute;
- font-size: 0.6rem;
- font-weight: bold;
- bottom: 4px;
- right: 6px;
- }
-
- .player-controls .buttons .play-pause-btn {
- background-color: var(--primary);
- color: var(--primary-foreground);
- width: 36px;
- height: 36px;
- border-radius: 50%;
- display: flex;
- justify-content: center;
- align-items: center;
- }
-
- .player-controls .buttons .play-pause-btn:hover {
- transform: scale(1.05);
- background-color: var(--primary);
- color: var(--primary-foreground);
- }
-
+
.player-controls .progress-container {
- width: 100%;
- max-width: 500px;
- display: flex;
- align-items: center;
- gap: .75rem;
- font-size: .8rem;
- color: var(--muted-foreground);
+ max-width: none;
}
-
- .progress-bar {
- flex-grow: 1;
- height: 4px;
- background-color: var(--secondary);
- border-radius: 2px;
- cursor: pointer;
- }
-
- .progress-bar .progress-fill {
- width: 0;
- height: 100%;
- background-color: var(--foreground);
- border-radius: 2px;
- transition: width .1s linear;
- }
-
- .progress-bar:hover .progress-fill {
- background-color: var(--highlight);
- }
-
- .volume-controls {
- display: flex;
- justify-content: flex-end;
- align-items: center;
- gap: .75rem;
- }
-
- .volume-controls button {
- background: transparent;
- border: none;
- color: var(--muted-foreground);
- cursor: pointer;
- transition: color .2s;
- }
-
- .volume-controls button:hover {
- color: var(--foreground);
- }
-
- .volume-controls .volume-bar {
- width: 100px;
- height: 4px;
- background-color: var(--secondary);
- border-radius: 2px;
- cursor: pointer;
- }
-
- .volume-controls .volume-bar .volume-fill {
- width: 70%;
- height: 100%;
- background-color: var(--foreground);
- border-radius: 2px;
- }
-
- .volume-controls .volume-bar:hover .volume-fill {
- background-color: var(--highlight);
- }
-
- #context-menu {
+
+ .now-playing-bar .volume-controls {
display: none;
- position: absolute;
- background-color: var(--card);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- padding: .5rem;
- box-shadow: 0 4px 12px rgba(0, 0, 0, .5);
- z-index: 1000;
}
-
- #context-menu ul {
- list-style: none;
- }
-
- #context-menu li {
- padding: .5rem .75rem;
- cursor: pointer;
- border-radius: 4px;
- transition: background-color .2s ease-in-out;
- }
-
- #context-menu li:hover {
- background-color: var(--secondary);
- }
-
- #queue-modal-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, .7);
- z-index: 1000;
- justify-content: center;
- align-items: center;
- }
-
- #queue-modal {
- background-color: var(--card);
- width: 90%;
- max-width: 500px;
- max-height: 80vh;
- border-radius: var(--radius);
- display: flex;
- flex-direction: column;
- }
-
- #queue-modal-header {
- padding: 1rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
- border-bottom: 1px solid var(--border);
- }
-
- #queue-modal-header h3 {
- margin: 0;
- }
-
- #queue-modal-header button {
- background: transparent;
- border: none;
- color: var(--muted-foreground);
- font-size: 1.5rem;
- cursor: pointer;
- }
-
- #queue-list {
- overflow-y: auto;
- padding: .5rem;
- }
-
- .placeholder-text {
- padding: 1rem;
- color: var(--muted-foreground);
- text-align: center;
- }
-
- @keyframes pulse {
- 0%, 100% { opacity: 1; }
- 50% { opacity: .5; }
- }
-
- .placeholder-text.loading {
- animation: pulse 1.5s infinite ease-in-out;
- }
-
- #api-instance-manager {
- margin-top: 1rem;
- padding-top: 1rem;
- border-top: 1px solid var(--border);
- }
-
- #api-instance-list {
- list-style: none;
- margin-bottom: 1rem;
- }
-
- #api-instance-list li {
- display: flex;
- align-items: center;
- gap: .75rem;
- padding: .75rem;
- background-color: var(--secondary);
- border-radius: var(--radius);
- margin-bottom: .5rem;
- }
-
- #api-instance-list li .instance-url {
- flex-grow: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- font-size: .9rem;
- }
-
- #api-instance-list li .controls {
- display: flex;
- gap: .5rem;
- }
-
- #api-instance-list li button {
- background: transparent;
- border: none;
- color: var(--muted-foreground);
- cursor: pointer;
- padding: 4px;
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: 4px;
- transition: all .2s ease-in-out;
- }
-
- #api-instance-list li button:hover {
- color: var(--foreground);
- background-color: var(--muted);
- }
-
- #api-instance-list li button:disabled {
- opacity: .3;
- cursor: not-allowed;
- }
-
- #add-instance-form {
- display: flex;
- gap: .75rem;
- }
-
- #add-instance-form input {
- flex-grow: 1;
- padding: .5rem .75rem;
- background-color: var(--input);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--foreground);
- }
-
- #add-instance-form button {
- padding: .5rem 1rem;
- background-color: var(--primary);
- color: var(--primary-foreground);
- border: none;
- border-radius: var(--radius);
- cursor: pointer;
- font-weight: 500;
- transition: opacity .2s;
- }
-
- #add-instance-form button:hover {
- opacity: .9;
- }
-
- #sidebar-overlay {
- display: none;
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, .5);
- z-index: 1999;
- }
-
- #about-section {
- margin-top: 2rem;
- padding-top: 2rem;
- border-top: 1px solid var(--border);
- }
-
- .about-content {
- padding: 1rem 0;
- }
-
- .about-description {
- color: var(--foreground);
- line-height: 1.6;
- margin-bottom: 1.5rem;
- }
-
- .about-features,
- .about-tech {
- margin-bottom: 1.5rem;
- }
-
- .about-features h4,
- .about-tech h4 {
- font-size: 1rem;
- font-weight: 600;
- margin-bottom: 0.75rem;
- color: var(--foreground);
- }
-
- .about-features ul {
- list-style: none;
- padding: 0;
- }
-
- .about-features li {
- padding: 0.5rem 0;
- padding-left: 1.5rem;
- position: relative;
- color: var(--foreground);
- line-height: 1.5;
- }
-
- .about-features li::before {
- content: "✓";
- position: absolute;
- left: 0;
- color: var(--highlight);
- font-weight: bold;
- }
-
- .about-tech p {
- color: var(--muted-foreground);
- font-family: 'Courier New', monospace;
- font-size: 0.9rem;
- }
-
+
.about-links {
- display: flex;
- gap: 1rem;
- margin: 1.5rem 0;
- flex-wrap: wrap;
+ flex-direction: column;
}
-
+
.github-link {
- display: inline-flex;
- align-items: center;
- gap: 0.5rem;
- padding: 0.75rem 1.25rem;
- background-color: var(--card);
- border: 1px solid var(--border);
- border-radius: var(--radius);
- color: var(--foreground);
- text-decoration: none;
- font-weight: 500;
- transition: all 0.2s;
+ width: 100%;
+ justify-content: center;
}
-
- .github-link:hover {
- background-color: var(--secondary);
- border-color: var(--highlight);
- transform: translateY(-2px);
+
+ .setting-item {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--spacing-md);
}
-
- .github-link svg {
- flex-shrink: 0;
+
+ .setting-item .info {
+ width: 100%;
}
+}
- .about-footer {
- margin-top: 2rem;
- padding-top: 1.5rem;
- border-top: 1px solid var(--border);
+@media (max-width: 480px) {
+ .card-grid {
+ grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+ gap: var(--spacing-sm);
}
-
- .about-footer p {
- margin: 0.5rem 0;
+
+ .section-title {
+ font-size: 1.25rem;
+ }
+
+ .detail-header-info .title {
+ font-size: 1.75rem;
+ }
+
+ .search-tab {
+ padding: var(--spacing-sm) var(--spacing-md);
font-size: 0.9rem;
}
-
- .about-footer .version {
- color: var(--foreground);
- font-weight: 600;
- }
-
- .about-footer .license {
- color: var(--muted-foreground);
- }
-
- .about-footer .disclaimer {
- color: var(--muted-foreground);
- font-size: 0.8rem;
- font-style: italic;
- margin-top: 1rem;
- padding: 0.75rem;
- background-color: var(--secondary);
- border-radius: var(--radius);
- border-left: 3px solid var(--muted-foreground);
- }
-
- .skeleton {
- background: linear-gradient(
- 90deg,
- var(--secondary) 0%,
- var(--muted) 50%,
- var(--secondary) 100%
- );
- background-size: 200% 100%;
- animation: skeleton-loading 1.5s ease-in-out infinite;
- border-radius: var(--radius);
- }
-
- @keyframes skeleton-loading {
- 0% {
- background-position: 200% 0;
- }
- 100% {
- background-position: -200% 0;
- }
- }
-
- .skeleton-track {
- display: grid;
- grid-template-columns: 40px 1fr auto;
- align-items: center;
- gap: var(--spacing-md);
- padding: var(--spacing-sm);
- margin-bottom: 2px;
- }
-
- .skeleton-track-number {
- width: 24px;
- height: 20px;
- margin: 0 auto;
- }
-
- .skeleton-track-info {
- display: flex;
- align-items: center;
- gap: var(--spacing-md);
- min-width: 0;
- }
-
- .skeleton-track-cover {
- width: 40px;
- height: 40px;
- flex-shrink: 0;
- border-radius: 4px;
- }
-
- .skeleton-track-details {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-xs);
- }
-
- .skeleton-track-title {
- height: 16px;
- width: 60%;
- max-width: 200px;
- }
-
- .skeleton-track-artist {
- height: 14px;
- width: 40%;
- max-width: 150px;
- }
-
- .skeleton-track-duration {
- width: 40px;
- height: 14px;
- }
-
- .skeleton-card {
- background-color: var(--card);
- border-radius: var(--radius);
- padding: var(--spacing-md);
- }
-
- .skeleton-card-image {
- width: 100%;
- aspect-ratio: 1/1;
- margin-bottom: var(--spacing-md);
- border-radius: calc(var(--radius) - 4px);
- }
-
- .skeleton-card.artist .skeleton-card-image {
- border-radius: 50%;
- }
-
- .skeleton-card-title {
- height: 18px;
- width: 80%;
- margin-bottom: var(--spacing-xs);
- }
-
- .skeleton-card-subtitle {
- height: 14px;
- width: 60%;
- }
-
- .skeleton-detail-header {
- display: flex;
- align-items: flex-end;
- gap: var(--spacing-xl);
- margin-bottom: var(--spacing-2xl);
- padding-bottom: var(--spacing-xl);
- }
-
- .skeleton-detail-image {
- width: 200px;
- height: 200px;
- flex-shrink: 0;
- border-radius: var(--radius);
- }
-
- .skeleton-detail-image.artist {
- border-radius: 50%;
- }
-
- .skeleton-detail-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: var(--spacing-md);
- }
-
- .skeleton-detail-type {
- height: 16px;
- width: 60px;
- }
-
- .skeleton-detail-title {
- height: 48px;
- width: 70%;
- max-width: 400px;
- }
-
- .skeleton-detail-meta {
- height: 16px;
- width: 50%;
- max-width: 300px;
- }
-
- .skeleton-container {
- width: 100%;
- }
-
- .content-loaded {
- animation: contentFadeIn 0.3s ease-in-out;
- }
-
- @keyframes contentFadeIn {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
- }
-
- @media (max-width: 1024px) {
- .app-container {
- grid-template-columns: 240px 1fr;
- }
-
- .card-grid {
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
- gap: var(--spacing-md);
- }
-
- .detail-header-info .title {
- font-size: 3rem;
- }
-
- .main-content {
- padding: var(--spacing-lg);
- }
- }
-
- @media (max-width: 768px) {
- .app-container {
- grid-template-columns: 1fr;
- grid-template-rows: auto 1fr auto;
- grid-template-areas:
- "header"
- "main"
- "player";
- }
-
- .main-content {
- padding: var(--spacing-md);
- grid-area: main;
- }
-
- .main-header {
- grid-area: header;
- padding: var(--spacing-md) var(--spacing-md) 0 var(--spacing-md);
- margin-bottom: var(--spacing-md);
- }
-
- .sidebar {
- position: fixed;
- top: 0;
- left: 0;
- height: 100%;
- transform: translateX(-100%);
- box-shadow: 0 0 20px rgba(0, 0, 0, .5);
- }
-
- .sidebar.is-open {
- transform: translateX(0);
- }
-
- .hamburger-menu {
- display: block;
- }
-
- #sidebar-overlay.is-visible {
- display: block;
- }
-
- .search-bar {
- max-width: none;
- }
-
- .content-section {
- margin-bottom: var(--spacing-xl);
- }
-
- .section-title {
- font-size: 1.5rem;
- margin-bottom: var(--spacing-md);
- }
-
- .card-grid {
- grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
- gap: var(--spacing-md);
- }
-
- .detail-header {
- flex-direction: column;
- align-items: flex-start;
- gap: var(--spacing-lg);
- padding-bottom: var(--spacing-md);
- margin-bottom: var(--spacing-lg);
- }
-
- .detail-header-image {
- width: 150px;
- height: 150px;
- }
-
- .detail-header-info .title {
- font-size: 2rem;
- line-height: 1.2;
- }
-
- .skeleton-detail-header {
- flex-direction: column;
- align-items: flex-start;
- gap: var(--spacing-lg);
- }
-
- .skeleton-detail-image {
- width: 150px;
- height: 150px;
- }
-
- .skeleton-detail-title {
- height: 40px;
- width: 90%;
- }
-
- .now-playing-bar {
- grid-template-columns: 1fr;
- grid-template-rows: auto auto;
- gap: var(--spacing-md);
- padding: var(--spacing-md);
- height: auto;
- place-items: center;
- }
-
- .now-playing-bar .track-info {
- grid-column: 1;
- grid-row: 1;
- width: 100%;
- justify-content: flex-start;
- }
-
- .track-info .cover {
- width: 48px;
- height: 48px;
- }
-
- .track-info .details {
- max-width: calc(100% - 64px);
- }
-
- .track-info .details .artist {
- display: block;
- }
-
- .now-playing-bar .player-controls {
- grid-column: 1;
- grid-row: 2;
- width: 100%;
- flex-direction: column;
- gap: var(--spacing-sm);
- }
-
- .player-controls .progress-container {
- display: flex;
- max-width: none;
- }
-
- .now-playing-bar .volume-controls {
- display: none;
- }
-
- .about-links {
- flex-direction: column;
- }
-
- .github-link {
- width: 100%;
- justify-content: center;
- }
-
- .setting-item {
- flex-direction: column;
- align-items: flex-start;
- gap: var(--spacing-md);
- }
-
- .setting-item .info {
- width: 100%;
- }
- }
-
- @media (max-width: 480px) {
- .card-grid {
- grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
- gap: var(--spacing-sm);
- }
-
- .section-title {
- font-size: 1.25rem;
- }
-
- .detail-header-info .title {
- font-size: 1.75rem;
- }
-
- .search-tab {
- padding: var(--spacing-sm) var(--spacing-md);
- font-size: 0.9rem;
- }
- }
\ No newline at end of file
+}
\ No newline at end of file