Spaces:
Running
Running
Fix "Có bản mới" reload loop + cache-bust v6 + lối vào hang & áo đỏ MU
#2
by bep40 - opened
- frontend/index.html +2 -2
- patch_app.mjs +84 -13
frontend/index.html
CHANGED
|
@@ -93,6 +93,6 @@
|
|
| 93 |
<body>
|
| 94 |
<div id="root"></div>
|
| 95 |
<!-- Bundle: app.bundle.js tự chứa toàn bộ (react, three, zustand) -->
|
| 96 |
-
<script src="/static/app.bundle.js?v=
|
| 97 |
</body>
|
| 98 |
-
</html>
|
|
|
|
| 93 |
<body>
|
| 94 |
<div id="root"></div>
|
| 95 |
<!-- Bundle: app.bundle.js tự chứa toàn bộ (react, three, zustand) -->
|
| 96 |
+
<script src="/static/app.bundle.js?v=6"></script>
|
| 97 |
</body>
|
| 98 |
+
</html>
|
patch_app.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
/* Patch app.js + cave3d.js tại build-time.
|
| 2 |
app.js: thêm nút Kéo co + Giữ bóng (challenge sheet),
|
| 3 |
-
ErrorBoundary + window.onerror/unhandledrejection → POST /api/log
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
Chạy TRƯỚC esbuild trong Dockerfile build stage.
|
| 8 |
Nếu pattern không khớp -> exit(1) để build fail (không ship bản thiếu). */
|
| 9 |
import fs from 'node:fs'
|
|
@@ -113,32 +113,103 @@ createRoot(document.getElementById('root')).render(
|
|
| 113 |
</CaveErrorBoundary>
|
| 114 |
</React.StrictMode>
|
| 115 |
)` ],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
])
|
| 117 |
|
| 118 |
/* ================= cave3d.js ================= */
|
| 119 |
patchFile('frontend/cave3d.js', [
|
|
|
|
| 120 |
[ ` <Entrance />
|
| 121 |
<hemisphereLight args={[0xe8c090, 0x7a6152, 1.5]} />`,
|
| 122 |
-
` <
|
|
|
|
| 123 |
<OutdoorScene />
|
| 124 |
<hemisphereLight args={[0xe8c090, 0x7a6152, 1.5]} />` ],
|
|
|
|
| 125 |
[ ` <mesh position={[0, 3, 17.5]}>
|
| 126 |
<planeGeometry args={[14, 10]} />
|
| 127 |
<meshBasicMaterial color="#f4e7cf" side={THREE.DoubleSide} fog={false} />
|
| 128 |
</mesh>`,
|
| 129 |
` <mesh position={[0, 3.2, 17.6]}>
|
| 130 |
<ringGeometry args={[6.3, 7.5, 30]} />
|
| 131 |
-
<
|
| 132 |
-
</mesh>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
[ `export { floorHeight, domeHeight }`,
|
| 134 |
`export { floorHeight, domeHeight }
|
| 135 |
|
| 136 |
/* =========================================================================
|
| 137 |
-
OUTDOOR SCENE — sân ngoài cửa hang (+z), nhìn qua lối vào
|
| 138 |
-
trời + đất + lối đi + đá tảng + cây + ánh sáng ban ngày
|
| 139 |
-
Tunnel kết thúc ~z=73.5 → mọi vật outdoor nằm z>76 để không che cửa hang.
|
| 140 |
-
Mặt trời (directionalLight) đặt phía sân z=140 → chiếu về phía hang,
|
| 141 |
-
rọi sáng mặt tiền của đá/cây khi nhìn từ trong hang ra.
|
| 142 |
========================================================================= */
|
| 143 |
function _makeSkyTex() {
|
| 144 |
if (typeof document === 'undefined') return null
|
|
@@ -262,4 +333,4 @@ function OutdoorScene() {
|
|
| 262 |
</group>
|
| 263 |
)
|
| 264 |
}` ],
|
| 265 |
-
])
|
|
|
|
| 1 |
/* Patch app.js + cave3d.js tại build-time.
|
| 2 |
app.js: thêm nút Kéo co + Giữ bóng (challenge sheet),
|
| 3 |
+
ErrorBoundary + window.onerror/unhandledrejection → POST /api/log,
|
| 4 |
+
FIX UpdateNotice "Có bản mới": lần check đầu = baseline version đang chạy;
|
| 5 |
+
chỉ báo "Có bản mới" khi version.json thực sự đổi — hết tự reload liên tục.
|
| 6 |
+
cave3d.js: OutdoorScene (sân ngoài cửa hang) + LỐI VÀO sáng rõ + ÁO ĐỎ MU #7 trưng bày.
|
| 7 |
Chạy TRƯỚC esbuild trong Dockerfile build stage.
|
| 8 |
Nếu pattern không khớp -> exit(1) để build fail (không ship bản thiếu). */
|
| 9 |
import fs from 'node:fs'
|
|
|
|
| 113 |
</CaveErrorBoundary>
|
| 114 |
</React.StrictMode>
|
| 115 |
)` ],
|
| 116 |
+
/* ---- FIX "Có bản mới" tự tải lại liên tục ----
|
| 117 |
+
Cũ: `window.__hangmuVersion = null` mỗi lần mount → `id !== null`
|
| 118 |
+
LUÔN đúng → cứ 15s reload, kể cả khi không có bản mới.
|
| 119 |
+
Mới: lần check đầu = baseline (version đang chạy), chỉ báo "Có bản mới"
|
| 120 |
+
khi version.json thay đổi THẬT SỰ sau đó. */
|
| 121 |
+
[ ` if (alive && id && id !== window.__hangmuVersion) setState({ available: true, seconds: 15, busy: false })`,
|
| 122 |
+
` if (!alive || !id) return
|
| 123 |
+
if (baseline === null) { baseline = id; return }
|
| 124 |
+
if (id !== baseline) setState({ available: true, seconds: 15, busy: false })` ],
|
| 125 |
+
[ ` window.__hangmuVersion = null`,
|
| 126 |
+
` let baseline = null` ],
|
| 127 |
])
|
| 128 |
|
| 129 |
/* ================= cave3d.js ================= */
|
| 130 |
patchFile('frontend/cave3d.js', [
|
| 131 |
+
/* CaveScene: thêm JerseyDisplay + OutdoorScene */
|
| 132 |
[ ` <Entrance />
|
| 133 |
<hemisphereLight args={[0xe8c090, 0x7a6152, 1.5]} />`,
|
| 134 |
+
` <JerseyDisplay />
|
| 135 |
+
<Entrance />
|
| 136 |
<OutdoorScene />
|
| 137 |
<hemisphereLight args={[0xe8c090, 0x7a6152, 1.5]} />` ],
|
| 138 |
+
/* LỐI VÀO HANG: thay tấm chặn mờ bằng vành đá sáng + vòm đá quanh cửa */
|
| 139 |
[ ` <mesh position={[0, 3, 17.5]}>
|
| 140 |
<planeGeometry args={[14, 10]} />
|
| 141 |
<meshBasicMaterial color="#f4e7cf" side={THREE.DoubleSide} fog={false} />
|
| 142 |
</mesh>`,
|
| 143 |
` <mesh position={[0, 3.2, 17.6]}>
|
| 144 |
<ringGeometry args={[6.3, 7.5, 30]} />
|
| 145 |
+
<meshLambertMaterial color="#6b5540" side={THREE.DoubleSide} fog={false} />
|
| 146 |
+
</mesh>
|
| 147 |
+
<mesh position={[0, 3.3, 17.3]}>
|
| 148 |
+
<torusGeometry args={[6.9, 0.72, 8, 26, Math.PI]} />
|
| 149 |
+
<meshLambertMaterial color="#8a6f55" flatShading side={THREE.DoubleSide} fog={false} />
|
| 150 |
+
</mesh>
|
| 151 |
+
<pointLight position={[0, 10, 15.5]} color="#ffe9c8" intensity={45} distance={30} decay={1.2} fog={false} />` ],
|
| 152 |
+
/* ÁO ĐỎ MU TRƯNG BÀY — jersey #7 trong khung gỗ cạnh màn hình lớn */
|
| 153 |
+
[ `export function CaveScene() {`,
|
| 154 |
+
`/* =========================================================================
|
| 155 |
+
ÁO ĐỎ MU TRƯNG BÀY — jersey #7 trong khung gỗ cạnh màn hình lớn
|
| 156 |
+
========================================================================= */
|
| 157 |
+
function _jerseyTex() {
|
| 158 |
+
if (typeof document === 'undefined') return null
|
| 159 |
+
const c = document.createElement('canvas'); c.width = 512; c.height = 640
|
| 160 |
+
const g = c.getContext('2d')
|
| 161 |
+
g.fillStyle = '#17120d'; g.fillRect(0, 0, 512, 640)
|
| 162 |
+
g.fillStyle = '#241b12'; g.fillRect(26, 26, 460, 588)
|
| 163 |
+
g.fillStyle = '#c8102e'
|
| 164 |
+
g.beginPath(); g.moveTo(138, 168); g.lineTo(374, 168); g.lineTo(374, 468); g.lineTo(138, 468); g.closePath(); g.fill()
|
| 165 |
+
g.beginPath(); g.moveTo(138, 168); g.lineTo(50, 118); g.lineTo(86, 332); g.lineTo(138, 298); g.closePath(); g.fill()
|
| 166 |
+
g.beginPath(); g.moveTo(374, 168); g.lineTo(462, 118); g.lineTo(426, 332); g.lineTo(374, 298); g.closePath(); g.fill()
|
| 167 |
+
g.fillStyle = '#7c0d20'
|
| 168 |
+
g.beginPath(); g.arc(256, 172, 36, 0, Math.PI * 2); g.fill()
|
| 169 |
+
g.fillStyle = '#ffffff'
|
| 170 |
+
g.font = '800 200px Georgia, serif'; g.textAlign = 'center'; g.textBaseline = 'middle'
|
| 171 |
+
g.fillText('7', 256, 368)
|
| 172 |
+
g.fillStyle = '#ffe9c0'; g.font = '700 40px Georgia, serif'
|
| 173 |
+
g.fillText('MANCHESTER UNITED', 256, 548)
|
| 174 |
+
const t = new THREE.CanvasTexture(c)
|
| 175 |
+
t.colorSpace = THREE.SRGBColorSpace
|
| 176 |
+
t.anisotropy = 16
|
| 177 |
+
return t
|
| 178 |
+
}
|
| 179 |
+
function JerseyDisplay() {
|
| 180 |
+
const [tex, setTex] = React.useState(null)
|
| 181 |
+
React.useEffect(() => {
|
| 182 |
+
let alive = true
|
| 183 |
+
const t = _jerseyTex()
|
| 184 |
+
if (alive && t) setTex(t)
|
| 185 |
+
return () => { alive = false }
|
| 186 |
+
}, [])
|
| 187 |
+
return (
|
| 188 |
+
<group position={[15.5, 0, -41]} rotation={[0, -0.42, 0]}>
|
| 189 |
+
<mesh position={[0, 0.425, -0.12]}>
|
| 190 |
+
<boxGeometry args={[0.55, 0.85, 0.55]} />
|
| 191 |
+
<meshLambertMaterial color="#3d2a1a" flatShading />
|
| 192 |
+
</mesh>
|
| 193 |
+
<mesh position={[0, 2.15, -0.1]}>
|
| 194 |
+
<boxGeometry args={[2.7, 3.55, 0.16]} />
|
| 195 |
+
<meshLambertMaterial color="#5a3b22" flatShading />
|
| 196 |
+
</mesh>
|
| 197 |
+
{tex && (
|
| 198 |
+
<mesh position={[0, 2.18, 0.04]}>
|
| 199 |
+
<planeGeometry args={[2.35, 2.94]} />
|
| 200 |
+
<meshBasicMaterial map={tex} transparent fog={false} toneMapped={false} />
|
| 201 |
+
</mesh>
|
| 202 |
+
)}
|
| 203 |
+
</group>
|
| 204 |
+
)
|
| 205 |
+
}
|
| 206 |
+
export function CaveScene() {` ],
|
| 207 |
+
/* OutdoorScene — sân ngoài cửa hang */
|
| 208 |
[ `export { floorHeight, domeHeight }`,
|
| 209 |
`export { floorHeight, domeHeight }
|
| 210 |
|
| 211 |
/* =========================================================================
|
| 212 |
+
OUTDOOR SCENE — sân ngoài cửa hang (+z), nhìn qua lối vào
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
========================================================================= */
|
| 214 |
function _makeSkyTex() {
|
| 215 |
if (typeof document === 'undefined') return null
|
|
|
|
| 333 |
</group>
|
| 334 |
)
|
| 335 |
}` ],
|
| 336 |
+
])
|