Fix "Có bản mới" reload loop + cache-bust v6 + lối vào hang & áo đỏ MU

#2
by bep40 - opened
Files changed (2) hide show
  1. frontend/index.html +2 -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=5"></script>
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
- cave3d.js: OutdoorScene — sân ngoài cửa hang (trời, đất, lối đi, đá, cây, ánh sáng ngày).
5
- Tọa độ: tunnel kết thúc ~z=73.5 → outdoor bắt đầu z>76 để không che cửa hang.
6
- Ánh sáng ngày chiếu từ phía sân (z=140) về phía hang để mặt tiền được rọi sáng.
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
- ` <Entrance />
 
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
- <meshBasicMaterial color="#0b0705" side={THREE.DoubleSide} fog={false} />
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
+ ])