Files
tvbox_collect/tvbox/lib/pomo-cyberpunk.html
T
2026-06-29 03:41:14 +00:00

3247 lines
209 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"><title>片库 · 4K</title><!-- ES5 兼容引导层(文档第一个内联脚本,必须 ES5):polyfill + 特性检测 + App 标记。见开发文档 15.5 --><script>
(function () {
"use strict";
var D = document, root = D.documentElement;
if (window.fongmiBridge || window.fm || window.fongmi) root.className += " fm-native";
if (window.fongmiClient && window.fongmiClient.isLeanback) root.className += " tv-mode";
if (!Element.prototype.matches) Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
if (!Element.prototype.closest) Element.prototype.closest = function (sel) {
var el = this; while (el && el.nodeType === 1) { if (el.matches(sel)) return el; el = el.parentNode; } return null;
};
function addRC(p) { if (p && !p.replaceChildren) p.replaceChildren = function () {
while (this.firstChild) this.removeChild(this.firstChild);
for (var i = 0; i < arguments.length; i++) this.appendChild(arguments[i]);
}; }
addRC(Element.prototype); if (typeof DocumentFragment !== "undefined") addRC(DocumentFragment.prototype);
if (!Object.values) Object.values = function (o) { var r = [], k; for (k in o) if (Object.prototype.hasOwnProperty.call(o, k)) r.push(o[k]); return r; };
if (!Object.entries) Object.entries = function (o) { var r = [], k; for (k in o) if (Object.prototype.hasOwnProperty.call(o, k)) r.push([k, o[k]]); return r; };
if (!Array.from) Array.from = function (a) { var r = [], i, n = a ? a.length : 0; if (typeof n !== "number") n = 0; for (i = 0; i < n; i++) r.push(a[i]); return r; };
if (!Array.prototype.includes) Array.prototype.includes = function (v) { return this.indexOf(v) !== -1; };
if (!Array.prototype.flat) Array.prototype.flat = function () { return this.reduce(function (a, c) { return a.concat(Array.isArray(c) ? c.flat() : c); }, []); };
if (window.NodeList && NodeList.prototype && !NodeList.prototype.forEach) NodeList.prototype.forEach = Array.prototype.forEach;
if (window.HTMLCollection && HTMLCollection.prototype && !HTMLCollection.prototype.forEach) HTMLCollection.prototype.forEach = Array.prototype.forEach;
if (!String.prototype.includes) String.prototype.includes = function (s) { return this.indexOf(s) !== -1; };
if (!String.prototype.startsWith) String.prototype.startsWith = function (s) { return this.lastIndexOf(s, 0) === 0; };
if (!String.prototype.endsWith) String.prototype.endsWith = function (s) { var i = this.length - s.length; return i >= 0 && this.indexOf(s, i) === i; };
if (typeof Promise !== "undefined" && !Promise.prototype["finally"]) Promise.prototype["finally"] = function (cb) {
return this.then(function (v) { return Promise.resolve(cb()).then(function () { return v; }); },
function (e) { return Promise.resolve(cb()).then(function () { throw e; }); });
};
var supports = (window.CSS && CSS.supports) ? function (p, v) { try { return CSS.supports(p, v); } catch (e) { return false; } } : function () { return false; };
function flexGapOk() {
var f = D.createElement("div");
f.style.cssText = "display:flex;flex-direction:column;position:absolute;visibility:hidden;width:1px;gap:1px;";
var c1 = D.createElement("div"), c2 = D.createElement("div");
c1.style.height = "1px"; c2.style.height = "1px";
f.appendChild(c1); f.appendChild(c2);
(D.body || root).appendChild(f); var ok = f.scrollHeight === 3; f.parentNode.removeChild(f); return ok;
}
function aspectOk() {
if (!supports("aspect-ratio", "1 / 1")) return false;
var b = D.createElement("div"); b.style.cssText = "position:absolute;visibility:hidden;width:50px;aspect-ratio:1/1;";
(D.body || root).appendChild(b); var ok = b.offsetHeight >= 40; b.parentNode.removeChild(b); return ok;
}
function detect() {
if (!flexGapOk()) root.className += " no-layout-gap";
if (!(supports("width", "min(1px,2px)") && supports("width", "clamp(1px,2px,3px)"))) root.className += " no-css-functions";
if (!aspectOk()) root.className += " no-aspect-ratio";
}
if (D.body) detect(); else D.addEventListener("DOMContentLoaded", detect);
})();
</script><style>
/* ============================================================
片库 · 4K 原盘影视下载 — home-cinema 暗金质感
page 透明(壁纸透出)+ 半透明面板承载文字
signature:按画质分组的资源行(scene 发布名 + 体积 + 复制/下载)
语法/兼容:仅基础选择器,无 :is/:has/:focus-visible;无 clamp/min/max
inset 前置 top/left/right/bottom 兜底;动效在 TV / reduce-motion 关闭
============================================================ */
:root {
--bg-fallback: #05050d;
--panel-rgb: 16, 14, 32;
--panel: rgba(var(--panel-rgb), .72);
--panel-soft: rgba(var(--panel-rgb), .52);
--panel-strong: rgba(var(--panel-rgb), .92);
--line: rgba(255, 43, 214, .18);
--line-soft: rgba(255, 43, 214, .09);
--line-strong: rgba(0, 240, 255, .35);
--ink: #eafcff;
--ink-soft: #9fb8c9;
--ink-faint: #5d6f8a;
--gold: #ff2bd6;
--gold-deep: #c200a3;
--gold-lite: #ff7af0;
--gold-edge: #00f0ff;
--gold-rgb: 255, 43, 214;
--on-gold: #08020a;
--gold-soft: rgba(var(--gold-rgb), .16);
--cyan: #00f0ff;
--slate: #8fb6c9;
--green: #39ff8f;
--good: #39ff8f; --warn: #ffe156; --bad: #ff3864; --gray: #5d6f8a;
--neon-glow: 0 0 6px rgba(255,43,214,.7), 0 0 16px rgba(0,240,255,.35);
--safe-top: var(--fm-safe-top, 0px);
--safe-bottom: var(--fm-safe-bottom, 0px);
--safe-left: var(--fm-safe-left, 0px);
--safe-right: var(--fm-safe-right, 0px);
--r-sm: 8px; --r: 12px; --r-lg: 16px;
--maxw: 1180px;
--gut: 14px; /* 统一“到边框”和“卡片间”的间距,避免忽宽忽窄 */
--ease: cubic-bezier(.22, .61, .36, 1);
--display: "Audiowide", "Orbitron", "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei", "Source Han Sans SC", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--mono: "Share Tech Mono", "SFMono-Regular", "JetBrains Mono", "Roboto Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace;
}
@import url("https://fonts.googleapis.com/css2?family=Audiowide&family=Orbitron:wght@500;700;900&family=Share+Tech+Mono&display=swap");
/* 备选主题色(点左上角 P 循环切换):只改强调色变量,全站随之换色 */
html[data-theme="rose"] {
--gold: #ff2d78; --gold-deep: #cc0055; --gold-lite: #ff80b0; --gold-edge: #f7ff00; --gold-rgb: 255, 45, 120; --on-gold: #1a0010;
--cyan: #f7ff00; --line: rgba(255,45,120,.18); --line-soft: rgba(255,45,120,.09); --line-strong: rgba(247,255,0,.35);
}
html[data-theme="ocean"] {
--gold: #00e5ff; --gold-deep: #00a3c4; --gold-lite: #7af7ff; --gold-edge: #ff6b00; --gold-rgb: 0, 229, 255; --on-gold: #001016;
--cyan: #ff6b00; --line: rgba(0,229,255,.18); --line-soft: rgba(0,229,255,.09); --line-strong: rgba(255,107,0,.35);
}
html[data-theme="matrix"] {
--gold: #00ff41; --gold-deep: #00b32c; --gold-lite: #7dffaa; --gold-edge: #ff003c; --gold-rgb: 0, 255, 65; --on-gold: #001a08;
--cyan: #ff4060; --line: rgba(0,255,65,.18); --line-soft: rgba(0,255,65,.09); --line-strong: rgba(255,64,96,.35);
}
html[data-theme="reactor"] {
--gold: #ff8c00; --gold-deep: #cc6400; --gold-lite: #ffbc60; --gold-edge: #00cfff; --gold-rgb: 255, 140, 0; --on-gold: #1a0500;
--cyan: #00cfff; --line: rgba(255,140,0,.18); --line-soft: rgba(255,140,0,.09); --line-strong: rgba(0,207,255,.35);
}
html[data-theme="void"] {
--gold: #bf00ff; --gold-deep: #8800cc; --gold-lite: #df80ff; --gold-edge: #aaff00; --gold-rgb: 191, 0, 255; --on-gold: #080014;
--cyan: #aaff00; --line: rgba(191,0,255,.18); --line-soft: rgba(191,0,255,.09); --line-strong: rgba(170,255,0,.35);
}
* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; padding: 0; min-height: 100%; background: transparent; color: var(--ink); font-family: var(--display); font-size: 15px; line-height: 1.5; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
html:not(.fm-native), html:not(.fm-native) body { background: var(--bg-fallback); }
img { display: block; max-width: 100%; }
button { font-family: inherit; color: inherit; cursor: pointer; }
input { font-family: inherit; }
.hidden { display: none !important; }
.app { position: relative; min-height: 100vh; min-height: var(--fm-web-height, 100vh); background: transparent; padding-left: var(--safe-left); padding-right: var(--safe-right); }
html.fm-native .app { background: linear-gradient(180deg, rgba(5,4,14,.6), rgba(5,4,14,.4) 28%, rgba(5,4,14,.55)); }
/* 赛博朋克霓虹氛围光 + 网格 + 扫描线(低成本,TV 关闭) */
.app::before { content: ""; position: fixed; top: 0; left: 0; right: 0; bottom: auto; height: 560px; pointer-events: none; z-index: 0;
will-change: transform; transform: translateZ(0);
background:
radial-gradient(ellipse 80% 60% at 10% -5%, color-mix(in srgb, var(--cyan) 28%, transparent), color-mix(in srgb, var(--cyan) 0%, transparent) 55%),
radial-gradient(ellipse 80% 60% at 90% -5%, rgba(var(--gold-rgb),.28), rgba(var(--gold-rgb),0) 55%),
radial-gradient(ellipse 40% 30% at 50% 0%, rgba(255,255,255,.04), transparent 60%); }
html:not(.tv-mode) body::before { content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
will-change: transform; transform: translateZ(0);
background-image:
linear-gradient(color-mix(in srgb, var(--cyan) 6%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in srgb, var(--cyan) 6%, transparent) 1px, transparent 1px),
linear-gradient(color-mix(in srgb, var(--cyan) 2%, transparent) 1px, transparent 1px),
linear-gradient(90deg, color-mix(in srgb, var(--cyan) 2%, transparent) 1px, transparent 1px),
linear-gradient(180deg, color-mix(in srgb, var(--gold) 7%, transparent) 0%, transparent 35%);
background-size: 120px 120px, 120px 120px, 24px 24px, 24px 24px, 100% 100%; }
html:not(.tv-mode) body::after { content: ""; position: fixed; left: 0; right: 0; top: 0; height: 2px; z-index: 0; pointer-events: none;
will-change: transform, top;
background: linear-gradient(90deg, transparent, var(--cyan), var(--gold), transparent);
box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 80%, transparent);
animation: scanline 5.5s linear infinite; }
@keyframes scanline { 0% { top: -2px; opacity: 0; } 8% { opacity: 1; } 92% { opacity: 1; } 100% { top: 100%; opacity: 0; } }
.tv-mode .app::before { display: none; }
/* ── 赛博进度条 ── */
#cybar {
position: fixed; top: 0; left: 0; right: 0; height: 3px; z-index: 200;
pointer-events: none; opacity: 0; transition: opacity .18s;
}
#cybar.active { opacity: 1; }
#cybar-track {
position: absolute; top: 0; left: 0; height: 100%; width: 0%;
background: linear-gradient(90deg, var(--gold), var(--cyan));
box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 90%, transparent), 0 0 20px rgba(var(--gold-rgb),.6);
transition: width .28s cubic-bezier(.4,0,.2,1);
}
/* 分段闪烁节点 */
#cybar-segs {
position: absolute; top: 0; left: 0; right: 0; height: 100%;
display: flex; align-items: center;
}
.cybar-seg {
flex: 1; height: 100%; position: relative;
}
.cybar-seg::after {
content: ""; position: absolute; right: 0; top: -1px; bottom: -1px; width: 1px;
background: color-mix(in srgb, var(--cyan) 30%, transparent);
}
/* 进度条顶部方块光标 */
#cybar-cursor {
position: absolute; top: -2px; height: 7px; width: 4px;
background: #fff;
box-shadow: 0 0 8px var(--cyan), 0 0 18px rgba(var(--gold-rgb),.8);
transform: translateX(-2px);
transition: left .28s cubic-bezier(.4,0,.2,1);
animation: cybarCursorBlink .55s step-end infinite;
}
@keyframes cybarCursorBlink { 0%, 60% { opacity: 1; } 61%, 100% { opacity: .3; } }
/* 进度条右侧文字标签 */
#cybar-label {
position: absolute; right: 8px; top: 5px;
font-family: var(--mono); font-size: 8px; letter-spacing: 1.5px;
color: color-mix(in srgb, var(--cyan) 70%, transparent);
text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 80%, transparent);
white-space: nowrap; pointer-events: none; opacity: 0;
transition: opacity .18s;
}
#cybar.active #cybar-label { opacity: 1; }
/* 完成时闪烁消失 */
@keyframes cybarDone {
0% { opacity: 1; filter: brightness(1); }
30% { opacity: 1; filter: brightness(2.5); }
55% { opacity: .6; filter: brightness(1.5); }
80% { opacity: .3; }
100% { opacity: 0; }
}
#cybar.done { animation: cybarDone .55s var(--ease) forwards; }
/* ── Typewriter 光标 ── */
.tw-cursor {
display: inline-block; width: 1ch; color: var(--cyan);
animation: cursor-blink 0.9s step-end infinite;
margin-left: 1px;
}
/* ── 数字翻牌 ── */
@keyframes numRoll {
0% { transform: translateY(-80%); opacity: 0; filter: blur(3px); }
60% { filter: blur(0); }
100% { transform: translateY(0); opacity: 1; filter: blur(0); }
}
.num-roll { display: inline-block; animation: numRoll .42s var(--ease) both; }
/* ── 文字乱码还原 ── */
@keyframes glitchReveal {
0% { opacity: 0; }
15% { opacity: 1; }
100% { opacity: 1; }
}
.scramble-target { display: inline; }
.wrap { position: relative; z-index: 1; width: 100%; max-width: var(--maxw); margin: 0 auto; padding: 0 var(--gut); }
/* ---------------- 顶栏 ---------------- */
.topbar { position: sticky; top: 0; z-index: 20; padding-top: calc(var(--safe-top) + 12px); padding-bottom: 12px; background: linear-gradient(to bottom, rgba(4,2,12,.97), rgba(4,2,12,.7) 72%, rgba(4,2,12,0)); backdrop-filter: blur(14px); transition: padding .25s var(--ease), background .25s var(--ease), box-shadow .25s var(--ease); }
.topbar::before { content: ""; display: none; }
/* 实时状态文字 — 由 JS 驱动 */
/* 状态栏移到首页搜索框上方 */
.home-status-bar { display: flex; align-items: center; gap: 10px; margin: 14px 0 0; padding: 6px 12px; font-family: var(--mono); font-size: 9px; letter-spacing: 2px; color: color-mix(in srgb, var(--cyan) 50%, transparent); border-top: 1px solid color-mix(in srgb, var(--cyan) 12%, transparent); border-bottom: 1px solid color-mix(in srgb, var(--cyan) 8%, transparent); background: color-mix(in srgb, var(--cyan) 3%, transparent); position: relative; overflow: hidden; flex-wrap: nowrap; height: 26px; }
.home-status-bar::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px; background: linear-gradient(to bottom, var(--cyan), var(--gold)); box-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 60%, transparent); }
.home-status-bar::after { content: ""; position: absolute; right: 0; top: 0; bottom: 0; width: 2px; background: linear-gradient(to bottom, var(--gold), var(--cyan)); box-shadow: 0 0 6px rgba(var(--gold-rgb),.6); }
.home-status-indicator { width: 6px; height: 6px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px var(--green); flex-shrink: 0; animation: statusPulse 1.8s ease-in-out infinite; }
@keyframes statusPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
.home-status-text { flex: 1 1 auto; text-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 60%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; font-variant-numeric: tabular-nums; }
.home-status-right { flex-shrink: 0; color: color-mix(in srgb, var(--gold) 40%, transparent); text-shadow: 0 0 8px rgba(var(--gold-rgb),.4); white-space: nowrap; }
.topbar::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; background: linear-gradient(90deg, transparent 2%, var(--cyan) 20%, var(--gold) 80%, transparent 98%); box-shadow: 0 0 10px rgba(var(--gold-rgb),.5), 0 0 3px color-mix(in srgb, var(--cyan) 80%, transparent); opacity: 1; }
/* 向下滚动后头部收缩、玻璃感更强(更省空间、更聚焦) */
.topbar.shrink { padding-top: calc(var(--safe-top) + 6px); padding-bottom: 8px; background: linear-gradient(to bottom, rgba(6,7,9,.98), rgba(6,7,9,.9)); box-shadow: 0 6px 20px rgba(0,0,0,.45); }
.topbar.shrink .brand .logo { width: 28px; height: 28px; font-size: 16px; }
.topbar.shrink .searchbox { height: 38px; }
.topbar .wrap { display: flex; align-items: center; }
.brand { display: flex; align-items: center; flex: 0 1 auto; min-width: 0; margin-right: 16px; text-decoration: none; overflow: visible; }
.brand .logo { cursor: pointer; width: 34px; height: 34px; border-radius: 0; background: transparent; border: 1px solid rgba(var(--gold-rgb),.7); box-shadow: 0 0 8px rgba(var(--gold-rgb),.4), 0 0 20px rgba(var(--gold-rgb),.15), inset 0 0 8px rgba(var(--gold-rgb),.08); display: flex; align-items: center; justify-content: center; color: var(--gold); font-family: var(--mono); font-weight: 900; font-size: 16px; margin-right: 10px; position: relative; transition: width .25s var(--ease), height .25s var(--ease), font-size .25s var(--ease), transform .2s var(--ease); animation: logoPulse 2.6s ease-in-out infinite; }
.brand .logo::before { content: ""; position: absolute; top: -3px; left: -3px; width: 8px; height: 8px; border-top: 1px solid var(--cyan); border-left: 1px solid var(--cyan); }
.brand .logo::after { content: ""; position: absolute; bottom: -3px; right: -3px; width: 8px; height: 8px; border-bottom: 1px solid var(--cyan); border-right: 1px solid var(--cyan); }
@keyframes logoPulse {
0%, 100% { box-shadow: 0 0 8px rgba(var(--gold-rgb),.4), 0 0 20px rgba(var(--gold-rgb),.15), inset 0 0 8px rgba(var(--gold-rgb),.08); }
50% { box-shadow: 0 0 14px rgba(var(--gold-rgb),.8), 0 0 32px rgba(var(--gold-rgb),.3), inset 0 0 12px rgba(var(--gold-rgb),.12); }
}
.tv-mode .brand .logo { animation: none; }
.brand .logo:active { transform: scale(.88) rotate(-12deg); }
.brand b { font-family: "Audiowide", var(--display); font-size: 21px; font-weight: 400; line-height: 1.5; letter-spacing: 1.5px; color: var(--ink); white-space: nowrap; overflow: visible; text-transform: uppercase;
text-shadow: 1.5px 0 0 color-mix(in srgb, var(--cyan) 85%, transparent), -1.5px 0 0 color-mix(in srgb, var(--gold) 85%, transparent), 0 0 14px color-mix(in srgb, var(--cyan) 50%, transparent), 0 0 26px rgba(var(--gold-rgb),.35); }
.searchbox { position: relative; flex: 1 1 auto; min-width: 0; display: flex; align-items: center; height: 42px; padding: 0 4px 0 16px; background: var(--panel-strong); border: 1px solid color-mix(in srgb, var(--cyan) 30%, transparent); border-radius: 4px; transition: border-color .2s var(--ease), background .2s var(--ease), height .25s var(--ease), box-shadow .2s var(--ease); box-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 12%, transparent); }
.searchbox:focus-within { border-color: var(--cyan); background: var(--panel-strong); box-shadow: 0 0 0 1px color-mix(in srgb, var(--cyan) 40%, transparent), 0 0 10px color-mix(in srgb, var(--cyan) 30%, transparent); }
.searchbox svg { width: 17px; height: 17px; flex: 0 0 auto; fill: var(--cyan); }
.searchbox input { flex: 1 1 auto; min-width: 0; height: 100%; border: 0; outline: 0; background: transparent; color: var(--ink); font-family: var(--mono); font-size: 14px; padding: 0 10px; }
.searchbox input::placeholder { color: color-mix(in srgb, var(--cyan) 50%, transparent); }
.searchbox .go { flex: 0 0 auto; height: 32px; padding: 0 16px; border: 1px solid var(--gold); border-radius: 2px; background: transparent; color: var(--gold); font-family: var(--mono); font-weight: 800; font-size: 11px; letter-spacing: 2.5px; transition: all .15s var(--ease); text-transform: uppercase; position: relative; overflow: hidden; }
.searchbox .go::before { content: ""; position: absolute; inset: 0; background: var(--gold); opacity: 0; transition: opacity .15s; }
.searchbox .go:hover { color: var(--on-gold); box-shadow: 0 0 16px rgba(var(--gold-rgb),.5); }
.searchbox .go:hover::before { opacity: 1; }
.searchbox .go:active { transform: scale(.96); }
/* 搜索输入联想下拉 */
.suggest { position: absolute; top: calc(100% + 8px); left: 0; right: 0; z-index: 40; background: var(--panel-strong); border: 1px solid var(--line-strong); border-radius: 14px; box-shadow: 0 16px 44px rgba(0,0,0,.55); max-height: 62vh; overflow-y: auto; -webkit-overflow-scrolling: touch; display: none; }
.suggest.show { display: block; }
.suggest .sg { display: flex; align-items: center; gap: 11px; padding: 8px 12px; border-bottom: 1px solid var(--line-soft); }
.no-layout-gap .suggest .sg > * { margin-right: 11px; }
.suggest .sg:last-child { border-bottom: 0; }
.suggest .sg:active, .suggest .sg.focus { background: rgba(255,255,255,.07); }
.suggest .sg .pp { flex: 0 0 auto; width: 38px; height: 54px; border-radius: 6px; overflow: hidden; background: #15161c; }
.suggest .sg .pp img { width: 100%; height: 100%; object-fit: cover; display: block; }
.suggest .sg .pp .ph { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 9px; color: rgba(255,255,255,.7); padding: 3px; text-align: center; line-height: 1.1; }
.suggest .sg .tx { flex: 1 1 auto; min-width: 0; }
.suggest .sg .tt { font-size: 14px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.suggest .sg .mt { font-size: 11.5px; color: var(--ink-faint); margin-top: 3px; }
.suggest .sg.kwrow .kwic { display: flex; align-items: center; justify-content: center; font-size: 17px; background: transparent; }
.suggest .sg.kwrow .tt { color: var(--ink-soft); }
/* ---------------- 公告条 ---------------- */
.notice { display: flex; align-items: center; gap: 10px; margin: 14px 0 2px; padding: 10px 12px; background: color-mix(in srgb, var(--gold) 4%, transparent); border: 1px solid color-mix(in srgb, var(--gold) 25%, transparent); border-left: 3px solid var(--gold); border-radius: 0; color: var(--ink-soft); font-size: 12px; font-family: var(--mono); line-height: 1.5; letter-spacing: .3px; position: relative; }
.notice::before { content: "// NOTICE"; position: absolute; top: -9px; left: 12px; background: var(--bg-fallback); padding: 0 6px; font-size: 9px; color: color-mix(in srgb, var(--gold) 50%, transparent); letter-spacing: 2px; }
.no-layout-gap .notice > * { margin-right: 10px; }
.notice .ic { flex: 0 0 auto; width: 18px; height: 18px; border-radius: 0; background: transparent; border: 1px solid rgba(var(--gold-rgb),.5); color: var(--gold); font-weight: 900; font-size: 11px; font-family: var(--mono); display: flex; align-items: center; justify-content: center; }
.notice .x { margin-left: auto; flex: 0 0 auto; background: transparent; border: 1px solid rgba(var(--gold-rgb),.2); border-radius: 2px; color: rgba(var(--gold-rgb),.5); font-size: 15px; line-height: 1; padding: 1px 6px; font-family: var(--mono); transition: color .2s, border-color .2s, background .2s; }
.notice .x:hover { color: var(--gold); border-color: rgba(var(--gold-rgb),.5); background: rgba(var(--gold-rgb),.06); }
.notice .x:active { color: var(--ink); }
/* ---------------- 分类导航 ---------------- */
.nav { display: flex; gap: 8px; overflow-x: auto; overflow-y: visible; padding: 18px 4px 10px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.nav::-webkit-scrollbar { display: none; }
.no-layout-gap .nav > * { margin-right: 8px; }
.navtab { flex: 0 0 auto; border: 0; border-bottom: 2px solid color-mix(in srgb, var(--cyan) 12%, transparent); background: transparent; color: color-mix(in srgb, var(--cyan) 40%, transparent); height: 35px; padding: 0 16px; border-radius: 0; font-size: 11px; font-family: var(--mono); font-weight: 700; letter-spacing: 2px; text-transform: uppercase; white-space: nowrap; transition: color .2s, border-color .2s, box-shadow .2s; position: relative; }
.navtab.on { color: var(--cyan); border-bottom-color: var(--cyan); text-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 80%, transparent), 0 0 20px color-mix(in srgb, var(--cyan) 40%, transparent); box-shadow: 0 4px 12px color-mix(in srgb, var(--cyan) 20%, transparent); }
.navtab:focus { outline: 0; }
.navtab.focus { box-shadow: 0 0 0 1px var(--cyan); }
/* ---------------- 区块标题 ---------------- */
.section-head { display: flex; align-items: center; justify-content: space-between; margin: 20px 0 12px; }
.section-head h2 { position: relative; margin: 0; padding-left: 16px; font-size: 14px; font-family: var(--mono); font-weight: 700; letter-spacing: 2.5px; text-transform: uppercase; color: var(--cyan); text-shadow: 0 0 10px color-mix(in srgb, var(--cyan) 50%, transparent), 0 0 24px color-mix(in srgb, var(--cyan) 20%, transparent); }
.section-head h2::before { content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 8px; height: 2px; background: var(--cyan); box-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 80%, transparent); }
.section-head h2::after { content: ""; position: absolute; left: 2px; top: 50%; transform: translateY(-50%) translateY(-4px); width: 2px; height: 8px; background: var(--cyan); box-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 80%, transparent); }
/* ---------------- 海报网格 ----------------
直接照搬 emby 文档的方案:窄屏固定 3 列;≥720px 起换成 auto-fill + minmax
按目标卡宽(132px/156px)自动算列数,列数随宽度平滑变化,
横屏只是"宽度变大",会被同一套规则自动接住,不需要再单独写 orientation 断点 */
.grid { display: block; }
@supports (display: grid) {
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gut); }
@media (min-width: 720px) {
.grid { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); }
}
@media (min-width: 1180px) {
.grid { grid-template-columns: repeat(auto-fill, minmax(156px, 1fr)); }
}
}
/* 无 flex/grid gap 支持时的兜底(极少数老内核才会触发):固定 3 列 */
.no-layout-gap .grid { display: flex; flex-wrap: wrap; gap: 0; margin: 0 calc(var(--gut) / -2); font-size: 0; }
.no-layout-gap .grid > .card { display: block; width: calc(33.3% - var(--gut)); margin: 0 calc(var(--gut) / 2) var(--gut); font-size: 15px; }
/* 分类切换动效:内容按方向滑入 + 淡入(fwd 从右进,back 从左进) */
@keyframes gridSwapFwd { from { opacity: 0; transform: translateX(28px); } to { opacity: 1; transform: translateX(0); } }
@keyframes gridSwapBack { from { opacity: 0; transform: translateX(-28px); } to { opacity: 1; transform: translateX(0); } }
.grid.gswap-fwd { animation: gridSwapFwd .34s var(--ease); }
.grid.gswap-back { animation: gridSwapBack .34s var(--ease); }
/* 卡片首次/筛选/续载时逐个淡入(交错),动画结束由 JS 摘掉 .cin 以恢复按压反馈 */
@keyframes cardIn { from { opacity: 0; transform: translateY(8px); clip-path: inset(0 0 100% 0); } 40% { clip-path: inset(0 0 0% 0); } to { opacity: 1; transform: none; clip-path: inset(0 0 0% 0); } }
.card.cin { animation: cardIn .38s var(--ease) both; }
.tv-mode .card.cin { animation: none; }
.tv-mode .grid.gswap-fwd, .tv-mode .grid.gswap-back { animation: none; }
.card { position: relative; text-align: left; padding: 0; background: rgba(8,10,18,.85); border: 1px solid color-mix(in srgb, var(--cyan) 15%, transparent); border-radius: 0; overflow: hidden; transition: transform .16s var(--ease), box-shadow .2s var(--ease), border-color .2s; }
.card::before { content: ""; position: absolute; top: -1px; left: -1px; width: 12px; height: 12px; border-top: 2px solid color-mix(in srgb, var(--cyan) 60%, transparent); border-left: 2px solid color-mix(in srgb, var(--cyan) 60%, transparent); z-index: 3; pointer-events: none; }
.card::after { content: ""; position: absolute; bottom: -1px; right: -1px; width: 12px; height: 12px; border-bottom: 2px solid rgba(var(--gold-rgb),.4); border-right: 2px solid rgba(var(--gold-rgb),.4); z-index: 3; pointer-events: none; }
.card:hover { border-color: color-mix(in srgb, var(--cyan) 35%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--cyan) 12%, transparent), 0 4px 24px rgba(0,0,0,.5); }
.card:hover::before { border-color: var(--cyan); }
.card:hover::after { border-color: var(--gold); }
.card:active { transform: scale(.975); border-color: var(--cyan); box-shadow: 0 0 24px color-mix(in srgb, var(--cyan) 40%, transparent); }
.card .poster { position: relative; width: 100%; background: #0d0f16; background-image: radial-gradient(85% 65% at 26% -8%, rgba(var(--gold-rgb), .08), rgba(var(--gold-rgb), 0) 60%), linear-gradient(150deg, #1a1b24 0%, #0e0f16 55%, #080910 100%); aspect-ratio: 2 / 3; }
.card .poster::before { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; inset: 0;
background-image: repeating-linear-gradient(0deg, color-mix(in srgb, var(--cyan) 2%, transparent) 0px, color-mix(in srgb, var(--cyan) 2%, transparent) 1px, transparent 1px, transparent 3px);
pointer-events: none; z-index: 1; opacity: 0; transition: opacity .2s; }
.card:hover .poster::before { opacity: 1; }
.card .poster::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; top: auto; height: 50%; z-index: 2; background: linear-gradient(to top, rgba(4,5,10,.85) 0%, rgba(4,5,10,.3) 50%, rgba(4,5,10,0) 100%); pointer-events: none; }
.no-aspect-ratio .card .poster { height: 0; padding-top: 150%; }
.card .poster .ph, .card .poster img { position: absolute; top: 0; left: 0; right: 0; bottom: 0; inset: 0; width: 100%; height: 100%; }
.card .poster img { object-fit: cover; }
.card .poster .ph { display: flex; align-items: center; justify-content: center; text-align: center; padding: 10px; color: rgba(255,255,255,.85); font-weight: 800; font-size: 14px; line-height: 1.3; text-shadow: 0 1px 4px rgba(0,0,0,.5); font-family: var(--mono); }
.qtag { position: absolute; z-index: 2; top: 7px; left: 7px; font-size: 9px; font-family: var(--mono); font-weight: 900; letter-spacing: 1.5px; padding: 2px 6px; border-radius: 0; color: var(--on-gold); background: var(--gold); box-shadow: 0 0 10px rgba(var(--gold-rgb),.7); text-transform: uppercase; }
.qtag.q4k { background: transparent; color: var(--cyan); border: 1px solid var(--cyan); box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 50%, transparent), inset 0 0 4px color-mix(in srgb, var(--cyan) 10%, transparent); }
.qtag.qbd { background: transparent; color: var(--gold); border: 1px solid var(--gold); box-shadow: 0 0 8px rgba(var(--gold-rgb),.5); }
.qtag.qweb { background: transparent; color: #8fb6c9; border: 1px solid rgba(143,182,201,.4); box-shadow: none; }
.remark { position: absolute; z-index: 2; right: 7px; bottom: 7px; font-size: 9px; font-family: var(--mono); font-weight: 700; letter-spacing: 1px; color: rgba(255,255,255,.7); background: rgba(4,6,10,.8); border: 1px solid rgba(255,255,255,.1); padding: 2px 6px; border-radius: 0; }
.yeartag { position: absolute; z-index: 2; left: 7px; bottom: 7px; font-size: 9px; font-family: var(--mono); font-weight: 700; color: color-mix(in srgb, var(--cyan) 70%, transparent); background: color-mix(in srgb, var(--cyan) 6%, transparent); border: 1px solid color-mix(in srgb, var(--cyan) 20%, transparent); padding: 2px 6px; border-radius: 0; }
.card .meta { padding: 5px 9px 6px; background: color-mix(in srgb, var(--cyan) 1%, transparent); position: relative; overflow: hidden; }
.card .meta::before { content: ""; position: absolute; inset: 0; background: repeating-linear-gradient(0deg, color-mix(in srgb, var(--cyan) 4%, transparent) 0px, color-mix(in srgb, var(--cyan) 4%, transparent) 1px, transparent 1px, transparent 3px); pointer-events: none; z-index: 0; }
.card .meta .t { font-size: 13px; font-weight: 700; font-family: var(--mono); color: var(--ink); line-height: 18px; height: 18px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; transition: color .2s, text-shadow .2s; letter-spacing: .5px; position: relative; z-index: 1; text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 55%, transparent), 0 0 14px color-mix(in srgb, var(--cyan) 25%, transparent); }
.card .meta .sub { display: flex; align-items: center; gap: 6px; margin-top: 5px; height: 17px; white-space: nowrap; overflow: hidden; position: relative; z-index: 1; }
.no-layout-gap .card .meta .sub > * { margin-right: 6px; }
.card .meta .sub .yr { font-size: 11px; font-family: var(--mono); color: color-mix(in srgb, var(--cyan) 35%, transparent); flex: 0 0 auto; }
.rate { display: inline-flex; align-items: center; height: 15px; line-height: 1; white-space: nowrap; font-family: var(--mono); font-size: 9px; font-weight: 800; border-radius: 0; padding: 0 4px; flex: 0 0 auto; letter-spacing: .4px; border: 1px solid; }
.rate.im { background: transparent; color: var(--gold); border-color: rgba(var(--gold-rgb),.35); text-shadow: 0 0 6px rgba(var(--gold-rgb),.5); }
.rate.db { background: transparent; color: var(--green); border-color: rgba(57,255,143,.3); text-shadow: 0 0 6px rgba(57,255,143,.4); }
.card:focus { outline: 0; }
.card.focus { box-shadow: 0 0 0 1px var(--cyan), 0 0 20px color-mix(in srgb, var(--cyan) 20%, transparent); border-color: transparent; transform: translateY(-2px); }
.card.focus .meta .t { color: var(--cyan); text-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 50%, transparent); }
/* 品牌副标(窄屏隐藏) */
/* ---------------- 筛选器 ---------------- */
.filters { margin: 4px 0 14px; overflow: hidden; }
.filters.collapsed { display: none; }
.filterrow { display: flex; align-items: center; gap: 8px; overflow-x: auto; padding: 5px 0; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.filterrow::-webkit-scrollbar { display: none; }
.no-layout-gap .filterrow > * { margin-right: 8px; }
.filterrow .flabel { flex: 0 0 auto; font-size: 12px; color: var(--ink-faint); font-weight: 700; width: 40px; }
.fpill { flex: 0 0 auto; height: 28px; padding: 0 12px; border-radius: 2px; border: 1px solid color-mix(in srgb, var(--cyan) 15%, transparent); background: color-mix(in srgb, var(--cyan) 3%, transparent); color: color-mix(in srgb, var(--cyan) 45%, transparent); font-size: 11px; font-family: var(--mono); font-weight: 700; letter-spacing: 1px; text-transform: uppercase; white-space: nowrap; transition: color .2s, background .2s, border-color .2s, box-shadow .2s; }
.fpill.on { background: color-mix(in srgb, var(--cyan) 10%, transparent); border-color: color-mix(in srgb, var(--cyan) 50%, transparent); color: var(--cyan); box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 20%, transparent), inset 0 0 8px color-mix(in srgb, var(--cyan) 5%, transparent); text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 60%, transparent); }
.fpill:focus { outline: 0; }
.fpill.focus { box-shadow: 0 0 0 1px var(--gold); }
.ftools { display: flex; align-items: center; justify-content: space-between; margin-top: 8px; }
.ftools .res { font-size: 12px; color: var(--ink-faint); }
.fclear { font-size: 11px; font-family: var(--mono); letter-spacing: 1.5px; text-transform: uppercase; color: color-mix(in srgb, var(--gold) 50%, transparent); background: transparent; border: 1px solid color-mix(in srgb, var(--gold) 20%, transparent); border-radius: 2px; height: 26px; padding: 0 12px; transition: color .2s, border-color .2s, box-shadow .2s; }
.fclear:hover { color: var(--gold); border-color: rgba(var(--gold-rgb),.4); box-shadow: 0 0 8px rgba(var(--gold-rgb),.15); }
.fclear:focus { outline: 0; } .fclear.focus { box-shadow: 0 0 0 1px var(--gold); }
/* 筛选 收起/展开 按钮(放在区块标题右侧,不占用额外整行高度) */
.ftoggle { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; height: 28px; padding: 0 12px; border-radius: 2px; border: 1px solid color-mix(in srgb, var(--cyan) 20%, transparent); background: color-mix(in srgb, var(--cyan) 3%, transparent); color: color-mix(in srgb, var(--cyan) 50%, transparent); font-size: 11px; font-family: var(--mono); font-weight: 700; letter-spacing: 1.5px; text-transform: uppercase; transition: color .2s, background .2s, border-color .2s, box-shadow .2s; }
.ftoggle:hover { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 40%, transparent); background: color-mix(in srgb, var(--cyan) 6%, transparent); }
.no-layout-gap .ftoggle > * { margin-right: 5px; }
.ftoggle-count { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 2px; background: var(--gold); color: var(--on-gold); font-size: 10px; font-weight: 900; font-family: var(--mono); }
.ftoggle-count.hidden { display: none; }
.ftoggle-chev { width: 14px; height: 14px; flex: 0 0 auto; fill: currentColor; transition: transform .2s var(--ease); transform: rotate(-90deg); }
.ftoggle.open .ftoggle-chev { transform: rotate(0deg); }
.ftoggle:focus { outline: 0; } .ftoggle.focus { box-shadow: 0 0 0 1px var(--gold); }
/* 画质角标(片库 用语:4K / Dolby / HDR / Web */
.qtag.tDolby { background: transparent; color: #ff9d4d; border: 1px solid rgba(255,157,77,.5); box-shadow: 0 0 8px rgba(255,157,77,.3); }
.qtag.tHDR { background: transparent; color: #c9a6ff; border: 1px solid rgba(201,166,255,.4); box-shadow: 0 0 8px rgba(201,166,255,.2); }
.qtag.t4K { background: transparent; color: var(--cyan); border: 1px solid color-mix(in srgb, var(--cyan) 50%, transparent); box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 30%, transparent); }
.qtag.tWeb { background: transparent; color: #8fb6c9; border: 1px solid rgba(143,182,201,.3); }
.qtag.tHD { background: transparent; color: #aaa; border: 1px solid rgba(170,170,170,.25); }
.qtag.tStar { background: transparent; color: var(--gold); border: 1px solid rgba(var(--gold-rgb),.4); text-shadow: 0 0 6px rgba(var(--gold-rgb),.5); }
.rate.st { background: transparent; color: var(--gold); border: 1px solid rgba(var(--gold-rgb),.35); }
/* 卡片中英双标题 */
.card .meta .en { font-size: 11px; line-height: 15px; height: 15px; color: var(--ink-faint); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card .meta .gen { font-size: 11px; line-height: 15px; height: 15px; color: var(--ink-faint); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.loadmore { display: block; width: 100%; max-width: 360px; margin: 18px auto 6px; height: 44px; border: 1px solid color-mix(in srgb, var(--cyan) 25%, transparent); background: transparent; color: color-mix(in srgb, var(--cyan) 60%, transparent); border-radius: 2px; font-size: 11px; font-family: var(--mono); font-weight: 700; letter-spacing: 3px; text-transform: uppercase; transition: all .2s var(--ease); position: relative; overflow: hidden; }
.loadmore::before { content: ""; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--cyan) 6%, transparent), transparent); transition: left .5s var(--ease); }
.loadmore:hover { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 50%, transparent); box-shadow: 0 0 18px color-mix(in srgb, var(--cyan) 15%, transparent), inset 0 0 18px color-mix(in srgb, var(--cyan) 4%, transparent); }
.loadmore:hover::before { left: 100%; }
.loadmore:active { background: color-mix(in srgb, var(--cyan) 5%, transparent); }
.loadmore:focus { outline: 0; } .loadmore.focus { box-shadow: 0 0 0 1px var(--gold); border-color: transparent; }
/* ---------------- 状态 / 骨架 ---------------- */
.state { text-align: center; color: var(--ink-faint); padding: 44px 16px; }
.state b { display: block; color: var(--ink-soft); font-size: 15px; margin-bottom: 6px; }
.state button { margin-top: 12px; background: transparent; border: 1px solid color-mix(in srgb, var(--cyan) 25%, transparent); color: var(--cyan); border-radius: 2px; height: 36px; padding: 0 18px; font-family: var(--mono); font-size: 11px; letter-spacing: 2px; text-transform: uppercase; transition: all .2s; }
.state button:hover { background: color-mix(in srgb, var(--cyan) 6%, transparent); box-shadow: 0 0 12px color-mix(in srgb, var(--cyan) 15%, transparent); }
.skel { border-radius: 10px; background: linear-gradient(100deg, rgba(255,255,255,.03), rgba(255,255,255,.16), rgba(255,255,255,.03)); background-size: 220% 100%; animation: shimmer 1.2s linear infinite; }
.skel.poster-skel { aspect-ratio: 2/3; background-color: #15161c; }
.no-aspect-ratio .skel.poster-skel { height: 0; padding-top: 150%; }
@keyframes shimmer { 0% { background-position: 120% 0; } 100% { background-position: -120% 0; } }
/* ---------------- 详情浮层 ---------------- */
body.detail-active .app { pointer-events: none; }
.sheet { position: fixed; top: 0; left: 0; right: 0; bottom: 0; inset: 0; z-index: 60; overflow-y: auto; -webkit-overflow-scrolling: touch; background: #08080f; transform: translateX(100%); transition: transform .26s cubic-bezier(.4,0,.2,1); will-change: transform; overscroll-behavior: contain; }
.sheet.active { transform: translateX(0); }
html.fm-native .sheet { background: #08080f; }
.sheet { background: #08080f; }
.sheet .hero { position: relative; height: 60vh; min-height: 360px; max-height: 600px; overflow: hidden; }
/* 模糊底:铺满整块作氛围背景(在主图之下),主图始终 cover 铺满裁切、不留白 */
.sheet .hero .blur { position: absolute; inset: 0; background-size: cover; background-position: center; filter: blur(34px) saturate(1.2); transform: scale(1.25); opacity: .5; }
.sheet .hero .bg { position: absolute; top: 0; left: 0; right: 0; bottom: 0; inset: 0; background-size: cover; background-position: center; background-repeat: no-repeat; background-color: #15161c; }
.sheet .hero .bg::after { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; inset: 0;
background:
linear-gradient(to bottom, rgba(11,12,15,0) 0, rgba(11,12,15,0) 40%, rgba(11,12,15,.88) 80%, #0b0c0f 100%),
linear-gradient(180deg, color-mix(in srgb, var(--cyan) 6%, transparent) 0%, transparent 20%),
repeating-linear-gradient(0deg, color-mix(in srgb, var(--cyan) 2%, transparent) 0px, color-mix(in srgb, var(--cyan) 2%, transparent) 1px, transparent 1px, transparent 4px); }
/* 顶部暖金氛围光,与首页 .app::before 同源,让进入详情页时光感连贯不断裂 */
.sheet .hero .heroglow { position: absolute; top: 0; left: 0; right: 0; height: 46%; z-index: 1; pointer-events: none; background: radial-gradient(120% 100% at 50% -16%, rgba(var(--gold-rgb), .22), rgba(var(--gold-rgb), 0) 68%); }
.tv-mode .sheet .hero .heroglow { display: none; }
.sheet .hero .topctl { position: relative; z-index: 3; display: flex; justify-content: flex-end; padding: calc(var(--safe-top) + 14px) 18px 0; }
.iconbtn { width: 40px; height: 40px; border-radius: 2px; border: 1px solid color-mix(in srgb, var(--cyan) 30%, transparent); background: rgba(0,12,16,.7); backdrop-filter: blur(6px); display: flex; align-items: center; justify-content: center; transition: all .15s var(--ease); }
.iconbtn:hover { border-color: var(--cyan); background: color-mix(in srgb, var(--cyan) 8%, transparent); box-shadow: 0 0 12px color-mix(in srgb, var(--cyan) 30%, transparent); }
.iconbtn:active { transform: scale(.92); }
.iconbtn svg { width: 19px; height: 19px; fill: var(--cyan); }
.iconbtn:focus { outline: 0; }
.iconbtn.focus { box-shadow: 0 0 0 1px var(--gold); }
/* 大图底部叠加的标题区 */
.sheet .herofg { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; padding: 0 18px 10px; max-width: var(--maxw); margin: 0 auto; text-align: center; display: flex; flex-direction: column; align-items: center; }
.hero-logo { margin-bottom: 8px; max-width: 92%; }
.hero-logo img { max-height: 120px; max-width: 100%; width: auto; object-fit: contain; filter: drop-shadow(0 2px 16px rgba(0,0,0,.8)) drop-shadow(0 0 32px rgba(0,0,0,.6)); }
.hero-logo.has-logo ~ h1 { display: none !important; }
.sheet .herofg h1 { margin: 0 0 8px; font-size: 32px; font-weight: 900; color: #fff; line-height: 1.12; letter-spacing: 1px; text-shadow: 2px 0 color-mix(in srgb, var(--cyan) 50%, transparent), -2px 0 color-mix(in srgb, var(--gold) 50%, transparent), 0 0 20px rgba(255,255,255,.3); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; text-align: center; }
/* ── h1 glitch 入场 ── */
@keyframes detailH1Glitch {
0% { opacity:0; transform: translateX(0); clip-path: inset(0 0 100% 0); text-shadow: 6px 0 color-mix(in srgb, var(--cyan) 90%, transparent), -6px 0 color-mix(in srgb, var(--gold) 90%, transparent); }
8% { opacity:1; clip-path: inset(60% 0 0 0); transform: translateX(-4px); text-shadow: 6px 0 color-mix(in srgb, var(--cyan) 90%, transparent), -6px 0 color-mix(in srgb, var(--gold) 90%, transparent); }
16% { clip-path: inset(20% 0 40% 0); transform: translateX(3px); text-shadow: 4px 0 color-mix(in srgb, var(--cyan) 70%, transparent), -4px 0 color-mix(in srgb, var(--gold) 70%, transparent); }
24% { clip-path: inset(80% 0 0 0); transform: translateX(-2px); }
32% { clip-path: inset(0 0 0 0); transform: translateX(0); text-shadow: 2px 0 color-mix(in srgb, var(--cyan) 50%, transparent), -2px 0 color-mix(in srgb, var(--gold) 50%, transparent), 0 0 20px rgba(255,255,255,.3); }
100% { clip-path: inset(0 0 0 0); transform: translateX(0); text-shadow: 2px 0 color-mix(in srgb, var(--cyan) 50%, transparent), -2px 0 color-mix(in srgb, var(--gold) 50%, transparent), 0 0 20px rgba(255,255,255,.3); }
}
.sheet .herofg h1.glitch-enter { animation: detailH1Glitch .55s var(--ease) both; }
@media (prefers-reduced-motion: reduce) { .sheet .herofg h1.glitch-enter { animation: none; opacity: 1; } }
.sheet .herofg .en { color: rgba(255,255,255,.6); font-size: 12px; font-family: var(--mono); letter-spacing: 1px; margin-bottom: 12px; text-shadow: 0 1px 6px rgba(0,0,0,.6); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; text-align: center; }
.sheet .herofg .chips { display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; }
.no-layout-gap .sheet .herofg .chips > * { margin: 0 7px 7px 0; }
.sheet .body { position: relative; z-index: 2; padding: 4px 18px 64px; max-width: var(--maxw); margin: 0 auto; background: #0b0c0f; }
/* 详情页网格背景已移除 */
.sheet .body > * { position: relative; z-index: 1; }
.chip { font-size: 10px; color: color-mix(in srgb, var(--cyan) 85%, transparent); background: color-mix(in srgb, var(--cyan) 8%, transparent); border: 1px solid color-mix(in srgb, var(--cyan) 30%, transparent); padding: 2px 8px; border-radius: 3px; backdrop-filter: blur(4px); font-family: var(--mono); letter-spacing: .4px; font-weight: 700; text-transform: uppercase; }
@media (min-width: 720px) { .sheet .hero { height: 60vh; } .sheet .herofg h1 { font-size: 40px; } }
/* 横屏/矮屏(如手机横置):min-height 360 会把大图撑得过高,几乎占满屏;此处压低 */
@media (max-height: 560px) { .sheet .hero { height: 72vh; min-height: 190px; max-height: none; } .sheet .herofg { padding-bottom: 12px; } .sheet .herofg h1 { font-size: 24px; margin-bottom: 5px; } .sheet .herofg .en { margin-bottom: 7px; } }
.tv-mode .sheet .hero { height: 66vh; }
.tv-mode .sheet .herofg h1 { font-size: 44px; }
/* ── 海报轮播 ── */
/* ── hero 轮播背景层 ── */
.hero-bg-blur { position: absolute; top: 0; left: 0; right: 0; bottom: 0; inset: 0; z-index: 0; background-size: cover; background-position: center; filter: blur(34px) saturate(1.2); transform: scale(1.25); opacity: .5; }
.hero-bg { position: absolute; top: 0; left: 0; right: 0; bottom: 0; inset: 0; z-index: 1; background-size: cover; background-position: center; background-repeat: no-repeat; background-color: #15161c; transition: opacity .6s ease; }
.hero-bg::after { content: ""; position: absolute; top: 0; left: 0; right: 0; bottom: 0; inset: 0; z-index: 2;
background: linear-gradient(to bottom, rgba(11,12,15,0) 0, rgba(11,12,15,0) 55%, rgba(11,12,15,.68) 84%, #0b0c0f 100%); }
.hero-bg-next { z-index: 0; opacity: 0; transition: none; }
/* 操作区:播放按钮居中大展示 */
.actions { display: flex; flex-direction: column; align-items: center; gap: 10px; margin: 8px 0 9px; position: relative; }
/* SYSTEM READY 标签改为 inline 不占布局空间 */
.actions::before { content: "SYSTEM READY ▶"; position: absolute; top: 0; left: 50%; transform: translate(-50%, -100%); margin-top: -4px; font-family: var(--mono); font-size: 9px; letter-spacing: 2px; color: color-mix(in srgb, var(--cyan) 30%, transparent); white-space: nowrap; pointer-events: none; }
.no-layout-gap .actions > * { margin-right: 0; }
/* 主播放按钮:宽大居中,赛博卡片样式 */
.actbtn.primary {
width: 160px; height: 42px; flex: none;
border: 1px solid rgba(var(--gold-rgb),.85); border-top-color: rgba(255,255,255,.5); border-radius: 0;
background: linear-gradient(100deg, rgba(40,4,30,.9) 0%, rgba(var(--gold-rgb),.22) 55%, rgba(var(--gold-rgb),.42) 100%);
color: #fff; font-size: 12px; font-weight: 900; font-family: var(--mono);
letter-spacing: 4px; text-transform: uppercase;
display: flex; align-items: center; justify-content: center; gap: 9px;
position: relative; overflow: visible;
clip-path: polygon(14px 0%, 100% 0%, calc(100% - 14px) 100%, 0% 100%);
user-select: none; -webkit-user-select: none;
box-shadow: 0 0 14px rgba(var(--gold-rgb),.55), 0 0 38px rgba(var(--gold-rgb),.35), 0 0 70px rgba(var(--gold-rgb),.18), inset 0 1px 0 rgba(255,255,255,.15), inset 0 0 18px rgba(var(--gold-rgb),.15);
text-shadow: 0 0 14px rgba(var(--gold-rgb),1), 0 0 6px rgba(255,255,255,.8);
transition: all .2s var(--ease);
}
.actbtn.primary::before { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(255,255,255,.12), transparent); transition: left .5s var(--ease); left: -100%; }
.actbtn.primary:hover { box-shadow: 0 0 18px rgba(var(--gold-rgb),.7), 0 0 50px rgba(var(--gold-rgb),.5), 0 0 90px rgba(var(--gold-rgb),.25), inset 0 1px 0 rgba(255,255,255,.2), inset 0 0 24px rgba(var(--gold-rgb),.2); border-color: var(--gold-lite); }
.actbtn.primary:hover::before { left: 100%; }
/* 隐藏旧的 actbtn(非 primary)— 由 JS 用新结构替代,CSS 先预隐藏 */
.actbtn:not(.primary) { display: none; }
/* ── PLAY 开机动效 ── */
@keyframes playPowerOn {
0% { transform: scaleX(.3) scaleY(.6); opacity: 0; border-color: transparent; box-shadow: none; color: transparent; }
25% { transform: scaleX(1) scaleY(.6); opacity: 1; border-color: var(--gold); box-shadow: 0 0 24px rgba(var(--gold-rgb),.7); color: transparent; }
55% { transform: scaleX(1) scaleY(1); border-color: var(--gold); box-shadow: 0 0 32px rgba(var(--gold-rgb),.5); color: transparent; }
75% { color: transparent; }
85% { color: var(--gold); opacity: 1; }
100% { transform: scaleX(1) scaleY(1); opacity: 1; }
}
.actbtn.primary.power-on { animation: playPowerOn .52s var(--ease) both; }
@media (prefers-reduced-motion: reduce) { .actbtn.primary.power-on { animation: none; } }
/* 元数据 */
.metablk { margin: 18px 0 6px; background: color-mix(in srgb, var(--cyan) 3%, transparent); border: 1px solid color-mix(in srgb, var(--cyan) 20%, transparent); border-radius: 4px; padding: 0 16px; position: relative; overflow: hidden; }
.metablk::before { content: "// DATA BLOCK"; position: absolute; top: 8px; right: 12px; font-family: var(--mono); font-size: 10px; color: color-mix(in srgb, var(--cyan) 25%, transparent); letter-spacing: 1px; }
.metablk::after { content: ""; position: absolute; top: 0; left: 0; width: 3px; height: 100%; background: linear-gradient(to bottom, var(--cyan), var(--gold)); box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 50%, transparent); }
.metarow { display: flex; padding: 11px 0; border-bottom: 1px solid color-mix(in srgb, var(--cyan) 8%, transparent); font-size: 13px; position: relative; }
.metarow:last-child { border-bottom: 0; }
.metarow::before { content: ">"; position: absolute; left: -14px; color: color-mix(in srgb, var(--cyan) 30%, transparent); font-family: var(--mono); font-size: 11px; }
.metarow .k { flex: 0 0 62px; color: var(--cyan); font-family: var(--mono); font-weight: 700; letter-spacing: .3px; text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 40%, transparent); }
.metarow .v { flex: 1 1 auto; min-width: 0; color: var(--ink); line-height: 1.55; letter-spacing: .3px; text-shadow: 0 0 10px color-mix(in srgb, var(--cyan) 8%, transparent); }
/* 评分 + 元数据 合并一行 */
.infobar { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px; margin: 13px 0 9px; }
.infobar .drating { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; justify-content: center; }
.infobar .metablk { display: flex; flex-wrap: wrap; gap: 6px; margin: 0; justify-content: center; }
/* 评分标签行 */
.drating { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin: 0; }
.no-layout-gap .drating > * { margin: 0 6px 6px 0; }
/* 评分标签:与 .mtag 统一风格 */
.drating .rtag {
font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: .8px;
padding: 4px 10px; white-space: nowrap; text-transform: uppercase;
border: 1px solid; border-radius: 0;
display: inline-flex; align-items: center; gap: 4px;
line-height: 1.2;
}
.drating .rtag.st {
color: #ffe156; border-color: rgba(255,225,86,.35);
background: rgba(255,225,86,.06);
}
.drating .rtag.im {
color: var(--gold); border-color: rgba(var(--gold-rgb),.35);
background: rgba(var(--gold-rgb),.06);
}
.drating .rtag.db {
color: var(--green); border-color: rgba(57,255,143,.3);
background: rgba(57,255,143,.05);
}
.metatags { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin: 0; }
.no-layout-gap .metatags > * { margin: 0 8px 8px 0; }
.mtag { font-size: 11px; font-weight: 700; font-family: var(--mono); letter-spacing: .8px; text-transform: uppercase; color: color-mix(in srgb, var(--cyan) 50%, transparent); background: color-mix(in srgb, var(--cyan) 4%, transparent); border: 1px solid color-mix(in srgb, var(--cyan) 15%, transparent); padding: 4px 10px; border-radius: 0; white-space: nowrap; line-height: 1.2; }
.mtag .ml { color: color-mix(in srgb, var(--cyan) 30%, transparent); font-weight: 700; margin-right: 5px; }
.mtag.key { color: var(--gold); background: rgba(var(--gold-rgb),.06); border-color: rgba(var(--gold-rgb),.3); text-shadow: 0 0 6px rgba(var(--gold-rgb),.3); }
.blk-title { position: relative; padding-left: 12px; font-size: 16px; font-weight: 800; margin: 24px 0 12px; text-shadow: 0 1px 3px rgba(0,0,0,.5); font-family: var(--mono); letter-spacing: 1.5px; text-transform: uppercase; }
.blk-title::before { content: ""; position: absolute; left: 0; top: 2px; bottom: 2px; width: 4px; border-radius: 3px; background: linear-gradient(to bottom, var(--gold-lite), var(--gold-deep)); box-shadow: 0 0 8px rgba(var(--gold-rgb),.6); }
.blk-title::after { content: " _"; color: var(--cyan); opacity: .7; animation: cursor-blink 1.2s step-end infinite; }
@keyframes cursor-blink { 0%, 100% { opacity: .7; } 50% { opacity: 0; } }
@keyframes blkGlitch {
0% { text-shadow: 0 1px 3px rgba(0,0,0,.5); transform: translateX(0); clip-path: none; }
10% { text-shadow: 3px 0 color-mix(in srgb, var(--cyan) 90%, transparent), -3px 0 rgba(var(--gold-rgb),.9); transform: translateX(-3px); clip-path: inset(20% 0 50% 0); }
20% { text-shadow: -3px 0 color-mix(in srgb, var(--cyan) 90%, transparent), 3px 0 rgba(var(--gold-rgb),.9); transform: translateX(3px); clip-path: inset(55% 0 10% 0); }
30% { text-shadow: 2px 0 color-mix(in srgb, var(--cyan) 70%, transparent), -2px 0 rgba(var(--gold-rgb),.7); transform: translateX(-1px); clip-path: inset(0 0 0 0); }
40% { text-shadow: 0 1px 3px rgba(0,0,0,.5); transform: translateX(0); clip-path: none; }
100% { text-shadow: 0 1px 3px rgba(0,0,0,.5); transform: translateX(0); clip-path: none; }
}
.blk-title.glitch-fire { animation: blkGlitch .32s steps(1) both; }
@media (prefers-reduced-motion: reduce) { .blk-title.glitch-fire { animation: none; } }
.desc-box { position: relative; }
.desc { color: rgba(200,220,240,.75); font-size: 13px; line-height: 1.7; background: rgba(4,8,18,.85); border: 1px solid color-mix(in srgb, var(--cyan) 22%, transparent); border-radius: 0; padding: 12px 14px; position: relative; font-family: var(--mono); letter-spacing: .4px; box-shadow: inset 0 0 24px color-mix(in srgb, var(--cyan) 3%, transparent); }
.desc::before { content: "// SYNOPSIS"; position: absolute; top: -9px; left: 12px; background: #0b0c0f; padding: 0 6px; font-size: 9px; color: color-mix(in srgb, var(--cyan) 40%, transparent); letter-spacing: 1.5px; }
.desc::after { content: ""; position: absolute; top: 0; right: 0; width: 32px; height: 32px; background: linear-gradient(225deg, color-mix(in srgb, var(--cyan) 6%, transparent) 0%, transparent 60%); pointer-events: none; }
.desc.clamp { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.desc.clamp.open { display: block; -webkit-line-clamp: unset; overflow: visible; }
.no-css-functions .desc.clamp { max-height: 64px; overflow: hidden; }
.no-css-functions .desc.clamp.open { max-height: none; }
.desc-toggle { display: block; margin: 6px auto 0; background: transparent; border: 0; color: color-mix(in srgb, var(--cyan) 55%, transparent); font-family: var(--mono); font-size: 10.5px; letter-spacing: 1.5px; text-transform: uppercase; padding: 2px 8px; }
.desc-toggle:hover { color: var(--cyan); }
.desc-toggle.hidden { display: none; }
/* 剧照 */
.stills { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.stills::-webkit-scrollbar { display: none; }
.no-layout-gap .stills > * { margin-right: 10px; }
.stills .s { flex: 0 0 auto; width: 230px; aspect-ratio: 16/9; border-radius: 0; overflow: hidden; background: #0d0f16; border: 1px solid color-mix(in srgb, var(--cyan) 12%, transparent); transition: border-color .2s, box-shadow .2s; }
.stills .s:hover { border-color: color-mix(in srgb, var(--cyan) 35%, transparent); box-shadow: 0 0 14px color-mix(in srgb, var(--cyan) 12%, transparent); }
.stills .s img { width: 100%; height: 100%; object-fit: cover; }
/* 演员阵容:横向滚动条带,每个 chip = 圆形霓虹头像(左)+ 姓名/角色(右) */
.castrow { display: flex; align-items: flex-start; gap: 14px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.castrow::-webkit-scrollbar { display: none; }
.no-layout-gap .castrow > * { margin-right: 14px; }
.castchip { flex: 0 0 auto; width: 64px; display: flex; flex-direction: column; align-items: center; gap: 7px; cursor: pointer; }
.castchip:active .cav { transform: scale(.92); }
.cav { flex: 0 0 auto; width: 44px; height: 44px; border-radius: 3px; overflow: hidden; background: #0d0f16; border: 1px solid rgba(var(--gold-rgb),.75); box-shadow: 0 0 8px rgba(var(--gold-rgb),.45), 0 0 18px rgba(var(--gold-rgb),.15), inset 0 0 8px rgba(var(--gold-rgb),.08); position: relative; }
.cav::before { content: ""; position: absolute; top: -3px; left: -3px; width: 8px; height: 8px; border-top: 1px solid var(--cyan); border-left: 1px solid var(--cyan); }
.cav::after { content: ""; position: absolute; bottom: -3px; right: -3px; width: 8px; height: 8px; border-bottom: 1px solid var(--cyan); border-right: 1px solid var(--cyan); }
.castchip:hover .cav { box-shadow: 0 0 12px rgba(var(--gold-rgb),.6), 0 0 26px rgba(var(--gold-rgb),.25), inset 0 0 10px rgba(var(--gold-rgb),.1); }
.cav img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cph { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: var(--gold); font-family: var(--mono); font-weight: 900; font-size: 17px; text-shadow: 0 0 8px rgba(var(--gold-rgb),.7); background: rgba(var(--gold-rgb),.06); }
/* 无头像时用人物剪影背景 */
.cav.cav-noimg {
background: rgba(var(--gold-rgb),.06);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 44'%3E%3Ccircle cx='22' cy='16' r='8' fill='rgba(255%2C43%2C214%2C0.22)'/%3E%3Cellipse cx='22' cy='38' rx='13' ry='9' fill='rgba(255%2C43%2C214%2C0.15)'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center;
background-size: 100% 100%;
}
.ctx { width: 100%; min-width: 0; display: flex; flex-direction: column; align-items: center; line-height: 1.3; text-align: center; }
.cname { width: 100%; font-size: 11.5px; font-weight: 700; font-family: var(--mono); color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; letter-spacing: .5px; text-shadow: 0 0 8px rgba(255,255,255,.15); }
.crole { width: 100%; font-size: 10px; color: color-mix(in srgb, var(--cyan) 65%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; letter-spacing: .5px; text-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 40%, transparent); }
.crole.role-dir { color: var(--gold); text-shadow: 0 0 5px rgba(var(--gold-rgb),.4); }
/* 选中演员高亮 */
.castchip.filmo-active .cav { border-color: var(--cyan); box-shadow: 0 0 12px color-mix(in srgb, var(--cyan) 60%, transparent), 0 0 26px color-mix(in srgb, var(--cyan) 25%, transparent); }
/* 演员相关作品:点头像后内联展开在 castrow 下方 */
.cast-filmo-wrap { margin: 8px 0 4px; overflow: hidden; transition: max-height .35s cubic-bezier(.22,.61,.36,1); max-height: 0; }
.cast-filmo-wrap.open { max-height: 600px; }
.cast-filmo-inner { padding: 10px 0 4px; }
.cast-filmo-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid color-mix(in srgb, var(--cyan) 10%, transparent); }
.cast-filmo-name { font-size: 13px; font-weight: 800; font-family: var(--mono); color: var(--ink); letter-spacing: .5px; }
.cast-filmo-close { margin-left: auto; width: 26px; height: 26px; border: 1px solid color-mix(in srgb, var(--cyan) 20%, transparent); background: transparent; color: color-mix(in srgb, var(--cyan) 60%, transparent); font-size: 16px; line-height: 1; border-radius: 2px; display: flex; align-items: center; justify-content: center; cursor: pointer; }
.cast-filmo-close:hover { color: var(--cyan); border-color: var(--cyan); }
.cast-filmo-loading { text-align: center; color: var(--ink-faint); font-family: var(--mono); font-size: 11px; letter-spacing: 2px; padding: 20px 0; }
/* 横向单排滚动,卡片复用首页 .card 同款样式(角标/星标/年份/标题),固定宽度以适配横滚条带
.fg-cell 为通用单元格规则,演员关联作品(filmogrid-inline)和猜你喜欢(reco)共用,保证卡片尺寸/字体完全一致 */
.filmogrid-inline, .reco { display: flex; gap: var(--gut); overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
.filmogrid-inline::-webkit-scrollbar, .reco::-webkit-scrollbar { display: none; }
.fg-cell { flex: 0 0 auto; width: 132px; }
.fg-cell .card { width: 100%; }
/* 资源分组 */
.resgroup { margin-bottom: 18px; }
.resgroup .gh { display: flex; align-items: center; gap: 8px; margin: 0 0 10px; font-size: 13px; font-weight: 800; font-family: var(--mono); letter-spacing: 1px; text-transform: uppercase; color: color-mix(in srgb, var(--cyan) 60%, transparent); }
.resgroup .gh::before { content: "//"; color: color-mix(in srgb, var(--cyan) 30%, transparent); margin-right: 2px; }
.no-layout-gap .resgroup .gh > * { margin-right: 8px; }
.gtag { font-size: 12px; font-weight: 900; letter-spacing: .4px; padding: 3px 10px; border-radius: 6px; color: var(--on-gold); }
.gtag.原盘 { background: linear-gradient(95deg, var(--gold-lite), var(--gold)); }
.gtag.4K, .gtag.中字4K { background: linear-gradient(95deg, #8bd9ff, var(--cyan)); color: #04121f; }
.gtag.1080P { background: var(--slate); color: #14161c; }
.gtag.其它 { background: #6f6a5e; color: #fff; }
.resgroup .gh .cnt { color: var(--ink-faint); font-weight: 600; font-size: 12px; }
/* 资源画质类别:并排标签(原盘 / 4K / 中字4K…),点选切换下方框内容 */
.restabs { display: flex; gap: 8px; overflow-x: auto; padding: 2px 0 12px; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
.restabs::-webkit-scrollbar { display: none; }
.no-layout-gap .restabs > * { margin-right: 8px; }
.restab { flex: 0 0 auto; display: flex; align-items: center; gap: 7px; height: 34px; padding: 0 14px; border-radius: 3px; border: 1px solid color-mix(in srgb, var(--cyan) 15%, transparent); background: color-mix(in srgb, var(--cyan) 3%, transparent); color: var(--ink-soft); font-size: 12px; font-weight: 800; font-family: var(--mono); letter-spacing: .8px; text-transform: uppercase; white-space: nowrap; transition: color .2s, background .2s, border-color .2s, box-shadow .2s; }
.no-layout-gap .restab > * { margin-right: 7px; }
.restab .rt-cnt { display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 17px; padding: 0 5px; border-radius: 999px; background: rgba(255,255,255,.08); color: var(--ink-faint); font-size: 11px; font-weight: 800; }
.restab:focus { outline: 0; }
.restab.focus { box-shadow: 0 0 0 2px var(--gold); }
/* 选中态:用各画质代表色,呼应卡片角标 */
.restab.on { border-color: transparent; }
.restab.on .rt-cnt { background: rgba(0,0,0,.18); }
.restab.on[data-tier="原盘"] { background: linear-gradient(95deg, var(--gold-lite), var(--gold)); color: var(--on-gold); }
.restab.on[data-tier="原盘"] .rt-cnt { color: rgba(42,28,0,.7); }
.restab.on[data-tier="4K"], .restab.on[data-tier="中字4K"] { background: linear-gradient(95deg, #8bd9ff, var(--cyan)); color: #04121f; }
.restab.on[data-tier="4K"] .rt-cnt, .restab.on[data-tier="中字4K"] .rt-cnt { color: rgba(4,18,31,.72); }
.restab.on[data-tier="1080P"] { background: var(--slate); color: #14161c; }
.restab.on[data-tier="1080P"] .rt-cnt { color: rgba(20,22,28,.65); }
.restab.on[data-tier="720P"], .restab.on[data-tier="其它"] { background: #6f6a5e; color: #fff; }
.restab.on[data-tier="720P"] .rt-cnt, .restab.on[data-tier="其它"] .rt-cnt { background: rgba(255,255,255,.2); color: #fff; }
.resbox { display: block; }
.resitem { display: flex; align-items: center; gap: 12px; min-height: 58px; padding: 9px 13px; margin-bottom: 8px; background: rgba(8,10,16,.9); border: 1px solid color-mix(in srgb, var(--cyan) 12%, transparent); border-left: 3px solid var(--cyan); border-radius: 4px; transition: box-shadow .2s, border-color .2s, background .2s; position: relative; overflow: hidden; }
.resitem::before { content: ""; position: absolute; top: 0; left: -100%; right: auto; width: 100%; height: 1px; background: linear-gradient(90deg, transparent, var(--cyan), rgba(var(--gold-rgb),.8), transparent); opacity: 0; transition: opacity .15s; }
.resitem:hover::before { opacity: 1; animation: resitemSweep .45s var(--ease) forwards; }
@keyframes resitemSweep { 0% { left: -100%; opacity: 1; } 100% { left: 100%; opacity: .3; } }
@media (prefers-reduced-motion: reduce) { .resitem:hover::before { animation: none; left: 0; opacity: .7; width: 100%; } }
.resitem::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(90deg, color-mix(in srgb, var(--cyan) 2%, transparent), transparent 30%); pointer-events: none; }
.resitem:hover { background: color-mix(in srgb, var(--cyan) 4%, transparent); border-color: color-mix(in srgb, var(--cyan) 30%, transparent); box-shadow: 0 0 16px color-mix(in srgb, var(--cyan) 10%, transparent), inset 0 0 20px color-mix(in srgb, var(--cyan) 3%, transparent); }
.no-layout-gap .resitem > * { margin-right: 12px; }
.resitem.focus { box-shadow: 0 0 0 2px var(--gold); border-color: transparent; }
.dot { flex: 0 0 auto; width: 7px; height: 7px; border-radius: 2px; background: var(--gray); box-shadow: 0 0 6px rgba(93,111,138,.6); }
.dot.ok { background: var(--good); box-shadow: 0 0 7px rgba(57,255,143,.85), 0 0 14px rgba(57,255,143,.4); animation: dotPulse 2s ease-in-out infinite; }
.dot.bad { background: var(--bad); box-shadow: 0 0 7px rgba(255,56,100,.85); } .dot.locked { background: var(--warn); box-shadow: 0 0 7px rgba(255,225,86,.85); }
.dot.uncertain { background: #6f8aa5; } .dot.unsupported { background: #555048; box-shadow: none; }
@keyframes dotPulse { 0%, 100% { opacity: 1; } 50% { opacity: .5; } }
.resitem .ri { flex: 1 1 auto; min-width: 0; overflow: hidden; }
.resitem .ri .tags { display: flex; flex-wrap: wrap; gap: 5px; }
.no-layout-gap .resitem .ri .tags > * { margin: 0 5px 5px 0; }
/* ── 赛博标签卡片:每类标签独立颜色方块,带斜切角和霓虹边光 ── */
.btag {
font-family: var(--mono); font-size: 10px; font-weight: 800; line-height: 1;
padding: 3px 8px; white-space: nowrap; letter-spacing: 1px; text-transform: uppercase;
position: relative; display: inline-flex; align-items: center;
border-radius: 0;
/* 默认:青色 */
color: var(--cyan); background: color-mix(in srgb, var(--cyan) 7%, transparent);
border: 1px solid color-mix(in srgb, var(--cyan) 30%, transparent);
box-shadow: 0 0 6px color-mix(in srgb, var(--cyan) 12%, transparent), inset 0 0 4px color-mix(in srgb, var(--cyan) 4%, transparent);
clip-path: polygon(4px 0%, 100% 0%, calc(100% - 4px) 100%, 0% 100%);
}
/* 网盘类型:品红/金 */
.btag.disk {
color: var(--gold); background: rgba(var(--gold-rgb),.08);
border-color: rgba(var(--gold-rgb),.5); font-weight: 900;
box-shadow: 0 0 8px rgba(var(--gold-rgb),.2), inset 0 0 6px rgba(var(--gold-rgb),.06);
text-shadow: 0 0 8px rgba(var(--gold-rgb),.5);
clip-path: polygon(4px 0%, 100% 0%, calc(100% - 4px) 100%, 0% 100%);
}
/* 提取码:紫色 */
.btag.pw {
color: #c9a6ff; background: rgba(201,166,255,.07);
border-color: rgba(201,166,255,.4);
box-shadow: 0 0 6px rgba(201,166,255,.15);
clip-path: polygon(4px 0%, 100% 0%, calc(100% - 4px) 100%, 0% 100%);
}
/* 画质类标签独立配色 */
.btag[class*="4K"],.btag:not(.disk):not(.pw)[data-b="4K"] { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 50%, transparent); background: color-mix(in srgb, var(--cyan) 10%, transparent); box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 20%, transparent); }
.btag-4k { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 50%, transparent); background: color-mix(in srgb, var(--cyan) 10%, transparent); box-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 20%, transparent); }
.btag-hdr { color: #c9a6ff; border-color: rgba(201,166,255,.45); background: rgba(201,166,255,.07); box-shadow: 0 0 6px rgba(201,166,255,.15); }
.btag-dv { color: #ff9d4d; border-color: rgba(255,157,77,.45); background: rgba(255,157,77,.07); box-shadow: 0 0 6px rgba(255,157,77,.15); }
.btag-atmos { color: #39ff8f; border-color: rgba(57,255,143,.4); background: rgba(57,255,143,.06); box-shadow: 0 0 6px rgba(57,255,143,.12); }
.btag-cn { color: #ffe156; border-color: rgba(255,225,86,.4); background: rgba(255,225,86,.06); box-shadow: 0 0 5px rgba(255,225,86,.12); }
.btag-1080 { color: #8fb6c9; border-color: rgba(143,182,201,.4); background: rgba(143,182,201,.06); }
.btag-remux { color: var(--gold); border-color: rgba(var(--gold-rgb),.5); background: rgba(var(--gold-rgb),.08); box-shadow: 0 0 6px rgba(var(--gold-rgb),.15); font-weight: 900; }
.btag-web { color: rgba(143,182,201,.7); border-color: rgba(143,182,201,.25); background: rgba(143,182,201,.04); }
.btag-h265 { color: #7af7ff; border-color: rgba(122,247,255,.3); background: rgba(122,247,255,.05); }
.btag-h264 { color: color-mix(in srgb, var(--cyan) 40%, transparent); border-color: color-mix(in srgb, var(--cyan) 18%, transparent); background: color-mix(in srgb, var(--cyan) 3%, transparent); }
.btag-720 { color: #5d6f8a; border-color: rgba(93,111,138,.3); background: rgba(93,111,138,.05); }
.btag-hibit { color: #ff6ad5; border-color: rgba(255,106,213,.45); background: rgba(255,106,213,.07); box-shadow: 0 0 6px rgba(255,106,213,.15); font-weight: 900; }
.resitem .size { flex: 0 0 auto; font-family: var(--mono); font-size: 12px; font-weight: 800; color: var(--cyan); background: color-mix(in srgb, var(--cyan) 6%, transparent); border: 1px solid color-mix(in srgb, var(--cyan) 25%, transparent); padding: 3px 8px; border-radius: 0; white-space: nowrap; letter-spacing: .5px; text-shadow: 0 0 8px color-mix(in srgb, var(--cyan) 50%, transparent); }
.resitem .ops { flex: 0 0 auto; display: flex; gap: 6px; }
.no-layout-gap .resitem .ops > * { margin-right: 6px; }
.resitem .op { font-family: var(--mono); font-size: 11px; font-weight: 800; letter-spacing: 1.5px; text-transform: uppercase; border-radius: 2px; height: 30px; padding: 0 12px; border: 1px solid color-mix(in srgb, var(--cyan) 20%, transparent); background: transparent; color: color-mix(in srgb, var(--cyan) 60%, transparent); transition: all .15s var(--ease); }
.resitem .op:hover { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 50%, transparent); background: color-mix(in srgb, var(--cyan) 6%, transparent); box-shadow: 0 0 10px color-mix(in srgb, var(--cyan) 15%, transparent); }
.resitem .op:active { transform: scale(.95); }
.resitem .op.play {
background: linear-gradient(135deg, rgba(var(--gold-rgb),.14) 0%, rgba(var(--gold-rgb),.04) 100%);
border: 1px solid var(--gold);
color: var(--gold); font-weight: 900; letter-spacing: 2px;
box-shadow: 0 0 12px rgba(var(--gold-rgb),.3), inset 0 0 10px rgba(var(--gold-rgb),.06);
text-shadow: 0 0 10px rgba(var(--gold-rgb),.7);
clip-path: polygon(6px 0%, 100% 0%, calc(100% - 6px) 100%, 0% 100%);
position: relative; overflow: hidden;
}
.resitem .op.play::before {
content: ""; position: absolute; inset: 0;
background: linear-gradient(90deg, transparent, rgba(var(--gold-rgb),.12), transparent);
left: -100%; transition: left .45s var(--ease);
}
.resitem .op.play:hover { background: rgba(var(--gold-rgb),.2); border-color: var(--gold-lite); box-shadow: 0 0 22px rgba(var(--gold-rgb),.5), inset 0 0 16px rgba(var(--gold-rgb),.08); color: var(--gold-lite); }
.resitem .op.play:hover::before { left: 100%; }
.resitem .op.play:active { background: rgba(var(--gold-rgb),.25); }
.resmore { width: 100%; height: 36px; margin: 2px 0 4px; border: 1px dashed color-mix(in srgb, var(--cyan) 20%, transparent); background: transparent; color: color-mix(in srgb, var(--cyan) 40%, transparent); border-radius: 2px; font-family: var(--mono); font-size: 11px; font-weight: 700; letter-spacing: 2px; text-transform: uppercase; transition: color .2s, border-color .2s, box-shadow .2s; }
.resmore:hover { color: var(--cyan); border-color: color-mix(in srgb, var(--cyan) 40%, transparent); box-shadow: 0 0 10px color-mix(in srgb, var(--cyan) 10%, transparent); }
.resmore:active { color: var(--cyan); }
.resmore:focus { outline: 0; }
.resmore.focus { box-shadow: 0 0 0 1px var(--cyan); border-color: transparent; }
.resitem .op:focus { outline: 0; }
.resitem .op.focus { box-shadow: 0 0 0 2px var(--cyan); }
/* 提示条 */
.toast { position: fixed; left: 50%; bottom: calc(var(--safe-bottom) + 26px); transform: translateX(-50%); z-index: 90; background: rgba(2,6,14,.95); border: 1px solid color-mix(in srgb, var(--cyan) 40%, transparent); border-left: 3px solid var(--cyan); color: var(--cyan); font-size: 12px; font-family: var(--mono); letter-spacing: 1px; padding: 10px 18px; border-radius: 0; box-shadow: 0 0 20px color-mix(in srgb, var(--cyan) 20%, transparent), 0 8px 28px rgba(0,0,0,.6); opacity: 0; pointer-events: none; transition: opacity .22s var(--ease); max-width: 80%; }
.toast.show { opacity: 1; }
/* 剧照/海报大图查看器 */
.imgviewer { position: fixed; top: 0; left: 0; right: 0; bottom: 0; inset: 0; z-index: 95; background: rgba(3,2,8,.96); display: none; align-items: center; justify-content: center; padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left); }
.imgviewer.show { display: flex; }
.imgviewer .ivstage { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
.imgviewer img { max-width: 92vw; max-height: 88vh; object-fit: contain; box-shadow: 0 0 40px rgba(0,0,0,.6); }
.imgviewer .ivclose { position: absolute; top: calc(var(--safe-top) + 14px); right: calc(var(--safe-right) + 14px); width: 36px; height: 36px; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--cyan) 40%, transparent); background: rgba(2,6,14,.7); color: var(--ink); display: flex; align-items: center; justify-content: center; z-index: 2; }
.imgviewer .ivclose svg { width: 16px; height: 16px; fill: currentColor; }
.imgviewer .ivnav { position: absolute; top: 50%; transform: translateY(-50%); width: 42px; height: 42px; border-radius: 50%; border: 1px solid color-mix(in srgb, var(--cyan) 30%, transparent); background: rgba(2,6,14,.6); color: var(--ink); display: flex; align-items: center; justify-content: center; z-index: 2; }
.imgviewer .ivnav svg { width: 18px; height: 18px; fill: currentColor; }
.imgviewer .ivprev { left: calc(var(--safe-left) + 10px); }
.imgviewer .ivnext { right: calc(var(--safe-right) + 10px); }
.imgviewer .ivcount { position: absolute; bottom: calc(var(--safe-bottom) + 14px); left: 50%; transform: translateX(-50%); font-family: var(--mono); font-size: 11px; letter-spacing: 1px; color: rgba(255,255,255,.6); z-index: 2; }
.tv-mode .imgviewer .ivnav, .tv-mode .imgviewer .ivclose { display: none; }
.hero { cursor: pointer; }
.stills .s { cursor: pointer; }
/* 回到顶部悬浮按钮:滚动一段后淡入,点按平滑回顶;详情页内隐藏 */
.backtop { position: fixed; right: 16px; bottom: calc(var(--safe-bottom) + 78px); z-index: 88; width: 42px; height: 42px; border-radius: 2px; border: 1px solid color-mix(in srgb, var(--cyan) 35%, transparent); background: rgba(4,8,14,.85); color: var(--cyan); font-size: 18px; font-weight: 800; line-height: 1; font-family: var(--mono); box-shadow: 0 0 12px color-mix(in srgb, var(--cyan) 15%, transparent), inset 0 0 8px color-mix(in srgb, var(--cyan) 3%, transparent); opacity: 0; transform: translateY(12px) scale(.9); pointer-events: none; transition: opacity .24s var(--ease), transform .24s var(--ease); }
.backtop.show { opacity: 1; transform: none; pointer-events: auto; }
.backtop:hover { border-color: var(--cyan); box-shadow: 0 0 20px color-mix(in srgb, var(--cyan) 30%, transparent); }
.backtop:active { transform: scale(.9); }
body.detail-active .backtop { opacity: 0 !important; pointer-events: none !important; }
.tv-mode .backtop { display: none; }
/* 随机一部:常驻右下角,点一下从当前列表随便打开一部 */
.dice { position: fixed; right: 16px; bottom: calc(var(--safe-bottom) + 22px); z-index: 88; width: 42px; height: 42px; border-radius: 2px; border: 1px solid rgba(var(--gold-rgb),.35); background: rgba(8,4,14,.85); font-size: 20px; line-height: 1; box-shadow: 0 0 12px rgba(var(--gold-rgb),.15), inset 0 0 8px rgba(var(--gold-rgb),.03); transition: transform .25s var(--ease), opacity .22s var(--ease), box-shadow .2s; }
.dice:hover { border-color: var(--gold); box-shadow: 0 0 20px rgba(var(--gold-rgb),.3); }
.dice:active { transform: scale(.88) rotate(-20deg); }
body.detail-active .dice { opacity: 0; pointer-events: none; }
.tv-mode .dice { display: none; }
/* 卡片按压反馈:轻微下沉,触感更明确 */
.card:active { transform: scale(.975); }
/* 猜你喜欢:横向单排,卡片尺寸/字体与演员关联作品共用 .fg-cell 规则(见上方 .filmogrid-inline, .reco */
.no-layout-gap .filmogrid-inline > *, .no-layout-gap .reco > * { margin-right: var(--gut); }
/* ---------------- TV 大屏 ---------------- */
.tv-mode { font-size: 17px; }
.tv-mode .wrap { max-width: none; padding: 0 52px; }
.tv-mode .app { padding-top: 10px; padding-bottom: 27px; }
.tv-mode .brand b { font-size: 26px; }
.tv-mode .section-head h2 { font-size: 23px; }
@supports (display: grid) { .tv-mode .grid { grid-template-columns: repeat(6, 1fr); } }
.tv-mode.no-layout-gap .grid > .card { width: calc(16.6% - var(--gut)); }
.tv-mode .card .meta .t { font-size: 18px; }
.tv-mode .searchbox input { font-size: 18px; }
/* TV 关闭动效,优先稳定帧率 */
.tv-mode .card, .tv-mode .actbtn, .tv-mode .op, .tv-mode .navtab, .tv-mode .skel, .tv-mode .iconbtn { transition: none; animation: none; }
.tv-mode .card.focus { transform: none; }
/* 降级:去掉 calc 之外不需要的;no-css-functions 不影响本表(未用 clamp/min/max */
.no-css-functions .app { padding-left: 0; padding-right: 0; }
/* 尊重系统“减少动态效果” */
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; }
.card:active, .actbtn:active, .op:active, .iconbtn:active, .card.focus { transform: none !important; }
}
/* ── 赛博朋克增强 ── */
.card:hover { box-shadow: 0 0 0 1px color-mix(in srgb, var(--cyan) 35%, transparent), 0 0 18px color-mix(in srgb, var(--cyan) 15%, transparent), 0 6px 24px rgba(0,0,0,.5); }
.qtag { text-shadow: 0 0 8px currentColor; }
.blk-title { text-shadow: 0 0 12px color-mix(in srgb, var(--cyan) 60%, transparent), 0 0 28px color-mix(in srgb, var(--cyan) 25%, transparent); }
.navtab:hover { color: var(--cyan); text-shadow: 0 0 10px color-mix(in srgb, var(--cyan) 80%, transparent); border-bottom-color: color-mix(in srgb, var(--cyan) 40%, transparent); }
.notice { box-shadow: inset 4px 0 14px rgba(var(--gold-rgb),.06), 0 0 18px rgba(var(--gold-rgb),.04); }
.fpill.on { box-shadow: 0 0 8px rgba(var(--gold-rgb),.4), inset 0 0 6px rgba(var(--gold-rgb),.08); }
@keyframes skelGlow { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
.skel { background: linear-gradient(90deg, rgba(255,255,255,.04) 25%, rgba(0,240,255,.07) 50%, rgba(255,255,255,.04) 75%) !important; background-size: 200% 100% !important; animation: skelGlow 2.2s linear infinite !important; }
.searchbox:focus-within::after { content: ""; position: absolute; top: -1px; left: 10%; right: 10%; height: 1px; background: var(--cyan); box-shadow: 0 0 8px var(--cyan); pointer-events: none; }
</style></head><body><!-- 赛博进度条 --><div id="cybar"><div id="cybar-segs"></div><div id="cybar-track"></div><div id="cybar-cursor"></div><div id="cybar-label">LOADING</div></div><div class="app" id="app"><header class="topbar"><div class="wrap"><a class="brand" href="#home" id="brand"><span class="logo" id="themeBtn" title="点击切换主题色"></span><b>片库</b></a><div class="searchbox"><svg viewbox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 5 1.49-1.49-5-5zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z" /></svg><input id="q" type="search" inputmode="search" placeholder="" autocomplete="off" spellcheck="false"><button class="go focusable" id="goBtn" data-fk="go">搜索</button><div class="suggest" id="suggest"></div></div></div></header><main class="wrap" id="home"><div class="home-status-bar"><span class="home-status-indicator"></span><span class="home-status-text" id="topbarStatus">NET_LINK::ACTIVE</span><span class="home-status-right">片库</span></div><div class="notice" id="notice"><span class="ic">!</span><span>为方便观看尽量提供网盘资源,没有 4K 的也会提供蓝光原盘版本,保证清晰度。</span><button class="x focusable" id="noticeX" data-fk="noticex" aria-label="关闭">×</button></div><nav class="nav" id="nav"></nav><section><div class="section-head"><h2 id="listTitle">TG 4K Movies</h2><button class="ftoggle focusable" id="ftoggle" data-fk="ftoggle" aria-label="展开或收起筛选"><span>筛选</span><span class="ftoggle-count hidden" id="ftoggleCount"></span><svg class="ftoggle-chev" viewbox="0 0 24 24"><path d="M7 10l5 5 5-5z" /></svg></button></div><div class="filters" id="filters"></div><div class="grid" id="grid"></div><div id="listState"></div></section></main></div><!-- 详情 --><div class="sheet" id="sheet" aria-hidden="true"><div class="hero"><div class="hero-bg-blur" id="heroBgBlur"></div><div class="hero-bg" id="heroBgMain"></div><div class="hero-bg hero-bg-next" id="heroBgNext"></div><div class="heroglow"></div><div class="herofg"><div class="hero-logo hidden" id="dLogo"></div><h1 id="dTitle"></h1><div class="chips" id="dChips"></div></div></div><div class="body"><div class="actions"><button class="actbtn primary focusable" id="actPlay" data-fk="act-play"><svg viewbox="0 0 24 24" style="width:13px;height:13px;fill:currentColor;flex-shrink:0"><path d="M8 5v14l11-7z" /></svg>PLAY ▸</button></div><div class="infobar" id="infoBar"><div class="drating" id="dRating"></div><div class="metablk" id="metablk"></div></div><div class="blk-title">剧情简介</div><div class="desc-box"><div class="desc clamp" id="dDesc"></div><button class="desc-toggle hidden" id="descToggle" type="button">展开</button></div><div id="castWrap" class="hidden"><div class="blk-title">演员阵容</div><div class="castrow" id="cast"></div></div><div class="blk-title" id="resTitle">在线资源</div><div id="resState"></div><div id="resgroups"></div><div id="stillsWrap" class="hidden"><div class="blk-title">影视剧照</div><div class="stills" id="stills"></div></div><div id="recoWrap" class="hidden"><div class="blk-title">猜你喜欢</div><div class="reco" id="reco"></div></div></div></div><div class="toast" id="toast"></div><button class="dice" id="diceBtn" aria-label="随便看看">🎲</button><button class="backtop" id="backTop" aria-label="回到顶部"></button><div class="imgviewer" id="imgViewer" aria-hidden="true"><div class="ivstage"><button class="ivnav ivprev" id="ivPrev" aria-label="上一张"><svg viewbox="0 0 24 24"><path d="M15.41 7.41 14 6l-6 6 6 6 1.41-1.41L10.83 12z" /></svg></button><img id="ivImg" src="" alt=""><button class="ivnav ivnext" id="ivNext" aria-label="下一张"><svg viewbox="0 0 24 24"><path d="M8.59 16.59 10 18l6-6-6-6-1.41 1.41L13.17 12z" /></svg></button><button class="ivclose" id="ivClose" aria-label="关闭"><svg viewbox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 7 19l5.59-5.59L17.59 19 19 17.59 13.41 12z" /></svg></button><div class="ivcount" id="ivCount"></div></div></div><script>
/* ============================================================
片库 WebHome —— 业务代码(ES2017 基线:禁止 ?. ?? ||= &&= ??= 等)
通过 fm SDK 工作:fm.req 取数据、fm.res 加载图片、fm.pan.play 推送播放、
fm.pan.check 检网盘有效性、fm.search 聚合搜索、fm.cache 持久化
============================================================ */
"use strict";
var CONFIG = {
/* 可选:片库 风格目录 JSON 接口。留空用内置演示数据,首屏仍可渲染。
约定:?cat=分类key -> { list:[ {id,title,en,year,genre,country,director,cast,imdb,quality,remark,poster,backdrop,desc,stills:[],resources:[{tier,name,size,url,type,password}]} ] } */
catalogApi: "",
/* 接口走 api.tmdb.org(同 nostr-emby 文档方案):国内大部分地区不需要代理即可直连,
比经典域名 api.themoviedb.org 稳定得多(后者常被污染)。图片走 image.tmdb.org,同样直连不经代理;
尺寸分级参考 emby:网格小图按角色封顶到较高档位,主视觉图给最高档位 */
tmdb: { apiKey: "d7040155454e7fdf547c4d889ebbcca7", lang: "zh-CN", apiBase: "https://api.tmdb.org/3", imageBase: "https://images.tmdb.org/t/p/", listSize: "w780", heroSize: "original", stillSize: "original", castSize: "w185" },
imageHeaders: {},
pansou: { apiBase: "https://so.252035.xyz", diskTypes: ["quark","aliyun","baidu","uc","tianyi","xunlei","123","115","mobile"] }
};
var DISK_LABEL = { quark:"夸克", aliyun:"阿里", baidu:"百度", uc:"UC", tianyi:"天翼", xunlei:"迅雷", "123":"123云盘", "115":"115", mobile:"移动云", magnet:"磁力", ed2k:"电驴", thunder:"迅雷链", others:"其它" };
var COUNTRY = { US:"美国", CN:"中国大陆", HK:"中国香港", TW:"中国台湾", JP:"日本", KR:"韩国", GB:"英国", FR:"法国", DE:"德国", IT:"意大利", ES:"西班牙", IN:"印度", TH:"泰国", RU:"俄罗斯", CA:"加拿大", AU:"澳大利亚", SE:"瑞典", DK:"丹麦", NO:"挪威", BR:"巴西", IE:"爱尔兰", NZ:"新西兰", BE:"比利时", NL:"荷兰", MX:"墨西哥" };
/* 分类即查询定义:name 显示名,其余字段驱动 TMDB discover
tv:剧集 genres:类型(多类型取并集 OR)
sort:"vote" 走高分排序 minVotes/maxVotes:票数门槛 yearLte:年代上限
originCC:制片地区默认值(国家码数组,OR 取并集),仅在用户未手选地区时生效;
该分类同时会令「地区」筛选只保留 originCC 覆盖到的选项,不允许选到其它地区
注:地区/语言与单一类型已由「地区/类型」筛选统一负责,导航不放任何涉及单一地区或单类型的项,
只保留筛选选不出来的精选合集(高分/冷门/老片/华语)与冷门类型(战争/西部/音乐/纪录)及剧集入口 */
var CATS = [
{ key:"new", name:"最新热门", recent:true },
{ key:"huayu", name:"华语佳片", originCC:["CN","HK","TW"], airSort:true },
{ key:"huayu_tv", name:"华语热播", tv:true, originCC:["CN","HK","TW"], recent:true, airSort:true },
{ key:"top250", name:"高分经典", sort:"vote", minVotes:3000 },
{ key:"jingdian", name:"殿堂老片", sort:"vote", minVotes:1500, yearLte:"2010-12-31" },
{ key:"lengmen", name:"冷门佳片", sort:"vote", minVotes:80, maxVotes:1200 },
{ key:"zhanzheng", name:"战争史诗", genres:[10752,36] },
{ key:"xibu", name:"西部片", genres:[37] },
{ key:"yinyue", name:"音乐歌舞", genres:[10402] },
{ key:"jilu", name:"纪录片", genres:[99] },
{ key:"ju", name:"热门剧集", tv:true }
];
function catDef(key) { for (var i = 0; i < CATS.length; i++) if (CATS[i].key === key) return CATS[i]; return {}; }
var IS_NATIVE = !!(window.fongmiBridge || window.fm || window.fongmi);
var CLIENT = window.fongmiClient || {};
var IS_TV = !!CLIENT.isLeanback || document.documentElement.classList.contains("tv-mode");
function fmReady() {
return new Promise(function (resolve) {
if (window.fm) return resolve(window.fm);
if (!IS_NATIVE) return resolve(null);
var done = false;
function ok() { if (done) return; done = true; resolve(window.fm || null); }
window.addEventListener("fmsdk", ok);
var t = 0, iv = setInterval(function () { t += 60; if (window.fm || t > 4000) { clearInterval(iv); ok(); } }, 60);
});
}
var Store = {
get: function (k) { return fmReady().then(function (fm) {
if (fm && fm.cache) return fm.cache.get(k, "片库");
try { return localStorage.getItem("片库_" + k) || ""; } catch (e) { return ""; }
}).catch(function () { return ""; }); },
set: function (k, v) { return fmReady().then(function (fm) {
if (fm && fm.cache) return fm.cache.set(k, v, "片库");
try { localStorage.setItem("片库_" + k, v); } catch (e) {} return {};
}).catch(function () { return {}; }); }
};
function parseJSONLoose(txt) {
if (txt == null) return null;
if (typeof txt === "object") return txt;
var s = String(txt).replace(/^\uFEFF/, "").trim();
if (!s) return null;
try { return JSON.parse(s); } catch (e) {
var a = s.indexOf("{"), b = s.indexOf("[");
var start = (a === -1) ? b : (b === -1 ? a : Math.min(a, b));
var end = Math.max(s.lastIndexOf("}"), s.lastIndexOf("]"));
if (start !== -1 && end > start) { try { return JSON.parse(s.slice(start, end + 1)); } catch (e2) {} }
throw e;
}
}
function request(url, options) {
options = options || {};
return fmReady().then(function (fm) {
if (fm && fm.req) return fm.req(url, {
method: options.method || "GET", headers: options.headers || {}, body: options.body || "",
responseType: "text", timeout: options.timeout || 20
}).then(function (r) { if (!r || !r.ok) throw new Error((r && r.error) || ("HTTP " + (r ? r.status : "?"))); return parseJSONLoose(r.body); });
return fetch(url, { method: options.method || "GET", headers: options.headers || {}, body: options.body || undefined })
.then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); })
.then(parseJSONLoose);
});
}
/* 直连加载(参考 nostr-emby):图片不走 fm.res/代理,直接 img.src
失败时在 images.tmdb.org ↔ image.tmdb.org 之间换一次域名再重试 */
function altHost(url) {
if (!url) return url;
if (url.indexOf("://images.tmdb.org") !== -1) return url.replace("://images.tmdb.org", "://image.tmdb.org");
if (url.indexOf("://image.tmdb.org") !== -1) return url.replace("://image.tmdb.org", "://images.tmdb.org");
return url;
}
/* 兼容旧调用:直连返回原图地址 */
function resUrl(url) { return url || ""; }
function makeImg(url, altText) {
var img = el("img"); img.decoding = "async"; img.alt = altText || "";
img.loading = IS_TV ? "eager" : "lazy"; /* TV WebView 不支持 lazy,用 eager */
var tried = false;
img.onerror = function () { if (!tried && url) { tried = true; this.src = altHost(url); return; } this.style.display = "none"; };
img.src = url;
return img;
}
/* ── 海报轮播(参考 nostr-embytoken + 预加载 + crossfade ── */
var _carousel = { urls: [], idx: 0, timer: null, token: 0, interval: 4500 };
function _carouselSetBg(url) {
var main = $("#heroBgMain"), blur = $("#heroBgBlur");
if (!main) return;
var val = url ? "url('" + url + "')" : "none";
main.style.backgroundImage = val;
if (blur) blur.style.backgroundImage = val;
}
function _carouselCrossfade(url) {
var main = $("#heroBgMain"), next = $("#heroBgNext"), blur = $("#heroBgBlur");
if (!main || !next) return;
/* next 层预先设好图,opacity 0 在下方 */
next.style.backgroundImage = "url('" + url + "')";
next.style.zIndex = "0";
next.style.opacity = "0";
next.style.transition = "none";
/* 下一帧把 next 推到上方并淡入,同时 main 淡出 */
requestAnimationFrame(function () {
requestAnimationFrame(function () {
next.style.zIndex = "2";
next.style.transition = "opacity .7s ease";
next.style.opacity = "1";
main.style.transition = "opacity .7s ease";
main.style.opacity = "0";
/* blur 层在动画结束后再换图,避免透出旧图时 blur 已经是新图 */
setTimeout(function () {
main.style.backgroundImage = "url('" + url + "')";
main.style.transition = "none";
main.style.opacity = "1";
next.style.transition = "none";
next.style.opacity = "0";
next.style.zIndex = "0";
if (blur) blur.style.backgroundImage = "url('" + url + "')";
}, 750);
});
});
}
function _carouselGo(token) {
if (token !== _carousel.token) return;
var images = _carousel.urls, len = images.length;
if (!len) return;
var start = _carousel.idx;
var next = (start + 1) % len;
_carouselTryLoad(next, 0, token);
}
function _carouselTryLoad(idx, attempts, token) {
if (token !== _carousel.token) return;
var images = _carousel.urls, len = images.length;
if (!images.length || attempts >= len) return;
var url = images[idx];
if (!url) { _carouselTryLoad((idx + 1) % len, attempts + 1, token); return; }
if (IS_TV) {
/* TV 端跳过预加载直接切换 */
_carousel.idx = idx;
_carouselCrossfade(url);
return;
}
var probe = new Image();
probe.onload = function () {
if (token !== _carousel.token) return;
_carousel.idx = idx;
_carouselCrossfade(url);
};
probe.onerror = function () {
_carouselTryLoad((idx + 1) % len, attempts + 1, token);
};
probe.src = url;
}
function _carouselStart() {
_carouselStop();
if (_carousel.urls.length < 2) return;
var token = _carousel.token;
_carousel.timer = setInterval(function () {
if (document.visibilityState === "hidden") return;
var sheet = $("#sheet"); if (!sheet || !sheet.classList.contains("active")) return;
_carouselGo(token);
}, _carousel.interval);
}
function _carouselStop() {
if (_carousel.timer) { clearInterval(_carousel.timer); _carousel.timer = null; }
_carousel.token += 1;
}
function setCarouselUrls(urls) {
_carouselStop();
_carousel.urls = urls || []; _carousel.idx = 0;
_carouselSetBg(_carousel.urls[0] || "");
_carouselStart();
}
/* TMDB 图片换尺寸(如 w342 -> w780),非 TMDB URL 原样返回 */
function tmdbResize(url, size) { if (!url) return url; return url.replace(/\/w\d+\//, "/" + size + "/").replace(/\/original\//, "/" + size + "/"); }
/* 把任意图片 URL 经 Canvas 模糊后返回 base64 data URI
radius: CSS blur px(默认 8);走 fm.res() 解决跨域 */
function blurImageToDataURL(url, radius) {
radius = radius || 8;
return new Promise(function (resolve) {
if (!url) { resolve(""); return; }
/* 尝试用 fm.res() 代理绕过跨域 */
var src = url;
try {
if (window.fm && window.fm.res) src = window.fm.res(url);
} catch (e) {}
var img = new Image();
img.crossOrigin = "anonymous";
img.onload = function () {
try {
var w = img.naturalWidth || img.width || 960;
var h = img.naturalHeight || img.height || 540;
/* 缩小绘制降低内存,blur 视觉上看不出差别 */
var scale = Math.min(1, 480 / Math.max(w, h));
var cw = Math.round(w * scale), ch = Math.round(h * scale);
var canvas = document.createElement("canvas");
canvas.width = cw; canvas.height = ch;
var ctx = canvas.getContext("2d");
ctx.filter = "blur(" + radius + "px)";
/* 多绘一圈防止边缘透明晕 */
var pad = radius * 2;
ctx.drawImage(img, -pad, -pad, cw + pad * 2, ch + pad * 2);
resolve(canvas.toDataURL("image/jpeg", 0.82));
} catch (e) { resolve(url); /* canvas 污染等异常:降级用原图 */ }
};
img.onerror = function () { resolve(url); };
img.src = src;
});
}
/* 竖屏?优先 matchMedia,回退宽高比较 */
function isPortrait() {
if (window.matchMedia) { try { return window.matchMedia("(orientation: portrait)").matches; } catch (e) {} }
return (window.innerHeight || 0) >= (window.innerWidth || 0);
}
/* 沉浸大图:竖屏用竖版海报(升清 original),横屏用横版剧照;缺图互相回退 */
function heroImageUrl(item) {
var poster = pick(item, "poster", ""), backdrop = pick(item, "backdrop", "");
var bigP = poster ? tmdbResize(poster, "original") : "";
var bigB = backdrop ? tmdbResize(backdrop, "original") : "";
return isPortrait() ? (bigP || bigB || "") : (bigB || bigP || "");
}
function applyHeroImage(item) {
if (!item) return;
/* 等待 stills 加载,不提前显示海报 */
setCarouselUrls([]);
}
function applyHeroCarousel(item, stills) {
if (!item) return;
/* 轮播只展示后续加入的 stills,不包含 poster/backdrop 海报图 */
var urls = [];
(stills || []).forEach(function (u) { if (urls.indexOf(u) < 0) urls.push(u); });
setCarouselUrls(urls);
}
function $(sel, root) { return (root || document).querySelector(sel); }
function el(tag, cls) { var e = document.createElement(tag); if (cls) e.className = cls; return e; }
function pick(o, k, d) { return (o && o[k] != null) ? o[k] : d; }
function num(v) { var n = parseFloat(v); return isNaN(n) ? 0 : n; }
function cssEsc(s) { return String(s).replace(/["\\]/g, "\\$&"); }
var toastTimer = null;
function toast(msg) { var t = $("#toast"); t.textContent = msg; t.classList.add("show"); if (toastTimer) clearTimeout(toastTimer); toastTimer = setTimeout(function () { t.classList.remove("show"); }, 2200); }
function copyText(text) {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(text).then(function () { toast("已复制链接"); }, function () { fallbackCopy(text); });
} else fallbackCopy(text);
}
function fallbackCopy(text) {
try {
var ta = el("textarea"); ta.value = text; ta.style.cssText = "position:fixed;opacity:0;"; document.body.appendChild(ta);
ta.select(); document.execCommand("copy"); document.body.removeChild(ta); toast("已复制链接");
} catch (e) { toast("复制失败,请手动选择"); }
}
function hueOf(s) { var h = 0, i; s = s || ""; for (i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) % 360; return h; }
function posterInner(item) {
var poster = pick(item, "poster", "");
if (poster) return makeImg(poster, pick(item, "title", ""));
var h = hueOf(pick(item, "title", "")); var ph = el("div", "ph"); ph.setAttribute("data-ph", "1");
ph.style.background = "linear-gradient(150deg, hsl(" + h + ",18%,28%), hsl(" + ((h + 30) % 360) + ",16%,14%))";
ph.textContent = pick(item, "title", "影片"); return ph;
}
function qualityClass(q) { q = (q || "").toUpperCase();
if (q.indexOf("4K") !== -1 || q.indexOf("2160") !== -1) return "q4k";
if (q.indexOf("原盘") !== -1 || q.indexOf("BLURAY") !== -1 || q.indexOf("REMUX") !== -1 || q.indexOf("蓝光") !== -1) return "qbd";
if (q.indexOf("WEB") !== -1 || q.indexOf("HD") !== -1) return "qweb";
return "";
}
/* ---------- TMDB 自动海报/剧照(填 apiKey 才启用) ---------- */
function tmdbEnabled() { return !!(CONFIG.tmdb && CONFIG.tmdb.apiKey); }
function getArtwork(item) {
if (!tmdbEnabled()) {
/* 没有 TMDB,直接用数据源原图(可能低清) */
return Promise.resolve(pick(item, "poster", "") ? { poster: item.poster, backdrop: pick(item, "backdrop", ""), id: pick(item, "tmdbId", 0), type: pick(item, "tmdbType", "") } : null);
}
var ck = "art_" + pick(item, "title", "") + "_" + pick(item, "year", "");
return Store.get(ck).then(function (cached) {
if (cached) { try { var o = JSON.parse(cached); if (o && o.poster) return o; } catch (e) {} }
var t = CONFIG.tmdb;
var q = pick(item, "en", "") || pick(item, "title", "");
var url = (t.apiBase || "https://api.tmdb.org/3") + "/search/multi?api_key=" + encodeURIComponent(t.apiKey) +
"&language=" + encodeURIComponent(t.lang || "zh-CN") + "&query=" + encodeURIComponent(q);
return request(url, { timeout: 15 }).then(function (data) {
var arr = (data && data.results) ? data.results : [];
var hit = null, i; for (i = 0; i < arr.length; i++) { if (arr[i] && arr[i].poster_path) { hit = arr[i]; break; } }
if (!hit) return null;
var out = {
poster: t.imageBase + (t.listSize || "w780") + hit.poster_path,
backdrop: hit.backdrop_path ? (t.imageBase + (t.heroSize || "original") + hit.backdrop_path) : "",
id: hit.id || 0, type: hit.media_type || "movie"
};
Store.set(ck, JSON.stringify(out)); return out;
});
}).catch(function () { return null; });
}
function getStills(art) {
if (!tmdbEnabled() || !art || !art.id) return Promise.resolve([]);
var t = CONFIG.tmdb, type = art.type === "tv" ? "tv" : "movie";
var url = (t.apiBase || "https://api.tmdb.org/3") + "/" + type + "/" + art.id + "/images?api_key=" + encodeURIComponent(t.apiKey);
return request(url, { timeout: 15 }).then(function (data) {
var arr = (data && data.backdrops) ? data.backdrops : [];
/* 参考 Eclipse 选图策略:无文字水印的剧照优先(更干净),同档再按评分降序,最多取 6 张 */
arr = arr.filter(function (b) { return b && b.file_path; }).sort(function (a, b) {
var an = a.iso_639_1 ? 1 : 0, bn = b.iso_639_1 ? 1 : 0;
if (an !== bn) return an - bn;
return (b.vote_average || 0) - (a.vote_average || 0);
});
return arr.slice(0, 6).map(function (b) { return t.imageBase + (t.stillSize || "w500") + b.file_path; });
}).catch(function () { return []; });
}
/* TMDB 详情:类型 / 别名 / 国家 / 导演 / 时间 / 演员 / 简介 */
function getDetailMeta(art) {
if (!tmdbEnabled() || !art || !art.id) return Promise.resolve(null);
var t = CONFIG.tmdb, type = art.type === "tv" ? "tv" : "movie";
var ck = "meta_" + type + "_" + art.id;
return Store.get(ck).then(function (cached) {
if (cached) { try { var o = JSON.parse(cached); if (o) return o; } catch (e) {} }
var url = (t.apiBase || "https://api.tmdb.org/3") + "/" + type + "/" + art.id + "?api_key=" + encodeURIComponent(t.apiKey) +
"&language=" + encodeURIComponent(t.lang || "zh-CN") + "&append_to_response=credits,alternative_titles";
return request(url, { timeout: 15 }).then(function (d) {
if (!d) return null;
var genre = (d.genres || []).map(function (g) { return g.name; }).join(", ");
var date = type === "tv" ? (d.first_air_date || "") : (d.release_date || "");
var countries = [];
(d.origin_country || []).forEach(function (c) { countries.push(COUNTRY[c] || c); });
if (!countries.length && d.production_countries) d.production_countries.forEach(function (c) { countries.push(COUNTRY[c.iso_3166_1] || c.name); });
var credits = d.credits || {};
var director = "";
if (type === "tv") director = (d.created_by || []).map(function (p) { return p.name; }).join(" / ");
if (!director && credits.crew) director = credits.crew.filter(function (p) { return p.job === "Director"; }).slice(0, 2).map(function (p) { return p.name; }).join(" / ");
var cast = (credits.cast || []).slice(0, 8).map(function (p) { return p.name; }).join(" / ");
/* 头像获取方式:在参考文件 renderCast 的基础上加入导演(导演排在最前面),同样按 profile_path 过滤,没有真实头像的人直接不显示,不做任何兜底 */
var dirList = (credits.crew || []).filter(function (p) { return p.job === "Director" && p.profile_path; }).slice(0, 4).map(function (p) {
return { name: p.name, role: "导演", avatar: t.imageBase + (t.castSize || "w185") + p.profile_path, tmdbPersonId: p.id };
});
var castList = (credits.cast || []).filter(function (p) { return p.profile_path; }).slice(0, 20).map(function (p) {
return { name: p.name, role: p.character || "", avatar: t.imageBase + (t.castSize || "w185") + p.profile_path, tmdbPersonId: p.id };
});
castList = dirList.concat(castList);
var at = d.alternative_titles || {}; var titles = at.titles || at.results || []; var alts = [];
titles.forEach(function (x) { if (["CN", "TW", "HK"].indexOf(x.iso_3166_1) !== -1 && x.title) alts.push(x.title); });
var meta = { genre: genre, date: date, country: countries.join(" / "), director: director, cast: cast, castList: castList,
alias: alts.slice(0, 4).join(" / "), overview: d.overview || "", runtime: d.runtime || 0 };
Store.set(ck, JSON.stringify(meta));
return meta;
});
}).catch(function () { return null; });
}
function runPool(items, worker, concurrency) {
concurrency = concurrency || 4;
return new Promise(function (resolve) {
var idx = 0, active = 0, done = 0, n = items.length; if (!n) return resolve();
function next() { while (active < concurrency && idx < n) { var cur = items[idx++]; active++;
Promise.resolve(worker(cur))["finally"](function () { active--; done++; if (done >= n) resolve(); else next(); }); } }
next();
});
}
function patchCardPoster(item, art) {
if (!art || !art.poster) return;
item.poster = art.poster; if (art.backdrop) item.backdrop = art.backdrop;
var card = $("#grid .card[data-id=\"" + cssEsc(pick(item, "id", "")) + "\"]"); if (!card) return;
var poster = card.querySelector(".poster"); var ph = poster ? poster.querySelector("[data-ph]") : null;
if (poster && ph) { poster.replaceChild(makeImg(art.poster, pick(item, "title", "")), ph); }
}
function enrichPosters(items) {
if (!tmdbEnabled()) return;
var pending = (items || []).filter(function (it) { return !pick(it, "poster", ""); });
var myToken = STATE.reqToken;
runPool(pending, function (it) { return getArtwork(it).then(function (art) { if (STATE.reqToken !== myToken) return; patchCardPoster(it, art); }); }, 4);
}
/* ============================================================
内置演示数据(catalogApi 为空时用;片名取自 片库 公开收录,作占位演示)
============================================================ */
var DEMO = [
{ id:"1715", title:"造梦之家", en:"The Fabelmans", year:"2022", genre:"剧情, 家庭", country:"美国", director:"史蒂文·斯皮尔伯格", cast:"米歇尔·威廉姆斯 / 保罗·达诺", imdb:"7.5", quality:"4K原盘", remark:"原盘", cats:["jiating","blu"],
desc:"斯皮尔伯格的初心之作。少年萨姆爱上电影并尝试创作,多年后却透过镜头发现关于母亲的心碎真相。" },
{ id:"1503", title:"我们一起摇太阳", en:"Viva La Vida", year:"2024", genre:"剧情, 爱情", country:"中国大陆", director:"韩延", cast:"彭昱畅 / 李庚希", imdb:"6.8", quality:"4K", remark:"4K", cats:["huayu"],
desc:"两个身患重病的年轻人在至暗时刻相遇,彼此照亮,决定一起认真地活下去。" },
{ id:"1792", title:"邻里美好的一天", en:"A Beautiful Day in the Neighborhood", year:"2019", genre:"剧情, 传记", country:"美国", director:"玛丽埃尔·海勒", cast:"汤姆·汉克斯 / 马修·里斯", imdb:"7.2", quality:"1080P", remark:"蓝光", cats:["jiating","lengmen"],
desc:"愤世嫉俗的记者采访儿童节目主持人罗杰斯先生,在相处中被其善意慢慢改变。" },
{ id:"1163", title:"美少女特攻队", en:"Sucker Punch", year:"2011", genre:"动作, 奇幻", country:"美国", director:"扎克·施奈德", cast:"艾米莉·布朗宁 / 阿比·考尼什", imdb:"6.0", quality:"4K原盘", remark:"原盘", cats:["lengmen","blu"],
desc:"被送进精神病院的少女在想象世界里召集同伴,策划一场惊心动魄的越狱与反抗。" },
{ id:"2287", title:"玩尽杀绝", en:"Wild Things", year:"1998", genre:"惊悚, 悬疑", country:"美国", director:"约翰·麦克诺顿", cast:"凯文·贝肯 / 马特·狄龙", imdb:"6.5", quality:"1080P原盘", remark:"原盘", cats:["lengmen","blu"],
desc:"佛罗里达小镇一桩指控引出层层反转,谁是猎人谁是猎物始终扑朔迷离。" },
{ id:"3001", title:"侏罗纪公园", en:"Jurassic Park", year:"1993", genre:"科幻, 冒险", country:"美国", director:"史蒂文·斯皮尔伯格", cast:"萨姆·尼尔 / 劳拉·邓恩", imdb:"8.2", quality:"4K原盘", remark:"原盘", cats:["top250","blu"],
desc:"科学家用远古 DNA 复活恐龙建成主题公园,系统失控后游客陷入生死逃亡。" },
{ id:"3002", title:"千与千寻", en:"Spirited Away", year:"2001", genre:"动画, 奇幻", country:"日本", director:"宫崎骏", cast:"柊瑠美 / 入野自由", imdb:"8.6", quality:"4K", remark:"4K", cats:["donghua","top250"],
desc:"少女千寻误入神灵世界,父母被变成猪,她在汤屋打工求生,找回名字与归途。" },
{ id:"4101", title:"周处除三害", en:"The Pig the Snake and the Pigeon", year:"2023", genre:"动作, 犯罪", country:"中国台湾", director:"黄精甫", cast:"阮经天 / 袁富华", imdb:"7.4", quality:"4K原盘", remark:"原盘", cats:["huayu","blu"],
desc:"绝症通缉犯决意在死前除掉两名比自己更恶的通缉要犯,以求被人记住。" },
{ id:"4102", title:"满江红", en:"Full River Red", year:"2023", genre:"剧情, 悬疑", country:"中国大陆", director:"张艺谋", cast:"沈腾 / 易烊千玺", imdb:"6.5", quality:"4K", remark:"4K", cats:["huayu"],
desc:"南宋年间,一桩命案牵出宰相府的惊天密谋,一夜之间众人性命交织。" },
{ id:"4103", title:"流浪地球2", en:"The Wandering Earth II", year:"2023", genre:"科幻, 灾难", country:"中国大陆", director:"郭帆", cast:"吴京 / 刘德华", imdb:"8.0", quality:"4K原盘", remark:"原盘", cats:["huayu","blu"],
desc:"太阳危机迫近,人类启动“移山计划”推动地球远航,危机与牺牲接踵而至。" },
{ id:"4104", title:"封神第一部", en:"Creation of the Gods I", year:"2023", genre:"奇幻, 古装", country:"中国大陆", director:"乌尔善", cast:"费翔 / 李雪健", imdb:"7.0", quality:"4K", remark:"4K", cats:["huayu"],
desc:"商王殷寿与妖狐妲己祸乱朝纲,姬发逐渐看清真相,踏上反抗暴政之路。" },
{ id:"4105", title:"热辣滚烫", en:"YOLO", year:"2024", genre:"剧情, 喜剧", country:"中国大陆", director:"贾玲", cast:"贾玲 / 雷佳音", imdb:"6.6", quality:"4K", remark:"4K", cats:["huayu"],
desc:"宅家多年的女孩在一次次跌落后接触拳击,慢慢学会为自己活一次。" },
{ id:"4106", title:"长安三万里", en:"Chang An", year:"2023", genre:"动画, 历史", country:"中国大陆", director:"谢君伟 / 邹靖", cast:"配音 / 杨天翔", imdb:"7.6", quality:"4K", remark:"4K", cats:["huayu","donghua"],
desc:"暮年高适回望与李白的一生交集,盛唐群星与诗篇在烽烟中徐徐展开。" },
{ id:"4201", title:"寻梦环游记", en:"Coco", year:"2017", genre:"动画, 家庭", country:"美国", director:"李·昂克里奇", cast:"安东尼·冈萨雷斯", imdb:"8.4", quality:"4K原盘", remark:"原盘", cats:["donghua","jiating","blu"],
desc:"热爱音乐的男孩误入亡灵世界,追寻家族秘密,也读懂了记忆与爱的意义。" },
{ id:"4202", title:"你的名字", en:"Your Name", year:"2016", genre:"动画, 爱情", country:"日本", director:"新海诚", cast:"神木隆之介 / 上白石萌音", imdb:"8.4", quality:"4K", remark:"4K", cats:["donghua"],
desc:"素未谋面的少年少女在梦中互换身体,跨越时间与距离追寻彼此。" },
{ id:"4203", title:"蜘蛛侠:纵横宇宙", en:"Spider-Man: Across the Spider-Verse", year:"2023", genre:"动画, 动作", country:"美国", director:"杰昆·多斯桑托斯", cast:"沙梅克·摩尔", imdb:"8.5", quality:"4K原盘", remark:"原盘", cats:["donghua","blu"],
desc:"迈尔斯穿梭多元宇宙,与无数蜘蛛侠相遇,也面对关于命运的抉择。" },
{ id:"4204", title:"疯狂动物城", en:"Zootopia", year:"2016", genre:"动画, 喜剧", country:"美国", director:"拜伦·霍华德", cast:"金妮弗·古德温", imdb:"8.0", quality:"4K", remark:"4K", cats:["donghua"],
desc:"兔子警官与狐狸骗子联手破案,揭开动物乌托邦背后的偏见与阴谋。" },
{ id:"4205", title:"心灵奇旅", en:"Soul", year:"2020", genre:"动画, 奇幻", country:"美国", director:"彼特·道格特", cast:"杰米·福克斯", imdb:"8.0", quality:"4K", remark:"4K", cats:["donghua"],
desc:"中学音乐老师意外坠入灵魂世界,重新思考活着的意义与生活的火花。" },
{ id:"4301", title:"当幸福来敲门", en:"The Pursuit of Happyness", year:"2006", genre:"剧情, 传记", country:"美国", director:"加布里埃尔·穆奇诺", cast:"威尔·史密斯", imdb:"8.0", quality:"1080P", remark:"蓝光", cats:["jiating"],
desc:"濒临破产的父亲带着儿子在困顿中坚持,最终凭努力赢得人生转机。" },
{ id:"4302", title:"怦然心动", en:"Flipped", year:"2010", genre:"爱情, 喜剧", country:"美国", director:"罗伯·莱纳", cast:"玛德琳·卡罗尔", imdb:"7.7", quality:"1080P", remark:"蓝光", cats:["jiating","lengmen"],
desc:"少男少女从童年到青春的双视角初恋,关于成长与看见彼此的故事。" },
{ id:"4303", title:"阿甘正传", en:"Forrest Gump", year:"1994", genre:"剧情", country:"美国", director:"罗伯特·泽米吉斯", cast:"汤姆·汉克斯", imdb:"8.8", quality:"4K原盘", remark:"原盘", cats:["top250","jiating","blu"],
desc:"智商不高却心地纯善的阿甘,用奔跑的一生见证了美国几十年的变迁。" },
{ id:"4401", title:"三块广告牌", en:"Three Billboards Outside Ebbing, Missouri", year:"2017", genre:"剧情, 犯罪", country:"美国", director:"马丁·麦克唐纳", cast:"弗兰西斯·麦克多蒙德", imdb:"8.1", quality:"1080P", remark:"蓝光", cats:["lengmen"],
desc:"母亲为女儿命案立起三块广告牌质问警方,引爆小镇的愤怒与悲悯。" },
{ id:"4402", title:"海边的曼彻斯特", en:"Manchester by the Sea", year:"2016", genre:"剧情", country:"美国", director:"肯尼斯·罗纳根", cast:"卡西·阿弗莱克", imdb:"7.8", quality:"1080P", remark:"蓝光", cats:["lengmen"],
desc:"沉默寡言的修理工被迫回乡照顾侄子,旧日伤痛在海风中无声翻涌。" },
{ id:"4403", title:"时空恋旅人", en:"About Time", year:"2013", genre:"爱情, 奇幻", country:"英国", director:"理查德·柯蒂斯", cast:"多姆纳尔·格里森", imdb:"7.8", quality:"1080P", remark:"蓝光", cats:["lengmen"],
desc:"能穿越时间的青年反复回到过去,最终懂得珍惜的是平凡的每一天。" },
{ id:"4501", title:"肖申克的救赎", en:"The Shawshank Redemption", year:"1994", genre:"剧情, 犯罪", country:"美国", director:"弗兰克·德拉邦特", cast:"蒂姆·罗宾斯", imdb:"9.3", quality:"4K原盘", remark:"原盘", cats:["top250","blu"],
desc:"蒙冤入狱的银行家以惊人的耐心与希望,在高墙之内谋划自己的自由。" },
{ id:"4502", title:"教父", en:"The Godfather", year:"1972", genre:"剧情, 犯罪", country:"美国", director:"弗朗西斯·科波拉", cast:"马龙·白兰度 / 阿尔·帕西诺", imdb:"9.2", quality:"4K原盘", remark:"原盘", cats:["top250","blu"],
desc:"柯里昂家族的权力更迭,一个不愿入局的儿子最终成为冷酷的新教父。" },
{ id:"4503", title:"盗梦空间", en:"Inception", year:"2010", genre:"科幻, 动作", country:"美国", director:"克里斯托弗·诺兰", cast:"莱昂纳多·迪卡普里奥", imdb:"8.8", quality:"4K原盘", remark:"原盘", cats:["top250","blu"],
desc:"盗梦高手潜入他人梦境植入意念,层层梦境中真实与虚幻逐渐模糊。" },
{ id:"4504", title:"星际穿越", en:"Interstellar", year:"2014", genre:"科幻, 冒险", country:"美国", director:"克里斯托弗·诺兰", cast:"马修·麦康纳", imdb:"8.7", quality:"4K原盘", remark:"原盘", cats:["top250","blu"],
desc:"地球濒危,宇航员穿越虫洞为人类寻找新家园,跨越时空守护亲情。" },
{ id:"4505", title:"黑客帝国", en:"The Matrix", year:"1999", genre:"科幻, 动作", country:"美国", director:"沃卓斯基姐妹", cast:"基努·里维斯", imdb:"8.7", quality:"4K原盘", remark:"原盘", cats:["top250","blu"],
desc:"程序员发现世界是机器制造的幻象,觉醒后投身反抗以夺回真实。" },
{ id:"4601", title:"欢颜", en:"Joyful Reunion", year:"2023", genre:"剧情, 年代", country:"中国大陆", director:"徐兵", cast:"董子健 / 张鲁一", imdb:"7.4", quality:"4K", remark:"全11集", cats:["ju","huayu"],
desc:"乱世年代,一笔神秘款项要被护送给远方的组织,一路险象环生、人心难测。" },
{ id:"4602", title:"漫长的季节", en:"The Long Season", year:"2023", genre:"剧情, 悬疑", country:"中国大陆", director:"辛爽", cast:"范伟 / 秦昊", imdb:"9.0", quality:"4K", remark:"全12集", cats:["ju"],
desc:"东北小城一桩旧案缠绕三个家庭二十年,秋叶飘落处真相终被翻起。" },
{ id:"4603", title:"繁花", en:"Blossoms Shanghai", year:"2023", genre:"剧情, 年代", country:"中国大陆", director:"王家卫", cast:"胡歌 / 马伊琍", imdb:"7.8", quality:"4K原盘", remark:"全30集", cats:["ju","blu"],
desc:"九十年代的上海滩,宝总在商海沉浮,霓虹之下是机遇、情义与时代。" },
{ id:"4604", title:"三体", en:"Three-Body", year:"2023", genre:"科幻, 悬疑", country:"中国大陆", director:"杨磊", cast:"张鲁一 / 于和伟", imdb:"8.7", quality:"4K", remark:"全30集", cats:["ju"],
desc:"纳米科学家卷入一连串科学家离奇死亡,逐步揭开三体文明的降临。" },
{ id:"4605", title:"狂飙", en:"The Knockout", year:"2023", genre:"剧情, 犯罪", country:"中国大陆", director:"徐纪周", cast:"张译 / 张颂文", imdb:"8.3", quality:"1080P", remark:"全39集", cats:["ju"],
desc:"扫黑除恶跨越二十年,正义警察与崛起的黑恶势力之间的漫长博弈。" },
{ id:"4606", title:"黑暗荣耀", en:"The Glory", year:"2022", genre:"剧情, 复仇", country:"韩国", director:"安吉镐", cast:"宋慧乔 / 李到晛", imdb:"8.1", quality:"4K", remark:"全16集", cats:["ju"],
desc:"遭受校园暴力的女孩用半生筹谋一场精密复仇,逐一清算昔日施暴者。" },
{ id:"4607", title:"最后生还者", en:"The Last of Us", year:"2023", genre:"剧情, 末日", country:"美国", director:"克雷格·麦辛", cast:"佩德罗·帕斯卡 / 贝拉·拉姆齐", imdb:"8.7", quality:"4K原盘", remark:"全9集", cats:["ju","blu"],
desc:"真菌疫情后的废土上,老兵护送拥有免疫力的少女穿越危机四伏的美国。" },
{ id:"5101", title:"庆余年第二季", en:"Joy of Life Season 2", year:"2024", genre:"剧情, 古装", country:"中国大陆", director:"孙皓", cast:"张若昀 / 吴刚 / 李沁", imdb:"8.5", quality:"4K", remark:"全36集", cats:["huayu_tv","ju"],
desc:"范闲再度入京,权谋角力更为激烈,父子情仇与庙堂暗战交织铺开。" },
{ id:"5102", title:"与凤行", en:"My Journey with You", year:"2024", genre:"剧情, 仙侠", country:"中国大陆", director:"林玉芬", cast:"赵丽颖 / 林更新", imdb:"7.2", quality:"4K", remark:"全40集", cats:["huayu_tv"],
desc:"魔界太子与人间女子命运交缠,跨越重重阻碍共历乱世情劫。" },
{ id:"5103", title:"玫瑰的故事", en:"The Story of Rose", year:"2024", genre:"剧情, 爱情", country:"中国大陆", director:"汪俊", cast:"刘亦菲 / 万彧雨", imdb:"7.8", quality:"4K", remark:"全38集", cats:["huayu_tv"],
desc:"名为黄亦玫的女子在不同年代的爱情与人生抉择,绽放属于自己的玫瑰。" },
{ id:"5104", title:"繁花", en:"Blossoms Shanghai", year:"2023", genre:"剧情, 年代", country:"中国大陆", director:"王家卫", cast:"胡歌 / 马伊琍 / 唐嫣", imdb:"7.8", quality:"4K原盘", remark:"全30集", cats:["huayu_tv","ju","blu"],
desc:"九十年代的上海滩,宝总在商海沉浮,霓虹之下是机遇、情义与时代。" },
{ id:"5105", title:"三体", en:"Three-Body", year:"2023", genre:"科幻, 悬疑", country:"中国大陆", director:"杨磊", cast:"张鲁一 / 于和伟 / 陈瑾", imdb:"8.7", quality:"4K", remark:"全30集", cats:["huayu_tv","ju"],
desc:"纳米科学家卷入一连串科学家离奇死亡,逐步揭开三体文明的降临。" },
{ id:"5106", title:"狂飙", en:"The Knockout", year:"2023", genre:"剧情, 犯罪", country:"中国大陆", director:"徐纪周", cast:"张译 / 张颂文 / 李一桐", imdb:"8.3", quality:"1080P", remark:"全39集", cats:["huayu_tv","ju"],
desc:"扫黑除恶跨越二十年,正义警察与崛起的黑恶势力之间的漫长博弈。" },
{ id:"5107", title:"漫长的季节", en:"The Long Season", year:"2023", genre:"剧情, 悬疑", country:"中国大陆", director:"辛爽", cast:"范伟 / 秦昊 / 陈明昊", imdb:"9.0", quality:"4K", remark:"全12集", cats:["huayu_tv","ju"],
desc:"东北小城一桩旧案缠绕三个家庭二十年,秋叶飘落处真相终被翻起。" },
{ id:"5108", title:"风起陇西", en:"The Wind Blows From Longxi", year:"2022", genre:"剧情, 古装", country:"中国大陆", director:"路阳", cast:"陈坤 / 白宇", imdb:"8.5", quality:"4K", remark:"全32集", cats:["huayu_tv"],
desc:"三国时代的谍战,两个情报员在蜀魏之间夹缝求存,忠义与生死难两全。" },
{ id:"5109", title:"山海情", en:"Minning Town", year:"2021", genre:"剧情, 年代", country:"中国大陆", director:"孔笙 / 孙墨龙", cast:"黄轩 / 张嘉益 / 闫妮", imdb:"9.0", quality:"1080P", remark:"全23集", cats:["huayu_tv"],
desc:"宁夏西海固的农民在扶贫政策下背井离乡,在戈壁滩上建立新家园的艰辛历程。" },
{ id:"5110", title:"大江大河", en:"Great River", year:"2018", genre:"剧情, 年代", country:"中国大陆", director:"孔笙 / 黄伟", cast:"王凯 / 杨烁 / 童瑶", imdb:"8.9", quality:"1080P", remark:"全47集", cats:["huayu_tv"],
desc:"改革开放时代,三个出身不同的年轻人在时代浪潮中各自奋进,命运交织。" }
];
var STATE = { cat: "new", full: [], list: [], reqToken: 0, detail: null, driveCheck: true, clientFilter: true,
page: 1, totalPages: 1, hasMore: false, loadingMore: false,
filters: { genre: "全部", region: "全部", year: "全部", sort: "最新" }, filtersOpen: false,
featured: [], heroIdx: 0, heroTimer: null, playQueue: [], playIdx: 0, expand: {} };
var FILTERS = {
genre: ["全部","剧情","喜剧","动作","科幻","爱情","动画","犯罪","悬疑","奇幻","家庭","惊悚","恐怖"],
region: ["全部","华语","大陆","香港","台湾","美国","日本","韩国","英国"],
year: ["全部","2026","2025","2024","2023","2022","2021","2020","更早"],
sort: ["最新","评分"]
};
var TMDB_GENRE = { "剧情":18, "喜剧":35, "动作":28, "科幻":878, "爱情":10749, "动画":16, "犯罪":80, "悬疑":9648, "奇幻":14, "家庭":10751, "惊悚":53, "恐怖":27 };
var REGION_CC = { "大陆":"CN", "香港":"HK", "台湾":"TW", "美国":"US", "日本":"JP", "韩国":"KR", "英国":"GB" };
/* TMDB 剧集(TV)与电影的类型 ID 不同。在剧集分类里用「类型」筛选时,
把电影类型 ID 映射成剧集类型 ID;无对应的(爱情/惊悚/恐怖/音乐)则不按类型过滤,
避免筛出空列表 */
var MOVIE_TO_TV_GENRE = {
28: 10759, 12: 10759, /* 动作/冒险 → Action & Adventure */
16: 16, /* 动画 */
35: 35, /* 喜剧 */
80: 80, /* 犯罪 */
99: 99, /* 纪录 */
18: 18, /* 剧情 */
10751: 10751, /* 家庭 */
14: 10765, 878: 10765, /* 奇幻/科幻 → Sci-Fi & Fantasy */
9648: 9648, /* 悬疑 */
10752: 10768, 36: 10768,/* 战争/历史 → War & Politics */
37: 37 /* 西部 */
};
/* ============================================================
数据加载
============================================================ */
function demoFor(cat) {
var base = DEMO.slice();
if (cat !== "new") base = base.filter(function (m) { return (m.cats || []).indexOf(cat) !== -1; });
if (cat === "top250") base = base.slice().sort(function (a, b) { return num(b.imdb) - num(a.imdb); });
if (cat === "huayu_tv" || cat === "huayu") base = base.slice().sort(function (a, b) { return num(b.year) - num(a.year); });
return base;
}
function mapTmdb(r, isTV) {
if (!r) return null;
var t = CONFIG.tmdb;
var title = isTV ? (r.name || r.original_name) : (r.title || r.original_title);
var orig = isTV ? r.original_name : r.original_title;
var date = isTV ? (r.first_air_date || "") : (r.release_date || "");
return {
id: "tmdb_" + (isTV ? "tv" : "mv") + "_" + r.id, tmdbId: r.id, tmdbType: isTV ? "tv" : "movie",
title: title || "", en: (orig && orig !== title) ? orig : "", year: date ? date.slice(0, 4) : "",
rating: r.vote_average ? (Math.round(r.vote_average * 10) / 10) : 0,
poster: r.poster_path ? (t.imageBase + (t.listSize || "w780") + r.poster_path) : "",
backdrop: r.backdrop_path ? (t.imageBase + "original" + r.backdrop_path) : "",
desc: r.overview || ""
};
}
function tmdbPage(cat, f, page) {
var t = CONFIG.tmdb, def = catDef(cat), isTV = !!def.tv;
var base = (t.apiBase || "https://api.tmdb.org/3") + "/discover/" + (isTV ? "tv" : "movie");
var p = ["api_key=" + encodeURIComponent(t.apiKey), "language=" + encodeURIComponent(t.lang || "zh-CN"), "include_adult=false", "page=" + (page || 1)];
var lteField = isTV ? "first_air_date.lte" : "primary_release_date.lte";
var gteField = isTV ? "first_air_date.gte" : "primary_release_date.gte";
/* 排序:跟随用户选择(分类切换时已把默认排序设成该分类的自然排序),让排序按钮处处生效 */
var sort = (f.sort === "评分") ? "vote_average.desc" : (def.airSort ? (isTV ? "first_air_date.desc" : "primary_release_date.desc") : "popularity.desc");
/* 类型:用户手选优先(窄化为单一类型),否则用分类自带类型,多类型取并集 OR */
var genres = [];
if (f.genre !== "全部" && TMDB_GENRE[f.genre]) genres = [TMDB_GENRE[f.genre]];
else if (def.genres && def.genres.length) genres = def.genres.slice();
/* 剧集:电影类型 ID 换成剧集类型 ID,无对应则丢弃(不按类型过滤而非筛空) */
if (isTV && genres.length) {
var mapped = [];
genres.forEach(function (g) { var tv = MOVIE_TO_TV_GENRE[g]; if (tv && mapped.indexOf(tv) === -1) mapped.push(tv); });
genres = mapped;
}
if (genres.length) p.push("with_genres=" + genres.join("|"));
/* 地区/语言:选「华语」按原始语言(zh)过滤,其余按制片国家过滤;
用户未手选地区(仍为「全部」)时,分类自带的 originCC 默认值生效(如"华语佳片"默认大陆|香港|台湾) */
if (f.region === "华语") {
p.push("with_original_language=zh");
} else if (f.region !== "全部" && REGION_CC[f.region]) {
p.push("with_origin_country=" + REGION_CC[f.region]);
} else if (f.region === "全部" && def.originCC && def.originCC.length) {
p.push("with_origin_country=" + def.originCC.join("|"));
}
/* 票数门槛 / 年代上限 */
if (def.minVotes) p.push("vote_count.gte=" + def.minVotes);
if (def.maxVotes) p.push("vote_count.lte=" + def.maxVotes);
if (def.yearLte) p.push(lteField + "=" + def.yearLte);
/* 用户选择年份 */
var userYear = (f.year && f.year !== "全部");
if (userYear) {
if (f.year === "更早") p.push(lteField + "=2019-12-31");
else p.push((isTV ? "first_air_date_year" : "primary_release_year") + "=" + f.year);
}
/* 近两年偏好:仅“最新热门”启用,且未手选年份时;
其余分类(地区/类型等)按热度全量拉取,总页数多,能一直加载更多 */
var biasRecent = (def.recent && !def.airSort && sort === "popularity.desc" && !userYear);
/* 华语热播:限定近90天首播,按上线日期降序 */
if (def.airSort && !userYear) {
var now = new Date(), ago90 = new Date(now.getTime() - 90 * 86400000);
p.push(lteField + "=" + now.toISOString().slice(0, 10));
p.push(gteField + "=" + ago90.toISOString().slice(0, 10));
}
if (biasRecent) {
var today = new Date(), past = new Date(today.getTime() - 730 * 86400000);
p.push(lteField + "=" + today.toISOString().slice(0, 10));
p.push(gteField + "=" + past.toISOString().slice(0, 10));
}
if (sort === "popularity.desc" && p.join("&").indexOf("vote_count.gte") === -1) p.push("vote_count.gte=" + (biasRecent ? 20 : 10));
if (sort === "vote_average.desc" && p.join("&").indexOf("vote_count.gte") === -1) p.push("vote_count.gte=200");
p.push("sort_by=" + sort);
return request(base + "?" + p.join("&"), { timeout: 18 }).then(function (d) {
var arr = (d && d.results) ? d.results : [];
var items = arr.map(function (r) { return mapTmdb(r, isTV); }).filter(function (x) { return x && x.poster; });
return { items: items, total: (d && d.total_pages) ? d.total_pages : 1 };
});
}
function dedupeById(list) {
var seen = {}, out = [];
(list || []).forEach(function (m) { var id = pick(m, "id", ""); if (id && seen[id]) return; if (id) seen[id] = 1; out.push(m); });
return out;
}
/* 从 fromPage 起顺序拉 count 页(容错:某页失败就停在已拿到的),
过程中持续更新 STATE.page / totalPages / hasMore */
function tmdbPages(cat, f, fromPage, count) {
var acc = [];
function step(pn, left) {
if (left <= 0) return Promise.resolve();
return tmdbPage(cat, f, pn).then(function (r) {
acc = acc.concat(r.items);
STATE.totalPages = r.total || STATE.totalPages || 1;
STATE.page = pn;
STATE.hasMore = (pn < STATE.totalPages);
if (!STATE.hasMore) return;
return step(pn + 1, left - 1);
}, function () { STATE.hasMore = ((pn - 1) < (STATE.totalPages || 1)); });
}
return step(fromPage, count).then(function () { return acc; });
}
function loadCatalog(cat) {
STATE.page = 1; STATE.totalPages = 1; STATE.hasMore = false; STATE.loadingMore = false;
if (CONFIG.catalogApi) {
STATE.clientFilter = true;
var url = CONFIG.catalogApi + (CONFIG.catalogApi.indexOf("?") !== -1 ? "&" : "?") + "cat=" + encodeURIComponent(cat);
return request(url).then(function (data) { return (data && data.list) ? data.list : (Array.isArray(data) ? data : []); });
}
if (tmdbEnabled()) {
STATE.clientFilter = false;
/* 首屏直接拉 3 页 ≈ 60 部,量大一点;剩下的滚动到底自动续 */
return tmdbPages(cat, STATE.filters, 1, 3).then(function (items) {
if (!items.length) { STATE.clientFilter = true; STATE.hasMore = false; return demoFor(cat); }
return dedupeById(items);
}).catch(function () { STATE.clientFilter = true; STATE.hasMore = false; return demoFor(cat); });
}
STATE.clientFilter = true;
return Promise.resolve(demoFor(cat));
}
/* 加载更多(仅 TMDB 分页):一次续 2 页,攒得更快 */
function loadMore() {
if (STATE.clientFilter || !STATE.hasMore || STATE.loadingMore) return;
STATE.loadingMore = true;
var b = $("#listState .loadmore"); if (b) { b.textContent = "加载中…"; }
var token = STATE.reqToken, from = (STATE.page || 1) + 1;
tmdbPages(STATE.cat, STATE.filters, from, 2).then(function (items) {
if (token !== STATE.reqToken) return;
STATE.full = dedupeById((STATE.full || []).concat(items));
STATE.loadingMore = false; STATE._appendMode = true; renderList(token);
}).catch(function () { if (token !== STATE.reqToken) return; STATE.loadingMore = false; STATE.hasMore = false; renderList(token); });
}
/* ============================================================
首页
============================================================ */
function renderNav() {
var nav = $("#nav");
if (!nav.childNodes.length) {
CATS.forEach(function (c) {
var b = el("button", "navtab focusable"); b.textContent = c.name;
b.setAttribute("data-cat", c.key); b.setAttribute("data-fk", "cat-" + c.key);
b.onclick = function () { setCat(c.key); }; nav.appendChild(b);
});
}
Array.from(nav.children).forEach(function (b) { b.classList.toggle("on", b.getAttribute("data-cat") === STATE.cat); });
var c = CATS.filter(function (x) { return x.key === STATE.cat; })[0];
$("#listTitle").textContent = (STATE.cat === "new") ? "TG 4K Movies" : (c ? c.name : "TG 4K Movies");
}
function setCat(k) {
if (STATE.cat === k) return;
var from = catIndex();
STATE.cat = k;
/* 切换方向:往后(下一个)内容从右侧滑入,往前从左侧滑入 */
STATE.swapDir = (catIndex() > from) ? "fwd" : "back";
/* 切换分类后筛选自动重置;默认排序取该分类的自然排序(高分类默认按评分) */
STATE.filters = { genre: "全部", region: "全部", year: "全部", sort: (catDef(k).sort === "vote" ? "评分" : "最新") };
syncFilterPills();
syncRegionOptions();
renderNav(); renderGrid(); scrollNavToActive();
}
/* 当前分类在 CATS 中的序号 */
function catIndex() { for (var i = 0; i < CATS.length; i++) if (CATS[i].key === STATE.cat) return i; return 0; }
/* 把激活的分类标签滚到可见居中位置(滑动切换后用得上) */
function scrollNavToActive() {
var nav = $("#nav"); if (!nav) return;
var on = nav.querySelector(".navtab.on"); if (!on) return;
try { on.scrollIntoView({ block: "nearest", inline: "center" }); } catch (e) {}
}
/* 网格切换动效:按方向滑入 + 淡入;可重复触发(先移除类、强制回流再加回)
TV 端与“减少动态效果”下跳过,只换数据不做动画 */
function prefersReduceMotion() {
if (!window.matchMedia) return false;
try { return window.matchMedia("(prefers-reduced-motion: reduce)").matches; } catch (e) { return false; }
}
function playGridEnter() {
if (!STATE.swapDir) return;
if (IS_TV || prefersReduceMotion()) return;
var grid = $("#grid"); if (!grid) return;
var cls = STATE.swapDir === "back" ? "gswap-back" : "gswap-fwd";
grid.classList.remove("gswap-fwd", "gswap-back");
void grid.offsetWidth; /* 强制回流,确保动画从头播放 */
grid.classList.add(cls);
}
/* 按方向切换到相邻分类:delta=+1 下一个,-1 上一个;到边界不动 */
function switchCatBy(delta) {
var i = catIndex() + delta;
if (i < 0 || i >= CATS.length) return false;
var c = CATS[i];
setCat(c.key);
toast(c.name);
return true;
}
function deriveTag(item) {
var q = (pick(item, "quality", "") + " " + pick(item, "remark", "")).toUpperCase();
if (/HDR|DV|杜比视界/.test(q)) return { t: "HDR", c: "tHDR" };
if (/原盘|REMUX|DOLBY|蓝光|BLURAY/.test(q)) return { t: "Dolby", c: "tDolby" };
if (/4K|2160/.test(q)) return { t: "4K", c: "t4K" };
if (/WEB|1080/.test(q)) return { t: "Web", c: "tWeb" };
return { t: "HD", c: "tHD" };
}
function buildCard(item) {
var card = el("button", "card focusable");
card.setAttribute("data-fk", "card-" + pick(item, "id", Math.random())); card.setAttribute("data-id", pick(item, "id", ""));
var poster = el("div", "poster"); poster.appendChild(posterInner(item));
/* 左上角标:有画质标画质,否则标评分(只在这一处显示,避免重复) */
if (pick(item, "quality", "")) {
var tg = deriveTag(item); var qt = el("span", "qtag " + tg.c); qt.textContent = tg.t; poster.appendChild(qt);
var rmk = pick(item, "remark", ""); if (rmk && rmk !== tg.t) { var rm = el("span", "remark"); rm.textContent = rmk; poster.appendChild(rm); }
} else {
var sc = num(item.rating) || num(item.imdb) || num(item.douban);
if (sc > 0) { var st = el("span", "qtag tStar"); st.textContent = "★ " + sc; poster.appendChild(st); }
}
/* 左下角标:年份(挪到海报上,让下方只留一行片名,卡片更紧凑) */
if (pick(item, "year", "")) { var yb = el("span", "yeartag"); yb.textContent = item.year; poster.appendChild(yb); }
card.appendChild(poster);
var meta = el("div", "meta");
var t = el("div", "t"); t.textContent = pick(item, "title", ""); meta.appendChild(t);
card.appendChild(meta);
card.onclick = function () { openDetail(item); };
return card;
}
function stateNode(title, sub, retry) {
var d = el("div", "state"); var b = el("b"); b.textContent = title; d.appendChild(b);
d.appendChild(document.createTextNode(sub || ""));
if (retry) { var btn = el("button", "focusable"); btn.textContent = "重试"; btn.setAttribute("data-fk", "retry"); btn.onclick = retry; d.appendChild(document.createElement("br")); d.appendChild(btn); }
return d;
}
function renderGrid() {
var grid = $("#grid"), st = $("#listState"), token = ++STATE.reqToken;
/* 切换分类时立即清空换骨架(即时反馈),否则仅在空时铺骨架 */
if (!grid.children.length || STATE.swapDir) {
var f0 = document.createDocumentFragment();
for (var i = 0; i < 6; i++) { var c = el("div", "card"); c.appendChild(el("div", "skel poster-skel")); f0.appendChild(c); }
grid.replaceChildren(f0);
playGridEnter();
}
st.replaceChildren();
loadCatalog(STATE.cat).then(function (list) {
if (token !== STATE.reqToken) return;
STATE.full = list || [];
renderList(token);
}).catch(function (e) {
if (token !== STATE.reqToken) return;
grid.replaceChildren(); st.replaceChildren(stateNode("加载失败", String(e && e.message || e), function () { renderGrid(); }));
});
}
function renderList(token) {
if (token == null) token = STATE.reqToken;
var grid = $("#grid"), st = $("#listState");
STATE.list = STATE.clientFilter ? applyFilters(STATE.full) : (STATE.full || []).slice();
updateFilterCount();
if (!STATE.list.length) { grid.replaceChildren(); st.replaceChildren(stateNode("没有符合条件的影片", "试试放宽筛选", null)); STATE.swapDir = null; STATE._appendMode = false; STATE._renderedCount = 0; return; }
st.replaceChildren();
/* 交错淡入:续载(append)只动画新增的卡,分类切换交给容器滑入(gswap)不重复动画 */
var appendMode = !!STATE._appendMode;
var startAnim = appendMode ? (STATE._renderedCount || 0) : 0;
var doStagger = !STATE.swapDir && !IS_TV && !prefersReduceMotion();
var frag = document.createDocumentFragment();
STATE.list.forEach(function (item, i) {
var c = buildCard(item);
if (doStagger && i >= startAnim) { c.classList.add("cin"); c.style.animationDelay = Math.min((i - startAnim) * 35, 420) + "ms"; }
frag.appendChild(c);
});
grid.replaceChildren(frag);
playGridEnter(); /* 真实内容铺好后再滑入一次 */
STATE.swapDir = null; /* 仅切换分类时容器动画,筛选/加载更多不触发 */
STATE._appendMode = false;
STATE._renderedCount = STATE.list.length;
enrichPosters(STATE.list);
if (!STATE.clientFilter && STATE.hasMore) {
var more = el("button", "loadmore focusable"); more.setAttribute("data-fk", "loadmore");
more.textContent = STATE.loadingMore ? "加载中…" : "加载更多";
more.onclick = function () { loadMore(); };
st.appendChild(more);
}
}
function applyFilters(list) {
var f = STATE.filters, out = (list || []).slice();
if (f.genre !== "全部") out = out.filter(function (m) { return (pick(m, "genre", "")).indexOf(f.genre) !== -1; });
if (f.region !== "全部") out = out.filter(function (m) { return (pick(m, "country", "")).indexOf(f.region) !== -1; });
if (f.year !== "全部") out = out.filter(function (m) {
if (f.year === "更早") return num(m.year) > 0 && num(m.year) < 2020;
return String(pick(m, "year", "")) === f.year;
});
if (f.sort === "评分") out.sort(function (a, b) { return (num(b.rating) || num(b.imdb)) - (num(a.rating) || num(a.imdb)); });
else out.sort(function (a, b) { return num(b.year) - num(a.year); });
return out;
}
function updateFilterCount() { var r = $("#fres"); if (r) r.textContent = "共 " + STATE.list.length + " 部"; }
/* 地区选项收窄:分类若带 originCC(如"华语佳片"=大陆|香港|台湾),
地区筛选面板里只保留「全部」+ originCC 覆盖到的地区名,其余地区(含"华语")隐藏不可选;
普通分类(无 originCC)不受限,仍展示完整地区列表 */
function regionOptionsFor(def) {
if (!def || !def.originCC || !def.originCC.length) return null;
var rev = {}; Object.keys(REGION_CC).forEach(function (k) { rev[REGION_CC[k]] = k; });
var names = ["全部"];
def.originCC.forEach(function (cc) { var name = rev[cc]; if (name && names.indexOf(name) === -1) names.push(name); });
return names;
}
function syncRegionOptions() {
var box = $("#filters"); if (!box) return;
var allow = regionOptionsFor(catDef(STATE.cat));
Array.from(box.querySelectorAll('.fpill[data-fkey="region"]')).forEach(function (b) {
b.classList.toggle("hidden", !!allow && allow.indexOf(b.getAttribute("data-fval")) === -1);
});
}
/* 筛选器 */
function renderFilters() {
var box = $("#filters"); if (box.childNodes.length) { syncFilterPills(); syncRegionOptions(); return; }
var rows = [["类型","genre"],["地区","region"],["年份","year"],["排序","sort"]];
var frag = document.createDocumentFragment();
rows.forEach(function (r) {
var label = r[0], key = r[1];
var row = el("div", "filterrow");
var lb = el("span", "flabel"); lb.textContent = label; row.appendChild(lb);
FILTERS[key].forEach(function (opt) {
var b = el("button", "fpill focusable"); b.textContent = opt;
b.setAttribute("data-fkey", key); b.setAttribute("data-fval", opt); b.setAttribute("data-fk", "f-" + key + "-" + opt);
if (STATE.filters[key] === opt) b.classList.add("on");
b.onclick = function () { STATE.filters[key] = opt; syncFilterPills(); renderGrid(); };
row.appendChild(b);
});
frag.appendChild(row);
});
var tools = el("div", "ftools");
var res = el("span", "res"); res.id = "fres"; tools.appendChild(res);
var clr = el("button", "fclear focusable"); clr.textContent = "清除筛选"; clr.setAttribute("data-fk", "fclear");
clr.onclick = function () { STATE.filters = { genre: "全部", region: "全部", year: "全部", sort: "最新" }; syncFilterPills(); renderGrid(); };
tools.appendChild(clr);
frag.appendChild(tools);
box.replaceChildren(frag);
syncFiltersOpen();
syncRegionOptions();
}
function syncFilterPills() {
Array.from($("#filters").querySelectorAll(".fpill")).forEach(function (b) {
b.classList.toggle("on", STATE.filters[b.getAttribute("data-fkey")] === b.getAttribute("data-fval"));
});
syncFiltersOpen();
}
function syncFiltersOpen() {
var box = $("#filters"), btn = $("#ftoggle"); if (!box || !btn) return;
box.classList.toggle("collapsed", !STATE.filtersOpen);
btn.classList.toggle("open", STATE.filtersOpen);
var cnt = $("#ftoggleCount"); if (!cnt) return;
var n = 0, f = STATE.filters;
if (f.genre !== "全部") n++; if (f.region !== "全部") n++; if (f.year !== "全部") n++; if (f.sort !== "最新") n++;
cnt.textContent = n ? String(n) : "";
cnt.classList.toggle("hidden", !n);
}
/* ============================================================
详情
============================================================ */
function openDetail(item) {
STATE.detail = item;
var st = { route: "detail", id: pick(item, "id", "") };
/* 已在详情页内再打开新片(如点“猜你喜欢”)时替换当前历史记录,
避免历史层级叠加导致返回要按多次 */
if (document.body.classList.contains("detail-active")) history.replaceState(st, "", "#detail");
else history.pushState(st, "", "#detail");
showDetail(item);
}
function metaRow(k, v) { var r = el("div", "metarow"); var kk = el("div", "k"); kk.textContent = k; var vv = el("div", "v"); vv.textContent = v; r.appendChild(kk); r.appendChild(vv); return r; }
/* 多值字符串拆分(/ , 、 顿号等分隔),去空 */
function splitVals(v) { return String(v == null ? "" : v).split(/\s*[\/,、,]\s*/).map(function (s) { return s.trim(); }).filter(function (s) { return s; }); }
/* 元数据标签:可带前缀小标签(如“导演”),key=true 时高亮成金色 */
function metaTag(label, value, key) {
var s = el("span", "mtag" + (key ? " key" : ""));
if (label) { var l = el("span", "ml"); l.textContent = label; s.appendChild(l); }
s.appendChild(document.createTextNode(value));
return s;
}
/* TMDB 影片 logo(中文或英文,PNG/SVG,取第一个) */
function getTmdbLogo(art) {
if (!tmdbEnabled() || !art || !art.id) return Promise.resolve("");
var t = CONFIG.tmdb, type = art.type === "tv" ? "tv" : "movie";
var ck = "logo2_" + type + "_" + art.id;
return Store.get(ck).then(function (cached) {
if (cached !== undefined && cached !== null && cached !== "") { try { return JSON.parse(cached); } catch(e) { return cached; } }
var url = (t.apiBase || "https://api.tmdb.org/3") + "/" + type + "/" + art.id +
"/images?api_key=" + encodeURIComponent(t.apiKey) + "&include_image_language=en,zh,null";
return request(url, { timeout: 12 }).then(function (data) {
var logos = (data && data.logos) ? data.logos : [];
/* 优先中文,其次英文,再次任意 */
var pick_zh = null, pick_en = null, pick_any = null, i;
for (i = 0; i < logos.length; i++) {
var lg = logos[i];
if (!lg || !lg.file_path) continue;
if (!pick_any) pick_any = lg;
if (!pick_zh && lg.iso_639_1 === "zh") { pick_zh = lg; }
if (!pick_en && lg.iso_639_1 === "en") { pick_en = lg; }
}
var chosen = pick_zh || pick_en || pick_any;
var logoUrl = chosen ? (t.imageBase + "w500" + chosen.file_path) : "";
Store.set(ck, JSON.stringify(logoUrl));
return logoUrl;
});
}).catch(function () { return ""; });
}
function applyHeroLogo(logoUrl) {
var el2 = $("#dLogo"); if (!el2) return;
el2.replaceChildren();
if (logoUrl) {
var img = makeImg(logoUrl, "");
img.onerror = function () { el2.classList.remove("has-logo"); el2.classList.add("hidden"); };
el2.appendChild(img);
el2.classList.add("has-logo");
el2.classList.remove("hidden");
} else {
el2.classList.remove("has-logo");
el2.classList.add("hidden");
}
}
function renderDetailInfo(item) {
$("#dTitle").textContent = pick(item, "title", "—") + (pick(item, "year", "") ? (" (" + item.year + ")") : "");
var chips = $("#dChips"); chips.replaceChildren();
if (pick(item, "quality", "")) { var qc = el("span", "chip"); qc.textContent = item.quality; chips.appendChild(qc); }
/* 评分移至 body 区 dRating,做成彩色赛博卡片标签 */
var dr = $("#dRating"); dr.replaceChildren();
if (num(item.rating) > 0) { var rt = el("span", "rtag st"); rt.textContent = "★ " + item.rating; dr.appendChild(rt); }
if (num(item.imdb) > 0) { var im = el("span", "rtag im"); im.textContent = "IMDb " + item.imdb; dr.appendChild(im); }
if (num(item.douban) > 0) { var db = el("span", "rtag db"); db.textContent = "豆瓣 " + item.douban; dr.appendChild(db); }
if (!dr.children.length) dr.classList.add("hidden"); else dr.classList.remove("hidden");
/* 元数据精简为一排圆角标签:国家 / 类型 / 时间 / 时长(导演已并入下方“团队阵容”) */
var mb = $("#metablk"); mb.replaceChildren(); mb.className = "metatags";
var frag = document.createDocumentFragment();
splitVals(pick(item, "country", "")).forEach(function (c) { frag.appendChild(metaTag("", c, true)); });
splitVals(pick(item, "genre", "")).forEach(function (g) { frag.appendChild(metaTag("", g)); });
var when = pick(item, "date", "") || (pick(item, "year", "") ? String(item.year) : "");
if (when) frag.appendChild(metaTag("", when));
if (num(item.runtime) > 0) frag.appendChild(metaTag("", num(item.runtime) + " 分钟"));
mb.appendChild(frag);
if (!mb.children.length) mb.classList.add("hidden"); else mb.classList.remove("hidden");
$("#dDesc").textContent = pick(item, "desc", "") || pick(item, "overview", "") || "暂无简介。下方资源可一键复制或推送播放。";
setupDescToggle();
}
function setupDescToggle() {
var d = $("#dDesc"), btn = $("#descToggle");
if (!d || !btn) return;
d.classList.add("open"); btn.textContent = "收起";
setTimeout(function () {
if (d.scrollHeight - d.clientHeight > 4 || d.classList.contains("open")) btn.classList.remove("hidden"); else btn.classList.add("hidden");
}, 0);
}
function showDetail(item) {
closeFilmo();
STATE._detailRenderedItem = item;
document.body.classList.add("detail-active");
var sheet = $("#sheet"); sheet.classList.add("active"); sheet.setAttribute("aria-hidden", "false"); sheet.scrollTop = 0;
applyHeroImage(item);
applyHeroLogo(""); /* 先清空上一部的 logo */
renderDetailInfo(item);
renderStills(pick(item, "stills", []) || []);
renderCast(item);
/* 详情页进场动效:h1 glitch + PLAY 开机 */
(function triggerEnterAnimations() {
var h1 = sheet.querySelector(".herofg h1");
var playBtn = $("#actPlay");
if (h1) {
h1.classList.remove("glitch-enter");
void h1.offsetWidth; /* reflow 强制重新触发 */
h1.classList.add("glitch-enter");
}
if (playBtn) {
playBtn.classList.remove("power-on");
void playBtn.offsetWidth;
playBtn.classList.add("power-on");
}
})();
/* 后台预热 blur wallPic,播放时直接取缓存 */
/* 预热播放器图片(pic 预热;wallPic 运行时动态生成 blur 不预热) */
fmReady().then(function (fm) {
if (!fm || !fm.preloadArtwork) return;
var pic = pick(item, "poster", "") ? tmdbResize(pick(item, "poster", ""), "w780") : "";
if (pic) fm.preloadArtwork(pic, "");
}).catch(function () {});
STATE.playQueue = []; STATE.playIdx = 0; updatePlayBtn();
loadResources(item);
loadReco(item);
/* TMDB:补剧照大图 + 完整元数据(别名/时间/演员/简介等,仅填空缺) */
if (tmdbEnabled()) {
var artToken = STATE.reqToken;
getArtwork(item).then(function (art) {
if (!art || STATE.detail !== item || STATE.reqToken !== artToken) return;
if (art.poster && !pick(item, "poster", "")) item.poster = art.poster;
if (art.backdrop && !pick(item, "backdrop", "")) item.backdrop = art.backdrop;
applyHeroImage(item);
/* backdrop 可能刚到,重新预热 blur */
if (!(pick(item, "stills", []) || []).length) getStills(art).then(function (ss) { if (STATE.detail === item && ss.length) { item.stills = ss; renderStills(ss); applyHeroCarousel(item, ss); } }); else applyHeroCarousel(item, pick(item, "stills", []) || []);
/* 拉取影片中文徽标 */
getTmdbLogo(art).then(function (logoUrl) { if (STATE.detail === item) applyHeroLogo(logoUrl); });
/* 更新预热:pic 更新后重新预热 */
fmReady().then(function (fm) {
if (!fm || !fm.preloadArtwork || STATE.detail !== item) return;
var pic2 = pick(item, "poster", "") ? tmdbResize(pick(item, "poster", ""), "w780") : "";
if (pic2) fm.preloadArtwork(pic2, "");
}).catch(function () {});
var metaToken = STATE.reqToken;
getDetailMeta(art).then(function (m) {
if (!m || STATE.detail !== item || STATE.reqToken !== metaToken) return;
["genre","date","country","director","cast","alias","overview"].forEach(function (k) { if (!pick(item, k, "") && m[k]) item[k] = m[k]; });
if (m.castList && m.castList.length) item.castList = m.castList;
renderDetailInfo(item);
renderCast(item);
});
});
}
}
function renderStills(stills) {
var wrap = $("#stillsWrap"), box = $("#stills");
if (!stills || !stills.length) { wrap.classList.add("hidden"); box.replaceChildren(); return; }
var frag = document.createDocumentFragment();
stills.forEach(function (u) { var s = el("div", "s"); s.appendChild(makeImg(u, "")); frag.appendChild(s); });
box.replaceChildren(frag); wrap.classList.remove("hidden");
}
/* 演员头像区:完全同步参考文件 renderCast,只用 TMDB castList(已按 profile_path 过滤),没有头像的人不会出现在列表里,不做任何占位/兜底 */
function castAvatar(name, url) {
var av = el("span", "cav");
av.appendChild(makeImg(url, name));
return av;
}
function renderCast(item) {
var wrap = $("#castWrap"), box = $("#cast");
if (!wrap || !box) return;
var list = (pick(item, "castList", []) || []).slice(0, 14);
if (!list.length) { wrap.classList.add("hidden"); box.replaceChildren(); return; }
var frag = document.createDocumentFragment();
list.forEach(function (p) {
var chip = el("div", "castchip");
chip.appendChild(castAvatar(p.name, p.avatar));
var tx = el("span", "ctx");
var nm = el("span", "cname"); nm.textContent = p.name || ""; tx.appendChild(nm);
if (p.role) { var rl = el("span", "crole" + (p.role === "导演" ? " role-dir" : "")); rl.textContent = p.role; tx.appendChild(rl); }
chip.appendChild(tx);
chip.onclick = (function (person, chipEl) { return function () { openFilmo(person, chipEl); }; })(p, chip);
frag.appendChild(chip);
});
box.replaceChildren(frag); wrap.classList.remove("hidden");
}
/* ===== 演员相关作品:内联展开(点头像 → 下方出现一排同款首页卡片,已排除综艺类) ===== */
var _filmoWrap = null; /* 当前展开的 .cast-filmo-wrap 节点 */
var _filmoChip = null; /* 当前高亮的 castchip */
var _filmoOpen = false;
var FILMO_EXCLUDE_GENRES = [10764, 10767]; /* TMDB TVReality(综艺/真人秀) + Talk(脱口秀) */
function openFilmo(person, chipEl) {
var castWrap = $("#castWrap"); if (!castWrap) return;
/* 若点同一个人则折叠关闭 */
if (_filmoChip === chipEl && _filmoOpen) { closeFilmo(); return; }
closeFilmo();
if (!tmdbEnabled()) return;
/* 高亮选中演员 */
if (chipEl) { chipEl.classList.add("filmo-active"); _filmoChip = chipEl; }
/* 创建内联展开容器,插在 castWrap 内部 castrow 之后 */
var wrap = el("div", "cast-filmo-wrap");
var inner = el("div", "cast-filmo-inner");
/* 小标题 */
var head = el("div", "cast-filmo-head");
var nameEl = el("span", "cast-filmo-name"); nameEl.textContent = (person.name || "") + " 的作品";
var closeBtn = el("button", "cast-filmo-close"); closeBtn.textContent = "×"; closeBtn.onclick = closeFilmo;
head.appendChild(nameEl); head.appendChild(closeBtn);
inner.appendChild(head);
/* loading */
var loadingEl = el("div", "cast-filmo-loading"); loadingEl.textContent = "LOADING ···";
inner.appendChild(loadingEl);
wrap.appendChild(inner);
castWrap.appendChild(wrap);
_filmoWrap = wrap; _filmoOpen = true;
/* 动画展开 */
requestAnimationFrame(function () { wrap.classList.add("open"); });
/* 拉 TMDB */
var t = CONFIG.tmdb;
var pid = person.tmdbPersonId;
function doLoad(id) {
var url = (t.apiBase || "https://api.tmdb.org/3") + "/person/" + id + "/combined_credits?api_key=" + encodeURIComponent(t.apiKey) + "&language=" + encodeURIComponent(t.lang || "zh-CN");
request(url, { timeout: 14 }).then(function (d) {
if (!_filmoOpen || _filmoWrap !== wrap) return;
var cast = (d && d.cast) ? d.cast : [];
/* 排除综艺/真人秀/脱口秀(仅针对剧集类,电影无此类型 id 冲突) */
cast = cast.filter(function (r) {
if (r.media_type !== "tv") return true;
var gids = r.genre_ids || [];
for (var i = 0; i < gids.length; i++) { if (FILMO_EXCLUDE_GENRES.indexOf(gids[i]) !== -1) return false; }
return true;
});
var seen = {}, items = [];
cast.sort(function (a, b) { return (b.popularity || 0) - (a.popularity || 0); });
cast.forEach(function (r) { if (r.poster_path && !seen[r.id]) { seen[r.id] = 1; items.push(r); } });
items = items.slice(0, 20);
inner.replaceChildren(head);
if (!items.length) { var emp = el("div", "cast-filmo-loading"); emp.textContent = "暂无相关作品"; inner.appendChild(emp); return; }
var grid = el("div", "filmogrid-inline");
items.forEach(function (r) {
var mapped = mapTmdb(r, r.media_type === "tv");
var cell = el("div", "fg-cell");
var card = buildCard(mapped);
cell.appendChild(card);
grid.appendChild(cell);
});
inner.appendChild(grid);
}).catch(function () { if (_filmoWrap === wrap) { inner.replaceChildren(head); var e2 = el("div", "cast-filmo-loading"); e2.textContent = "加载失败"; inner.appendChild(e2); } });
}
if (pid) { doLoad(pid); }
else if (person.name) {
var surl = (t.apiBase || "https://api.tmdb.org/3") + "/search/person?api_key=" + encodeURIComponent(t.apiKey) + "&language=" + encodeURIComponent(t.lang || "zh-CN") + "&query=" + encodeURIComponent(person.name);
request(surl, { timeout: 12 }).then(function (d) {
if (!_filmoOpen || _filmoWrap !== wrap) return;
var r = d && d.results && d.results[0];
if (!r) { inner.replaceChildren(head); var e3 = el("div", "cast-filmo-loading"); e3.textContent = "未找到演员数据"; inner.appendChild(e3); return; }
person.tmdbPersonId = r.id; doLoad(r.id);
}).catch(function () { if (_filmoWrap === wrap) { inner.replaceChildren(head); var e4 = el("div", "cast-filmo-loading"); e4.textContent = "加载失败"; inner.appendChild(e4); } });
} else {
inner.replaceChildren(head); var e5 = el("div", "cast-filmo-loading"); e5.textContent = "无演员ID"; inner.appendChild(e5);
}
}
function closeFilmo() {
if (_filmoWrap) { _filmoWrap.classList.remove("open"); var w = _filmoWrap; setTimeout(function () { if (w.parentNode) w.parentNode.removeChild(w); }, 350); _filmoWrap = null; }
if (_filmoChip) { _filmoChip.classList.remove("filmo-active"); _filmoChip = null; }
_filmoOpen = false;
}
/* ---------- 猜你喜欢(TMDB 同类推荐) ---------- */
function renderReco(items) {
var wrap = $("#recoWrap"), box = $("#reco");
if (!wrap || !box) return;
if (!items || !items.length) { wrap.classList.add("hidden"); box.replaceChildren(); return; }
var frag = document.createDocumentFragment();
items.forEach(function (it) {
var cell = el("div", "fg-cell");
cell.appendChild(buildCard(it));
frag.appendChild(cell);
});
box.replaceChildren(frag); wrap.classList.remove("hidden");
}
function pickReco(d, isTV) {
if (!d || !d.results) return [];
return d.results.map(function (r) { return mapTmdb(r, isTV); }).filter(function (x) { return x && x.poster; }).slice(0, 12);
}
function loadReco(item) {
renderReco([]); /* 先清空,避免闪上一部的推荐 */
if (!tmdbEnabled()) return;
var id = pick(item, "tmdbId", 0), type = pick(item, "tmdbType", "movie"), isTV = (type === "tv");
if (!id) return; /* 非 TMDB 来源(如聚合搜索)无 id 则不显示 */
var t = CONFIG.tmdb;
var common = "?api_key=" + encodeURIComponent(t.apiKey) + "&language=" + encodeURIComponent(t.lang || "zh-CN") + "&page=1";
var baseT = (t.apiBase || "https://api.tmdb.org/3") + "/" + type + "/" + id + "/";
function get(kind) { return request(baseT + kind + common, { timeout: 14 }); }
get("recommendations").then(function (d) {
if (STATE.detail !== item) return;
var arr = pickReco(d, isTV);
if (arr.length) { renderReco(arr); return; }
get("similar").then(function (d2) { if (STATE.detail === item) renderReco(pickReco(d2, isTV)); }, function () {});
}, function () {
get("similar").then(function (d2) { if (STATE.detail === item) renderReco(pickReco(d2, isTV)); }, function () {});
});
}
function closeDetail() {
closeFilmo();
_carouselStop();
++STATE.reqToken;
STATE.detail = null;
STATE._detailRenderedItem = null;
document.body.classList.remove("detail-active");
var sheet = $("#sheet"); sheet.classList.remove("active"); sheet.setAttribute("aria-hidden", "true");
/* sheet 以 transform 滑出(.26s),动画结束后再清理重型 DOM,
避免 display 切换 + DOM 清理同帧导致 reflow 卡顿,视觉上退出有延迟 */
setTimeout(function () {
renderReco([]);
var stills = $("#stills"); if (stills) stills.replaceChildren();
var cast = $("#cast"); if (cast) cast.replaceChildren();
var resgroups = $("#resgroups"); if (resgroups) resgroups.replaceChildren();
var resState = $("#resState"); if (resState) resState.replaceChildren();
sheet.scrollTop = 0;
if (IS_TV) restoreHomeFocus();
}, 300);
}
/* ---------- 资源:PanSou 检索 + 按画质分组 ---------- */
var TIER_ORDER = ["原盘", "4K", "中字4K", "1080P", "720P", "其它"];
function tierOf(name) {
var s = (name || "").toUpperCase();
var zh = /中字|中文字幕|简繁|国语|国配|双语/.test(name || "");
var remux = /REMUX|原盘|COMPLETE|UHD\.?BLURAY|BLURAY/.test(s);
var is4k = /2160P|\b4K\b|UHD|HDR|DOLBY|DV\b/.test(s);
if (remux) return "原盘";
if (is4k) return zh ? "中字4K" : "4K";
if (/1080P/.test(s)) return "1080P";
if (/720P/.test(s)) return "720P";
return "其它";
}
function sizeOf(name) {
var m = (name || "").match(/(\d+(?:\.\d+)?)\s?(GB|MB|TB)/i);
return m ? (m[1] + m[2].toUpperCase()) : "";
}
function loadResources(item) {
var resState = $("#resState"), groups = $("#resgroups");
groups.replaceChildren();
resState.replaceChildren(stateNode("正在检索资源…", "聚合网盘 / 磁力(PanSou", null));
var token = ++STATE.reqToken;
/* 已有结构化资源直接用,否则 PanSou 检索 */
var pre = pick(item, "resources", []);
if (pre && pre.length) {
renderResourcesFromRows(pre.map(function (r) {
return { name: pick(r, "name", ""), url: pick(r, "url", ""), size: pick(r, "size", "") || sizeOf(pick(r, "name", "")),
type: pick(r, "type", "") || normType("", pick(r, "url", "")), tier: pick(r, "tier", "") || tierOf(pick(r, "name", "")),
password: pick(r, "password", ""), state: "" };
}));
return;
}
var kw = pick(item, "title", ""), tries = 0;
function attempt() {
tries++;
pansouSearch(kw).then(function (rows) {
if (token !== STATE.reqToken) return;
if (!rows.length) {
if (tries < 2) { setTimeout(attempt, 400); return; } /* 空结果也再试一次 */
resState.replaceChildren(stateNode("未找到网盘资源", "暂无匹配的网盘资源", null));
return;
}
renderResourcesFromRows(rows);
}).catch(function (e) {
if (token !== STATE.reqToken) return;
if (tries < 3) { setTimeout(attempt, 500); return; } /* 自动重试,免去手点重试 */
resState.replaceChildren(stateNode("检索失败", String(e && e.message || e), function () { loadResources(item); }));
});
}
attempt();
}
function parsePansou(data, kw) {
var merged = {};
if (data && data.merged_by_type) merged = data.merged_by_type;
else if (data && data.data && data.data.merged_by_type) merged = data.data.merged_by_type;
var rows = [], seen = {};
Object.keys(merged).forEach(function (type) {
(merged[type] || []).forEach(function (r) {
var u = pick(r, "url", "") || pick(r, "link", ""); if (!u) return;
var t = normType(type, u); var key = t + "|" + u; if (seen[key]) return; seen[key] = 1;
var name = pick(r, "note", "") || pick(r, "title", "") || kw;
rows.push({ name: name, url: u, size: sizeOf(name), type: t, tier: tierOf(name),
password: pick(r, "password", "") || pick(r, "pwd", ""), state: "" });
});
});
return rows;
}
function pansouSearch(kw) {
var apiBase = CONFIG.pansou.apiBase;
function viaPost() {
return request(apiBase + "/api/search", { method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ kw: kw, res: "merge", src: "all", cloud_types: CONFIG.pansou.diskTypes }), timeout: 25 });
}
function viaGet() {
return request(apiBase + "/api/search?kw=" + encodeURIComponent(kw) + "&res=merge&src=all", { method: "GET", timeout: 25 });
}
return viaPost().then(function (d) { return parsePansou(d, kw); }, function () {
return viaGet().then(function (d) { return parsePansou(d, kw); }); /* POST 400/失败时回退 GET */
});
}
function normType(type, url) {
type = (type || "").toLowerCase(); if (DISK_LABEL[type]) return type;
url = (url || "").toLowerCase();
if (url.indexOf("magnet:") === 0) return "magnet";
if (url.indexOf("ed2k:") === 0) return "ed2k";
if (url.indexOf("thunder:") === 0) return "thunder";
if (url.indexOf("quark") !== -1) return "quark";
if (url.indexOf("aliyundrive") !== -1 || url.indexOf("alipan") !== -1) return "aliyun";
if (url.indexOf("pan.baidu") !== -1) return "baidu";
if (url.indexOf("drive.uc") !== -1) return "uc";
if (url.indexOf("cloud.189") !== -1) return "tianyi";
if (url.indexOf("xunlei") !== -1) return "xunlei";
if (url.indexOf("123") !== -1 && url.indexOf("/s/") !== -1) return "123";
if (url.indexOf("115") !== -1) return "115";
if (url.indexOf("139.com") !== -1 || url.indexOf("caiyun") !== -1) return "mobile";
return "others";
}
/* 体积数值(MB)用于排序 */
function sizeVal(s) { var m = (s || "").match(/(\d+(?:\.\d+)?)\s?(TB|GB|MB)/i); if (!m) return 0; var n = parseFloat(m[1]), u = m[2].toUpperCase(); return u === "TB" ? n * 1048576 : (u === "GB" ? n * 1024 : n); }
function diskPriority(t) { var o = ["quark","baidu","aliyun","uc","tianyi","115","123","xunlei","mobile"]; var i = o.indexOf(t); if (i !== -1) return i; if (t === "magnet") return 20; if (t === "thunder") return 21; if (t === "ed2k") return 22; return 30; }
/* 同一发布名跨网盘只留一条(优先夸克/阿里等稳定盘) */
function normName(s) { return String(s || "").toLowerCase().replace(/[\s._\-\[\]()【】]+/g, ""); }
function dedupeByName(rows) {
var sorted = (rows || []).slice().sort(function (a, b) { return diskPriority(a.type) - diskPriority(b.type); });
var seen = {}, out = [];
sorted.forEach(function (r) { var k = (r.tier || "") + "|" + normName(r.name); if (seen[k]) return; seen[k] = 1; out.push(r); });
return out;
}
function groupResources(rows) {
var g = {}; rows.forEach(function (r) { var t = r.tier || "其它"; if (!g[t]) g[t] = []; g[t].push(r); });
Object.keys(g).forEach(function (t) { g[t].sort(function (a, b) { var d = sizeVal(b.size) - sizeVal(a.size); return d !== 0 ? d : (diskPriority(a.type) - diskPriority(b.type)); }); });
var ordered = [];
TIER_ORDER.forEach(function (t) { if (g[t]) { ordered.push({ tier: t, items: g[t] }); delete g[t]; } });
Object.keys(g).forEach(function (t) { ordered.push({ tier: t, items: g[t] }); });
return ordered;
}
function tierClass(t) { return ["原盘","4K","中字4K","1080P","其它"].indexOf(t) !== -1 ? t : "其它"; }
/* 只保留高画质(原盘 / 4K / 中字4K,画质关键词由 tierOf 检测);若一条都没有则回退全部,避免空列表 */
var HIGH_TIERS = ["原盘", "4K", "中字4K"];
function filterHighQuality(rows) {
var hi = (rows || []).filter(function (r) { return HIGH_TIERS.indexOf(r.tier || tierOf(r.name)) !== -1; });
return hi.length ? hi : (rows || []);
}
function renderResourcesFromRows(rows) {
rows = dedupeByName(filterHighQuality(rows));
if (!rows.length) { $("#resState").replaceChildren(stateNode("暂无可用资源", "暂无匹配的网盘资源", null)); $("#resgroups").replaceChildren(); return; }
$("#resState").replaceChildren();
STATE.expand = {};
STATE.resTier = null;
renderResGroups(groupResources(rows));
scheduleChecks();
}
var RES_LIMIT = 6;
function renderResGroups(ordered) {
var box = $("#resgroups"); box.replaceChildren();
if (!ordered.length) { STATE.allRes = []; resetPlayQueue(); return; }
STATE.allRes = [];
if (!STATE.expand) STATE.expand = {};
/* 全部资源入队列/检测(不分当前显示的是哪类) */
ordered.forEach(function (grp) { grp.items.forEach(function (r) { STATE.allRes.push(r); }); });
/* 选中的画质类别:默认第一类(原盘优先),失效则回退第一类 */
var tiers = ordered.map(function (g) { return g.tier; });
if (!STATE.resTier || tiers.indexOf(STATE.resTier) === -1) STATE.resTier = tiers[0];
/* —— 类别标签:原盘 / 4K / 中字4K… 并排一行 —— */
var tabs = el("div", "restabs");
ordered.forEach(function (grp) {
var tab = el("button", "restab focusable");
tab.setAttribute("data-tier", grp.tier);
tab.setAttribute("data-fk", "restab-" + grp.tier);
if (grp.tier === STATE.resTier) tab.classList.add("on");
var lbl = el("span", "rt-label"); lbl.textContent = grp.tier; tab.appendChild(lbl);
var cnt = el("span", "rt-cnt"); cnt.textContent = grp.items.length; tab.appendChild(cnt);
tab.onclick = function () { if (STATE.resTier === grp.tier) return; STATE.resTier = grp.tier; renderResGroups(ordered); };
tabs.appendChild(tab);
});
box.appendChild(tabs);
/* —— 下方资源框:只展示选中类别,切标签即换内容 —— */
var sel = ordered.filter(function (g) { return g.tier === STATE.resTier; })[0] || ordered[0];
var content = el("div", "resbox");
var expanded = !!STATE.expand[sel.tier];
var show = expanded ? sel.items : sel.items.slice(0, RES_LIMIT);
show.forEach(function (r) { content.appendChild(buildResItem(r)); });
if (sel.items.length > RES_LIMIT) {
var more = el("button", "resmore focusable"); more.setAttribute("data-fk", "more-" + sel.tier);
more.textContent = expanded ? "收起" : ("展开全部 " + sel.items.length + " 条 ▾");
more.onclick = function () { STATE.expand[sel.tier] = !expanded; renderResGroups(ordered); };
content.appendChild(more);
}
box.appendChild(content);
resetPlayQueue();
}
function resBadges(r) {
var s = (r.name || "").toUpperCase(), zh = (r.name || ""), out = [];
if (/2160P|\b4K\b|UHD/.test(s)) out.push("4K"); else if (/1080P/.test(s)) out.push("1080P"); else if (/720P/.test(s)) out.push("720P");
if (/REMUX/.test(s)) out.push("REMUX"); else if (/原盘|BLURAY|BDMV|COMPLETE|蓝光/.test(zh + s)) out.push("原盘");
else if (/WEB-?DL|WEBRIP|WEB\b/.test(s)) out.push("WEB");
if (/DOLBY\s?VISION|\bDV\b|杜比视界/.test(zh + s)) out.push("DV");
if (/HDR10\+|HDR/.test(s)) out.push("HDR");
if (/X265|H\.?265|HEVC/.test(s)) out.push("H265"); else if (/X264|H\.?264|AVC/.test(s)) out.push("H264");
if (/ATMOS/.test(s)) out.push("Atmos");
if (/国语|国配|双语/.test(zh)) out.push("国语");
if (/中字|中文字幕|简繁|内封|内嵌/.test(zh)) out.push("中字");
if (/高码率?|高码版/.test(zh)) out.push("高码");
return out.slice(0, 5);
}
function buildResItem(r) {
var row = el("div", "resitem");
var dot = el("span", "dot " + (r.state || "")); dot.setAttribute("data-url", r.url); row.appendChild(dot);
var ri = el("div", "ri");
var tags = el("div", "tags");
var disk = el("span", "btag disk"); disk.textContent = DISK_LABEL[r.type] || r.type; tags.appendChild(disk);
/* 各 badge 赋予独立颜色 class */
var badgeClassMap = { "4K":"btag-4k","2160P":"btag-4k","UHD":"btag-4k","1080P":"btag-1080","720P":"btag-720","REMUX":"btag-remux","原盘":"btag-remux","WEB":"btag-web","DV":"btag-dv","HDR":"btag-hdr","Atmos":"btag-atmos","国语":"btag-cn","中字":"btag-cn","H265":"btag-h265","H264":"btag-h264","高码":"btag-hibit" };
var badges = resBadges(r);
badges.forEach(function (b) { var t = el("span", "btag " + (badgeClassMap[b] || "")); t.textContent = b; tags.appendChild(t); });
/* 提取码:只有同时出现「高码」标签才显示,避免低码率资源也带提取码混淆 */
if (r.password && badges.indexOf("高码") !== -1) { var pw = el("span", "btag pw"); pw.textContent = "码 " + r.password; tags.appendChild(pw); }
ri.appendChild(tags);
row.appendChild(ri);
if (r.size) { var sz = el("span", "size"); sz.textContent = r.size; row.appendChild(sz); }
var ops = el("div", "ops");
var play = el("button", "op play focusable"); play.textContent = "▶ PLAY"; play.setAttribute("data-fk", "play"); play.onclick = function () { playResource(r); }; ops.appendChild(play);
row.appendChild(ops);
return row;
}
function scheduleChecks() {
if (!STATE.driveCheck || !STATE.allRes) { applyDots(); return; }
var items = STATE.allRes.filter(function (r) { return !r.state && checkable(r.type); }).slice(0, 24);
if (!items.length) return;
fmReady().then(function (fm) {
if (!fm || !fm.pan || !fm.pan.check) return;
return fm.pan.check(items.map(function (r) { return { type: r.type, url: r.url, password: r.password || "" }; })).then(function (res) {
var results = (res && res.results) ? res.results : [];
results.forEach(function (out, i) { if (items[i]) items[i].state = pick(out, "state", "uncertain"); });
pruneInvalid();
});
}).catch(function () {});
}/* 失效(bad / 不支持)的资源直接从列表移除 */
function pruneInvalid() {
var all = STATE.allRes || [];
var keep = all.filter(function (r) { var s = r.state || ""; return s !== "bad" && s !== "unsupported"; });
if (keep.length !== all.length) {
if (!keep.length) { $("#resgroups").replaceChildren(); $("#resState").replaceChildren(stateNode("暂无可用资源", "有效高画质资源暂时没找到", null)); STATE.allRes = []; resetPlayQueue(); return; }
renderResGroups(groupResources(keep));
} else {
applyDots();
}
}
function checkable(t) { return ["aliyun","quark","uc","baidu","tianyi","123","xunlei","115","mobile"].indexOf(t) !== -1; }
function applyDots() {
var dots = $("#resgroups").querySelectorAll(".dot");
dots.forEach(function (dot) {
var u = dot.getAttribute("data-url");
var r = (STATE.allRes || []).filter(function (x) { return x.url === u; })[0];
if (r) dot.className = "dot " + (r.state || "");
});
}
function resetPlayQueue() {
STATE.playQueue = (STATE.allRes || []).slice();
STATE.playIdx = 0;
updatePlayBtn();
}
function updatePlayBtn() {
var b = $("#actPlay"); if (!b) return;
var n = (STATE.playQueue || []).length;
b.textContent = (n > 1 && STATE.playIdx > 0) ? "换源 ▸" : "PLAY ▸";
}
function nextPlayableIndex(from) {
var q = STATE.playQueue || [], n = q.length; if (!n) return -1;
for (var c = 0; c < n; c++) {
var i = (from + c) % n, st = q[i].state || "";
if (st !== "bad" && st !== "unsupported") return i;
}
return from % n;
}
function doPlay(r) {
var detail = STATE.detail || {};
var pic = pick(detail, "poster", "") || "";
var wallPic = pick(detail, "backdrop", "") || pick(detail, "poster", "") || "";
if (wallPic) wallPic = tmdbResize(wallPic, "original");
if (pic) pic = tmdbResize(pic, "w780");
fmReady().then(function (fm) {
if (!fm) { toast("请在 App 内打开以推送播放"); return; }
if (fm.pan && fm.pan.play) {
fm.pan.play({ type: r.type, url: r.url, password: r.password || "", title: pick(detail, "title", "") || r.name, pic: pic, wallPic: wallPic });
}
}).catch(function (e) { toast("播放失败:" + (e && e.message || e)); });
}
/* 「播放」按钮:播当前源,播不了返回后再点自动换下一条 */
function playNext() {
var q = STATE.playQueue || [];
if (!q.length) { toast("暂无可播放资源"); return; }
var i = nextPlayableIndex(STATE.playIdx);
if (i < 0) { toast("暂无可播放资源"); return; }
var r = q[i];
STATE.playIdx = (i + 1) % q.length;
updatePlayBtn();
toast(q.length > 1 ? ("推送播放 " + (i + 1) + "/" + q.length + " · 播不了返回再点可换源") : "正在推送播放…");
doPlay(r);
}
/* 资源行的「下载」:播该条,并把换源游标接到它后面 */
function playResource(r) {
var q = STATE.playQueue && STATE.playQueue.length ? STATE.playQueue : (STATE.allRes || []);
var idx = q.indexOf(r);
if (idx !== -1 && q.length) { STATE.playIdx = (idx + 1) % q.length; updatePlayBtn(); }
toast("正在推送播放…");
doPlay(r);
}
/* 探索更多 */
function searchTitle(kw) { kw = (kw || "").trim(); if (!kw) return; openDetail({ id: "search", title: kw, desc: "“" + kw + "” 的网盘 / 磁力检索结果。" }); }
/* ---------- 主题色:点左上角 P 循环切换(琥珀金→蔷薇粉→湖蓝) ---------- */
var THEMES = ["", "rose", "ocean", "matrix", "reactor", "void"];
var THEME_NAMES = { "": "品红×电青", "rose": "热粉×电黄", "ocean": "电青×核橙", "matrix": "毒绿×血红", "reactor": "核橙×等离子蓝", "void": "幽灵紫×酸绿" };
function applyTheme(name) {
if (name && THEMES.indexOf(name) > 0) document.documentElement.setAttribute("data-theme", name);
else document.documentElement.removeAttribute("data-theme");
}
function cycleTheme() {
var cur = document.documentElement.getAttribute("data-theme") || "";
var i = THEMES.indexOf(cur); if (i === -1) i = 0;
var next = THEMES[(i + 1) % THEMES.length];
applyTheme(next);
Store.set("theme", next);
toast("主题色:" + THEME_NAMES[next]);
}
/* ---------- 搜索输入联想(TMDB 实时匹配) ---------- */
var sgState = { timer: null, token: 0 };
function tmdbSuggest(kw) {
if (!tmdbEnabled()) return Promise.resolve([]);
var t = CONFIG.tmdb;
var url = (t.apiBase || "https://api.tmdb.org/3") + "/search/multi?api_key=" + encodeURIComponent(t.apiKey) + "&language=" + encodeURIComponent(t.lang || "zh-CN") + "&include_adult=false&page=1&query=" + encodeURIComponent(kw);
return request(url, { timeout: 12 }).then(function (d) {
if (!d || !d.results) return [];
return d.results
.filter(function (r) { return r && (r.media_type === "movie" || r.media_type === "tv"); })
.map(function (r) { return mapTmdb(r, r.media_type === "tv"); })
.filter(function (x) { return x && x.title; })
.slice(0, 8);
});
}
function hideSuggest() { var box = $("#suggest"); if (box) { box.classList.remove("show"); box.replaceChildren(); } }
function renderSuggest(kw, items) {
var box = $("#suggest"); if (!box) return;
var frag = document.createDocumentFragment();
items.forEach(function (it) {
var row = el("div", "sg focusable"); row.setAttribute("data-fk", "sg-" + pick(it, "id", ""));
var pp = el("div", "pp"); pp.appendChild(posterInner(it)); row.appendChild(pp);
var tx = el("div", "tx");
var tt = el("div", "tt"); tt.textContent = pick(it, "title", ""); tx.appendChild(tt);
var meta = [(it.tmdbType === "tv" ? "剧集" : "电影"), pick(it, "year", "")].filter(function (s) { return s; }).join(" · ");
if (num(it.rating) > 0) meta += " ★ " + it.rating;
var mt = el("div", "mt"); mt.textContent = meta; tx.appendChild(mt);
row.appendChild(tx);
row.addEventListener("mousedown", function (e) { e.preventDefault(); }); /* 防止点击前输入框先失焦 */
row.onclick = function () { hideSuggest(); var qi = $("#q"); if (qi) qi.blur(); openDetail(it); };
frag.appendChild(row);
});
/* 末行:直接按关键词检索资源(保留原行为) */
var raw = el("div", "sg kwrow focusable"); raw.setAttribute("data-fk", "sg-raw");
var ic = el("div", "pp kwic"); ic.textContent = "🔍"; raw.appendChild(ic);
var rtx = el("div", "tx"); var rtt = el("div", "tt"); rtt.textContent = "直接搜索 “" + kw + "” 的资源"; rtx.appendChild(rtt); raw.appendChild(rtx);
raw.addEventListener("mousedown", function (e) { e.preventDefault(); });
raw.onclick = function () { hideSuggest(); var qi = $("#q"); if (qi) qi.blur(); searchTitle(kw); };
frag.appendChild(raw);
box.replaceChildren(frag); box.classList.add("show");
}
function setupSuggest() {
if (IS_TV) return; /* TV 端输入框只读、走原生搜索,不做联想 */
var q = $("#q"); if (!q) return;
function trigger() {
var kw = q.value.trim();
if (sgState.timer) clearTimeout(sgState.timer);
if (kw.length < 2) { hideSuggest(); return; }
sgState.timer = setTimeout(function () {
var token = ++sgState.token;
tmdbSuggest(kw).then(function (items) {
if (token !== sgState.token || q.value.trim() !== kw) return; /* 过期/已改动则丢弃 */
if (!items.length) { hideSuggest(); return; }
renderSuggest(kw, items);
}, function () {});
}, 260);
}
q.addEventListener("input", trigger);
q.addEventListener("focus", trigger);
q.addEventListener("blur", function () { setTimeout(hideSuggest, 160); });
q.addEventListener("keydown", function (e) { if (e.key === "Escape" || e.keyCode === 27) hideSuggest(); });
document.addEventListener("click", function (e) {
var inSb = e.target && e.target.closest ? e.target.closest(".searchbox") : null;
if (!inSb) hideSuggest();
});
}
/* ============================================================
chrome / 视口
============================================================ */
function applyChrome() {
fmReady().then(function (fm) {
if (!fm || !fm.ui || !fm.ui.setChrome) return;
fm.ui.setChrome({ mode: "edge", statusBarStyle: "light", navigationBarStyle: "light", startup: true }).catch(function () {});
});
}
/* ============================================================
TV 焦点
============================================================ */
var lastHomeFocus = null;
function focusables(scope) { return Array.from((scope || document).querySelectorAll(".focusable")).filter(isVisibleFocusable); }
function isVisibleFocusable(el) {
if (!el) return false; if (el.getAttribute("tabindex") === "-1") return false;
var a = el; while (a) { if (a.getAttribute && a.getAttribute("aria-hidden") === "true") return false; a = a.parentNode; }
var r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0;
}
function setFocusClass(el) { var p = document.querySelector(".focusable.focus"); if (p && p !== el) p.classList.remove("focus"); if (el) el.classList.add("focus"); }
function focusEl(el) {
if (!el) return;
try { el.focus({ preventScroll: true }); } catch (e) { try { el.focus(); } catch (e2) {} }
setFocusClass(el);
requestAnimationFrame(function () { var r = el.getBoundingClientRect(); if (r.top < 80 || r.bottom > (window.innerHeight - 20)) { try { el.scrollIntoView({ block: "center" }); } catch (e) { el.scrollIntoView(); } } });
}
function restoreHomeFocus() { if (lastHomeFocus && isVisibleFocusable(lastHomeFocus)) focusEl(lastHomeFocus); else { var f = focusables($("#home")); if (f.length) focusEl(f[0]); } }
/* 按实际宽度自适应网格列数:手机 3,平板 4-6,TV 6(覆盖 CSS 媒体查询) */
function gridColumns() {
var grid = $("#grid"); if (!grid) return 3;
var tpl = window.getComputedStyle(grid).getPropertyValue("grid-template-columns");
if (tpl && tpl.indexOf(" ") !== -1) return tpl.trim().split(/\s+/).length;
return IS_TV ? 6 : 3;
}
function geomNearest(cur, dir, scope) {
var list = focusables(scope), cr = cur.getBoundingClientRect();
var cx = cr.left + cr.width / 2, cy = cr.top + cr.height / 2, best = null, bestScore = Infinity;
list.forEach(function (el) {
if (el === cur) return; var r = el.getBoundingClientRect();
var x = r.left + r.width / 2, y = r.top + r.height / 2, dx = x - cx, dy = y - cy;
var ok = (dir === "right" && dx > 6) || (dir === "left" && dx < -6) || (dir === "down" && dy > 6) || (dir === "up" && dy < -6);
if (!ok) return;
var along = (dir === "left" || dir === "right") ? Math.abs(dx) : Math.abs(dy);
var cross = (dir === "left" || dir === "right") ? Math.abs(dy) : Math.abs(dx);
var score = along + cross * 2; if (score < bestScore) { bestScore = score; best = el; }
});
return best;
}
function onKey(e) {
var key = e.key || "", code = e.keyCode || 0;
var isLeft = key === "ArrowLeft" || code === 37, isUp = key === "ArrowUp" || code === 38;
var isRight = key === "ArrowRight" || code === 39, isDown = key === "ArrowDown" || code === 40;
var isOk = key === "Enter" || key === "OK" || code === 13 || code === 23 || code === 66;
var isBack = key === "Backspace" || key === "Escape" || key === "GoBack" || code === 27 || code === 8 || code === 4;
if (isBack) { if (STATE.detail || document.body.classList.contains("detail-active")) { e.preventDefault(); history.back(); return; } }
if (!IS_TV) return;
var cur = document.querySelector(".focusable.focus") || document.activeElement;
if (!cur || !cur.classList || !cur.classList.contains("focusable")) { if (isLeft || isUp || isRight || isDown) { e.preventDefault(); restoreHomeFocus(); return; } }
var scope = (STATE.detail || document.body.classList.contains("detail-active")) ? $("#sheet") : document;
if (isOk) { e.preventDefault(); if (cur) { if (cur.tagName === "INPUT") { cur.removeAttribute("readonly"); try { cur.focus(); } catch (er) {} } else cur.click(); } return; }
if (isLeft || isRight || isUp || isDown) {
e.preventDefault(); if (!cur) { restoreHomeFocus(); return; }
var dir = isLeft ? "left" : isRight ? "right" : isUp ? "up" : "down", next = null;
if (cur.classList.contains("card") && cur.parentNode && cur.parentNode.id === "grid") {
var cards = Array.from($("#grid").children).filter(function (c) { return c.classList.contains("focusable"); });
var idx = cards.indexOf(cur), cols = gridColumns();
if (dir === "right") next = cards[idx + 1]; else if (dir === "left") next = cards[idx - 1];
else if (dir === "down") next = cards[idx + cols]; else if (dir === "up") next = cards[idx - cols];
}
if (!next) next = geomNearest(cur, dir, scope);
if (next) focusEl(next);
}
}
function setupTvInput() { if (!IS_TV) return; var q = $("#q"); q.setAttribute("readonly", "readonly"); q.addEventListener("blur", function () { q.setAttribute("readonly", "readonly"); }); }
/* ============================================================
首页左右滑动切换分类
- 横向为主、位移够大、用时够短才触发,避免误伤竖向滚动
- 命中横向滚动条(分类条 / 筛选行 / 剧照 / 资源标签 / 输入框)时不接管
- 详情页与 TV 端不启用
============================================================ */
function setupSwipe() {
if (IS_TV) return;
var area = $("#home"); if (!area) return;
var sx = 0, sy = 0, st = 0, tracking = false;
area.addEventListener("touchstart", function (e) {
tracking = false;
if (document.body.classList.contains("detail-active")) return;
if (!e.touches || e.touches.length !== 1) return;
var t = e.target;
if (t && t.closest && t.closest(".nav, .filterrow, .stills, .restabs, input, textarea")) return;
var p = e.touches[0]; sx = p.clientX; sy = p.clientY; st = Date.now(); tracking = true;
}, { passive: true });
area.addEventListener("touchend", function (e) {
if (!tracking) return; tracking = false;
var p = e.changedTouches && e.changedTouches[0]; if (!p) return;
var dx = p.clientX - sx, dy = p.clientY - sy, dt = Date.now() - st;
if (dt > 700) return; /* 太慢不算滑动 */
if (Math.abs(dx) < 60) return; /* 位移不够 */
if (Math.abs(dx) < Math.abs(dy) * 1.6) return; /* 必须以横向为主 */
switchCatBy(dx < 0 ? 1 : -1); /* 左滑下一个,右滑上一个 */
});
}
/* ============================================================
滚动到底自动加载更多(无限滚动)
- rAF 节流,距底 700px 内就预加载,滚动更顺滑
- 详情页 / 客户端筛选模式(演示数据)下不触发
============================================================ */
function nearListBottom() {
var doc = document.documentElement, b = document.body;
var y = window.pageYOffset || doc.scrollTop || b.scrollTop || 0;
var vh = window.innerHeight || doc.clientHeight || 0;
var full = Math.max(b.scrollHeight, doc.scrollHeight, b.offsetHeight, doc.offsetHeight);
return (y + vh) >= (full - 700);
}
var _scrollTick = false;
function onScrollLoad() {
if (_scrollTick) return; _scrollTick = true;
requestAnimationFrame(function () {
_scrollTick = false;
var y = window.pageYOffset || document.documentElement.scrollTop || 0;
var detail = document.body.classList.contains("detail-active");
var bt = $("#backTop");
if (bt) { if (y > 800 && !detail) bt.classList.add("show"); else bt.classList.remove("show"); }
var tb = document.querySelector(".topbar");
if (tb) { if (y > 24) tb.classList.add("shrink"); else tb.classList.remove("shrink"); }
if (detail) return;
if (STATE.clientFilter || !STATE.hasMore || STATE.loadingMore) return;
if (nearListBottom()) loadMore();
});
}
function setupInfiniteScroll() {
window.addEventListener("scroll", onScrollLoad, { passive: true });
window.addEventListener("resize", onScrollLoad, { passive: true });
var bt = $("#backTop");
if (bt) bt.onclick = function () { try { window.scrollTo({ top: 0, behavior: "smooth" }); } catch (e) { window.scrollTo(0, 0); } };
/* 随机一部:从当前列表随便打开一部 */
var dice = $("#diceBtn");
if (dice) dice.onclick = function () {
var list = STATE.list || [];
if (!list.length) { toast("列表加载中…"); return; }
openDetail(list[Math.floor(Math.random() * list.length)]);
};
/* 卡片入场动画结束后摘掉 .cin,恢复按压等正常变换 */
var grid = $("#grid");
if (grid) grid.addEventListener("animationend", function (e) {
var el2 = e.target;
if (el2 && el2.classList && el2.classList.contains("cin")) { el2.classList.remove("cin"); el2.style.animationDelay = ""; }
});
}
/* ============================================================
路由
============================================================ */
function restoreDetail(item) {
/* 从播放器返回:废掉所有旧请求回调,只恢复 UI */
++STATE.reqToken;
STATE.detail = item;
closeFilmo();
document.body.classList.add("detail-active");
var sheet = $("#sheet"); sheet.classList.add("active"); sheet.setAttribute("aria-hidden", "false");
/* 还是同一部片、DOM 还在(没被 closeDetail 清空)就直接复用,不重新渲染 —
避免每次从播放器回来都整页重建造成卡顿延迟 */
if (STATE._detailRenderedItem === item && $("#resgroups") && $("#resgroups").children.length) {
return;
}
STATE._detailRenderedItem = item;
applyHeroImage(item);
renderDetailInfo(item);
renderStills(pick(item, "stills", []) || []);
renderCast(item);
applyHeroCarousel(item, pick(item, "stills", []) || []);
}
function onRoute(state) {
state = state || { route: "home" };
if (state.route === "detail" && STATE.detail) restoreDetail(STATE.detail);
else if (document.body.classList.contains("detail-active")) closeDetail();
}
window.addEventListener("popstate", function (e) { onRoute(e.state); });
/* ============================================================
启动
============================================================ */
function boot() {
renderNav();
renderFilters();
renderGrid();
/* 每次重载都重置为默认分类「最新热门」+ 默认筛选,不恢复上次选择 */
Store.get("noticeHidden").then(function (v) { if (v === "1") $("#notice").classList.add("hidden"); });
/* 清除旧的低清图片缓存(升级到 original 尺寸后一次性清理) */
try { var _ks = Object.keys(localStorage); _ks.forEach(function(k){ if(k.indexOf("片库_art_")===0||k.indexOf("片库_meta_")===0||k.indexOf("片库_logo_")===0) localStorage.removeItem(k); }); } catch(e) {}
Store.get("theme").then(function (v) { if (v) applyTheme(v); });
Store.get("filtersOpen").then(function (v) { STATE.filtersOpen = (v === "1"); syncFiltersOpen(); });
var q = $("#q");
function doSearch() { var kw = q.value.trim(); hideSuggest(); if (kw) searchTitle(kw); }
$("#goBtn").onclick = doSearch;
q.addEventListener("keydown", function (e) { if (e.key === "Enter" || e.keyCode === 13) { e.preventDefault(); doSearch(); } });
$("#noticeX").onclick = function () { $("#notice").classList.add("hidden"); Store.set("noticeHidden", "1"); };
$("#ftoggle").onclick = function () { STATE.filtersOpen = !STATE.filtersOpen; Store.set("filtersOpen", STATE.filtersOpen ? "1" : "0"); syncFiltersOpen(); };
$("#brand").onclick = function (e) { e.preventDefault(); if (document.body.classList.contains("detail-active")) history.back(); window.scrollTo(0, 0); };
/* 左上角 P:一键循环切换主题色(拦截冒泡,不触发回首页) */
var themeBtn = $("#themeBtn");
if (themeBtn) themeBtn.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); cycleTheme(); });
$("#descToggle").onclick = function () {
var d = $("#dDesc"); var open = d.classList.toggle("open");
this.textContent = open ? "收起" : "展开";
};
/* 播放按钮:短按 → 播放;长按(500ms)→ 原生搜索 */
(function () {
var btn = $("#actPlay");
if (!btn) return;
var pressTimer = null;
var fired = false;
function startPress() {
fired = false;
pressTimer = setTimeout(function () {
fired = true;
if (navigator.vibrate) navigator.vibrate(40);
var kw = pick(STATE.detail, "title", "") || $("#dTitle").textContent;
var detail = STATE.detail || {};
var pic = pick(detail, "poster", "") ? tmdbResize(pick(detail, "poster", ""), "w780") : "";
var wallPic = pick(detail, "backdrop", "") || pick(detail, "poster", "") || "";
if (wallPic) wallPic = tmdbResize(wallPic, "original");
fmReady().then(function (fm) {
if (fm && fm.search) fm.search(kw, { direct: true, pic: pic, wallPic: wallPic });
else toast("请在 App 内使用原生搜索");
});
}, 500);
}
function cancelPress() {
clearTimeout(pressTimer);
pressTimer = null;
}
btn.addEventListener("mousedown", startPress);
btn.addEventListener("touchstart", startPress, { passive: true });
btn.addEventListener("mouseup", cancelPress);
btn.addEventListener("mouseleave", cancelPress);
btn.addEventListener("touchend", cancelPress);
btn.addEventListener("touchcancel", cancelPress);
btn.onclick = function () { if (!fired) playNext(); };
})();
document.addEventListener("keydown", onKey, true);
setupTvInput();
setupSuggest();
setupSwipe();
setupInfiniteScroll();
/* 横竖屏切换:详情页大图随朝向切换竖版/横版 */
function onOrient() { if (STATE.detail && document.body.classList.contains("detail-active")) applyHeroImage(STATE.detail); }
window.addEventListener("resize", onOrient);
window.addEventListener("orientationchange", onOrient);
window.addEventListener("fmviewport", onOrient);
fmReady().then(function (fm) {
if (!fm) return;
if (fm.config) fm.config().then(function (c) { STATE.driveCheck = (c && c.driveCheck !== false); }).catch(function () {});
applyChrome();
});
if (IS_TV) setTimeout(function () { var f = $("#nav").querySelector(".focusable"); if (f) { lastHomeFocus = f; focusEl(f); } }, 120);
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
else boot();
/* ============================================================
区块标题随机 glitch 帧
每隔随机 3–9 秒,挑一个可见的 .blk-title 触发单次错位闪烁
============================================================ */
(function () {
"use strict";
if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
function fireOne() {
var titles = document.querySelectorAll(".blk-title:not(.glitch-fire)");
/* 只选视口内可见的 */
var visible = [];
for (var i = 0; i < titles.length; i++) {
var r = titles[i].getBoundingClientRect();
if (r.top >= 0 && r.bottom <= window.innerHeight) visible.push(titles[i]);
}
if (!visible.length) { schedule(); return; }
var target = visible[Math.random() * visible.length | 0];
target.classList.add("glitch-fire");
target.addEventListener("animationend", function onEnd() {
target.classList.remove("glitch-fire");
target.removeEventListener("animationend", onEnd);
});
schedule();
}
function schedule() {
/* 详情页开着时跳过,回首页再启动 */
if (document.body.classList.contains("detail-active")) {
setTimeout(schedule, 1200);
return;
}
setTimeout(fireOne, 3000 + Math.random() * 6000);
}
schedule();
})();
/* ============================================================
搜索框打字机光标
输入框空闲时逐字打印提示文字 + _ 光标闪烁
聚焦 / 有输入值时暂停并隐藏
============================================================ */
(function () {
"use strict";
var HINTS = [
"搜片名·网盘资源_",
"4K 原盘·直链下载_",
"输入标题·一键复制_"
];
var TYPE_SPD = 68; /* 每字符间隔 ms */
var PAUSE = 2200; /* 打完后停顿 ms */
var ERASE_SPD = 32; /* 退格间隔 ms */
var input = document.getElementById("q");
if (!input) return;
var hintIdx = 0, charIdx = 0, timer = null, erasing = false, paused = false;
function setPlaceholder(txt) { input.placeholder = txt; }
function tick() {
if (paused || input.value.length > 0) { timer = setTimeout(tick, 200); return; }
var full = HINTS[hintIdx];
if (!erasing) {
charIdx++;
setPlaceholder(full.slice(0, charIdx));
if (charIdx >= full.length) {
erasing = true;
timer = setTimeout(tick, PAUSE);
} else {
timer = setTimeout(tick, TYPE_SPD + (Math.random() * 30 | 0));
}
} else {
charIdx--;
/* 光标 _ 始终跟着退格 */
var cur = full.slice(0, charIdx);
setPlaceholder(charIdx > 0 ? cur + (cur.slice(-1) === "_" ? "" : "_") : "");
if (charIdx <= 0) {
erasing = false;
charIdx = 0;
hintIdx = (hintIdx + 1) % HINTS.length;
timer = setTimeout(tick, 380);
} else {
timer = setTimeout(tick, ERASE_SPD);
}
}
}
input.addEventListener("focus", function () {
paused = true;
setPlaceholder("");
clearTimeout(timer);
});
input.addEventListener("blur", function () {
if (input.value.length > 0) return;
paused = false;
/* 重新从头打 */
erasing = false; charIdx = 0;
setPlaceholder("");
timer = setTimeout(tick, 600);
});
input.addEventListener("input", function () {
if (input.value.length > 0) { clearTimeout(timer); setPlaceholder(""); }
});
/* 启动 */
timer = setTimeout(tick, 800);
})();
/* ============================================================
topbar 实时状态文字
格式:HH:MM:SS FPS CAT_CODE NET_LINK::ACTIVE
============================================================ */
(function () {
"use strict";
var el = document.getElementById("topbarStatus");
if (!el) return;
var fpsCount = 0, fps = 0, lastFpsSec = 0;
/* FPS 计数 */
(function fpsTick(ts) {
fpsCount++;
var sec = (ts / 1000) | 0;
if (sec !== lastFpsSec) { fps = fpsCount; fpsCount = 0; lastFpsSec = sec; }
requestAnimationFrame(fpsTick);
})(0);
function pad2(n) { return n < 10 ? "0" + n : "" + n; }
function tick() {
var d = new Date();
var timeStr = pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + pad2(d.getSeconds());
/* detail 模式显示片名代码 */
var detailActive = document.body.classList.contains("detail-active");
var statusCode = detailActive ? "MODE::DETAIL" : "NET_LINK::ACTIVE";
var fpsNum = fps > 99 ? 99 : fps;
var fpsStr = fps > 0 ? ("FPS:" + pad2(fpsNum)) : "FPS:--";
el.textContent = timeStr + " " + fpsStr + " " + statusCode;
}
setInterval(tick, 1000);
tick();
})();
/* ============================================================
赛博进度条 CyBar
============================================================ */
var CyBar = (function () {
"use strict";
var bar = document.getElementById("cybar");
var track = document.getElementById("cybar-track");
var cursor = document.getElementById("cybar-cursor");
var label = document.getElementById("cybar-label");
var segs = document.getElementById("cybar-segs");
var _pct = 0, _timer = null, _doneTimer = null, _tasks = 0, _done = 0;
var SEG_COUNT = 12;
/* 生成分段节点 */
(function buildSegs() {
for (var i = 0; i < SEG_COUNT; i++) {
var s = document.createElement("div");
s.className = "cybar-seg";
segs.appendChild(s);
}
})();
function _setWidth(pct) {
_pct = Math.min(100, Math.max(0, pct));
track.style.width = _pct + "%";
cursor.style.left = _pct + "%";
/* 点亮经过的分段 */
var kids = segs.children;
for (var i = 0; i < kids.length; i++) {
var threshold = (i + 1) / SEG_COUNT * 100;
if (_pct >= threshold) {
kids[i].style.background = "color-mix(in srgb, var(--gold) " + (20 + i * 4) + "%, transparent)";
kids[i].style.boxShadow = "0 0 6px rgba(var(--gold-rgb),.3)";
} else {
kids[i].style.background = "";
kids[i].style.boxShadow = "";
}
}
}
function _animateTo(target, duration, cb) {
var start = _pct, startTime = null;
duration = duration || 400;
function step(ts) {
if (!startTime) startTime = ts;
var p = Math.min((ts - startTime) / duration, 1);
var ease = 1 - Math.pow(1 - p, 3); /* ease-out-cubic */
_setWidth(start + (target - start) * ease);
if (p < 1) requestAnimationFrame(step);
else if (cb) cb();
}
requestAnimationFrame(step);
}
/* 分段闪烁填充:每段依次点亮 */
function _segFlash(fromPct, toPct, stepCount, interval, cb) {
stepCount = stepCount || 4;
interval = interval || 80;
var step = (toPct - fromPct) / stepCount;
var i = 0;
function next() {
i++;
_setWidth(fromPct + step * i);
if (i < stepCount) setTimeout(next, interval);
else if (cb) setTimeout(cb, interval);
}
next();
}
/* 公开 API */
function start(labelText, totalTasks) {
_tasks = totalTasks || 1; _done = 0;
if (_doneTimer) { clearTimeout(_doneTimer); _doneTimer = null; }
bar.classList.remove("done");
bar.classList.add("active");
label.textContent = labelText || "LOADING";
_setWidth(0);
/* 快速填到 15% 给用户即时反馈 */
_segFlash(0, 15, 3, 60);
}
function progress(pct, labelText) {
if (labelText) label.textContent = labelText;
_animateTo(pct);
}
/* 单任务推进:n/total */
function tick(labelText) {
_done++;
var target = Math.min(90, (_done / _tasks) * 90 + 10);
if (labelText) label.textContent = labelText;
_segFlash(_pct, target, 3, 55);
}
function finish(labelText) {
if (labelText) label.textContent = labelText || "DONE";
/* 闪烁填满再消失 */
_segFlash(_pct, 100, 6, 45, function () {
bar.classList.remove("active");
bar.classList.add("done");
_doneTimer = setTimeout(function () {
bar.classList.remove("done");
_setWidth(0);
var kids = segs.children;
for (var i = 0; i < kids.length; i++) { kids[i].style.background = ""; kids[i].style.boxShadow = ""; }
}, 600);
});
}
function error(labelText) {
label.textContent = labelText || "ERR";
label.style.color = "var(--bad)";
setTimeout(function () { finish(); label.style.color = ""; }, 800);
}
return { start: start, progress: progress, tick: tick, finish: finish, error: error };
})();
/* ============================================================
Typewriter — 逐字打印,带光标
============================================================ */
var Typewriter = (function () {
"use strict";
var _queue = [], _running = false;
function _type(el, text, speed, cb) {
/* 清空,追加光标 */
el.textContent = "";
var cur = document.createElement("span");
cur.className = "tw-cursor";
cur.textContent = "_";
el.appendChild(cur);
var i = 0;
var CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789:_.<>[]|/-";
function step() {
if (i >= text.length) {
/* 完成后光标保留 */
if (cb) cb();
return;
}
/* 插入乱码帧再还原 */
var fake = CHARS[Math.floor(Math.random() * CHARS.length)];
el.insertBefore(document.createTextNode(fake), cur);
setTimeout(function () {
/* 替换为真实字符 */
el.replaceChild(document.createTextNode(text[i]), el.childNodes[i]);
i++;
setTimeout(step, speed || 28);
}, 30);
}
setTimeout(step, 0);
}
/* 公开:直接打印到元素 */
function to(el, text, speed) {
if (!el) return;
_type(el, text, speed || 28);
}
return { to: to };
})();
/* ============================================================
数字翻牌动画
============================================================ */
function numRollIn(el) {
if (!el) return;
var nodes = el.querySelectorAll ? el.querySelectorAll(".num-roll") : [];
/* 如果没有子节点则包裹整个文本 */
if (!nodes.length) {
var orig = el.textContent;
var span = document.createElement("span");
span.className = "num-roll";
span.textContent = orig;
el.textContent = "";
el.appendChild(span);
/* 触发 reflow */
void span.offsetWidth;
span.style.animationDuration = ".38s";
} else {
nodes.forEach(function (n, i) {
void n.offsetWidth;
n.style.animationDelay = (i * 60) + "ms";
});
}
}
/* ============================================================
文字乱码还原
scramble(el, finalText, duration_ms)
============================================================ */
function scrambleText(el, finalText, duration) {
if (!el) return;
duration = duration || 320;
var CHARS = "アイウエオカキクケコ01サシスセソ10タチツテト#$%&ABXYZ";
var len = finalText.length;
var revealed = 0;
var interval = duration / (len * 2.5);
var timer = setInterval(function () {
var out = "";
for (var i = 0; i < len; i++) {
if (i < revealed) {
out += finalText[i];
} else {
out += CHARS[Math.floor(Math.random() * CHARS.length)];
}
}
el.textContent = out;
revealed += 0.8;
if (revealed >= len) {
el.textContent = finalText;
clearInterval(timer);
}
}, interval);
}
/* ============================================================
接入:给 renderGrid / loadResources / loadReco / openDetail 挂进度条
============================================================ */
(function patchWithCyBar() {
"use strict";
/* — renderGrid — */
var _origRenderGrid = window.renderGrid || (typeof renderGrid !== "undefined" ? renderGrid : null);
if (_origRenderGrid) {
window.renderGrid = function () {
CyBar.start("CATALOG::FETCH", 1);
_origRenderGrid.apply(this, arguments);
};
}
/* — loadCatalog 完成时推进进度条(monkey-patch renderList
renderList 在 loadCatalog resolve 后调用,是个好的钩子点 */
var _origRenderList = window.renderList || (typeof renderList !== "undefined" ? renderList : null);
if (_origRenderList) {
window.renderList = function () {
CyBar.finish("CATALOG::OK");
_origRenderList.apply(this, arguments);
};
}
/* — loadResources — */
var _origLoadResources = window.loadResources || (typeof loadResources !== "undefined" ? loadResources : null);
if (_origLoadResources) {
window.loadResources = function (item) {
CyBar.start("RESOURCE::FETCH", 1);
_origLoadResources.apply(this, arguments);
};
}
/* — renderResourcesFromRows — 资源渲染完毕 */
var _origRenderRows = window.renderResourcesFromRows || (typeof renderResourcesFromRows !== "undefined" ? renderResourcesFromRows : null);
if (_origRenderRows) {
window.renderResourcesFromRows = function () {
_origRenderRows.apply(this, arguments);
CyBar.finish("RESOURCE::OK");
};
}
/* — openDetail — */
var _origOpenDetail = window.openDetail || (typeof openDetail !== "undefined" ? openDetail : null);
if (_origOpenDetail) {
window.openDetail = function (item) {
CyBar.start("DETAIL::LOAD", 2);
_origOpenDetail.apply(this, arguments);
};
}
/* — loadReco 完成后 tick — */
var _origLoadReco = window.loadReco || (typeof loadReco !== "undefined" ? loadReco : null);
if (_origLoadReco) {
window.loadReco = function () {
_origLoadReco.apply(this, arguments);
setTimeout(function () { CyBar.tick("RECO::OK"); }, 600);
};
}
})();
/* ============================================================
Status bar:已禁用打字机效果,常显示
============================================================ */
/* ============================================================
评分翻牌:showDetail 后触发
============================================================ */
(function patchNumRoll() {
"use strict";
var _origRenderDetailInfo = window.renderDetailInfo || (typeof renderDetailInfo !== "undefined" ? renderDetailInfo : null);
if (!_origRenderDetailInfo) return;
window.renderDetailInfo = function (item) {
_origRenderDetailInfo.apply(this, arguments);
/* 评分元素在 #dRating 下 */
setTimeout(function () {
var dr = document.getElementById("dRating");
if (!dr) return;
var tags = dr.querySelectorAll(".rtag");
tags.forEach(function (tag, i) {
var orig = tag.textContent;
tag.textContent = "";
var span = document.createElement("span");
span.className = "num-roll";
span.style.animationDelay = (i * 80) + "ms";
span.textContent = orig;
tag.appendChild(span);
});
}, 120);
};
})();
/* ============================================================
标题乱码还原:openDetail 打开详情时触发
============================================================ */
(function patchTitleScramble() {
"use strict";
var _origShowDetail = window.showDetail || (typeof showDetail !== "undefined" ? showDetail : null);
if (!_origShowDetail) return;
window.showDetail = function (item) {
_origShowDetail.apply(this, arguments);
setTimeout(function () {
var h1 = document.querySelector("#sheet .herofg h1");
if (!h1) return;
var title = h1.textContent;
if (title) scrambleText(h1, title, 380);
}, 80); /* glitch-enter 动画开始后触发 */
};
})();
</script></body></html>