Files
examination/index.html
T
wonder 273b88219a feat: 前端优化 - 明暗模式、题型Tab分隔、移除统计
- 支持明暗主题切换,用户选择持久化到 localStorage
- 题型通过 Tab 按钮分隔展示,支持过滤
- 移除正确/错误统计和进度条
- 题型颜色通过 CSS 变量集中管理(--type-*)
- JS 抽取 QUESTION_TYPES 常量,便于扩展
- 更新架构文档反映上述变更

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-02 18:40:50 +08:00

577 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎯 CS 知识应试强化</title>
<style>
:root, :root[data-theme="dark"] {
--bg: #0f172a; --surface: #1e293b; --surface2: #334155;
--text: #e2e8f0; --text2: #94a3b8; --accent: #6366f1;
--accent2: #818cf8; --green: #22c55e; --red: #ef4444;
--yellow: #eab308; --radius: 12px;
--header-bg: linear-gradient(135deg, #312e81, #1e1b4b);
}
:root[data-theme="light"] {
--bg: #f1f5f9; --surface: #ffffff; --surface2: #e2e8f0;
--text: #1e293b; --text2: #64748b; --accent: #6366f1;
--accent2: #4f46e5; --green: #16a34a; --red: #dc2626;
--yellow: #ca8a04; --radius: 12px;
--header-bg: linear-gradient(135deg, #6366f1, #4f46e5);
}
/* Question type colors */
:root {
--type-fill_blank: #818cf8;
--type-single_choice: #22c55e;
--type-multiple_choice: #ec4899;
--type-true_false: #eab308;
--type-short_answer: #3b82f6;
--type-code_reading: #f97316;
--type-scenario: #a855f7;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); line-height: 1.6; min-height: 100vh; }
/* Header */
.header { background: var(--header-bg); padding: 28px 24px; text-align: center; border-bottom: 1px solid var(--surface2); position: relative; }
.header h1 { font-size: 26px; font-weight: 700; margin-bottom: 6px; }
.header p { color: rgba(255,255,255,0.7); font-size: 14px; }
.theme-toggle { position: absolute; top: 16px; right: 16px; background: rgba(255,255,255,0.15); border: none; color: white; width: 36px; height: 36px; border-radius: 8px; cursor: pointer; font-size: 18px; transition: background 0.2s; }
.theme-toggle:hover { background: rgba(255,255,255,0.25); }
/* Layout */
.app { display: flex; min-height: calc(100vh - 100px); }
.sidebar { width: 280px; background: var(--surface); border-right: 1px solid var(--surface2); padding: 16px; overflow-y: auto; flex-shrink: 0; }
.main { flex: 1; padding: 24px; overflow-y: auto; max-height: calc(100vh - 100px); }
/* Sidebar */
.sidebar-title { font-size: 13px; color: var(--text2); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 12px; padding: 0 8px; }
.topic-list { list-style: none; }
.topic-item { padding: 12px 16px; border-radius: 8px; cursor: pointer; transition: all 0.2s; margin-bottom: 4px; }
.topic-item:hover { background: var(--surface2); }
.topic-item.active { background: var(--accent); color: white; }
.topic-item .topic-name { font-size: 14px; font-weight: 600; }
.topic-item .topic-desc { font-size: 12px; color: var(--text2); margin-top: 2px; }
.topic-item.active .topic-desc { color: rgba(255,255,255,0.7); }
.topic-item .topic-stats { font-size: 11px; color: var(--text2); margin-top: 4px; }
.topic-item.active .topic-stats { color: rgba(255,255,255,0.6); }
.sidebar-section { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--surface2); }
.import-btn { width: 100%; padding: 10px; border-radius: 8px; border: 1px dashed var(--surface2); background: transparent; color: var(--text2); cursor: pointer; font-size: 13px; transition: all 0.2s; }
.import-btn:hover { border-color: var(--accent); color: var(--accent); }
/* Tabs */
.tabs { display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 20px; padding: 4px; background: var(--surface); border-radius: var(--radius); }
.tab { padding: 8px 16px; border-radius: 8px; border: none; background: transparent; color: var(--text2); cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.2s; }
.tab:hover { background: var(--surface2); color: var(--text); }
.tab.active { background: var(--accent); color: white; }
/* Toolbar */
.toolbar { display: flex; flex-wrap: wrap; gap: 12px; justify-content: flex-end; align-items: center; margin-bottom: 16px; }
.toolbar-actions { display: flex; gap: 8px; }
/* Buttons */
.btn { padding: 8px 16px; border-radius: 8px; border: none; cursor: pointer; font-size: 13px; font-weight: 600; transition: all 0.2s; }
.btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: var(--accent2); }
.btn-outline { background: transparent; border: 1px solid var(--surface2); color: var(--text2); }
.btn-outline:hover { border-color: var(--accent); color: var(--accent); }
.btn-sm { padding: 5px 12px; font-size: 12px; }
/* Question Card */
.q-card { background: var(--surface); border-radius: var(--radius); padding: 20px; margin-bottom: 16px; border: 1px solid var(--surface2); transition: border-color 0.3s; }
.q-card.correct { border-color: var(--green); }
.q-card.wrong { border-color: var(--red); }
.q-card.type-fill_blank { border-color: color-mix(in srgb, var(--type-fill_blank) 40%, transparent); }
.q-card.type-single_choice { border-color: color-mix(in srgb, var(--type-single_choice) 40%, transparent); }
.q-card.type-multiple_choice { border-color: color-mix(in srgb, var(--type-multiple_choice) 40%, transparent); }
.q-card.type-true_false { border-color: color-mix(in srgb, var(--type-true_false) 40%, transparent); }
.q-card.type-short_answer { border-color: color-mix(in srgb, var(--type-short_answer) 40%, transparent); }
.q-card.type-code_reading { border-color: color-mix(in srgb, var(--type-code_reading) 40%, transparent); }
.q-card.type-scenario { border-color: color-mix(in srgb, var(--type-scenario) 40%, transparent); }
.q-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 8px; flex-wrap: wrap; }
.q-number { font-size: 13px; color: var(--text2); font-weight: 600; }
.q-difficulty { display: flex; gap: 2px; }
.q-difficulty .star { width: 8px; height: 8px; border-radius: 50%; background: var(--surface2); }
.q-difficulty .star.active { background: var(--yellow); }
.q-text { font-size: 15px; font-weight: 500; margin-bottom: 16px; line-height: 1.7; }
/* Fill blank input inline */
.blank-input { display: inline-block; min-width: 100px; padding: 2px 8px; border: none; border-bottom: 2px dashed var(--accent); background: transparent; color: var(--text); font-size: 15px; font-weight: 500; outline: none; transition: all 0.2s; }
.blank-input:focus { border-bottom-color: var(--accent2); border-bottom-style: solid; }
.blank-input.correct { border-bottom-color: var(--green); color: var(--green); }
.blank-input.wrong { border-bottom-color: var(--red); color: var(--red); }
/* Options */
.options { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.option { display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: 8px; border: 1px solid var(--surface2); cursor: pointer; transition: all 0.2s; font-size: 14px; }
.option:hover { border-color: var(--accent); background: rgba(99,102,241,0.05); }
.option.selected { border-color: var(--accent); background: rgba(99,102,241,0.1); }
.option.correct { border-color: var(--green); background: rgba(34,197,94,0.1); }
.option.wrong { border-color: var(--red); background: rgba(239,68,68,0.1); }
.option .radio { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--surface2); flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
.option.selected .radio { border-color: var(--accent); }
.option.selected .radio::after { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--accent); }
.option.correct .radio { border-color: var(--green); }
.option.correct .radio::after { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--green); }
.option.wrong .radio { border-color: var(--red); }
.option.wrong .radio::after { content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--red); }
/* Actions */
.actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; margin-top: 4px; }
/* Answer box */
.answer-box { display: none; margin-top: 12px; padding: 16px; border-radius: 8px; background: var(--bg); border: 1px solid var(--surface2); font-size: 14px; }
.answer-box.show { display: block; }
.answer-box .label { font-weight: 600; color: var(--accent2); margin-bottom: 4px; }
.answer-box .answer-text { color: var(--green); font-weight: 600; margin-bottom: 8px; }
.answer-box .explanation { color: var(--text2); line-height: 1.7; }
/* Tags */
.q-tags { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 8px; }
.q-tag { font-size: 11px; padding: 2px 8px; border-radius: 4px; background: var(--surface2); color: var(--text2); }
/* Welcome */
.welcome { text-align: center; padding: 80px 20px; color: var(--text2); }
.welcome h2 { font-size: 20px; color: var(--text); margin-bottom: 8px; }
.welcome p { max-width: 400px; margin: 0 auto; }
/* Modal */
.modal-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 200; align-items: center; justify-content: center; }
.modal-overlay.show { display: flex; }
.modal { background: var(--surface); border-radius: var(--radius); padding: 24px; max-width: 600px; width: 90%; max-height: 80vh; overflow-y: auto; }
.modal h3 { margin-bottom: 16px; }
.modal textarea { width: 100%; height: 200px; padding: 12px; border-radius: 8px; border: 1px solid var(--surface2); background: var(--bg); color: var(--text); font-family: monospace; font-size: 13px; resize: vertical; outline: none; }
.modal textarea:focus { border-color: var(--accent); }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
.modal-or { text-align: center; color: var(--text2); font-size: 13px; margin: 12px 0; }
.file-input { display: block; width: 100%; padding: 12px; border: 1px dashed var(--surface2); border-radius: 8px; text-align: center; color: var(--text2); cursor: pointer; font-size: 13px; }
.file-input:hover { border-color: var(--accent); }
/* Responsive */
@media (max-width: 768px) {
.app { flex-direction: column; }
.sidebar { width: 100%; border-right: none; border-bottom: 1px solid var(--surface2); max-height: 200px; }
.main { max-height: none; }
.header h1 { font-size: 20px; }
}
</style>
</head>
<body>
<div class="header">
<h1>🎯 CS 知识应试强化</h1>
<p>选择主题,开始训练</p>
<button class="theme-toggle" onclick="toggleTheme()" title="切换主题">🌙</button>
</div>
<div class="app">
<div class="sidebar">
<div class="sidebar-title">📚 主题列表</div>
<ul class="topic-list" id="topicList"></ul>
<div class="sidebar-section">
<button class="import-btn" onclick="openImportModal()">📥 导入题目</button>
</div>
</div>
<div class="main" id="mainContent">
<div class="welcome">
<h2>👋 欢迎</h2>
<p>从左侧选择一个主题开始练习</p>
</div>
</div>
</div>
<!-- Import Modal -->
<div class="modal-overlay" id="importModal">
<div class="modal">
<h3>📥 导入题目</h3>
<p style="color:var(--text2);font-size:13px;margin-bottom:12px;">粘符合规范的 JSON 数据(topics 格式)</p>
<textarea id="importText" placeholder='{"topic":"custom","type":"single_choice","questions":[...]}'></textarea>
<div class="modal-or">— 或者 —</div>
<label class="file-input">
📁 选择 JSON 文件
<input type="file" accept=".json" style="display:none" onchange="handleFileImport(event)">
</label>
<div class="modal-actions">
<button class="btn btn-outline" onclick="closeImportModal()">取消</button>
<button class="btn btn-primary" onclick="doImport()">导入</button>
</div>
</div>
</div>
<script>
// ─── Constants ───
const QUESTION_TYPES = {
fill_blank: { label: '填空', color: '--type-fill_blank' },
single_choice: { label: '选择', color: '--type-single_choice' },
multiple_choice: { label: '多选', color: '--type-multiple_choice' },
true_false: { label: '判断', color: '--type-true_false' },
short_answer: { label: '简答', color: '--type-short_answer' },
code_reading: { label: '代码', color: '--type-code_reading' },
scenario: { label: '场景', color: '--type-scenario' },
};
// ─── State ───
let topics = [];
let currentTopic = null;
let currentQuestions = []; // flattened from all type files
let answers = {}; // qId -> { value, correct, revealed }
let customQuestions = []; // imported
let activeTab = 'all'; // 'all' or type name
// ─── Init ───
async function init() {
// Theme init
const saved = localStorage.getItem('theme') || 'dark';
document.documentElement.dataset.theme = saved;
updateThemeToggle();
try {
const res = await fetch('topics/index.json');
const data = await res.json();
topics = data.topics || [];
renderTopicList();
} catch (e) {
document.getElementById('topicList').innerHTML = '<li style="color:var(--red);padding:12px;">加载主题列表失败</li>';
}
}
// ─── Theme ───
function toggleTheme() {
const current = document.documentElement.dataset.theme;
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.dataset.theme = next;
localStorage.setItem('theme', next);
updateThemeToggle();
}
function updateThemeToggle() {
const btn = document.querySelector('.theme-toggle');
if (btn) btn.textContent = document.documentElement.dataset.theme === 'dark' ? '🌙' : '☀️';
}
// ─── Topic List ───
function renderTopicList() {
const ul = document.getElementById('topicList');
let html = '';
topics.forEach(t => {
const active = currentTopic === t.slug ? 'active' : '';
html += `<li class="topic-item ${active}" onclick="selectTopic('${t.slug}')">
<div class="topic-name">${t.name}</div>
<div class="topic-desc">${t.description}</div>
<div class="topic-stats">${t.stats.total} 题 · ${Object.entries(t.stats.by_type).map(([k,v])=>`${typeLabel(k)} ${v}`).join(' / ')}</div>
</li>`;
});
if (customQuestions.length) {
html += `<li class="topic-item ${currentTopic === '__custom__' ? 'active' : ''}" onclick="selectTopic('__custom__')">
<div class="topic-name">📥 自定义导入</div>
<div class="topic-desc">临时导入的题目</div>
<div class="topic-stats">${customQuestions.length} 题</div>
</li>`;
}
ul.innerHTML = html;
}
function typeLabel(type) {
return QUESTION_TYPES[type]?.label || type;
}
// ─── Select Topic ───
async function selectTopic(slug) {
currentTopic = slug;
answers = {};
activeTab = 'all';
renderTopicList();
if (slug === '__custom__') {
currentQuestions = customQuestions;
renderQuiz();
return;
}
const topic = topics.find(t => t.slug === slug);
if (!topic) return;
try {
// Load meta to get question file list
const metaRes = await fetch(`topics/${slug}/meta.json`);
const meta = await metaRes.json();
currentQuestions = [];
for (const typeFile of meta.question_files) {
const qRes = await fetch(`topics/${slug}/${typeFile}.json`);
const qData = await qRes.json();
currentQuestions.push(...qData.questions);
}
renderQuiz();
} catch (e) {
document.getElementById('mainContent').innerHTML = `<div class="welcome"><h2>❌ 加载失败</h2><p>${e.message}</p></div>`;
}
}
// ─── Render Quiz ───
function getFilteredQuestions() {
if (activeTab === 'all') return currentQuestions;
return currentQuestions.filter(q => q.type === activeTab);
}
function renderQuiz() {
const main = document.getElementById('mainContent');
if (!currentQuestions.length) {
main.innerHTML = '<div class="welcome"><h2>暂无题目</h2><p>该主题下没有题目数据</p></div>';
return;
}
// Build tabs
const types = [...new Set(currentQuestions.map(q => q.type))];
let tabsHtml = `<button class="tab ${activeTab === 'all' ? 'active' : ''}" onclick="switchTab('all')">全部 ${currentQuestions.length}</button>`;
types.forEach(type => {
const count = currentQuestions.filter(q => q.type === type).length;
tabsHtml += `<button class="tab ${activeTab === type ? 'active' : ''}" onclick="switchTab('${type}')">${typeLabel(type)} ${count}</button>`;
});
let html = `
<div class="tabs">${tabsHtml}</div>
<div class="toolbar">
<div class="toolbar-actions">
<button class="btn btn-outline btn-sm" onclick="resetAll()">🔄 重置</button>
<button class="btn btn-outline btn-sm" onclick="showAllAnswers()">👁 全部显示</button>
<button class="btn btn-primary btn-sm" onclick="submitAll()">📝 提交全部</button>
</div>
</div>
`;
const filtered = getFilteredQuestions();
filtered.forEach((q, i) => {
html += renderQuestionCard(q, i);
});
main.innerHTML = html;
}
function switchTab(tab) {
activeTab = tab;
renderQuiz();
}
function renderQuestionCard(q, i) {
const state = answers[q.id];
const revealed = state?.revealed;
const correct = state?.correct;
const cardClass = revealed ? (correct ? 'correct' : 'wrong') : '';
let stars = '';
for (let s = 1; s <= 5; s++) {
stars += `<div class="star ${s <= q.difficulty ? 'active' : ''}"></div>`;
}
let html = `<div class="q-card ${cardClass} type-${q.type}" id="card-${q.id}">
<div class="q-header">
<span class="q-number">#${i + 1}</span>
<div class="q-difficulty">${stars}</div>
</div>
<div class="q-text">`;
if (q.type === 'fill_blank') {
// Replace ______ with input
const parts = q.question.split(/_{3,}/);
parts.forEach((part, pi) => {
html += escapeHtml(part);
if (pi < parts.length - 1) {
const val = revealed ? (Array.isArray(q.answer) ? q.answer[0] : q.answer) : (state?.value || '');
const cls = revealed ? (correct ? 'blank-input correct' : 'blank-input wrong') : 'blank-input';
html += `<input class="${cls}" id="input-${q.id}" value="${escapeHtml(val)}" ${revealed ? 'disabled' : ''} placeholder="填空" onchange="saveFill('${q.id}')" onkeydown="if(event.key==='Enter')checkFill('${q.id}')">`;
}
});
} else {
html += escapeHtml(q.question);
}
html += `</div>`;
// Options for single_choice
if (q.type === 'single_choice' && q.options) {
html += `<div class="options">`;
for (const [letter, text] of Object.entries(q.options)) {
let optClass = '';
if (revealed) {
if (letter === q.answer) optClass = 'correct';
else if (state?.value === letter) optClass = 'wrong';
} else if (state?.value === letter) {
optClass = 'selected';
}
html += `<div class="option ${optClass}" onclick="selectOption('${q.id}','${letter}')">
<div class="radio"></div>
<span><strong>${letter}.</strong> ${escapeHtml(text)}</span>
</div>`;
}
html += `</div>`;
}
// Actions
html += `<div class="actions">`;
if (!revealed) {
if (q.type === 'fill_blank') {
html += `<button class="btn btn-primary btn-sm" onclick="checkFill('${q.id}')">✓ 检查</button>`;
}
html += `<button class="btn btn-outline btn-sm" onclick="revealAnswer('${q.id}')">👁 显示答案</button>`;
}
html += `</div>`;
// Answer box
const answerText = Array.isArray(q.answer) ? q.answer.join(' / ') : String(q.answer);
html += `<div class="answer-box ${revealed ? 'show' : ''}" id="ans-${q.id}">
<div class="label">正确答案</div>
<div class="answer-text">${escapeHtml(answerText)}</div>
<div class="explanation">💡 ${escapeHtml(q.explanation || '')}</div>
</div>`;
// Tags
if (q.tags?.length) {
html += `<div class="q-tags">${q.tags.map(t => `<span class="q-tag">${escapeHtml(t)}</span>`).join('')}</div>`;
}
html += `</div>`;
return html;
}
// ─── Interactions ───
function selectOption(qId, letter) {
const q = currentQuestions.find(x => x.id === qId);
if (!q || answers[qId]?.revealed) return;
answers[qId] = { value: letter, correct: false, revealed: false };
renderQuiz();
}
function saveFill(qId) {
const input = document.getElementById(`input-${qId}`);
if (!input) return;
const val = input.value.trim();
if (val) {
answers[qId] = { value: val, correct: false, revealed: false };
}
}
function checkFill(qId) {
const input = document.getElementById(`input-${qId}`);
if (!input) return;
const val = input.value.trim();
if (!val) return;
const q = currentQuestions.find(x => x.id === qId);
if (!q) return;
const correct = matchFillAnswer(val, q.answer, q.answer_rule || 'any');
answers[qId] = { value: val, correct, revealed: true };
renderQuiz();
}
function matchFillAnswer(userVal, answers, rule) {
const norm = s => s.toLowerCase().replace(/[\s\-_\/\\,,。;;::'""''()()\[\]【]{}]+/g, '').trim();
const nv = norm(userVal);
if (!Array.isArray(answers)) answers = [answers];
const normalized = answers.map(norm);
if (rule === 'all') return normalized.every(a => nv.includes(a));
if (rule === 'ordered') {
let lastIdx = -1;
for (const a of normalized) {
const idx = nv.indexOf(a, lastIdx + 1);
if (idx === -1) return false;
lastIdx = idx;
}
return true;
}
// any
return normalized.some(a => nv === a || nv.includes(a));
}
function revealAnswer(qId) {
const q = currentQuestions.find(x => x.id === qId);
if (!q) return;
if (!answers[qId]) answers[qId] = { value: '', correct: false };
if (q.type === 'single_choice') {
answers[qId].correct = answers[qId].value === q.answer;
}
answers[qId].revealed = true;
renderQuiz();
}
function showAllAnswers() {
currentQuestions.forEach(q => {
if (!answers[q.id]?.revealed) {
if (!answers[q.id]) answers[q.id] = { value: '', correct: false };
if (q.type === 'single_choice') {
answers[q.id].correct = answers[q.id].value === q.answer;
}
answers[q.id].revealed = true;
}
});
renderQuiz();
}
function submitAll() {
currentQuestions.forEach(q => {
if (!answers[q.id]) answers[q.id] = { value: '', correct: false };
if (!answers[q.id].revealed) {
if (q.type === 'single_choice') {
answers[q.id].correct = answers[q.id].value === q.answer;
} else if (q.type === 'fill_blank') {
answers[q.id].correct = matchFillAnswer(answers[q.id].value, q.answer, q.answer_rule || 'any');
}
answers[q.id].revealed = true;
}
});
renderQuiz();
}
function resetAll() {
answers = {};
renderQuiz();
}
// ─── Import ───
function openImportModal() { document.getElementById('importModal').classList.add('show'); }
function closeImportModal() { document.getElementById('importModal').classList.remove('show'); }
function doImport() {
const text = document.getElementById('importText').value.trim();
if (!text) return;
try {
const data = JSON.parse(text);
const questions = data.questions || (Array.isArray(data) ? data : [data]);
customQuestions.push(...questions);
closeImportModal();
renderTopicList();
if (currentTopic === '__custom__') selectTopic('__custom__');
document.getElementById('importText').value = '';
} catch (e) {
alert('JSON 解析失败: ' + e.message);
}
}
function handleFileImport(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
document.getElementById('importText').value = e.target.result;
};
reader.readAsText(file);
}
// ─── Util ───
function escapeHtml(s) {
if (!s) return '';
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
// ─── Start ───
init();
</script>
</body>
</html>