initial commit

This commit is contained in:
2026-01-29 20:06:03 +08:00
commit 21cae2336c
14 changed files with 1530 additions and 0 deletions

21
agent.md Normal file
View File

@@ -0,0 +1,21 @@
## Agent
这是一个仿 YouTube 的纯前端页面, 不需要与后端进行任何交互. 但需要尽量做到完整的仿照前端页面
### 项目需求
- 页面顶栏从左到右分别为 菜单(按钮), YouTube 图标(图片), 搜索框(编辑控件, 居中), 搜索按钮(按钮, 附着在编辑框最右侧), 语音识别(按钮, 紧靠在编辑框右侧), 创建(按钮, 居于右侧, 文字左侧还附有 "+" 图标), 通知(按钮, 图标为铃铛样式), 用户头像(按钮, 图标为用户头像)
- 搜索框要求可点击, 且点击时有渐变动画.
- 搜索框要求可在本地数据库中搜索数据. 要求可以进行关键词搜索, 且搜索出的内容要求展示在基于搜索框展开的列表中, 关键词着重标记. 本地数据来源请你自己生成一份视频列表存储在 `/src/media.list` 中.
- 左边侧边栏从上到下分别为 首页(按钮, 左侧图标为房子样式), Shorts(按钮), 订阅(板块, 字的右边附有 ">" 图标, 可点击), 我(板块, 内容从上到下分别为 历史记录, 播放列表, 稍后再看, 赞过的视频, 我的视频, 注意: 每个按钮的左侧均附有图标. 如不知道应该放什么图标, 则直接填写 `/img` 目录的内容, 虽然现在该目录不存在, 但稍后会往目录中放置图片), 探索(板块, 内容从上到下分别为 音乐, 电影, 直播, 如不知道应该放什么图标, 则直接填写 `/img` 目录的内容), 更多 YouTube 产品与功能(板块, 内容分别为 `YouTube Premium`, `YouTube 工作室`, `YouTube Music`, `YouTube Kids`, 如不知道应该放什么图标, 则直接填写 `/img` 目录的内容), 设置(按钮), 举报记录(按钮), 帮助(按钮), 发送反馈(按钮).
- 侧边栏的订阅板块中要求展示订阅的 YouTuber 头像和 ID, 具体可以自行生成.
- 侧边栏底部要求展示页面及版权信息. 例如: `<C> 2026 Google LLC`
- 主页内容要求展示普通横屏视频和短视频(即 `Shorts`), 普通横屏视频的卡片在 `3840x2560` 的分辨率下一行展示的数量为 3 条, 可根据窗口大小进行自适应. Shorts 内容紧接在横屏视频卡片下方, 在同等分辨率下展示的数量为一行 5 条.
- 每个横屏视频卡片中要求展示发布者头像, 视频标题, 发布者 ID, 播放次数, 上传时间. 在卡片的右下角要求附有 "···" 样式的可展开的菜单栏, 内容为 添加到待播列表, 保存到"稍后再看", 保存到播放列表, 下载, 分享, 不感兴趣, 不推荐此频道, 举报. 这些按钮的左侧均有图标.
- Shorts 卡片展示的内容和横屏视频卡片展示的内容差不多, 但不展示发布者头像.
- 主页请放置尽量多的视频卡片, 可以做到整体页面上下滑动.
- 在主页第一行视频的上方还有一些小按钮, 可以跳转到相应的板块浏览对应板块的视频. 分别为 全部, 音乐, 游戏, 直播, 合辑, 动画, 手工艺品, 最近上传, 已观看, 发现新视频. 这些按钮均要求可以点击, 并作出相应视频过滤功能.
### 总要求
- 项目中所有要求与后端进行交互的按钮均不用做具体功能, 只需要做出一个按钮即可.
- 过滤视频, 搜索等只需要获取视频数据的功能均需要在自行生成的视频数据库中进行操作, 不需要与后端对接.
- 将所有生成的视频等数据放置在 `/src` 目录中.
- 可以随意使用 `TailWindCSS`, `Vue` 等前端框架, 但**绝对不是必须使用**, 你可以使用其他前端框架, 如 `React`, `Angular` 等.
- 代码完成后请进行自我审查, 确保运行时能够达到所有想要的效果后再停止思考.

