🎮ArcadeLab

手机沙箱实验台

by EpicCoder88
1042 lines58.6 KB
▶ Play
<!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 {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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.