Files
tvbox_collect/jaychouqq/yingshi/js7/AV-NEW3.js
T
2026-06-29 03:41:14 +00:00

1055 lines
51 KiB
JavaScript
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.
const API_BASE = 'https://x.xvideos4.tk/api.php/v1/records';
const API_KEY = 'text';
const NAV = [
{ sort: 'daily', label: '今日', href: '/zh-CN/', icon: '🔥', range: 'today' },
{ sort: 'weekly', label: '本周', href: '/zh-CN/weekly/', icon: '📅', range: 'week' },
{ sort: 'monthly', label: '本月', href: '/zh-CN/monthly/',icon: '🗓', range: 'month' },
{ sort: 'favorite', label: '收藏', href: '/zh-CN/all/', icon: '⭐', range: 'all' },
];
const CATEGORIES = [
{ id: 'gay', name: '男娘' },
{ id: 'anime', name: '动漫' },
{ id: 'lolita', name: '少女' },
{ id: 'shaved', name: '白虎' },
{ id: 'kyonyu', name: '巨乳' },
{ id: 'jk', name: '高中生' },
{ id: 'beautiful-girl', name: '美少女' },
{ id: 'small-breasts', name: '贫乳' },
{ id: 'sm', name: 'SM' },
{ id: 'masturbation', name: '自慰' },
{ id: 'hamedori', name: '自拍' },
{ id: 'female-pervert', name: '痴女' },
{ id: 'personal-filming',name: '私拍' },
{ id: 'outdoor', name: '户外' },
{ id: 'big-sister', name: '姐姐' },
{ id: 'incest', name: '乱伦' },
{ id: 'married-woman', name: '人妻' },
{ id: 'orgy', name: '群交' },
{ id: 'fellatio', name: '口交' },
{ id: 'bukkake', name: '颜射' },
];
async function apiList(options) {
const sort = options.sort;
const category = options.category;
const page = options.page;
const q = options.q;
const navItem = NAV.find(function(n) { return n.sort === sort; }) || NAV[0];
const qp = new URLSearchParams({
api_key: API_KEY,
range: navItem.range,
type: 'video',
page: String(page || 1),
limit: '24'
});
let keyword = '';
if (q) {
keyword = q;
qp.set('q', q);
} else if (category) {
const cat = CATEGORIES.find(function(c) { return c.id === category; });
keyword = cat ? cat.name : category;
qp.set('q', keyword);
}
const res = await fetch(API_BASE + '?' + qp.toString(), {
headers: { 'Accept': 'application/json' }
});
if (!res.ok) return null;
const json = await res.json().catch(function() { return null; });
if (json && json.data && keyword) {
json.data = json.data.filter(function(item) {
return (item.tweet_text && item.tweet_text.indexOf(keyword) !== -1) ||
(item.author_name && item.author_name.indexOf(keyword) !== -1);
});
}
return json;
}
async function apiDetail(id) {
const qpQ = new URLSearchParams({
api_key: API_KEY,
range: 'all',
type: 'video',
page: '1',
limit: '1',
q: id
});
const res = await fetch(API_BASE + '?' + qpQ.toString(), {
headers: { 'Accept': 'application/json' }
});
if (!res.ok) return null;
const data = await res.json().catch(function() { return null; });
const list = (data && data.data) ? data.data : [];
if (!list.length) return null;
return list.find(function(i) { return String(i.id) === String(id); }) || list[0] || null;
}
function normalizeItem(item) {
const video = (item.media && item.media.videos && item.media.videos[0]) ? item.media.videos[0] : {};
return {
id: item.id || '',
title: item.tweet_text || '未知',
thumb: video.thumbnail || '',
videoUrl: video.url || '',
time: item.duration_str || '00:00',
pv: '0',
favorite: '0',
};
}
function isHlsUrl(url) {
if (!url) return false;
return /\.m3u8|playlist|\/HLS\/|\/hls\//i.test(url);
}
function isMp4Url(url) {
if (!url) return false;
return /\.mp4(\?|$)|\.webm(\?|$)|\.ogg(\?|$)/i.test(url);
}
function jsonResponse(data, status) {
status = status || 200;
return new Response(JSON.stringify(data), {
status: status,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-cache',
},
});
}
export default {
async fetch(request) {
const url = new URL(request.url);
const path = url.pathname;
const params = url.searchParams;
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS, POST',
'Access-Control-Allow-Headers': 'Range, Content-Type',
}
});
}
try {
if (path === '/play') return handlePlay(params);
if (path === '/api/resolve') return handleResolve(params);
if (path === '/api/refresh') return handleRefresh(params.get('id') || '');
/* ===== M3U 订阅路由 ===== */
if (path === '/love.m3u') return handleM3U(params, url);
if (path.match(/^\/love-(.+)\.m3u$/)) {
const seg = path.match(/^\/love-(.+)\.m3u$/)[1];
// /love-{category}.m3u or /love-daily.m3u /love-weekly.m3u /love-monthly.m3u
const rangeKeys = { daily: 'today', weekly: 'week', monthly: 'month' };
if (rangeKeys[seg]) {
params.set('range', rangeKeys[seg]);
} else {
params.set('category', seg);
}
return handleM3U(params, url);
}
if (path === '/api/extract' && request.method === 'POST') {
const body = await request.json();
const res = await fetch('https://x.xvideos4.tk/extract', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const data = await res.json();
return jsonResponse(data, res.status);
}
if (path.indexOf('/movie/') !== -1) {
const parts = path.split('/').filter(Boolean);
const id = parts.pop();
return handleDetail(id);
}
if (path === '/search') {
const q = params.get('tag') || params.get('q') || '';
const page = parseInt(params.get('page') || '1', 10);
return handleList({ sort: 'favorite', category: '', q: q, page: page, isSearch: true, origin: url.origin });
}
const catMatch = path.match(/\/category\/([^\/\s?]+)/);
if (catMatch) {
const page = parseInt(params.get('page') || '1', 10);
return handleList({ sort: 'favorite', category: catMatch[1], page: page, origin: url.origin });
}
const sortMap = {
'/zh-cn/weekly': 'weekly', '/zh-CN/weekly': 'weekly',
'/zh-cn/weekly/': 'weekly', '/zh-CN/weekly/': 'weekly',
'/zh-cn/monthly': 'monthly', '/zh-CN/monthly': 'monthly',
'/zh-cn/monthly/':'monthly', '/zh-CN/monthly/':'monthly',
'/zh-cn/all': 'favorite','/zh-CN/all': 'favorite',
'/zh-cn/all/': 'favorite','/zh-CN/all/': 'favorite',
};
const sort = sortMap[path] || 'daily';
const page = parseInt(params.get('page') || '1', 10);
return handleList({ sort: sort, category: '', page: page, origin: url.origin });
} catch (e) {
return new Response('Error: ' + e.message + '\n' + e.stack, { status: 500 });
}
}
};
/* ================================================================
* M3U 订阅生成
* 访问方式:
* /love.m3u 全部视频(今日,前3页)
* /love.m3u?range=week 本周
* /love.m3u?range=month 本月
* /love.m3u?range=all 全部时段
* /love.m3u?category=anime 动漫分类
* /love.m3u?pages=5 抓取页数(1-10)
* /love-daily.m3u 今日快捷
* /love-weekly.m3u 本周快捷
* /love-monthly.m3u 本月快捷
* /love-anime.m3u 分类快捷
* ================================================================ */
async function handleM3U(params, reqUrl) {
const range = params.get('range') || 'today';
const category = params.get('category') || '';
const q = params.get('q') || '';
const maxPages = Math.min(Math.max(parseInt(params.get('pages') || '3', 10), 1), 10);
const rangeToSort = { today: 'daily', week: 'weekly', month: 'monthly', all: 'favorite' };
const sort = rangeToSort[range] || 'daily';
const pageNums = [];
for (let i = 1; i <= maxPages; i++) pageNums.push(i);
const results = await Promise.all(
pageNums.map(function(p) {
return apiList({ sort: sort, category: category, q: q, page: p }).catch(function() { return null; });
})
);
const allMovies = [];
const seen = {};
for (const data of results) {
const list = (data && data.data) ? data.data : [];
for (const item of list) {
const m = normalizeItem(item);
if (m.videoUrl && !seen[m.id]) {
seen[m.id] = true;
allMovies.push(m);
}
}
}
let groupTitle = '全部';
if (category) {
const cat = CATEGORIES.find(function(c) { return c.id === category; });
groupTitle = cat ? cat.name : category;
} else if (range === 'today') { groupTitle = '今日'; }
else if (range === 'week') { groupTitle = '本周'; }
else if (range === 'month') { groupTitle = '本月'; }
const lines = ['#EXTM3U x-tvg-url=""'];
for (const m of allMovies) {
if (!m.videoUrl) continue;
const title = m.title.replace(/,/g, '');
lines.push(
'#EXTINF:-1 tvg-id="' + m.id + '" tvg-name="' + title + '" tvg-logo="' + m.thumb + '" group-title="' + groupTitle + '" duration="' + m.time + '",' + title
);
lines.push(m.videoUrl);
}
const body = lines.join('\n') + '\n';
const filename = category ? ('love-' + category + '.m3u') : ('love-' + range + '.m3u');
return new Response(body, {
headers: {
'Content-Type': 'application/x-mpegurl; charset=utf-8',
'Content-Disposition': 'attachment; filename="' + filename + '"',
'Access-Control-Allow-Origin': '*',
'Cache-Control': 'no-cache',
}
});
}
async function handleResolve(params) {
const rawUrl = params.get('url') || '';
if (!rawUrl || rawUrl.indexOf('http') !== 0) {
return jsonResponse({ error: 'invalid_url' }, 400);
}
let origin = '';
try { origin = new URL(rawUrl).origin; } catch (_) {}
const commonHeaders = {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) '
+ 'AppleWebKit/605.1.15 (KHTML, like Gecko) '
+ 'Version/17.0 Mobile/15E148 Safari/604.1',
'Referer': origin ? origin + '/' : 'https://x.xvideos4.tk/',
'Origin': origin || 'https://x.xvideos4.tk',
'Accept': 'video/mp4,video/webm,application/x-mpegurl,*/*;q=0.9',
};
try {
const ctrl = new AbortController();
const timer = setTimeout(function() { ctrl.abort(); }, 8000);
const res = await fetch(rawUrl, {
method: 'HEAD', redirect: 'follow', signal: ctrl.signal, headers: commonHeaders,
});
clearTimeout(timer);
const ct = res.headers.get('content-type') || '';
const finalUrl = res.url || rawUrl;
const isHls = ct.indexOf('mpegurl') !== -1 || ct.indexOf('x-mpegurl') !== -1 || isHlsUrl(finalUrl);
const isMp4 = ct.indexOf('video/') === 0 || isMp4Url(finalUrl);
return jsonResponse({ url: finalUrl, isHls: isHls, isMp4: isMp4, contentType: ct, status: res.status });
} catch (headErr) {
try {
const ctrl2 = new AbortController();
const timer2 = setTimeout(function() { ctrl2.abort(); }, 8000);
const res2 = await fetch(rawUrl, {
method: 'GET', redirect: 'follow', signal: ctrl2.signal, headers: commonHeaders,
});
clearTimeout(timer2);
const ct2 = res2.headers.get('content-type') || '';
const finalUrl2 = res2.url || rawUrl;
const isHls2 = ct2.indexOf('mpegurl') !== -1 || ct2.indexOf('x-mpegurl') !== -1 || isHlsUrl(finalUrl2);
const isMp42 = ct2.indexOf('video/') === 0 || isMp4Url(finalUrl2);
return jsonResponse({ url: finalUrl2, isHls: isHls2, isMp4: isMp42, contentType: ct2, status: res2.status });
} catch (getErr) {
return jsonResponse({
url: rawUrl, isHls: isHlsUrl(rawUrl), isMp4: isMp4Url(rawUrl), error: getErr.message,
});
}
}
}
async function handleRefresh(id) {
if (!id) return jsonResponse({ error: 'no id' }, 400);
try {
const raw = await apiDetail(id);
if (!raw) return jsonResponse({ error: 'not_found' }, 404);
const item = normalizeItem(raw);
const proxySrc = item.videoUrl;
if (!proxySrc) return jsonResponse({ error: 'no_url' }, 404);
return jsonResponse({ proxySrc: proxySrc, isHls: isHlsUrl(proxySrc) });
} catch (e) {
return jsonResponse({ error: e.message }, 500);
}
}
async function handleList(options) {
if (options.sort === 'favorite' && !options.category && !options.q && !options.isSearch) {
return handleFavorites();
}
const data = await apiList(options);
const list = (data && data.data) ? data.data : [];
const movies = list.map(normalizeItem);
const page = options.page;
const lastPage = page + 1;
const category = options.category;
const catLabel = CATEGORIES.find(function(c) { return c.id === category; })?.name || category;
const title = category ? '# ' + catLabel : (options.isSearch ? '搜索:' + options.q : (NAV.find(function(n) { return n.sort === options.sort; })?.label || '今日'));
return new Response(
renderLayout(renderList(movies, options), title, options.sort),
{ headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
);
}
function handleFavorites() {
return new Response(
renderLayout(renderFavoritesPage(), '我的收藏', 'favorite'),
{ headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
);
}
function renderFavoritesPage() {
return '<div class="page-wrap">' +
'<div class="fav-page-header">' +
'<h2 class="fav-page-title">⭐ 我的收藏</h2>' +
'<button class="fav-clear-btn" onclick="clearAllFavs()">清空收藏</button>' +
'</div>' +
'<div id="fav-grid" class="grid"></div>' +
'<div id="fav-empty" class="empty-state" style="display:none">' +
'<div class="empty-icon">⭐</div>' +
'<div class="empty-text">暂无收藏,去浏览视频并点击 ⭐ 收藏吧</div>' +
'</div>' +
'</div>' +
`<script>
document.addEventListener('DOMContentLoaded', function() {
renderFavPage();
});
function renderFavPage() {
var favs = {};
try { favs = JSON.parse(localStorage.getItem('otc_favs') || '{}'); } catch(e) {}
var list = Object.values(favs).reverse();
var grid = document.getElementById('fav-grid');
var empty = document.getElementById('fav-empty');
if (!list.length) {
grid.style.display = 'none';
empty.style.display = 'block';
return;
}
grid.style.display = '';
empty.style.display = 'none';
grid.innerHTML = list.map(function(m) {
var thumb = m.thumb || 'https://placehold.co/300x533/111/333?text=No+Image';
var id = String(m.id || '');
var title = String(m.title || id).replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
return '<div class="card" id="fav-card-'+id+'">' +
'<a href="/zh-CN/movie/' + encodeURIComponent(id) + '" class="card-thumb">' +
'<img loading="lazy" src="' + thumb + '" alt="' + title + '"' +
' onerror="this.src=\'https://placehold.co/300x533/111/333?text=No+Image\'">' +
'<span class="badge-time">' + (m.time || '') + '</span>' +
'</a>' +
'<div class="card-body">' +
'<a href="/zh-CN/movie/' + encodeURIComponent(id) + '" class="card-title-link">' +
'<h3 class="card-title">' + title + '</h3>' +
'</a>' +
'<div class="card-meta">' +
'<span></span>' +
'<button class="card-fav-btn faved"' +
' data-id="' + id + '"' +
' data-title="' + title + '"' +
' data-thumb="' + (m.thumb||'').replace(/"/g,'&quot;') + '"' +
' data-video="' + (m.video||'').replace(/"/g,'&quot;') + '"' +
' data-time="' + (m.time ||'') + '"' +
' onclick="event.preventDefault();event.stopPropagation();toggleFav(this)">⭐ 已收藏' +
'</button>' +
'</div>' +
'</div>' +
'</div>';
}).join('');
}
function clearAllFavs() {
if (!confirm('确认清空所有收藏?')) return;
localStorage.removeItem('otc_favs');
renderFavPage();
_toast('已清空收藏');
}
</script>`;
}
async function handleDetail(id) {
const raw = await apiDetail(id);
const movie = raw ? normalizeItem(raw) : {
id: id, title: id, thumb: '', videoUrl: '', time: '00:00', pv: '0', favorite: '0'
};
return new Response(
renderLayout(renderDetail(movie), movie.title, ''),
{ headers: { 'Content-Type': 'text/html;charset=UTF-8' } }
);
}
async function handlePlay(params) {
const videoUrl = params.get('v') || '';
if (!videoUrl || videoUrl.indexOf('http') !== 0) {
return new Response('无效的播放链接', { status: 400 });
}
return new Response(null, { status: 302, headers: { Location: videoUrl } });
}
function renderList(movies, options) {
const sort = options.sort;
const category = options.category;
const q = options.q;
const page = options.page;
const isSearch = options.isSearch;
const origin = options.origin || '';
const grid = movies.map(function(m) {
const thumb = m.thumb || 'https://placehold.co/300x533/111/333?text=No+Image';
const playHref = m.videoUrl ? m.videoUrl : '/zh-CN/movie/' + encodeURIComponent(m.id);
const playTarget = m.videoUrl ? ' target="_blank" rel="noopener noreferrer"' : '';
return '<div class="card">' +
'<a href="' + playHref + '"' + playTarget + ' class="card-thumb">' +
'<img loading="lazy" src="' + thumb + '" alt="' + esc(m.title) + '"' +
' onerror="this.src=\'https://placehold.co/300x533/111/333?text=No+Image\'"> ' +
'<span class="badge-time">' + esc(m.time) + '</span>' +
'<button class="card-fav-btn" data-id="' + esc(m.id) + '" data-title="' + esc(m.title) + '" data-thumb="' + esc(m.thumb) + '" data-video="' + esc(m.videoUrl) + '" data-time="' + esc(m.time) + '" onclick="event.preventDefault();event.stopPropagation();toggleFav(this)" aria-label="收藏">⭐</button>' +
'</a>' +
'<div class="card-body">' +
'<a href="/zh-CN/movie/' + encodeURIComponent(m.id) + '" class="card-title-link">' +
'<h3 class="card-title">' + esc(m.title) + '</h3>' +
'</a>' +
'<div class="card-meta">' +
'<span>⏱ ' + esc(m.time) + '</span>' +
'</div>' +
'</div>' +
'</div>';
}).join('');
function pageUrl(p) {
if (isSearch) return '/search?tag=' + encodeURIComponent(q || category || '') + '&page=' + p;
if (category) return '/zh-CN/category/' + encodeURIComponent(category) + '?page=' + p;
return (NAV.find(function(n) { return n.sort === sort; })?.href || '/zh-CN/') + '?page=' + p;
}
const pagination = (page > 1 || movies.length > 0) ?
'<div class="pagination">' +
(page > 1
? '<a href="' + pageUrl(page-1) + '" class="page-btn">← 上一页</a>'
: '<span class="page-btn disabled">← 上一页</span>') +
'<span class="page-info">第 ' + page + ' 页</span>' +
(movies.length >= 20
? '<a href="' + pageUrl(page+1) + '" class="page-btn">下一页 →</a>'
: '<span class="page-btn disabled">下一页 →</span>') +
'</div>' : '';
const catChips = CATEGORIES.map(function(c) {
return '<a href="/zh-CN/category/' + c.id + '" class="chip' + (category === c.id ? ' chip-active' : '') + '">' + esc(c.name) + '</a>';
}).join('');
const empty = movies.length === 0 ?
'<div class="empty-state">' +
'<div class="empty-icon">📭</div>' +
'<div class="empty-text">暂无内容</div>' +
'</div>' : '';
return '<div class="page-wrap">' +
'<div class="extract-section">' +
'<div class="extract-label">帖子链接</div>' +
'<div class="extract-box">' +
'<input type="text" id="extractUrl" placeholder="https://x.com/user/status/1234567890…" autocomplete="off">' +
'<button id="extractBtn" onclick="doExtract()">' +
'<span id="extractBtnIcon">⬇</span>' +
'<span id="extractBtnText">提取</span>' +
'</button>' +
'</div>' +
'<div id="extractStatus" class="extract-status"></div>' +
'<div id="extractResult" class="extract-result-area" style="display:none"></div>' +
'</div>' +
'<div class="m3u-section">' +
'<div class="m3u-section-head">' +
'<span class="m3u-section-title">📺 M3U 播放列表</span>' +
'<span class="m3u-section-tip">适用于电视盒子 / Kodi / IPTV 播放器</span>' +
'</div>' +
'<div class="m3u-links">' +
'<a href="' + origin + '/love.m3u?range=today&pages=5" class="m3u-btn" download>🔥 今日</a>' +
'<a href="' + origin + '/love.m3u?range=week&pages=5" class="m3u-btn" download>📅 本周</a>' +
'<a href="' + origin + '/love.m3u?range=month&pages=5" class="m3u-btn" download>🗓 本月</a>' +
'<a href="' + origin + '/love.m3u?range=all&pages=5" class="m3u-btn" download>✨ 全部</a>' +
'</div>' +
(category ? '<div class="m3u-cat-row"><a href="' + origin + '/love-' + category + '.m3u?pages=5" class="m3u-btn m3u-btn-cat" download>📁 当前分类:' + esc(catLabel || category) + '</a></div>' : '') +
'<div class="m3u-copy-row">' +
'<span class="m3u-copy-label">订阅地址:</span>' +
'<input type="text" class="m3u-copy-input" id="m3uUrlInput" readonly' +
' value="' + origin + (category ? ('/love-' + category + '.m3u?pages=5') : '/love.m3u?range=today&pages=5') + '">' +
'<button class="m3u-copy-btn" onclick="copyM3UUrl()">复制</button>' +
'</div>' +
'</div>' +
'<div class="search-bar-wrap">' +
'<form action="/search" method="GET" class="search-form">' +
'<input type="search" name="tag" placeholder="搜索标签 / 账号…"' +
' value="' + esc(isSearch ? (q || category) : '') + '"' +
' autocomplete="off" enterkeyhint="search">' +
'<button type="submit" aria-label="搜索">' +
'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">' +
'<circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/>' +
'</svg>' +
'</button>' +
'</form>' +
'</div>' +
'<div class="chips-wrap">' +
'<div class="chips-scroll">' +
'<a href="/zh-CN/" class="chip' + (!category ? ' chip-active' : '') + '">全部分类</a>' +
catChips +
'</div>' +
'</div>' +
(movies.length > 0 ? '<div class="grid">' + grid + '</div>' + pagination : empty) +
'</div>' +
'<script>' +
'async function doExtract() {' +
' var url = document.getElementById("extractUrl").value.trim();' +
' var btn = document.getElementById("extractBtn");' +
' var status = document.getElementById("extractStatus");' +
' var resultArea = document.getElementById("extractResult");' +
' if (!url) {' +
' status.innerText = "请输入帖子链接";' +
' status.className = "extract-status error";' +
' return;' +
' }' +
' btn.disabled = true;' +
' document.getElementById("extractBtnText").innerText = "提取中...";' +
' status.innerText = "正在提取中...";' +
' status.className = "extract-status loading";' +
' resultArea.style.display = "none";' +
' try {' +
' var res = await fetch("/api/extract", {' +
' method: "POST",' +
' headers: { "Content-Type": "application/json" },' +
' body: JSON.stringify({ url: url })' +
' });' +
' var json = await res.json();' +
' if (res.ok) {' +
' status.innerText = "提取成功!";' +
' status.className = "extract-status success";' +
' renderExtractResult(json);' +
' } else {' +
' status.innerText = json.error || "提取失败,请检查链接";' +
' status.className = "extract-status error";' +
' }' +
' } catch (e) {' +
' status.innerText = "网络错误: " + e.message;' +
' status.className = "extract-status error";' +
' } finally {' +
' btn.disabled = false;' +
' document.getElementById("extractBtnText").innerText = "提取";' +
' }' +
'}' +
'function renderExtractResult(json) {' +
' var area = document.getElementById("extractResult");' +
' var videos = (json.media && json.media.videos) ? json.media.videos : [];' +
' var images = (json.media && json.media.images) ? json.media.images : [];' +
' var html = "<div class=\'res-grid\'>";' +
' videos.forEach(function(v) {' +
' html += "<div class=\'res-item\'>" +' +
' "<div class=\'res-thumb\'>" +' +
' "<img src=\'" + (v.thumbnail || "") + "\' border=\'0\'>" +' +
' "<span class=\'res-badge\'>视频</span>" +' +
' "</div>" +' +
' "<a href=\'" + v.url + "\' target=\'_blank\' class=\'res-dl-btn\'>下载视频</a>" +' +
' "</div>";' +
' });' +
' images.forEach(function(img) {' +
' html += "<div class=\'res-item\'>" +' +
' "<div class=\'res-thumb\'>" +' +
' "<img src=\'" + img.url + "\' border=\'0\'>" +' +
' "<span class=\'res-badge\'>图片</span>" +' +
' "</div>" +' +
' "<a href=\'" + img.url + "\' target=\'_blank\' class=\'res-dl-btn\'>下载图片</a>" +' +
' "</div>";' +
' });' +
' html += "</div>";' +
' area.innerHTML = html;' +
' area.style.display = "block";' +
'}' +
'function copyM3UUrl() {' +
' var inp = document.getElementById("m3uUrlInput");' +
' if (!inp) return;' +
' var full = inp.value;' +
' if (navigator.clipboard && navigator.clipboard.writeText) {' +
' navigator.clipboard.writeText(full).then(function() {' +
' _m3uCopied();' +
' }).catch(function() { _m3uFallback(full); });' +
' } else { _m3uFallback(full); }' +
'}' +
'function _m3uFallback(text) {' +
' var ta = document.createElement("textarea");' +
' ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0";' +
' document.body.appendChild(ta); ta.select();' +
' try { document.execCommand("copy"); _m3uCopied(); } catch(e) {}' +
' document.body.removeChild(ta);' +
'}' +
'function _m3uCopied() {' +
' var btn = document.querySelector(".m3u-copy-btn");' +
' if (!btn) return;' +
' var orig = btn.textContent;' +
' btn.textContent = "已复制 ✓"; btn.style.background = "#52c41a";' +
' setTimeout(function() { btn.textContent = orig; btn.style.background = ""; }, 2000);' +
'}' +
'</script>';
}
function renderDetail(movie) {
const thumbSrc = movie.thumb || '';
const proxySrc = movie.videoUrl || '';
const movieId = movie.id || '';
const guessHls = isHlsUrl(proxySrc);
const noVideoBlock = '<div class="no-video"><span>🎬</span><p>视频链接不可用</p></div>';
const playerBlock = proxySrc ?
'<div class="player-shell" id="playerShell">' +
'<div class="player-poster" id="playerPoster">' +
(thumbSrc
? '<img src="' + esc(thumbSrc) + '" alt="' + esc(movie.title) + '" class="poster-img">'
: '<div class="poster-img poster-blank"></div>') +
'<button class="big-play-btn" id="bigPlayBtn" aria-label="播放">' +
'<svg viewBox="0 0 24 24" fill="white" width="44" height="44"><polygon points="5,3 19,12 5,21"/></svg>' +
'</button>' +
'<div class="player-spinner hidden" id="playerSpinner">' +
'<div class="spinner-ring"></div>' +
'</div>' +
'<div class="player-error hidden" id="playerError">' +
'<span>⚠️</span>' +
'<p id="playerErrorMsg">加载失败</p>' +
'<button class="retry-btn" onclick="initPlayer()">重试</button>' +
'</div>' +
'</div>' +
'<video id="mainVideo" class="main-video" playsinline webkit-playsinline preload="none" ' +
(thumbSrc ? 'poster="' + esc(thumbSrc) + '"' : '') +
' crossorigin="anonymous"></video>' +
'</div>' : noVideoBlock;
const playerScript = proxySrc ?
'<script>' +
'(function () {' +
' var SRC = ' + JSON.stringify(proxySrc) + ';' +
' var THUMB = ' + JSON.stringify(thumbSrc) + ';' +
' var GUESS_HLS = ' + guessHls + ';' +
' var HLS_CDN = "https://cdn.jsdelivr.net/npm/hls.js@1/dist/hls.min.js";' +
' var video = document.getElementById("mainVideo");' +
' var poster = document.getElementById("playerPoster");' +
' var bigPlayBtn = document.getElementById("bigPlayBtn");' +
' var spinner = document.getElementById("playerSpinner");' +
' var errorBox = document.getElementById("playerError");' +
' var errorMsg = document.getElementById("playerErrorMsg");' +
' var hlsInstance = null;' +
' var started = false;' +
' function show(el){ el && el.classList.remove("hidden"); }' +
' function hide(el){ el && el.classList.add("hidden"); }' +
' function initPlayer() {' +
' hide(errorBox); show(bigPlayBtn); show(poster);' +
' video.src = "";' +
' if(hlsInstance){ hlsInstance.destroy(); hlsInstance = null; }' +
' }' +
' function startLoad() {' +
' if(started) return; started = true;' +
' hide(bigPlayBtn); show(spinner);' +
' if(GUESS_HLS) {' +
' if(video.canPlayType("application/vnd.apple.mpegurl")) {' +
' video.src = SRC; video.play().catch(function(e){ console.error(e); });' +
' } else {' +
' var s = document.createElement("script");' +
' s.src = HLS_CDN;' +
' s.onload = function() {' +
' if(!Hls.isSupported()) {' +
' hide(spinner); show(errorBox); errorMsg.innerText = "浏览器不支持 HLS"; return;' +
' }' +
' hlsInstance = new Hls(); hlsInstance.loadSource(SRC); hlsInstance.attachMedia(video);' +
' hlsInstance.on(Hls.Events.MANIFEST_PARSED, function(){ video.play().catch(function(e){ console.error(e); }); });' +
' };' +
' document.head.appendChild(s);' +
' }' +
' } else {' +
' video.src = SRC; video.play().catch(function(e){ console.error(e); });' +
' }' +
' }' +
' video.addEventListener("playing", function(){ hide(poster); hide(spinner); });' +
' video.addEventListener("error", function(){ hide(spinner); show(errorBox); errorMsg.innerText = "视频加载出错"; });' +
' bigPlayBtn.onclick = startLoad;' +
'})();' +
'</script>' : '';
return '<div class="detail-wrap">' +
'<a href="javascript:history.back()" class="back-btn">' +
'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">' +
'<path d="M19 12H5M12 19l-7-7 7-7"/>' +
'</svg>' +
' 返回' +
'</a>' +
'<div class="detail-card">' +
'<div class="video-wrap">' + playerBlock + '</div>' +
'<div class="detail-body">' +
'<h1 class="detail-title">' + esc(movie.title) + '</h1>' +
'<div class="detail-meta">' +
'<span>⏱ ' + esc(movie.time) + '</span>' +
'</div>' +
'<div class="action-bar">' +
'<button class="action-btn like-btn" data-id="' + esc(movieId) + '" onclick="toggleLike(this)">' +
'<span class="action-icon">❤</span>' +
'<span class="btn-label">点赞</span>' +
'</button>' +
'<button class="action-btn fav-btn"' +
' data-id="' + esc(movieId) + '"' +
' data-title="' + esc(movie.title) + '"' +
' data-thumb="' + esc(thumbSrc) + '"' +
' data-video="' + esc(proxySrc) + '"' +
' data-time="' + esc(movie.time) + '"' +
' onclick="toggleFav(this)">' +
'<span class="action-icon">⭐</span>' +
'<span class="btn-label">收藏</span>' +
'</button>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
playerScript +
`<script>
(function(){
var id = ` + JSON.stringify(movieId) + `;
var likes = {}; var favs = {};
try { likes = JSON.parse(localStorage.getItem('otc_likes') || '{}'); } catch(e) {}
try { favs = JSON.parse(localStorage.getItem('otc_favs') || '{}'); } catch(e) {}
var lb = document.querySelector('.like-btn[data-id]');
var fb = document.querySelector('.fav-btn[data-id]');
if (lb && likes[id]) { lb.classList.add('liked'); lb.querySelector('.btn-label').textContent = '已点赞'; }
if (fb && favs[id]) { fb.classList.add('faved'); fb.querySelector('.btn-label').textContent = '已收藏'; }
})();
</script>`;
}
function esc(s) {
return String(s || '')
.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}
function renderLayout(content, title, activeSort) {
const navLinks = NAV.map(function(n) {
return '<a href="' + n.href + '" class="nav-link' + (activeSort === n.sort ? ' active' : '') + '">' + n.label + '</a>';
}).join('');
const bottomNav = NAV.map(function(n) {
return '<a href="' + n.href + '" class="bnav-item' + (activeSort === n.sort ? ' active' : '') + '">' +
'<span class="bnav-icon">' + n.icon + '</span>' +
'<span class="bnav-label">' + n.label + '</span>' +
'</a>';
}).join('');
return '<!DOCTYPE html>' +
'<html lang="zh-CN">' +
'<head>' +
'<meta charset="UTF-8">' +
'<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">' +
'<meta name="theme-color" content="#000000">' +
'<title>' + esc(title) + ' - OTC VIDEO</title>' +
'<style>' +
'*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}' +
':root{' +
' --bg:#000;--surface:#0d0d0d;--surface2:#161616;--border:rgba(255,255,255,0.07);' +
' --text:#e8e8e8;--muted:#555;--accent:#e8195a;--accent2:#ff6b9d;' +
' --radius:14px;--nav-h:56px;--bnav-h:60px;--safe-b:env(safe-area-inset-bottom,0px);' +
'}' +
'html{scroll-behavior:smooth}' +
'body{background:var(--bg);color:var(--text);font-family:sans-serif;font-size:14px;' +
' line-height:1.5;-webkit-font-smoothing:antialiased;overflow-x:hidden;}' +
'a{color:inherit;text-decoration:none}' +
'img{display:block;max-width:100%}' +
'button{cursor:pointer;border:none;font-family:inherit}' +
'.topnav{' +
' position:sticky;top:0;z-index:100;height:var(--nav-h);' +
' background:rgba(0,0,0,0.85);' +
' backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);' +
' border-bottom:1px solid var(--border);display:flex;align-items:center;padding:0 20px;gap:16px;}' +
'.logo{font-size:20px;font-weight:800;letter-spacing:-0.5px;white-space:nowrap;flex-shrink:0;}' +
'.logo span{color:var(--accent)}' +
'.topnav-search{' +
' flex:1;max-width:340px;display:flex;align-items:center;' +
' background:var(--surface2);border:1px solid var(--border);' +
' border-radius:10px;overflow:hidden;transition:border-color .2s;}' +
'.topnav-search:focus-within{border-color:rgba(232,25,90,.4)}' +
'.topnav-search input{' +
' flex:1;background:transparent;border:none;outline:none;' +
' color:var(--text);font-size:13px;padding:8px 12px;}' +
'.topnav-search input::placeholder{color:var(--muted)}' +
'.topnav-search button{' +
' background:transparent;color:var(--muted);padding:8px 12px;' +
' display:flex;align-items:center;transition:color .2s;}' +
'.topnav-search button:hover{color:var(--accent2)}' +
'.topnav-links{display:flex;gap:4px;margin-left:auto}' +
'.nav-link{' +
' padding:6px 14px;border-radius:8px;font-size:13px;font-weight:700;' +
' color:var(--muted);transition:background .15s,color .15s;white-space:nowrap;}' +
'.nav-link:hover{color:var(--text);background:var(--surface2)}' +
'.nav-link.active{color:#fff;background:var(--accent)}' +
'.extract-section { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; margin-bottom: 24px; }' +
'.extract-label { font-size: 14px; color: var(--text); margin-bottom: 12px; font-weight: 500; }' +
'.extract-box { display: flex; gap: 10px; align-items: stretch; }' +
'.extract-box input { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); padding: 12px 16px; font-size: 14px; outline: none; }' +
'.extract-box input:focus { border-color: var(--accent); }' +
'.extract-box button { background: #1d9bf0; color: #fff; padding: 0 20px; border-radius: 8px; font-weight: 700; display: flex; align-items: center; gap: 6px; transition: opacity .2s; }' +
'.extract-box button:hover { opacity: 0.9; }' +
'.extract-box button:disabled { opacity: 0.5; cursor: not-allowed; }' +
'.extract-status { margin-top: 10px; font-size: 12px; }' +
'.extract-status.error { color: #ff4d4f; }' +
'.extract-status.success { color: #52c41a; }' +
'.extract-status.loading { color: #1d9bf0; }' +
'.extract-result-area { margin-top: 20px; padding-top: 20px; border-top: 1px dashed var(--border); }' +
'.res-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }' +
'.res-item { background: var(--surface2); border-radius: 8px; overflow: hidden; display: flex; flex-direction: column; }' +
'.res-thumb { position: relative; aspect-ratio: 16/9; }' +
'.res-thumb img { width: 100%; height: 100%; object-fit: cover; }' +
'.res-badge { position: absolute; top: 4px; right: 4px; background: rgba(0,0,0,0.6); color: #fff; font-size: 10px; padding: 2px 6px; border-radius: 4px; }' +
'.res-dl-btn { padding: 8px; text-align: center; font-size: 12px; font-weight: 700; background: var(--accent); color: #fff; }' +
'.bnav{' +
' position:fixed;bottom:0;left:0;right:0;z-index:100;' +
' height:calc(var(--bnav-h) + var(--safe-b));padding-bottom:var(--safe-b);' +
' background:rgba(0,0,0,0.92);' +
' backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);' +
' border-top:1px solid var(--border);flex-direction:row;align-items:stretch;display:none;}' +
'.bnav-item{' +
' flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;' +
' gap:3px;color:var(--muted);transition:color .15s;' +
' -webkit-tap-highlight-color:transparent;user-select:none;}' +
'.bnav-item.active{color:var(--accent)}' +
'.bnav-icon{font-size:18px;line-height:1}' +
'.bnav-label{font-size:10px;font-weight:700}' +
'.main-content{min-height:calc(100vh - var(--nav-h))}' +
'.page-wrap{max-width:1400px;margin:0 auto;padding:20px 16px 32px}' +
'.search-bar-wrap{display:none;margin-bottom:16px}' +
'.search-form{' +
' display:flex;align-items:center;background:var(--surface2);' +
' border:1px solid var(--border);border-radius:12px;overflow:hidden;transition:border-color .2s;}' +
'.search-form:focus-within{border-color:rgba(232,25,90,.4)}' +
'.search-form input{' +
' flex:1;background:transparent;border:none;outline:none;color:var(--text);' +
' font-size:15px;padding:13px 16px;min-width:0;}' +
'.search-form input::placeholder{color:var(--muted)}' +
'.search-form button{' +
' background:var(--accent);color:#fff;padding:0 18px;height:100%;min-height:50px;' +
' display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s;}' +
'.search-form button:hover{background:var(--accent2)}' +
'.chips-wrap{' +
' margin-bottom:20px;' +
' -webkit-mask:linear-gradient(to right,#000 88%,transparent 100%);' +
' mask:linear-gradient(to right,#000 88%,transparent 100%);}' +
'.chips-scroll{display:flex;gap:8px;overflow-x:auto;padding-bottom:4px;scrollbar-width:none}' +
'.chips-scroll::-webkit-scrollbar{display:none}' +
'.chip{' +
' flex-shrink:0;padding:7px 14px;border-radius:999px;font-size:12px;font-weight:700;' +
' background:var(--surface2);border:1px solid var(--border);color:var(--muted);' +
' white-space:nowrap;transition:background .15s,color .15s,border-color .15s;' +
' -webkit-tap-highlight-color:transparent;}' +
'.chip:hover{color:var(--accent2);border-color:rgba(232,25,90,.35)}' +
'.chip-active{background:var(--accent);border-color:var(--accent);color:#fff !important}' +
'.grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}' +
'.card{' +
' background:var(--surface);border-radius:var(--radius);overflow:hidden;' +
' border:1px solid var(--border);transition:border-color .2s,transform .2s;will-change:transform;}' +
'.card:hover{border-color:rgba(232,25,90,.35);transform:translateY(-2px)}' +
'.card:active{transform:scale(.97)}' +
'.card-thumb{display:block;position:relative;aspect-ratio:9/16;overflow:hidden;background:var(--surface2)}' +
'.card-thumb img{width:100%;height:100%;object-fit:cover;transition:transform .4s}' +
'.card:hover .card-thumb img{transform:scale(1.05)}' +
'.badge-time{' +
' position:absolute;bottom:6px;right:6px;background:rgba(0,0,0,.75);color:#fff;' +
' font-size:10px;font-family:monospace;' +
' padding:2px 6px;border-radius:5px;backdrop-filter:blur(4px);}' +
'.card-body{padding:10px 10px 8px}' +
'.card-title{' +
' font-size:11px;font-weight:500;color:#ccc;' +
' display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;' +
' overflow:hidden;line-height:1.45;min-height:32px;margin-bottom:6px;}' +
'.card-title-link{display:block;color:inherit;text-decoration:none}' +
'.card-title-link:hover .card-title{color:var(--accent2)}' +
'.card-meta{display:flex;justify-content:space-between;font-size:10px;color:var(--muted)}' +
'.fav{color:var(--accent2)}' +
'.pagination{' +
' display:flex;align-items:center;justify-content:center;' +
' gap:12px;padding:32px 0 8px;flex-wrap:wrap;}' +
'.page-btn{' +
' display:inline-flex;align-items:center;padding:11px 24px;' +
' background:var(--surface2);border:1px solid var(--border);' +
' border-radius:10px;font-size:13px;font-weight:700;color:var(--text);' +
' transition:background .15s,border-color .15s;-webkit-tap-highlight-color:transparent;}' +
'.page-btn:not(.disabled):hover{background:var(--accent);border-color:var(--accent)}' +
'.page-btn.disabled{opacity:.3;pointer-events:none}' +
'.page-info{font-size:12px;color:var(--muted)}' +
'.empty-state{text-align:center;padding:80px 20px}' +
'.empty-icon{font-size:48px;opacity:.25;margin-bottom:12px}' +
'.empty-text{font-size:16px;font-weight:700;color:var(--muted)}' +
'.detail-wrap{max-width:960px;margin:0 auto;padding:20px 16px 40px}' +
'.back-btn{' +
' display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:700;' +
' color:var(--muted);padding:8px 0;margin-bottom:16px;transition:color .15s;' +
' -webkit-tap-highlight-color:transparent;min-height:44px;}' +
'.back-btn:hover{color:var(--accent2)}' +
'.detail-card{' +
' background:var(--surface);border-radius:20px;overflow:hidden;' +
' border:1px solid var(--border);}' +
'.video-wrap{' +
' position:relative; width:100%; aspect-ratio:16/9; background:#000; overflow:hidden;' +
'}' +
'.player-shell{' +
' position:absolute;inset:0; display:flex;align-items:center;justify-content:center; background:#000;' +
'}' +
'.main-video{' +
' position:absolute;inset:0; width:100%;height:100%; object-fit:contain; background:#000; z-index:1;' +
'}' +
'.player-poster{' +
' position:absolute;inset:0; display:flex;flex-direction:column; align-items:center;justify-content:center; z-index:10; background:#000; transition:opacity .4s ease;' +
'}' +
'.poster-img{' +
' position:absolute;inset:0; width:100%;height:100%; object-fit:cover; opacity:.55;' +
'}' +
'.poster-blank{background:var(--surface2)}' +
'.big-play-btn{' +
' position:relative;z-index:11; width:76px;height:76px;border-radius:50%; background:var(--accent);' +
' display:flex;align-items:center;justify-content:center; padding-left:5px; box-shadow:0 6px 32px rgba(232,25,90,.55);' +
' transition:transform .15s,background .15s; -webkit-tap-highlight-color:transparent;' +
'}' +
'.big-play-btn:hover{transform:scale(1.1);background:var(--accent2)}' +
'.big-play-btn:active{transform:scale(.95)}' +
'.player-spinner{' +
' position:absolute;inset:0;z-index:12; display:flex;align-items:center;justify-content:center; background:rgba(0,0,0,.45);' +
'}' +
'.spinner-ring{' +
' width:44px;height:44px;border-radius:50%; border:3px solid rgba(255,255,255,.15); border-top-color:var(--accent); animation:spin .8s linear infinite;' +
'}' +
'@keyframes spin{to{transform:rotate(360deg)}}' +
'.player-error{' +
' position:absolute;inset:0;z-index:13; display:flex;flex-direction:column; align-items:center;justify-content:center;gap:10px; background:rgba(0,0,0,.75);color:var(--text);text-align:center;padding:20px;' +
'}' +
'.player-error span{font-size:32px}' +
'.player-error p{font-size:13px;color:var(--muted);max-width:260px}' +
'.retry-btn{' +
' margin-top:4px;padding:9px 22px;border-radius:999px; background:var(--accent);color:#fff;font-size:13px;font-weight:700; transition:background .15s;' +
'}' +
'.retry-btn:hover{background:var(--accent2)}' +
'.no-video{' +
' position:absolute;inset:0; display:flex;flex-direction:column; align-items:center;justify-content:center; gap:10px;color:var(--muted);font-size:14px;' +
'}' +
'.no-video span{font-size:40px;opacity:.4}' +
'.hidden{display:none !important}' +
'.detail-body{padding:20px}' +
'.detail-title{' +
' font-size:18px; font-weight:800;line-height:1.3;margin-bottom:12px;}' +
'.detail-meta{display:flex;gap:18px;font-size:13px;color:var(--muted);margin-bottom:4px;flex-wrap:wrap}' +
'.m3u-section{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px 20px;margin-bottom:20px}' +
'.m3u-section-head{display:flex;align-items:baseline;gap:10px;margin-bottom:14px;flex-wrap:wrap}' +
'.m3u-section-title{font-size:14px;font-weight:700;color:var(--text)}' +
'.m3u-section-tip{font-size:11px;color:var(--muted)}' +
'.m3u-links{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}' +
'.m3u-btn{display:inline-flex;align-items:center;gap:5px;padding:8px 16px;border-radius:8px;font-size:12px;font-weight:700;background:var(--surface2);border:1px solid var(--border);color:var(--text);transition:all .2s;white-space:nowrap;text-decoration:none;-webkit-tap-highlight-color:transparent;}' +
'.m3u-btn:hover{border-color:rgba(29,155,240,.5);color:#1d9bf0}' +
'.m3u-btn-cat{background:rgba(29,155,240,.1);border-color:rgba(29,155,240,.3);color:#1d9bf0}' +
'.m3u-cat-row{margin-bottom:10px}' +
'.m3u-copy-row{display:flex;align-items:center;gap:8px;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:6px 8px 6px 12px}' +
'.m3u-copy-label{font-size:11px;color:var(--muted);white-space:nowrap;flex-shrink:0}' +
'.m3u-copy-input{flex:1;background:transparent;border:none;outline:none;color:var(--muted);font-size:12px;font-family:monospace;min-width:0}' +
'.m3u-copy-btn{flex-shrink:0;background:var(--accent);color:#fff;border:none;padding:6px 14px;border-radius:6px;font-size:12px;font-weight:700;cursor:pointer;transition:background .15s}' +
'.m3u-copy-btn:hover{background:var(--accent2)}' +
'.action-bar{display:flex;gap:12px;margin-top:16px;flex-wrap:wrap}' +
'.action-btn{display:inline-flex;align-items:center;gap:8px;padding:11px 22px;border-radius:999px;font-size:14px;font-weight:700;border:1.5px solid var(--border);background:var(--surface2);color:var(--text);transition:all .2s;-webkit-tap-highlight-color:transparent;min-height:44px;}' +
'.action-btn:hover{border-color:rgba(232,25,90,.4);color:var(--accent2)}' +
'.action-btn.liked{background:rgba(232,25,90,.15);border-color:var(--accent);color:var(--accent)}' +
'.action-btn.faved{background:rgba(255,200,0,.12);border-color:#f5a623;color:#f5a623}' +
'.action-icon{font-size:18px;line-height:1}' +
'.card-fav-btn{position:absolute;top:6px;right:6px;z-index:5;background:rgba(0,0,0,.6);color:#aaa;border:none;border-radius:8px;font-size:14px;padding:4px 8px;line-height:1;backdrop-filter:blur(4px);transition:all .2s;-webkit-tap-highlight-color:transparent;}' +
'.card-fav-btn:hover,.card-fav-btn.faved{color:#f5a623}' +
'.card-fav-btn.faved{background:rgba(245,166,35,.2)}' +
'.fav-page-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}' +
'.fav-page-title{font-size:20px;font-weight:800}' +
'.fav-clear-btn{padding:8px 18px;border-radius:999px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;transition:all .15s;}' +
'.fav-clear-btn:hover{border-color:var(--accent);color:var(--accent)}' +
'.otc-toast{position:fixed;bottom:calc(var(--bnav-h) + var(--safe-b) + 16px);left:50%;transform:translateX(-50%) translateY(20px);background:rgba(30,30,30,.95);color:#fff;padding:10px 22px;border-radius:999px;font-size:13px;font-weight:700;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .25s,transform .25s;z-index:9999;backdrop-filter:blur(10px);}' +
'.otc-toast.show{opacity:1;transform:translateX(-50%) translateY(0)}' +
'@media(min-width:1024px){.otc-toast{bottom:24px}}' +
'@media(min-width:540px){ .grid{grid-template-columns:repeat(3,1fr);gap:12px} .card-title{font-size:12px} }' +
'@media(min-width:768px){ .grid{grid-template-columns:repeat(4,1fr);gap:14px} .page-wrap{padding:24px 24px 40px} .detail-body{padding:24px 28px} .big-play-btn{width:88px;height:88px} }' +
'@media(min-width:1024px){ .grid{grid-template-columns:repeat(5,1fr);gap:16px} .search-bar-wrap{display:none !important} .topnav-search{display:flex} .topnav-links{display:flex} .bnav{display:none !important} .main-content{padding-bottom:0} .detail-wrap{padding:28px 24px 56px} .detail-body{padding:28px 36px} .big-play-btn{width:96px;height:96px} }' +
'@media(min-width:1280px){ .grid{grid-template-columns:repeat(6,1fr)} }' +
'@media(max-width:1023px){ .topnav-search{display:none} .topnav-links{display:none} .search-bar-wrap{display:block} .bnav{display:flex} .main-content{padding-bottom:calc(var(--bnav-h) + var(--safe-b) + 8px)} }' +
'@media(max-width:500px) and (orientation:portrait){ .video-wrap{aspect-ratio:9/16;max-height:72vw;aspect-ratio:unset;height:56vw} }' +
'</style>' +
'</head>' +
'<body>' +
'<nav class="topnav">' +
' <a href="/zh-CN/" class="logo">OTC<span>.</span>VIDEO</a>' +
' <div class="topnav-links">' +
navLinks +
' </div>' +
'</nav>' +
'<main class="main-content">' +
content +
'</main>' +
'<nav class="bnav">' +
bottomNav +
'</nav>' +
`<script>
var OTC_LIKES_KEY='otc_likes', OTC_FAVS_KEY='otc_favs';
function _getLikes(){try{return JSON.parse(localStorage.getItem(OTC_LIKES_KEY)||'{}')}catch(e){return{}}}
function _getFavs() {try{return JSON.parse(localStorage.getItem(OTC_FAVS_KEY) ||'{}')}catch(e){return{}}}
function toggleLike(btn){
var id=btn.dataset.id; var likes=_getLikes(); var on=!!likes[id];
if(on){delete likes[id];}else{likes[id]=Date.now();}
localStorage.setItem(OTC_LIKES_KEY,JSON.stringify(likes));
_applyLike(btn,!on); _toast(on?'已取消点赞':'点赞成功 ❤');
}
function toggleFav(btn){
var id=btn.dataset.id; var favs=_getFavs(); var on=!!favs[id];
if(on){
delete favs[id];
var card=document.getElementById('fav-card-'+id);
if(card){ card.style.transition='opacity .3s'; card.style.opacity='0'; setTimeout(function(){card.remove();_checkFavEmpty();},300); }
} else {
favs[id]={id:id,title:btn.dataset.title||id,thumb:btn.dataset.thumb||'',video:btn.dataset.video||'',time:btn.dataset.time||''};
}
localStorage.setItem(OTC_FAVS_KEY,JSON.stringify(favs));
document.querySelectorAll('[data-id="'+id+'"].card-fav-btn,[data-id="'+id+'"].fav-btn').forEach(function(b){_applyFav(b,!on);});
_toast(on?'已取消收藏':'收藏成功 ⭐');
}
function _applyLike(btn,on){
btn.classList.toggle('liked',on);
var lb=btn.querySelector('.btn-label'); if(lb) lb.textContent=on?'已点赞':'点赞';
}
function _applyFav(btn,on){
btn.classList.toggle('faved',on);
var lb=btn.querySelector('.btn-label'); if(lb) lb.textContent=on?'已收藏':'收藏';
}
function _checkFavEmpty(){
var grid=document.getElementById('fav-grid');
var empty=document.getElementById('fav-empty');
if(grid && empty && grid.children.length===0){grid.style.display='none';empty.style.display='block';}
}
function _toast(msg){
var t=document.createElement('div'); t.className='otc-toast'; t.textContent=msg;
document.body.appendChild(t);
requestAnimationFrame(function(){t.classList.add('show');});
setTimeout(function(){t.classList.remove('show');setTimeout(function(){t.remove();},300);},2000);
}
document.addEventListener('DOMContentLoaded',function(){
var likes=_getLikes(); var favs=_getFavs();
document.querySelectorAll('.like-btn[data-id]').forEach(function(btn){_applyLike(btn,!!likes[btn.dataset.id]);});
document.querySelectorAll('.fav-btn[data-id],.card-fav-btn[data-id]').forEach(function(btn){_applyFav(btn,!!favs[btn.dataset.id]);});
});
</script>` +
'</body>' +
'</html>';
}