680
css/style.css Normal file
View File

@@ -0,0 +1,680 @@
/* 全局样式重置 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Roboto', Arial, sans-serif;
background-color: #f1f1f1;
color: #333;
}
/* 主容器 */
.youtube-container {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
/* 顶栏样式 */
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
height: 56px;
background-color: white;
border-bottom: 1px solid #e0e0e0;
position: sticky;
top: 0;
z-index: 100;
}
.header-left {
display: flex;
align-items: center;
}
.menu-btn {
background: none;
border: none;
font-size: 24px;
padding: 8px;
margin-right: 16px;
cursor: pointer;
color: #606060;
}
.youtube-logo img {
height: 20px;
}
.header-center {
display: flex;
align-items: center;
flex: 1;
max-width: 640px;
margin: 0 40px;
}
.search-container {
display: flex;
align-items: center;
width: 100%;
}
.search-input {
flex: 1;
height: 40px;
padding: 0 16px;
border: 1px solid #d3d3d3;
border-radius: 20px 0 0 20px;
font-size: 16px;
outline: none;
transition: all 0.3s ease;
}
.search-input:focus {
border-color: #1a73e8;
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
}
.search-btn {
height: 40px;
padding: 0 20px;
background-color: #f8f8f8;
border: 1px solid #d3d3d3;
border-left: none;
border-radius: 0 20px 20px 0;
cursor: pointer;
color: #606060;
}
.search-btn:hover {
background-color: #f0f0f0;
}
.voice-search-btn {
margin-left: 8px;
height: 40px;
width: 40px;
border-radius: 50%;
background-color: #f8f8f8;
border: none;
cursor: pointer;
color: #606060;
}
.voice-search-btn:hover {
background-color: #f0f0f0;
}
.header-right {
display: flex;
align-items: center;
}
.create-btn {
display: flex;
align-items: center;
background: none;
border: none;
padding: 8px 16px;
margin-right: 16px;
cursor: pointer;
color: #606060;
font-size: 14px;
}
.create-btn i {
margin-right: 4px;
font-size: 20px;
}
.notification-btn {
background: none;
border: none;
padding: 8px;
margin-right: 16px;
cursor: pointer;
color: #606060;
font-size: 20px;
}
.profile-btn {
background: none;
border: none;
padding: 8px;
cursor: pointer;
color: #606060;
font-size: 20px;
}
/* 主内容区 */
.main-content {
display: flex;
flex: 1;
overflow: hidden;
}
/* 侧边栏样式 */
.sidebar {
width: 240px;
background-color: white;
border-right: 1px solid #e0e0e0;
overflow-y: auto;
flex-shrink: 0;
}
.sidebar-nav {
padding: 12px 0;
}
.nav-section {
margin-bottom: 24px;
}
.nav-section-title {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24px 8px;
font-size: 14px;
font-weight: 500;
color: #606060;
text-transform: uppercase;
}
.nav-btn {
display: flex;
align-items: center;
width: 100%;
padding: 8px 24px;
background: none;
border: none;
text-align: left;
cursor: pointer;
font-size: 14px;
color: #333;
transition: background-color 0.2s ease;
}
.nav-btn:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.nav-btn.active {
background-color: rgba(0, 0, 0, 0.1);
font-weight: 500;
}
.nav-btn i {
margin-right: 16px;
font-size: 20px;
width: 24px;
}
.subscriptions-list {
padding: 0 12px;
}
.subscription-item {
display: flex;
align-items: center;
padding: 8px 12px;
cursor: pointer;
border-radius: 8px;
transition: background-color 0.2s ease;
}
.subscription-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.subscription-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
margin-right: 12px;
background-color: #e0e0e0;
overflow: hidden;
}
.subscription-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.subscription-name {
font-size: 14px;
color: #333;
}
.sidebar-footer {
padding: 16px 24px;
font-size: 12px;
color: #909090;
border-top: 1px solid #e0e0e0;
}
/* 视频内容区 */
.video-content {
flex: 1;
overflow-y: auto;
padding: 24px;
background-color: #f9f9f9;
}
/* 板块按钮 */
.category-buttons {
display: flex;
gap: 12px;
margin-bottom: 24px;
flex-wrap: wrap;
}
.category-btn {
padding: 8px 16px;
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 20px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.category-btn:hover {
background-color: #f1f1f1;
}
.category-btn.active {
background-color: #333;
color: white;
border-color: #333;
}
/* 视频网格 */
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: 24px;
margin-bottom: 48px;
}
/* 视频卡片 */
.video-card {
background-color: white;
border-radius: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
cursor: pointer;
position: static;
}
.video-thumbnail {
overflow: hidden;
border-radius: 12px 12px 0 0;
}
.video-menu {
position: relative;
z-index: 100;
}
.video-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.video-thumbnail {
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 比例 */
background-color: #f1f1f1;
}
.video-thumbnail img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.video-duration {
position: absolute;
bottom: 8px;
right: 8px;
background-color: rgba(0, 0, 0, 0.8);
color: white;
font-size: 12px;
padding: 2px 6px;
border-radius: 4px;
}
.video-info {
display: flex;
padding: 12px;
}
.video-channel-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
margin-right: 12px;
background-color: #e0e0e0;
}
.video-details {
flex: 1;
min-width: 0;
}
.video-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.video-meta {
font-size: 12px;
color: #606060;
line-height: 1.4;
}
.video-channel {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-bottom: 4px;
}
.video-stats {
display: flex;
gap: 8px;
}
.video-menu {
position: relative;
}
.menu-toggle {
background: none;
border: none;
padding: 8px;
cursor: pointer;
color: #606060;
font-size: 16px;
opacity: 0;
transition: opacity 0.2s ease;
}
.video-card:hover .menu-toggle {
opacity: 1;
}
.menu-dropdown {
position: absolute;
top: 100%;
right: 0;
background-color: white;
border: 1px solid #e0e0e0;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
padding: 8px 0;
min-width: 200px;
z-index: 1000;
display: none;
}
.menu-dropdown.show {
display: block;
}
.menu-item {
display: flex;
align-items: center;
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s ease;
}
.menu-item:hover {
background-color: #f1f1f1;
}
.menu-item i {
margin-right: 12px;
font-size: 16px;
color: #606060;
}
/* Shorts 部分 */
.shorts-section {
margin-top: 48px;
}
.shorts-section h3 {
font-size: 20px;
font-weight: 500;
margin-bottom: 16px;
}
.shorts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 16px;
}
/* Shorts 卡片 */
.shorts-card {
position: relative;
width: 100%;
padding-top: 177.78%; /* 9:16 比例 */
background-color: #f1f1f1;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.shorts-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.shorts-thumbnail {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.shorts-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.shorts-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 12px;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.8));
color: white;
}
.shorts-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.shorts-stats {
font-size: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.shorts-stats i {
margin-right: 4px;
}
/* 搜索结果弹窗 */
.search-results {
position: fixed;
top: 56px;
left: 0;
right: 0;
background-color: white;
border-bottom: 1px solid #e0e0e0;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 99;
max-height: 80vh;
overflow-y: auto;
}
.search-results-content {
padding: 16px;
}
.search-result-item {
display: flex;
align-items: flex-start;
padding: 12px;
cursor: pointer;
border-radius: 8px;
transition: background-color 0.2s ease;
margin-bottom: 8px;
}
.search-result-item:hover {
background-color: #f1f1f1;
}
.search-result-thumbnail {
width: 120px;
height: 68px;
border-radius: 8px;
margin-right: 16px;
background-color: #f1f1f1;
flex-shrink: 0;
}
.search-result-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
.search-result-details {
flex: 1;
min-width: 0;
}
.search-result-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 4px;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
}
.search-result-channel {
font-size: 12px;
color: #606060;
margin-bottom: 2px;
}
.search-result-stats {
font-size: 12px;
color: #606060;
}
.highlight {
background-color: #fff3cd;
font-weight: 500;
}
/* 响应式设计 */
@media (max-width: 1200px) {
.video-grid {
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
}
}
@media (max-width: 992px) {
.sidebar {
width: 200px;
}
.video-grid {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
}
@media (max-width: 768px) {
.sidebar {
display: none;
}
.header-center {
margin: 0 16px;
}
.create-btn span {
display: none;
}
.video-content {
padding: 16px;
}
.video-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 12px;
}
.shorts-grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 12px;
}
}
@media (max-width: 576px) {
.header-center {
display: none;
}
.video-grid {
grid-template-columns: 1fr;
}
.shorts-grid {
grid-template-columns: repeat(4, 1fr);
}
}

