Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title></title> | |
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
<link href="https://fonts.googleapis.com/css2?family=Montserrat&display=swap" rel="stylesheet"> | |
<style> | |
:root { | |
--bg-primary: #0a0f18; | |
--bg-secondary: #141e2f; | |
--text-primary: #ffffff; | |
--text-secondary: #adbac7; | |
--accent: #2188ff; | |
} | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
background-color: var(--bg-primary); | |
color: var(--text-primary); | |
} | |
body { | |
font-family: 'Montserrat', sans-serif; | |
} | |
header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 0.5rem 1rem; | |
background-color: var(--bg-secondary); | |
position: fixed; | |
top: 0; | |
left: 0; | |
right: 0; | |
z-index: 1000; | |
} | |
.logo { | |
color: var(--text-primary); | |
font-size: 1.2rem; | |
font-weight: bold; | |
display: flex; | |
align-items: center; | |
} | |
.logo i { | |
color: var(--accent); | |
margin-right: 0.5rem; | |
} | |
.search-bar { | |
flex-grow: 1; | |
max-width: 600px; | |
margin: 0 1rem; | |
} | |
.search-bar input { | |
width: 100%; | |
padding: 0.5rem 1rem; | |
border-radius: 20px; | |
border: 1px solid var(--text-secondary); | |
background-color: var(--bg-primary); | |
color: var(--text-primary); | |
} | |
.user-actions i { | |
margin-left: 1rem; | |
cursor: pointer; | |
} | |
main { | |
margin-top: 56px; | |
display: flex; | |
padding: 1rem; | |
} | |
.video-container { | |
flex: 1; | |
margin-right: 1rem; | |
} | |
.video-player { | |
position: relative; | |
width: 100%; | |
background-color: black; | |
margin-bottom: 1rem; | |
} | |
.video-player img { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
} | |
.video-info { | |
margin-bottom: 1rem; | |
} | |
.video-title { | |
font-size: 1.5rem; | |
margin-bottom: 0.5rem; | |
} | |
.video-stats { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding-bottom: 1rem; | |
border-bottom: 1px solid var(--bg-secondary); | |
} | |
.view-count { | |
color: var(--text-secondary); | |
} | |
.video-actions { | |
display: flex; | |
gap: 1rem; | |
} | |
.action-button { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
background: none; | |
border: none; | |
color: var(--text-primary); | |
cursor: pointer; | |
} | |
.channel-info { | |
display: flex; | |
align-items: center; | |
padding-bottom: 1rem; | |
border-bottom: 1px solid var(--bg-secondary); | |
} | |
.channel-avatar { | |
width: 48px; | |
height: 48px; | |
border-radius: 50%; | |
background-color: var(--bg-secondary); | |
margin-right: 1rem; | |
} | |
.channel-details { | |
display: grid; | |
align-items: center; | |
} | |
.channel-name { | |
font-weight: bold; | |
} | |
.subscriber-count { | |
color: var(--text-secondary); | |
font-size: 0.9rem; | |
margin-bottom: 0.5rem; | |
} | |
.subscribe-button { | |
background-color: var(--accent); | |
color: var(--text-primary); | |
border: none; | |
padding: 0.5rem 1rem; | |
border-radius: 20px; | |
cursor: pointer; | |
font-weight: bold; | |
} | |
.video-description { | |
margin-top: 1rem; | |
color: var(--text-secondary); | |
white-space: pre-line; | |
} | |
.recommendations { | |
width: 400px; | |
} | |
.recommendation { | |
display: flex; | |
margin-bottom: 0.5rem; | |
cursor: pointer; | |
} | |
.recommendation-thumbnail { | |
width: 160px; | |
height: 90px; | |
background-color: var(--bg-secondary); | |
margin-right: 0.5rem; | |
flex-shrink: 0; | |
} | |
.recommendation-info h3 { | |
font-size: 0.9rem; | |
margin-bottom: 0.25rem; | |
} | |
.recommendation-info p { | |
font-size: 0.8rem; | |
color: var(--text-secondary); | |
} | |
.comments-section { | |
margin-top: 1rem; | |
} | |
.comments-header { | |
display: flex; | |
align-items: center; | |
margin-bottom: 1rem; | |
} | |
.comment-count { | |
margin-right: 1rem; | |
} | |
.sort-button { | |
display: flex; | |
align-items: center; | |
background: none; | |
border: none; | |
color: var(--text-primary); | |
cursor: pointer; | |
} | |
.comment { | |
display: flex; | |
margin-bottom: 1rem; | |
} | |
.comment-avatar { | |
width: 40px; | |
height: 40px; | |
border-radius: 50%; | |
background-color: var(--bg-secondary); | |
margin-right: 1rem; | |
} | |
.comment-content { | |
flex: 1; | |
} | |
.comment-header { | |
display: flex; | |
align-items: center; | |
margin-bottom: 0.25rem; | |
} | |
.comment-author { | |
font-weight: bold; | |
margin-right: 0.5rem; | |
} | |
.comment-timestamp { | |
color: var(--text-secondary); | |
font-size: 0.9rem; | |
} | |
@media (max-width: 1200px) { | |
.recommendations { | |
width: 300px; | |
} | |
} | |
@media (max-width: 1000px) { | |
main { | |
flex-direction: column; | |
} | |
.video-container { | |
margin-right: 0; | |
margin-bottom: 1rem; | |
} | |
.recommendations { | |
width: 100%; | |
} | |
.recommendation { | |
width: 100%; | |
} | |
} | |
@media (max-width: 768px) { | |
.logo span { | |
display: none; | |
} | |
.user-actions { | |
display: none; | |
} | |
.video-stats { | |
flex-direction: column; | |
align-items: flex-start; | |
} | |
.video-actions { | |
margin-top: 0.5rem; | |
} | |
.recommendation-thumbnail { | |
width: 120px; | |
height: 68px; | |
} | |
} | |
video::-webkit-media-text-track-display { | |
font-size: 2vw; | |
} | |
video {max-height: 80vh; | |
pointer-events:visible;} | |
input, button { | |
font-family: 'Montserrat', sans-serif; | |
} | |
.shareVideo { | |
width: 500px; | |
padding: 20px; | |
background-color: #0d2233; | |
color: #fff; | |
border-radius: 12px; | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | |
} | |
.shareVideoHeader { | |
font-size: 1.5rem; | |
font-weight: bold; | |
margin-bottom: 15px; | |
} | |
.shareVideoContent { | |
display: flex; | |
flex-direction: column; | |
gap: 20px; | |
} | |
.shareVideoActions { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
} | |
.urlSection { | |
display: flex; | |
align-items: center; | |
background-color: #0a1e2b; | |
padding: 10px; | |
border-radius: 8px; | |
} | |
.urlSection input { | |
width: 100%; | |
padding: 8px; | |
font-size: 1rem; | |
color: #fff; | |
background-color: transparent; | |
border: none; | |
outline: none; | |
} | |
.urlSection input::placeholder { | |
color: #88a0b3; | |
} | |
.urlSection button { | |
padding: 8px 16px; | |
margin-left: 10px; | |
color: #fff; | |
border: none; | |
border-radius: 8px; | |
cursor: pointer; | |
transition: background-color 0.3s ease; | |
} | |
.urlSection button:hover { | |
background-color: #0073e6; | |
} | |
.shareOptions { | |
display: flex; | |
gap: 15px; | |
} | |
.shareOption { | |
background-color: #1b2f40; | |
padding: 10px 15px; | |
border-radius: 8px; | |
cursor: pointer; | |
transition: background-color 0.3s ease; | |
} | |
.shareOption:hover { | |
background-color: #233a4e; | |
} | |
.shareOption img { | |
width: 24px; | |
height: 24px; | |
margin-right: 10px; | |
} | |
.shareOption span { | |
font-size: 1rem; | |
color: #fff; | |
} | |
.copyButton { | |
background-color: rgb(0, 72, 85); | |
} | |
.closeButton { | |
color: rgb(255, 255, 255); | |
right: 10px; | |
top: 3px; | |
position: absolute; | |
transition: all 0.3s ease; | |
user-select: none; | |
} | |
.closeButton:hover { | |
color: rgb(255, 0, 0); | |
} | |
</style> | |
</head> | |
<body> | |
<header> | |
<div class="logo"> | |
<i class="fab fa-youtube"></i> | |
<a href="index.html" style="color: white; text-decoration: none; ::after { text-decoration: underline; }">HuggingTube</a> | |
</div> | |
<div class="search-bar"> | |
<input type="text" placeholder="Search"> | |
</div> | |
<div class="user-actions"> | |
<i class="fas fa-video"></i> | |
<i class="fas fa-bell"></i> | |
<i class="fas fa-user-circle"></i> | |
</div> | |
</header> | |
<main> | |
<div class="video-container"> | |
<div class="video-player"> | |
<style> #video-element { width: 100%; height: 100%;} </style> | |
<video id="video-element" controls> | |
<track src="" kind="subtitles" srclang="en" label="English" default> | |
</video> | |
</div> | |
<script>document.addEventListener("DOMContentLoaded", function () { | |
// Function to decrypt the link | |
const decryptLink = (encryptedLink, key) => { | |
try { | |
// Decode base64 | |
const decoded = atob(encryptedLink); | |
// XOR decrypt | |
const decrypted = decoded.split('').map((char, i) => { | |
return String.fromCharCode(char.charCodeAt(0) ^ key.charCodeAt(i % key.length)); | |
}).join(''); | |
return decrypted; | |
} catch (error) { | |
console.error("Decryption error:", error); | |
return null; | |
} | |
}; | |
// Get the URL fragment after the first '#' | |
const urlHash = window.location.hash; | |
if (!urlHash) { | |
console.error("No hash found in the URL"); | |
window.location.href = "index.html"; | |
return; | |
} | |
const encryptedLink = urlHash.substring(1); // Remove the '#' and get the rest | |
if (!encryptedLink) { | |
console.error("No encrypted link found in the URL"); | |
return; | |
} | |
// Decrypt the encrypted part of the URL | |
const jsonUrl = decryptLink(encryptedLink, 'JesusIsGod'); | |
if (!jsonUrl) { | |
console.error("Failed to decrypt URL"); | |
return; | |
} | |
// Store the decrypted URL | |
localStorage.setItem("video_to_watch", jsonUrl); | |
// Fetch the JSON data from the decrypted URL | |
fetch(jsonUrl) | |
.then(response => { | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
return response.json(); | |
}) | |
.then(data => { | |
// Update the page with the scraped JSON data | |
document.getElementById("video-title").innerText = data.title; | |
const date = new Date(data.uploadTimestamp); | |
document.getElementById("view-count").innerText = `Uploaded on ${date.toLocaleString('en-US', { month: 'long' })} ${date.getDate()}, ${date.getFullYear()} at ${date.toLocaleTimeString()}`; | |
document.getElementById("channel-name").innerText = data.uploader; | |
document.getElementById("video-description").innerText = data.description; | |
// Set the document title | |
document.title = data.title + " - HuggingTube"; | |
// Define base URLs for global resources | |
const videoBaseUrl = 'https://huggingface.co/spaces/vericudebuget/ok4231/resolve/main/'; | |
const subtitleBaseUrl = 'https://huggingface.co/spaces/vericudebuget/ok4231/raw/main/'; | |
// Update the video source | |
const videoElement = document.getElementById("video-element"); | |
if (videoElement) { | |
const videoUrl = `${videoBaseUrl}${data.fileLocation}`; | |
const subtitleUrl = data.subtitleLocation ? `${subtitleBaseUrl}${data.subtitleLocation}` : ''; | |
videoElement.src = videoUrl; | |
// Handle subtitles | |
if (subtitleUrl) { | |
fetch(subtitleUrl) | |
.then(response => { | |
if (!response.ok) { | |
throw new Error(`Failed to fetch subtitles: ${response.statusText}`); | |
} | |
return response.text(); | |
}) | |
.then(subtitleText => { | |
const blob = new Blob([subtitleText], { type: 'text/vtt' }); | |
const blobUrl = URL.createObjectURL(blob); | |
const trackElement = videoElement.querySelector('track'); | |
trackElement.src = blobUrl; | |
trackElement.default = true; | |
trackElement.srclang = "en"; | |
trackElement.label = "English"; | |
console.log("Subtitles successfully loaded."); | |
}) | |
.catch(error => { | |
console.error("Error fetching or loading subtitles:", error); | |
}); | |
} | |
// Add video event listeners | |
videoElement.addEventListener('error', function(e) { | |
console.error("Video error:", e); | |
}); | |
videoElement.addEventListener('loadedmetadata', function() { | |
console.log("Video metadata loaded successfully"); | |
}); | |
} else { | |
console.error("Video element not found in the DOM"); | |
} | |
}) | |
.catch(error => { | |
console.error("Error fetching or parsing the JSON data:", error); | |
}); | |
// Download functionality | |
document.getElementById("download-button").addEventListener("click", function() { | |
const videoElement = document.getElementById("video-element"); | |
const videoUrl = videoElement.src; | |
const fileName = videoUrl.split('/').pop().split('?')[0]; | |
console.log("Downloading video:", fileName); | |
const link = document.createElement('a'); | |
link.href = videoUrl; | |
link.setAttribute('download', fileName); | |
link.style.display = 'none'; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
}); | |
// Video playback control | |
let videoElement = document.getElementById("video-element"); | |
let pausePlayVideoTimeout = null; | |
let spacePressed = false; | |
function pausePlayVideo(event) { | |
if (event.code === 'Space' && !spacePressed) { | |
spacePressed = true; | |
if (pausePlayVideoTimeout === null) { | |
pausePlayVideoTimeout = setTimeout(function() { | |
pausePlayVideoTimeout = null; | |
}, 100); | |
videoElement.paused ? videoElement.play() : videoElement.pause(); | |
} | |
} | |
} | |
function resetSpacePressed(event) { | |
if (event.code === 'Space') { | |
spacePressed = false; | |
} | |
} | |
// Event listeners | |
document.addEventListener('keydown', pausePlayVideo); | |
document.addEventListener('keyup', resetSpacePressed); | |
// Prevent default behaviors | |
window.addEventListener("keydown", function(e) { | |
if ((e.key === " " || e.key === "ArrowUp" || e.key === "ArrowDown" || "Tab") && e.target === document.body) { | |
e.preventDefault(); | |
} | |
}); | |
window.addEventListener("focus", function(e) { | |
if (e.target === document.body) { | |
document.activeElement.blur(); | |
} | |
}, true); | |
}); | |
</script> | |
<div class="video-info"> | |
<h1 class="video-title" id="video-title">Loading...</h1> | |
<div class="video-stats"> | |
<span class="view-count" id="view-count">Loading...</span> | |
<!-- Share Video Container --> | |
<div class="shareVideo" id="shareVideoContainer" style="display: none;"> | |
<div class="shareVideoHeader"> | |
<div>Share this video! <div class="closeButton" onclick="closeShareVideo()">×</div> </div> | |
</div> | |
<div class="shareVideoContent"> | |
<!-- URL Copy Section --> | |
<div class="urlSection"> | |
<input id="urlInput" type="text" readonly /> | |
<button onclick="copyURL()" class="copyButton">Copy</button> | |
</div> | |
</div> | |
<p id="message" class="message"></p> | |
</div> | |
<!-- HTML Elements --> | |
<div class="video-actions"> | |
<button class="action-button" id="download-button"> | |
<i class="fas fa-download"></i> Download | |
</button> | |
</div> | |
</div> | |
</div> | |
<div class="video-page-container"> | |
<div class="main-content"> | |
<div class="channel-info"> | |
<div class="channel-avatar" style="align-items: center;"></div> | |
<div class="channel-details"> | |
<span class="channel-name" id="channel-name">Loading...</span> | |
</div> | |
</div> | |
<div class="video-description" id="video-description"> | |
Loading description... | |
</div> | |
</div> | |
</div> | |
</div> | |
<aside class="recommendations"> | |
<div class="recommendation-skeleton"> | |
<div class="thumbnail-skeleton"></div> | |
<div class="info-skeleton"> | |
<div class="title-skeleton"></div> | |
<div class="meta-skeleton"></div> | |
</div> | |
</div> | |
</aside> | |
</div> | |
<script> | |
async function fetchVideoMetadata() { | |
try { | |
const currentVideoUrl = localStorage.getItem("video_to_watch"); | |
if (!currentVideoUrl) { | |
console.error("No current video URL found"); | |
return; | |
} | |
const currentVideoResponse = await fetch(currentVideoUrl); | |
const currentVideo = await currentVideoResponse.json(); | |
const response = await fetch('https://huggingface.co/spaces/vericudebuget/ok4231/raw/main/metadata/video-index.json'); | |
const videoIndexData = await response.json(); | |
const baseUrl = 'https://huggingface.co/spaces/vericudebuget/ok4231/raw/main/metadata/'; | |
const thumbnailBaseUrl = 'https://huggingface.co/spaces/vericudebuget/ok4231/raw/main/thumbnails/'; | |
const calculateRelevanceScore = (videoData, currentVideo) => { | |
let score = 0; | |
if (videoData.uploader === currentVideo.uploader) score += 5; | |
const currentWords = currentVideo.title.toLowerCase().split(' '); | |
const videoWords = videoData.title.toLowerCase().split(' '); | |
const commonWords = currentWords.filter(word => videoWords.includes(word)); | |
score += commonWords.length; | |
const currentDesc = currentVideo.description.toLowerCase().split(' '); | |
const videoDesc = videoData.description.toLowerCase().split(' '); | |
const commonDesc = currentDesc.filter(word => videoDesc.includes(word)); | |
score += commonDesc.length * 0.5; | |
const timeDiff = Math.abs(new Date(videoData.uploadTimestamp) - new Date(currentVideo.uploadTimestamp)); | |
const daysDiff = timeDiff / (1000 * 60 * 60 * 24); | |
score += Math.max(0, 2 - (daysDiff / 30)); | |
return score; | |
}; | |
const encryptLink = (link, key) => { | |
const encrypted = link.split('').map((char, i) => { | |
return String.fromCharCode(char.charCodeAt(0) ^ key.charCodeAt(i % key.length)); | |
}).join(''); | |
return btoa(encrypted); | |
}; | |
const formatDuration = (duration) => { | |
if (duration === null || duration === undefined) return 'N/A'; | |
const minutes = Math.floor(duration / 60); | |
const seconds = duration % 60; | |
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; | |
}; | |
const recommendationsContainer = document.querySelector('.recommendations'); | |
recommendationsContainer.innerHTML = ''; | |
// Store recommendations for sorting | |
let recommendations = []; | |
// Process videos and store them | |
for (let videoIndex of videoIndexData) { | |
let fileName = videoIndex.url.substring(videoIndex.url.lastIndexOf('/') + 1); | |
let finalUrl = baseUrl + encodeURIComponent(fileName); | |
try { | |
const videoResponse = await fetch(finalUrl); | |
const videoData = await videoResponse.json(); | |
if (finalUrl === currentVideoUrl) continue; | |
const relevanceScore = calculateRelevanceScore(videoData, currentVideo); | |
const thumbnailUrl = thumbnailBaseUrl + encodeURIComponent( | |
videoData.thumbnailLocation.substring(videoData.thumbnailLocation.lastIndexOf('/') + 1) | |
); | |
recommendations.push({ | |
...videoData, | |
relevanceScore, | |
finalUrl, | |
thumbnailUrl | |
}); | |
// Sort recommendations by relevance score | |
recommendations.sort((a, b) => b.relevanceScore - a.relevanceScore); | |
// Refresh the entire recommendations list | |
recommendationsContainer.innerHTML = ''; | |
recommendations.forEach(video => { | |
const recommendationElement = document.createElement('div'); | |
recommendationElement.classList.add('recommendation'); | |
recommendationElement.innerHTML = ` | |
<div class="thumbnail-container"> | |
<img | |
src="${video.thumbnailUrl}" | |
alt="${video.title}" | |
class="thumbnail-image" | |
loading="lazy" | |
> | |
<span class="duration-overlay">${formatDuration(video.duration)}</span> | |
</div> | |
<div class="recommendation-info"> | |
<h3 class="video-title-recommendation">${video.title}</h3> | |
<p class="uploader">${video.uploader}</p> | |
<p class="upload-date">${new Intl.DateTimeFormat('en-US', { | |
month: 'long', | |
day: 'numeric', | |
year: 'numeric' | |
}).format(new Date(video.uploadTimestamp))}</p> | |
</div> | |
`; | |
recommendationElement.addEventListener('click', () => { | |
const encryptedLink = encryptLink(video.finalUrl, 'JesusIsGod'); | |
window.location.href = `video-player.html#${encryptedLink}`; | |
setTimeout(() => window.location.reload(), 30); | |
}); | |
recommendationsContainer.appendChild(recommendationElement); | |
}); | |
} catch (error) { | |
console.error(`Error fetching video metadata for ${fileName}:`, error); | |
} | |
} | |
} catch (error) { | |
console.error('Error fetching video metadata:', error); | |
} | |
} | |
// Call the function on page load | |
window.onload = fetchVideoMetadata; | |
</script> | |
<style> | |
/* Layout containers */ | |
.video-page-container { | |
display: flex; | |
gap: 24px; | |
max-width: 1800px; | |
margin: 0 auto; | |
padding: 16px; | |
padding-bottom: 50px; | |
overflow: hidden; | |
} | |
.main-content { | |
flex: 1; | |
min-width: 0; | |
display: flex; | |
flex-direction: column; | |
} | |
.recommendations { | |
width: 400px; | |
flex-shrink: 0; | |
height: 100vh; | |
overflow-y: scroll; | |
padding: 0 8px; | |
scrollbar-width: none; /* Firefox */ | |
-ms-overflow-style: none; /* IE and Edge */ | |
} | |
/* Hide scrollbar for Chrome, Safari and Opera */ | |
.recommendations::-webkit-scrollbar { | |
display: none; | |
} | |
/* Recommendation styles */ | |
.recommendation { | |
display: flex; | |
gap: 12px; | |
cursor: pointer; | |
transition: background-color 0.2s; | |
padding: 8px; | |
border-radius: 8px; | |
margin-bottom: 8px; | |
} | |
.recommendation:hover { | |
background-color: rgba(0, 0, 0, 0.05); | |
} | |
.thumbnail-container { | |
position: relative; | |
width: 160px; | |
height: 90px; | |
overflow: hidden; | |
border-radius: 8px; | |
flex-shrink: 0; | |
} | |
.thumbnail-image { | |
width: 100%; | |
height: 100%; | |
object-fit: cover; | |
} | |
.duration-overlay { | |
position: absolute; | |
top: 4px; | |
right: 4px; | |
background-color: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 2px 4px; | |
border-radius: 4px; | |
font-size: 12px; | |
font-weight: 500; | |
} | |
.recommendation-info { | |
flex: 1; | |
min-width: 0; | |
} | |
.video-title-recommendation { | |
font-size: 14px; | |
font-weight: 500; | |
margin: 0 0 4px 0; | |
display: -webkit-box; | |
-webkit-line-clamp: 2; | |
-webkit-box-orient: vertical; | |
overflow: hidden; | |
} | |
.uploader { | |
font-size: 12px; | |
color: #606060; | |
margin: 0 0 2px 0; | |
} | |
.upload-date { | |
font-size: 12px; | |
color: #606060; | |
margin: 0; | |
} | |
/* Loading skeleton styles */ | |
.recommendation-skeleton { | |
display: flex; | |
gap: 12px; | |
padding: 8px; | |
} | |
.thumbnail-skeleton { | |
width: 160px; | |
height: 90px; | |
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); | |
background-size: 200% 100%; | |
animation: loading 1.5s infinite; | |
border-radius: 8px; | |
} | |
.info-skeleton { | |
flex: 1; | |
} | |
.title-skeleton { | |
height: 16px; | |
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); | |
background-size: 200% 100%; | |
animation: loading 1.5s infinite; | |
margin-bottom: 8px; | |
border-radius: 4px; | |
} | |
.meta-skeleton { | |
height: 12px; | |
width: 60%; | |
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); | |
background-size: 200% 100%; | |
animation: loading 1.5s infinite; | |
border-radius: 4px; | |
} | |
@keyframes loading { | |
0% { background-position: 200% 0; } | |
100% { background-position: -200% 0; } | |
} | |
/* Mobile styles (YouTube-like) */ | |
@media (max-width: 1020px) { | |
.video-page-container { | |
flex-direction: column; | |
height: auto; | |
overflow: visible; | |
} | |
.recommendations { | |
width: 100%; | |
height: auto; | |
padding: 0; | |
overflow-y: visible; | |
} | |
.recommendation { | |
padding: 12px 0; | |
margin: 0; | |
border-radius: 0; | |
border-bottom: 1px solid #e5e5e5; | |
} | |
.thumbnail-container { | |
width: 140px; | |
height: 80px; | |
} | |
.video-title-recommendation { | |
font-size: 16px; | |
-webkit-line-clamp: 2; | |
} | |
.uploader, .upload-date { | |
font-size: 14px; | |
} | |
} | |
/* Mobile styles (smaller screens) */ | |
@media (max-width: 640px) { | |
.video-page-container { | |
padding: 0; | |
} | |
.recommendation { | |
padding: 8px; | |
} | |
.thumbnail-container { | |
width: 120px; | |
height: 68px; | |
} | |
.video-title-recommendation { | |
font-size: 14px; | |
} | |
.uploader, .upload-date { | |
font-size: 12px; | |
} | |
} | |
</style> | |
<style> | |
.subtitle { | |
display: flex; | |
background-color: rgb(0, 72, 85); | |
border-radius: 5px; | |
width: fit-content; | |
padding: 1px 5px; | |
align-items: center; | |
transform-origin: left bottom; | |
transform: scale(80%); | |
} | |
.subtitle_icon { | |
width: auto; | |
height: 1em; | |
margin-right: 5px; | |
} | |
.subtitle_content { | |
filter: invert(100%); | |
display: flex; | |
align-items: center; | |
} | |
.subtitle_text { | |
align-self: center; | |
text-align: center; | |
color: #000000; | |
} | |
</style> | |
<script>window.onpopstate = function(event) { | |
location.reload(); | |
}; | |
</script> | |
</aside> | |
</main> | |
</body> | |
</html> | |