diff --git a/src/pages/broadcasts/details.tsx b/src/pages/broadcasts/details.tsx
index c4d96b80..4f585076 100644
--- a/src/pages/broadcasts/details.tsx
+++ b/src/pages/broadcasts/details.tsx
@@ -85,27 +85,28 @@ export default function VideoDetails(): ReactElement {
-
{title}
+
{title}
+
+ {video.type === 'shorts' ? '📱 Shorts' : '🎥 Video'}
+
-
- {video.type === 'shorts' ? '📱 Shorts' : '🎥 Video'}
+
-
+
+
Watch in full screen for the best viewing experience
+
diff --git a/src/pages/broadcasts/index.css b/src/pages/broadcasts/index.css
index 80ebeb8e..5ce98fce 100644
--- a/src/pages/broadcasts/index.css
+++ b/src/pages/broadcasts/index.css
@@ -157,12 +157,10 @@ h1 {
}
.podcast-embed-large {
- margin-bottom: 2rem;
-}
-
-.podcast-transcript {
- background: #fff;
- padding: 2rem;
+ position: relative;
+ width: 100%;
+ padding-top: 56.25%; /* 16:9 Aspect Ratio */
+ margin: 2rem 0;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
@@ -177,18 +175,22 @@ h1 {
}
.details-card {
- max-width: 1000px;
+ max-width: 1200px;
margin: 0 auto;
padding: 2rem;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
+ border-radius: 12px;
background: var(--ifm-background-surface-color);
+ width: 100%;
+ box-sizing: border-box;
}
.video-container {
- max-width: 1200px;
+ max-width: 1400px;
margin: 0 auto;
- padding: 2rem;
+ padding: 2rem 1rem;
+ width: 100%;
+ box-sizing: border-box;
}
.video-subtitle {
@@ -310,10 +312,17 @@ h1 {
}
.video-embed-large {
- width: 100%;
- margin: 1rem 0;
position: relative;
+ width: 100%;
padding-top: 56.25%; /* 16:9 Aspect Ratio */
+ margin: 2rem 0;
+ border-radius: 12px;
+ overflow: hidden;
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
+ background: #000;
+ max-width: 1200px;
+ margin-left: auto;
+ margin-right: auto;
}
.video-embed-large iframe {
@@ -322,9 +331,70 @@ h1 {
left: 0;
width: 100%;
height: 100%;
+ border: none;
border-radius: 8px;
}
+.video-title h1 {
+ font-size: 2rem;
+ margin: 0 0 0.5rem 0;
+ color: var(--ifm-heading-color);
+ line-height: 1.2;
+}
+
+.video-type {
+ display: inline-block;
+ background: var(--ifm-color-primary);
+ color: white;
+ padding: 0.25rem 0.75rem;
+ border-radius: 20px;
+ font-size: 0.9rem;
+ margin-bottom: 1rem;
+}
+
+.video-description {
+ font-size: 1.1rem;
+ line-height: 1.6;
+ color: var(--ifm-font-color-base);
+ margin-bottom: 2rem;
+ max-width: 800px;
+}
+
+.video-meta {
+ text-align: center;
+ margin-top: 1.5rem;
+ color: var(--ifm-color-emphasis-600);
+ font-size: 0.9rem;
+ font-style: italic;
+}
+
+/* Responsive adjustments */
+@media (max-width: 996px) {
+ .video-title h1 {
+ font-size: 1.75rem;
+ }
+
+ .video-description {
+ font-size: 1rem;
+ }
+}
+
+@media (max-width: 768px) {
+ .video-title h1 {
+ font-size: 1.5rem;
+ }
+
+ .video-embed-large {
+ border-radius: 0;
+ margin: 1.5rem -1rem;
+ width: calc(100% + 2rem);
+ }
+
+ .video-embed-large iframe {
+ border-radius: 0;
+ }
+}
+
.pagination {
display: flex;
justify-content: center;
diff --git a/src/pages/broadcasts/index.tsx b/src/pages/broadcasts/index.tsx
index 05332447..e5cf1718 100644
--- a/src/pages/broadcasts/index.tsx
+++ b/src/pages/broadcasts/index.tsx
@@ -11,12 +11,25 @@ interface VideoData {
}
const getYoutubeVideoId = (url: string): string => {
- let videoId = '';
- const normalMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\s]+)/);
- const shortsMatch = url.match(/youtube\.com\/shorts\/([^&\s]+)/);
- if (normalMatch) videoId = normalMatch[1];
- else if (shortsMatch) videoId = shortsMatch[1];
- return videoId;
+ // Handle youtu.be short links
+ if (url.includes('youtu.be/')) {
+ const match = url.match(/youtu\.be\/([^?&\s]+)/);
+ return match ? match[1].split('?')[0] : '';
+ }
+
+ // Handle youtube.com/watch?v= links
+ if (url.includes('youtube.com/watch')) {
+ const match = url.match(/[?&]v=([^&\s]+)/);
+ return match ? match[1].split('&')[0] : '';
+ }
+
+ // Handle youtube.com/shorts/ links
+ if (url.includes('youtube.com/shorts/')) {
+ const match = url.match(/shorts\/([^?&\s]+)/);
+ return match ? match[1].split('?')[0] : '';
+ }
+
+ return '';
};
const getYoutubeContentType = (url: string): 'video' | 'shorts' =>
@@ -50,10 +63,64 @@ const VideoCard: React.FC<{
onClick: (video: VideoData, event: React.MouseEvent | React.KeyboardEvent) => void;
}> = ({ video, onClick }) => {
const [title, setTitle] = useState('Loading...');
- const [thumbnailError, setThumbnailError] = useState(false);
+ const [thumbnailUrl, setThumbnailUrl] = useState('');
const videoId = getYoutubeVideoId(video.youtubeUrl);
+
+ // Try different thumbnail qualities in sequence
+ const tryThumbnailUrl = (url: string) => {
+ if (!videoId) return;
+
+ const img = new Image();
+ img.crossOrigin = 'anonymous'; // Handle CORS if needed
+
+ img.onload = () => {
+ // Only set the URL if it's not an error image
+ if (img.width > 0 && img.height > 0) {
+ setThumbnailUrl(url);
+ } else {
+ handleThumbnailError(url);
+ }
+ };
+
+ img.onerror = () => handleThumbnailError(url);
+ img.src = url;
+ };
+
+ const handleThumbnailError = (failedUrl: string) => {
+ console.log(`Failed to load thumbnail: ${failedUrl}`);
+
+ if (failedUrl.includes('maxresdefault')) {
+ // Try hqdefault if maxresdefault fails
+ tryThumbnailUrl(`https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`);
+ } else if (failedUrl.includes('hqdefault')) {
+ // Try mqdefault if hqdefault fails
+ tryThumbnailUrl(`https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`);
+ } else if (failedUrl.includes('mqdefault')) {
+ // Try default if mqdefault fails
+ tryThumbnailUrl(`https://i.ytimg.com/vi/${videoId}/default.jpg`);
+ } else {
+ // All options failed, show placeholder
+ setThumbnailUrl('');
+ }
+ };
useEffect(() => {
+ if (!videoId) return;
+
+ // Start with the highest quality thumbnail
+ console.log(`Loading thumbnails for video ID: ${videoId}`);
+ tryThumbnailUrl(`https://i.ytimg.com/vi/${videoId}/maxresdefault.jpg`);
+
+ // Also try the first frame as a fallback (sometimes works when others don't)
+ const firstFrameUrl = `https://img.youtube.com/vi/${videoId}/0.jpg`;
+ setTimeout(() => {
+ if (!thumbnailUrl) {
+ console.log('Trying first frame as fallback');
+ tryThumbnailUrl(firstFrameUrl);
+ }
+ }, 1000);
+
+ // Fetch video title
const fetchVideoTitle = async () => {
try {
const response = await fetch(`https://www.youtube.com/oembed?url=${encodeURIComponent(video.youtubeUrl)}&format=json`);
@@ -64,8 +131,7 @@ const VideoCard: React.FC<{
console.error('Error fetching video title:', error);
}
};
-
- setThumbnailError(false);
+
fetchVideoTitle();
}, [video.youtubeUrl, videoId]);
@@ -80,7 +146,8 @@ const VideoCard: React.FC<{
}}
>
-
{title}
+
+
{title}
{video.type === 'shorts' ? (
@@ -96,26 +163,27 @@ const VideoCard: React.FC<{
- {!thumbnailError && (
-

{
- const img = e.target as HTMLImageElement;
- if (img.src.includes('maxresdefault')) {
- img.src = `https://i.ytimg.com/vi/${videoId}/sddefault.jpg`;
- } else if (img.src.includes('sddefault')) {
- img.src = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
- } else if (img.src.includes('hqdefault')) {
- img.src = `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`;
- } else {
- setThumbnailError(true);
- }
- }}
- />
- )}
-
â–¶
+
+ {thumbnailUrl ? (
+

{
+ const img = e.target as HTMLImageElement;
+ console.log('Image error:', img.src);
+ // Let the parent component handle the error
+ setThumbnailUrl('');
+ }}
+ />
+ ) : (
+
+ Loading thumbnail...
+
+ )}
+ {/* Play button removed as per request */}
+
@@ -146,7 +214,8 @@ const Pagination: React.FC<{
totalPages: number;
setCurrentPage: (page: number) => void;
}> = ({ currentPage, totalPages, setCurrentPage }) => (
-