BIN
img/cover/game-06.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

BIN
img/cover/game-07.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
img/cover/game-08.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

BIN
img/cover/live-09.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

BIN
img/cover/music-01.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
img/cover/music-02.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
img/cover/music-03.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

BIN
img/cover/music-04.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
img/cover/music-05.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

209
index.html Normal file
View File

@@ -0,0 +1,209 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube Revived</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="youtube-container">
<!-- 顶栏 -->
<header class="header">
<div class="header-left">
<button class="menu-btn">
<i class="fas fa-bars"></i>
</button>
<div class="youtube-logo">
<img src="https://www.gstatic.com/youtube/img/branding/youtubelogo/svg/youtubelogo.svg" alt="YouTube">
</div>
</div>
<div class="header-center">
<div class="search-container">
<input type="text" class="search-input" placeholder="搜索">
<button class="search-btn">
<i class="fas fa-search"></i>
</button>
<button class="voice-search-btn">
<i class="fas fa-microphone"></i>
</button>
</div>
</div>
<div class="header-right">
<button class="create-btn">
<i class="fas fa-plus"></i>
<span>创建</span>
</button>
<button class="notification-btn">
<i class="fas fa-bell"></i>
</button>
<button class="profile-btn">
<i class="fas fa-user"></i>
</button>
</div>
</header>
<!-- 主内容区 -->
<main class="main-content">
<!-- 侧边栏 -->
<aside class="sidebar">
<nav class="sidebar-nav">
<div class="nav-section">
<button class="nav-btn active">
<i class="fas fa-home"></i>
<span>首页</span>
</button>
<button class="nav-btn">
<i class="fas fa-play"></i>
<span>Shorts</span>
</button>
</div>
<div class="nav-section">
<div class="nav-section-title">
<span>订阅</span>
<i class="fas fa-chevron-right"></i>
</div>
<div class="subscriptions-list">
<!-- 订阅内容将通过 JS 动态生成 -->
</div>
</div>
<div class="nav-section">
<div class="nav-section-title">
<span></span>
</div>
<button class="nav-btn">
<i class="fas fa-history"></i>
<span>历史记录</span>
</button>
<button class="nav-btn">
<i class="fas fa-list"></i>
<span>播放列表</span>
</button>
<button class="nav-btn">
<i class="fas fa-clock"></i>
<span>稍后再看</span>
</button>
<button class="nav-btn">
<i class="fas fa-thumbs-up"></i>
<span>赞过的视频</span>
</button>
<button class="nav-btn">
<i class="fas fa-video"></i>
<span>我的视频</span>
</button>
</div>
<div class="nav-section">
<div class="nav-section-title">
<span>探索</span>
</div>
<button class="nav-btn">
<i class="fas fa-music"></i>
<span>音乐</span>
</button>
<button class="nav-btn">
<i class="fas fa-film"></i>
<span>电影</span>
</button>
<button class="nav-btn">
<i class="fas fa-video"></i>
<span>直播</span>
</button>
</div>
<div class="nav-section">
<div class="nav-section-title">
<span>更多 YouTube 产品与功能</span>
</div>
<button class="nav-btn">
<i class="fas fa-crown"></i>
<span>YouTube Premium</span>
</button>
<button class="nav-btn">
<i class="fas fa-store"></i>
<span>YouTube 工作室</span>
</button>
<button class="nav-btn">
<i class="fas fa-music"></i>
<span>YouTube Music</span>
</button>
<button class="nav-btn">
<i class="fas fa-child"></i>
<span>YouTube Kids</span>
</button>
</div>
<div class="nav-section">
<button class="nav-btn">
<i class="fas fa-cog"></i>
<span>设置</span>
</button>
<button class="nav-btn">
<i class="fas fa-flag"></i>
<span>举报记录</span>
</button>
<button class="nav-btn">
<i class="fas fa-question-circle"></i>
<span>帮助</span>
</button>
<button class="nav-btn">
<i class="fas fa-comment"></i>
<span>发送反馈</span>
</button>
</div>
</nav>
<div class="sidebar-footer">
<p>&lt;C&gt; 2026 Google LLC</p>
<p>Pure Frontend version</p>
<p>Coded by Madobi Nanami && AI</p>
</div>
</aside>
<!-- 视频内容区 -->
<div class="video-content">
<!-- 板块按钮 -->
<div class="category-buttons">
<button class="category-btn active">全部</button>
<button class="category-btn">音乐</button>
<button class="category-btn">游戏</button>
<button class="category-btn">直播</button>
<button class="category-btn">合辑</button>
<button class="category-btn">动画</button>
<button class="category-btn">手工艺品</button>
<button class="category-btn">最近上传</button>
<button class="category-btn">已观看</button>
<button class="category-btn">发现新视频</button>
</div>
<!-- 横屏视频 -->
<div class="video-grid">
<!-- 视频卡片将通过 JS 动态生成 -->
</div>
<!-- Shorts -->
<div class="shorts-section">
<h3>Shorts</h3>
<div class="shorts-grid">
<!-- Shorts 卡片将通过 JS 动态生成 -->
</div>
</div>
</div>
</main>
<!-- 搜索结果弹窗 -->
<div class="search-results" style="display: none;">
<div class="search-results-content">
<!-- 搜索结果将通过 JS 动态生成 -->
</div>
</div>
</div>
<script src="js/script.js"></script>
</body>
</html>

