<!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">
<title>LingoApple 英语学习</title>
<!-- 引入 Tailwind CSS 和 Feather Icons -->
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #F2F2F7;
-webkit-tap-highlight-color: transparent;
overflow-x: hidden;
}
.hide-scrollbar::-webkit-scrollbar { display: none; }
.hide-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.active-scale:active { transform: scale(0.96); transition: transform 0.1s; }
/* 页面切换动画 */
.tab-content {
display: none;
animation: fadeIn 0.4s ease-out;
}
.tab-content.active { display: block; }
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 详情页滑入动画 */
.detail-view {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: #fff; z-index: 40;
transform: translateX(100%);
transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
overflow-y: auto;
}
.detail-view.active { transform: translateX(0); }
/* 底部弹窗动画 */
.bottom-sheet {
transform: translateY(100%);
transition: transform 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.bottom-sheet.active { transform: translateY(0); }
.text-shadow { text-shadow: 0 1px 4px rgba(0,0,0,0.3); }
.glass-nav { background: rgba(255, 255, 255, 0.75); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); }
</style>
</head>
<body class="selection:bg-blue-200">
<div id="tab-home" class="tab-content active px-6 pt-16 pb-32">
<header class="mb-6">
<p class="text-gray-500 font-medium uppercase tracking-wider text-sm mb-1" id="current-date">Today</p>
<h1 class="text-3xl font-bold text-gray-900 tracking-tight">发现文章</h1>
</header>
<!-- 分级选择器 (Segmented Control) -->
<div class="bg-gray-200/80 p-1 rounded-xl flex mb-6">
<button onclick="setFilter('article', 'B1')" id="btn-art-B1" class="flex-1 py-1.5 text-sm font-semibold rounded-lg bg-white shadow-sm text-gray-900 transition-all">B1 中级</button>
<button onclick="setFilter('article', 'B2')" id="btn-art-B2" class="flex-1 py-1.5 text-sm font-semibold rounded-lg text-gray-500 hover:text-gray-700 transition-all">B2 中高级</button>
<button onclick="setFilter('article', 'C1')" id="btn-art-C1" class="flex-1 py-1.5 text-sm font-semibold rounded-lg text-gray-500 hover:text-gray-700 transition-all">C1 高级</button>
</div>
<div id="articles-container" class="space-y-4">
<!-- 动态渲染文章列表 -->
</div>
</div>
<div id="tab-video" class="tab-content px-6 pt-16 pb-32">
<header class="mb-6">
<h1 class="text-3xl font-bold text-gray-900 tracking-tight">情景对话</h1>
</header>
<!-- 分级选择器 -->
<div class="bg-gray-200/80 p-1 rounded-xl flex mb-6">
<button onclick="setFilter('video', 'B1')" id="btn-vid-B1" class="flex-1 py-1.5 text-sm font-semibold rounded-lg bg-white shadow-sm text-gray-900 transition-all">B1 日常</button>
<button onclick="setFilter('video', 'B2')" id="btn-vid-B2" class="flex-1 py-1.5 text-sm font-semibold rounded-lg text-gray-500 hover:text-gray-700 transition-all">B2 职场</button>
<button onclick="setFilter('video', 'C1')" id="btn-vid-C1" class="flex-1 py-1.5 text-sm font-semibold rounded-lg text-gray-500 hover:text-gray-700 transition-all">C1 商业</button>
</div>
<div id="videos-container" class="space-y-4">
<!-- 动态渲染视频列表 -->
</div>
</div>
<div id="tab-vocab" class="tab-content px-6 pt-16 pb-32">
<header class="mb-8">
<h1 class="text-3xl font-bold text-gray-900 tracking-tight">我的生词本</h1>
<p class="text-gray-500 mt-2">共收录 <span id="vocab-count" class="font-bold text-blue-500">0</span> 个单词</p>
</header>
<div id="vocab-list" class="space-y-3">
<!-- 动态渲染生词 -->
</div>
</div>
<div id="tab-profile" class="tab-content px-6 pt-16 pb-32">
<header class="mb-8 text-center mt-10">
<div class="w-24 h-24 bg-gray-200 rounded-full mx-auto mb-4 flex items-center justify-center overflow-hidden">
<img src="https://api.dicebear.com/7.x/notionists/svg?seed=Felix" alt="avatar" class="w-full h-full object-cover">
</div>
<h1 class="text-2xl font-bold text-gray-900">Apple User</h1>
<p class="text-gray-500 text-sm mt-1">词汇量评估: ~3500</p>
</header>
<div class="bg-white rounded-3xl p-6 shadow-sm border border-gray-100 flex items-center justify-between mb-4">
<div>
<h3 class="text-gray-500 font-medium mb-1">连续学习</h3>
<div class="flex items-baseline gap-1">
<span id="streak-count" class="text-4xl font-bold text-gray-900">12</span>
<span class="text-gray-400 font-medium">天</span>
</div>
</div>
<div class="w-16 h-16 bg-orange-50 rounded-full flex items-center justify-center">
<i data-feather="flame" class="w-8 h-8 text-orange-500"></i>
</div>
</div>
</div>
<nav id="bottom-nav" class="fixed bottom-0 left-0 right-0 glass-nav border-t border-gray-200/50 pb-safe z-30 transition-transform duration-300">
<div class="flex justify-around items-center h-16 px-2 pb-2">
<button onclick="switchTab('home')" id="nav-home" class="flex flex-col items-center justify-center w-16 h-full space-y-1 text-[#007AFF] transition-colors">
<i data-feather="book-open" class="w-6 h-6"></i>
<span class="text-[10px] font-medium">阅读</span>
</button>
<button onclick="switchTab('video')" id="nav-video" class="flex flex-col items-center justify-center w-16 h-full space-y-1 text-gray-400 hover:text-gray-600 transition-colors">
<i data-feather="play-circle" class="w-6 h-6"></i>
<span class="text-[10px] font-medium">看剧</span>
</button>
<button onclick="switchTab('vocab')" id="nav-vocab" class="flex flex-col items-center justify-center w-16 h-full space-y-1 text-gray-400 hover:text-gray-600 transition-colors">
<i data-feather="bookmark" class="w-6 h-6"></i>
<span class="text-[10px] font-medium">生词本</span>
</button>
<button onclick="switchTab('profile')" id="nav-profile" class="flex flex-col items-center justify-center w-16 h-full space-y-1 text-gray-400 hover:text-gray-600 transition-colors">
<i data-feather="user" class="w-6 h-6"></i>
<span class="text-[10px] font-medium">我的</span>
</button>
</div>
</nav>
<div id="article-view" class="detail-view">
<div class="sticky top-0 z-10 glass-nav border-b border-gray-100 px-4 py-4 flex items-center justify-between">
<button onclick="closeDetail('article')" class="p-2 -ml-2 text-[#007AFF] active-scale rounded-full">
<i data-feather="chevron-left" class="w-7 h-7"></i>
</button>
<div id="article-level-badge" class="text-sm font-semibold text-gray-900 bg-gray-100 px-3 py-1 rounded-full">B1</div>
<button class="p-2 -mr-2 text-[#007AFF] active-scale">
<i data-feather="headphones" class="w-6 h-6"></i>
</button>
</div>
<div class="px-5 py-6 max-w-2xl mx-auto pb-32">
<img id="article-cover" src="" class="w-full h-56 object-cover rounded-3xl mb-6 shadow-sm">
<h1 id="article-title" class="text-3xl font-bold text-gray-900 mb-6 leading-tight tracking-tight">Title</h1>
<div id="article-content" class="text-lg text-gray-800 leading-relaxed space-y-5 font-serif">
<!-- 动态渲染内容 -->
</div>
<div class="mt-12 text-center text-gray-400 text-sm">
点击文中任意单词即可查看释义
</div>
<button onclick="doCheckIn()" class="mt-8 w-full bg-[#007AFF] text-white font-semibold text-lg py-4 rounded-2xl shadow-[0_8px_20px_rgb(0,122,255,0.3)] active-scale transition-all">
完成阅读并打卡
</button>
</div>
</div>
<div id="video-view" class="detail-view bg-black flex flex-col">
<div class="absolute top-0 left-0 right-0 z-30 bg-gradient-to-b from-black/80 to-transparent px-4 py-3 flex items-center justify-between">
<button onclick="closeDetail('video')" class="p-2 -ml-2 text-white bg-black/20 backdrop-blur-md rounded-full active-scale">
<i data-feather="chevron-down" class="w-6 h-6"></i>
</button>
<div class="text-sm font-semibold text-white text-shadow">情景演练</div>
<div class="w-10"></div>
</div>
<div class="w-full relative flex-shrink-0 bg-gray-900 pt-14 pb-4 px-2">
<div id="youtube-container" class="w-full aspect-video rounded-xl overflow-hidden shadow-2xl bg-black">
<!-- YouTube Iframe -->
</div>
</div>
<div class="flex-1 bg-[#F2F2F7] rounded-t-[32px] -mt-2 z-20 relative px-4 pt-6 overflow-y-auto pb-32">
<div class="w-12 h-1.5 bg-gray-300 rounded-full mx-auto mb-5"></div>
<h2 id="video-title" class="text-2xl font-bold text-gray-900 mb-2 px-2">Title</h2>
<!-- 讲解模块 -->
<div class="px-2 mb-6 mt-4">
<button onclick="toggleSummary()" class="w-full bg-white border border-blue-100 text-[#007AFF] font-semibold text-sm py-3.5 rounded-2xl shadow-sm flex items-center justify-center gap-2 active-scale transition-colors">
<i data-feather="file-text" class="w-4 h-4"></i>
<span>查看本课讲解与总结</span>
<i data-feather="chevron-down" id="summary-icon" class="w-4 h-4 transition-transform duration-300"></i>
</button>
<div id="summary-panel" class="hidden mt-3 p-5 bg-white shadow-sm border border-gray-100 rounded-2xl text-[15px] text-gray-700 leading-relaxed font-serif">
</div>
</div>
<!-- 剧本区 -->
<div class="space-y-4 px-2" id="script-container"></div>
<div class="px-2 mt-10">
<button onclick="doCheckIn()" class="w-full bg-[#007AFF] text-white font-semibold text-lg py-4 rounded-2xl shadow-[0_8px_20px_rgb(0,122,255,0.3)] active-scale transition-all">
完成练习并打卡
</button>
</div>
</div>
</div>
<div id="dict-backdrop" class="fixed inset-0 bg-black/30 backdrop-blur-sm z-50 hidden opacity-0 transition-opacity duration-300" onclick="closeDict()"></div>
<div id="dict-modal" class="fixed bottom-0 left-0 right-0 bg-white rounded-t-[32px] p-6 z-50 bottom-sheet flex flex-col max-h-[80vh] shadow-[0_-10px_40px_rgba(0,0,0,0.1)]">
<div class="w-12 h-1.5 bg-gray-200 rounded-full mx-auto mb-5"></div>
<div class="flex justify-between items-start mb-4">
<div>
<h3 id="dict-word" class="text-3xl font-bold text-gray-900 capitalize mb-1">Word</h3>
<div class="flex items-center gap-3">
<span id="dict-phonetic" class="text-[#007AFF] font-serif text-lg">/wɜːd/</span>
<button class="text-[#007AFF] bg-blue-50 p-1.5 rounded-full active-scale">
<i data-feather="volume-2" class="w-4 h-4"></i>
</button>
</div>
</div>
</div>
<div class="bg-gray-50 rounded-2xl p-4 mb-6 flex-1 overflow-y-auto">
<span id="dict-pos" class="inline-block bg-gray-200 text-gray-600 text-xs px-2 py-0.5 rounded mb-2 font-bold uppercase"></span>
<p id="dict-def" class="text-gray-800 text-lg leading-relaxed"></p>
</div>
<button id="btn-add-vocab" onclick="handleAddVocab()" class="w-full bg-gray-900 text-white font-semibold py-4 rounded-2xl active-scale transition-colors text-lg flex items-center justify-center gap-2">
<i data-feather="plus" class="w-5 h-5"></i> 加入生词本
</button>
</div>
<!-- 打卡成功特效 -->
<div id="checkin-toast" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-gray-900/90 backdrop-blur-md text-white px-8 py-6 rounded-3xl z-50 flex flex-col items-center gap-3 opacity-0 pointer-events-none transition-opacity duration-300 scale-95">
<div class="w-16 h-16 bg-green-500 rounded-full flex items-center justify-center mb-2">
<i data-feather="check" class="w-8 h-8 text-white"></i>
</div>
<h3 class="text-xl font-bold">打卡成功!</h3>
<p class="text-gray-300 text-sm">连续学习天数 +1</p>
</div>
<script>
// --- 初始化日期 ---
const options = { weekday: 'long', month: 'short', day: 'numeric' };
document.getElementById('current-date').textContent = new Date().toLocaleDateString('en-US', options);
// --- 全局状态 ---
let MY_VOCAB = [];
let STREAK = 12;
let currentWordData = null;
// --- 视频 YouTube ID 池 (用于提取多样且真实的视频自带封面) ---
const YT_ID_POOL = [
'2VeQTuSSiI0', '4zXys7i8Zrc', 'Z3HJCQJ2Lmo', 'dQw4w9WgXcQ', 'jNQXAC9IVRw',
'M7FIvfx5J10', 'tgbNymZ7vqY', 'kJQP7kiw5Fk', '9bZkp7q19f0', 'F4tHL8reNCs',
'fJ9rUzIMcZQ', 'lWA2pjMjpBs', 'hT_nvWreIhg', 'V-_O7nl0Ii0', '3tmd-ClpJxA'
];
// --- 基础精编数据 ---
const BASE_ARTICLES = [
{ id: 'a1', level: 'B1', title: 'The Magic of Morning Routines', tag: 'Daily Life', image: 'https://images.unsplash.com/photo-1511988617509-a57c8a288659?auto=format&fit=crop&w=600&q=80',
content: "Starting your day with a solid morning routine can completely transform your life. Many successful people emphasize the importance of waking up early. It gives you a quiet moment before the chaos of the day begins. You can use this time to meditate, read, or exercise. For example, drinking a glass of water and doing light stretches can boost your energy. It is not about doing everything perfectly, but about consistency. When you establish a habit, your brain requires less effort to perform it. Try to avoid checking your phone immediately after waking up. Instead, focus on setting positive intentions. Over time, these small micro-habits compound into massive changes. You will find yourself feeling less stressed and more focused during your commute to work. Remember, the goal is progress, not perfection." },
{ id: 'a2', level: 'B2', title: 'Mastering Remote Collaboration', tag: 'Workplace', image: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?auto=format&fit=crop&w=600&q=80',
content: "In today's fast-paced corporate environment, effective remote collaboration is no longer just a perk; it is an absolute necessity. Working from home presents unique challenges, primarily concerning communication and maintaining team cohesion. Without the spontaneous water-cooler conversations, colleagues must be more intentional about checking in with each other. Utilizing project management tools efficiently can bridge the geographical gap. However, the true secret lies in developing a strong sense of empathy. When interpreting a brief text message or email, always assume positive intent. Furthermore, regular video conferences, while sometimes exhausting, provide crucial non-verbal cues that text lacks. To prevent burnout, it is essential to establish clear boundaries between professional and personal life. As a manager, fostering a culture where asking for help is encouraged rather than penalized will significantly enhance overall productivity and employee satisfaction. Remember to seek diverse perspectives to solve complex problems innovatively." },
{ id: 'a3', level: 'C1', title: 'Advanced Negotiation Tactics', tag: 'Business', image: 'https://images.unsplash.com/photo-1556761175-5973dc0f32e7?auto=format&fit=crop&w=600&q=80',
content: "Navigating the intricate landscape of high-stakes business negotiations demands far more than mere assertiveness. It requires a profound understanding of behavioral psychology and strategic empathy. Novice negotiators often make the critical error of focusing exclusively on their own objectives, neglecting to identify the underlying interests of their counterparts. To secure a truly lucrative deal, one must meticulously map out the leverage points before entering the boardroom. This involves anticipating objections and preparing mutually beneficial concessions. Silence is arguably the most potent, yet underutilized, weapon in a negotiator's arsenal. By pausing deliberately after a significant proposal, you compel the other party to fill the void, often revealing vital information. Moreover, framing your offers in a way that highlights what they stand to lose, rather than gain, taps into the psychological principle of loss aversion. Ultimately, the objective is not to defeat the opponent, but to forge a sustainable partnership that yields long-term dividends for both organizations." }
];
const BASE_VIDEOS = [
{ id: 'v1', level: 'B1', title: 'Coffee Shop Order', tag: 'Daily', youtubeId: '2VeQTuSSiI0',
image: 'https://img.youtube.com/vi/2VeQTuSSiI0/hqdefault.jpg', // 直接抓取 YouTube 封面
summary: "<strong>💡 场景解析:</strong><br>在咖啡馆点单时,最地道的说法不是 'I want',而是 'Can I get'。<br><br><strong>📌 核心词汇:</strong><br>1. latte: 拿铁<br>2. regular: 常规的<br>3. to go: 打包带走",
script: [{s:'Barista', t:"Hi, what can I get for you today?"}, {s:'You', t:"Can I get a large iced latte, please?"}, {s:'Barista', t:"For here or to go?"}, {s:'You', t:"To go, please."}] },
{ id: 'v2', level: 'B2', title: 'Office Small Talk', tag: 'Work', youtubeId: '4zXys7i8Zrc',
image: 'https://img.youtube.com/vi/4zXys7i8Zrc/hqdefault.jpg', // 直接抓取 YouTube 封面
summary: "<strong>💡 场景解析:</strong><br>讨论通勤(commute)是职场寒暄最安全的破冰话题。<br><br><strong>📌 核心词汇:</strong><br>1. commute: 通勤<br>2. collaboration: 协作",
script: [{s:'Colleague', t:"How was your commute this morning?"}, {s:'You', t:"Not bad, traffic was surprisingly light."}, {s:'Colleague', t:"Great. Let's focus on team collaboration today."}] },
{ id: 'v3', level: 'C1', title: 'Negotiation Strategy', tag: 'Business', youtubeId: 'Z3HJCQJ2Lmo',
image: 'https://img.youtube.com/vi/Z3HJCQJ2Lmo/hqdefault.jpg', // 直接抓取 YouTube 封面
summary: "<strong>💡 场景解析:</strong><br>高阶谈判需挖掘潜在利益(underlying interests)并使用筹码(leverage)。",
script: [{s:'Speaker', t:"Don't just focus on the price. Find the underlying interests."}, {s:'You', t:"Exactly. We must leverage our unique value to secure a lucrative deal."}] }
];
// --- 海量数据生成器 (500/300) ---
const ALL_ARTICLES = [...BASE_ARTICLES];
const ALL_VIDEOS = [...BASE_VIDEOS];
['B1', 'B2', 'C1'].forEach(level => {
const artBase = BASE_ARTICLES.find(a => a.level === level);
const vidBase = BASE_VIDEOS.find(v => v.level === level);
// 自动生成 500 篇文章
for(let i = 2; i <= 500; i++) {
ALL_ARTICLES.push({
id: `a_${level}_${i}`, level: level, tag: artBase.tag,
title: `${level} Reading Vol.${i}`,
// 核心修改:利用唯一 ID 生成随机种子,保证 500 张配图完全不重复
image: `https://picsum.photos/seed/art_${level}_${i}/400/400`,
content: artBase.content // 演示时复用文本结构,词汇可点击查词
});
}
// 自动生成 300 个视频
for(let i = 2; i <= 300; i++) {
const assignedYtId = YT_ID_POOL[i % YT_ID_POOL.length];
ALL_VIDEOS.push({
id: `v_${level}_${i}`, level: level, tag: vidBase.tag,
title: `${level} Scenario Vol.${i}`,
youtubeId: assignedYtId,
// 核心修改:强制读取该视频在 YouTube 服务器上的真实原生封面 (High Quality)
image: `https://img.youtube.com/vi/${assignedYtId}/hqdefault.jpg`,
summary: vidBase.summary, script: vidBase.script
});
}
});
// --- STREAMING_CHUNK: 核心正则渲染引擎 (万物皆可点) ---
// 这个函数安全地跳过 HTML 标签,只把纯英文单词提取出来绑定点击事件
function parseAndHighlight(htmlStr) {
if(!htmlStr) return '';
// 预设的高级词汇标蓝
const highLevelWords = ['consistency', 'commute', 'collaboration', 'innovative', 'lucrative', 'leverage', 'empathy', 'procrastination', 'latte', 'underlying'];
return htmlStr.replace(/(<[^>]+>)|([a-zA-Z']+)/g, function(match, htmlTag, word) {
if (htmlTag) return htmlTag; // 保持原有 HTML 标签不变
if (word) {
const clean = word.toLowerCase();
const isHigh = highLevelWords.includes(clean);
const classList = isHigh
? 'text-blue-600 font-medium bg-blue-50/80 px-0.5 rounded cursor-pointer active:bg-blue-200 transition-colors inline-block'
: 'cursor-pointer active:bg-gray-200 rounded transition-colors inline-block';
// 用 span 包裹每个单词,并注入 onclick
return `<span class="${classList}" onclick="event.stopPropagation(); lookupWord('${word}')">${word}</span>`;
}
return match;
});
}
// --- 查词引擎 (模拟 API) ---
const MOCK_DICT = {
'commute': { p: '/kəˈmjuːt/', pos: 'v./n.', d: '通勤;上下班往返' },
'lucrative': { p: '/ˈluːkrətɪv/', pos: 'adj.', d: '利润丰厚的;赚钱的' },
'leverage': { p: '/ˈliːvərɪdʒ/', pos: 'n./v.', d: '杠杆作用;筹码;利用' },
'collaboration': { p: '/kəˌlæbəˈreɪʃn/', pos: 'n.', d: '合作;协作' },
'empathy': { p: '/ˈempəθi/', pos: 'n.', d: '同理心;共鸣' }
};
function lookupWord(rawWord) {
const word = rawWord.toLowerCase().replace(/[^a-z]/g, '');
if(!word) return;
// 模拟 API 返回
currentWordData = MOCK_DICT[word] || {
word: word,
p: `/[ ${word.substring(0,3)}... ]/`,
pos: 'n./v.',
d: `这是 "${word}" 的智能查词结果。在真实环境中这里将直接调用牛津词典或有道 API,返回精准的中文释义和英文例句。`
};
currentWordData.word = word; // 确保 word 字段存在
// 填充 UI
document.getElementById('dict-word').innerText = word;
document.getElementById('dict-phonetic').innerText = currentWordData.p;
document.getElementById('dict-pos').innerText = currentWordData.pos;
document.getElementById('dict-def').innerText = currentWordData.d;
// 检查是否已在生词本
const btn = document.getElementById('btn-add-vocab');
if (MY_VOCAB.some(v => v.word === word)) {
btn.innerHTML = `<i data-feather="check" class="w-5 h-5"></i> 已在生词本`;
btn.className = "w-full bg-gray-100 text-gray-400 font-semibold py-4 rounded-2xl flex items-center justify-center gap-2 cursor-default";
} else {
btn.innerHTML = `<i data-feather="plus" class="w-5 h-5"></i> 加入生词本`;
btn.className = "w-full bg-gray-900 text-white font-semibold py-4 rounded-2xl active-scale transition-colors text-lg flex items-center justify-center gap-2";
}
feather.replace();
// 显示弹窗
document.getElementById('dict-backdrop').classList.remove('hidden');
setTimeout(() => {
document.getElementById('dict-backdrop').classList.remove('opacity-0');
document.getElementById('dict-modal').classList.add('active');
}, 10);
}
function closeDict() {
document.getElementById('dict-backdrop').classList.add('opacity-0');
document.getElementById('dict-modal').classList.remove('active');
setTimeout(() => {
document.getElementById('dict-backdrop').classList.add('hidden');
}, 300);
}
function handleAddVocab() {
if (!currentWordData || MY_VOCAB.some(v => v.word === currentWordData.word)) return;
MY_VOCAB.push({
word: currentWordData.word,
p: currentWordData.p,
d: currentWordData.d
});
closeDict();
renderVocabList(); // 更新生词本UI
}
function removeFromVocab(word) {
MY_VOCAB = MY_VOCAB.filter(v => v.word !== word);
renderVocabList();
}
// --- UI 渲染逻辑 ---
function renderVocabList() {
const container = document.getElementById('vocab-list');
document.getElementById('vocab-count').innerText = MY_VOCAB.length;
if (MY_VOCAB.length === 0) {
container.innerHTML = `
<div class="flex flex-col items-center justify-center py-20 text-center opacity-60">
<i data-feather="book" class="w-12 h-12 mb-4"></i>
<p>生词本空空如也<br>点击文章中的单词即可添加</p>
</div>`;
} else {
// 倒序排列,最新添加的在最上面
container.innerHTML = [...MY_VOCAB].reverse().map(v => `
<div class="bg-white rounded-2xl p-4 shadow-sm flex items-center justify-between">
<div>
<div class="flex items-baseline gap-2 mb-0.5">
<h3 class="text-lg font-bold text-gray-900">${v.word}</h3>
<span class="text-sm text-[#007AFF] font-serif">${v.p}</span>
</div>
<p class="text-sm text-gray-600 line-clamp-1">${v.d}</p>
</div>
<button onclick="removeFromVocab('${v.word}')" class="p-2 text-gray-300 hover:text-red-500 rounded-full transition-colors active-scale">
<i data-feather="trash-2" class="w-5 h-5"></i>
</button>
</div>
`).join('');
}
feather.replace();
}
// 渲染列表 (带截断防止 DOM 过载,仅渲染前30条用于展示)
function setFilter(type, level) {
// 更新按钮样式
['B1', 'B2', 'C1'].forEach(l => {
const btn = document.getElementById(`btn-${type === 'article'?'art':'vid'}-${l}`);
if(l === level) {
btn.className = "flex-1 py-1.5 text-sm font-semibold rounded-lg bg-white shadow-sm text-gray-900 transition-all";
} else {
btn.className = "flex-1 py-1.5 text-sm font-semibold rounded-lg text-gray-500 hover:text-gray-700 transition-all";
}
});
if (type === 'article') {
const filtered = ALL_ARTICLES.filter(a => a.level === level).slice(0, 30);
document.getElementById('articles-container').innerHTML = filtered.map(a => `
<div onclick="openArticle('${a.id}')" class="bg-white rounded-3xl p-3 shadow-sm flex gap-4 active-scale transition-transform cursor-pointer">
<img src="${a.image}" class="w-24 h-24 rounded-2xl object-cover bg-gray-100">
<div class="flex flex-col justify-center flex-1">
<span class="text-xs font-semibold text-[#007AFF] mb-1">${a.tag}</span>
<h3 class="font-bold text-gray-900 leading-snug mb-1 text-base">${a.title}</h3>
<div class="text-xs text-gray-400">~${a.content.split(' ').length} words</div>
</div>
</div>
`).join('');
} else {
const filtered = ALL_VIDEOS.filter(v => v.level === level).slice(0, 30);
document.getElementById('videos-container').innerHTML = filtered.map(v => `
<div onclick="openVideo('${v.id}')" class="bg-white rounded-3xl p-3 shadow-sm flex gap-4 active-scale transition-transform cursor-pointer">
<div class="w-24 h-24 rounded-2xl relative overflow-hidden bg-gray-900">
<img src="${v.image}" class="w-full h-full object-cover opacity-70">
<i data-feather="play-circle" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-white w-8 h-8 opacity-90"></i>
</div>
<div class="flex flex-col justify-center flex-1">
<span class="text-xs font-semibold text-[#007AFF] mb-1">${v.tag}</span>
<h3 class="font-bold text-gray-900 leading-snug mb-1 text-base">${v.title}</h3>
<div class="text-xs text-gray-400">YouTube Video</div>
</div>
</div>
`).join('');
}
feather.replace();
}
// --- 页面路由与详情展示 ---
function switchTab(tabId) {
document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active'));
document.getElementById(`tab-${tabId}`).classList.add('active');
// 导航栏高亮
['home', 'video', 'vocab', 'profile'].forEach(id => {
const btn = document.getElementById(`nav-${id}`);
if(id === tabId) {
btn.classList.replace('text-gray-400', 'text-[#007AFF]');
} else {
btn.classList.replace('text-[#007AFF]', 'text-gray-400');
}
});
window.scrollTo(0, 0);
}
function openArticle(id) {
const art = ALL_ARTICLES.find(a => a.id === id);
document.getElementById('article-level-badge').innerText = art.level;
document.getElementById('article-cover').src = art.image;
document.getElementById('article-title').innerText = art.title;
// 核心:处理文章内容,使每个单词可点击
// 将文章按段落拆分(这里简单地以句子加换行模拟段落)
const paragraphs = art.content.split('. ').map(s => s + '.').join('<br><br>');
document.getElementById('article-content').innerHTML = parseAndHighlight(paragraphs);
document.getElementById('article-view').classList.add('active');
document.getElementById('bottom-nav').style.transform = 'translateY(100%)'; // 隐藏底部栏
}
function openVideo(id) {
const vid = ALL_VIDEOS.find(v => v.id === id);
document.getElementById('video-title').innerText = vid.title;
// 加载真实 YouTube
document.getElementById('youtube-container').innerHTML = `
<iframe class="w-full h-full" src="https://www.youtube.com/embed/${vid.youtubeId}?autoplay=1&playsinline=1" frameborder="0" allowfullscreen></iframe>
`;
// 渲染讲解(全部单词可点)
document.getElementById('summary-panel').innerHTML = parseAndHighlight(vid.summary);
document.getElementById('summary-panel').classList.add('hidden');
document.getElementById('summary-icon').style.transform = 'rotate(0deg)';
// 渲染剧本(全部单词可点)
document.getElementById('script-container').innerHTML = vid.script.map(line => {
const isYou = line.s === 'You';
const bubbleClass = isYou ? 'bg-[#007AFF] text-white rounded-tr-sm' : 'bg-gray-200 text-gray-900 rounded-tl-sm';
const micBtn = isYou ? `<button class="absolute -left-12 top-1/2 -translate-y-1/2 w-10 h-10 bg-blue-50 text-[#007AFF] rounded-full flex items-center justify-center active-scale shadow-sm"><i data-feather="mic" class="w-5 h-5"></i></button>` : '';
return `
<div class="flex flex-col ${isYou ? 'items-end' : 'items-start'}">
<span class="text-xs font-semibold text-gray-400 mb-1 ml-1">${line.s}</span>
<div class="max-w-[85%] rounded-2xl p-4 text-[15px] relative ${bubbleClass}">
${parseAndHighlight(line.t)}
${micBtn}
</div>
</div>`;
}).join('');
feather.replace();
document.getElementById('video-view').classList.add('active');
document.getElementById('bottom-nav').style.transform = 'translateY(100%)';
}
function closeDetail(type) {
document.getElementById(`${type}-view`).classList.remove('active');
document.getElementById('bottom-nav').style.transform = 'translateY(0)';
if(type === 'video') {
// 彻底销毁 iframe,停止视频和声音
document.getElementById('youtube-container').innerHTML = '';
}
}
function toggleSummary() {
const panel = document.getElementById('summary-panel');
const icon = document.getElementById('summary-icon');
if(panel.classList.contains('hidden')) {
panel.classList.remove('hidden');
icon.style.transform = 'rotate(180deg)';
} else {
panel.classList.add('hidden');
icon.style.transform = 'rotate(0deg)';
}
}
function doCheckIn() {
STREAK++;
document.getElementById('streak-count').innerText = STREAK;
closeDetail('article');
closeDetail('video');
const toast = document.getElementById('checkin-toast');
toast.classList.remove('opacity-0', 'scale-95');
setTimeout(() => {
toast.classList.add('opacity-0', 'scale-95');
}, 2500);
}
// --- 启动应用 ---
window.onload = () => {
setFilter('article', 'B1');
setFilter('video', 'B1');
renderVocabList();
feather.replace();
};
</script>
</body>
</html>