-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstart.html
More file actions
162 lines (147 loc) · 7.01 KB
/
start.html
File metadata and controls
162 lines (147 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
<!DOCTYPE html>
<html lang="zh-Hans">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="signaling-url" content="__SIGNALING_URL__" />
<title>VoxWeb</title>
<!--
浏览器能力前置检测。
必须在 trunk 注入的 `<script type="module">` 解析之前同步运行;
不通过时 `window.stop()` 中止 wasm 预加载并用 `document.write` 替换整个文档。
详见 docs/features/persistence.md §十五 / docs/reference.md §浏览器能力前置检测。
-->
<script>
(function () {
// 开发模式跳过:URL 加 ?force=1
if (new URLSearchParams(location.search).get('force') === '1') {
return;
}
var checks = {
wasm: function () {
return typeof WebAssembly === 'object'
&& typeof WebAssembly.instantiate === 'function';
},
webgpu: function () {
return 'gpu' in navigator;
},
opfs: function () {
return !!(navigator.storage
&& typeof navigator.storage.getDirectory === 'function');
},
webrtc: function () {
return typeof RTCPeerConnection === 'function'
&& 'createDataChannel' in RTCPeerConnection.prototype;
},
websocket: function () {
return typeof WebSocket === 'function';
},
pointerlock: function () {
return 'requestPointerLock' in HTMLElement.prototype;
}
};
var required = ['wasm', 'webgpu', 'opfs', 'webrtc', 'websocket', 'pointerlock'];
var missing = required.filter(function (k) { return !checks[k](); });
// 检测移动设备:Android / iOS 手机和平板
// 现代 Android Chrome 虽通过所有 API 检测,但游戏无触屏控件,不适用于移动端
var ua = navigator.userAgent || '';
var isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(ua)
|| (navigator.maxTouchPoints > 2 && /Macintosh/i.test(ua));
// 全部满足 + 非移动设备:放行后续 trunk 注入的 wasm 加载
if (missing.length === 0 && !isMobile) return;
// 仅 WebRTC 缺失 + 非移动设备:允许进入单机模式,仅设 flag 供 Rust 端读取
if (!isMobile && missing.length === 1 && missing[0] === 'webrtc') {
window.__VOXWEB_FORCE_LOCAL_ONLY = true;
return;
}
var messages = {
wasm: '不支持 WebAssembly。请升级浏览器。',
webgpu: '不支持 WebGPU。Firefox 用户需切换 nightly;Safari 需升级到 17 以上。',
opfs: '不支持 OPFS(存档系统所需)。请升级到 Chrome / Edge 102+、Firefox 111+、Safari 17+。',
webrtc: '不支持 WebRTC。无法多人联机。',
websocket: '不支持 WebSocket。无法连接信令服务器。',
pointerlock: '不支持指针锁。第一人称视角无法启用。'
};
// 构建检测失败行:移动设备优先显示
var rowKeys = missing.slice();
if (isMobile) rowKeys.unshift('mobile');
var rows = rowKeys.map(function (k) {
if (k === 'mobile') {
return '<tr><td class="key">mobile</td><td class="msg">检测到移动设备。VoxWeb 暂不支持触屏操作,请使用桌面浏览器。</td></tr>';
}
return '<tr><td class="key">' + k + '</td><td class="msg">' + messages[k] + '</td></tr>';
}).join('');
var html = [
'<!DOCTYPE html>',
'<html lang="zh-Hans"><head>',
'<meta charset="UTF-8">',
'<meta name="viewport" content="width=device-width, initial-scale=1.0">',
'<title>VoxWeb 不兼容此浏览器</title>',
'<style>',
'html, body { margin: 0; min-height: 100vh; background: #1a1a2e;',
' color: #e8e3da; font-family: "Segoe UI", "Helvetica Neue", sans-serif; }',
'body { display: flex; align-items: center; justify-content: center; padding: 24px; }',
'.box { max-width: 640px; width: 100%; padding: 40px;',
' background: rgba(255,255,255,0.04);',
' border: 1px solid rgba(255,255,255,0.08); border-radius: 4px; }',
'h1 { margin: 0 0 8px; font-size: 1.4rem; font-weight: 500; color: #ff8a8a; }',
'.lead { margin: 0 0 20px; color: #9a9290; line-height: 1.6; }',
'table { width: 100%; border-collapse: collapse; margin: 0 0 20px; font-size: 0.9rem; }',
'td { padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.06); vertical-align: top; }',
'td.key { width: 110px; color: #ffb37a; font-family: ui-monospace, Consolas, monospace; font-weight: 600; }',
'td.msg { color: #d6cfc6; line-height: 1.5; }',
'.hint { margin: 0; font-size: 0.85rem; color: #8a8380; line-height: 1.6; }',
'.hint + .hint { margin-top: 10px; }',
'.hint a { color: #a6b3d6; text-decoration: none; }',
'.hint a:hover { text-decoration: underline; }',
'.hint strong { color: #c0bab1; font-weight: 500; }',
'</style>',
'</head><body>',
'<div class="box">',
'<h1>很抱歉,你的浏览器不支持运行 VoxWeb</h1>',
'<p class="lead">VoxWeb 需要现代浏览器的以下能力:</p>',
'<table>', rows, '</table>',
'<p class="hint">推荐使用桌面端 <strong>Chrome / Edge ≥ 113</strong>、<strong>Firefox ≥ 115(WebGPU 需 nightly)</strong>、<strong>Safari ≥ 17</strong>。移动端浏览器暂不支持(需要键盘与鼠标操作)。</p>',
'<p class="hint">仅供开发调试:<a href="?force=1">跳过检测继续</a>(不保证任何功能可用)。</p>',
'</div>',
'</body></html>'
].join('');
// 中止 trunk 已发起的预加载请求(含 wasm 二进制)
window.stop();
// 同步替换整个文档:解析器到达 trunk 注入的 <script> 之前完成
document.open();
document.write(html);
document.close();
})();
</script>
<!-- 构建时复制 index.html -->
<link data-trunk rel="copy-file" href="index.html" />
<!-- trunk 入口:编译期注入 wasm-bindgen 生成的 JS 胶水 + WASM 二进制 -->
<link data-trunk rel="rust" href="crates/client/Cargo.toml" data-type="main"
data-wasm-opt="z"
data-wasm-opt-params="--enable-bulk-memory --enable-nontrapping-float-to-int --enable-sign-ext --enable-mutable-globals --enable-reference-types --enable-multivalue"
data-no-import="false" />
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
overflow: hidden;
background: #1a1a2e;
}
/* 游戏画布:始终铺满全屏 */
#game {
position: fixed;
inset: 0;
width: 100vw;
height: 100vh;
display: block;
background: transparent;
}
</style>
</head>
<body>
<!-- 游戏画布:trunk 编译的 WASM 加载后会绑定到这里 -->
<canvas id="game"></canvas>
</body>
</html>