现在Vibe coding 已经成为了主流,很多厂内部喜欢搞什么token使用量排名

但是这几天我想用AI把我的网站变成一个看番站,出现了一个很大的局限性

这是相关代码

anime.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//- 引入 Artplayer 和 HLS 支持 (用于播放直链和 m3u8)
script(src="https://cdn.jsdelivr.net/npm/hls.js/dist/hls.min.js")
script(src="https://cdn.jsdelivr.net/npm/artplayer/dist/artplayer.js")

//- 页面主体内容
.article-content(style="max-width: 1200px; margin: 0 auto; padding: 20px;")

//- 标题和图标
div(style="display: flex; align-items: center; gap: 8px; margin-bottom: 20px; font-size: 1.4rem; font-weight: bold; color: var(--text-color);")
svg(xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-brand-bilibili")
path(stroke="none" d="M0 0h24v24H0z" fill="none")
path(d="M3 10a4 4 0 0 1 4 -4h10a4 4 0 0 1 4 4v6a4 4 0 0 1 -4 4h-10a4 4 0 0 1 -4 -4v-6z")
path(d="M8 3l2 3")
path(d="M16 3l-2 3")
path(d="M9 13v-2")
path(d="M15 11v2")
span 我的追番

//- 追番卡片网格
.bili-grid
//- ★ 新增:数据兼容与扁平化处理
- //- ★ 新增:数据兼容与扁平化处理 (修复 Hexo 子目录解析问题)
- var animeList = []
- if (site.data) {
- // 1. 兼容旧版:如果还是使用单一的 source/_data/anime.yml
- if (site.data.anime) {
- if (Array.isArray(site.data.anime)) {
- animeList = animeList.concat(site.data.anime);
- } else {
- animeList.push(site.data.anime);
- }
- }
- // 2. 支持文件夹:扫描 source/_data/anime/ 目录下的所有文件
- // Hexo 会把子文件夹下的文件解析成 "anime/文件名" 的形式
- for (var key in site.data) {
- if (key.indexOf('anime/') === 0) { // 找到所有以 anime/ 开头的配置
- var val = site.data[key];
- if (Array.isArray(val)) {
- animeList = animeList.concat(val); // 如果文件里是数组
- } else if (typeof val === 'object' && val !== null) {
- animeList.push(val); // 如果文件里是单个对象
- }
- }
- }
- }

if animeList.length > 0
each item in animeList
//- ★ 注入 data-sources 和 data-episodes 数据
- var sourcesJson = item.sources ? JSON.stringify(item.sources) : "[]";
- var episodesJson = item.episodes ? JSON.stringify(item.episodes) : "[]";
- var defaultUrl = item.url || "";

a.bili-card-wrapper(href="javascript:void(0);" data-url=defaultUrl data-title=item.title data-bangumi=item.bangumi_url data-sources=sourcesJson data-episodes=episodesJson)
.bili-card
.cover-wrapper
img(src=item.cover alt=item.title loading="lazy" referrerpolicy="no-referrer")
.watched-badge(style="display:none;") 已观看
.play-overlay
span.play-text 点击观看
h3= item.title
else
p(style="grid-column: 1 / -1; text-align: center;") 暂无追番数据,请确保已在 source/_data/anime/ 目录下创建了 yml 文件

//- ★ 播放器模态窗口 (更新了UI区)
#anime-modal.anime-modal
.anime-modal-content
.anime-modal-header
h2#anime-modal-title 正在加载...
.anime-modal-tools
//- 选集/换源按钮
button#anime-episode-btn.anime-tool-btn(style="display:none;")
i.fas.fa-list-ul
span 选集/换源
//- 关闭按钮
button#anime-modal-close.anime-modal-close
i.fas.fa-times

.anime-modal-body
//- 原本的 iframe (用于 B站、解析接口等网页)
iframe#anime-modal-iframe(src="" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true")

//- ★ 新增:Artplayer 容器
#anime-artplayer-container(style="width:100%; height:100%; display:none;")

//- ★ 选集侧边栏面板
#anime-episode-sidebar.anime-episode-sidebar
.sidebar-header
h3 播放列表
//- ★ 新增:换源下拉菜单
select#anime-source-select(style="display:none;")
button#sidebar-close-btn
i.fas.fa-chevron-right
.sidebar-list#anime-episode-list
//- JS动态渲染的集数列表

//- 右键角色菜单窗口
#anime-context-menu.anime-context-menu
.context-menu-header
span#context-menu-title 登场角色
span#context-menu-loading.loading-text(style="display:none;") 加载中...
.context-menu-body#context-menu-characters

anime.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
document.addEventListener("DOMContentLoaded", function () {
// ==========================================
// ★ 1. 全局解除防盗链 (绕过腾讯、B站等视频直链限制)
// ==========================================
let metaReferrer = document.querySelector('meta[name="referrer"]');
if (!metaReferrer) {
metaReferrer = document.createElement("meta");
metaReferrer.name = "referrer";
metaReferrer.content = "no-referrer";
document.head.appendChild(metaReferrer);
} else {
metaReferrer.content = "no-referrer";
}

// ==========================================
// ★ 2. 初始化配置与本地历史记录
// ==========================================
const animeConfig = window.theme && window.theme.anime ? window.theme.anime : {};
const isHistoryEnabled = animeConfig.history !== false;
const globalApi = animeConfig.player_api || "";
let art = null; // 全局 Artplayer 实例

// 提取 B站 BVID
function extractBvid(url) {
const match = url.match(/BV[a-zA-Z0-9]+/);
return match ? match[0] : url;
}

// 修复原生全屏事件下的侧边栏显示问题
document.addEventListener("fullscreenchange", function () {
const sidebar = document.getElementById("anime-episode-sidebar");
if (document.fullscreenElement) {
sidebar.classList.add("fixed-fullscreen");
document.fullscreenElement.appendChild(sidebar);
} else {
sidebar.classList.remove("fixed-fullscreen");
document.querySelector(".anime-modal-body").appendChild(sidebar);
}
});

// ==========================================
// ★ 3. 核心播放控制 (切换 iframe 或 Artplayer)
// ==========================================
function playVideo(rawUrl, apiOverride) {
const api = apiOverride !== undefined ? apiOverride : globalApi;
let finalUrl = rawUrl;

// 处理 B站视频解析逻辑
if (api.includes("player.bilibili.com")) {
const bvid = extractBvid(rawUrl);
finalUrl = bvid.startsWith("BV")
? `${api}${bvid}&high_quality=1&danmaku=1`
: rawUrl;
} else {
finalUrl = api ? `${api}${rawUrl}` : rawUrl;
}

const iframe = document.getElementById("anime-modal-iframe");
const playerContainer = document.getElementById("anime-artplayer-container");
const episodeSidebar = document.getElementById("anime-episode-sidebar");

// 拦截 blob 临时链接
if (finalUrl.startsWith("blob:")) {
alert("播放失败!\n\n检测到使用了 blob: 链接,这通常是其他网站的临时缓存,无法跨站播放。\n请换源或使用真实的 .m3u8 直链!");
iframe.style.display = "none";
playerContainer.style.display = "none";
return;
}

// 判断是否为视频直链 (m3u8, mp4 等)
const isDirectVideo =
finalUrl.match(/\.(mp4|m3u8|flv|webm)($|\?)/i) ||
finalUrl.includes("toutiao50.com") ||
finalUrl.includes("/video/tos/");

if (isDirectVideo) {
// 是直链:使用 Artplayer,隐藏 iframe
iframe.style.display = "none";
iframe.src = "";
playerContainer.style.display = "block";

const accentColor =
getComputedStyle(document.documentElement).getPropertyValue("--accent-color").trim() || "#ff6b6b";

let customType = {};
if (finalUrl.includes(".m3u8") || finalUrl.includes("m3u8")) {
customType.m3u8 = function (video, url, artInstance) {
if (Hls.isSupported()) {
if (artInstance.hls) artInstance.hls.destroy();
const hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
artInstance.hls = hls;
if (!artInstance.hlsDestroyBound) {
artInstance.on("destroy", () => {
if (artInstance.hls) artInstance.hls.destroy();
});
artInstance.hlsDestroyBound = true;
}
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = url;
} else {
artInstance.notice.show = "您的浏览器不支持 m3u8 播放";
}
};
}

if (art) {
art.switchUrl(finalUrl);
} else {
art = new Artplayer({
container: playerContainer,
url: finalUrl,
customType: customType,
autoplay: true,
theme: accentColor,
volume: 0.8,
isLive: false,
muted: false,
fullscreen: true,
pip: false,
playbackRate: true,
setting: true,
autoOrientation: true,
lock: true,
controls: [
{
position: "right",
html: '<div style="display:flex;align-items:center;gap:4px;font-size:14px;padding:0 10px;cursor:pointer;"><i class="fas fa-list-ul"></i> 选集</div>',
tooltip: "播放列表",
click: function () {
episodeSidebar.classList.toggle("show");
},
},
],
});

// 自动播放下一集
art.on("video:ended", function () {
const activeBtn = document.querySelector(".episode-btn.active");
if (
activeBtn &&
activeBtn.nextElementSibling &&
activeBtn.nextElementSibling.classList.contains("episode-btn")
) {
art.notice.show = "即将自动播放下一集...";
setTimeout(() => {
activeBtn.nextElementSibling.click();
}, 1500);
} else {
art.notice.show = "已经是最后一集了~";
}
});
}
} else {
// 网页外链/第三方解析接口:使用 iframe
if (art) {
const safeBody = document.querySelector(".anime-modal-body");
if (safeBody && episodeSidebar && episodeSidebar.parentNode !== safeBody) {
episodeSidebar.classList.remove("fixed-fullscreen");
safeBody.appendChild(episodeSidebar);
}
art.destroy(false);
art = null;
}
playerContainer.style.display = "none";
iframe.style.display = "block";
iframe.src = finalUrl;
}
}

// ==========================================
// ★ 4. 观看历史记录逻辑
// ==========================================
const HISTORY_KEY = "anime_watch_history";
function getHistory() {
try { return JSON.parse(localStorage.getItem(HISTORY_KEY)) || {}; } catch { return {}; }
}
function saveHistory(title) {
if (!isHistoryEnabled) return;
const history = getHistory();
history[title] = new Date().getTime();
localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
updateBadges();
}
function updateBadges() {
if (!isHistoryEnabled) return;
const history = getHistory();
document.querySelectorAll(".bili-card-wrapper").forEach((card) => {
const title = card.getAttribute("data-title");
const badge = card.querySelector(".watched-badge");
if (history[title] && badge) badge.style.display = "block";
});
}
updateBadges();

// ==========================================
// ★ 5. 获取 DOM 元素
// ==========================================
const modal = document.getElementById("anime-modal");
const modalTitle = document.getElementById("anime-modal-title");
const modalClose = document.getElementById("anime-modal-close");
const episodeBtn = document.getElementById("anime-episode-btn");
const episodeSidebar = document.getElementById("anime-episode-sidebar");
const episodeList = document.getElementById("anime-episode-list");
const sidebarCloseBtn = document.getElementById("sidebar-close-btn");
const sourceSelect = document.getElementById("anime-source-select");
const contextMenu = document.getElementById("anime-context-menu");
const charactersContainer = document.getElementById("context-menu-characters");
const loadingText = document.getElementById("context-menu-loading");

// ==========================================
// ★ 6. 绑定卡片交互事件 (纯本地 yml 数据)
// ==========================================
document.querySelectorAll(".bili-card-wrapper").forEach((card) => {
// 6.1 左键点击卡片 -> 获取播放源并打开模态框
card.addEventListener("click", function (e) {
if (e.button !== 0) return;

const title = this.getAttribute("data-title");
const defaultUrl = this.getAttribute("data-url"); // 兼容没有 sources 只有单个 url 的旧版

// 读取 YAML 中配置好的本地源
let yamlSources = JSON.parse(this.getAttribute("data-sources") || "[]");
let rootEpisodes = JSON.parse(this.getAttribute("data-episodes") || "[]");

// 兼容旧版写法:如果没有 sources 数组但有 episodes 数组
if (yamlSources.length === 0 && rootEpisodes.length > 0) {
yamlSources = [{ name: "默认源", episodes: rootEpisodes }];
}

saveHistory(title);
contextMenu.classList.remove("show");
modalTitle.textContent = title;

if (yamlSources.length > 0) {
episodeBtn.style.display = "flex";
sourceSelect.style.display = yamlSources.length > 1 ? "block" : "none";
sourceSelect.innerHTML = "";

// 填充下拉菜单
yamlSources.forEach((src, idx) => {
const opt = document.createElement("option");
opt.value = idx;
opt.textContent = src.name;
sourceSelect.appendChild(opt);
});

// 渲染选集按钮函数
function renderEpisodes(sourceIndex) {
const currentSource = yamlSources[sourceIndex];
const eps = currentSource.episodes || [];
episodeList.innerHTML = "";

if (eps.length === 0) {
episodeList.innerHTML = `<div style="padding: 20px; color: #aaa; text-align: center;">该源暂无集数数据</div>`;
return;
}

eps.forEach((ep, index) => {
const btn = document.createElement("button");
btn.className = "episode-btn";
if (index === 0) btn.classList.add("active");
btn.textContent = ep.title;

btn.addEventListener("click", () => {
document.querySelectorAll(".episode-btn").forEach((b) => b.classList.remove("active"));
btn.classList.add("active");
playVideo(ep.url, currentSource.player_api);
});
episodeList.appendChild(btn);
});

// 自动播放该源的第一集
playVideo(eps[0].url, currentSource.player_api);
}

// 监听换源事件
sourceSelect.onchange = (e) => renderEpisodes(e.target.value);

// 默认渲染第一个源
renderEpisodes(0);

} else if (defaultUrl) {
// 非常旧的单链接写法
episodeBtn.style.display = "none";
sourceSelect.style.display = "none";
playVideo(defaultUrl, globalApi);
} else {
// 什么数据都没有
episodeBtn.style.display = "none";
sourceSelect.style.display = "none";
episodeList.innerHTML = `<div style="padding: 20px; color: #ff6b6b; text-align: center;">未能找到该番剧的播放源,请检查 yml 配置</div>`;
}

modal.classList.add("active");
document.body.style.overflow = "hidden";
episodeSidebar.classList.remove("show"); // 默认不展开侧边栏
});

// 6.2 右键点击卡片 -> 请求 Bangumi API 展示角色
card.addEventListener("contextmenu", function (e) {
e.preventDefault();
const bangumiUrl = this.getAttribute("data-bangumi");
charactersContainer.innerHTML = "";

let x = e.clientX;
let y = e.clientY;
contextMenu.style.left = `${x}px`;
contextMenu.style.top = `${y}px`;
contextMenu.classList.add("show");

const rect = contextMenu.getBoundingClientRect();
if (x + rect.width > window.innerWidth) contextMenu.style.left = `${window.innerWidth - rect.width - 10}px`;
if (y + rect.height > window.innerHeight) contextMenu.style.top = `${window.innerHeight - rect.height - 10}px`;

if (!bangumiUrl) {
charactersContainer.innerHTML = "<div style='grid-column: 1/-1; text-align:center; color:#888;'>未配置 bangumi_url</div>";
return;
}

const subjectMatch = bangumiUrl.match(/\/subject\/(\d+)/);
if (!subjectMatch) {
charactersContainer.innerHTML = "<div style='grid-column: 1/-1; text-align:center; color:#888;'>无法解析 Bangumi ID</div>";
return;
}

const subjectId = subjectMatch[1];
loadingText.style.display = "inline";

fetch(`https://api.bgm.tv/v0/subjects/${subjectId}/characters`)
.then((res) => res.json())
.then((data) => {
loadingText.style.display = "none";
if (!data || data.length === 0) {
charactersContainer.innerHTML = "<div style='grid-column: 1/-1; text-align:center; color:#888;'>暂无角色信息</div>";
return;
}
const topCharacters = data.slice(0, 30);
topCharacters.forEach((char) => {
const imgSrc = char.images && char.images.grid ? char.images.grid : "https://api.bgm.tv/v0/img/no_icon_subject.png";
const item = document.createElement("div");
item.className = "character-item";
item.innerHTML = `
<img class="character-avatar" src="${imgSrc}" alt="${char.name}" referrerpolicy="no-referrer">
<span class="character-name" title="${char.name}">${char.name}</span>
`;
charactersContainer.appendChild(item);
});
})
.catch((err) => {
console.error(err);
loadingText.style.display = "none";
charactersContainer.innerHTML = "<div style='grid-column: 1/-1; text-align:center; color:#888;'>加载失败,请检查网络</div>";
});
});
});

// ==========================================
// ★ 7. 模态框与侧边栏控制逻辑
// ==========================================
if (episodeBtn) episodeBtn.addEventListener("click", () => episodeSidebar.classList.toggle("show"));
if (sidebarCloseBtn) sidebarCloseBtn.addEventListener("click", () => episodeSidebar.classList.remove("show"));

function closeModal() {
const safeBody = document.querySelector(".anime-modal-body");
const safeSidebar = document.getElementById("anime-episode-sidebar");
if (safeBody && safeSidebar && safeSidebar.parentNode !== safeBody) {
safeSidebar.classList.remove("fixed-fullscreen");
safeBody.appendChild(safeSidebar);
}

modal.classList.remove("active");
if (episodeSidebar) episodeSidebar.classList.remove("show");

const iframe = document.getElementById("anime-modal-iframe");
if (iframe) iframe.src = "";
if (art) {
art.destroy(false);
art = null;
}
document.body.style.overflow = "";
}

if (modalClose) modalClose.addEventListener("click", closeModal);
if (modal) {
modal.addEventListener("click", function (e) {
if (e.target === modal) closeModal();
});
}

// 点击空白处关闭右键角色菜单
document.addEventListener("click", function (e) {
if (contextMenu && !contextMenu.contains(e.target)) {
contextMenu.classList.remove("show");
}
});

// 滚动时关闭右键角色菜单
window.addEventListener(
"scroll",
function () {
if (contextMenu && contextMenu.classList.contains("show")) {
contextMenu.classList.remove("show");
}
},
{ passive: true },
);
});

anime.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
/* ===================================================================
Bilibili 追番展示墙样式 (anime.css)
=================================================================== */

/* --- 1. 网格与卡片布局 --- */
.bili-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 24px;
align-items: stretch;
margin-top: 20px;
}

