792 lines
23 KiB
JavaScript
792 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 元素
|
||
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 = '';
|
||
|
||
// 渲染长视频
|
||
videosOnly.forEach(video => {
|
||
const videoCard = createVideoCard(video);
|
||
videoGrid.appendChild(videoCard);
|
||
});
|
||
|
||
// 渲染短视频
|
||
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); |