Files
Toolbox/public/tools/thought_lab/labs/physics_balls/index.html
T
yangxiangyuan 91148fe064 feat: 新增物理弹球实验室及配套功能
1. 新增物理弹球游戏全套代码,包含物理引擎、规则、渲染、游戏逻辑、存档、音效、UI
2. 新增物理弹球相关调试与单元测试脚本
3. 优化私有剪贴上传功能:增加200MB上传限制与错误提示
4. 修复复古弹球桌的光滑面防粘附与休眠逻辑
2026-08-08 22:31:33 +08:00

174 lines
9.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>物理弹球 · 思想实验室</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { height: 100%; background: #000; overflow: hidden; font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; }
#pb-wrap { position: relative; height: 100%; max-width: 100vw; aspect-ratio: 420 / 880; margin: 0 auto; touch-action: none; user-select: none; }
@media (min-aspect-ratio: 420/880) { #pb-wrap { height: 100%; } }
#pb-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.hidden { display: none !important; }
button { font-family: inherit; cursor: pointer; border: none; }
/* ---------- HUD ---------- */
#pb-hud { position: absolute; top: 0; left: 0; right: 0; padding: 10px 12px; display: flex; align-items: flex-start; pointer-events: none; }
#pb-pause-btn { pointer-events: auto; background: none; color: #ddd; font-size: 20px; width: 34px; height: 34px; border-radius: 50%; border: 2px solid #888; line-height: 1; }
#pb-time { margin-left: 10px; margin-top: 6px; color: #999; font-size: 13px; }
#pb-balls { margin-left: auto; color: #c89458; font-size: 14px; margin-top: 8px; }
#pb-big { color: #ffd83d; font-size: 12px; border: 1px solid #ffd83d; border-radius: 10px; padding: 2px 8px; margin-left: 10px; margin-top: 6px; }
#pb-score { margin-left: 12px; color: #fff; font-size: 26px; font-weight: 600; }
#pb-score.pop { animation: pb-pop 0.18s ease; }
@keyframes pb-pop { 0% { transform: scale(1.3); color: #ffd83d; } 100% { transform: scale(1); } }
/* ---------- 主菜单(覆盖层不挡拖拽,仅按钮可点) ---------- */
#pb-menu { position: absolute; inset: 0; pointer-events: none; }
#pb-menu button, #pb-daily { pointer-events: auto; }
#pb-gear { position: absolute; top: 10px; left: 10px; width: 34px; height: 34px; border-radius: 50%; background: #f7c81a; color: #4a3600; font-size: 18px; }
#pb-help { position: absolute; top: 10px; right: 10px; width: 34px; height: 34px; border-radius: 50%; background: #333; color: #ddd; font-size: 17px; font-weight: 700; }
#pb-best { position: absolute; top: 52px; right: 14px; color: #f7c81a; font-size: 15px; }
#pb-logo { position: absolute; top: 16%; left: 50%; transform: translateX(-50%); }
#pb-title { position: absolute; top: calc(16% + 130px); left: 0; right: 0; text-align: center; color: #fff; font-size: 22px; letter-spacing: 14px; text-indent: 14px; }
#pb-side-balls { position: absolute; left: 0; top: 52%; background: #ff2f7b; color: #fff; border-radius: 0 22px 22px 0; padding: 8px 16px 8px 10px; font-size: 14px; }
#pb-side-records { position: absolute; left: 0; top: calc(52% + 52px); background: #7b2fff; color: #fff; border-radius: 0 22px 22px 0; padding: 8px 16px 8px 10px; font-size: 14px; }
#pb-daily { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 86px; height: 86px; border-radius: 50%; overflow: hidden; background: linear-gradient(#fff 0 50%, #f7c81a 50% 100%); text-align: center; font-size: 15px; font-weight: 700; color: #222; line-height: 1.5; padding-top: 14px; box-shadow: 0 0 0 3px rgba(255,255,255,0.15); }
#pb-hint-text { position: absolute; bottom: 7%; left: 0; right: 0; text-align: center; color: #ddd; font-size: 15px; }
#pb-hint-line { position: absolute; bottom: 12%; left: 18%; right: 18%; height: 2px; background: #fff; }
/* ---------- 覆盖层通用 ---------- */
.overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.88); display: flex; flex-direction: column; align-items: center; z-index: 5; }
.ov-btn-blue { background: #1478e6; color: #fff; border-radius: 26px; padding: 13px 44px; font-size: 16px; margin-top: 14px; display: flex; align-items: center; gap: 10px; }
.ov-btn-pink { background: #ff2f7b; color: #fff; border-radius: 26px; padding: 13px 40px; font-size: 16px; margin-top: 30px; }
/* ---------- 暂停 ---------- */
#pb-pause h2 { color: #fff; font-size: 26px; letter-spacing: 12px; margin-top: 24%; }
/* ---------- 结束 ---------- */
#pb-over h2 { color: #fff; font-size: 24px; margin-top: 22%; }
#pb-over-score { color: #f7c81a; font-size: 52px; font-weight: 700; margin-top: 12px; }
#pb-over-time { color: #999; margin-top: 6px; }
#pb-over-new { color: #ff2f7b; font-weight: 700; margin-top: 8px; }
#pb-over-best { color: #f7c81a; margin-top: 4px; font-size: 14px; }
/* ---------- 记录 ---------- */
#pb-records { padding-top: 8%; }
#pb-records h2 { color: #fff; font-size: 20px; margin-bottom: 14px; }
#pb-records-list { width: 100%; flex: 1; overflow-y: auto; }
.pb-rec-row { display: flex; align-items: center; padding: 12px 16px; background: #383838; }
.pb-rec-row.alt { background: #262626; }
.pb-rec-rank { width: 40px; font-size: 18px; font-weight: 700; }
.pb-rec-date { font-size: 13px; flex: 1; }
.pb-rec-right { text-align: right; }
.pb-rec-right b { display: block; font-size: 20px; }
.pb-rec-right i { font-style: normal; font-size: 12px; }
.pb-rec-empty { color: #888; text-align: center; margin-top: 40px; }
#pb-records-close { margin: 12px 0 20px; }
/* ---------- 指南 ---------- */
#pb-guide { padding-top: 16%; }
#pb-guide h2 { color: #fff; font-size: 22px; margin-bottom: 18px; }
#pb-guide-demo { background: #000; }
#pb-guide p { color: #eee; font-size: 14px; margin-top: 14px; }
#pb-guide-ok { margin-top: 26px; }
/* ---------- 设置 ---------- */
#pb-settings { padding-top: 30%; }
#pb-settings h2 { color: #fff; font-size: 20px; margin-bottom: 24px; }
.pb-set-row { color: #ddd; font-size: 16px; display: flex; align-items: center; gap: 12px; }
.pb-set-row input[type="checkbox"] { width: 20px; height: 20px; }
.pb-set-slider { margin-top: 22px; }
.pb-set-slider input[type="range"] { width: 170px; accent-color: #f7c81a; }
.pb-set-slider b { color: #f7c81a; min-width: 30px; text-align: right; }
.pb-set-hint { color: #888; font-size: 12px; margin-top: 10px; }
/* ---------- toast ---------- */
#pb-toast { position: absolute; left: 50%; bottom: 16%; transform: translateX(-50%); background: rgba(40,40,40,0.95); color: #eee; font-size: 13px; padding: 9px 18px; border-radius: 20px; opacity: 0; transition: opacity 0.25s; pointer-events: none; z-index: 9; white-space: nowrap; }
#pb-toast.show { opacity: 1; }
</style>
</head>
<body>
<div id="pb-wrap">
<canvas id="pb-canvas" width="420" height="880"></canvas>
<div id="pb-hud" class="hidden">
<button id="pb-pause-btn" title="暂停">❚</button>
<div id="pb-time">时间 00:00</div>
<div id="pb-balls">🥎 0</div>
<div id="pb-big" class="hidden">大球 ×2</div>
<div id="pb-score">0</div>
</div>
<div id="pb-menu">
<button id="pb-gear" title="设置">⚙</button>
<button id="pb-help" title="游戏指南">?</button>
<div id="pb-best">最佳: 0</div>
<canvas id="pb-logo" width="220" height="120"></canvas>
<div id="pb-title">物理弹球</div>
<button id="pb-side-balls">🥎 球 <b id="pb-ball-count">1</b></button>
<button id="pb-side-records">📋 记录</button>
<button id="pb-daily">今天<br>+3 🥎</button>
<div id="pb-hint-line"></div>
<div id="pb-hint-text">拖动瞄准</div>
</div>
<div id="pb-pause" class="overlay hidden">
<h2>暂 停</h2>
<button id="pb-share" class="ov-btn-blue">📋 复制成绩</button>
<button id="pb-restart" class="ov-btn-blue">⟳ 重新开始</button>
<button id="pb-resume" class="ov-btn-pink">▶ 继续游戏</button>
</div>
<div id="pb-over" class="overlay hidden">
<h2>本局结束</h2>
<div id="pb-over-score">0</div>
<div id="pb-over-time">用时 00:00</div>
<div id="pb-over-new">★ 新纪录 ★</div>
<div id="pb-over-best">最佳: 0</div>
<button id="pb-again" class="ov-btn-pink">再来一局</button>
<button id="pb-to-menu" class="ov-btn-blue">返回主菜单</button>
</div>
<div id="pb-records" class="overlay hidden">
<h2>记录</h2>
<div id="pb-records-list"></div>
<button id="pb-records-close" class="ov-btn-blue">返回</button>
</div>
<div id="pb-guide" class="overlay hidden">
<h2>游戏指南</h2>
<canvas id="pb-guide-demo" width="220" height="160"></canvas>
<p>1. 水平移动手指,瞄准数字块。</p>
<p>2. 击中数字块得分(小球+1,大球+2)。</p>
<p>3. 数字块到达顶端,游戏结束。</p>
<p>4. 留意出现的道具助你一臂之力。</p>
<p>5. 球陷入夹角时会全力研磨接触的数字块,快速开出逃逸空间。</p>
<button id="pb-guide-ok" class="ov-btn-blue">知道了!</button>
</div>
<div id="pb-settings" class="overlay hidden">
<h2>设置</h2>
<label class="pb-set-row"><input type="checkbox" id="pb-sound-toggle" checked> 音效</label>
<div class="pb-set-row pb-set-slider">
<span>逃逸强度</span>
<input type="range" id="pb-escape-slider" min="0" max="100" step="5">
<b id="pb-escape-val">50</b>
</div>
<div class="pb-set-hint">越高:卡住的球挣脱越猛,越简单</div>
<button id="pb-settings-close" class="ov-btn-blue">返回</button>
</div>
<div id="pb-toast"></div>
</div>
<script src="pb_physics.js"></script>
<script src="pb_rules.js"></script>
<script src="pb_storage.js"></script>
<script src="pb_audio.js"></script>
<script src="pb_render.js"></script>
<script src="pb_game.js"></script>
<script src="pb_ui.js"></script>
</body>
</html>