/* 外层容器:消除移动端点击时的长方形描边和高亮阴影 */
.bili-card-wrapper {
display: block;
text-decoration: none;
height: 100%;
cursor: pointer;
outline: none !important;
-webkit-tap-highlight-color: transparent;
}

.bili-card-wrapper:focus,
.bili-card-wrapper:active {
outline: none !important;
}

/* 内层卡片:负责显示UI和动画位移 */
.bili-card {
display: flex;
flex-direction: column;
align-items: center;
overflow: hidden;
border-radius: 14px;
padding: 10px;
height: 100%;
box-sizing: border-box;
background: linear-gradient(180deg, rgba(150, 150, 150, 0.06), rgba(150, 150, 150, 0.02));
border: 1px solid rgba(150, 150, 150, 0.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

/* 悬停外层容器时,让内层卡片上浮并加深阴影 */
.bili-card-wrapper:hover .bili-card {
transform: translateY(-4px);
box-shadow: 0 12px 26px rgba(0, 0, 0, 0.15);
border-color: rgba(150, 150, 150, 0.3);
}

/* 封面包装器 */
.cover-wrapper {
position: relative;
width: 100%;
border-radius: 10px;
overflow: hidden;
}

/* 番剧封面图 */
.bili-card img {
width: 100%;
height: auto;
aspect-ratio: 3 / 4;
object-fit: cover;
border-radius: 10px;
box-shadow: 0 6px 14px rgba(0, 0, 0, 0.15);
}

/* 番剧标题 */
.bili-card h3 {
width: 100%;
font-size: 1.15rem;
margin: 12px 0 0 0;
padding: 0;
font-weight: 600;
color: var(--text-color);
text-align: center;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
border-left: none;
}

/* --- 2. 徽章与悬停特效 --- */

/* "已观看" 徽章 */
.watched-badge {
position: absolute;
bottom: 8px;
right: 8px;
background: rgba(255, 107, 107, 0.9);
color: white;
font-size: 0.75rem;
padding: 3px 8px;
border-radius: 4px;
font-weight: bold;
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
z-index: 5;
}

/* 封面悬停遮罩层:默认全透明,鼠标移入显现 */
.play-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition: opacity 0.3s ease;
-webkit-backdrop-filter: blur(2px);
backdrop-filter: blur(2px);
border-radius: 10px;
/* 贴合封面的圆角 */
z-index: 10;
}

.bili-card-wrapper:hover .play-overlay {
opacity: 1;
}

/* "点击观看" 悬浮按钮:随主题色变色 */
.play-text {
color: #ffffff;
background-color: var(--accent-color, #ff6b6b);
font-size: 1.1rem;
font-weight: bold;
padding: 8px 18px;
border-radius: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transform: scale(0.9);
transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.bili-card-wrapper:hover .play-text {
transform: scale(1);
}

/* --- 3. 模态播放器窗口 (与文章模态尺寸对齐) --- */
.anime-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
/* 【卡顿修复】移除了会导致大面积视频渲染掉帧的 backdrop-filter: blur(10px); 改为了加深颜色的背景 */
background: rgba(0, 0, 0, 0.92);
z-index: 9999;
align-items: flex-start;
/* 居上,与文章模态保持一致 */
justify-content: center;
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
}

.anime-modal.active {
display: flex;
}

.anime-modal-content {
background: var(--card-background-color, #ffffff);
width: 95%;
/* 与文章模态宽度一致 */
max-width: 1400px;
/* 与文章模态最大宽度一致 */
height: 90vh;
/* 与文章模态高度一致 */
margin-top: 3vh;
/* 与文章模态上边距一致 */
border-radius: 8px;
display: flex;
flex-direction: column;
overflow: hidden;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
position: relative;
}

.anime-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid var(--border-color, #e5e5e5);
/* ★ 新增:提升顶部工具栏层级,防止被下方 iframe 遮挡吃掉点击事件 */
position: relative;
z-index: 100;
}

.anime-modal-header h2 {
margin: 0;
font-size: 1.5rem;
color: var(--text-color, #333);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

/* 模态框顶部工具栏 (含选集和关闭按钮) */
.anime-modal-tools {
display: flex;
align-items: center;
gap: 15px;
}

/* 选集按钮 UI */
.anime-tool-btn {
background: var(--background-color, #f0f0f0);
border: 1px solid var(--border-color, #ddd);
color: var(--text-color, #333);
padding: 6px 12px;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 6px;
transition: all 0.2s;
}

.anime-tool-btn:hover {
color: var(--accent-color);
border-color: var(--accent-color);
}

.anime-modal-close {
background: transparent;
border: none;
color: var(--text-color, #333);
font-size: 1.5rem;
cursor: pointer;
transition: color 0.2s;
display: flex;
align-items: center;
justify-content: center;
}

.anime-modal-close:hover {
color: var(--accent-color, #ff6b6b);
}

.anime-modal-body {
flex: 1;
background: #000;
position: relative;
/* 为侧边栏提供定位基准 */
overflow: hidden;
}

.anime-modal-body iframe {
width: 100%;
height: 100%;
border: none;
display: block;
}

/* --- 4. 选集侧边栏 (抽屉动画 UI) --- */
.anime-episode-sidebar {
position: absolute;
top: 0;
right: -320px;
/* 默认隐藏在右侧视野外 */
width: 300px;
height: 100%;
/* 同样为了性能移除 blur() */
background: rgba(25, 25, 25, 0.98);
box-shadow: -5px 0 25px rgba(0, 0, 0, 0.5);
transition: right 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
display: flex;
flex-direction: column;
/* ★ 修改:将 z-index 提至极高,绝对压制所有视频容器 */
z-index: 2147483647;
border-left: 1px solid rgba(255, 255, 255, 0.1);
}

/* 唤出状态 */
.anime-episode-sidebar.show {
right: 0;
}

.sidebar-header {
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
color: #fff;
}

.sidebar-header h3 {
margin: 0;
font-size: 1.1rem;
}

#sidebar-close-btn {
background: transparent;
border: none;
color: #aaa;
font-size: 1.2rem;
cursor: pointer;
transition: color 0.2s;
}

#sidebar-close-btn:hover {
color: #fff;
}

.sidebar-list {
flex: 1;
overflow-y: auto;
/* ★ 为底部留出充分的空白,方便选择最后几集 */
padding: 10px 10px 80px 10px;
display: flex;
flex-direction: column;
gap: 8px;
}

/* 侧边栏滚动条美化 */
.sidebar-list::-webkit-scrollbar {
width: 6px;
}

.sidebar-list::-webkit-scrollbar-thumb {
background: #555;
border-radius: 3px;
}

/* 每一集的按钮 */
.episode-btn {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #ddd;
padding: 12px 15px;
border-radius: 8px;
text-align: left;
cursor: pointer;
font-size: 0.95rem;
transition: all 0.2s;
}

.episode-btn:hover {
background: rgba(255, 255, 255, 0.15);
color: #fff;
}

/* 当前正在播放的集数高亮 */
.episode-btn.active {
background: var(--accent-color);
color: #fff;
border-color: var(--accent-color);
font-weight: bold;
}


/* --- 5. 右键角色菜单 --- */
.anime-context-menu {
position: fixed;
display: none;
width: 320px;
background: var(--card-background-color, #ffffff);
border: 1px solid var(--border-color, #e5e5e5);
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
z-index: 10000;
/* 保证在最顶端 */
overflow: hidden;
flex-direction: column;
}

.anime-context-menu.show {
display: flex;
}

.context-menu-header {
padding: 12px 16px;
border-bottom: 1px solid var(--border-color, #e5e5e5);
font-weight: bold;
display: flex;
justify-content: space-between;
font-size: 1rem;
color: var(--text-color, #333);
}

.loading-text {
font-size: 0.85rem;
color: var(--accent-color, #ff6b6b);
}

.context-menu-body {
padding: 16px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
max-height: 320px;
overflow-y: auto;
}

.character-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
gap: 8px;
}

.character-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--border-color, #eee);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.character-name {
font-size: 0.8rem;
color: var(--text-color, #333);
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}

/* 角色菜单滚动条美化 */
.context-menu-body::-webkit-scrollbar {
width: 6px;
}

.context-menu-body::-webkit-scrollbar-track {
background: transparent;
}

.context-menu-body::-webkit-scrollbar-thumb {
background: #ccc;
border-radius: 3px;
}

/* --- 6. 响应式布局 --- */
@media (max-width: 1200px) {
.bili-grid {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}

@media (max-width: 900px) {
.bili-grid {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}

@media (max-width: 768px) {

/* 1. 取消模态框外部的遮罩留白,变为一个完整的原生页面 */
.anime-modal {
padding: 0;
}

.anime-modal-content {
width: 100%;
height: 100vh;
max-height: 100vh;
margin-top: 0;
border-radius: 0;
display: flex;
flex-direction: column;
}

/* 2. 视频区域固定为 16:9 比例,停靠在页面上方 */
.anime-modal-body {
flex: none;
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
min-height: 30vh;
}

/* 3. ★ 恢复手机端的侧边栏抽屉模式,适应屏幕大小 */
.anime-episode-sidebar {
width: 240px !important;
/* 手机端抽屉变窄 */
right: -240px !important;
/* 默认隐藏 */
}

.anime-episode-sidebar.show {
right: 0 !important;
/* 呼出时显示 */
}

/* 4. ★ 确保顶部栏的呼出按钮在手机端可见 */
#anime-episode-btn,
#sidebar-close-btn {
display: flex !important;
}
}

/* 手机端:卡片排成一竖列 */
@media (max-width: 640px) {
.bili-grid {
grid-template-columns: 1fr;
gap: 20px;
}

.bili-card-wrapper {
max-width: 65%;
margin: 0 auto;
}
}

@media (max-width: 480px) {
.bili-card-wrapper {
max-width: 85%;
}
}

/* --- 7. 暗色模式 (Dark Mode) 适配 --- */
html.dark-mode .bili-card {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0.02));
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 8px 18px rgba(0, 0, 0, 0.3);
}

/* 暗色模式 - 模态框及选集按钮 */
html.dark-mode .anime-modal-content {
background: #1e1e1e;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);
}

html.dark-mode .anime-modal-header {
border-color: #333;
}

html.dark-mode .anime-modal-header h2,
html.dark-mode .anime-modal-close {
color: #eee;
}

html.dark-mode .anime-tool-btn {
background: #2a2a2a;
border-color: #444;
color: #ddd;
}

html.dark-mode .anime-tool-btn:hover {
color: var(--accent-color);
border-color: var(--accent-color);
}

/* 暗色模式 - 右键菜单 */
html.dark-mode .anime-context-menu {
background: #262626;
border-color: #333;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
}

html.dark-mode .context-menu-header {
border-color: #333;
color: #eee;
}

html.dark-mode .character-name {
color: #eee;
}

html.dark-mode .character-avatar {
border-color: #444;
}

html.dark-mode .context-menu-body::-webkit-scrollbar-thumb {
background: #555;
}

/* --- 换源下拉框样式 --- */
#anime-source-select {
background: rgba(255, 255, 255, 0.1);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
padding: 4px 8px;
font-size: 0.85rem;
outline: none;
cursor: pointer;
margin-right: 10px;
}

#anime-source-select option {
background: #222;
color: #fff;
}

/* --- artplayer播放器 --- */
.artplayer-app {
background: #000;
}

/* ===================================================================
★ 新增:全屏与网页全屏专用的层级和定位修复
=================================================================== */
.anime-episode-sidebar.fixed-fullscreen {
position: fixed !important;
top: 0 !important;
height: 100vh !important;
z-index: 2147483647 !important;
/* 强制覆盖播放器 */
}

yml动漫源文件

然后我只需要在_data文件夹的anime文件夹导入这些动漫的源

例如工作细胞BLACK.yml

1
2
3
4
5
6
7
8
9
10
- title: 工作细胞BLACK
cover: https://i1.hdslb.com/bfs/bangumi/image/cbf16b39569f75717c8af09985baf566cf739299.png@660w_884h.webp
bangumi_url: "https://bangumi.tv/subject/304065"
sources:
- name: "BiliBili"
player_api: ""
episodes:
- title: "1"
url: "https://www.bilibili.com/bangumi/play/ep373963"

这样就实现了最基础的播放了,我使用的是artplayer播放器,然后让番在模态窗口播放,然后还能换源

但是最开始我用的是一种非常笨拙的方法,就是手动将小网站上的视频直链一点一点的导入,也就是那种一集一集title

刚开始的时候我误认为这些链接是永久的,我在我的blog里也试了很多遍,完全可以像一个看番的网站一样播放

但是我忽略了一件事就是这些网站有防跟踪和防播放源窃取机制,每隔五六个小时这些源就会全部失效,取而代之的新的源

于是我从我经常看番的一个软件——Kazumi上找灵感,毕竟这个基于给定的规则实现了自动从这些网站上爬番的直链然后播放,可以自动读取番的相关信息还有播放列表,全是自动化的操作,但是我不清楚是怎么做到的,而且我不知道怎么做到不触发网站的反爬虫机制的,因为我让AI写的爬虫脚本都会被看番的网站屏蔽

在我对这个一无所知的情况下,无论我如何要求AI 帮我做这件事,它根本就做不到,这就是所谓的局限性—–AI的能力取决于使用者

感觉自己还是太菜了,否则这个想法很快就实现了,只是目前的版本只能短暂是伪造看番的功能,无法实现一劳永逸的效果

关于AI幻觉

AI幻觉这个问题很严重,因为AI在不知道真实情况下会瞎编,而且是一本正经的瞎编,举个例子

image-20260515140259280

幸亏我没有接受它的方法,看到“你的直觉非常正确”时直接气笑了

image-20260515140404400