mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
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:
parent
f07e5b9e5a
commit
30fdfaf452
@ -22,10 +22,14 @@
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
align-content: start;
|
||||
align-content: flex-start;
|
||||
justify-items: stretch;
|
||||
justify-content: stretch;
|
||||
grid-auto-rows: 120px; /* Compact row height */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Video item cards */
|
||||
@ -87,8 +91,8 @@
|
||||
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;
|
||||
height: 120px; /* Compact height since text is overlaid */
|
||||
min-height: 120px;
|
||||
flex-shrink: 0;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
@ -141,9 +145,45 @@
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
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;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.vjs-related-video-info {
|
||||
padding: 10px;
|
||||
@ -181,24 +221,46 @@
|
||||
/* Swiper specific info styling */
|
||||
.vjs-swiper-item .vjs-related-video-info {
|
||||
padding: 10px;
|
||||
height: 110px; /* Increased height for better content display */
|
||||
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 {
|
||||
font-size: 13px; /* Slightly larger for better readability */
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
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 {
|
||||
font-size: 11px; /* Slightly larger for better readability */
|
||||
font-size: 11px;
|
||||
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 */
|
||||
@media (max-width: 599px) {
|
||||
@media (max-width: 699px) {
|
||||
/* Small screens use swiper - styles handled by JS */
|
||||
.vjs-related-video-thumbnail-container {
|
||||
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 {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@ -71,10 +71,16 @@ class EndScreenOverlay extends Component {
|
||||
grid.style.gridTemplateColumns = `repeat(${columns}, 1fr)`;
|
||||
grid.style.gap = '12px';
|
||||
grid.style.padding = '20px';
|
||||
grid.style.width = '100%';
|
||||
grid.style.height = '100%';
|
||||
grid.style.overflowY = 'auto';
|
||||
grid.style.alignContent = 'start';
|
||||
grid.style.alignContent = 'flex-start';
|
||||
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
|
||||
videosToShow.forEach((video) => {
|
||||
@ -174,25 +180,33 @@ class EndScreenOverlay extends Component {
|
||||
const playerWidth = playerEl?.offsetWidth || window.innerWidth;
|
||||
const playerHeight = playerEl?.offsetHeight || window.innerHeight;
|
||||
|
||||
// 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));
|
||||
// Calculate available space more accurately
|
||||
const controlBarHeight = 60;
|
||||
const padding = 40; // Total padding (20px top + 20px bottom)
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
|
||||
} else if (playerWidth >= 700) {
|
||||
const columns = 2;
|
||||
return { columns, maxVideos: columns * maxRows, useSwiper: false }; // Fill all available rows
|
||||
} 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.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';
|
||||
|
||||
// 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.scrollSnapAlign = 'start';
|
||||
} else {
|
||||
item.style.height = '180px';
|
||||
item.style.minHeight = '180px';
|
||||
item.style.height = '120px'; // Same compact height for regular grid
|
||||
item.style.minHeight = '120px';
|
||||
item.style.width = '100%';
|
||||
}
|
||||
|
||||
@ -251,13 +269,11 @@ class EndScreenOverlay extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
// Create thumbnail container
|
||||
const thumbnailContainer = this.createThumbnailContainer(video, isSwiperMode);
|
||||
// Create thumbnail container with overlaid text
|
||||
const thumbnailContainer = this.createThumbnailWithOverlay(video, isSwiperMode);
|
||||
item.appendChild(thumbnailContainer);
|
||||
|
||||
// Create simplified info section
|
||||
const info = this.createVideoInfo(video, isSwiperMode);
|
||||
item.appendChild(info);
|
||||
console.log('Created video item with overlay:', item);
|
||||
|
||||
// Add click handler
|
||||
this.addClickHandler(item, video);
|
||||
@ -315,39 +331,160 @@ class EndScreenOverlay extends Component {
|
||||
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', {
|
||||
className: 'vjs-related-video-info',
|
||||
});
|
||||
|
||||
// Consistent info styling with increased height for swiper mode
|
||||
const padding = isSwiperMode ? '10px' : '10px';
|
||||
const infoHeight = isSwiperMode ? '110px' : '70px'; // Increased height for swiper mode
|
||||
// Note: Using simplified styling for debugging
|
||||
|
||||
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.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;
|
||||
info.style.display = 'block'; // Use simple block display
|
||||
info.style.width = '100%';
|
||||
info.style.height = 'auto';
|
||||
info.style.minHeight = '80px';
|
||||
info.style.position = 'relative';
|
||||
info.style.zIndex = '10';
|
||||
|
||||
// Title with responsive text handling
|
||||
const title = videojs.dom.createEl('div', {
|
||||
className: 'vjs-related-video-title',
|
||||
});
|
||||
title.textContent = video.title;
|
||||
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 = isSwiperMode ? '3' : '2'; // Allow 3 lines for swiper mode
|
||||
title.style.webkitBoxOrient = 'vertical';
|
||||
title.style.marginBottom = isSwiperMode ? '8px' : '4px';
|
||||
title.textContent = video.title || 'Sample Video Title';
|
||||
console.log('Setting title:', video.title, 'for video:', video);
|
||||
|
||||
// Note: Using fixed styling for debugging
|
||||
|
||||
// Simple, guaranteed visible title styling
|
||||
title.style.fontSize = '14px';
|
||||
title.style.fontWeight = 'bold';
|
||||
title.style.lineHeight = '1.4';
|
||||
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
|
||||
const meta = videojs.dom.createEl('div', {
|
||||
@ -367,18 +504,27 @@ class EndScreenOverlay extends Component {
|
||||
metaText = 'Unknown • No views';
|
||||
}
|
||||
|
||||
meta.textContent = metaText;
|
||||
meta.style.fontSize = isSwiperMode ? '11px' : '11px'; // Slightly larger font for better readability
|
||||
meta.textContent = metaText || 'Sample Author • 1K views';
|
||||
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.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
|
||||
meta.style.backgroundColor = 'rgba(0, 255, 0, 0.2)'; // Green background for debugging
|
||||
meta.style.padding = '4px';
|
||||
meta.style.display = 'block';
|
||||
meta.style.width = '100%';
|
||||
meta.style.position = 'relative';
|
||||
meta.style.zIndex = '20';
|
||||
|
||||
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;
|
||||
}
|
||||
@ -557,7 +703,7 @@ class EndScreenOverlay extends Component {
|
||||
},
|
||||
{
|
||||
id: 'sample10',
|
||||
title: 'AWS Cloud Computing',
|
||||
title: 'AWS Cloud Computing Essentials',
|
||||
author: 'Cloud Master',
|
||||
views: '1.8M views',
|
||||
duration: 4800,
|
||||
@ -571,11 +717,67 @@ class EndScreenOverlay extends Component {
|
||||
},
|
||||
{
|
||||
id: 'sample12',
|
||||
title: 'Kubernetes Orchestration',
|
||||
title: 'Kubernetes Orchestration Guide',
|
||||
author: 'Container Pro',
|
||||
views: '680K views',
|
||||
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,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user