mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-11 09:58:53 -05:00
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:
parent
24e9fb4e40
commit
107b8d9db0
@ -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 {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -25,15 +25,17 @@ class EndScreenOverlay extends Component {
|
||||
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.top = '0';
|
||||
overlay.style.left = '0';
|
||||
overlay.style.right = '0';
|
||||
overlay.style.bottom = '60px'; // Leave space for control bar
|
||||
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.overflow = 'hidden';
|
||||
overlay.style.boxSizing = 'border-box';
|
||||
|
||||
// Create responsive grid
|
||||
const grid = this.createGrid();
|
||||
@ -43,30 +45,38 @@ class EndScreenOverlay extends Component {
|
||||
}
|
||||
|
||||
createGrid() {
|
||||
const grid = videojs.dom.createEl('div', {
|
||||
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)`;
|
||||
const { columns, maxVideos, useSwiper } = this.getGridConfig();
|
||||
|
||||
// Get videos to show - access directly from options during createEl
|
||||
const relatedVideos = this.options_?.relatedVideos || this.relatedVideos || [];
|
||||
|
||||
const videosToShow =
|
||||
relatedVideos.length > 0
|
||||
? relatedVideos.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) => {
|
||||
const videoItem = this.createVideoItem(video);
|
||||
grid.appendChild(videoItem);
|
||||
@ -75,20 +85,114 @@ class EndScreenOverlay extends Component {
|
||||
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() {
|
||||
const playerEl = this.player().el();
|
||||
const playerWidth = playerEl?.offsetWidth || window.innerWidth;
|
||||
const playerHeight = playerEl?.offsetHeight || window.innerHeight;
|
||||
|
||||
// Responsive grid configuration
|
||||
if (playerWidth >= 1200) {
|
||||
return { columns: 4, maxVideos: 8 }; // 4x2 grid for large screens
|
||||
} else if (playerWidth >= 800) {
|
||||
return { columns: 3, maxVideos: 6 }; // 3x2 grid for medium screens
|
||||
} else if (playerWidth >= 500) {
|
||||
return { columns: 2, maxVideos: 4 }; // 2x2 grid for small screens
|
||||
// Calculate available space for better utilization
|
||||
const availableHeight = playerHeight - 140; // Account for controls and padding
|
||||
const cardHeight = 180; // Consistent card height
|
||||
const maxRows = Math.max(2, Math.floor(availableHeight / cardHeight));
|
||||
|
||||
// Enhanced grid configuration to fill large screens better
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
createVideoItem(video) {
|
||||
createVideoItem(video, isSwiperMode = false) {
|
||||
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.backgroundColor = '#1a1a1a';
|
||||
item.style.borderRadius = '8px';
|
||||
item.style.borderRadius = '6px';
|
||||
item.style.overflow = 'hidden';
|
||||
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) {
|
||||
item.style.touchAction = 'manipulation';
|
||||
} else {
|
||||
item.addEventListener('mouseenter', () => {
|
||||
item.style.transform = 'scale(1.05)';
|
||||
item.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)';
|
||||
item.style.transform = 'translateY(-2px)';
|
||||
item.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.25)';
|
||||
});
|
||||
item.addEventListener('mouseleave', () => {
|
||||
item.style.transform = 'scale(1)';
|
||||
item.style.transform = 'translateY(0)';
|
||||
item.style.boxShadow = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Create thumbnail
|
||||
const thumbnail = this.createThumbnail(video);
|
||||
item.appendChild(thumbnail);
|
||||
// Create thumbnail container
|
||||
const thumbnailContainer = this.createThumbnailContainer(video, isSwiperMode);
|
||||
item.appendChild(thumbnailContainer);
|
||||
|
||||
// Create info overlay
|
||||
const info = this.createVideoInfo(video);
|
||||
// Create simplified info section
|
||||
const info = this.createVideoInfo(video, isSwiperMode);
|
||||
item.appendChild(info);
|
||||
|
||||
// Add click handler
|
||||
@ -143,7 +265,18 @@ class EndScreenOverlay extends Component {
|
||||
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', {
|
||||
className: 'vjs-related-video-thumbnail',
|
||||
src: video.thumbnail || this.getPlaceholderImage(video.title),
|
||||
@ -152,74 +285,158 @@ class EndScreenOverlay extends Component {
|
||||
|
||||
// Thumbnail styling
|
||||
thumbnail.style.width = '100%';
|
||||
thumbnail.style.height = '120px';
|
||||
thumbnail.style.height = '100%';
|
||||
thumbnail.style.objectFit = 'cover';
|
||||
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) {
|
||||
const duration = videojs.dom.createEl('div', {
|
||||
className: 'vjs-video-duration',
|
||||
});
|
||||
duration.textContent = this.formatDuration(video.duration);
|
||||
duration.style.position = 'absolute';
|
||||
duration.style.bottom = '50px';
|
||||
duration.style.right = '8px';
|
||||
duration.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
|
||||
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 = '4px';
|
||||
duration.style.fontSize = '12px';
|
||||
duration.style.fontWeight = 'bold';
|
||||
duration.style.borderRadius = '3px';
|
||||
duration.style.fontSize = isSwiperMode ? '10px' : '11px';
|
||||
duration.style.fontWeight = '600';
|
||||
duration.style.lineHeight = '1';
|
||||
duration.style.zIndex = '2';
|
||||
|
||||
// Add duration to parent item (will be added later)
|
||||
thumbnail.durationBadge = duration;
|
||||
container.appendChild(duration);
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
return container;
|
||||
}
|
||||
|
||||
createVideoInfo(video) {
|
||||
createVideoInfo(video, isSwiperMode = false) {
|
||||
const info = videojs.dom.createEl('div', {
|
||||
className: 'vjs-related-video-info',
|
||||
});
|
||||
|
||||
// Info styling
|
||||
info.style.padding = '12px';
|
||||
info.style.color = 'white';
|
||||
// Consistent info styling with increased height for swiper mode
|
||||
const padding = isSwiperMode ? '10px' : '10px';
|
||||
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', {
|
||||
className: 'vjs-related-video-title',
|
||||
});
|
||||
title.textContent = video.title;
|
||||
title.style.fontSize = '14px';
|
||||
title.style.fontWeight = 'bold';
|
||||
title.style.marginBottom = '4px';
|
||||
title.style.fontSize = isSwiperMode ? '13px' : '13px'; // Slightly larger font for better readability
|
||||
title.style.fontWeight = '600';
|
||||
title.style.lineHeight = '1.3';
|
||||
title.style.overflow = 'hidden';
|
||||
title.style.textOverflow = 'ellipsis';
|
||||
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.marginBottom = isSwiperMode ? '8px' : '4px';
|
||||
title.style.color = '#ffffff';
|
||||
|
||||
// Author and views
|
||||
// Meta information - always show for swiper mode
|
||||
const meta = videojs.dom.createEl('div', {
|
||||
className: 'vjs-related-video-meta',
|
||||
});
|
||||
meta.textContent = `${video.author} • ${video.views}`;
|
||||
meta.style.fontSize = '12px';
|
||||
meta.style.color = '#aaa';
|
||||
|
||||
// Format meta text more cleanly - ensure both author and views are shown
|
||||
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.textOverflow = 'ellipsis';
|
||||
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(meta);
|
||||
info.appendChild(meta); // Always append meta for consistent layout
|
||||
|
||||
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) {
|
||||
const clickHandler = () => {
|
||||
const isEmbedPlayer = this.player().id() === 'video-embed' || window.parent !== window;
|
||||
@ -239,11 +456,6 @@ class EndScreenOverlay extends Component {
|
||||
} else {
|
||||
item.addEventListener('click', clickHandler);
|
||||
}
|
||||
|
||||
// Add duration badge if it exists
|
||||
if (item.querySelector('img').durationBadge) {
|
||||
item.appendChild(item.querySelector('img').durationBadge);
|
||||
}
|
||||
}
|
||||
|
||||
formatDuration(seconds) {
|
||||
@ -280,18 +492,90 @@ class EndScreenOverlay extends Component {
|
||||
|
||||
createSampleVideos() {
|
||||
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: 'sample3', title: 'CSS Grid Layout', author: 'Web Dev Academy', views: '1.2M views', duration: 2400 },
|
||||
{ id: 'sample4', title: 'Node.js Backend', author: 'Code Master', views: '650K views', duration: 3600 },
|
||||
{ id: 'sample5', title: 'Vue.js Guide', author: 'Frontend Pro', views: '980K views', duration: 2800 },
|
||||
{
|
||||
id: 'sample1',
|
||||
title: 'React Full Course - Complete Tutorial for Beginners',
|
||||
author: 'Bro Code',
|
||||
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',
|
||||
title: 'Python Data Science',
|
||||
title: 'Python Data Science Bootcamp',
|
||||
author: 'Data Academy',
|
||||
views: '1.5M views',
|
||||
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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user