343
js/script.js Normal file
View File

@@ -0,0 +1,343 @@
// 全局变量
let videos = [];
let currentCategory = '全部';
// 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();
// 渲染 Shorts
renderShorts();
// 渲染订阅内容
renderSubscriptions();
// 绑定事件
bindEvents();
}
// 加载视频数据
async function loadVideos() {
try {
const response = await fetch('src/media.json');
videos = await response.json();
} catch (error) {
console.error('加载视频数据失败:', error);
// 使用默认数据
videos = [];
}
}
// 渲染视频
function renderVideos() {
const filteredVideos = filterVideosByCategory(currentCategory);
videoGrid.innerHTML = '';
filteredVideos.forEach(video => {
if (video.type === 'video') {
const videoCard = createVideoCard(video);
videoGrid.appendChild(videoCard);
}
});
}
// 渲染 Shorts
function renderShorts() {
const filteredVideos = filterVideosByCategory(currentCategory);
shortsGrid.innerHTML = '';
filteredVideos.forEach(video => {
if (video.type === 'short') {
const shortsCard = createShortsCard(video);
shortsGrid.appendChild(shortsCard);
}
});
}
// 根据分类过滤视频
function filterVideosByCategory(category) {
if (category === '全部') {
return videos;
}
// 简单的分类映射
const categoryMap = {
'音乐': ['音乐'],
'游戏': ['游戏'],
'直播': ['直播'],
'合辑': ['合辑'],
'动画': ['动画'],
'手工艺品': ['手工艺品'],
'最近上传': videos.filter(v => v.uploadDate.includes('天前')),
'已观看': [],
'发现新视频': videos
};
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"></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} 次观看</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>添加到待播列表</span>
</div>
<div class="menu-item">
<i class="fas fa-clock"></i>
<span>保存到"稍后再看"</span>
</div>
<div class="menu-item">
<i class="fas fa-list"></i>
<span>保存到播放列表</span>
</div>
<div class="menu-item">
<i class="fas fa-download"></i>
<span>下载</span>
</div>
<div class="menu-item">
<i class="fas fa-share"></i>
<span>分享</span>
</div>
<div class="menu-item">
<i class="fas fa-thumbs-down"></i>
<span>不感兴趣</span>
</div>
<div class="menu-item">
<i class="fas fa-ban"></i>
<span>不推荐此频道</span>
</div>
<div class="menu-item">
<i class="fas fa-flag"></i>
<span>举报</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', () => {
menuDropdown.classList.remove('show');
});
return card;
}
// 创建 Shorts 卡片
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() {
const subscriptions = [
{ name: '编程大师', avatar: 'https://picsum.photos/id/100/48/48' },
{ name: '前端开发', avatar: 'https://picsum.photos/id/101/48/48' },
{ name: '数据科学', avatar: 'https://picsum.photos/id/102/48/48' },
{ name: 'AI 研究', avatar: 'https://picsum.photos/id/103/48/48' },
{ name: '设计达人', avatar: 'https://picsum.photos/id/104/48/48' }
];
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) {
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);
}
// 渲染搜索结果
function renderSearchResults(results, query) {
searchResultsContent.innerHTML = '';
if (results.length === 0) {
searchResultsContent.innerHTML = '<p>没有找到相关视频</p>';
searchResults.style.display = 'block';
return;
}
results.forEach(video => {
const item = document.createElement('div');
item.className = 'search-result-item';
// 高亮关键词
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} 次观看 · ${video.uploadDate}</div>
</div>
`;
searchResultsContent.appendChild(item);
});
searchResults.style.display = 'block';
}
// 绑定事件
function bindEvents() {
// 搜索事件
searchInput.addEventListener('input', (e) => {
performSearch(e.target.value);
});
searchBtn.addEventListener('click', () => {
performSearch(searchInput.value);
});
// 点击其他地方关闭搜索结果
document.addEventListener('click', (e) => {
if (!e.target.closest('.search-container') && !e.target.closest('.search-results')) {
searchResults.style.display = 'none';
}
});
// 分类按钮事件
categoryBtns.forEach(btn => {
btn.addEventListener('click', () => {
// 移除所有活动状态
categoryBtns.forEach(b => b.classList.remove('active'));
// 添加当前活动状态
btn.classList.add('active');
// 更新当前分类
currentCategory = btn.textContent;
// 重新渲染视频
renderVideos();
renderShorts();
});
});
// 侧边栏导航按钮事件
const navBtns = document.querySelectorAll('.nav-btn');
navBtns.forEach(btn => {
btn.addEventListener('click', () => {
navBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
// 订阅板块展开/收起
const subscriptionTitle = document.querySelector('.nav-section-title');
subscriptionTitle.addEventListener('click', () => {
const subscriptionsList = document.querySelector('.subscriptions-list');
const chevron = subscriptionTitle.querySelector('i');
if (subscriptionsList.style.display === 'none') {
subscriptionsList.style.display = 'block';
chevron.style.transform = 'rotate(0deg)';
} else {
subscriptionsList.style.display = 'none';
chevron.style.transform = 'rotate(-90deg)';
}
});
}
// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', init);

277
src/media.json Normal file
View File

@@ -0,0 +1,277 @@
[
{
"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 主題曲「沈黙の魔女」bgmKitkit Lu COVER",
"channel": "KitKit Lu",
"views": "1,856,164",
"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": "React vs Vue vs Angular",
"channel": "前端开发",
"views": "1,876,543",
"uploadDate": "3天前",
"duration": "19:45",
"thumbnail": "https://picsum.photos/id/9/640/360",
"category": "live",
"type": "live"
},
{
"id": 10,
"title": "机器学习入门到精通",
"channel": "AI 研究",
"views": "2,345,678",
"uploadDate": "5天前",
"duration": "35:22",
"thumbnail": "https://picsum.photos/id/10/640/360",
"category": "AI",
"type": "video"
},
{
"id": 11,
"title": "编程技巧分享",
"channel": "编程大师",
"views": "456,789",
"uploadDate": "1天前",
"duration": "01:23",
"thumbnail": "https://picsum.photos/id/11/360/640",
"category": "编程",
"type": "short"
},
{
"id": 12,
"title": "JavaScript 小技巧",
"channel": "前端开发",
"views": "789,012",
"uploadDate": "2天前",
"duration": "00:58",
"thumbnail": "https://picsum.photos/id/12/360/640",
"category": "编程",
"type": "short"
},
{
"id": 13,
"title": "Python 一行代码",
"channel": "数据科学",
"views": "567,890",
"uploadDate": "3天前",
"duration": "01:15",
"thumbnail": "https://picsum.photos/id/13/360/640",
"category": "编程",
"type": "short"
},
{
"id": 14,
"title": "前端开发日常",
"channel": "前端开发",
"views": "987,654",
"uploadDate": "1天前",
"duration": "01:32",
"thumbnail": "https://picsum.photos/id/14/360/640",
"category": "编程",
"type": "short"
},
{
"id": 15,
"title": "AI 生成艺术",
"channel": "AI 研究",
"views": "1,234,567",
"uploadDate": "2天前",
"duration": "01:45",
"thumbnail": "https://picsum.photos/id/15/360/640",
"category": "AI",
"type": "short"
},
{
"id": 16,
"title": "数据可视化技巧",
"channel": "数据科学",
"views": "654,321",
"uploadDate": "4天前",
"duration": "01:05",
"thumbnail": "https://picsum.photos/id/16/360/640",
"category": "编程",
"type": "short"
},
{
"id": 17,
"title": "编程面试题",
"channel": "编程大师",
"views": "876,543",
"uploadDate": "3天前",
"duration": "01:20",
"thumbnail": "https://picsum.photos/id/17/360/640",
"category": "编程",
"type": "short"
},
{
"id": 18,
"title": "Web 设计趋势",
"channel": "前端开发",
"views": "765,432",
"uploadDate": "5天前",
"duration": "01:10",
"thumbnail": "https://picsum.photos/id/18/360/640",
"category": "设计",
"type": "short"
},
{
"id": 19,
"title": "机器学习模型训练",
"channel": "AI 研究",
"views": "1,098,765",
"uploadDate": "2天前",
"duration": "01:35",
"thumbnail": "https://picsum.photos/id/19/360/640",
"category": "AI",
"type": "short"
},
{
"id": 20,
"title": "Python 库推荐",
"channel": "数据科学",
"views": "543,210",
"uploadDate": "1天前",
"duration": "01:08",
"thumbnail": "https://picsum.photos/id/20/360/640",
"category": "编程",
"type": "short"
},
{
"id": 21,
"title": "2024年前端开发趋势",
"channel": "前端开发",
"views": "1,345,678",
"uploadDate": "1周前",
"duration": "18:32",
"thumbnail": "https://picsum.photos/id/21/640/360",
"category": "编程",
"type": "video"
},
{
"id": 22,
"title": "数据结构与算法基础",
"channel": "编程大师",
"views": "987,654",
"uploadDate": "3天前",
"duration": "25:45",
"thumbnail": "https://picsum.photos/id/22/640/360",
"category": "编程",
"type": "video"
},
{
"id": 23,
"title": "人工智能的未来发展",
"channel": "AI 研究",
"views": "2,123,456",
"uploadDate": "5天前",
"duration": "30:15",
"thumbnail": "https://picsum.photos/id/23/640/360",
"category": "AI",
"type": "video"
},
{
"id": 24,
"title": "Python Web 开发实战",
"channel": "数据科学",
"views": "765,432",
"uploadDate": "2周前",
"duration": "22:33",
"thumbnail": "https://picsum.photos/id/24/640/360",
"category": "编程",
"type": "video"
},
{
"id": 25,
"title": "CSS 高级技巧",
"channel": "前端开发",
"views": "876,543",
"uploadDate": "1天前",
"duration": "15:22",
"thumbnail": "https://picsum.photos/id/25/640/360",
"category": "编程",
"type": "video"
}
]