Lashtw commited on
Commit
f652d6e
·
verified ·
1 Parent(s): e32b9e6

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +605 -19
index.html CHANGED
@@ -1,19 +1,605 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { RefreshCw, Check, HelpCircle, ArrowRight, Sparkles, Coins, X } from 'lucide-react';
3
+
4
+ // 定義題目結構
5
+ interface Problem {
6
+ id: number;
7
+ category: string;
8
+ question: string; // LaTeX 格式
9
+ answers: string[]; // 所有可接受的答案格式
10
+ hint: string;
11
+ }
12
+
13
+ // 題庫資料 (從 PDF 2-6 頁摘錄)
14
+ const PROBLEMS: Problem[] = [
15
+ // Page 2: 提公因式
16
+ {
17
+ id: 1,
18
+ category: "基礎提公因式 (Page 2)",
19
+ question: "3b^2 + 2b",
20
+ answers: ["b(3b+2)", "(3b+2)b", "b*(3b+2)"],
21
+ hint: "觀察兩項都有什麼共同的英文字母?"
22
+ },
23
+ {
24
+ id: 2,
25
+ category: "基礎提公因式 (Page 2)",
26
+ question: "5x^2 + 2x",
27
+ answers: ["x(5x+2)", "(5x+2)x"],
28
+ hint: "提出公因式 x"
29
+ },
30
+ {
31
+ id: 3,
32
+ category: "基礎提公因式 (Page 2)",
33
+ question: "6y^2 - 2y",
34
+ answers: ["2y(3y-1)", "(3y-1)2y"],
35
+ hint: "數字 6 和 2 的公因數是?變數 y 也可以提出來。"
36
+ },
37
+
38
+ // Page 3: 分組/括號提公因式
39
+ {
40
+ id: 4,
41
+ category: "分組提公因式 (Page 3)",
42
+ question: "2x(x-2) + 5(x-2)",
43
+ answers: ["(x-2)(2x+5)", "(2x+5)(x-2)"],
44
+ hint: "把 (x-2) 當作一個整體提出來"
45
+ },
46
+ {
47
+ id: 5,
48
+ category: "分組提公因式 (Page 3)",
49
+ question: "x(3x-7) + 4(3x-7)",
50
+ answers: ["(3x-7)(x+4)", "(x+4)(3x-7)"],
51
+ hint: "大家都有 (3x-7)"
52
+ },
53
+ {
54
+ id: 6,
55
+ category: "分組提公因式 (Page 3)",
56
+ question: "3x(2x-5) + (2x-5)(x+8)",
57
+ answers: ["(2x-5)(4x+8)", "4(2x-5)(x+2)", "(4x+8)(2x-5)", "4(x+2)(2x-5)"],
58
+ hint: "提出 (2x-5) 後,剩下的要合併:3x + (x+8)"
59
+ },
60
+
61
+ // Page 4: 變號提公因式
62
+ {
63
+ id: 7,
64
+ category: "變號提公因式 (Page 4)",
65
+ question: "x(2x-1) + (1-2x)",
66
+ answers: ["(2x-1)(x-1)", "(x-1)(2x-1)"],
67
+ hint: "注意 (1-2x) 可以寫成 -(2x-1)"
68
+ },
69
+ {
70
+ id: 8,
71
+ category: "變號提公因式 (Page 4)",
72
+ question: "x(2x-3) - 3(3-2x)",
73
+ answers: ["(2x-3)(x+3)", "(x+3)(2x-3)"],
74
+ hint: "-(3-2x) 其實就是 +(2x-3)"
75
+ },
76
+
77
+ // Page 5: 平方差公式 a^2 - b^2 = (a+b)(a-b)
78
+ {
79
+ id: 9,
80
+ category: "平方差公式 (Page 5)",
81
+ question: "x^2 - 49",
82
+ answers: ["(x+7)(x-7)", "(x-7)(x+7)"],
83
+ hint: "49 是 7 的平方"
84
+ },
85
+ {
86
+ id: 10,
87
+ category: "平方差公式 (Page 5)",
88
+ question: "25x^2 - 36",
89
+ answers: ["(5x+6)(5x-6)", "(5x-6)(5x+6)"],
90
+ hint: "25x^2 是 (5x) 的平方,36 是 6 的平方"
91
+ },
92
+ {
93
+ id: 11,
94
+ category: "平方差公式 (Page 5)",
95
+ question: "49 - (y-1)^2",
96
+ answers: ["(8-y)(y+6)", "(y+6)(8-y)", "-(y-8)(y+6)", "(7-(y-1))(7+(y-1))"], // 包含未化簡與化簡後的答案
97
+ hint: "A=7, B=(y-1),套用 (A+B)(A-B)"
98
+ },
99
+
100
+ // Page 6: 完全平方公式
101
+ {
102
+ id: 12,
103
+ category: "完全平方公式 (Page 6)",
104
+ question: "x^2 + 8x + 16",
105
+ answers: ["(x+4)^2", "(x+4)(x+4)"],
106
+ hint: "頭平方,尾平方,2倍頭尾在中央。16 是 4 的平方。"
107
+ },
108
+ {
109
+ id: 13,
110
+ category: "完全平方公式 (Page 6)",
111
+ question: "x^2 - 10x + 25",
112
+ answers: ["(x-5)^2", "(x-5)(x-5)"],
113
+ hint: "中間是負號,所以是差的平方"
114
+ },
115
+ {
116
+ id: 14,
117
+ category: "完全平方公式 (Page 6)",
118
+ question: "16x^2 - 24x + 9",
119
+ answers: ["(4x-3)^2", "(4x-3)(4x-3)"],
120
+ hint: "16x^2 是 (4x)^2,9 是 3^2"
121
+ },
122
+ {
123
+ id: 15,
124
+ category: "綜合挑戰",
125
+ question: "(x+1)(x-3) - (x-3)",
126
+ answers: ["x(x-3)", "(x-3)x"],
127
+ hint: "後面的 -(x-3) 可以看作 -1(x-3)"
128
+ }
129
+ ];
130
+
131
+ // 工具:標準化輸入字串 (移除空格,統一格式)
132
+ const normalizeInput = (input: string): string => {
133
+ return input.replace(/\s/g, "")
134
+ .replace(/\^2/g, "^2") // 確保次方符號一致
135
+ .replace(/(/g, "(") // 處理全形括號
136
+ .replace(/)/g, ")");
137
+ };
138
+
139
+ // 取得當前題目需要的變數
140
+ const getProblemVariables = (question: string): string[] => {
141
+ const vars = new Set<string>();
142
+ if (question.includes('x')) vars.add('x');
143
+ if (question.includes('y')) vars.add('y');
144
+ if (question.includes('a')) vars.add('a');
145
+ if (question.includes('b')) vars.add('b');
146
+ // 如果題目完全沒有變數(純數字題),預設給 x
147
+ if (vars.size === 0) return ['x'];
148
+ return Array.from(vars).sort();
149
+ };
150
+
151
+ // 特效組件:漂浮的數字/符號
152
+ const FloatingNumber = ({ x, y, value }: { x: number, y: number, value: string }) => (
153
+ <div
154
+ className="absolute text-amber-400 font-bold animate-bounce pointer-events-none select-none opacity-40"
155
+ style={{ left: x, top: y, fontSize: '20px' }}
156
+ >
157
+ {value}
158
+ </div>
159
+ );
160
+
161
+ // 金幣噴發特效
162
+ const CoinBurst = () => {
163
+ const coins = Array.from({ length: 25 }).map((_, i) => ({
164
+ id: i,
165
+ tx: (Math.random() - 0.5) * 300,
166
+ ty: (Math.random() - 1.5) * 250,
167
+ rot: Math.random() * 720,
168
+ scale: 0.5 + Math.random() * 0.5,
169
+ delay: Math.random() * 0.2
170
+ }));
171
+
172
+ return (
173
+ <div className="absolute top-1/2 left-1/2 w-0 h-0 overflow-visible pointer-events-none z-50">
174
+ {coins.map((c) => (
175
+ <div
176
+ key={c.id}
177
+ className="absolute w-8 h-8 bg-yellow-400 rounded-full border-2 border-yellow-600 flex items-center justify-center text-yellow-700 font-bold shadow-lg"
178
+ style={{
179
+ '--tx': `${c.tx}px`,
180
+ '--ty': `${c.ty}px`,
181
+ '--rot': `${c.rot}deg`,
182
+ animation: `coinFly 1.5s cubic-bezier(0.1, 0.8, 0.2, 1) forwards ${c.delay}s`
183
+ } as React.CSSProperties}
184
+ >
185
+ $
186
+ </div>
187
+ ))}
188
+ </div>
189
+ );
190
+ };
191
+
192
+ // SVG 寶箱組件
193
+ const TreasureChest = ({
194
+ isOpen,
195
+ shake,
196
+ children,
197
+ questionText
198
+ }: {
199
+ isOpen: boolean,
200
+ shake: boolean,
201
+ children: React.ReactNode,
202
+ questionText: React.ReactNode
203
+ }) => {
204
+ return (
205
+ <div className={`relative w-full max-w-[320px] sm:max-w-[400px] mx-auto transition-transform duration-300 ${shake ? 'animate-shake' : ''} ${isOpen ? 'scale-105' : ''}`}>
206
+
207
+ {/* 寶箱本體 SVG */}
208
+ <svg viewBox="0 0 400 320" className="w-full drop-shadow-2xl filter">
209
+ <defs>
210
+ <linearGradient id="woodGradient" x1="0%" y1="0%" x2="100%" y2="0%">
211
+ <stop offset="0%" stopColor="#5D4037" />
212
+ <stop offset="50%" stopColor="#8D6E63" />
213
+ <stop offset="100%" stopColor="#5D4037" />
214
+ </linearGradient>
215
+ <linearGradient id="goldGradient" x1="0%" y1="0%" x2="0%" y2="100%">
216
+ <stop offset="0%" stopColor="#FFD700" />
217
+ <stop offset="50%" stopColor="#FDB931" />
218
+ <stop offset="100%" stopColor="#C49102" />
219
+ </linearGradient>
220
+ <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
221
+ <feGaussianBlur stdDeviation="5" result="blur" />
222
+ <feComposite in="SourceGraphic" in2="blur" operator="over" />
223
+ </filter>
224
+ </defs>
225
+
226
+ <path d="M 20 120 L 380 120 L 380 280 Q 380 310 350 310 L 50 310 Q 20 310 20 280 Z" fill="url(#woodGradient)" stroke="#3E2723" strokeWidth="4" />
227
+ <path d="M 20 120 L 50 120 L 50 310 M 380 120 L 350 120 L 350 310 M 200 120 L 200 310" stroke="url(#goldGradient)" strokeWidth="8" fill="none" />
228
+
229
+ <g className="transition-transform duration-700 origin-bottom" style={{ transformOrigin: '200px 120px', transform: isOpen ? 'rotateX(-110deg) translateY(-20px)' : 'rotateX(0)' }}>
230
+ <path d="M 10 120 Q 200 20 390 120 L 390 120 L 10 120 Z" fill="url(#woodGradient)" stroke="#3E2723" strokeWidth="4" />
231
+ <path d="M 40 118 Q 200 40 360 118" stroke="url(#goldGradient)" strokeWidth="8" fill="none" />
232
+ <path d="M 200 70 L 200 120" stroke="url(#goldGradient)" strokeWidth="8" fill="none" />
233
+ </g>
234
+
235
+ {isOpen && (
236
+ <g className="animate-fade-in">
237
+ <rect x="50" y="120" width="300" height="160" fill="#1a120b" opacity="0.9" />
238
+ <circle cx="200" cy="200" r="60" fill="url(#goldGradient)" filter="url(#glow)" className="animate-pulse opacity-50" />
239
+ <path d="M 80 280 Q 200 180 320 280 Z" fill="url(#goldGradient)" opacity="0.8" />
240
+ </g>
241
+ )}
242
+ </svg>
243
+
244
+ <div className={`absolute -top-16 left-0 right-0 text-center transition-all duration-500 ${isOpen ? 'opacity-0' : 'opacity-100'}`}>
245
+ <div className="inline-block bg-black/60 backdrop-blur-sm text-amber-300 px-6 py-3 rounded-lg border border-amber-500/50 shadow-[0_0_15px_rgba(245,158,11,0.3)]">
246
+ {questionText}
247
+ </div>
248
+ </div>
249
+
250
+ <div className={`absolute top-[55%] left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[70%] transition-opacity duration-300 ${isOpen ? 'opacity-0 pointer-events-none' : 'opacity-100'}`}>
251
+ <div className="bg-black/80 p-2 rounded-lg border-2 border-amber-600 shadow-inner">
252
+ {children}
253
+ </div>
254
+ <div className="text-center text-[10px] text-amber-500/70 mt-1 font-mono tracking-widest">SECRET CODE</div>
255
+ </div>
256
+
257
+ {isOpen && (
258
+ <>
259
+ <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 pointer-events-none">
260
+ <Sparkles className="w-40 h-40 text-yellow-300 animate-spin-slow opacity-80" />
261
+ </div>
262
+ <CoinBurst />
263
+ </>
264
+ )}
265
+ </div>
266
+ );
267
+ };
268
+
269
+ export default function FactoringGame() {
270
+ const [currentLevel, setCurrentLevel] = useState(0);
271
+ const [inputAnswer, setInputAnswer] = useState("");
272
+ const [gameStatus, setGameStatus] = useState<"playing" | "success" | "error" | "completed">("playing");
273
+ const [shake, setShake] = useState(false);
274
+ const [showHint, setShowHint] = useState(false);
275
+ const [score, setScore] = useState(0);
276
+ const inputRef = useRef<HTMLInputElement>(null);
277
+
278
+ // 嚴格模式下的音效處理 (Web Audio API)
279
+ const playSuccessSound = () => {
280
+ try {
281
+ const AudioContext = window.AudioContext || (window as any).webkitAudioContext;
282
+ if (!AudioContext) return;
283
+
284
+ const ctx = new AudioContext();
285
+ const oscillator = ctx.createOscillator();
286
+ const gainNode = ctx.createGain();
287
+
288
+ oscillator.connect(gainNode);
289
+ gainNode.connect(ctx.destination);
290
+
291
+ oscillator.type = 'sine';
292
+ oscillator.frequency.setValueAtTime(523.25, ctx.currentTime);
293
+ oscillator.frequency.exponentialRampToValueAtTime(1046.5, ctx.currentTime + 0.1);
294
+
295
+ gainNode.gain.setValueAtTime(0.3, ctx.currentTime);
296
+ gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.5);
297
+
298
+ oscillator.start();
299
+ oscillator.stop(ctx.currentTime + 0.5);
300
+ } catch (e) {
301
+ console.error("Audio play failed", e);
302
+ }
303
+ };
304
+
305
+ // 處理背景動畫
306
+ const [particles, setParticles] = useState<{x:number, y:number, val:string}[]>([]);
307
+
308
+ useEffect(() => {
309
+ const symbols = ["x²", "y", "+", "-", "( )", "a", "b"];
310
+ const interval = setInterval(() => {
311
+ setParticles(prev => {
312
+ const newP = [...prev, {
313
+ x: Math.random() * 100,
314
+ y: Math.random() * 100,
315
+ val: symbols[Math.floor(Math.random() * symbols.length)]
316
+ }];
317
+ if (newP.length > 20) newP.shift();
318
+ return newP;
319
+ });
320
+ }, 2000);
321
+ return () => clearInterval(interval);
322
+ }, []);
323
+
324
+ const currentProblem = PROBLEMS[currentLevel];
325
+
326
+ const handleCheckAnswer = () => {
327
+ if (!inputAnswer) return;
328
+
329
+ const normalizedUser = normalizeInput(inputAnswer);
330
+ const normalizedAnswers = currentProblem.answers.map(normalizeInput);
331
+
332
+ if (normalizedAnswers.includes(normalizedUser)) {
333
+ setGameStatus("success");
334
+ playSuccessSound();
335
+
336
+ setTimeout(() => {
337
+ if (currentLevel < PROBLEMS.length - 1) {
338
+ setCurrentLevel(prev => prev + 1);
339
+ setInputAnswer("");
340
+ setGameStatus("playing");
341
+ setShowHint(false);
342
+ setScore(prev => prev + 100);
343
+ } else {
344
+ setGameStatus("completed");
345
+ }
346
+ }, 2000);
347
+ } else {
348
+ setGameStatus("error");
349
+ setShake(true);
350
+ setTimeout(() => setShake(false), 500);
351
+ if (navigator.vibrate) navigator.vibrate(200);
352
+ }
353
+ };
354
+
355
+ const handleKeyDown = (e: React.KeyboardEvent) => {
356
+ if (e.key === 'Enter') {
357
+ handleCheckAnswer();
358
+ }
359
+ };
360
+
361
+ const insertSymbol = (symbol: string) => {
362
+ setInputAnswer(prev => prev + symbol);
363
+ inputRef.current?.focus();
364
+ };
365
+
366
+ const renderMath = (text: string) => {
367
+ const parts = text.split(/(\^[0-9]+)/g);
368
+ return (
369
+ <span className="font-serif text-2xl md:text-3xl tracking-wider text-amber-100 font-bold drop-shadow-md select-none">
370
+ {parts.map((part, i) => {
371
+ if (part.startsWith("^")) {
372
+ return <sup key={i} className="text-lg text-amber-300">{part.substring(1)}</sup>;
373
+ }
374
+ return <span key={i}>{part}</span>;
375
+ })}
376
+ </span>
377
+ );
378
+ };
379
+
380
+ const getKeyboardButtons = () => {
381
+ const variableButtons = getProblemVariables(currentProblem.question);
382
+ const operatorButtons = ['+', '-', '^2', '(', ')'];
383
+ const numberButtons = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
384
+
385
+ return {
386
+ topRow: Array.from(new Set([...variableButtons, ...operatorButtons])),
387
+ numberRow: numberButtons
388
+ };
389
+ };
390
+
391
+ const keyboard = getKeyboardButtons();
392
+
393
+ if (gameStatus === "completed") {
394
+ return (
395
+ <div className="fixed inset-0 bg-[#1a120b] text-white flex flex-col items-center justify-center p-4 overflow-hidden font-sans touch-none">
396
+ <div className="absolute inset-0 opacity-20">
397
+ {particles.map((p, i) => <FloatingNumber key={i} x={p.x * 10} y={p.y * 10} value={p.val} />)}
398
+ </div>
399
+ <div className="bg-[#2d1f16] p-8 rounded-2xl shadow-2xl border-4 border-amber-500 text-center max-w-md w-full z-10 animate-fade-in-up">
400
+ <Coins className="w-24 h-24 text-yellow-400 mx-auto mb-6 animate-bounce" />
401
+ <h1 className="text-4xl font-bold text-yellow-400 mb-4">大富翁誕生!</h1>
402
+ <p className="text-amber-200 mb-8 text-lg">你已成功解開所有謎題,獲得了傳說中的代數寶藏!</p>
403
+ <div className="text-3xl font-bold text-white mb-8 flex items-center justify-center gap-2">
404
+ <span>總獎金:</span>
405
+ <span className="text-yellow-400">${score + 100}</span>
406
+ </div>
407
+ <button
408
+ onClick={() => window.location.reload()}
409
+ className="bg-amber-600 hover:bg-amber-500 text-white font-bold py-3 px-8 rounded-full transition-all transform hover:scale-105 flex items-center justify-center mx-auto gap-2 shadow-lg"
410
+ >
411
+ <RefreshCw className="w-5 h-5" />
412
+ 再次挑戰
413
+ </button>
414
+ </div>
415
+ </div>
416
+ );
417
+ }
418
+
419
+ return (
420
+ <div className="fixed inset-0 bg-[#1a120b] text-white flex flex-col items-center justify-center p-4 overflow-hidden font-sans touch-none select-none">
421
+ {/* 背景粒子 */}
422
+ <div className="absolute inset-0 pointer-events-none overflow-hidden">
423
+ {particles.map((p, i) => (
424
+ <div
425
+ key={i}
426
+ className="absolute text-amber-900/30 font-bold text-xl transition-all duration-1000"
427
+ style={{ left: `${p.x}%`, top: `${p.y}%` }}
428
+ >
429
+ {p.val}
430
+ </div>
431
+ ))}
432
+ </div>
433
+
434
+ {/* 遊戲主介面 */}
435
+ <div className="w-full max-w-2xl z-10 flex flex-col items-center h-full justify-center">
436
+
437
+ {/* 頂部資訊欄 */}
438
+ <div className="w-full flex justify-between items-center mb-4 px-4 max-w-[400px]">
439
+ <div className="flex items-center gap-2">
440
+ <span className="bg-amber-700/80 text-amber-100 text-xs font-bold px-3 py-1 rounded-full border border-amber-600 uppercase tracking-wider shadow-lg">
441
+ Level {currentLevel + 1}
442
+ </span>
443
+ </div>
444
+ <div className="flex items-center gap-2 bg-black/40 px-4 py-1 rounded-full border border-amber-900/50 shadow-[0_0_10px_rgba(234,179,8,0.2)]">
445
+ <Coins className="w-5 h-5 text-yellow-400" />
446
+ <span className="text-yellow-400 font-bold font-mono text-lg">${score}</span>
447
+ </div>
448
+ </div>
449
+
450
+ {/* 寶箱與題目區域 */}
451
+ <div className="mb-4 w-full flex justify-center relative max-h-[40vh]">
452
+ <TreasureChest
453
+ isOpen={gameStatus === "success"}
454
+ shake={shake}
455
+ questionText={renderMath(currentProblem.question)}
456
+ >
457
+ {/* 嵌入寶箱的輸入框 */}
458
+ <div className="flex gap-1">
459
+ <input
460
+ ref={inputRef}
461
+ type="text"
462
+ value={inputAnswer}
463
+ onChange={(e) => {
464
+ setInputAnswer(e.target.value);
465
+ setGameStatus("playing");
466
+ }}
467
+ onKeyDown={handleKeyDown}
468
+ placeholder="密碼"
469
+ className={`w-full bg-transparent text-center text-xl font-mono font-bold tracking-widest focus:outline-none transition-colors placeholder-amber-900/50 ${
470
+ gameStatus === "error" ? "text-red-400" : "text-amber-400"
471
+ }`}
472
+ autoComplete="off"
473
+ disabled={gameStatus === "success"}
474
+ readOnly // 防止手機虛擬鍵盤彈出,強制使用遊戲內鍵盤
475
+ />
476
+ <button
477
+ onClick={handleCheckAnswer}
478
+ disabled={!inputAnswer || gameStatus === "success"}
479
+ className={`px-2 rounded hover:bg-amber-500/20 transition-colors flex items-center justify-center ${
480
+ gameStatus === "success" ? "text-green-400" : "text-amber-500"
481
+ }`}
482
+ >
483
+ {gameStatus === "success" ? <Check className="w-6 h-6" /> : <ArrowRight className="w-6 h-6" />}
484
+ </button>
485
+ </div>
486
+ </TreasureChest>
487
+ </div>
488
+
489
+ {/* 操作區域 (提示與鍵盤) */}
490
+ <div className={`w-full max-w-[400px] transition-all duration-500 flex flex-col justify-end ${gameStatus === "success" ? "opacity-0 translate-y-10 pointer-events-none" : "opacity-100 translate-y-0"}`}>
491
+
492
+ {/* 提示按鈕與內容 */}
493
+ <div className="flex justify-center mb-2 min-h-[24px]">
494
+ <button
495
+ onClick={() => setShowHint(!showHint)}
496
+ className="text-amber-400/70 hover:text-amber-300 text-xs flex items-center gap-1 transition-colors px-3 py-1 rounded hover:bg-amber-900/30"
497
+ >
498
+ <HelpCircle className="w-3 h-3" />
499
+ {showHint ? "隱藏提示" : "需要提示?"}
500
+ </button>
501
+ </div>
502
+
503
+ {showHint && (
504
+ <div className="absolute top-20 left-4 right-4 z-50 bg-blue-900/90 backdrop-blur text-blue-200 text-sm p-4 rounded-lg text-center animate-fade-in border border-blue-500/50 shadow-2xl">
505
+ <button onClick={() => setShowHint(false)} className="absolute top-2 right-2 text-blue-300"><X className="w-4 h-4"/></button>
506
+ 💡 提示:{currentProblem.hint}
507
+ </div>
508
+ )}
509
+
510
+ {/* 狀態訊息 */}
511
+ <div className="h-6 text-center mb-2">
512
+ {gameStatus === "error" && (
513
+ <p className="text-red-400 font-bold animate-pulse text-sm bg-red-900/20 inline-block px-4 py-0.5 rounded">
514
+ 密碼錯誤!
515
+ </p>
516
+ )}
517
+ </div>
518
+
519
+ {/* 動態虛擬鍵盤 - 優化觸控區 */}
520
+ <div className="bg-[#2d1f16] p-3 rounded-xl border-t-4 border-amber-800 shadow-2xl mx-2 pb-6 md:pb-3">
521
+ {/* 第一層:變數與運算符 */}
522
+ <div className="grid grid-cols-5 gap-1.5 mb-2">
523
+ {keyboard.topRow.map((symbol) => (
524
+ <button
525
+ key={symbol}
526
+ onClick={() => insertSymbol(symbol)}
527
+ className="bg-[#4e342e] hover:bg-[#5d4037] active:bg-[#3e2723] text-amber-100 py-3 rounded-lg font-mono text-lg font-bold transition-all border-b-4 border-[#281912] active:border-b-0 active:translate-y-1 shadow-md touch-manipulation"
528
+ disabled={gameStatus === "success"}
529
+ >
530
+ {symbol}
531
+ </button>
532
+ ))}
533
+ <button
534
+ onClick={() => {
535
+ setInputAnswer("");
536
+ }}
537
+ className="bg-[#3e2723] hover:bg-red-900/50 text-red-300 py-3 rounded-lg font-mono text-xs font-bold transition-all border-b-4 border-[#1a100a] active:border-b-0 active:translate-y-1 shadow-md col-span-1 touch-manipulation"
538
+ disabled={gameStatus === "success"}
539
+ >
540
+ CLR
541
+ </button>
542
+ </div>
543
+
544
+ {/* 第二層:數字鍵盤 */}
545
+ <div className="grid grid-cols-5 gap-1.5 border-t border-amber-900/30 pt-2">
546
+ {keyboard.numberRow.map((num) => (
547
+ <button
548
+ key={num}
549
+ onClick={() => insertSymbol(num)}
550
+ className="bg-[#3e2723] hover:bg-[#4e342e] active:bg-[#2d1f16] text-amber-200/80 py-3 rounded-lg font-mono text-lg font-bold transition-all border-b-4 border-[#1a100a] active:border-b-0 active:translate-y-1 shadow-md touch-manipulation"
551
+ disabled={gameStatus === "success"}
552
+ >
553
+ {num}
554
+ </button>
555
+ ))}
556
+ </div>
557
+ </div>
558
+ </div>
559
+
560
+ <div className="text-center mt-4 text-amber-900/40 text-[10px]">
561
+ 代數遺跡:公因式的寶藏
562
+ </div>
563
+ </div>
564
+
565
+ <style>{`
566
+ @keyframes shake {
567
+ 0%, 100% { transform: translateX(0); }
568
+ 25% { transform: translateX(-5px); }
569
+ 75% { transform: translateX(5px); }
570
+ }
571
+ .animate-shake {
572
+ animation: shake 0.4s cubic-bezier(.36,.07,.19,.97) both;
573
+ }
574
+ .animate-fade-in {
575
+ animation: fadeIn 0.5s ease-out;
576
+ }
577
+ .animate-spin-slow {
578
+ animation: spin 8s linear infinite;
579
+ }
580
+ @keyframes spin {
581
+ from { transform: rotate(0deg); }
582
+ to { transform: rotate(360deg); }
583
+ }
584
+ @keyframes fadeIn {
585
+ from { opacity: 0; transform: translateY(-10px); }
586
+ to { opacity: 1; transform: translateY(0); }
587
+ }
588
+ @keyframes coinFly {
589
+ 0% {
590
+ transform: translate(-50%, -50%) scale(0) rotate(0deg);
591
+ opacity: 1;
592
+ }
593
+ 10% {
594
+ transform: translate(-50%, -50%) scale(1) rotate(0deg);
595
+ opacity: 1;
596
+ }
597
+ 100% {
598
+ transform: translate(calc(-50% + var(--tx)), calc(-50% + var(--ty))) scale(0.5) rotate(var(--rot));
599
+ opacity: 0;
600
+ }
601
+ }
602
+ `}</style>
603
+ </div>
604
+ );
605
+ }