Update comments from Chinese to English for better international collaboration Standardize comment style across HTML, CSS and JavaScript files Remove unnecessary comments about global variables
791 lines
23 KiB
JavaScript
791 lines
23 KiB
JavaScript
let videos = [
|
||
{
|
||
"id": 1,
|
||
"title": "透明なパレット (self cover)",
|
||
"channel": "Aqu3ra",
|
||
"views": "576,197",
|
||
"uploadDate": "1 months ago",
|
||
"duration": "3:03",
|
||
"thumbnail": "img/cover/music-01.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 2,
|
||
"title": "【歌ってみた】Duvet / covered by ヰ世界情緒",
|
||
"channel": "ヰ世界情緒 -Isekaijoucho-",
|
||
"views": "721,627",
|
||
"uploadDate": "5 days ago",
|
||
"duration": "3:32",
|
||
"thumbnail": "img/cover/music-02.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 3,
|
||
"title": "ヨルシカ 『 晴る 』 -文化祭2025-",
|
||
"channel": "countben",
|
||
"views": "142,561",
|
||
"uploadDate": "2 months ago",
|
||
"duration": "4:46",
|
||
"thumbnail": "img/cover/music-03.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 4,
|
||
"title": "【Silent Witch沈默魔女的秘密】OST 主題曲「沈黙の魔女」bgm|Kitkit Lu COVER",
|
||
"channel": "KitKit Lu",
|
||
"views": "1,856,264",
|
||
"uploadDate": "5 months ago",
|
||
"duration": "2:39",
|
||
"thumbnail": "img/cover/music-04.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 5,
|
||
"title": "I Can't Wait feat. GUMI",
|
||
"channel": "d0tc0mmie",
|
||
"views": "315,293",
|
||
"uploadDate": "1 month ago",
|
||
"duration": "1:35",
|
||
"thumbnail": "img/cover/music-05.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 6,
|
||
"title": "MiSiDE: ZERO Update – Full Gameplay + Ending (Showcase)",
|
||
"channel": "RUSH PLAY",
|
||
"views": "900,156",
|
||
"uploadDate": "4 weeks ago",
|
||
"duration": "24:39",
|
||
"thumbnail": "img/cover/game-06.jpg",
|
||
"category": "game",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 7,
|
||
"title": "Minecraftの効果音でサイエンス",
|
||
"channel": "gmailアカウント",
|
||
"views": "651,192",
|
||
"uploadDate": "11 months ago",
|
||
"duration": "1:05",
|
||
"thumbnail": "img/cover/game-07.jpg",
|
||
"category": "game",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 8,
|
||
"title": "osu! NEEDS to Take Notes... (00PARTS World's 1st Clear)",
|
||
"channel": "bmc",
|
||
"views": "892,163",
|
||
"uploadDate": "1 month ago",
|
||
"duration": "8:21",
|
||
"thumbnail": "img/cover/game-08.jpg",
|
||
"category": "game",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 9,
|
||
"title": "ヨルシカ - 春泥棒(OFFICIAL VIDEO)",
|
||
"channel": "ヨルシカ / n-buna Official",
|
||
"views": "123,456,789",
|
||
"uploadDate": "5 years ago",
|
||
"duration": "4:17",
|
||
"thumbnail": "img/cover/music-09.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 10,
|
||
"title": "YOASOBI「アイドル」Official Music Video",
|
||
"channel": "YOASOBI Official",
|
||
"views": "456,789,123",
|
||
"uploadDate": "2 years ago",
|
||
"duration": "3:33",
|
||
"thumbnail": "img/cover/music-10.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 11,
|
||
"title": "花譜 - 不可解",
|
||
"channel": "花譜 -KAF-",
|
||
"views": "12,345,678",
|
||
"uploadDate": "3 years ago",
|
||
"duration": "4:05",
|
||
"thumbnail": "img/cover/music-11.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 12,
|
||
"title": "ZUTOMAYO - STUDY ME",
|
||
"channel": "ZUTOMAYO",
|
||
"views": "23,456,789",
|
||
"uploadDate": "1 year ago",
|
||
"duration": "3:45",
|
||
"thumbnail": "img/cover/music-12.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 13,
|
||
"title": "Eve - 廻廻奇譚",
|
||
"channel": "Eve",
|
||
"views": "89,123,456",
|
||
"uploadDate": "4 years ago",
|
||
"duration": "3:55",
|
||
"thumbnail": "img/cover/music-13.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 14,
|
||
"title": "ClariS - コネクト",
|
||
"channel": "ClariS Official",
|
||
"views": "45,678,901",
|
||
"uploadDate": "10 years ago",
|
||
"duration": "4:30",
|
||
"thumbnail": "img/cover/music-14.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 15,
|
||
"title": "米津玄師 Kenshi Yonezu - Lemon",
|
||
"channel": "Kenshi Yonezu 米津玄師",
|
||
"views": "96,539,145",
|
||
"uploadDate": "7 years ago",
|
||
"duration": "4:35",
|
||
"thumbnail": "img/cover/music-15.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 16,
|
||
"title": "【初音ミク】 夜明けと蛍 【オリジナル】",
|
||
"channel": "ヨルシカ / n-buna Official",
|
||
"views": "27,749,534",
|
||
"uploadDate": "8 years ago",
|
||
"duration": "5:10",
|
||
"thumbnail": "img/cover/music-16.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 17,
|
||
"title": "Empty old City - Buffer",
|
||
"channel": "Empty old city",
|
||
"views": "838,946",
|
||
"uploadDate": "1 year ago",
|
||
"duration": "3:18",
|
||
"thumbnail": "img/cover/music-17.jpg",
|
||
"category": "music",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 18,
|
||
"title": "I built Git for Minecraft for a hackathon and won",
|
||
"channel": "MathRayyan",
|
||
"views": "288,346",
|
||
"uploadDate": "12 days ago",
|
||
"duration": "5:13",
|
||
"thumbnail": "img/cover/game-18.jpg",
|
||
"category": "game",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 19,
|
||
"title": "怎麼讓遊戲越來越真?PBR貼圖是怎麼做到的?",
|
||
"channel": "孫拓海Taku",
|
||
"views": "25,364",
|
||
"uploadDate": "3 weeks ago",
|
||
"duration": "13:14",
|
||
"thumbnail": "img/cover/game-19.jpg",
|
||
"category": "game",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 20,
|
||
"title": "「空あくあ!」當さくな說出這句台詞時 全場都淚崩了....【結城さくな】【Vtuber中文/翻譯/精華】",
|
||
"channel": "Dar烤肉!",
|
||
"views": "116,498",
|
||
"uploadDate": "7 days ago",
|
||
"duration": "8:35",
|
||
"thumbnail": "img/cover/game-20.jpg",
|
||
"category": "game",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 21,
|
||
"title": "「明日方舟:终末地性能分析:二游画质巅峰?榨干手机!",
|
||
"channel": "极客湾Geekerwan",
|
||
"views": "51,649",
|
||
"uploadDate": "10 days ago",
|
||
"duration": "22:13",
|
||
"thumbnail": "img/cover/tech-21.jpg",
|
||
"category": "tech",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 22,
|
||
"title": "What GPU is the BEST for Linux Gaming?",
|
||
"channel": "Linus Tech Tips",
|
||
"views": "96,751",
|
||
"uploadDate": "7 days ago",
|
||
"duration": "10:55",
|
||
"thumbnail": "img/cover/tech-22.jpg",
|
||
"category": "tech",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 23,
|
||
"title": "How Hard Is It to Build Your First PC?",
|
||
"channel": "PLACITECH",
|
||
"views": "4,351",
|
||
"uploadDate": "2 months ago",
|
||
"duration": "12:47",
|
||
"thumbnail": "img/cover/tech-23.jpg",
|
||
"category": "tech",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 24,
|
||
"title": "回顾我们最中意的 2025 年产品发布 - 谷歌开发者新闻(年终特辑)",
|
||
"channel": "Google for Developers",
|
||
"views": "3,475",
|
||
"uploadDate": "1 month ago",
|
||
"duration": "6:39",
|
||
"thumbnail": "img/cover/tech-24.jpg",
|
||
"category": "tech",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 25,
|
||
"title": "Commodore 128 Alternate Universe",
|
||
"channel": "The 8-Bit Guy",
|
||
"views": "17,498",
|
||
"uploadDate": "4 months ago",
|
||
"duration": "8:35",
|
||
"thumbnail": "img/cover/tech-25.jpg",
|
||
"category": "tech",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 26,
|
||
"title": "\"The Roller Coaster\" - Behind the Scenes - Time Lapse & Commentary",
|
||
"channel": "AlanBeckerTutorials",
|
||
"views": "193,762",
|
||
"uploadDate": "7 years ago",
|
||
"duration": "14:52",
|
||
"thumbnail": "img/cover/art-26.jpg",
|
||
"category": "art",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 27,
|
||
"title": "Animation vs. Game Design",
|
||
"channel": "Alan Backer",
|
||
"views": "694,264",
|
||
"uploadDate": "2 months ago",
|
||
"duration": "3:12",
|
||
"thumbnail": "img/cover/art-27.jpg",
|
||
"category": "art",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 28,
|
||
"title": "The Far Lands - Animation vs. Minecraft Shorts Ep 38",
|
||
"channel": "Alan Backer",
|
||
"views": "975,192",
|
||
"uploadDate": "1 month ago",
|
||
"duration": "9:10",
|
||
"thumbnail": "img/cover/art-28.jpg",
|
||
"category": "art",
|
||
"type": "video"
|
||
},
|
||
{
|
||
"id": 29,
|
||
"title": "【Official MV】ray 超かぐや姫!Version/かぐや (cv.夏吉ゆうこ) & 月見ヤチヨ (cv.早見沙織) from #超かぐや姫 !【新作アニメーション】",
|
||
"channel": "",
|
||
"views": "78,192",
|
||
"uploadDate": "5 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-29.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 30,
|
||
"title": "ドッペルゲンガー feat.初音ミク#vocaloid #初音ミク",
|
||
"channel": "",
|
||
"views": "2,359",
|
||
"uploadDate": "3 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-30.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 31,
|
||
"title": "Melt CPK! Remix — English Subtitled Version",
|
||
"channel": "",
|
||
"views": "2,264",
|
||
"uploadDate": "3 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-31.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 32,
|
||
"title": "【Suite History】花譜×#KTちゃん「ギミギミ逃避行feat. #KTちゃん(Prod. peko)」 #花譜 #KTちゃん #組曲 #shorts",
|
||
"channel": "",
|
||
"views": "5,430",
|
||
"uploadDate": "2 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-32.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 33,
|
||
"title": "ヨルシカ - プレイシック #ヨルシカ #プレイシック #二人称 #yorushika #playsick #secondperson",
|
||
"channel": "",
|
||
"views": "39,254",
|
||
"uploadDate": "10 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-33.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 34,
|
||
"title": "#ヨルシカ #live #前世 #yorushika",
|
||
"channel": "",
|
||
"views": "36,181",
|
||
"uploadDate": "1 month ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-34.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 35,
|
||
"title": "【歌ってみた】蜜月アン・ドゥ・トロワ covered by ヰ世界情緒 #shorts",
|
||
"channel": "",
|
||
"views": "2,163",
|
||
"uploadDate": "6 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-35.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 36,
|
||
"title": "Thank you 2025🩵Have a wonderful new year! #YOASOBI_2025",
|
||
"channel": "",
|
||
"views": "36,165",
|
||
"uploadDate": "20 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-36.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 37,
|
||
"title": "初めてラブソングを書きました。新曲「ハートマーク feat.川谷絵音」#礼衣 #ハートマーク",
|
||
"channel": "",
|
||
"views": "2,609",
|
||
"uploadDate": "3 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/music-37.jpg",
|
||
"category": "music",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 38,
|
||
"title": "Would you play this at the school talent show for $500? #osu #osugame",
|
||
"channel": "",
|
||
"views": "133,658",
|
||
"uploadDate": "2 months ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/game-38.jpg",
|
||
"category": "game",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 39,
|
||
"title": "May your holidays be filled with great vibes, full combos, and lots of epic tracks! 🎵",
|
||
"channel": "",
|
||
"views": "1,658",
|
||
"uploadDate": "1 months ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/game-39.jpg",
|
||
"category": "game",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 40,
|
||
"title": "PANDORA PARADOXXX RE:MASTER alternative spin method #maimai #maimaiでらっくす #maimai_dx",
|
||
"channel": "",
|
||
"views": "25,365",
|
||
"uploadDate": "12 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/game-40.jpg",
|
||
"category": "game",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 41,
|
||
"title": "Arch Linux VS Debian in 1 Min #linux #arch #debian",
|
||
"channel": "",
|
||
"views": "3,565",
|
||
"uploadDate": "11 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/tech-41.jpg",
|
||
"category": "tech",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 42,
|
||
"title": "#linux #homelab #opensource #tech #debian",
|
||
"channel": "",
|
||
"views": "7,925",
|
||
"uploadDate": "10 days ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/tech-42.jpg",
|
||
"category": "tech",
|
||
"type": "short"
|
||
},
|
||
{
|
||
"id": 43,
|
||
"title": "The one key keyboard #tech",
|
||
"channel": "",
|
||
"views": "4,571",
|
||
"uploadDate": "1 month ago",
|
||
"duration": "",
|
||
"thumbnail": "img/cover/tech-43.jpg",
|
||
"category": "tech",
|
||
"type": "short"
|
||
},
|
||
];
|
||
let currentCategory = 'All';
|
||
|
||
// DOM elements
|
||
const searchInput = document.querySelector('.search-input');
|
||
const searchBtn = document.querySelector('.search-btn');
|
||
const searchResults = document.querySelector('.search-results');
|
||
const searchResultsContent = document.querySelector('.search-results-content');
|
||
const videoGrid = document.querySelector('.video-grid');
|
||
const shortsGrid = document.querySelector('.shorts-grid');
|
||
const categoryBtns = document.querySelectorAll('.category-btn');
|
||
const subscriptionsList = document.querySelector('.subscriptions-list');
|
||
|
||
async function init() {
|
||
await loadVideos();
|
||
renderVideos();
|
||
renderSubscriptions();
|
||
bindEvents();
|
||
}
|
||
|
||
async function loadVideos() {
|
||
return Promise.resolve(); // Already defined in global scope
|
||
}
|
||
|
||
function renderVideos() {
|
||
const filteredVideos = filterVideosByCategory(currentCategory);
|
||
const videosOnly = filteredVideos.filter(video => video.type === 'video');
|
||
const shortsOnly = filteredVideos.filter(video => video.type === 'short');
|
||
|
||
videoGrid.innerHTML = '';
|
||
shortsGrid.innerHTML = '';
|
||
|
||
// Render long videos
|
||
videosOnly.forEach(video => {
|
||
const videoCard = createVideoCard(video);
|
||
videoGrid.appendChild(videoCard);
|
||
});
|
||
|
||
// Render shorts videos
|
||
shortsOnly.forEach(video => {
|
||
const shortsCard = createShortsCard(video);
|
||
shortsGrid.appendChild(shortsCard);
|
||
});
|
||
}
|
||
|
||
function filterVideosByCategory(category) {
|
||
if (category === 'All') {
|
||
return videos;
|
||
}
|
||
|
||
const categoryMap = {
|
||
'Music': ['music'],
|
||
'Game': ['game'],
|
||
'Technology': ['tech'],
|
||
'Art': ['art'],
|
||
'Recently': videos.filter(v => v.uploadDate.includes('days ago')),
|
||
'Watched': []
|
||
};
|
||
|
||
if (categoryMap[category]) {
|
||
if (Array.isArray(categoryMap[category]) && categoryMap[category].length > 0 && typeof categoryMap[category][0] === 'string') {
|
||
return videos.filter(v => categoryMap[category].includes(v.category));
|
||
} else if (Array.isArray(categoryMap[category])) {
|
||
return categoryMap[category];
|
||
}
|
||
}
|
||
|
||
return videos;
|
||
}
|
||
|
||
function createVideoCard(video) {
|
||
const card = document.createElement('div');
|
||
card.className = 'video-card';
|
||
|
||
card.innerHTML = `
|
||
<div class="video-thumbnail">
|
||
<img src="${video.thumbnail}" alt="${video.title}">
|
||
<span class="video-duration">${video.duration}</span>
|
||
</div>
|
||
<div class="video-info">
|
||
<div class="video-channel-avatar">
|
||
<i class="fas fa-user avatar-img"></i>
|
||
</div>
|
||
<div class="video-details">
|
||
<h3 class="video-title">${video.title}</h3>
|
||
<div class="video-meta">
|
||
<div class="video-channel">${video.channel}</div>
|
||
<div class="video-stats">
|
||
<span>${video.views} views</span>
|
||
<span>${video.uploadDate}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="video-menu">
|
||
<button class="menu-toggle">
|
||
<i class="fas fa-ellipsis-v"></i>
|
||
</button>
|
||
<div class="menu-dropdown">
|
||
<div class="menu-item">
|
||
<i class="fas fa-plus"></i>
|
||
<span>Add to Watch Later</span>
|
||
</div>
|
||
<div class="menu-item">
|
||
<i class="fas fa-clock"></i>
|
||
<span>Save to "Watch Later"</span>
|
||
</div>
|
||
<div class="menu-item">
|
||
<i class="fas fa-list"></i>
|
||
<span>Save to Playlist</span>
|
||
</div>
|
||
<div class="menu-item">
|
||
<i class="fas fa-download"></i>
|
||
<span>Download</span>
|
||
</div>
|
||
<div class="menu-item">
|
||
<i class="fas fa-share"></i>
|
||
<span>Share</span>
|
||
</div>
|
||
<div class="menu-item">
|
||
<i class="fas fa-thumbs-down"></i>
|
||
<span>Not Interested</span>
|
||
</div>
|
||
<div class="menu-item">
|
||
<i class="fas fa-ban"></i>
|
||
<span>Report Channel</span>
|
||
</div>
|
||
<div class="menu-item">
|
||
<i class="fas fa-flag"></i>
|
||
<span>Report Video</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
const menuToggle = card.querySelector('.menu-toggle');
|
||
const menuDropdown = card.querySelector('.menu-dropdown');
|
||
|
||
menuToggle.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
menuDropdown.classList.toggle('show');
|
||
});
|
||
|
||
document.addEventListener('click', () => { // Close the menu when clicking outside
|
||
menuDropdown.classList.remove('show');
|
||
});
|
||
|
||
return card;
|
||
}
|
||
|
||
function createShortsCard(video) {
|
||
const card = document.createElement('div');
|
||
card.className = 'shorts-card';
|
||
|
||
card.innerHTML = `
|
||
<div class="shorts-thumbnail">
|
||
<img src="${video.thumbnail}" alt="${video.title}">
|
||
</div>
|
||
<div class="shorts-info">
|
||
<h3 class="shorts-title">${video.title}</h3>
|
||
<div class="shorts-stats">
|
||
<span><i class="fas fa-eye"></i>${video.views}</span>
|
||
<span><i class="fas fa-clock"></i>${video.uploadDate}</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
return card;
|
||
}
|
||
|
||
function renderSubscriptions() { // Render the subscriptions list
|
||
const subscriptions = [
|
||
{ name: '花譜 -KAF-', avatar: 'img/avatar/kaf.png' },
|
||
{ name: 'ryo (supercell)', avatar: 'img/avatar/ryo.jpg' },
|
||
{ name: 'ヰ世界情緒', avatar: 'img/avatar/isekaijoucho.jpg' },
|
||
{ name: 'MyGo!!!!!', avatar: 'img/avatar/mygo.png' },
|
||
{ name: 'ZUTOMAYO', avatar: 'img/avatar/ztmy.jpg' },
|
||
{ name: 'Aqu3ra', avatar: 'img/avatar/aqu3ra.png' },
|
||
{ name: '『超かぐや姫 ! 』公式', avatar: 'img/avatar/cpk.png' },
|
||
{ name: '礼衣 / Rei', avatar: 'img/avatar/rei.jpg' },
|
||
{ name: 'tayori', avatar: 'img/avatar/tayori.jpg' }
|
||
];
|
||
|
||
subscriptionsList.innerHTML = '';
|
||
|
||
subscriptions.forEach(sub => {
|
||
const item = document.createElement('div');
|
||
item.className = 'subscription-item';
|
||
|
||
item.innerHTML = `
|
||
<div class="subscription-avatar">
|
||
<img src="${sub.avatar}" alt="${sub.name}">
|
||
</div>
|
||
<div class="subscription-name">${sub.name}</div>
|
||
`;
|
||
|
||
subscriptionsList.appendChild(item);
|
||
});
|
||
}
|
||
function performSearch(query) { // Perform the search and render the results
|
||
if (!query.trim()) {
|
||
searchResults.style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
const results = videos.filter(video =>
|
||
video.title.toLowerCase().includes(query.toLowerCase()) ||
|
||
video.channel.toLowerCase().includes(query.toLowerCase())
|
||
);
|
||
|
||
renderSearchResults(results, query);
|
||
}
|
||
|
||
// Render the search results
|
||
function renderSearchResults(results, query) {
|
||
searchResultsContent.innerHTML = '';
|
||
|
||
if (results.length === 0) {
|
||
searchResultsContent.innerHTML = '<p>No videos found related to your search.</p>';
|
||
searchResults.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
results.forEach(video => {
|
||
const item = document.createElement('div');
|
||
item.className = 'search-result-item';
|
||
|
||
// Highlight the search keywords in the title and channel name
|
||
const highlightedTitle = video.title.replace(new RegExp(`(${query})`, 'gi'), '<span class="highlight">$1</span>');
|
||
const highlightedChannel = video.channel.replace(new RegExp(`(${query})`, 'gi'), '<span class="highlight">$1</span>');
|
||
|
||
item.innerHTML = `
|
||
<div class="search-result-thumbnail">
|
||
<img src="${video.thumbnail}" alt="${video.title}">
|
||
</div>
|
||
<div class="search-result-details">
|
||
<h4 class="search-result-title">${highlightedTitle}</h4>
|
||
<div class="search-result-channel">${highlightedChannel}</div>
|
||
<div class="search-result-stats">${video.views} views · ${video.uploadDate}</div>
|
||
</div>
|
||
`;
|
||
|
||
searchResultsContent.appendChild(item);
|
||
});
|
||
|
||
searchResults.style.display = 'block';
|
||
}
|
||
|
||
// Bind events to the DOM elements
|
||
function bindEvents() { // Bind events to the DOM elements
|
||
// Menu button event - toggle sidebar
|
||
const menuBtn = document.querySelector('.menu-btn');
|
||
const sidebar = document.querySelector('.sidebar');
|
||
|
||
menuBtn.addEventListener('click', () => {
|
||
sidebar.classList.toggle('collapsed');
|
||
});
|
||
|
||
// Search event
|
||
searchInput.addEventListener('input', (e) => {
|
||
performSearch(e.target.value);
|
||
});
|
||
|
||
searchBtn.addEventListener('click', () => {
|
||
performSearch(searchInput.value);
|
||
});
|
||
|
||
// Close the search results when clicking outside
|
||
document.addEventListener('click', (e) => {
|
||
if (!e.target.closest('.search-container') && !e.target.closest('.search-results')) {
|
||
searchResults.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// Category buttons event
|
||
categoryBtns.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
// Remove all active states
|
||
categoryBtns.forEach(b => b.classList.remove('active'));
|
||
// Add current active state
|
||
btn.classList.add('active');
|
||
// Update current category
|
||
currentCategory = btn.textContent;
|
||
// Re-render videos and shorts
|
||
renderVideos();
|
||
});
|
||
});
|
||
|
||
// Sidebar navigation buttons event
|
||
const navBtns = document.querySelectorAll('.nav-btn');
|
||
navBtns.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
// Remove all active states
|
||
navBtns.forEach(b => b.classList.remove('active'));
|
||
// Add current active state
|
||
btn.classList.add('active');
|
||
});
|
||
});
|
||
|
||
// Subscription section expand/collapse event
|
||
const subscriptionTitle = document.querySelector('.nav-section-title');
|
||
subscriptionTitle.addEventListener('click', () => {
|
||
const subscriptionsList = document.querySelector('.subscriptions-list');
|
||
const chevron = subscriptionTitle.querySelector('i');
|
||
|
||
subscriptionsList.classList.toggle('collapsed');
|
||
|
||
if (subscriptionsList.classList.contains('collapsed')) {
|
||
chevron.style.transform = 'rotate(-90deg)';
|
||
} else {
|
||
chevron.style.transform = 'rotate(0deg)';
|
||
}
|
||
});
|
||
}
|
||
|
||
// Page load event: initialize the application
|
||
window.addEventListener('DOMContentLoaded', init); |