video covers
This commit is contained in:
parent
71b65e70a8
commit
2e1367e5c2
7 changed files with 250 additions and 49 deletions
26
index.html
26
index.html
|
|
@ -376,12 +376,30 @@
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
class="lucide lucide-repeat-icon lucide-repeat"
|
class="lucide lucide-repeat-icon lucide-repeat"
|
||||||
>
|
>
|
||||||
<path d="m17 2 4 4-4 4" />
|
<path d="m17 2 4 4-4 4"></path>
|
||||||
<path d="M3 11v-1a4 4 0 0 1 4-4h14" />
|
<path d="M3 11v-1a4 4 0 0 1 4-4h14"></path>
|
||||||
<path d="m7 22-4-4 4-4" />
|
<path d="m7 22-4-4 4-4"></path>
|
||||||
<path d="M21 13v1a4 4 0 0 1-4 4H3" />
|
<path d="M21 13v1a4 4 0 0 1-4 4H3"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="fs-quality-btn" class="fs-quality-btn" title="Quality" style="display: none">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M12 20h9"></path>
|
||||||
|
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z"></path>
|
||||||
|
</svg>
|
||||||
|
<span class="fs-quality-label">Auto</span>
|
||||||
|
</button>
|
||||||
|
<div id="fs-quality-menu" class="fs-quality-menu" style="display: none"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="fullscreen-volume-container">
|
<div class="fullscreen-volume-container">
|
||||||
<button id="fs-volume-btn" class="fs-volume-btn" title="Mute">
|
<button id="fs-volume-btn" class="fs-volume-btn" title="Mute">
|
||||||
|
|
|
||||||
16
js/api.js
16
js/api.js
|
|
@ -1547,6 +1547,22 @@ export class LosslessAPI {
|
||||||
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
|
return `https://resources.tidal.com/images/${formattedId}/${size}x${size}.jpg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVideoCoverUrl(imageId, size = '1280') {
|
||||||
|
if (!imageId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof imageId === 'string' &&
|
||||||
|
(imageId.startsWith('http') || imageId.startsWith('blob:') || imageId.startsWith('assets/'))
|
||||||
|
) {
|
||||||
|
return imageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedId = String(imageId).replace(/-/g, '/');
|
||||||
|
return `https://resources.tidal.com/images/${formattedId}/${size}x720.jpg`;
|
||||||
|
}
|
||||||
|
|
||||||
async clearCache() {
|
async clearCache() {
|
||||||
await this.cache.clear();
|
await this.cache.clear();
|
||||||
this.streamCache.clear();
|
this.streamCache.clear();
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,19 @@ export class MusicAPI {
|
||||||
return this.tidalAPI.getCoverUrl(id, size);
|
return this.tidalAPI.getCoverUrl(id, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getVideoCoverUrl(imageId, size = '1280') {
|
||||||
|
if (!imageId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof imageId === 'string' && imageId.startsWith('blob:')) {
|
||||||
|
return imageId;
|
||||||
|
}
|
||||||
|
if (typeof imageId === 'string' && imageId.startsWith('q:')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.tidalAPI.getVideoCoverUrl(imageId, size);
|
||||||
|
}
|
||||||
|
|
||||||
async getVideoArtwork(title, artist) {
|
async getVideoArtwork(title, artist) {
|
||||||
const cacheKey = `${title}-${artist}`.toLowerCase();
|
const cacheKey = `${title}-${artist}`.toLowerCase();
|
||||||
if (this.videoArtworkCache.has(cacheKey)) {
|
if (this.videoArtworkCache.has(cacheKey)) {
|
||||||
|
|
|
||||||
81
js/player.js
81
js/player.js
|
|
@ -452,7 +452,7 @@ export class Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
setupHlsVideo(video, result, fallbackImg) {
|
setupHlsVideo(video, result, fallbackImg) {
|
||||||
const url = result.videoUrl || result.hlsUrl || result; // Allow passing just the URL
|
const url = result.videoUrl || result.hlsUrl || result;
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
|
|
||||||
if (this.hls) {
|
if (this.hls) {
|
||||||
|
|
@ -460,12 +460,20 @@ export class Player {
|
||||||
this.hls = null;
|
this.hls = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const qualityBtn = document.getElementById('fs-quality-btn');
|
||||||
|
const qualityMenu = document.getElementById('fs-quality-menu');
|
||||||
|
if (qualityBtn) qualityBtn.style.display = 'none';
|
||||||
|
if (qualityMenu) qualityMenu.style.display = 'none';
|
||||||
|
|
||||||
if (typeof url === 'string' && (url.includes('.m3u8') || url.includes('application/vnd.apple.mpegurl'))) {
|
if (typeof url === 'string' && (url.includes('.m3u8') || url.includes('application/vnd.apple.mpegurl'))) {
|
||||||
if (Hls.isSupported()) {
|
if (Hls.isSupported()) {
|
||||||
this.hls = new Hls();
|
this.hls = new Hls();
|
||||||
this.hls.loadSource(url);
|
this.hls.loadSource(url);
|
||||||
this.hls.attachMedia(video);
|
this.hls.attachMedia(video);
|
||||||
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {});
|
this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||||
|
video.play().catch(() => {});
|
||||||
|
this.setupVideoQualitySelector();
|
||||||
|
});
|
||||||
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
this.hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
if (data.fatal) {
|
if (data.fatal) {
|
||||||
console.warn('HLS fatal error:', data.type);
|
console.warn('HLS fatal error:', data.type);
|
||||||
|
|
@ -491,6 +499,75 @@ export class Player {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupVideoQualitySelector() {
|
||||||
|
if (!this.hls || !this.hls.levels || this.hls.levels.length === 0) return;
|
||||||
|
|
||||||
|
const qualityBtn = document.getElementById('fs-quality-btn');
|
||||||
|
const qualityMenu = document.getElementById('fs-quality-menu');
|
||||||
|
if (!qualityBtn || !qualityMenu) return;
|
||||||
|
|
||||||
|
const levels = this.hls.levels;
|
||||||
|
const qualityLabels = [
|
||||||
|
'Auto',
|
||||||
|
...levels.map((level, i) => {
|
||||||
|
const height = level.height || 0;
|
||||||
|
const bandwidth = level.bitrate || 0;
|
||||||
|
if (height >= 1080) return '1080p';
|
||||||
|
if (height >= 720) return '720p';
|
||||||
|
if (height >= 480) return '480p';
|
||||||
|
if (height >= 360) return '360p';
|
||||||
|
if (height >= 180) return '180p';
|
||||||
|
return `${Math.round(bandwidth / 1000)}k`;
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const updateQualityMenu = () => {
|
||||||
|
const currentLevel = this.hls.currentLevel;
|
||||||
|
qualityMenu.innerHTML = qualityLabels
|
||||||
|
.map((label, i) => {
|
||||||
|
const isActive = currentLevel === i - 1 || (i === 0 && currentLevel === -1);
|
||||||
|
return `<button class="fs-quality-option ${isActive ? 'active' : ''}" data-level="${i - 1}">${label}</button>`;
|
||||||
|
})
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
qualityMenu.querySelectorAll('.fs-quality-option').forEach((btn) => {
|
||||||
|
btn.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const level = parseInt(btn.dataset.level);
|
||||||
|
this.hls.currentLevel = level;
|
||||||
|
const labelSpan = qualityBtn.querySelector('.fs-quality-label');
|
||||||
|
if (labelSpan) labelSpan.textContent = level === -1 ? 'Auto' : qualityLabels[level + 1] || 'Auto';
|
||||||
|
qualityMenu.style.display = 'none';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
qualityBtn.style.display = 'flex';
|
||||||
|
qualityBtn.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const isVisible = qualityMenu.style.display === 'block';
|
||||||
|
qualityMenu.style.display = isVisible ? 'none' : 'block';
|
||||||
|
if (!isVisible) {
|
||||||
|
updateQualityMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.hls.on(Hls.Events.LEVEL_SWITCHED, () => {
|
||||||
|
updateQualityMenu();
|
||||||
|
const labelSpan = qualityBtn.querySelector('.fs-quality-label');
|
||||||
|
if (labelSpan) {
|
||||||
|
const currentLevel = this.hls.currentLevel;
|
||||||
|
labelSpan.textContent = currentLevel === -1 ? 'Auto' : qualityLabels[currentLevel + 1] || 'Auto';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', () => {
|
||||||
|
qualityMenu.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
qualityMenu.onclick = (e) => e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
async playVideo(video) {
|
async playVideo(video) {
|
||||||
if (!video) return;
|
if (!video) return;
|
||||||
const videoTrack = {
|
const videoTrack = {
|
||||||
|
|
|
||||||
|
|
@ -251,6 +251,12 @@ export function initializeUIInteractions(player, api, ui) {
|
||||||
? `title="Blocked: ${contentBlockingSettings.isTrackBlocked(track.id) ? 'Track blocked' : contentBlockingSettings.isArtistBlocked(track.artist?.id) ? 'Artist blocked' : 'Album blocked'}"`
|
? `title="Blocked: ${contentBlockingSettings.isTrackBlocked(track.id) ? 'Track blocked' : contentBlockingSettings.isArtistBlocked(track.artist?.id) ? 'Artist blocked' : 'Album blocked'}"`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
const isVideo = track.type === 'video';
|
||||||
|
const coverUrl =
|
||||||
|
isVideo && track.imageId
|
||||||
|
? api.getVideoCoverUrl(track.imageId)
|
||||||
|
: api.getCoverUrl(track.album?.cover);
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="queue-track-item ${isPlaying ? 'playing' : ''} ${isBlocked ? 'blocked' : ''}" data-queue-index="${index}" data-track-id="${track.id}" draggable="${isBlocked ? 'false' : 'true'}" ${blockedTitle}>
|
<div class="queue-track-item ${isPlaying ? 'playing' : ''} ${isBlocked ? 'blocked' : ''}" data-queue-index="${index}" data-track-id="${track.id}" draggable="${isBlocked ? 'false' : 'true'}" ${blockedTitle}>
|
||||||
<div class="drag-handle">
|
<div class="drag-handle">
|
||||||
|
|
@ -260,7 +266,7 @@ export function initializeUIInteractions(player, api, ui) {
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="track-item-info">
|
<div class="track-item-info">
|
||||||
<img src="${api.getCoverUrl(track.album?.cover)}"
|
<img src="${coverUrl}"
|
||||||
class="track-item-cover" loading="lazy">
|
class="track-item-cover" loading="lazy">
|
||||||
<div class="track-item-details">
|
<div class="track-item-details">
|
||||||
<div class="title">${escapeHtml(trackTitle)} ${qualityBadge}</div>
|
<div class="title">${escapeHtml(trackTitle)} ${qualityBadge}</div>
|
||||||
|
|
|
||||||
60
js/ui.js
60
js/ui.js
|
|
@ -348,9 +348,19 @@ export class UIRenderer {
|
||||||
let trackImageHTML = '';
|
let trackImageHTML = '';
|
||||||
if (showCover) {
|
if (showCover) {
|
||||||
if (isVideo && this.currentPage === 'playlist') {
|
if (isVideo && this.currentPage === 'playlist') {
|
||||||
|
const videoCoverUrl = this.api.getVideoCoverUrl(track.imageId);
|
||||||
|
if (videoCoverUrl) {
|
||||||
|
trackImageHTML = `<img src="${videoCoverUrl}" alt="" class="track-item-cover" loading="lazy">`;
|
||||||
|
} else {
|
||||||
trackImageHTML = `<div class="track-item-cover video-icon-placeholder" style="display: flex; align-items: center; justify-content: center; background: var(--secondary);"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.7;"><path d="m22 8-6 4 6 4V8Z"/><rect width="14" height="12" x="2" y="6" rx="2" ry="2"/></svg></div>`;
|
trackImageHTML = `<div class="track-item-cover video-icon-placeholder" style="display: flex; align-items: center; justify-content: center; background: var(--secondary);"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity: 0.7;"><path d="m22 8-6 4 6 4V8Z"/><rect width="14" height="12" x="2" y="6" rx="2" ry="2"/></svg></div>`;
|
||||||
|
}
|
||||||
} else if (isVideo && (this.currentPage === 'search' || this.currentPage === 'library')) {
|
} else if (isVideo && (this.currentPage === 'search' || this.currentPage === 'library')) {
|
||||||
|
const videoCoverUrl = this.api.getVideoCoverUrl(track.imageId);
|
||||||
|
if (videoCoverUrl) {
|
||||||
|
trackImageHTML = `<img src="${videoCoverUrl}" alt="" class="track-item-cover" loading="lazy">`;
|
||||||
|
} else {
|
||||||
trackImageHTML = `<div class="track-item-cover video-icon-placeholder" style="display: flex; align-items: center; justify-content: center; background: var(--secondary);"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="opacity: 0.7;"><path d="M8 5v14l11-7z"/></svg></div>`;
|
trackImageHTML = `<div class="track-item-cover video-icon-placeholder" style="display: flex; align-items: center; justify-content: center; background: var(--secondary);"><svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" style="opacity: 0.7;"><path d="M8 5v14l11-7z"/></svg></div>`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
trackImageHTML = this.getCoverHTML(
|
trackImageHTML = this.getCoverHTML(
|
||||||
track.image || track.cover || track.album?.cover,
|
track.image || track.cover || track.album?.cover,
|
||||||
|
|
@ -670,10 +680,13 @@ export class UIRenderer {
|
||||||
const duration = formatTime(video.duration);
|
const duration = formatTime(video.duration);
|
||||||
const artistName = getTrackArtists(video);
|
const artistName = getTrackArtists(video);
|
||||||
|
|
||||||
|
const videoCoverUrl = this.api.getVideoCoverUrl(video.imageId);
|
||||||
const cover = video.image || video.cover;
|
const cover = video.image || video.cover;
|
||||||
let imageHTML;
|
let imageHTML;
|
||||||
|
|
||||||
if (cover) {
|
if (videoCoverUrl) {
|
||||||
|
imageHTML = `<img src="${videoCoverUrl}" alt="${escapeHtml(video.title)}" class="card-image" loading="lazy">`;
|
||||||
|
} else if (cover) {
|
||||||
imageHTML = this.getCoverHTML(cover, escapeHtml(video.title));
|
imageHTML = this.getCoverHTML(cover, escapeHtml(video.title));
|
||||||
} else {
|
} else {
|
||||||
imageHTML = `<div class="card-image video-icon-placeholder" style="display: flex; align-items: center; justify-content: center; background: var(--secondary); aspect-ratio: 16/9; width: 100%;"><svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor" style="opacity: 0.7;"><path d="M8 5v14l11-7z"/></svg></div>`;
|
imageHTML = `<div class="card-image video-icon-placeholder" style="display: flex; align-items: center; justify-content: center; background: var(--secondary); aspect-ratio: 16/9; width: 100%;"><svg width="48" height="48" viewBox="0 0 24 24" fill="currentColor" style="opacity: 0.7;"><path d="M8 5v14l11-7z"/></svg></div>`;
|
||||||
|
|
@ -1012,6 +1025,11 @@ export class UIRenderer {
|
||||||
if (image) image.style.display = 'block';
|
if (image) image.style.display = 'block';
|
||||||
if (visualizerContainer) visualizerContainer.style.display = 'block';
|
if (visualizerContainer) visualizerContainer.style.display = 'block';
|
||||||
|
|
||||||
|
const qualityBtn = document.getElementById('fs-quality-btn');
|
||||||
|
const qualityMenu = document.getElementById('fs-quality-menu');
|
||||||
|
if (qualityBtn) qualityBtn.style.display = 'none';
|
||||||
|
if (qualityMenu) qualityMenu.style.display = 'none';
|
||||||
|
|
||||||
const videoCoverUrl = track.videoUrl || track.videoCoverUrl || track.album?.videoCoverUrl || null;
|
const videoCoverUrl = track.videoUrl || track.videoCoverUrl || track.album?.videoCoverUrl || null;
|
||||||
const coverUrl = videoCoverUrl || this.api.getCoverUrl(track.album?.cover, '1280');
|
const coverUrl = videoCoverUrl || this.api.getCoverUrl(track.album?.cover, '1280');
|
||||||
|
|
||||||
|
|
@ -1227,50 +1245,20 @@ export class UIRenderer {
|
||||||
// Mouse move handler
|
// Mouse move handler
|
||||||
const handleMouseMove = (e) => {
|
const handleMouseMove = (e) => {
|
||||||
const rect = overlay.getBoundingClientRect();
|
const rect = overlay.getBoundingClientRect();
|
||||||
// Check if mouse is near the top-right corner (within 150px from right, 100px from top)
|
|
||||||
const isNearTopRight = e.clientY < 100 && e.clientX > rect.width - 150;
|
const isNearTopRight = e.clientY < 100 && e.clientX > rect.width - 150;
|
||||||
|
|
||||||
if (isUIHidden) {
|
if (isUIHidden) {
|
||||||
if (overlay.classList.contains('is-video-mode')) {
|
if (overlay.classList.contains('is-video-mode')) {
|
||||||
toggleUI();
|
if (isNearTopRight) {
|
||||||
|
showButton();
|
||||||
|
} else {
|
||||||
|
hideButton();
|
||||||
|
}
|
||||||
} else if (isNearTopRight) {
|
} else if (isNearTopRight) {
|
||||||
showButton();
|
showButton();
|
||||||
} else {
|
} else {
|
||||||
hideButton();
|
hideButton();
|
||||||
}
|
}
|
||||||
} else if (overlay.classList.contains('is-video-mode')) {
|
|
||||||
resetVideoHideTimer();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let videoHideTimer = null;
|
|
||||||
const resetVideoHideTimer = () => {
|
|
||||||
if (videoHideTimer) clearTimeout(videoHideTimer);
|
|
||||||
if (!overlay.classList.contains('is-video-mode') || isUIHidden) return;
|
|
||||||
|
|
||||||
videoHideTimer = setTimeout(() => {
|
|
||||||
if (!isUIHidden && overlay.classList.contains('is-video-mode')) {
|
|
||||||
toggleUI();
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
};
|
|
||||||
|
|
||||||
resetVideoHideTimer();
|
|
||||||
|
|
||||||
// Toggle UI visibility
|
|
||||||
const toggleUI = () => {
|
|
||||||
isUIHidden = !isUIHidden;
|
|
||||||
overlay.classList.toggle('ui-hidden', isUIHidden);
|
|
||||||
toggleBtn.classList.toggle('active', isUIHidden);
|
|
||||||
toggleBtn.title = isUIHidden ? 'Show UI' : 'Hide UI';
|
|
||||||
|
|
||||||
if (isUIHidden) {
|
|
||||||
// When UI is hidden, immediately hide the button
|
|
||||||
// It will reappear when mouse nears top-right
|
|
||||||
hideButton();
|
|
||||||
} else {
|
|
||||||
// When UI is shown, keep button visible
|
|
||||||
showButton();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
91
styles.css
91
styles.css
|
|
@ -2113,6 +2113,10 @@ input[type='search']::-webkit-search-cancel-button {
|
||||||
color: var(--highlight);
|
color: var(--highlight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.track-item.video-track-item {
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
|
||||||
.track-item.unavailable {
|
.track-item.unavailable {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
@ -5436,12 +5440,12 @@ img[src=''] {
|
||||||
right: 2rem;
|
right: 2rem;
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
background: rgb(15, 15, 15, 0.5);
|
background: transparent;
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: none;
|
||||||
padding: 0.6rem 1rem;
|
padding: 0.6rem 1rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid rgb(255, 255, 255, 0.05);
|
border: none;
|
||||||
box-shadow: 0 4px 20px rgb(0, 0, 0, 0.4);
|
box-shadow: none;
|
||||||
transition:
|
transition:
|
||||||
transform 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
transform 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||||||
opacity 0.4s ease;
|
opacity 0.4s ease;
|
||||||
|
|
@ -5478,6 +5482,84 @@ img[src=''] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#fullscreen-cover-overlay.is-video-mode .fs-quality-btn {
|
||||||
|
display: flex !important;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fullscreen-cover-overlay.is-video-mode .fs-quality-btn svg {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#fullscreen-cover-overlay.is-video-mode .fs-quality-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-quality-btn {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-quality-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fullscreen-volume-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-quality-menu {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
right: 0;
|
||||||
|
background: rgb(20, 20, 20);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 4px;
|
||||||
|
min-width: 120px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 1000;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-quality-option {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: white;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-quality-option:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fs-quality-option.active {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
#fullscreen-cover-overlay.is-video-mode .fullscreen-volume-container {
|
#fullscreen-cover-overlay.is-video-mode .fullscreen-volume-container {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
@ -8193,6 +8275,7 @@ body:has(#side-panel.active) #close-fullscreen-cover-btn {
|
||||||
|
|
||||||
.video-card .card-image-container {
|
.video-card .card-image-container {
|
||||||
aspect-ratio: 16 / 9 !important;
|
aspect-ratio: 16 / 9 !important;
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-card .card-image {
|
.video-card .card-image {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue