手机沙箱实验台
by EpicCoder881042 lines58.6 KB
<!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>手机沙箱实验台</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0a0e14; --panel: #161b22; --border: #30363d;
--text: #e6edf3; --text-dim: #8b949e;
--accent: #58a6ff; --green: #3fb950; --red: #f85149; --purple: #bc8cff;
--radius: 14px; --radius-sm: 8px;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}
html, body {
height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
background: radial-gradient(circle at 50% 30%, #1a2332 0%, #0a0e14 70%);
color: var(--text); overflow: hidden;
}
.workbench { display: flex; height: 100vh; padding: 14px; gap: 16px; align-items: center; justify-content: center; }
.phone {
position: relative; flex-shrink: 0; width: 380px; height: 780px;
background: linear-gradient(145deg, #2e2e32 0%, #1c1c1e 50%, #2e2e32 100%);
border-radius: 56px; padding: 11px;
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.08), inset 0 0 0 3px rgba(0,0,0,0.5),
0 0 0 1px rgba(0,0,0,0.8), 0 30px 70px rgba(0,0,0,0.75), 0 0 120px rgba(88,166,255,0.12);
display: flex; flex-direction: column; align-self: center;
}
.phone::before { content: ""; position: absolute; right: -3px; top: 180px; width: 3px; height: 80px; background: #2a2a2e; border-radius: 0 3px 3px 0; }
.phone::after { content: ""; position: absolute; left: -3px; top: 140px; width: 3px; height: 60px; background: #2a2a2e; border-radius: 3px 0 0 3px; }
.phone-screen { flex: 1; position: relative; background: #000; border-radius: 46px; overflow: hidden; display: flex; flex-direction: column; }
.dynamic-island { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); width: 105px; height: 30px; background: #000; border-radius: 20px; z-index: 100; }
.dynamic-island::before { content: ""; position: absolute; right: 14px; top: 50%; transform: translateY(-50%); width: 10px; height: 10px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #1a3a5a 0%, #050810 60%); box-shadow: inset 0 0 2px rgba(80,150,220,0.8); }
.status-bar { height: 56px; display: flex; align-items: center; justify-content: space-between; padding: 18px 28px 0; color: #fff; font-size: 14px; font-weight: 600; position: relative; z-index: 50; flex-shrink: 0; pointer-events: none; letter-spacing: 0.2px; }
.status-time { font-variant-numeric: tabular-nums; }
.status-icons { display: flex; align-items: center; gap: 6px; }
.signal-icon { display: flex; align-items: flex-end; gap: 2px; height: 11px; }
.signal-icon span { width: 3px; background: #fff; border-radius: 1px; }
.signal-icon span:nth-child(1) { height: 4px; }
.signal-icon span:nth-child(2) { height: 6px; }
.signal-icon span:nth-child(3) { height: 8px; }
.signal-icon span:nth-child(4) { height: 10px; }
.battery-icon { width: 25px; height: 12px; border: 1.5px solid rgba(255,255,255,0.9); border-radius: 3.5px; position: relative; padding: 1.5px; }
.battery-icon::after { content: ""; position: absolute; right: -3.5px; top: 50%; transform: translateY(-50%); width: 2px; height: 5px; background: rgba(255,255,255,0.9); border-radius: 0 2px 2px 0; }
.battery-fill { height: 100%; background: #fff; border-radius: 1.5px; width: 78%; }
.screen-body { flex: 1; position: relative; overflow: hidden; }
.home-screen { position: absolute; inset: 0; z-index: 1; transition: opacity 0.45s cubic-bezier(0.4,0,0.2,1), transform 0.45s cubic-bezier(0.4,0,0.2,1); }
.home-screen.hidden { opacity: 0; transform: scale(0.88); pointer-events: none; }
.wallpaper {
position: absolute; inset: 0;
background: radial-gradient(ellipse at 30% 20%, rgba(88,100,200,0.75) 0%, transparent 55%),
radial-gradient(ellipse at 75% 70%, rgba(180,80,200,0.6) 0%, transparent 55%),
radial-gradient(ellipse at 85% 25%, rgba(60,120,200,0.55) 0%, transparent 45%),
radial-gradient(ellipse at 15% 85%, rgba(120,60,180,0.55) 0%, transparent 45%),
linear-gradient(180deg, #0a0e2a 0%, #1a1a3e 50%, #0d0518 100%);
}
.wallpaper::after { content: ""; position: absolute; inset: 0; background: radial-gradient(circle at 50% 40%, transparent 30%, rgba(0,0,0,0.35) 100%); }
.home-content { position: relative; z-index: 2; height: 100%; display: flex; flex-direction: column; padding: 20px 26px 30px; overflow-y: auto; }
.home-content::-webkit-scrollbar { display: none; }
.home-greeting { padding: 14px 6px 26px; color: #fff; }
.greeting-time { font-size: 58px; font-weight: 200; letter-spacing: -2px; line-height: 1; font-variant-numeric: tabular-nums; text-shadow: 0 2px 20px rgba(0,0,0,0.4); }
.greeting-date { font-size: 15px; font-weight: 500; opacity: 0.85; margin-top: 6px; letter-spacing: 0.3px; }
.app-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 22px 16px; padding: 0 4px; }
.app { display: flex; flex-direction: column; align-items: center; gap: 8px; background: transparent; border: none; cursor: pointer; padding: 0; font-family: inherit; color: #fff; }
.app-icon { width: 62px; height: 62px; border-radius: 16px; display: flex; align-items: center; justify-content: center; font-size: 28px; position: relative; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.3), inset 0 0 0 1px rgba(255,255,255,0.15); transition: transform 0.15s ease, filter 0.15s ease; }
.app-icon::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 50%; background: linear-gradient(180deg, rgba(255,255,255,0.22), transparent); pointer-events: none; }
.app:active .app-icon { transform: scale(0.88); filter: brightness(0.75); }
.app-name { font-size: 11.5px; font-weight: 500; letter-spacing: 0.2px; text-shadow: 0 1px 3px rgba(0,0,0,0.5); }
.icon-red { background: linear-gradient(160deg, #ff5b5b 0%, #d92727 100%); }
.icon-blue { background: linear-gradient(160deg, #5ba4ff 0%, #1c6cd9 100%); }
.icon-purple { background: linear-gradient(160deg, #b98cff 0%, #7c3aed 100%); }
.icon-green { background: linear-gradient(160deg, #4ee08b 0%, #17a05a 100%); }
.icon-orange { background: linear-gradient(160deg, #ffb15b 0%, #d9771c 100%); }
.dock { margin-top: auto; display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; padding: 12px 16px; background: rgba(255,255,255,0.14); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); border-radius: 26px; border: 1px solid rgba(255,255,255,0.12); }
.dock .app-icon { width: 54px; height: 54px; font-size: 24px; margin: 0 auto; }
.dock .app-name { display: none; }
.page-dots { display: flex; justify-content: center; gap: 6px; padding: 14px 0 6px; }
.page-dots span { width: 6px; height: 6px; border-radius: 50%; background: rgba(255,255,255,0.35); }
.page-dots span.active { background: #fff; }
.app-view { position: absolute; inset: 0; z-index: 5; display: flex; flex-direction: column; background: #0a0e1a; opacity: 0; transform: scale(1.08); pointer-events: none; transition: opacity 0.4s cubic-bezier(0.4,0,0.2,1), transform 0.4s cubic-bezier(0.4,0,0.2,1); }
.app-view.active { opacity: 1; transform: scale(1); pointer-events: auto; }
.app-view-header { height: 52px; display: flex; align-items: center; justify-content: space-between; padding: 8px 12px 0; background: rgba(22,27,34,0.95); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); color: #fff; font-size: 14px; font-weight: 600; flex-shrink: 0; border-bottom: 1px solid rgba(255,255,255,0.06); }
.back-btn { display: flex; align-items: center; gap: 3px; background: transparent; border: none; color: var(--accent); font-size: 14px; font-weight: 600; cursor: pointer; padding: 6px 10px; border-radius: 8px; font-family: inherit; transition: background 0.15s; }
.back-btn:active { background: rgba(88,166,255,0.15); }
.back-btn svg { width: 12px; height: 12px; }
.app-title { flex: 1; text-align: center; font-size: 14px; font-weight: 600; letter-spacing: 0.2px; color: #fff; }
.app-view-header .spacer { width: 64px; }
.app-view-body { flex: 1; position: relative; overflow: hidden; background: #fff; }
.app-view-body iframe { width: 100%; height: 100%; border: 0; display: block; background: #fff; }
.home-indicator { position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); width: 130px; height: 5px; border-radius: 3px; background: rgba(255,255,255,0.9); z-index: 200; pointer-events: none; opacity: 0.9; }
.sidebar { flex: 1; max-width: 440px; height: 100%; display: flex; flex-direction: column; gap: 10px; min-width: 0; min-height: 0; }
.card { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column; min-height: 0; }
.card-head { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; border-bottom: 1px solid var(--border); font-size: 12px; font-weight: 700; color: var(--text-dim); letter-spacing: 0.5px; text-transform: uppercase; flex-shrink: 0; }
.card-head .badge { font-size: 10px; font-weight: 600; padding: 2px 8px; border-radius: 999px; background: rgba(88,166,255,0.15); color: var(--accent); text-transform: none; letter-spacing: 0; }
.status-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: #1c2128; color: var(--text-dim); border: 1px solid var(--border); text-transform: none; letter-spacing: 0; }
.status-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--green); }
.controls { padding: 10px; display: flex; flex-wrap: wrap; gap: 6px; }
.btn { display: inline-flex; align-items: center; gap: 5px; padding: 7px 12px; font-size: 12.5px; font-weight: 600; border: 1px solid var(--border); border-radius: var(--radius-sm); background: #1c2128; color: var(--text); cursor: pointer; transition: all 0.15s; white-space: nowrap; font-family: inherit; }
.btn:hover { transform: translateY(-1px); background: #22272e; border-color: #484f58; }
.btn:active { transform: translateY(0); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.green { background: var(--green); border-color: var(--green); color: #fff; }
.btn.red { background: var(--red); border-color: var(--red); color: #fff; }
.btn.purple { background: var(--purple); border-color: var(--purple); color: #1a1a1a; }
.btn.orange { background: #d9771c; border-color: #d9771c; color: #fff; }
.btn.ghost { background: transparent; }
.logs-body { flex: 1; overflow-y: auto; padding: 8px 10px; font-family: var(--mono); font-size: 11.5px; line-height: 1.55; min-height: 100px; }
.logs-body::-webkit-scrollbar { width: 6px; }
.logs-body::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
.log-item { display: flex; gap: 6px; padding: 4px 6px; border-radius: 5px; margin-bottom: 2px; animation: slideIn 0.18s ease; }
@keyframes slideIn { from { opacity: 0; transform: translateX(-5px); } to { opacity: 1; transform: translateX(0); } }
.log-item:hover { background: #1c2128; }
.log-time { color: #484f58; flex-shrink: 0; font-size: 10.5px; padding-top: 1px; }
.log-tag { flex-shrink: 0; font-size: 9.5px; font-weight: 700; padding: 2px 5px; border-radius: 4px; height: fit-content; margin-top: 1px; }
.log-tag.info { background: rgba(88,166,255,0.15); color: var(--accent); }
.log-tag.ok { background: rgba(63,185,80,0.15); color: var(--green); }
.log-tag.warn { background: rgba(210,153,34,0.15); color: #d29922; }
.log-tag.error { background: rgba(248,81,73,0.15); color: var(--red); }
.log-msg { color: var(--text); word-break: break-word; }
.editor-card { flex-shrink: 0; }
.editor-body { padding: 8px; display: flex; flex-direction: column; gap: 6px; }
.editor-body textarea { width: 100%; height: 84px; resize: none; background: #0a0e14; color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 10px; font-family: var(--mono); font-size: 11.5px; line-height: 1.55; outline: none; transition: border-color 0.15s; }
.editor-body textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(88,166,255,0.12); }
.editor-actions { display: flex; gap: 6px; flex-wrap: wrap; }
@media (max-width: 960px) {
.workbench { flex-direction: column; padding: 10px; gap: 10px; overflow-y: auto; height: auto; min-height: 100vh; align-items: stretch; }
.phone { width: 100%; max-width: 360px; height: auto; aspect-ratio: 9 / 19.5; max-height: 70vh; margin: 0 auto; padding: 9px; border-radius: 48px; }
.phone-screen { border-radius: 40px; }
.dynamic-island { width: 90px; height: 26px; }
.greeting-time { font-size: 48px; }
.app-icon { width: 54px; height: 54px; font-size: 24px; border-radius: 14px; }
.app-grid { gap: 18px 12px; }
.sidebar { max-width: 100%; height: auto; }
.logs-body { max-height: 180px; }
}
</style>
</head>
<body>
<div class="workbench">
<div class="phone">
<div class="phone-screen">
<div class="dynamic-island"></div>
<div class="status-bar">
<span class="status-time" id="statusTime">9:41</span>
<span class="status-icons">
<div class="signal-icon"><span></span><span></span><span></span><span></span></div>
<svg width="16" height="12" viewBox="0 0 16 12" fill="none">
<path d="M1 4 C 4 1.5, 12 1.5, 15 4" stroke="#fff" stroke-width="1.6" stroke-linecap="round" fill="none"/>
<path d="M3.5 6.5 C 5.5 5, 10.5 5, 12.5 6.5" stroke="#fff" stroke-width="1.6" stroke-linecap="round" fill="none"/>
<circle cx="8" cy="9.6" r="1.3" fill="#fff"/>
</svg>
<div class="battery-icon"><div class="battery-fill"></div></div>
</span>
</div>
<div class="screen-body">
<div class="home-screen" id="homeScreen">
<div class="wallpaper"></div>
<div class="home-content">
<div class="home-greeting">
<div class="greeting-time" id="greetingTime">9:41</div>
<div class="greeting-date" id="greetingDate">10月2日 星期五</div>
</div>
<div class="app-grid">
<button class="app" data-app="test1" data-title="安全中心">
<div class="app-icon icon-red">🛡</div>
<div class="app-name">安全中心</div>
</button>
<button class="app" data-app="test5" data-title="文件管理">
<div class="app-icon icon-orange">📁</div>
<div class="app-name">文件管理</div>
</button>
<button class="app" data-app="test2" data-title="浏览器">
<div class="app-icon icon-blue">◐</div>
<div class="app-name">浏览器</div>
</button>
<button class="app" data-app="test3" data-title="影子空间">
<div class="app-icon icon-purple">◈</div>
<div class="app-name">影子空间</div>
</button>
<button class="app" data-app="test4" data-title="文本阅读器">
<div class="app-icon icon-green">¶</div>
<div class="app-name">文本阅读器</div>
</button>
</div>
<div class="page-dots">
<span class="active"></span>
<span></span>
</div>
<div class="dock">
<button class="app" data-app="dock" data-title="电话">
<div class="app-icon" style="background: linear-gradient(160deg, #5be05b 0%, #17a05a 100%);">☎</div>
</button>
<button class="app" data-app="dock" data-title="信息">
<div class="app-icon" style="background: linear-gradient(160deg, #5be0d0 0%, #17a0a0 100%);">✉</div>
</button>
<button class="app" data-app="dock" data-title="相机">
<div class="app-icon" style="background: linear-gradient(160deg, #666 0%, #222 100%);">◉</div>
</button>
<button class="app" data-app="dock" data-title="音乐">
<div class="app-icon" style="background: linear-gradient(160deg, #ff5b8c 0%, #d92760 100%);">♪</div>
</button>
</div>
</div>
</div>
<div class="app-view" id="appView">
<div class="app-view-header">
<button class="back-btn" id="backBtn">
<svg viewBox="0 0 12 20" fill="none">
<path d="M10 2 L2 10 L10 18" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
返回
</button>
<span class="app-title" id="appTitle">应用</span>
<span class="spacer"></span>
</div>
<div class="app-view-body">
<iframe id="sandboxFrame" sandbox="allow-scripts allow-same-origin allow-downloads allow-forms"></iframe>
</div>
</div>
</div>
<div class="home-indicator"></div>
</div>
</div>
<div class="sidebar">
<div class="card">
<div class="card-head">
<span>控制台</span>
<span class="status-badge"><span class="dot"></span><span id="statusBadge">沙箱就绪</span></span>
</div>
<div class="controls">
<button class="btn primary" id="btnReload">⟳ 重载</button>
<button class="btn red" id="btnTest1">安全中心</button>
<button class="btn orange" id="btnTest5">文件管理</button>
<button class="btn red" id="btnTest2">浏览器</button>
<button class="btn purple" id="btnTest3">影子空间</button>
<button class="btn green" id="btnTest4">文本阅读器</button>
<button class="btn ghost" id="btnClearLog">清空日志</button>
</div>
</div>
<div class="card" style="flex:1; min-height:0;">
<div class="card-head">
<span>运行日志</span>
<span class="badge" id="logCount">0</span>
</div>
<div class="logs-body" id="console"></div>
</div>
<div class="card editor-card">
<div class="card-head"><span>代码注入</span></div>
<div class="editor-body">
<textarea id="codeInput" placeholder="在此粘贴 HTML / JS..."></textarea>
<div class="editor-actions">
<button class="btn green" id="btnInject">▶ 注入运行</button>
<button class="btn ghost" id="btnWrapDoc">包裹文档</button>
</div>
</div>
</div>
</div>
</div>
<script>
function updateClock() {
var now = new Date();
var h = now.getHours();
var m = String(now.getMinutes()).padStart(2, '0');
var timeStr = h + ':' + m;
document.getElementById('statusTime').textContent = timeStr;
document.getElementById('greetingTime').textContent = timeStr;
var month = now.getMonth() + 1;
var day = now.getDate();
var weekdays = ['日','一','二','三','四','五','六'];
document.getElementById('greetingDate').textContent = month + '月' + day + '日 星期' + weekdays[now.getDay()];
}
updateClock();
setInterval(updateClock, 30000);
var $ = function(id) { return document.getElementById(id); };
var consoleEl = $('console');
var logCount = 0;
function log(msg, type) {
type = type || 'info';
var t = new Date().toTimeString().slice(0, 8) + '.' + String(new Date().getMilliseconds()).padStart(3, '0');
var tagMap = { info: '信息', ok: '成功', warn: '警告', error: '错误' };
var item = document.createElement('div');
item.className = 'log-item';
item.innerHTML = '<span class="log-time">' + t + '</span>' +
'<span class="log-tag ' + type + '">' + (tagMap[type] || '信息') + '</span>' +
'<span class="log-msg">' + escapeHtml(msg) + '</span>';
consoleEl.appendChild(item);
consoleEl.scrollTop = consoleEl.scrollHeight;
logCount++;
$('logCount').textContent = logCount;
while (consoleEl.children.length > 200) consoleEl.removeChild(consoleEl.firstChild);
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, function(c) {
return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];
});
}
var frame = $('sandboxFrame');
var statusBadge = $('statusBadge');
var homeScreen = $('homeScreen');
var appView = $('appView');
var appTitle = $('appTitle');
function resetSandbox() {
frame.srcdoc = '<!DOCTYPE html><html><head><meta charset="UTF-8"><style>html,body{margin:0;height:100%;}</style></head><body></body></html>';
statusBadge.textContent = '沙箱已重载';
log('沙箱已重载', 'ok');
}
function injectToSandbox(code) {
var doc = frame.contentDocument;
if (!doc) { log('无法访问沙箱', 'error'); return; }
doc.open(); doc.write(code); doc.close();
statusBadge.textContent = '已注入 · ' + new Date().toTimeString().slice(0, 8);
}
function openApp(appId, title) {
appTitle.textContent = title;
homeScreen.classList.add('hidden');
appView.classList.add('active');
setTimeout(function() { runTest(appId); }, 200);
log('打开应用:' + title, 'info');
}
function closeApp() {
appView.classList.remove('active');
homeScreen.classList.remove('hidden');
log('返回主屏幕', 'info');
}
// ================== 安全中心 v3(带文件防护) ==================
function securityCenterApp() {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: #0a0e1a; color: #e6edf3; overflow: hidden; }
.app { display: flex; flex-direction: column; height: 100%; padding: 12px; gap: 9px; }
.app-bar { display: flex; align-items: center; justify-content: space-between; padding: 2px; flex-shrink: 0; }
.app-bar .title { font-size: 17px; font-weight: 700; }
.app-bar .settings { font-size: 18px; opacity: 0.6; }
/* 盾牌状态 */
.shield-card { position: relative; border-radius: 16px; padding: 14px 14px; background: linear-gradient(135deg, rgba(63,185,80,0.18) 0%, rgba(88,166,255,0.08) 100%); border: 1px solid rgba(63,185,80,0.35); overflow: hidden; flex-shrink: 0; transition: all 0.3s; }
.shield-card.danger { background: linear-gradient(135deg, rgba(248,81,73,0.22) 0%, rgba(210,153,34,0.1) 100%); border-color: rgba(248,81,73,0.5); }
.shield-card::before { content: ""; position: absolute; top: -40px; right: -40px; width: 130px; height: 130px; border-radius: 50%; background: radial-gradient(circle, rgba(63,185,80,0.25) 0%, transparent 70%); }
.shield-card.danger::before { background: radial-gradient(circle, rgba(248,81,73,0.35) 0%, transparent 70%); animation: dangerPulse 1s infinite; }
@keyframes dangerPulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
.shield-head { display: flex; align-items: center; gap: 10px; position: relative; }
.shield-icon { width: 42px; height: 42px; border-radius: 12px; background: linear-gradient(135deg, #3fb950 0%, #2d7a3a 100%); display: flex; align-items: center; justify-content: center; font-size: 22px; box-shadow: 0 4px 14px rgba(63,185,80,0.4); flex-shrink: 0; transition: all 0.3s; }
.shield-card.danger .shield-icon { background: linear-gradient(135deg, #f85149 0%, #a01c1c 100%); box-shadow: 0 4px 14px rgba(248,81,73,0.5); }
.shield-info .t1 { font-size: 14.5px; font-weight: 700; color: #3fb950; transition: color 0.3s; }
.shield-card.danger .shield-info .t1 { color: #f85149; }
.shield-info .t2 { font-size: 11px; color: #8b949e; margin-top: 2px; }
.shield-status { margin-left: auto; display: flex; align-items: center; gap: 5px; padding: 4px 10px; border-radius: 999px; background: rgba(63,185,80,0.15); font-size: 10.5px; font-weight: 600; color: #3fb950; transition: all 0.3s; }
.shield-card.danger .shield-status { background: rgba(248,81,73,0.2); color: #f85149; }
.shield-status .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; box-shadow: 0 0 8px currentColor; animation: pulse 1.5s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.35; } }
/* 统计 */
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 7px; flex-shrink: 0; }
.stat { padding: 10px 5px 9px; background: rgba(22,27,34,0.85); border: 1px solid #30363d; border-radius: 12px; text-align: center; position: relative; overflow: hidden; }
.stat::before { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; background: currentColor; opacity: 0.6; }
.stat.green { color: #3fb950; }
.stat.blue { color: #58a6ff; }
.stat.red { color: #f85149; }
.stat .num { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; color: currentColor; }
.stat .label { font-size: 10px; color: #8b949e; margin-top: 5px; font-weight: 500; }
/* 文件防护区 */
.file-guard { background: rgba(22,27,34,0.85); border: 1px solid #30363d; border-radius: 12px; overflow: hidden; flex-shrink: 0; }
.fg-head { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid #30363d; }
.fg-head .fg-title { font-size: 11.5px; font-weight: 700; color: #8b949e; letter-spacing: 0.5px; display: flex; align-items: center; gap: 6px; }
.fg-head .fg-title::before { content: "📁"; font-size: 12px; }
.fg-head .fg-badge { font-size: 9.5px; font-weight: 700; padding: 2px 7px; border-radius: 999px; background: rgba(63,185,80,0.15); color: #3fb950; }
.fg-head .fg-badge.warn { background: rgba(210,153,34,0.15); color: #d29922; }
.fg-list { padding: 5px 8px 7px; display: flex; flex-direction: column; gap: 3px; }
.fg-file { display: flex; align-items: center; gap: 8px; padding: 5px 6px; border-radius: 6px; font-size: 11px; }
.fg-file .fg-name { flex: 1; color: #c9d1d9; font-family: ui-monospace, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fg-file .fg-status { font-size: 9.5px; font-weight: 700; padding: 1px 6px; border-radius: 4px; flex-shrink: 0; }
.fg-file .fg-status.ok { background: rgba(63,185,80,0.15); color: #3fb950; }
.fg-file .fg-status.corrupted { background: rgba(248,81,73,0.2); color: #f85149; animation: pulse 1s infinite; }
/* 扫描按钮 */
.scan-btn { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 10px; border: none; border-radius: 11px; background: linear-gradient(135deg, #58a6ff 0%, #3b82d9 100%); color: #fff; font-size: 13px; font-weight: 700; cursor: pointer; font-family: inherit; flex-shrink: 0; box-shadow: 0 4px 14px rgba(88,166,255,0.3); transition: transform 0.15s, filter 0.15s; }
.scan-btn:active { transform: scale(0.97); filter: brightness(0.85); }
.scan-btn.scanning { background: linear-gradient(135deg, #d29922 0%, #a1741a 100%); box-shadow: 0 4px 14px rgba(210,153,34,0.3); }
/* 日志 */
.logs-section { flex: 1; display: flex; flex-direction: column; background: rgba(13,17,23,0.9); border: 1px solid #30363d; border-radius: 12px; overflow: hidden; min-height: 0; }
.logs-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; border-bottom: 1px solid #30363d; background: rgba(22,27,34,0.95); flex-shrink: 0; }
.logs-header .h-title { font-size: 11.5px; font-weight: 700; color: #8b949e; letter-spacing: 0.5px; display: flex; align-items: center; gap: 6px; }
.logs-header .h-title::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: #3fb950; box-shadow: 0 0 6px #3fb950; animation: pulse 1.5s infinite; }
.logs-header .count-pill { font-size: 9.5px; font-weight: 700; padding: 2px 7px; border-radius: 999px; background: rgba(88,166,255,0.15); color: #58a6ff; font-variant-numeric: tabular-nums; }
.logs-list { flex: 1; overflow-y: auto; padding: 5px; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10.5px; line-height: 1.55; min-height: 0; }
.logs-list::-webkit-scrollbar { width: 4px; }
.logs-list::-webkit-scrollbar-thumb { background: #30363d; border-radius: 2px; }
.log-row { display: flex; gap: 5px; padding: 4px 6px; border-radius: 5px; margin-bottom: 1px; animation: logIn 0.25s ease; }
@keyframes logIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
.log-time { color: #484f58; flex-shrink: 0; font-size: 10px; padding-top: 1px; }
.log-tag { flex-shrink: 0; font-size: 8.5px; font-weight: 700; padding: 2px 4px; border-radius: 3px; height: fit-content; margin-top: 1px; }
.log-tag.info { background: rgba(88,166,255,0.15); color: #58a6ff; }
.log-tag.ok { background: rgba(63,185,80,0.15); color: #3fb950; }
.log-tag.warn { background: rgba(210,153,34,0.15); color: #d29922; }
.log-tag.error { background: rgba(248,81,73,0.15); color: #f85149; }
.log-msg { color: #c9d1d9; word-break: break-word; }
/* 紧急弹窗 */
.alert-modal { position: fixed; inset: 0; background: rgba(0,0,0,0.85); display: none; align-items: center; justify-content: center; z-index: 999; padding: 20px; }
.alert-modal.active { display: flex; animation: fadeIn 0.3s; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.alert-box { background: linear-gradient(160deg, #2a1215 0%, #1a0a0d 100%); border: 1.5px solid #f85149; border-radius: 18px; padding: 22px 20px; max-width: 320px; width: 100%; text-align: center; box-shadow: 0 0 60px rgba(248,81,73,0.4); }
.alert-box .a-icon { font-size: 48px; margin-bottom: 10px; animation: shake 0.5s infinite; }
@keyframes shake { 0%,100% { transform: rotate(0deg); } 25% { transform: rotate(-8deg); } 75% { transform: rotate(8deg); } }
.alert-box .a-title { font-size: 17px; font-weight: 800; color: #f85149; margin-bottom: 8px; letter-spacing: 0.5px; }
.alert-box .a-text { font-size: 12px; color: #c9d1d9; line-height: 1.7; margin-bottom: 16px; }
.alert-box .a-text b { color: #f85149; }
.alert-box .a-btn { padding: 9px 22px; border: none; border-radius: 9px; background: #f85149; color: #fff; font-size: 13px; font-weight: 700; cursor: pointer; font-family: inherit; }
.alert-box .a-btn:active { filter: brightness(0.85); }
</style>
</head>
<body>
<div class="app">
<div class="app-bar"><span class="title">安全中心</span><span class="settings">⚙</span></div>
<div class="shield-card" id="shieldCard">
<div class="shield-head">
<div class="shield-icon">🛡</div>
<div class="shield-info">
<div class="t1" id="shieldTitle">实时防护已开启</div>
<div class="t2" id="shieldSub">监控 DOM 注入 · 文件完整性</div>
</div>
<div class="shield-status" id="shieldStatus"><span class="dot"></span><span id="shieldStatusText">在线</span></div>
</div>
</div>
<div class="stats">
<div class="stat green"><div class="num" id="statBlocked">0</div><div class="label">已拦截</div></div>
<div class="stat blue"><div class="num" id="statScanned">0</div><div class="label">已扫描</div></div>
<div class="stat red"><div class="num" id="statThreats">0</div><div class="label">威胁</div></div>
</div>
<div class="file-guard">
<div class="fg-head">
<span class="fg-title">文件防护</span>
<span class="fg-badge" id="fgBadge">5 个文件受保护</span>
</div>
<div class="fg-list" id="fgList"></div>
</div>
<button class="scan-btn" id="scanBtn">⚡ 立即扫描</button>
<div class="logs-section">
<div class="logs-header"><span class="h-title">实时日志</span><span class="count-pill" id="logCount">0</span></div>
<div class="logs-list" id="logsList"></div>
</div>
</div>
<div class="alert-modal" id="alertModal">
<div class="alert-box">
<div class="a-icon">🚨</div>
<div class="a-title">检测到文件篡改</div>
<div class="a-text" id="alertText">文件 <b>readme.txt</b> 内容被恶意修改。<br>已自动阻断威胁并恢复文件。</div>
<button class="a-btn" id="alertClose">立即处理</button>
</div>
</div>
<script>
var logsList = document.getElementById("logsList");
var logCountEl = document.getElementById("logCount");
var statBlocked = document.getElementById("statBlocked");
var statScanned = document.getElementById("statScanned");
var statThreats = document.getElementById("statThreats");
var scanBtn = document.getElementById("scanBtn");
var fgList = document.getElementById("fgList");
var fgBadge = document.getElementById("fgBadge");
var shieldCard = document.getElementById("shieldCard");
var shieldTitle = document.getElementById("shieldTitle");
var shieldSub = document.getElementById("shieldSub");
var shieldStatusText = document.getElementById("shieldStatusText");
var alertModal = document.getElementById("alertModal");
var alertText = document.getElementById("alertText");
var logCount = 0, blocked = 0, scanned = 0, threats = 0;
// 受保护文件
var protectedFiles = [
{ name: "readme.txt", content: "这是你的个人文件。里面存了一些重要的笔记。请不要随便分享给他人。" },
{ name: "会议记录.txt", content: "2024年10月会议记录。讨论项目进度、下一阶段安排、预算分配。" },
{ name: "个人笔记.txt", content: "今天心情还不错。记录一下日常安排。" },
{ name: "项目方案.txt", content: "项目代号:晨星。目标:构建一个安全沙箱环境。" },
{ name: "密码备忘.txt", content: "重要密码备份:邮箱 admin@example.com。" }
];
// 乱码字符集
var symbols = "▓▒░█▄▀■□▪▫◊○●◇◆※§¶†‡¥€£¢¡¿☆★♠♣♥♦жщцчшъыьэюя";
// 判断文本是否为乱码
function isCorrupted(text) {
if (!text) return false;
var corruptCount = 0;
for (var i = 0; i < text.length; i++) {
if (symbols.indexOf(text[i]) >= 0) corruptCount++;
}
return corruptCount > text.length * 0.3;
}
function addLog(msg, type) {
var t = new Date().toTimeString().slice(0, 8);
var row = document.createElement("div");
row.className = "log-row";
var tagMap = { info: "信息", ok: "拦截", warn: "警告", error: "威胁" };
row.innerHTML = "<span class='log-time'>" + t + "</span><span class='log-tag " + (type || "info") + "'>" + (tagMap[type] || "信息") + "</span><span class='log-msg'>" + msg + "</span>";
logsList.appendChild(row);
logsList.scrollTop = logsList.scrollHeight;
logCount++;
logCountEl.textContent = logCount;
while (logsList.children.length > 100) logsList.removeChild(logsList.firstChild);
}
function bump(type) {
if (type === "blocked") { blocked++; statBlocked.textContent = blocked; }
if (type === "scanned") { scanned++; statScanned.textContent = scanned; }
if (type === "threats") { threats++; statThreats.textContent = threats; }
}
// 渲染文件防护列表
function renderFiles() {
fgList.innerHTML = "";
protectedFiles.forEach(function (f) {
var row = document.createElement("div");
row.className = "fg-file";
row.innerHTML = "<span class='fg-name'>" + f.name + "</span><span class='fg-status ok' data-status>✓ 正常</span>";
fgList.appendChild(row);
});
}
// 标记某个文件被感染
function markFileCorrupted(fileName) {
var rows = fgList.querySelectorAll(".fg-file");
rows.forEach(function (row) {
if (row.querySelector(".fg-name").textContent === fileName) {
var st = row.querySelector("[data-status]");
st.className = "fg-status corrupted";
st.textContent = "⚠ 已篡改";
}
});
}
// 恢复所有文件
function restoreFiles() {
var rows = fgList.querySelectorAll(".fg-file");
rows.forEach(function (row) {
var st = row.querySelector("[data-status]");
st.className = "fg-status ok";
st.textContent = "✓ 正常";
});
fgBadge.className = "fg-badge";
fgBadge.textContent = protectedFiles.length + " 个文件受保护";
}
// ===== 核心:监控文本变化,发现乱码就报警 =====
var originalContents = {};
protectedFiles.forEach(function (f) { originalContents[f.name] = f.content; });
function checkFileIntegrity() {
// 在当前页面中寻找所有可能被篡改的文本节点
var walker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
null,
false
);
var node;
var foundThreat = false;
while (node = walker.nextNode()) {
var text = node.nodeValue;
if (isCorrupted(text)) {
foundThreat = true;
break;
}
}
if (foundThreat) {
triggerThreat();
}
}
var threatHandled = false;
function triggerThreat() {
if (threatHandled) return;
threatHandled = true;
bump("blocked"); bump("threats");
shieldCard.classList.add("danger");
shieldTitle.textContent = "检测到文件篡改!";
shieldSub.textContent = "已自动阻断威胁并恢复文件";
shieldStatusText.textContent = "告警";
addLog("⚠ 检测到乱码注入,文件内容被篡改", "error");
addLog("已自动阻断可疑进程", "ok");
addLog("正在恢复文件完整性...", "info");
// 模拟恢复过程
setTimeout(function () {
addLog("文件完整性恢复完成", "ok");
shieldCard.classList.remove("danger");
shieldTitle.textContent = "实时防护已开启";
shieldSub.textContent = "监控 DOM 注入 · 文件完整性";
shieldStatusText.textContent = "在线";
alertModal.classList.add("active");
threatHandled = false;
}, 1500);
}
// 用 MutationObserver 监控文本变化
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
if (m.type === "characterData" || m.type === "childList") {
var text = m.target.textContent || "";
if (isCorrupted(text)) {
triggerThreat();
}
}
});
});
function startObserver() {
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
}
// ===== DOM 注入拦截(保留原有能力) =====
var KW = ["fake-virus", "无害弹窗", "绕过演示", "overlay"];
function has(txt) {
if (!txt) return false;
var s = String(txt).toLowerCase();
return KW.some(function (k) { return s.indexOf(k.toLowerCase()) >= 0; });
}
function susp(n) {
if (!n || n.nodeType !== 1) return false;
if (n.id && has(n.id)) return true;
if (n.className && typeof n.className === "string" && has(n.className)) return true;
try { if (n.textContent && has(n.textContent)) return true; } catch (e) {}
if (n.tagName === "IFRAME") {
var sd = n.getAttribute && n.getAttribute("srcdoc");
if (sd && has(sd)) return true;
}
return false;
}
function kill(n) {
if (!n) return;
try {
if (n.parentNode) n.parentNode.removeChild(n);
bump("blocked"); bump("threats");
addLog("已阻止可疑注入:" + (n.id || n.tagName), "ok");
} catch (e) {}
}
var oa = Node.prototype.appendChild;
var oi = Node.prototype.insertBefore;
Node.prototype.appendChild = function (n) {
if (susp(n)) { bump("blocked"); addLog("拦截 appendChild 注入", "ok"); return n; }
return oa.call(this, n);
};
Node.prototype.insertBefore = function (n, r) {
if (susp(n)) { bump("blocked"); addLog("拦截 insertBefore 注入", "ok"); return n; }
return oi.call(this, n, r);
};
// ===== 初始化 =====
addLog("安全中心 v3 启动", "info");
addLog("实时防护引擎加载完成", "ok");
addLog("文件完整性监控已激活", "ok");
addLog("正在监控 DOM 注入与文件篡改...", "info");
renderFiles();
startObserver();
// ===== 手动扫描 =====
scanBtn.addEventListener("click", function () {
if (scanBtn.classList.contains("scanning")) return;
scanBtn.classList.add("scanning");
scanBtn.textContent = "⏳ 扫描中...";
addLog("开始手动扫描", "info");
var step = 0, total = 5;
var timer = setInterval(function () {
step++;
bump("scanned");
if (step <= protectedFiles.length) {
addLog("检查文件:" + protectedFiles[step - 1].name, "info");
}
if (step >= total) {
clearInterval(timer);
addLog("扫描完成,未发现威胁", "ok");
scanBtn.classList.remove("scanning");
scanBtn.textContent = "⚡ 立即扫描";
}
}, 600);
});
// ===== 后台模拟扫描 =====
setInterval(function () {
bump("scanned");
if (Math.random() < 0.3) {
var msgs = ["监控进程行为", "检查网络连接", "文件完整性校验通过", "DOM 监控运行正常"];
addLog(msgs[Math.floor(Math.random() * msgs.length)], "ok");
}
}, 3500);
// ===== 模拟攻击:5 秒后注入乱码文本 =====
setTimeout(function () {
addLog("检测到可疑进程尝试修改文件内容", "warn");
var target = document.createElement("div");
target.style.position = "absolute";
target.style.left = "-9999px";
target.textContent = "▓▒░█▄▀■□▪▫◊○●◇◆※§¶†‡¥€£¢¡¿☆★♠♣♥♦жщцчшъыьэюя";
document.body.appendChild(target);
}, 5000);
// ===== 关闭弹窗 =====
document.getElementById("alertClose").addEventListener("click", function () {
alertModal.classList.remove("active");
restoreFiles();
});
// ===== 关闭弹窗后恢复 =====
document.getElementById("alertClose").addEventListener("click", function () {
addLog("用户已确认,威胁处理完毕", "ok");
});
<\/script>
</body>
</html>`;
}
// ================== 病毒 App ==================
function virusApp() {
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; background: #f6f8fa; color: #333; overflow: hidden; }
.app { display: flex; flex-direction: column; height: 100%; background: #fff; }
.app-bar { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: #fff; border-bottom: 1px solid #eee; flex-shrink: 0; }
.app-bar .title { font-size: 16px; font-weight: 700; }
.app-bar .meta { font-size: 11px; color: #888; }
.storage { padding: 12px 16px; background: #f8f9fb; border-bottom: 1px solid #eee; flex-shrink: 0; }
.storage-row { display: flex; justify-content: space-between; font-size: 11.5px; color: #666; margin-bottom: 6px; }
.storage-bar { height: 6px; background: #e5e7eb; border-radius: 3px; overflow: hidden; }
.storage-bar .fill { height: 100%; width: 24%; background: linear-gradient(90deg, #5ba4ff, #7c3aed); border-radius: 3px; }
.list { flex: 1; overflow-y: auto; padding: 4px 0; }
.item { display: flex; align-items: center; gap: 12px; padding: 12px 16px; cursor: pointer; transition: background 0.15s; border-bottom: 1px solid #f0f0f0; }
.item:active { background: #f0f4ff; }
.item-icon { width: 40px; height: 40px; border-radius: 10px; background: linear-gradient(160deg, #e8f0ff 0%, #c9dfff 100%); display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.item-info { flex: 1; min-width: 0; }
.item-name { font-size: 14px; font-weight: 600; color: #222; margin-bottom: 2px; }
.item-meta { font-size: 11px; color: #999; }
.preview { font-size: 11px; color: #aaa; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.viewer { position: absolute; inset: 0; background: #fff; display: none; flex-direction: column; z-index: 10; }
.viewer.active { display: flex; }
.viewer-head { display: flex; align-items: center; gap: 10px; padding: 12px 16px; border-bottom: 1px solid #eee; background: #fff; flex-shrink: 0; }
.viewer-head .back { font-size: 14px; color: #007aff; cursor: pointer; font-weight: 600; }
.viewer-head .vname { font-size: 14px; font-weight: 600; color: #222; }
.viewer-body { flex: 1; overflow-y: auto; padding: 20px 18px; font-size: 14px; line-height: 1.9; color: #333; white-space: pre-wrap; word-break: break-word; }
.viewer-body.corrupted { font-family: ui-monospace, monospace; font-size: 13px; color: #d92727; letter-spacing: 1px; }
.alert-overlay { position: absolute; inset: 0; background: rgba(120,0,0,0.85); display: none; flex-direction: column; align-items: center; justify-content: center; z-index: 100; color: #fff; padding: 30px; text-align: center; }
.alert-overlay.active { display: flex; animation: alertIn 0.4s ease; }
@keyframes alertIn { from { opacity: 0; transform: scale(1.1); } to { opacity: 1; transform: scale(1); } }
.alert-icon { font-size: 64px; margin-bottom: 16px; }
.alert-title { font-size: 22px; font-weight: 800; margin-bottom: 10px; letter-spacing: 1px; }
.alert-text { font-size: 13px; line-height: 1.7; color: rgba(255,255,255,0.85); margin-bottom: 20px; }
.alert-btn { padding: 10px 24px; border: 1.5px solid #fff; background: transparent; color: #fff; border-radius: 8px; font-size: 13px; font-weight: 700; cursor: pointer; font-family: inherit; }
.alert-btn:active { background: rgba(255,255,255,0.15); }
</style>
</head>
<body>
<div class="app">
<div class="app-bar"><span class="title">文件管理</span><span class="meta">本地存储 · 64GB</span></div>
<div class="storage">
<div class="storage-row"><span>已用 15.2 GB</span><span>可用 48.8 GB</span></div>
<div class="storage-bar"><div class="fill"></div></div>
</div>
<div class="list" id="fileList"></div>
<div class="viewer" id="viewer">
<div class="viewer-head"><span class="back" id="backBtn2">‹ 返回</span><span class="vname" id="vname">文件名</span></div>
<div class="viewer-body" id="vbody"></div>
</div>
<div class="alert-overlay" id="alert">
<div class="alert-icon">⚠</div>
<div class="alert-title">你的文件已被感染</div>
<div class="alert-text" id="alertText">所有文本内容已被加密为乱码。<br>本演示不会对真实设备造成任何影响。</div>
<button class="alert-btn" id="closeAlert">我知道了</button>
</div>
</div>
<script>
var files = [
{ name: "readme.txt", size: "1.2 KB", content: "这是你的个人文件。\\n\\n里面存了一些重要的笔记。\\n\\n请不要随便分享给他人。" },
{ name: "会议记录.txt", size: "3.4 KB", content: "2024年10月会议记录\\n\\n时间:10月2日下午2点\\n地点:三楼会议室\\n\\n讨论内容:\\n1. 项目进度汇报\\n2. 下一阶段安排\\n3. 预算分配问题" },
{ name: "个人笔记.txt", size: "5.6 KB", content: "今天心情还不错。\\n\\n记录一下:\\n- 早上8点起床\\n- 9点到公司\\n- 中午吃了拉面\\n- 晚上准备看个电影" },
{ name: "项目方案.txt", size: "8.2 KB", content: "项目代号:晨星\\n\\n目标:构建一个安全沙箱环境\\n\\n技术路线:\\n1. 前端使用 HTML/CSS/JS\\n2. 沙箱采用 iframe 隔离\\n3. 拦截器基于 MutationObserver\\n\\n预期上线时间:2024年底" },
{ name: "密码备忘.txt", size: "0.8 KB", content: "重要密码备份:\\n\\n邮箱: admin@example.com\\n密码: MyS3cretP@ss\\n\\n银行: 6222 **** **** 1234\\n密码: 不要告诉别人" }
];
var fileList = document.getElementById("fileList");
var viewer = document.getElementById("viewer");
var vname = document.getElementById("vname");
var vbody = document.getElementById("vbody");
var alertEl = document.getElementById("alert");
var alertText = document.getElementById("alertText");
var symbols = "▓▒░█▄▀■□▪▫◊○●◇◆※§¶†‡¥€£¢¡¿?¿¡☆★♠♣♥♦жщцчшъыьэюя";
function corrupt(text) {
var out = "";
for (var i = 0; i < text.length; i++) {
var c = text[i];
if (c === "\\n" || c === " ") { out += c; }
else { out += symbols[Math.floor(Math.random() * symbols.length)]; }
}
return out;
}
function renderList() {
fileList.innerHTML = "";
files.forEach(function (f, idx) {
var item = document.createElement("div");
item.className = "item";
item.innerHTML = "<div class='item-icon'>📄</div><div class='item-info'><div class='item-name' data-name>" + f.name + "</div><div class='item-meta'>" + f.size + "</div><div class='preview' data-preview>" + f.content.slice(0, 22).replace(/\\n/g, " ") + "...</div></div>";
item.addEventListener("click", function () { openFile(idx); });
fileList.appendChild(item);
});
}
function openFile(idx) {
var f = files[idx];
vname.textContent = f.name;
vbody.textContent = f.content;
vbody.classList.remove("corrupted");
viewer.classList.add("active");
setTimeout(function () { infect(idx); }, 1500);
}
function infect(idx) {
var f = files[idx];
var corrupted = corrupt(f.content);
var steps = 20;
var current = 0;
var timer = setInterval(function () {
current++;
var cut = Math.floor(corrupted.length * (current / steps));
vbody.textContent = corrupted.slice(0, cut) + (current < steps ? f.content.slice(cut) : "");
vbody.classList.add("corrupted");
if (current >= steps) {
clearInterval(timer);
vbody.textContent = corrupted;
setTimeout(function () {
fileList.querySelectorAll("[data-name]").forEach(function (el, i) {
el.textContent = corrupt(files[i].name);
});
fileList.querySelectorAll("[data-preview]").forEach(function (el, i) {
el.textContent = corrupt(files[i].content.slice(0, 22));
});
alertText.innerHTML = "文件 <b>" + f.name + "</b> 的内容已被加密为乱码。<br>本演示不会对真实设备造成任何影响。";
alertEl.classList.add("active");
}, 400);
}
}, 40);
}
document.getElementById("backBtn2").addEventListener("click", function () {
viewer.classList.remove("active");
});
document.getElementById("closeAlert").addEventListener("click", function () {
alertEl.classList.remove("active");
});
renderList();
<\/script>
</body>
</html>`;
}
// ================== 其他测试 ==================
function interceptorCode() {
return '(function(){' +
'var NOTICE="block-notice",t=null;' +
'function notice(m){var e=document.getElementById(NOTICE);if(!e)return;e.textContent=m;e.classList.add("show");clearTimeout(t);t=setTimeout(function(){e.classList.remove("show");},2000);}' +
'var KW=["fake-virus","无害弹窗","绕过演示","overlay"];' +
'function has(t){if(!t)return false;var s=String(t).toLowerCase();return KW.some(function(k){return s.indexOf(k.toLowerCase())>=0;});}' +
'function susp(n){if(!n||n.nodeType!==1)return false;if(n.id&&has(n.id))return true;if(n.className&&typeof n.className==="string"&&has(n.className))return true;try{if(n.textContent&&has(n.textContent))return true;}catch(e){}if(n.tagName==="IFRAME"){var sd=n.getAttribute&&n.getAttribute("srcdoc");if(sd&&has(sd))return true;}if(n.querySelector&&n.querySelector("#fake-virus"))return true;return false;}' +
'function scan(r,d){if(!r||d>8)return null;if(susp(r))return r;if(r.children){for(var i=0;i<r.children.length;i++){var h=scan(r.children[i],d+1);if(h)return h;}}if(r.shadowRoot){var h2=scan(r.shadowRoot,d+1);if(h2)return h2;}return null;}' +
'function kill(n,m){if(!n)return;try{if(n.parentNode)n.parentNode.removeChild(n);}catch(e){}notice(m||"已阻止");}' +
'var oa=Node.prototype.appendChild,oi=Node.prototype.insertBefore;' +
'Node.prototype.appendChild=function(n){if(susp(n)){notice("已阻止:检测到可疑弹窗");return n;}return oa.call(this,n);};' +
'Node.prototype.insertBefore=function(n,r){if(susp(n)){notice("已阻止:检测到可疑弹窗");return n;}return oi.call(this,n,r);};' +
'new MutationObserver(function(ms){ms.forEach(function(m){m.addedNodes.forEach(function(n){if(susp(n))kill(n,"已阻止:检测到可疑弹窗");});});}).observe(document.documentElement,{childList:true,subtree:true,attributes:true});' +
'setInterval(function(){var h=scan(document.documentElement,0);if(h&&h!==document.documentElement&&h.parentNode)kill(h,"已阻止:检测到可疑弹窗");},400);' +
'})();';
}
function shellHead(title) {
return '<!DOCTYPE html><html><head><meta charset="UTF-8"><style>' +
'body{margin:0;font-family:-apple-system,sans-serif;background:#f6f8fa;color:#333;min-height:100vh;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:10px;}' +
'h1{font-size:18px;color:#57606a;font-weight:600;}' +
'#block-notice{position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#3fb950;color:#fff;padding:10px 22px;border-radius:999px;font-weight:600;opacity:0;transition:opacity .3s;z-index:2147483647;font-size:13px;}' +
'#block-notice.show{opacity:1;}' +
'</style></head><body><h1>' + title + '</h1><div id="block-notice">已阻止</div>';
}
function runTest(id) {
if (id === 'test1') {
injectToSandbox(securityCenterApp());
log('打开安全中心 v3(带文件防护)', 'info');
}
else if (id === 'test5') {
injectToSandbox(virusApp());
log('打开文件管理', 'info');
log('⚠ 检测到可疑应用:文件管理', 'warn');
setTimeout(function() {
log('⚠ 文件内容被篡改为乱码', 'error');
}, 2500);
}
else if (id === 'test2') {
var code = shellHead('测试2 · iframe 绕过') +
'<script>' + interceptorCode() + '<\/script>' +
'<script>setTimeout(function(){var f=document.createElement("iframe");f.style.cssText="position:fixed;inset:0;width:100%;height:100%;border:0;z-index:999999;";f.srcdoc=\'<!DOCTYPE html><html><head><style>html,body{margin:0;height:100%;}.overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.78);}.box{padding:26px;border-radius:18px;background:#101010;color:#fff;text-align:center;font-family:sans-serif;}.title{color:#00ff88;font-weight:700;font-size:20px;}.count{font-size:56px;font-weight:800;}<\/style><\/head><body><div class="overlay"><div class="box"><div class="title">⚠ 绕过演示</div><div class="count" id="c">3</div></div></div><script>(function(){var e=document.getElementById("c"),n=3,t=setInterval(function(){n--;if(n>0){e.textContent=n;}else{clearInterval(t);}},1000);})();<\/script></body></html>\';document.body.appendChild(f);},500);<\/script>' +
'</body></html>';
injectToSandbox(code);
log('运行测试2:iframe 绕过', 'info');
}
else if (id === 'test3') {
var code = shellHead('测试3 · Shadow DOM') +
'<script>' + interceptorCode() + '<\/script>' +
'<script>setTimeout(function(){var h=document.createElement("div");document.body.appendChild(h);var s=h.attachShadow({mode:"open"});s.innerHTML=\'<style>.overlay{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.78);z-index:999999;}.box{padding:26px;border-radius:18px;background:#101010;color:#fff;text-align:center;font-family:sans-serif;}.title{color:#af52de;font-weight:700;font-size:20px;}.count{font-size:56px;font-weight:800;}<\/style><div class="overlay"><div class="box"><div class="title">⚠ Shadow DOM 绕过</div><div class="count">3</div></div></div>\';},500);<\/script>' +
'</body></html>';
injectToSandbox(code);
log('运行测试3:Shadow DOM', 'info');
}
else if (id === 'test4') {
var code = '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">' +
'<meta name="viewport" content="width=device-width, initial-scale=1.0"><title>文本显示</title>' +
'<style>body { margin:0; min-height:100vh; display:flex; align-items:center; justify-content:center; background:#f5f5f5; font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; color:#333; padding:20px; }' +
'.card { max-width:100%; background:#fff; border-radius:16px; padding:22px; box-shadow: 0 8px 30px rgba(0,0,0,.08); line-height:1.9; font-size:14px; }' +
'h1 { font-size:18px; margin:0 0 14px; color:#111; } p { margin:0 0 10px; }' +
'.tip { margin-top:16px; font-size:12px; color:#888; border-top:1px solid #eee; padding-top:12px; }</style></head>' +
'<body><div class="card"><h1>文本显示测试</h1>' +
'<p>这是一段中文文本,用来测试网页是否能正确显示,不乱码。</p>' +
'<p>English text, numbers 1234567890, symbols !@#$%^&*().</p>' +
'<p>如果打开后看到方块或问号,说明编码不是 UTF-8。</p>' +
'<div class="tip">编码:UTF-8</div></div></body></html>';
injectToSandbox(code);
log('运行测试4:文本显示', 'info');
}
else if (id === 'dock') {
var code = '<!DOCTYPE html><html><head><meta charset="UTF-8"><style>' +
'body{margin:0;height:100vh;display:flex;align-items:center;justify-content:center;background:#f5f5f5;font-family:-apple-system,sans-serif;color:#8b949e;font-size:14px;flex-direction:column;gap:8px;}' +
'.icon{font-size:48px;opacity:.4;}' +
'</style></head><body><div class="icon">⚙</div><div>此功能暂未开放</div></body></html>';
injectToSandbox(code);
}
}
// ================== 事件绑定 ==================
document.querySelectorAll('.app').forEach(function(app) {
app.addEventListener('click', function() {
openApp(app.dataset.app, app.dataset.title);
});
});
$('backBtn').addEventListener('click', closeApp);
$('btnTest1').onclick = function() { openApp('test1', '安全中心'); };
$('btnTest5').onclick = function() { openApp('test5', '文件管理'); };
$('btnTest2').onclick = function() { openApp('test2', '浏览器'); };
$('btnTest3').onclick = function() { openApp('test3', '影子空间'); };
$('btnTest4').onclick = function() { openApp('test4', '文本阅读器'); };
$('btnReload').onclick = resetSandbox;
$('btnClearLog').onclick = function() {
consoleEl.innerHTML = '';
logCount = 0;
$('logCount').textContent = '0';
log('日志已清空', 'info');
};
$('btnInject').onclick = function() {
var code = $('codeInput').value;
if (!code.trim()) { log('代码为空', 'warn'); return; }
if (!appView.classList.contains('active')) {
openApp('custom', '自定义注入');
setTimeout(function() { injectToSandbox(code); }, 400);
} else {
injectToSandbox(code);
}
log('代码已注入', 'ok');
};
$('btnWrapDoc').onclick = function() {
var raw = $('codeInput').value;
$('codeInput').value = '<!DOCTYPE html><html><head><meta charset="UTF-8"><style>body{margin:0;background:#f6f8fa;font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;}</style></head><body>' + raw + '</body></html>';
log('已包裹为完整文档', 'info');
};
resetSandbox();
log('手机沙箱实验台启动', 'ok');
log('安全中心已升级 v3:新增文件防护', 'warn');
log('点击主屏幕图标打开应用', 'info');
</script>
</body>
</html>Game Source: 手机沙箱实验台
Creator: EpicCoder88
Libraries: none
Complexity: complex (1042 lines, 58.6 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-epiccoder88-muqm7fbz" to link back to the original. Then publish at arcadelab.ai/publish.