video: detail pages — Photos gallery+lightbox, all Videos, Details/keywords, full cast
Frontend for the new data, on both movie + TV detail pages: - Photos: a backdrops rail → fullscreen lightbox (‹ › nav, keyboard arrows, Esc, counter). - Videos: a rail of every trailer/teaser/clip/featurette (YouTube thumbs) → opens in the existing player modal. - Details: budget / box office / language / country + keyword tag chips. - Cast & Crew gets a 'View all N' → full-cast modal (clickable to person; TV shows per-actor episode counts). All cached server-side (instant re-open) and lazy-loaded images. Isolated; shell tests cover the new sections + modals.
This commit is contained in:
parent
6fcebe7c4b
commit
9c29414e6d
4 changed files with 304 additions and 4 deletions
|
|
@ -436,6 +436,10 @@ def test_detail_keeps_preview_items_in_app():
|
|||
# Director/crew names link to the person page; episode-sync has a banner.
|
||||
assert "personName" in src and "data-vd-person" in src
|
||||
assert "data-vd-ep-syncing" in _INDEX and "showEpSyncing" in src
|
||||
# Media gallery + lightbox, all videos, facts/keywords, full-cast modal.
|
||||
assert "data-vd-gallery" in _INDEX and "data-vd-videos" in _INDEX
|
||||
assert "data-vd-facts" in _INDEX and "data-vd-cast-all" in _INDEX
|
||||
assert "openLightbox" in src and "renderVideos" in src and "openCastModal" in src
|
||||
# The old external 'similar' link (themoviedb.org/<kind>/<id>) is gone — the
|
||||
# only remaining themoviedb.org ref is the TMDB badge logo asset for owned items.
|
||||
assert "www.themoviedb.org/' + (s.kind" not in src
|
||||
|
|
|
|||
|
|
@ -923,10 +923,26 @@
|
|||
<div class="vd-episodes" data-vd-episodes></div>
|
||||
<!-- Cast & crew (TMDB) — populated by video-detail.js. -->
|
||||
<div class="vd-cast-section" data-vd-cast-section hidden>
|
||||
<h2 class="vd-section-h">Cast & Crew</h2>
|
||||
<div class="vd-section-head">
|
||||
<h2 class="vd-section-h">Cast & Crew</h2>
|
||||
<button class="vd-viewall" type="button" data-vd-cast-all hidden>View all</button>
|
||||
</div>
|
||||
<div class="vd-crew" data-vd-crew></div>
|
||||
<div class="vd-cast" data-vd-cast></div>
|
||||
</div>
|
||||
<div class="vd-facts-section" data-vd-facts-section hidden>
|
||||
<h2 class="vd-section-h">Details</h2>
|
||||
<div class="vd-facts" data-vd-facts></div>
|
||||
<div class="vd-keywords" data-vd-keywords></div>
|
||||
</div>
|
||||
<div class="vd-videos-section" data-vd-videos-section hidden>
|
||||
<h2 class="vd-section-h">Videos</h2>
|
||||
<div class="vd-videos" data-vd-videos></div>
|
||||
</div>
|
||||
<div class="vd-gallery-section" data-vd-gallery-section hidden>
|
||||
<h2 class="vd-section-h">Photos</h2>
|
||||
<div class="vd-gallery" data-vd-gallery></div>
|
||||
</div>
|
||||
<div class="vd-providers-section" data-vd-providers-section hidden>
|
||||
<h2 class="vd-section-h">Where to Watch</h2>
|
||||
<div class="vd-providers" data-vd-providers></div>
|
||||
|
|
@ -978,10 +994,26 @@
|
|||
<div class="vd-body">
|
||||
<div class="vd-details" data-vd-details></div>
|
||||
<div class="vd-cast-section" data-vd-cast-section hidden>
|
||||
<h2 class="vd-section-h">Cast & Crew</h2>
|
||||
<div class="vd-section-head">
|
||||
<h2 class="vd-section-h">Cast & Crew</h2>
|
||||
<button class="vd-viewall" type="button" data-vd-cast-all hidden>View all</button>
|
||||
</div>
|
||||
<div class="vd-crew" data-vd-crew></div>
|
||||
<div class="vd-cast" data-vd-cast></div>
|
||||
</div>
|
||||
<div class="vd-facts-section" data-vd-facts-section hidden>
|
||||
<h2 class="vd-section-h">Details</h2>
|
||||
<div class="vd-facts" data-vd-facts></div>
|
||||
<div class="vd-keywords" data-vd-keywords></div>
|
||||
</div>
|
||||
<div class="vd-videos-section" data-vd-videos-section hidden>
|
||||
<h2 class="vd-section-h">Videos</h2>
|
||||
<div class="vd-videos" data-vd-videos></div>
|
||||
</div>
|
||||
<div class="vd-gallery-section" data-vd-gallery-section hidden>
|
||||
<h2 class="vd-section-h">Photos</h2>
|
||||
<div class="vd-gallery" data-vd-gallery></div>
|
||||
</div>
|
||||
<div class="vd-providers-section" data-vd-providers-section hidden>
|
||||
<h2 class="vd-section-h">Where to Watch</h2>
|
||||
<div class="vd-providers" data-vd-providers></div>
|
||||
|
|
|
|||
|
|
@ -363,9 +363,12 @@
|
|||
// ── live TMDB extras (trailer / where-to-watch / similar) ─────────────────
|
||||
function resetExtras() {
|
||||
['[data-vd-providers-section]', '[data-vd-similar-section]', '[data-vd-collection-section]',
|
||||
'[data-vd-next-ep]', '[data-vd-crew-line]', '[data-vd-season-overview]'].forEach(function (s) {
|
||||
'[data-vd-next-ep]', '[data-vd-crew-line]', '[data-vd-season-overview]',
|
||||
'[data-vd-facts-section]', '[data-vd-videos-section]', '[data-vd-gallery-section]',
|
||||
'[data-vd-cast-all]'].forEach(function (s) {
|
||||
var n = q(s); if (n) n.hidden = true;
|
||||
});
|
||||
galleryImages = [];
|
||||
}
|
||||
function loadExtras(kind, id) {
|
||||
fetch(DETAIL_URL + kind + '/' + id + '/extras', { headers: { 'Accept': 'application/json' } })
|
||||
|
|
@ -455,6 +458,163 @@
|
|||
// "More Like This" — recommendations (better-curated), falling back to similar.
|
||||
var more = (ex.recommendations && ex.recommendations.length) ? ex.recommendations : ex.similar;
|
||||
renderRow('[data-vd-similar-section]', '[data-vd-similar]', more);
|
||||
|
||||
data.cast_full = ex.cast_full || null;
|
||||
renderCastAll(data);
|
||||
renderFacts(ex.facts, ex.keywords);
|
||||
renderVideos(ex.videos);
|
||||
renderGallery(ex.gallery);
|
||||
}
|
||||
|
||||
// ── facts / keywords ──────────────────────────────────────────────────────
|
||||
var LANGS = { en: 'English', es: 'Spanish', fr: 'French', de: 'German', it: 'Italian',
|
||||
ja: 'Japanese', ko: 'Korean', zh: 'Chinese', hi: 'Hindi', ru: 'Russian', pt: 'Portuguese',
|
||||
sv: 'Swedish', da: 'Danish', nl: 'Dutch', no: 'Norwegian', fi: 'Finnish', pl: 'Polish',
|
||||
tr: 'Turkish', ar: 'Arabic', he: 'Hebrew', th: 'Thai', cs: 'Czech' };
|
||||
function langName(c) { return LANGS[c] || String(c || '').toUpperCase(); }
|
||||
function fmtMoney(n) {
|
||||
if (n >= 1e9) return (n / 1e9).toFixed(1).replace(/\.0$/, '') + 'B';
|
||||
if (n >= 1e6) return (n / 1e6).toFixed(1).replace(/\.0$/, '') + 'M';
|
||||
if (n >= 1e3) return Math.round(n / 1e3) + 'K';
|
||||
return String(n);
|
||||
}
|
||||
function renderFacts(facts, keywords) {
|
||||
var sec = q('[data-vd-facts-section]'), host = q('[data-vd-facts]'), kwh = q('[data-vd-keywords]');
|
||||
facts = facts || {}; keywords = keywords || [];
|
||||
var rows = [];
|
||||
if (facts.budget) rows.push(['Budget', '$' + fmtMoney(facts.budget)]);
|
||||
if (facts.revenue) rows.push(['Box office', '$' + fmtMoney(facts.revenue)]);
|
||||
if (facts.original_language) rows.push(['Language', langName(facts.original_language)]);
|
||||
if (facts.countries && facts.countries.length) rows.push(['Country', facts.countries.join(', ')]);
|
||||
if (host) {
|
||||
host.innerHTML = rows.length ? '<div class="vd-detail-grid">' + rows.map(function (r) {
|
||||
return '<div class="vd-detail-row"><span class="vd-detail-k">' + esc(r[0]) +
|
||||
'</span><span class="vd-detail-v">' + esc(r[1]) + '</span></div>';
|
||||
}).join('') + '</div>' : '';
|
||||
}
|
||||
if (kwh) {
|
||||
kwh.innerHTML = keywords.map(function (k) { return '<span class="vd-kw">' + esc(k) + '</span>'; }).join('');
|
||||
}
|
||||
if (sec) sec.hidden = !(rows.length || keywords.length);
|
||||
}
|
||||
|
||||
// ── videos (all trailers/teasers/clips) ───────────────────────────────────
|
||||
function renderVideos(videos) {
|
||||
var sec = q('[data-vd-videos-section]'), host = q('[data-vd-videos]');
|
||||
if (!sec || !host) return;
|
||||
videos = videos || [];
|
||||
if (!videos.length) { sec.hidden = true; return; }
|
||||
sec.hidden = false;
|
||||
host.innerHTML = videos.map(function (v) {
|
||||
var thumb = 'https://img.youtube.com/vi/' + encodeURIComponent(v.key) + '/mqdefault.jpg';
|
||||
return '<button class="vd-video-card" type="button" data-vd-video="' + esc(v.key) + '">' +
|
||||
'<span class="vd-video-thumb"><img src="' + thumb + '" alt="" loading="lazy">' +
|
||||
'<span class="vd-video-play">▶</span></span>' +
|
||||
'<span class="vd-video-name">' + esc(v.name || v.type) + '</span>' +
|
||||
'<span class="vd-video-type">' + esc(v.type) + '</span></button>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── photos gallery + lightbox ─────────────────────────────────────────────
|
||||
var galleryImages = [], lightboxIdx = 0;
|
||||
function renderGallery(gallery) {
|
||||
var sec = q('[data-vd-gallery-section]'), host = q('[data-vd-gallery]');
|
||||
if (!sec || !host) return;
|
||||
var imgs = (gallery && gallery.backdrops) ? gallery.backdrops : [];
|
||||
galleryImages = imgs.map(function (g) { return g.full; });
|
||||
if (!imgs.length) { sec.hidden = true; return; }
|
||||
sec.hidden = false;
|
||||
host.innerHTML = imgs.map(function (g, i) {
|
||||
return '<button class="vd-shot" type="button" data-vd-shot="' + i + '">' +
|
||||
'<img src="' + esc(g.thumb) + '" alt="" loading="lazy"></button>';
|
||||
}).join('');
|
||||
}
|
||||
function openLightbox(idx) {
|
||||
if (!galleryImages.length) return;
|
||||
lightboxIdx = idx;
|
||||
var ov = document.getElementById('vd-lightbox');
|
||||
if (!ov) {
|
||||
ov = document.createElement('div'); ov.id = 'vd-lightbox'; ov.className = 'vd-lightbox';
|
||||
ov.addEventListener('click', function (e) {
|
||||
if (e.target.closest('[data-vd-lb-prev]')) lightboxStep(-1);
|
||||
else if (e.target.closest('[data-vd-lb-next]')) lightboxStep(1);
|
||||
else if (e.target === ov || e.target.closest('[data-vd-lb-close]')) closeLightbox();
|
||||
});
|
||||
document.body.appendChild(ov);
|
||||
}
|
||||
renderLightbox();
|
||||
ov.classList.add('vd-lightbox--open');
|
||||
}
|
||||
function renderLightbox() {
|
||||
var ov = document.getElementById('vd-lightbox'); if (!ov) return;
|
||||
ov.innerHTML = '<button class="vd-lb-close" type="button" data-vd-lb-close aria-label="Close">×</button>' +
|
||||
'<button class="vd-lb-nav vd-lb-prev" type="button" data-vd-lb-prev aria-label="Previous">‹</button>' +
|
||||
'<img class="vd-lb-img" src="' + esc(galleryImages[lightboxIdx]) + '" alt="">' +
|
||||
'<button class="vd-lb-nav vd-lb-next" type="button" data-vd-lb-next aria-label="Next">›</button>' +
|
||||
'<div class="vd-lb-count">' + (lightboxIdx + 1) + ' / ' + galleryImages.length + '</div>';
|
||||
}
|
||||
function lightboxStep(dir) {
|
||||
if (!galleryImages.length) return;
|
||||
lightboxIdx = (lightboxIdx + dir + galleryImages.length) % galleryImages.length;
|
||||
renderLightbox();
|
||||
}
|
||||
function closeLightbox() {
|
||||
var ov = document.getElementById('vd-lightbox');
|
||||
if (ov) { ov.classList.remove('vd-lightbox--open'); ov.innerHTML = ''; }
|
||||
}
|
||||
function lightboxOpen() {
|
||||
var ov = document.getElementById('vd-lightbox');
|
||||
return ov && ov.classList.contains('vd-lightbox--open');
|
||||
}
|
||||
|
||||
// ── full cast modal ───────────────────────────────────────────────────────
|
||||
function renderCastAll(d) {
|
||||
var btn = q('[data-vd-cast-all]');
|
||||
if (!btn) return;
|
||||
var n = (d.cast_full || []).length;
|
||||
btn.hidden = n === 0;
|
||||
if (n) btn.textContent = 'View all ' + n;
|
||||
}
|
||||
function castModalCard(p) {
|
||||
var img = p.photo
|
||||
? '<img class="vd-cm-photo" src="' + esc(p.photo) + '" alt="" loading="lazy" onerror="this.style.visibility=\'hidden\'">'
|
||||
: '<span class="vd-cm-photo vd-cm-photo--ph">' + esc((p.name || '?').charAt(0)) + '</span>';
|
||||
var eps = p.episode_count ? '<span class="vd-cm-eps">' + p.episode_count + ' eps</span>' : '';
|
||||
var inner = img + '<span class="vd-cm-name">' + esc(p.name) + '</span>' +
|
||||
(p.character ? '<span class="vd-cm-char">' + esc(p.character) + '</span>' : '') + eps;
|
||||
return p.tmdb_id
|
||||
? '<a class="vd-cm-card" href="/video-detail/tmdb/person/' + p.tmdb_id + '" data-vd-person="' + p.tmdb_id + '">' + inner + '</a>'
|
||||
: '<div class="vd-cm-card">' + inner + '</div>';
|
||||
}
|
||||
function openCastModal() {
|
||||
var cast = (data && data.cast_full) || [];
|
||||
if (!cast.length) return;
|
||||
var ov = document.getElementById('vd-cast-modal');
|
||||
if (!ov) {
|
||||
ov = document.createElement('div'); ov.id = 'vd-cast-modal'; ov.className = 'vd-cast-modal';
|
||||
ov.addEventListener('click', function (e) {
|
||||
var card = e.target.closest('[data-vd-person]');
|
||||
if (card) {
|
||||
if (modified(e)) return;
|
||||
e.preventDefault();
|
||||
var pid = parseInt(card.getAttribute('data-vd-person'), 10);
|
||||
closeCastModal();
|
||||
if (!isNaN(pid)) document.dispatchEvent(new CustomEvent('soulsync:video-open-detail',
|
||||
{ detail: { kind: 'person', id: pid, source: 'tmdb' } }));
|
||||
return;
|
||||
}
|
||||
if (e.target === ov || e.target.closest('[data-vd-cm-close]')) closeCastModal();
|
||||
});
|
||||
document.body.appendChild(ov);
|
||||
}
|
||||
ov.innerHTML = '<div class="vd-cm-box"><div class="vd-cm-head"><h3>Cast</h3>' +
|
||||
'<button class="vd-cm-close" type="button" data-vd-cm-close aria-label="Close">×</button></div>' +
|
||||
'<div class="vd-cm-grid">' + cast.map(castModalCard).join('') + '</div></div>';
|
||||
ov.classList.add('vd-cast-modal--open');
|
||||
}
|
||||
function closeCastModal() {
|
||||
var ov = document.getElementById('vd-cast-modal');
|
||||
if (ov) { ov.classList.remove('vd-cast-modal--open'); ov.innerHTML = ''; }
|
||||
}
|
||||
|
||||
// ── trailer modal (YouTube embed) ─────────────────────────────────────────
|
||||
|
|
@ -829,6 +989,12 @@
|
|||
{ detail: { kind: 'person', id: pid, source: 'tmdb' } }));
|
||||
return;
|
||||
}
|
||||
var shot = e.target.closest('[data-vd-shot]');
|
||||
if (shot && r.contains(shot)) { openLightbox(parseInt(shot.getAttribute('data-vd-shot'), 10) || 0); return; }
|
||||
var vid = e.target.closest('[data-vd-video]');
|
||||
if (vid && r.contains(vid)) { openTrailer(vid.getAttribute('data-vd-video')); return; }
|
||||
var castAll = e.target.closest('[data-vd-cast-all]');
|
||||
if (castAll && r.contains(castAll)) { openCastModal(); return; }
|
||||
var seasonBtn = e.target.closest('[data-vd-season]');
|
||||
if (seasonBtn && r.contains(seasonBtn)) { selectSeason(parseInt(seasonBtn.getAttribute('data-vd-season'), 10)); return; }
|
||||
var viewBtn = e.target.closest('[data-vd-view]');
|
||||
|
|
@ -859,7 +1025,11 @@
|
|||
document.addEventListener('soulsync:video-open-detail', onOpen);
|
||||
document.addEventListener('click', onClick);
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Escape') closeTrailer();
|
||||
if (e.key === 'Escape') { closeTrailer(); closeLightbox(); closeCastModal(); }
|
||||
else if (lightboxOpen()) {
|
||||
if (e.key === 'ArrowLeft') lightboxStep(-1);
|
||||
else if (e.key === 'ArrowRight') lightboxStep(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1265,3 +1265,97 @@ a.vd-prov:hover img, a.vd-prov:hover .vd-prov-ph { border-color: rgba(var(--vd-a
|
|||
beats the UA [hidden]{display:none}, so toggling el.hidden wouldn't hide it.
|
||||
Guarantee hidden always hides on the video detail/search/person pages. */
|
||||
.vd-page [hidden], .vp-page [hidden], .vsr-page [hidden] { display: none !important; }
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════════════
|
||||
Detail extras: Details/keywords, Videos, Photos gallery + lightbox, full cast
|
||||
════════════════════════════════════════════════════════════════════════ */
|
||||
.vd-section-head { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-bottom: 16px; }
|
||||
.vd-section-head .vd-section-h { margin: 0; }
|
||||
.vd-viewall {
|
||||
background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
color: rgba(255, 255, 255, 0.8); border-radius: 999px; padding: 6px 14px; font-size: 12.5px;
|
||||
font-weight: 700; cursor: pointer; transition: all 0.18s ease;
|
||||
}
|
||||
.vd-viewall:hover { background: rgba(var(--vd-accent-rgb), 0.2); border-color: rgba(var(--vd-accent-rgb), 0.5); color: #fff; }
|
||||
|
||||
.vd-facts-section, .vd-videos-section, .vd-gallery-section { margin-top: 44px; }
|
||||
.vd-keywords { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
|
||||
.vd-kw {
|
||||
padding: 5px 12px; border-radius: 999px; font-size: 12px; color: rgba(255, 255, 255, 0.7);
|
||||
background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
/* videos rail */
|
||||
.vd-videos { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 12px; scroll-snap-type: x proximity; }
|
||||
.vd-videos::-webkit-scrollbar, .vd-gallery::-webkit-scrollbar { height: 8px; }
|
||||
.vd-videos::-webkit-scrollbar-thumb, .vd-gallery::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 4px; }
|
||||
.vd-video-card { flex: 0 0 240px; width: 240px; scroll-snap-align: start; background: none; border: 0; padding: 0; cursor: pointer; text-align: left; }
|
||||
.vd-video-thumb {
|
||||
position: relative; display: block; aspect-ratio: 16 / 9; border-radius: 10px; overflow: hidden; background: #000;
|
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4); transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
.vd-video-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.vd-video-play {
|
||||
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
||||
font-size: 30px; color: #fff; background: rgba(0, 0, 0, 0.25); text-shadow: 0 2px 12px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
.vd-video-card:hover .vd-video-thumb { transform: translateY(-4px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55); }
|
||||
.vd-video-name { display: block; margin-top: 9px; font-size: 13px; font-weight: 600; color: #fff;
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.vd-video-type { display: block; font-size: 11.5px; color: rgba(255, 255, 255, 0.5); }
|
||||
|
||||
/* photos rail */
|
||||
.vd-gallery { display: flex; gap: 14px; overflow-x: auto; padding-bottom: 12px; scroll-snap-type: x proximity; }
|
||||
.vd-shot {
|
||||
flex: 0 0 280px; width: 280px; aspect-ratio: 16 / 9; scroll-snap-align: start; padding: 0; border: 0; cursor: pointer;
|
||||
border-radius: 10px; overflow: hidden; background: #000; box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
|
||||
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
||||
}
|
||||
.vd-shot img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.vd-shot:hover { transform: translateY(-4px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55); }
|
||||
|
||||
/* lightbox + full-cast modal share the accent fallback (they live on <body>) */
|
||||
.vd-lightbox, .vd-cast-modal { --vd-accent-rgb: var(--accent-rgb, 88, 101, 242); }
|
||||
.vd-lightbox {
|
||||
position: fixed; inset: 0; z-index: 1000; display: none; align-items: center; justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.92); backdrop-filter: blur(6px);
|
||||
}
|
||||
.vd-lightbox--open { display: flex; animation: vd-fade 0.2s ease; }
|
||||
@keyframes vd-fade { from { opacity: 0; } to { opacity: 1; } }
|
||||
.vd-lb-img { max-width: 92vw; max-height: 88vh; border-radius: 8px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.7); }
|
||||
.vd-lb-close { position: absolute; top: 20px; right: 24px; width: 42px; height: 42px; border-radius: 50%; border: 0; cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.12); color: #fff; font-size: 24px; }
|
||||
.vd-lb-close:hover { background: rgba(255, 255, 255, 0.22); }
|
||||
.vd-lb-nav { position: absolute; top: 50%; transform: translateY(-50%); width: 52px; height: 52px; border-radius: 50%; border: 0; cursor: pointer;
|
||||
background: rgba(255, 255, 255, 0.1); color: #fff; font-size: 30px; line-height: 1; }
|
||||
.vd-lb-nav:hover { background: rgba(255, 255, 255, 0.22); }
|
||||
.vd-lb-prev { left: 24px; }
|
||||
.vd-lb-next { right: 24px; }
|
||||
.vd-lb-count { position: absolute; bottom: 22px; left: 50%; transform: translateX(-50%); font-size: 13px; color: rgba(255, 255, 255, 0.7); }
|
||||
|
||||
.vd-cast-modal {
|
||||
position: fixed; inset: 0; z-index: 1000; display: none; align-items: center; justify-content: center;
|
||||
background: rgba(0, 0, 0, 0.75); backdrop-filter: blur(6px); padding: 30px;
|
||||
}
|
||||
.vd-cast-modal--open { display: flex; animation: vd-fade 0.2s ease; }
|
||||
.vd-cm-box {
|
||||
background: #16161c; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 16px;
|
||||
width: min(960px, 96vw); max-height: 86vh; overflow: hidden; display: flex; flex-direction: column;
|
||||
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
.vd-cm-head { display: flex; align-items: center; justify-content: space-between; padding: 18px 22px; border-bottom: 1px solid rgba(255, 255, 255, 0.08); }
|
||||
.vd-cm-head h3 { margin: 0; font-size: 18px; font-weight: 800; color: #fff; }
|
||||
.vd-cm-close { width: 36px; height: 36px; border-radius: 50%; border: 0; cursor: pointer; background: rgba(255, 255, 255, 0.08); color: #fff; font-size: 22px; }
|
||||
.vd-cm-close:hover { background: rgba(255, 255, 255, 0.18); }
|
||||
.vd-cm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(116px, 1fr)); gap: 18px; padding: 22px; overflow-y: auto; }
|
||||
.vd-cm-card { text-decoration: none; text-align: center; color: inherit; cursor: pointer; }
|
||||
.vd-cm-photo {
|
||||
width: 84px; height: 84px; border-radius: 50%; object-fit: cover; display: block; margin: 0 auto 9px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.08); transition: transform 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
.vd-cm-photo--ph { display: flex; align-items: center; justify-content: center; font-size: 30px; font-weight: 800;
|
||||
color: rgba(255, 255, 255, 0.5); background: linear-gradient(135deg, rgba(var(--vd-accent-rgb), 0.32), rgba(var(--vd-accent-rgb), 0.1)); }
|
||||
.vd-cm-card:hover .vd-cm-photo { transform: translateY(-3px); border-color: rgba(var(--vd-accent-rgb), 0.6); }
|
||||
.vd-cm-name { display: block; font-size: 13px; font-weight: 700; color: #fff; line-height: 1.3; }
|
||||
.vd-cm-char { display: block; font-size: 12px; color: rgba(255, 255, 255, 0.55); line-height: 1.3; }
|
||||
.vd-cm-eps { display: block; font-size: 11px; color: rgb(var(--vd-accent-rgb)); margin-top: 2px; font-weight: 600; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue