Redesign end screen overlay with compact grid and text overlays

Refactors EndScreenOverlay to use a more compact grid layout, overlays video title and meta info directly on thumbnails, and adjusts card heights for better space utilization. Updates CSS for responsive breakpoints, adds new sample videos, and improves grid configuration logic for various screen sizes. Debugging styles and logs are included for development visibility.
This commit is contained in:
Yiannis Christodoulou 2025-10-11 03:09:49 +03:00
parent f07e5b9e5a
commit 30fdfaf452
2 changed files with 361 additions and 63 deletions

View File

@ -22,10 +22,14 @@
display: grid; display: grid;
gap: 12px; gap: 12px;
padding: 20px; padding: 20px;
width: 100%;
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
align-content: start; align-content: flex-start;
justify-items: stretch; justify-items: stretch;
justify-content: stretch;
grid-auto-rows: 120px; /* Compact row height */
box-sizing: border-box;
} }
/* Video item cards */ /* Video item cards */
@ -87,8 +91,8 @@
min-width: calc(50% - 6px); /* 2 items visible with gap */ min-width: calc(50% - 6px); /* 2 items visible with gap */
width: calc(50% - 6px); width: calc(50% - 6px);
max-width: 180px; max-width: 180px;
height: 220px; /* Increased height for better content display */ height: 120px; /* Compact height since text is overlaid */
min-height: 220px; min-height: 120px;
flex-shrink: 0; flex-shrink: 0;
scroll-snap-align: start; scroll-snap-align: start;
} }
@ -141,9 +145,45 @@
font-size: 11px; font-size: 11px;
font-weight: 600; font-weight: 600;
line-height: 1; line-height: 1;
z-index: 3;
}
/* Text overlay on thumbnail */
.vjs-video-text-overlay {
position: absolute;
top: 8px;
left: 8px;
right: 8px;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.4) 70%, transparent 100%);
padding: 8px;
border-radius: 4px;
z-index: 2; z-index: 2;
} }
.vjs-overlay-title {
color: #ffffff;
font-size: 13px;
font-weight: 600;
line-height: 1.3;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
.vjs-overlay-meta {
color: #e0e0e0;
font-size: 11px;
line-height: 1.2;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
}
/* Video info section */ /* Video info section */
.vjs-related-video-info { .vjs-related-video-info {
padding: 10px; padding: 10px;
@ -181,24 +221,46 @@
/* Swiper specific info styling */ /* Swiper specific info styling */
.vjs-swiper-item .vjs-related-video-info { .vjs-swiper-item .vjs-related-video-info {
padding: 10px; padding: 10px;
height: 110px; /* Increased height for better content display */ height: 110px;
min-height: 110px; min-height: 110px;
display: flex !important;
flex-direction: column !important;
justify-content: flex-start !important;
align-items: stretch !important;
width: 100% !important;
box-sizing: border-box !important;
position: relative !important;
overflow: visible !important;
} }
.vjs-swiper-item .vjs-related-video-title { .vjs-swiper-item .vjs-related-video-title {
font-size: 13px; /* Slightly larger for better readability */ font-size: 13px;
line-height: 1.3; line-height: 1.4;
margin-bottom: 8px; margin-bottom: 8px;
-webkit-line-clamp: 3; /* Allow 3 lines for titles */ -webkit-line-clamp: 3;
color: #ffffff !important;
opacity: 1 !important;
visibility: visible !important;
width: 100% !important;
box-sizing: border-box !important;
position: relative !important;
z-index: 1 !important;
} }
.vjs-swiper-item .vjs-related-video-meta { .vjs-swiper-item .vjs-related-video-meta {
font-size: 11px; /* Slightly larger for better readability */ font-size: 11px;
margin-top: 4px; margin-top: 4px;
color: #b3b3b3 !important;
opacity: 1 !important;
visibility: visible !important;
width: 100% !important;
box-sizing: border-box !important;
position: relative !important;
z-index: 1 !important;
} }
/* Responsive breakpoints */ /* Responsive breakpoints */
@media (max-width: 599px) { @media (max-width: 699px) {
/* Small screens use swiper - styles handled by JS */ /* Small screens use swiper - styles handled by JS */
.vjs-related-video-thumbnail-container { .vjs-related-video-thumbnail-container {
height: 100px; height: 100px;
@ -209,7 +271,41 @@
} }
} }
@media (min-width: 600px) and (max-width: 899px) { /* Small devices like Galaxy A51 (401-600px) */
@media (min-width: 401px) and (max-width: 600px) {
.vjs-swiper-item {
height: 120px !important; /* Compact height with overlay text */
min-height: 120px !important;
}
.vjs-swiper-item .vjs-overlay-title {
font-size: 12px !important;
-webkit-line-clamp: 2 !important;
}
.vjs-swiper-item .vjs-overlay-meta {
font-size: 10px !important;
}
}
/* Very small devices (≤400px) */
@media (max-width: 400px) {
.vjs-swiper-item {
height: 120px !important; /* Compact height with overlay text */
min-height: 120px !important;
}
.vjs-swiper-item .vjs-overlay-title {
font-size: 11px !important;
-webkit-line-clamp: 2 !important;
}
.vjs-swiper-item .vjs-overlay-meta {
font-size: 9px !important;
}
}
@media (min-width: 700px) and (max-width: 899px) {
.vjs-related-videos-grid { .vjs-related-videos-grid {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
} }

View File

@ -71,10 +71,16 @@ class EndScreenOverlay extends Component {
grid.style.gridTemplateColumns = `repeat(${columns}, 1fr)`; grid.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
grid.style.gap = '12px'; grid.style.gap = '12px';
grid.style.padding = '20px'; grid.style.padding = '20px';
grid.style.width = '100%';
grid.style.height = '100%'; grid.style.height = '100%';
grid.style.overflowY = 'auto'; grid.style.overflowY = 'auto';
grid.style.alignContent = 'start'; grid.style.alignContent = 'flex-start';
grid.style.justifyItems = 'stretch'; grid.style.justifyItems = 'stretch';
grid.style.justifyContent = 'stretch';
grid.style.gridAutoRows = '120px';
grid.style.boxSizing = 'border-box';
console.log('Creating grid with', columns, 'columns and', videosToShow.length, 'videos');
// Create video items with consistent dimensions // Create video items with consistent dimensions
videosToShow.forEach((video) => { videosToShow.forEach((video) => {
@ -174,25 +180,33 @@ class EndScreenOverlay extends Component {
const playerWidth = playerEl?.offsetWidth || window.innerWidth; const playerWidth = playerEl?.offsetWidth || window.innerWidth;
const playerHeight = playerEl?.offsetHeight || window.innerHeight; const playerHeight = playerEl?.offsetHeight || window.innerHeight;
// Calculate available space for better utilization // Calculate available space more accurately
const availableHeight = playerHeight - 140; // Account for controls and padding const controlBarHeight = 60;
const cardHeight = 180; // Consistent card height const padding = 40; // Total padding (20px top + 20px bottom)
const maxRows = Math.max(2, Math.floor(availableHeight / cardHeight)); const availableHeight = playerHeight - controlBarHeight - padding;
const cardHeight = 120; // Compact card height with text overlay
const gap = 12; // Gap between items
// Enhanced grid configuration to fill large screens better // Calculate maximum rows that can fit - be more aggressive
const maxRows = Math.max(2, Math.floor((availableHeight + gap) / (cardHeight + gap)));
console.log('Grid Config:', { playerWidth, playerHeight, availableHeight, maxRows });
// Enhanced grid configuration to fill all available space
if (playerWidth >= 1600) { if (playerWidth >= 1600) {
const columns = 5; const columns = 5;
return { columns, maxVideos: columns * Math.min(maxRows, 3), useSwiper: false }; // 5 columns, up to 3 rows return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
} else if (playerWidth >= 1200) { } else if (playerWidth >= 1200) {
const columns = 4; const columns = 4;
return { columns, maxVideos: columns * Math.min(maxRows, 3), useSwiper: false }; // 4 columns, up to 3 rows return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
} else if (playerWidth >= 900) { } else if (playerWidth >= 900) {
const columns = 3; const columns = 3;
return { columns, maxVideos: columns * Math.min(maxRows, 2), useSwiper: false }; // 3 columns, up to 2 rows return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
} else if (playerWidth >= 600) { } else if (playerWidth >= 700) {
return { columns: 2, maxVideos: 4, useSwiper: false }; // 2x2 grid for medium screens const columns = 2;
return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
} else { } else {
return { columns: 2, maxVideos: 6, useSwiper: true }; // Use swiper for small screens return { columns: 2, maxVideos: 12, useSwiper: true }; // Use swiper for small screens
} }
} }
@ -227,13 +241,17 @@ class EndScreenOverlay extends Component {
item.style.minWidth = 'calc(50% - 6px)'; // 50% width minus half the gap item.style.minWidth = 'calc(50% - 6px)'; // 50% width minus half the gap
item.style.width = 'calc(50% - 6px)'; item.style.width = 'calc(50% - 6px)';
item.style.maxWidth = '180px'; // Maximum width for larger screens item.style.maxWidth = '180px'; // Maximum width for larger screens
item.style.height = '220px'; // Increased height for better content display
item.style.minHeight = '220px'; // Simpler height since text is overlaid on thumbnail
const cardHeight = '120px'; // Just the thumbnail height
item.style.height = cardHeight;
item.style.minHeight = cardHeight;
item.style.flexShrink = '0'; item.style.flexShrink = '0';
item.style.scrollSnapAlign = 'start'; item.style.scrollSnapAlign = 'start';
} else { } else {
item.style.height = '180px'; item.style.height = '120px'; // Same compact height for regular grid
item.style.minHeight = '180px'; item.style.minHeight = '120px';
item.style.width = '100%'; item.style.width = '100%';
} }
@ -251,13 +269,11 @@ class EndScreenOverlay extends Component {
}); });
} }
// Create thumbnail container // Create thumbnail container with overlaid text
const thumbnailContainer = this.createThumbnailContainer(video, isSwiperMode); const thumbnailContainer = this.createThumbnailWithOverlay(video, isSwiperMode);
item.appendChild(thumbnailContainer); item.appendChild(thumbnailContainer);
// Create simplified info section console.log('Created video item with overlay:', item);
const info = this.createVideoInfo(video, isSwiperMode);
item.appendChild(info);
// Add click handler // Add click handler
this.addClickHandler(item, video); this.addClickHandler(item, video);
@ -315,39 +331,160 @@ class EndScreenOverlay extends Component {
return container; return container;
} }
createVideoInfo(video, isSwiperMode = false) { createThumbnailWithOverlay(video, isSwiperMode = false) {
const container = videojs.dom.createEl('div', {
className: 'vjs-related-video-thumbnail-container',
});
// Container styling - full height since it contains everything
container.style.position = 'relative';
container.style.width = '100%';
container.style.height = '120px';
container.style.overflow = 'hidden';
container.style.borderRadius = '6px';
// Create thumbnail image
const thumbnail = videojs.dom.createEl('img', {
className: 'vjs-related-video-thumbnail',
src: video.thumbnail || this.getPlaceholderImage(video.title),
alt: video.title,
});
thumbnail.style.width = '100%';
thumbnail.style.height = '100%';
thumbnail.style.objectFit = 'cover';
thumbnail.style.display = 'block';
container.appendChild(thumbnail);
// Add duration badge at bottom right
if (video.duration && video.duration > 0) {
const duration = videojs.dom.createEl('div', {
className: 'vjs-video-duration',
});
duration.textContent = this.formatDuration(video.duration);
duration.style.position = 'absolute';
duration.style.bottom = '4px';
duration.style.right = '4px';
duration.style.backgroundColor = 'rgba(0, 0, 0, 0.85)';
duration.style.color = 'white';
duration.style.padding = '2px 6px';
duration.style.borderRadius = '3px';
duration.style.fontSize = '11px';
duration.style.fontWeight = '600';
duration.style.lineHeight = '1';
duration.style.zIndex = '3';
container.appendChild(duration);
}
// Create text overlay at top-left
const textOverlay = videojs.dom.createEl('div', {
className: 'vjs-video-text-overlay',
});
textOverlay.style.position = 'absolute';
textOverlay.style.top = '8px';
textOverlay.style.left = '8px';
textOverlay.style.right = '8px';
textOverlay.style.background =
'linear-gradient(to bottom, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 70%, transparent 100%)';
textOverlay.style.padding = '8px';
textOverlay.style.borderRadius = '4px';
textOverlay.style.zIndex = '2';
// Create title
const title = videojs.dom.createEl('div', {
className: 'vjs-overlay-title',
});
title.textContent = video.title || 'Sample Video Title';
title.style.color = '#ffffff';
title.style.fontSize = isSwiperMode ? '12px' : '13px';
title.style.fontWeight = '600';
title.style.lineHeight = '1.3';
title.style.marginBottom = '4px';
title.style.overflow = 'hidden';
title.style.textOverflow = 'ellipsis';
title.style.display = '-webkit-box';
title.style.webkitLineClamp = '2';
title.style.webkitBoxOrient = 'vertical';
title.style.textShadow = '0 1px 2px rgba(0,0,0,0.8)';
// Create meta info
const meta = videojs.dom.createEl('div', {
className: 'vjs-overlay-meta',
});
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 {
metaText = 'Unknown • No views';
}
meta.textContent = metaText;
meta.style.color = '#e0e0e0';
meta.style.fontSize = isSwiperMode ? '10px' : '11px';
meta.style.lineHeight = '1.2';
meta.style.overflow = 'hidden';
meta.style.textOverflow = 'ellipsis';
meta.style.whiteSpace = 'nowrap';
meta.style.textShadow = '0 1px 2px rgba(0,0,0,0.8)';
textOverlay.appendChild(title);
textOverlay.appendChild(meta);
container.appendChild(textOverlay);
console.log('Created thumbnail with overlay:', container);
console.log('Title:', title.textContent, 'Meta:', meta.textContent);
return container;
}
createVideoInfo(video) {
const info = videojs.dom.createEl('div', { const info = videojs.dom.createEl('div', {
className: 'vjs-related-video-info', className: 'vjs-related-video-info',
}); });
// Consistent info styling with increased height for swiper mode // Note: Using simplified styling for debugging
const padding = isSwiperMode ? '10px' : '10px';
const infoHeight = isSwiperMode ? '110px' : '70px'; // Increased height for swiper mode
info.style.padding = padding; // Force visible info section with simple styling
info.style.padding = '12px';
info.style.backgroundColor = 'rgba(26, 26, 26, 0.9)'; // Visible background for debugging
info.style.color = 'white'; info.style.color = 'white';
info.style.flex = '1'; info.style.display = 'block'; // Use simple block display
info.style.display = 'flex'; info.style.width = '100%';
info.style.flexDirection = 'column'; info.style.height = 'auto';
info.style.justifyContent = 'flex-start'; // Align content to top info.style.minHeight = '80px';
info.style.height = infoHeight; info.style.position = 'relative';
info.style.minHeight = infoHeight; info.style.zIndex = '10';
// Title with responsive text handling // 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 || 'Sample Video Title';
title.style.fontSize = isSwiperMode ? '13px' : '13px'; // Slightly larger font for better readability console.log('Setting title:', video.title, 'for video:', video);
title.style.fontWeight = '600';
title.style.lineHeight = '1.3'; // Note: Using fixed styling for debugging
title.style.overflow = 'hidden';
title.style.textOverflow = 'ellipsis'; // Simple, guaranteed visible title styling
title.style.display = '-webkit-box'; title.style.fontSize = '14px';
title.style.webkitLineClamp = isSwiperMode ? '3' : '2'; // Allow 3 lines for swiper mode title.style.fontWeight = 'bold';
title.style.webkitBoxOrient = 'vertical'; title.style.lineHeight = '1.4';
title.style.marginBottom = isSwiperMode ? '8px' : '4px';
title.style.color = '#ffffff'; title.style.color = '#ffffff';
title.style.backgroundColor = 'rgba(255, 0, 0, 0.2)'; // Red background for debugging
title.style.padding = '4px';
title.style.marginBottom = '8px';
title.style.display = 'block';
title.style.width = '100%';
title.style.wordWrap = 'break-word';
title.style.position = 'relative';
title.style.zIndex = '20';
// Meta information - always show for swiper mode // Meta information - always show for swiper mode
const meta = videojs.dom.createEl('div', { const meta = videojs.dom.createEl('div', {
@ -367,18 +504,27 @@ class EndScreenOverlay extends Component {
metaText = 'Unknown • No views'; metaText = 'Unknown • No views';
} }
meta.textContent = metaText; meta.textContent = metaText || 'Sample Author • 1K views';
meta.style.fontSize = isSwiperMode ? '11px' : '11px'; // Slightly larger font for better readability console.log('Setting meta:', metaText, 'for video:', video);
// Note: Using fixed styling for debugging
// Simple, guaranteed visible meta styling
meta.style.fontSize = '12px';
meta.style.color = '#b3b3b3'; meta.style.color = '#b3b3b3';
meta.style.overflow = 'hidden'; meta.style.backgroundColor = 'rgba(0, 255, 0, 0.2)'; // Green background for debugging
meta.style.textOverflow = 'ellipsis'; meta.style.padding = '4px';
meta.style.whiteSpace = 'nowrap'; meta.style.display = 'block';
meta.style.flexShrink = '0'; meta.style.width = '100%';
meta.style.lineHeight = '1.3'; meta.style.position = 'relative';
meta.style.marginTop = isSwiperMode ? '4px' : '0px'; // Add some spacing meta.style.zIndex = '20';
info.appendChild(title); info.appendChild(title);
info.appendChild(meta); // Always append meta for consistent layout info.appendChild(meta);
console.log('Created info section:', info);
console.log('Title element:', title, 'Text:', title.textContent);
console.log('Meta element:', meta, 'Text:', meta.textContent);
return info; return info;
} }
@ -557,7 +703,7 @@ class EndScreenOverlay extends Component {
}, },
{ {
id: 'sample10', id: 'sample10',
title: 'AWS Cloud Computing', title: 'AWS Cloud Computing Essentials',
author: 'Cloud Master', author: 'Cloud Master',
views: '1.8M views', views: '1.8M views',
duration: 4800, duration: 4800,
@ -571,11 +717,67 @@ class EndScreenOverlay extends Component {
}, },
{ {
id: 'sample12', id: 'sample12',
title: 'Kubernetes Orchestration', title: 'Kubernetes Orchestration Guide',
author: 'Container Pro', author: 'Container Pro',
views: '680K views', views: '680K views',
duration: 3900, duration: 3900,
}, },
{
id: 'sample13',
title: 'Redis Caching Strategies',
author: 'Cache Expert',
views: '520K views',
duration: 2250,
},
{
id: 'sample14',
title: 'Web Performance Optimization',
author: 'Speed Master',
views: '890K views',
duration: 3100,
},
{
id: 'sample15',
title: 'CI/CD Pipeline Setup',
author: 'DevOps Guide',
views: '710K views',
duration: 2900,
},
{
id: 'sample16',
title: 'Microservices Architecture',
author: 'System Design',
views: '1.3M views',
duration: 4500,
},
{
id: 'sample17',
title: 'Next.js App Router Tutorial',
author: 'Web Academy',
views: '640K views',
duration: 2650,
},
{
id: 'sample18',
title: 'Tailwind CSS Crash Course',
author: 'CSS Master',
views: '1.1M views',
duration: 1800,
},
{
id: 'sample19',
title: 'Git and GitHub Essentials',
author: 'Version Control Pro',
views: '2.3M views',
duration: 3300,
},
{
id: 'sample20',
title: 'REST API Best Practices',
author: 'API Design',
views: '780K views',
duration: 2400,
},
]; ];
} }