Enhance end screen overlay with responsive grid and swiper

Redesigned the end screen overlay to support a responsive grid layout for related videos on larger screens and a horizontal swiper for small screens. Improved card consistency, added navigation indicators for the swiper, and unified styling in both CSS and JS for better user experience and maintainability.
This commit is contained in:
Yiannis Christodoulou 2025-10-11 02:38:56 +03:00
parent 24e9fb4e40
commit 107b8d9db0
2 changed files with 615 additions and 77 deletions

View File

@ -1,3 +1,257 @@
/* ===== END SCREEN OVERLAY STYLES ===== */
.vjs-end-screen-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 60px; /* Leave space for control bar */
background: #000000; /* Solid black background */
display: none;
z-index: 100;
overflow: hidden; /* Prevent content from overflowing */
box-sizing: border-box;
}
.vjs-end-screen-overlay.vjs-show {
display: flex !important;
}
/* Related videos grid */
.vjs-related-videos-grid {
display: grid;
gap: 12px;
padding: 20px;
height: 100%;
overflow-y: auto;
align-content: start;
justify-items: stretch;
}
/* Video item cards */
.vjs-related-video-item {
position: relative;
background-color: #1a1a1a;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
transition:
transform 0.15s ease,
box-shadow 0.15s ease;
display: flex;
flex-direction: column;
height: 180px;
min-height: 180px;
width: 100%;
}
.vjs-related-video-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
/* Swiper specific styles */
.vjs-related-videos-swiper-container {
position: relative;
padding: 20px;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden; /* Prevent container overflow */
box-sizing: border-box;
}
.vjs-related-videos-swiper {
display: flex;
overflow-x: auto;
overflow-y: hidden;
gap: 12px;
padding-bottom: 10px;
scroll-behavior: smooth;
scroll-snap-type: x mandatory;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/Edge */
width: 100%;
max-width: 100%;
box-sizing: border-box;
/* Prevent scroll propagation */
overscroll-behavior-x: contain;
}
.vjs-related-videos-swiper::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
.vjs-swiper-item {
min-width: calc(50% - 6px); /* 2 items visible with gap */
width: calc(50% - 6px);
max-width: 180px;
height: 220px; /* Increased height for better content display */
min-height: 220px;
flex-shrink: 0;
scroll-snap-align: start;
}
.vjs-swiper-indicators {
display: flex;
justify-content: center;
gap: 8px;
margin-top: 10px;
}
.vjs-swiper-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.4);
cursor: pointer;
transition: background-color 0.2s ease;
}
.vjs-swiper-dot.active {
background-color: #ffffff;
}
/* Thumbnail container */
.vjs-related-video-thumbnail-container {
position: relative;
width: 100%;
height: 100px;
overflow: hidden;
flex-shrink: 0;
}
.vjs-related-video-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* Duration badge */
.vjs-video-duration {
position: absolute;
bottom: 4px;
right: 4px;
background-color: rgba(0, 0, 0, 0.85);
color: white;
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: 600;
line-height: 1;
z-index: 2;
}
/* Video info section */
.vjs-related-video-info {
padding: 10px;
color: white;
flex: 1;
display: flex;
flex-direction: column;
justify-content: flex-start;
min-height: 50px;
}
.vjs-related-video-title {
font-size: 13px;
font-weight: 600;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-bottom: 6px;
color: #ffffff;
}
.vjs-related-video-meta {
font-size: 11px;
color: #b3b3b3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-shrink: 0;
line-height: 1.2;
}
/* Swiper specific info styling */
.vjs-swiper-item .vjs-related-video-info {
padding: 10px;
height: 110px; /* Increased height for better content display */
min-height: 110px;
}
.vjs-swiper-item .vjs-related-video-title {
font-size: 13px; /* Slightly larger for better readability */
line-height: 1.3;
margin-bottom: 8px;
-webkit-line-clamp: 3; /* Allow 3 lines for titles */
}
.vjs-swiper-item .vjs-related-video-meta {
font-size: 11px; /* Slightly larger for better readability */
margin-top: 4px;
}
/* Responsive breakpoints */
@media (max-width: 599px) {
/* Small screens use swiper - styles handled by JS */
.vjs-related-video-thumbnail-container {
height: 100px;
}
.vjs-related-video-title {
font-size: 12px;
}
}
@media (min-width: 600px) and (max-width: 899px) {
.vjs-related-videos-grid {
grid-template-columns: repeat(2, 1fr);
}
.vjs-related-video-thumbnail-container {
height: 110px;
}
}
@media (min-width: 900px) and (max-width: 1199px) {
.vjs-related-videos-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (min-width: 1200px) and (max-width: 1599px) {
.vjs-related-videos-grid {
grid-template-columns: repeat(4, 1fr);
}
}
@media (min-width: 1600px) {
.vjs-related-videos-grid {
grid-template-columns: repeat(5, 1fr);
}
}
/* Hide poster and video when end screen is shown */
.vjs-ended .vjs-poster { .vjs-ended .vjs-poster {
display: none !important; display: none !important;
} }
/* Ensure video is completely hidden when end screen is active */
.video-js.vjs-ended video {
display: none !important;
opacity: 0 !important;
visibility: hidden !important;
}
/* Ensure end screen overlay covers everything with solid background */
.video-js.vjs-ended .vjs-end-screen-overlay {
background: #000000 !important;
z-index: 99999 !important;
display: flex !important;
}

View File

@ -25,15 +25,17 @@ class EndScreenOverlay extends Component {
className: 'vjs-end-screen-overlay', className: 'vjs-end-screen-overlay',
}); });
// Position overlay above control bar // Position overlay above control bar with solid black background
overlay.style.position = 'absolute'; overlay.style.position = 'absolute';
overlay.style.top = '0'; overlay.style.top = '0';
overlay.style.left = '0'; overlay.style.left = '0';
overlay.style.right = '0'; overlay.style.right = '0';
overlay.style.bottom = '60px'; // Leave space for control bar overlay.style.bottom = '60px'; // Leave space for control bar
overlay.style.display = 'none'; // Hidden by default overlay.style.display = 'none'; // Hidden by default
overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; overlay.style.backgroundColor = '#000000'; // Solid black background
overlay.style.zIndex = '100'; overlay.style.zIndex = '100';
overlay.style.overflow = 'hidden';
overlay.style.boxSizing = 'border-box';
// Create responsive grid // Create responsive grid
const grid = this.createGrid(); const grid = this.createGrid();
@ -43,30 +45,38 @@ class EndScreenOverlay extends Component {
} }
createGrid() { createGrid() {
const grid = videojs.dom.createEl('div', { const { columns, maxVideos, useSwiper } = this.getGridConfig();
className: 'vjs-related-videos-grid',
});
// Responsive grid styling
grid.style.display = 'grid';
grid.style.gap = '12px';
grid.style.padding = '20px';
grid.style.height = '100%';
grid.style.overflowY = 'auto';
// Responsive grid columns based on player size
const { columns, maxVideos } = this.getGridConfig();
grid.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
// Get videos to show - access directly from options during createEl // Get videos to show - access directly from options during createEl
const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || []; const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || [];
const videosToShow = const videosToShow =
relatedVideos.length > 0 relatedVideos.length > 0
? relatedVideos.slice(0, maxVideos) ? relatedVideos.slice(0, maxVideos)
: this.createSampleVideos().slice(0, maxVideos); : this.createSampleVideos().slice(0, maxVideos);
// Create video items if (useSwiper) {
return this.createSwiperGrid(videosToShow);
} else {
return this.createRegularGrid(columns, videosToShow);
}
}
createRegularGrid(columns, videosToShow) {
const grid = videojs.dom.createEl('div', {
className: 'vjs-related-videos-grid',
});
// Responsive grid styling with consistent dimensions
grid.style.display = 'grid';
grid.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
grid.style.gap = '12px';
grid.style.padding = '20px';
grid.style.height = '100%';
grid.style.overflowY = 'auto';
grid.style.alignContent = 'start';
grid.style.justifyItems = 'stretch';
// Create video items with consistent dimensions
videosToShow.forEach((video) => { videosToShow.forEach((video) => {
const videoItem = this.createVideoItem(video); const videoItem = this.createVideoItem(video);
grid.appendChild(videoItem); grid.appendChild(videoItem);
@ -75,20 +85,114 @@ class EndScreenOverlay extends Component {
return grid; return grid;
} }
createSwiperGrid(videosToShow) {
const container = videojs.dom.createEl('div', {
className: 'vjs-related-videos-swiper-container',
});
// Container styling - ensure it stays within bounds
container.style.position = 'relative';
container.style.padding = '20px';
container.style.height = '100%';
container.style.width = '100%';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.overflow = 'hidden'; // Prevent container overflow
container.style.boxSizing = 'border-box';
// Create swiper wrapper with proper containment
const swiperWrapper = videojs.dom.createEl('div', {
className: 'vjs-related-videos-swiper',
});
swiperWrapper.style.display = 'flex';
swiperWrapper.style.overflowX = 'auto';
swiperWrapper.style.overflowY = 'hidden';
swiperWrapper.style.gap = '12px';
swiperWrapper.style.paddingBottom = '10px';
swiperWrapper.style.scrollBehavior = 'smooth';
swiperWrapper.style.scrollSnapType = 'x mandatory';
swiperWrapper.style.width = '100%';
swiperWrapper.style.maxWidth = '100%';
swiperWrapper.style.boxSizing = 'border-box';
// Hide scrollbar and prevent scroll propagation
swiperWrapper.style.scrollbarWidth = 'none'; // Firefox
swiperWrapper.style.msOverflowStyle = 'none'; // IE/Edge
// Prevent scroll events from bubbling up to parent
swiperWrapper.addEventListener(
'wheel',
(e) => {
e.stopPropagation();
// Only prevent default if we're actually scrolling horizontally
const isScrollingHorizontally = Math.abs(e.deltaX) > Math.abs(e.deltaY);
if (isScrollingHorizontally) {
e.preventDefault();
swiperWrapper.scrollLeft += e.deltaX;
}
},
{ passive: false }
);
// Prevent touch events from affecting parent
swiperWrapper.addEventListener(
'touchstart',
(e) => {
e.stopPropagation();
},
{ passive: true }
);
swiperWrapper.addEventListener(
'touchmove',
(e) => {
e.stopPropagation();
},
{ passive: true }
);
// Create video items for swiper (show 2 at a time, but allow scrolling through all)
videosToShow.forEach((video) => {
const videoItem = this.createVideoItem(video, true); // Pass true for swiper mode
swiperWrapper.appendChild(videoItem);
});
container.appendChild(swiperWrapper);
// Add navigation indicators if there are more than 2 videos
if (videosToShow.length > 2) {
const indicators = this.createSwiperIndicators(videosToShow.length, swiperWrapper);
container.appendChild(indicators);
}
return container;
}
getGridConfig() { getGridConfig() {
const playerEl = this.player().el(); const playerEl = this.player().el();
const playerWidth = playerEl?.offsetWidth || window.innerWidth; const playerWidth = playerEl?.offsetWidth || window.innerWidth;
const playerHeight = playerEl?.offsetHeight || window.innerHeight; const playerHeight = playerEl?.offsetHeight || window.innerHeight;
// Responsive grid configuration // Calculate available space for better utilization
if (playerWidth >= 1200) { const availableHeight = playerHeight - 140; // Account for controls and padding
return { columns: 4, maxVideos: 8 }; // 4x2 grid for large screens const cardHeight = 180; // Consistent card height
} else if (playerWidth >= 800) { const maxRows = Math.max(2, Math.floor(availableHeight / cardHeight));
return { columns: 3, maxVideos: 6 }; // 3x2 grid for medium screens
} else if (playerWidth >= 500) { // Enhanced grid configuration to fill large screens better
return { columns: 2, maxVideos: 4 }; // 2x2 grid for small screens if (playerWidth >= 1600) {
const columns = 5;
return { columns, maxVideos: columns * Math.min(maxRows, 3), useSwiper: false }; // 5 columns, up to 3 rows
} else if (playerWidth >= 1200) {
const columns = 4;
return { columns, maxVideos: columns * Math.min(maxRows, 3), useSwiper: false }; // 4 columns, up to 3 rows
} else if (playerWidth >= 900) {
const columns = 3;
return { columns, maxVideos: columns * Math.min(maxRows, 2), useSwiper: false }; // 3 columns, up to 2 rows
} else if (playerWidth >= 600) {
return { columns: 2, maxVideos: 4, useSwiper: false }; // 2x2 grid for medium screens
} else { } else {
return { columns: 1, maxVideos: 3 }; // 1 column for very small screens return { columns: 2, maxVideos: 6, useSwiper: true }; // Use swiper for small screens
} }
} }
@ -102,39 +206,57 @@ class EndScreenOverlay extends Component {
return this.createSampleVideos().slice(0, maxVideos); return this.createSampleVideos().slice(0, maxVideos);
} }
createVideoItem(video) { createVideoItem(video, isSwiperMode = false) {
const item = videojs.dom.createEl('div', { const item = videojs.dom.createEl('div', {
className: 'vjs-related-video-item', className: `vjs-related-video-item ${isSwiperMode ? 'vjs-swiper-item' : ''}`,
}); });
// Item styling // Consistent item styling with fixed dimensions
item.style.position = 'relative'; item.style.position = 'relative';
item.style.backgroundColor = '#1a1a1a'; item.style.backgroundColor = '#1a1a1a';
item.style.borderRadius = '8px'; item.style.borderRadius = '6px';
item.style.overflow = 'hidden'; item.style.overflow = 'hidden';
item.style.cursor = 'pointer'; item.style.cursor = 'pointer';
item.style.transition = 'transform 0.2s ease, box-shadow 0.2s ease'; item.style.transition = 'transform 0.15s ease, box-shadow 0.15s ease';
item.style.display = 'flex';
item.style.flexDirection = 'column';
// Hover/touch effects // Consistent dimensions for all cards
if (isSwiperMode) {
// Calculate proper width for swiper items (2 items visible + gap)
item.style.minWidth = 'calc(50% - 6px)'; // 50% width minus half the gap
item.style.width = 'calc(50% - 6px)';
item.style.maxWidth = '180px'; // Maximum width for larger screens
item.style.height = '220px'; // Increased height for better content display
item.style.minHeight = '220px';
item.style.flexShrink = '0';
item.style.scrollSnapAlign = 'start';
} else {
item.style.height = '180px';
item.style.minHeight = '180px';
item.style.width = '100%';
}
// Subtle hover/touch effects
if (this.isTouchDevice) { if (this.isTouchDevice) {
item.style.touchAction = 'manipulation'; item.style.touchAction = 'manipulation';
} else { } else {
item.addEventListener('mouseenter', () => { item.addEventListener('mouseenter', () => {
item.style.transform = 'scale(1.05)'; item.style.transform = 'translateY(-2px)';
item.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)'; item.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.25)';
}); });
item.addEventListener('mouseleave', () => { item.addEventListener('mouseleave', () => {
item.style.transform = 'scale(1)'; item.style.transform = 'translateY(0)';
item.style.boxShadow = 'none'; item.style.boxShadow = 'none';
}); });
} }
// Create thumbnail // Create thumbnail container
const thumbnail = this.createThumbnail(video); const thumbnailContainer = this.createThumbnailContainer(video, isSwiperMode);
item.appendChild(thumbnail); item.appendChild(thumbnailContainer);
// Create info overlay // Create simplified info section
const info = this.createVideoInfo(video); const info = this.createVideoInfo(video, isSwiperMode);
item.appendChild(info); item.appendChild(info);
// Add click handler // Add click handler
@ -143,7 +265,18 @@ class EndScreenOverlay extends Component {
return item; return item;
} }
createThumbnail(video) { createThumbnailContainer(video, isSwiperMode = false) {
const container = videojs.dom.createEl('div', {
className: 'vjs-related-video-thumbnail-container',
});
// Container styling with consistent height
container.style.position = 'relative';
container.style.width = '100%';
container.style.height = isSwiperMode ? '100px' : '110px'; // Slightly taller for regular grid
container.style.overflow = 'hidden';
container.style.flexShrink = '0';
const thumbnail = videojs.dom.createEl('img', { const thumbnail = videojs.dom.createEl('img', {
className: 'vjs-related-video-thumbnail', className: 'vjs-related-video-thumbnail',
src: video.thumbnail || this.getPlaceholderImage(video.title), src: video.thumbnail || this.getPlaceholderImage(video.title),
@ -152,74 +285,158 @@ class EndScreenOverlay extends Component {
// Thumbnail styling // Thumbnail styling
thumbnail.style.width = '100%'; thumbnail.style.width = '100%';
thumbnail.style.height = '120px'; thumbnail.style.height = '100%';
thumbnail.style.objectFit = 'cover'; thumbnail.style.objectFit = 'cover';
thumbnail.style.display = 'block'; thumbnail.style.display = 'block';
// Add duration badge if available container.appendChild(thumbnail);
// Add duration badge at bottom right of thumbnail
if (video.duration && video.duration > 0) { if (video.duration && video.duration > 0) {
const duration = videojs.dom.createEl('div', { const duration = videojs.dom.createEl('div', {
className: 'vjs-video-duration', className: 'vjs-video-duration',
}); });
duration.textContent = this.formatDuration(video.duration); duration.textContent = this.formatDuration(video.duration);
duration.style.position = 'absolute'; duration.style.position = 'absolute';
duration.style.bottom = '50px'; duration.style.bottom = '4px';
duration.style.right = '8px'; duration.style.right = '4px';
duration.style.backgroundColor = 'rgba(0, 0, 0, 0.8)'; duration.style.backgroundColor = 'rgba(0, 0, 0, 0.85)';
duration.style.color = 'white'; duration.style.color = 'white';
duration.style.padding = '2px 6px'; duration.style.padding = '2px 6px';
duration.style.borderRadius = '4px'; duration.style.borderRadius = '3px';
duration.style.fontSize = '12px'; duration.style.fontSize = isSwiperMode ? '10px' : '11px';
duration.style.fontWeight = 'bold'; duration.style.fontWeight = '600';
duration.style.lineHeight = '1';
duration.style.zIndex = '2';
// Add duration to parent item (will be added later) container.appendChild(duration);
thumbnail.durationBadge = duration;
} }
return thumbnail; return container;
} }
createVideoInfo(video) { createVideoInfo(video, isSwiperMode = false) {
const info = videojs.dom.createEl('div', { const info = videojs.dom.createEl('div', {
className: 'vjs-related-video-info', className: 'vjs-related-video-info',
}); });
// Info styling // Consistent info styling with increased height for swiper mode
info.style.padding = '12px'; const padding = isSwiperMode ? '10px' : '10px';
info.style.color = 'white'; const infoHeight = isSwiperMode ? '110px' : '70px'; // Increased height for swiper mode
// Title info.style.padding = padding;
info.style.color = 'white';
info.style.flex = '1';
info.style.display = 'flex';
info.style.flexDirection = 'column';
info.style.justifyContent = 'flex-start'; // Align content to top
info.style.height = infoHeight;
info.style.minHeight = infoHeight;
// Title with responsive text handling
const title = videojs.dom.createEl('div', { const title = videojs.dom.createEl('div', {
className: 'vjs-related-video-title', className: 'vjs-related-video-title',
}); });
title.textContent = video.title; title.textContent = video.title;
title.style.fontSize = '14px'; title.style.fontSize = isSwiperMode ? '13px' : '13px'; // Slightly larger font for better readability
title.style.fontWeight = 'bold'; title.style.fontWeight = '600';
title.style.marginBottom = '4px';
title.style.lineHeight = '1.3'; title.style.lineHeight = '1.3';
title.style.overflow = 'hidden'; title.style.overflow = 'hidden';
title.style.textOverflow = 'ellipsis'; title.style.textOverflow = 'ellipsis';
title.style.display = '-webkit-box'; title.style.display = '-webkit-box';
title.style.webkitLineClamp = '2'; title.style.webkitLineClamp = isSwiperMode ? '3' : '2'; // Allow 3 lines for swiper mode
title.style.webkitBoxOrient = 'vertical'; title.style.webkitBoxOrient = 'vertical';
title.style.marginBottom = isSwiperMode ? '8px' : '4px';
title.style.color = '#ffffff';
// Author and views // Meta information - always show for swiper mode
const meta = videojs.dom.createEl('div', { const meta = videojs.dom.createEl('div', {
className: 'vjs-related-video-meta', className: 'vjs-related-video-meta',
}); });
meta.textContent = `${video.author}${video.views}`;
meta.style.fontSize = '12px'; // Format meta text more cleanly - ensure both author and views are shown
meta.style.color = '#aaa'; let metaText = '';
if (video.author && video.views) {
metaText = `${video.author}${video.views}`;
} else if (video.author) {
metaText = video.author;
} else if (video.views) {
metaText = video.views;
} else {
// Fallback for sample data
metaText = 'Unknown • No views';
}
meta.textContent = metaText;
meta.style.fontSize = isSwiperMode ? '11px' : '11px'; // Slightly larger font for better readability
meta.style.color = '#b3b3b3';
meta.style.overflow = 'hidden'; meta.style.overflow = 'hidden';
meta.style.textOverflow = 'ellipsis'; meta.style.textOverflow = 'ellipsis';
meta.style.whiteSpace = 'nowrap'; meta.style.whiteSpace = 'nowrap';
meta.style.flexShrink = '0';
meta.style.lineHeight = '1.3';
meta.style.marginTop = isSwiperMode ? '4px' : '0px'; // Add some spacing
info.appendChild(title); info.appendChild(title);
info.appendChild(meta); info.appendChild(meta); // Always append meta for consistent layout
return info; return info;
} }
createSwiperIndicators(totalVideos, swiperWrapper) {
const indicators = videojs.dom.createEl('div', {
className: 'vjs-swiper-indicators',
});
indicators.style.display = 'flex';
indicators.style.justifyContent = 'center';
indicators.style.gap = '8px';
indicators.style.marginTop = '10px';
const itemsPerView = 2;
const totalPages = Math.ceil(totalVideos / itemsPerView);
for (let i = 0; i < totalPages; i++) {
const dot = videojs.dom.createEl('div', {
className: 'vjs-swiper-dot',
});
dot.style.width = '8px';
dot.style.height = '8px';
dot.style.borderRadius = '50%';
dot.style.backgroundColor = i === 0 ? '#ffffff' : 'rgba(255, 255, 255, 0.4)';
dot.style.cursor = 'pointer';
dot.style.transition = 'background-color 0.2s ease';
dot.addEventListener('click', () => {
// Calculate scroll position based on container width
const containerWidth = swiperWrapper.offsetWidth;
const scrollPosition = i * containerWidth; // Scroll by full container width
swiperWrapper.scrollTo({ left: scrollPosition, behavior: 'smooth' });
// Update active dot
indicators.querySelectorAll('.vjs-swiper-dot').forEach((d, index) => {
d.style.backgroundColor = index === i ? '#ffffff' : 'rgba(255, 255, 255, 0.4)';
});
});
indicators.appendChild(dot);
}
// Update active dot on scroll
swiperWrapper.addEventListener('scroll', () => {
const scrollLeft = swiperWrapper.scrollLeft;
const containerWidth = swiperWrapper.offsetWidth;
const currentPage = Math.round(scrollLeft / containerWidth);
indicators.querySelectorAll('.vjs-swiper-dot').forEach((dot, index) => {
dot.style.backgroundColor = index === currentPage ? '#ffffff' : 'rgba(255, 255, 255, 0.4)';
});
});
return indicators;
}
addClickHandler(item, video) { addClickHandler(item, video) {
const clickHandler = () => { const clickHandler = () => {
const isEmbedPlayer = this.player().id() === 'video-embed' || window.parent !== window; const isEmbedPlayer = this.player().id() === 'video-embed' || window.parent !== window;
@ -239,11 +456,6 @@ class EndScreenOverlay extends Component {
} else { } else {
item.addEventListener('click', clickHandler); item.addEventListener('click', clickHandler);
} }
// Add duration badge if it exists
if (item.querySelector('img').durationBadge) {
item.appendChild(item.querySelector('img').durationBadge);
}
} }
formatDuration(seconds) { formatDuration(seconds) {
@ -280,18 +492,90 @@ class EndScreenOverlay extends Component {
createSampleVideos() { createSampleVideos() {
return [ return [
{ id: 'sample1', title: 'React Full Course', author: 'Bro Code', views: '2.1M views', duration: 1800 }, {
{ id: 'sample2', title: 'JavaScript ES6+', author: 'Tech Tutorials', views: '850K views', duration: 1200 }, id: 'sample1',
{ id: 'sample3', title: 'CSS Grid Layout', author: 'Web Dev Academy', views: '1.2M views', duration: 2400 }, title: 'React Full Course - Complete Tutorial for Beginners',
{ id: 'sample4', title: 'Node.js Backend', author: 'Code Master', views: '650K views', duration: 3600 }, author: 'Bro Code',
{ id: 'sample5', title: 'Vue.js Guide', author: 'Frontend Pro', views: '980K views', duration: 2800 }, views: '2.1M views',
duration: 1800,
},
{
id: 'sample2',
title: 'JavaScript ES6+ Modern Features',
author: 'Tech Tutorials',
views: '850K views',
duration: 1200,
},
{
id: 'sample3',
title: 'CSS Grid Layout Masterclass',
author: 'Web Dev Academy',
views: '1.2M views',
duration: 2400,
},
{
id: 'sample4',
title: 'Node.js Backend Development',
author: 'Code Master',
views: '650K views',
duration: 3600,
},
{
id: 'sample5',
title: 'Vue.js Complete Guide',
author: 'Frontend Pro',
views: '980K views',
duration: 2800,
},
{ {
id: 'sample6', id: 'sample6',
title: 'Python Data Science', title: 'Python Data Science Bootcamp',
author: 'Data Academy', author: 'Data Academy',
views: '1.5M views', views: '1.5M views',
duration: 4200, duration: 4200,
}, },
{
id: 'sample7',
title: 'TypeScript for Beginners',
author: 'Code School',
views: '750K views',
duration: 1950,
},
{
id: 'sample8',
title: 'Docker Container Tutorial',
author: 'DevOps Pro',
views: '920K views',
duration: 2700,
},
{
id: 'sample9',
title: 'MongoDB Database Design',
author: 'DB Expert',
views: '580K views',
duration: 3200,
},
{
id: 'sample10',
title: 'AWS Cloud Computing',
author: 'Cloud Master',
views: '1.8M views',
duration: 4800,
},
{
id: 'sample11',
title: 'GraphQL API Development',
author: 'API Guru',
views: '420K views',
duration: 2100,
},
{
id: 'sample12',
title: 'Kubernetes Orchestration',
author: 'Container Pro',
views: '680K views',
duration: 3900,
},
]; ];
} }