樱花动漫
https://www.yinghua228.com
分享者: jianghubailei (2226)发布时间: 2天前
原作者:cute-pan 添加搜索 (原生阅读不支持) 美化UI(顶部播放器,中间显示详情,下方多线路播放列表) 添加 自动播放下一集 新增 播放记录,断点续播
{
"articleStyle": 2,
"cacheFirst": false,
"customOrder": 0,
"enableJs": true,
"enabled": true,
"enabledCookieJar": true,
"header": "{\n \"User-Agent\": \"Mozilla\/5.0 (Linux; Android 10; TAS-AN00 Build\/HUAWEITAS-AN00; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/114.0.5735.61 Mobile Safari\/537.36 Super 4.6.5\",\n \"Referer\": \"https:\/\/www.yinghua228.com\/\",\n \"Cookie\": \"ad_dismiss=1\"\n}",
"injectJs": "let sources = [];\nlet currentSourceIndex = 0;\nlet currentEpisodeIndex = 0;\nlet dp = null;\nlet currentVideoUrl = '';\nlet autoPlayNext = true;\nlet currentVodId = '';\nlet currentVideoDuration = 0;\nlet progressTimer = null; \/\/ 全局定时器,精准控制\n\n\/\/ ======== 源变量的存取操作 =========\nfunction getSourceVar() {\n if (typeof source !== 'undefined' && source) {\n return String(source.getVariable()) || '';\n }\n return '';\n}\nfunction setSourceVar(value) {\n if (typeof source !== 'undefined' && source) {\n source.putVariable(value);\n }\n}\n\n\/\/ == 播放记录管理(多剧集 + 断点续播)==\nconst PROGRESS_SAVE_INTERVAL = 3000; \/\/ 每3秒保存一次进度\nconst PROGRESS_MIN_SAVE = 2; \/\/ 最小保存进度,2秒就存,避免短播放不记录\nconst PROGRESS_THRESHOLD = 0.95;\n\nfunction getAllRecords() {\n const varStr = getSourceVar();\n if (!varStr) return {};\n try { return JSON.parse(varStr); } catch (e) { return {}; }\n}\nfunction getRecord(vodId) {\n const all = getAllRecords();\n return all[vodId] || null;\n}\nfunction saveRecord(vodId, record) {\n const all = getAllRecords();\n all[vodId] = record;\n setSourceVar(JSON.stringify(all));\n}\nfunction removeRecord(vodId) {\n const all = getAllRecords();\n delete all[vodId];\n setSourceVar(JSON.stringify(all));\n}\nfunction cleanOldRecords() {\n const all = getAllRecords();\n const entries = Object.entries(all);\n if (entries.length > 50) {\n entries.sort((a,b) => (b[1].timestamp||0)-(a[1].timestamp||0));\n const keep = entries.slice(0,50);\n setSourceVar(JSON.stringify(Object.fromEntries(keep)));\n }\n}\n\n\/\/ ========== 核心工具函数(精准存\/清进度)==========\nfunction stopProgressTimer() {\n if (progressTimer) {\n clearInterval(progressTimer);\n progressTimer = null;\n }\n}\n\n\/\/ 保存**当前播放集**的进度\nfunction saveCurrentProgressImmediately() {\n if (!currentVodId || !dp || !sources[currentSourceIndex] || !sources[currentSourceIndex].episodes[currentEpisodeIndex]) {\n return;\n }\n const current = dp.video.currentTime || 0;\n const duration = dp.video.duration || 0;\n \/\/ 小于最小保存进度\/视频过短,不保存\n if (current < PROGRESS_MIN_SAVE || duration < 5) return;\n \n \/\/ 只保存**当前集**的进度,强制用全局当前的集数\/线路,杜绝串值\n const record = {\n sourceIndex: currentSourceIndex,\n episodeIndex: currentEpisodeIndex,\n episodeName: sources[currentSourceIndex].episodes[currentEpisodeIndex].name,\n progress: (current\/duration) > PROGRESS_THRESHOLD ? 0 : current,\n duration: duration,\n timestamp: Date.now() \/\/ 时间戳,确保最新记录\n };\n saveRecord(currentVodId, record);\n}\nfunction extractVodIdFromSourceContainer() {\n const html = $('#source-container').html() || '';\n const match = html.match(\/\"id\":\"(\\d+)\"\/) || html.match(\/\\\/id\\\/(\\d+)\/);\n return match ? match[1] : '';\n}\n\/\/ 【核心修复】重进时**严格校验记录的集数+时间戳**,只加载对应集的最新进度\nfunction playFromRecordOrFirstEpisode() {\n if (!currentVodId) { \n playEpisode(0,0, false, 0); \n return; \n }\n const record = getRecord(currentVodId);\n \/\/ 严格校验:记录存在+对应集数有效,才恢复进度\n if (record && sources[record.sourceIndex] && sources[record.sourceIndex].episodes[record.episodeIndex]) {\n currentSourceIndex = record.sourceIndex;\n currentEpisodeIndex = record.episodeIndex;\n $('.tab-btn').removeClass('active').eq(record.sourceIndex).addClass('active');\n initPlaylist();\n \/\/ 只恢复**该记录对应集**的进度,精准匹配\n playEpisode(record.sourceIndex, record.episodeIndex, false, record.progress);\n showError(`已恢复历史播放:${record.episodeName} ${formatTime(record.progress)}`);\n } else {\n playEpisode(0,0, false, 0);\n }\n}\n\n\/\/ ========== 页面初始化 ==========\n$(document).ready(function() {\n sources = parseSourcesFromHTML();\n currentVodId = extractVodIdFromSourceContainer();\n if (sources.length === 0) {\n $('#playlistContainer').html('<div class=\"error-msg show\">未找到播放列表数据<\/div>');\n return;\n }\n initTabs();\n initPlaylist();\n playFromRecordOrFirstEpisode();\n});\n\n\/\/ ========== 线路\/播放列表初始化(切集即时存进度)==========\nfunction initTabs() {\n const $tabs = $('#sourceTabs'); $tabs.empty();\n sources.forEach((src, index) => {\n const $btn = $('<button>').addClass('tab-btn').text(src.name).toggleClass('active', index===0);\n $btn.on('click', function() {\n \/\/ 切线路:强制存当前进度+清定时器,新线路从头播\n stopProgressTimer();\n saveCurrentProgressImmediately();\n $('.tab-btn').removeClass('active'); $(this).addClass('active');\n currentSourceIndex = index; currentEpisodeIndex = 0;\n initPlaylist();\n playEpisode(currentSourceIndex, currentEpisodeIndex, true, 0);\n });\n $tabs.append($btn);\n });\n addAutoPlayToggle();\n}\nfunction addAutoPlayToggle() {\n const $toggle = $('<button>').addClass('tab-btn').css({marginLeft:'auto',background:autoPlayNext?'#ff6b6b':'#666',fontSize:'12px'})\n .text(autoPlayNext?'连播: 开':'连播: 关').on('click', function() {\n autoPlayNext = !autoPlayNext;\n $(this).text(autoPlayNext?'连播: 开':'连播: 关').css('background',autoPlayNext?'#ff6b6b':'#666');\n showError(autoPlayNext?'已开启自动连播':'已关闭自动连播');\n updateAutoPlayStatus();\n });\n $('#sourceTabs').append($toggle);\n}\nfunction updateAutoPlayStatus() {\n $('#autoPlayStatus').text(autoPlayNext?'自动连播开启':'自动连播关闭').css('color',autoPlayNext?'#4ecdc4':'#888');\n}\n\/\/ 【核心修复】播放列表进度条**严格匹配记录的集数**,绝不显示错集进度\nfunction initPlaylist() {\n const $container = $('#playlistContainer'); const $errorMsg = $('#errorMsg').detach();\n $container.empty().append($errorMsg);\n const currentSource = sources[currentSourceIndex];\n const record = currentVodId ? getRecord(currentVodId) : null; \/\/ 只取当前视频的记录\n \n let recordTip = '';\n \/\/ 记录提示只显示**当前视频最后播放集**的进度\n if (record && (record.sourceIndex!==currentSourceIndex || record.episodeIndex!==currentEpisodeIndex)) {\n const progressText = record.progress>0 ? ` (${formatTime(record.progress)})` : '';\n recordTip = `<span style=\"color:#ff6b6b;margin-left:8px;cursor:pointer;\" onclick=\"resumeFromRecord()\">▶ 上次看到${record.episodeName}${progressText}<\/span>`;\n }\n $container.append(`\n <div class=\"now-playing\">\n <span class=\"label\">当前线路:<\/span><span class=\"source-name\">${currentSource.name}<\/span>\n <span style=\"margin:0 5px;\">|<\/span><span class=\"label\">选中:<\/span><span id=\"currentEpiText\">${currentSource.episodes[currentEpisodeIndex]?.name||''}<\/span>\n <span style=\"margin:0 5px;\">|<\/span><span id=\"autoPlayStatus\" style=\"color:${autoPlayNext?'#4ecdc4':'#888'};font-size:12px;\">${autoPlayNext?'自动连播开启':'自动连播关闭'}<\/span>\n ${recordTip}\n <\/div>\n `);\n $container.append(`<div class=\"playlist-title\"><span>播放列表<\/span><span class=\"episode-count\">共 ${currentSource.episodes.length} 集<\/span><\/div>`);\n const $grid = $('<div>').addClass('episode-grid');\n \n currentSource.episodes.forEach((ep, idx) => {\n const $btn = $('<button>').addClass('episode-btn').text(ep.name).toggleClass('playing', idx===currentEpisodeIndex);\n \/\/ 【关键】只有**当前记录的集数**,才显示进度条,其他集绝不显示\n if (record && record.sourceIndex === currentSourceIndex && record.episodeIndex === idx) {\n $btn.css('border', '2px solid #ff6b6b');\n if (record.progress > 0 && record.duration > 0) {\n const percent = Math.min((record.progress \/ record.duration) * 100, 100);\n const isFinished = percent > 95;\n $btn.css('position', 'relative');\n $btn.append(`\n <div style=\"\n position: absolute;\n bottom: 0;\n left: 0;\n height: 3px;\n background: ${isFinished ? '#4ecdc4' : '#ff6b6b'};\n width: ${isFinished ? 100 : percent}%;\n \"><\/div>\n <span style=\"\n position: absolute;\n top: 2px;\n right: 2px;\n font-size: 9px;\n color: ${isFinished ? '#4ecdc4' : '#ff6b6b'};\n background: rgba(0,0,0,0.7);\n padding: 1px 3px;\n border-radius: 3px;\n \">${isFinished ? '已看完' : formatTime(record.progress)}<\/span>\n `);\n } else {\n $btn.append('<span style=\"position:absolute;top:2px;right:2px;font-size:10px;color:#ff6b6b;\">上次<\/span>');\n }\n }\n $btn.on('click', function() {\n \/\/ 切集:即时存当前进度+清定时器,新集强制0秒从头播\n stopProgressTimer();\n saveCurrentProgressImmediately();\n currentEpisodeIndex = idx;\n initPlaylist();\n playEpisode(currentSourceIndex, currentEpisodeIndex, true, 0);\n });\n $grid.append($btn);\n });\n $container.append($grid);\n}\n\n\/\/ ========== 手动恢复历史记录(精准匹配集数)==========\nfunction resumeFromRecord() {\n const record = getRecord(currentVodId);\n if (!record) return;\n if (sources[record.sourceIndex] && sources[record.sourceIndex].episodes[record.episodeIndex]) {\n \/\/ 恢复前先保存当前播放的进度,避免丢失\n stopProgressTimer();\n saveCurrentProgressImmediately();\n currentSourceIndex = record.sourceIndex; currentEpisodeIndex = record.episodeIndex;\n $('.tab-btn').removeClass('active').eq(record.sourceIndex).addClass('active');\n initPlaylist();\n \/\/ 只恢复该记录对应集的进度\n playEpisode(currentSourceIndex, currentEpisodeIndex, false, record.progress);\n const progressText = record.progress>0 ? ` (${formatTime(record.progress)})` : '';\n showError(`已恢复至: ${record.episodeName}${progressText}`);\n }\n}\n\/\/ 时间格式化\nfunction formatTime(seconds) {\n if (!seconds || seconds<0) return '00:00';\n const h = Math.floor(seconds\/3600); const m = Math.floor((seconds%3600)\/60); const s = Math.floor(seconds%60);\n return h>0 ? `${h}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}` : `${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;\n}\n\n\/\/ ========== 播放核心函数(切集0秒,重进精准加载对应进度)==========\nfunction playEpisode(sourceIdx, episodeIdx, showTip=false, resumeProgress=0) {\n \/\/ 播放新集前,强制保存上一集进度,杜绝进度丢失\/串集\n stopProgressTimer();\n saveCurrentProgressImmediately();\n \n \/\/ 强制更新全局当前集数,确保后续保存进度精准\n currentSourceIndex = sourceIdx; \n currentEpisodeIndex = episodeIdx;\n \n const src = sources[sourceIdx]; \n const episode = src.episodes[episodeIdx];\n if (!episode) return; \/\/ 集数无效,直接返回\n \n $('#currentEpiText').text(episode.name);\n $('.episode-btn').removeClass('playing').eq(episodeIdx).addClass('playing');\n showLoading(true); hideError(); hideUrlTip();\n showError('正在解析播放地址,请稍候...');\n\n $.ajax({\n url: episode.url, type:'GET', dataType:'html', timeout:15000,\n success: function(html) {\n showLoading(false); hideError();\n if (!currentVodId) currentVodId = extractVodId(html);\n \/\/ 只使用外部传入的进度,切集传0则0,恢复记录传多少则多少,绝不自动读其他进度\n let startProgress = resumeProgress;\n parseAndPlay(html, showTip, startProgress);\n },\n error: function(xhr, status, error) {\n showLoading(false);\n showError(status==='timeout'?'请求超时,请尝试切换线路或刷新页面':xhr.status===404?'播放页面不存在,请尝试其他线路':'获取播放地址失败,请尝试切换线路');\n }\n });\n}\nfunction extractVodId(html) {\n if (currentVodId) return currentVodId;\n const match = html.match(\/\"id\":\"(\\d+)\"\/); if (match) return match[1];\n const urlMatch = html.match(\/\\\/id\\\/(\\d+)\/); if (urlMatch) return urlMatch[1];\n return '';\n}\nfunction parseAndPlay(html, showTip=false, startProgress=0) {\n try {\n const match = html.match(\/var\\s+player_aaaa\\s*=\\s*(\\{.+?\\})[\\s;]*<\/);\n if (!match) { showError('未找到播放器数据,该线路可能暂不可用'); return; }\n let jsonStr = match[1].replace(\/\\\\\/g,\"\").trim();\n let playerData; try { playerData = JSON.parse(jsonStr); } catch(e) { showError('数据解析失败,请尝试切换其他线路'); return; }\n if (!currentVodId && playerData.id) currentVodId = String(playerData.id);\n const videoUrl = playerData.url; if (!videoUrl) { showError('未找到视频链接,该线路可能暂不可用'); return; }\n currentVideoUrl = videoUrl;\n if (showTip) showUrlTip(videoUrl, '#playlistContainer');\n isDirectVideo(videoUrl) ? playWithDPlayer(videoUrl, playerData, startProgress) : playWithIframe(videoUrl);\n } catch(e) { showError('解析异常,请尝试切换线路:' + e.message.substring(0,50)); }\n}\nfunction isDirectVideo(url) {\n const lowerUrl = url.toLowerCase();\n return ['.mp4','.m3u8','.flv'].some(ext => lowerUrl.includes(ext));\n}\n\n\/\/ ========== DPlayer播放(精准按传入进度播放,实时存当前进度)==========\nfunction playWithDPlayer(url, playerData, startProgress=0) {\n \/\/ 销毁旧播放器,杜绝旧播放器的进度干扰\n if (dp) { \n dp.destroy(); \n dp = null; \n }\n $('#iframe-container').hide(); $('#dplayer').show(); hideUrlTip();\n const isM3U8 = url.toLowerCase().includes('.m3u8');\n dp = new DPlayer({\n container: document.getElementById('dplayer'),\n video: { url: url, type: isM3U8?'hls':'auto', pic: playerData.pic||'' },\n autoplay: true, theme: '#ff6b6b', lang: 'zh-cn', hotkey: true, preload: 'auto', volume: 0.8,\n playbackSpeed: [0.5,0.75,1,1.25,1.5,2]\n });\n let hasSeeked = false;\n\n \/\/ 视频元数据加载完成后,精准跳转到传入的进度(只跳当前集的)\n dp.on('loadedmetadata', function() {\n currentVideoDuration = dp.video.duration || 0;\n if (startProgress > 0 && currentVideoDuration > 0) {\n const safeProgress = Math.min(startProgress, currentVideoDuration-2);\n if (safeProgress > 0) { \n dp.seek(safeProgress); \n hasSeeked = true; \n showError(`已从 ${formatTime(safeProgress)} 继续播放`); \n }\n }\n });\n \/\/ 播放时启动定时器,实时保存**当前集**进度\n dp.on('play', function() {\n stopProgressTimer();\n progressTimer = setInterval(() => { saveCurrentProgressImmediately(); }, PROGRESS_SAVE_INTERVAL);\n });\n \/\/ 暂停时即时保存进度,避免退出丢失\n dp.on('pause', function() {\n stopProgressTimer();\n saveCurrentProgressImmediately();\n });\n \/\/ 进度更新时,只校正**当前集**的恢复进度,绝不串集\n dp.on('timeupdate', function() {\n if (!hasSeeked && startProgress > 0 && dp.video.currentTime < 3) {\n dp.seek(startProgress);\n hasSeeked = true;\n }\n });\n \/\/ 全屏\/退出全屏适配\n dp.on('fullscreen', function() {\n if (screen.orientation && screen.orientation.lock) {\n screen.orientation.lock('landscape').catch(()=>{screen.orientation.lock('any').catch(()=>{});});\n }\n });\n dp.on('fullscreen_cancel', function() {\n if (screen.orientation && screen.orientation.unlock) screen.orientation.unlock().catch(()=>{});\n });\n \/\/ M3U8视频适配\n if (isM3U8 && Hls.isSupported()) {\n dp.video.type = 'customHls';\n dp.video.customType = {\n customHls: function(video) { const hls = new Hls(); hls.loadSource(video.src); hls.attachMedia(video); }\n };\n }\n \/\/ 播放错误提示\n setTimeout(() => {\n dp.on('error', function() { showError('视频播放失败,请尝试切换线路或剧集'); });\n }, 10000);\n \/\/ 视频播放完毕\n dp.on('ended', function() {\n stopProgressTimer();\n saveCurrentProgressImmediately();\n if (autoPlayNext) playNextEpisode();\n else showError('本集已结束,自动连播已关闭');\n });\n \/\/ 页面关闭\/刷新时,强制保存当前进度,这是关键!\n window.addEventListener('beforeunload', function() {\n stopProgressTimer();\n saveCurrentProgressImmediately();\n });\n \/\/ 点击播放器显示视频地址\n $('#dplayer').off('click').on('click', function(e) {\n if ($(e.target).closest('.dplayer-controller, .dplayer-mask, #video-url-tip').length) return;\n showUrlTip(url, '#dplayer');\n });\n}\n\n\/\/ ========== 自动连播下一集(强制0秒从头播)==========\nfunction playNextEpisode() {\n const currentSource = sources[currentSourceIndex];\n const nextIndex = currentEpisodeIndex+1;\n if (nextIndex < currentSource.episodes.length) {\n showError(`即将播放下一集: ${currentSource.episodes[nextIndex].name}`);\n \/\/ 自动连播也强制0秒,从头播放下一集\n setTimeout(() => { playEpisode(currentSourceIndex, nextIndex, false, 0); }, 1500);\n } else {\n showError('本线路已播放完毕');\n $('#autoPlayStatus').text('本线路完结').css('color','#ff6b6b');\n cleanOldRecords();\n }\n}\n\n\/\/ ========== 外部播放器iframe播放 ==========\nfunction playWithIframe(url) {\n if (dp) { dp.destroy(); dp = null; }\n $('#dplayer').hide(); const $container = $('#iframe-container'); $container.empty().show();\n const $loading = $(`<div class=\"loading show\" style=\"position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);\"><div class=\"spinner\"><\/div><div>加载外部播放器...<\/div><\/div>`);\n $container.append($loading);\n const $iframe = $('<iframe>').attr('src','https:\/\/jx.quanmingjiexi.com\/?url='+url).attr('allowfullscreen','true').attr('frameborder','0').css({width:'100%',height:'100%',opacity:'0'}).on('load',function(){$loading.remove();$(this).css('opacity','1');});\n setTimeout(() => { $loading.remove(); $iframe.css('opacity','1'); }, 5000);\n $container.append($iframe);\n}\n\n\/\/ ========== 基础工具函数 ==========\nfunction showLoading(show) { $('#player-loading').toggleClass('show', show); }\nfunction showError(msg) { $('#errorMsg').text(msg).addClass('show'); setTimeout(()=>$('#errorMsg').removeClass('show'),3000); }\nfunction hideError() { $('#errorMsg').removeClass('show'); }\n\/\/ 全屏变化适配\ndocument.addEventListener('fullscreenchange', () => {\n if (document.fullscreenElement) {\n if (screen.orientation && screen.orientation.lock) screen.orientation.lock('landscape').catch(()=>{screen.orientation.lock('any').catch(()=>{});});\n } else {\n if (screen.orientation && screen.orientation.unlock) screen.orientation.unlock().catch(()=>{});\n }\n});\n\/\/ 屏幕旋转适配播放器大小\nwindow.addEventListener('orientationchange', () => { setTimeout(()=>dp&&dp.resize(),300); });\n\/\/ 显示视频地址提示\nfunction showUrlTip(url, container) {\n hideUrlTip(); if (window.urlTipTimeout) clearTimeout(window.urlTipTimeout);\n const $tip = $(`<div id=\"video-url-tip\" style=\"position:absolute;top:0;left:0;background:rgba(0,0,0,0.6);color:#fff;padding:8px 12px;font-size:12px;z-index:1000;max-width:100%;word-break:break-all;\"><div style=\"user-select:text;line-height:1.5;\">${url}<\/div><\/div>`);\n $(container||'body').append($tip);\n window.urlTipTimeout = setTimeout(()=>{hideUrlTip();},3000);\n}\nfunction hideUrlTip() { $('#video-url-tip').remove(); if (window.urlTipTimeout) clearTimeout(window.urlTipTimeout); window.urlTipTimeout=null; }\n\/\/ 详情展开\/收起\nfunction toggleDetail() {\n const content = document.getElementById('detailContent');\n const btn = document.querySelector('#info .toggle-btn');\n content.classList.toggle('show');\n btn.classList.toggle('active');\n btn.innerHTML = content.classList.contains('show') ? '收起详情 <span style=\"color:inherit\">▲<\/span>' : '查看详情 <span style=\"color:#ff6b6b\">▼<\/span>';\n}\n\/\/ 解析播放源和剧集\nfunction parseSourcesFromHTML() {\n const sources = [];\n $('.stui-pannel-box').each(function() {\n const $box = $(this);\n const sourceName = $box.find('.title').text().trim();\n const episodes = [];\n $box.find('.stui-content__playlist li a').each(function() {\n const $a = $(this);\n episodes.push({name:$a.text().trim(), url:$a.attr('href')});\n });\n if (episodes.length>0) sources.push({name:sourceName, episodes:episodes});\n });\n return sources;\n}\n",
"lastUpdateTime": 1773791081203,
"loadWithBaseUrl": true,
"preload": false,
"preloadJs": "window.java = java;\nwindow.source = source;",
"ruleArticles": ".stui-vodlist li||.stui-vodlist__media li",
"ruleContent": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <title>{{@@title@text}}<\/title>\n <link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/dplayer@1.25.0\/dist\/DPlayer.min.css\">\n<\/head>\n<body>\n <div id=\"dplayer\">\n <div class=\"loading\" id=\"player-loading\">\n <div class=\"spinner\"><\/div>\n <div>加载中...<\/div>\n <\/div>\n <\/div>\n \n <div id=\"iframe-container\">\n <div class=\"loading\" id=\"iframe-loading\">\n <div class=\"spinner\"><\/div>\n <div>加载外部播放器...<\/div>\n <\/div>\n <\/div>\n\n <div id=\"info\">\n <button class=\"toggle-btn\" onclick=\"toggleDetail()\">查看详情<\/button>\n \n <div class=\"detail-content\" id=\"detailContent\">\n <div class=\"detail-inner\">\n <div class=\"stui-content__detail\">\n{{@@.stui-content__detail > *:not(.play-btn)@html}}\n <\/div>\n <\/div>\n <\/div>\n<\/div>\n \n <div class=\"source-tabs\" id=\"sourceTabs\"><\/div>\n\n <div class=\"playlist-container\" id=\"playlistContainer\">\n <div class=\"error-msg\" id=\"errorMsg\"><\/div>\n <\/div>\n\n <!-- 原始播放列表数据 -->\n <div id=\"source-container\">\n {{@@.stui-pannel > .playlist@all}}\n <\/div>\n\n <script src=\"https:\/\/pss.bdstatic.com\/static\/superman\/js\/lib\/jquery-1-edb203c114.10.2.js\"><\/script>\n <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/hls.js@1.5.17\/dist\/hls.min.js\"><\/script>\n <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/dplayer@1.25.0\/dist\/DPlayer.min.js\"><\/script>\n\n<\/body>\n<\/html>",
"ruleImage": "a[0]@data-original",
"ruleLink": ".title a@href",
"ruleNextPage": "text.下一页@href",
"rulePubDate": "p@textNodes",
"ruleTitle": ".title a@text",
"searchUrl": "\/index.php\/vod\/search.html?wd={{key}}",
"showWebLog": false,
"singleUrl": false,
"sortUrl": "日本动漫::\/index.php\/vod\/type\/id\/20.html\n国产动漫::\/index.php\/vod\/type\/id\/21.html\n欧美动漫::\/index.php\/vod\/type\/id\/22.html\n动漫电影::\/index.php\/vod\/type\/id\/23.html\n其他动漫::\/index.php\/vod\/type\/id\/24.html",
"sourceComment": "原作者:cute-pan\n\n添加搜索 (原生阅读不支持)\n美化UI(顶部播放器,中间显示详情,下方多线路播放列表)\n添加 自动播放下一集\n\n新增 播放记录,断点续播",
"sourceGroup": "视频",
"sourceIcon": "https:\/\/tse2.mm.bing.net\/th\/id\/OIP.0w67-qvgh_VqOurfrC377AHaEK?dpr=3.5&pid=ImgDetMain&o=7&rm=3",
"sourceName": "樱花动漫",
"sourceUrl": "https:\/\/www.yinghua228.com",
"style": " * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n background: #0d0d0d;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n color: #fff;\n overflow-x: hidden;\n -webkit-tap-highlight-color: transparent;\n }\n\n #dplayer {\n width: 100%;\n height: 56.25vw;\n max-height: 70vh;\n background: #000;\n position: relative;\n z-index: 10;\n }\n\n .source-tabs {\n display: flex;\n overflow-x: auto;\n background: #1a1a1a;\n padding: 10px 5px;\n position: sticky;\n top: 0;\n z-index: 20;\n border-bottom: 1px solid #333;\n scrollbar-width: none;\n -ms-overflow-style: none;\n }\n\n .source-tabs::-webkit-scrollbar {\n display: none;\n }\n\n .tab-btn {\n flex-shrink: 0;\n padding: 8px 16px;\n margin: 0 5px;\n background: #2a2a2a;\n border: 1px solid #444;\n border-radius: 20px;\n color: #aaa;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.3s ease;\n white-space: nowrap;\n outline: none;\n }\n\n .tab-btn.active {\n background: #ff6b6b;\n border-color: #ff6b6b;\n color: #fff;\n box-shadow: 0 2px 8px rgba(255, 107, 107, 0.4);\n }\n .tab-btn:active {\n transform: scale(0.95);\n }\n\n .playlist-container {\n padding: 15px;\n background: #0d0d0d;\n min-height: calc(100vh - 56.25vw - 60px);\n }\n\n .playlist-title {\n font-size: 16px;\n color: #fff;\n margin-bottom: 15px;\n padding-left: 10px;\n border-left: 3px solid #ff6b6b;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .episode-count {\n font-size: 12px;\n color: #888;\n font-weight: normal;\n }\n\n .episode-grid {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 10px;\n }\n\n @media (min-width: 480px) {\n .episode-grid {\n grid-template-columns: repeat(5, 1fr);\n }\n }\n\n @media (min-width: 600px) {\n .episode-grid {\n grid-template-columns: repeat(6, 1fr);\n }\n }\n\n .episode-btn {\n background: #1f1f1f;\n border: 1px solid #333;\n border-radius: 8px;\n padding: 12px 5px;\n color: #ccc;\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n text-align: center;\n outline: none;\n position: relative;\n overflow: hidden;\n }\n\n .episode-btn::before {\n content: '';\n position: absolute;\n top: 0;\n left: -100%;\n width: 100%;\n height: 100%;\n background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);\n transition: left 0.5s;\n }\n\n .episode-btn:active::before {\n left: 100%;\n }\n\n .episode-btn.playing {\n background: #4ecdc4;\n border-color: #4ecdc4;\n color: #fff;\n animation: pulse 2s infinite;\n }\n\n @keyframes pulse {\n 0%, 100% { box-shadow: 0 0 0 0 rgba(78, 205, 196, 0.4); }\n 50% { box-shadow: 0 0 0 8px rgba(78, 205, 196, 0); }\n }\n\n .episode-btn:active {\n transform: scale(0.95);\n }\n\n #iframe-container {\n width: 100%;\n height: 56.25vw;\n max-height: 70vh;\n background: #000;\n display: none;\n position: relative;\n }\n\n #iframe-container iframe {\n width: 100%;\n height: 100%;\n border: none;\n }\n\n .loading {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n color: #ff6b6b;\n font-size: 14px;\n display: none;\n }\n\n .loading.show {\n display: block;\n }\n\n .spinner {\n width: 40px;\n height: 40px;\n border: 3px solid #333;\n border-top-color: #ff6b6b;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n margin: 0 auto 10px;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n .error-msg {\n background: rgba(255, 107, 107, 0.1);\n border: 1px solid #ff6b6b;\n color: #ff6b6b;\n padding: 10px;\n border-radius: 8px;\n margin: 10px 0;\n font-size: 13px;\n display: none;\n text-align: center;\n }\n\n .error-msg.show {\n display: block;\n }\n\n .now-playing {\n background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);\n padding: 12px 15px;\n margin: -15px -15px 15px -15px;\n border-bottom: 1px solid #333;\n font-size: 14px;\n color: #aaa;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .now-playing .label {\n color: #ff6b6b;\n font-weight: bold;\n }\n\n .now-playing .source-name {\n color: #4ecdc4;\n }\n\n .back-btn {\n position: fixed;\n top: 10px;\n left: 10px;\n z-index: 100;\n background: rgba(0,0,0,0.6);\n border: none;\n color: #fff;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 18px;\n cursor: pointer;\n backdrop-filter: blur(10px);\n }\n\n #source-container {\n display: none;\n }\n\n\/* ========== 详情区域样式(完全隔离,不会冲突)========== *\/\n#info {\n background: #1a1a1a;\n margin: 10px 15px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #333;\n}\n\n#info .toggle-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n width: 100%;\n padding: 12px 20px;\n background: #252525;\n color: #fff;\n border: none;\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n transition: all 0.3s ease;\n}\n\n#info .toggle-btn:hover {\n background: #2f2f2f;\n}\n\n#info .toggle-btn::after {\n content: '▼';\n font-size: 10px;\n color: #ff6b6b;\n transition: transform 0.3s ease;\n}\n\n#info .toggle-btn.active::after {\n transform: rotate(180deg);\n}\n\n#info .toggle-btn.active {\n background: #ff6b6b;\n color: #fff;\n}\n\n#info .toggle-btn.active::after {\n color: #fff;\n}\n\n#info .detail-content {\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.4s ease, opacity 0.3s ease;\n opacity: 0;\n background: #1a1a1a;\n}\n\n#info .detail-content.show {\n max-height: 800px;\n opacity: 1;\n}\n\n\/* 详情内部样式 - 全部加前缀避免冲突 *\/\n#info .detail-inner {\n padding: 15px;\n}\n\n#info .detail-title {\n font-size: 18px;\n color: #fff;\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n line-height: 1.4;\n}\n\n#info .detail-score {\n background: #ff6b6b;\n color: white;\n padding: 2px 8px;\n border-radius: 12px;\n font-size: 12px;\n font-weight: bold;\n flex-shrink: 0;\n}\n\n#info .detail-row {\n margin-bottom: 10px;\n font-size: 13px;\n line-height: 1.7;\n color: #ccc;\n}\n\n#info .detail-label {\n color: #888;\n margin-right: 5px;\n}\n\n#info .detail-row a {\n color: #4a9eff;\n text-decoration: none;\n margin-right: 6px;\n transition: color 0.2s;\n}\n\n#info .detail-row a:hover {\n color: #6ab3ff;\n text-decoration: underline;\n}\n\n#info .detail-split {\n display: inline-block;\n width: 1px;\n height: 10px;\n background: #444;\n margin: 0 8px;\n vertical-align: middle;\n}\n\n#info .detail-desc {\n background: #252525;\n padding: 12px;\n border-radius: 8px;\n margin-top: 12px;\n border-left: 3px solid #ff6b6b;\n font-size: 13px;\n line-height: 1.8;\n color: #bbb;\n}\n\n#info .detail-desc-label {\n display: block;\n color: #ff6b6b;\n margin-bottom: 6px;\n font-weight: 500;\n}\n\n\/* 隐藏元素 *\/\n#info .hidden-xs {\n display: inline;\n}\n\n@media (max-width: 480px) {\n #info .hidden-xs {\n display: none !important;\n }\n \n #info {\n margin: 8px;\n }\n \n #info .detail-inner {\n padding: 12px;\n }\n}\n\n\/* 详情区域a标签样式 - 灰色不刺眼 *\/\n#info a {\n color: #888 !important;\n text-decoration: none !important;\n pointer-events: none; \/* 阻止点击 *\/\n cursor: default;\n margin-right: 6px;\n}\n\n#info a:hover {\n color: #888 !important;\n text-decoration: none !important;\n}\n",
"type": 0
}