Lashtw commited on
Commit
b94cda0
·
verified ·
1 Parent(s): 31cc0fc

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +259 -47
index.html CHANGED
@@ -259,6 +259,7 @@
259
  ];
260
 
261
  // --- Data: Categories & Problems ---
 
262
  const ALL_CATEGORIES = [
263
  {
264
  id: "basic",
@@ -266,12 +267,36 @@
266
  desc: "萬丈高樓平地起,找出共同的寶藏。",
267
  color: "from-blue-500 to-cyan-500",
268
  problems: [
269
- { id: 101, q: "3b^2 + 2b", a: ["b(3b+2)", "(3b+2)b", "b(2+3b)"], h: "提出公因式 b" },
270
- { id: 102, q: "5x^2 + 2x", a: ["x(5x+2)", "(5x+2)x", "x(2+5x)"], h: "提出公因式 x" },
271
- { id: 103, q: "6y^2 - 2y", a: ["2y(3y-1)", "(3y-1)2y", "y(6y-2)", "2(3y^2-y)"], h: "係數 6 和 2 都有公因數 2" },
272
- { id: 104, q: "4x^2 - 8x", a: ["4x(x-2)", "x(4x-8)", "4(x^2-2x)", "2x(2x-4)", "2(2x^2-4x)", "(-4x)(2-x)", "4x(-2+x)"], h: "提出 4x" },
273
- { id: 105, q: "ax + bx", a: ["x(a+b)", "(a+b)x", "x(b+a)"], h: "最簡單的提公因式" },
274
- { id: 106, q: "3x^2 + 6x", a: ["3x(x+2)", "x(3x+6)", "3(x^2+2x)", "3x(2+x)"], h: "提出 3x" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
  ]
276
  },
277
  {
@@ -280,12 +305,36 @@
280
  desc: "分進合擊!尋找隱藏的括號。",
281
  color: "from-emerald-500 to-teal-500",
282
  problems: [
283
- { id: 201, q: "2x(x-2) + 5(x-2)", a: ["(x-2)(2x+5)", "(2x+5)(x-2)", "(x-2)(5+2x)", "(2-x)(-2x-5)"], h: "把 (x-2) 當作一個整體" },
284
- { id: 202, q: "x(3x-7) + 4(3x-7)", a: ["(3x-7)(x+4)", "(x+4)(3x-7)", "(3x-7)(4+x)", "(7-3x)(-x-4)"], h: "提出 (3x-7)" },
285
- { id: 203, q: "a(x+y) + b(x+y)", a: ["(x+y)(a+b)", "(a+b)(x+y)", "(y+x)(a+b)", "(x+y)(b+a)"], h: "都有 (x+y)" },
286
- { id: 204, q: "x^2 + 2x + xy + 2y", a: ["(x+2)(x+y)", "(x+y)(x+2)", "(2+x)(y+x)"], h: "前兩項一組,後兩項一組" },
287
- { id: 205, q: "ac + ad + bc + bd", a: ["(c+d)(a+b)", "(a+b)(c+d)", "(d+c)(a+b)", "(c+d)(b+a)"], h: "分組:a(c+d) + b(c+d)" },
288
- { id: 206, q: "3x(2x-5) + (2x-5)(x+8)", a: ["(2x-5)(4x+8)", "4(2x-5)(x+2)", "(4x+8)(2x-5)", "(2x-5)(8+4x)", "4(2x-5)(2+x)"], h: "合併剩下的:3x + (x+8)" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  ]
290
  },
291
  {
@@ -294,11 +343,31 @@
294
  desc: "敵人的敵人就是朋友,善用負號。",
295
  color: "from-amber-500 to-orange-500",
296
  problems: [
297
- { id: 301, q: "x(2x-1) + (1-2x)", a: ["(2x-1)(x-1)", "(x-1)(2x-1)", "(1-2x)(1-x)"], h: "(1-2x) 變號為 -(2x-1)" },
298
- { id: 302, q: "x(a-b) + y(b-a)", a: ["(a-b)(x-y)", "(x-y)(a-b)", "(b-a)(y-x)"], h: "(b-a) 變號為 -(a-b)" },
299
- { id: 303, q: "x(2x-3) - 3(3-2x)", a: ["(2x-3)(x+3)", "(x+3)(2x-3)", "(3-2x)(-x-3)"], h: "減去負的變成加" },
300
- { id: 304, q: "(x-y)^2 + 2(y-x)", a: ["(x-y)(x-y-2)", "(x-y-2)(x-y)", "(y-x)(y-x+2)"], h: "(y-x) 提出負號變成 -(x-y)" },
301
- { id: 305, q: "a(x-y) - b(y-x)", a: ["(x-y)(a+b)", "(a+b)(x-y)", "(y-x)(-a-b)"], h: "變號後提出 (x-y)" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  ]
303
  },
304
  {
@@ -307,11 +376,52 @@
307
  desc: "一加一減,成雙成對。",
308
  color: "from-rose-500 to-pink-500",
309
  problems: [
310
- { id: 401, q: "x^2 - 49", a: ["(x+7)(x-7)", "(x-7)(x+7)", "(7+x)(x-7)", "(-x-7)(7-x)"], h: "7 的平方是 49" },
311
- { id: 402, q: "x^2 - 1", a: ["(x+1)(x-1)", "(x-1)(x+1)", "(1+x)(x-1)"], h: "1 的平方還是 1" },
312
- { id: 403, q: "4x^2 - 9", a: ["(2x+3)(2x-3)", "(2x-3)(2x+3)", "(3+2x)(2x-3)"], h: "(2x)^2 - 3^2" },
313
- { id: 404, q: "25x^2 - 36y^2", a: ["(5x+6y)(5x-6y)", "(5x-6y)(5x+6y)", "(6y+5x)(5x-6y)"], h: "(5x)^2 - (6y)^2" },
314
- { id: 405, q: "49 - (y-1)^2", a: ["(8-y)(y+6)", "(y+6)(8-y)", "-(y-8)(y+6)", "(6+y)(8-y)", "(8-y)(6+y)", "(y-8)(-y-6)", "(-y-6)(y-8)"], h: "A=7, B=(y-1)" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  ]
316
  },
317
  {
@@ -320,11 +430,31 @@
320
  desc: "頭平方,尾平方,2倍頭尾在中央。",
321
  color: "from-violet-500 to-purple-500",
322
  problems: [
323
- { id: 501, q: "x^2 + 2x + 1", a: ["(x+1)^2", "(x+1)(x+1)", "(1+x)^2"], h: "1 是 1^2,中間是 2*x*1" },
324
- { id: 502, q: "x^2 + 8x + 16", a: ["(x+4)^2", "(x+4)(x+4)", "(4+x)^2"], h: "16 是 4^2" },
325
- { id: 503, q: "x^2 - 4x + 4", a: ["(x-2)^2", "(2-x)^2", "(x-2)(x-2)"], h: "中間是負號" },
326
- { id: 504, q: "x^2 - 10x + 25", a: ["(x-5)^2", "(5-x)^2", "(x-5)(x-5)"], h: "25 是 5^2" },
327
- { id: 505, q: "16x^2 - 24x + 9", a: ["(4x-3)^2", "(3-4x)^2", "(4x-3)(4x-3)"], h: "(4x)^2 和 3^2" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  ]
329
  }
330
  ];
@@ -417,6 +547,7 @@
417
  const [manualRoomInput, setManualRoomInput] = useState(""); // 新增:手動輸入的狀態
418
  const [roomHistory, setRoomHistory] = useState([]); // 新增:房間歷史紀錄
419
  const [selectedStudent, setSelectedStudent] = useState(null);
 
420
 
421
  // 新增:載入歷史紀錄
422
  useEffect(() => {
@@ -453,11 +584,11 @@
453
  }
454
  };
455
 
456
- // 新增:產生帶參數的網址與 QR Code 連結
457
- const getQrData = () => {
458
  const baseUrl = window.location.origin + window.location.pathname;
459
  const fullUrl = `${baseUrl}?room=${roomCode}`;
460
- return `https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=${encodeURIComponent(fullUrl)}`;
461
  };
462
 
463
  // Filter data by room
@@ -498,6 +629,24 @@
498
 
499
  return (
500
  <div className="min-h-screen flex flex-col p-4 md:p-6 relative bg-slate-900 text-slate-200 font-sans">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  <div className="flex justify-between items-center mb-6">
502
  <h2 className="text-2xl font-bold flex items-center gap-2 text-amber-400"><Icons.Teacher /> 教師管理後台</h2>
503
  <button onClick={onClose} className="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-lg">返回遊戲</button>
@@ -512,9 +661,15 @@
512
  <div className="text-xs text-slate-400 uppercase tracking-widest mb-2">目前房間代碼</div>
513
  {roomCode ? (
514
  <>
515
- <div className="text-4xl font-mono font-bold text-yellow-400 tracking-widest mb-4">{roomCode}</div>
516
- <div className="bg-white p-2 rounded-lg inline-block mx-auto">
517
- <img src={getQrData()} alt="Room QR" className="w-32 h-32" />
 
 
 
 
 
 
518
  </div>
519
  <div className="mt-2 text-xs text-slate-500">學生掃描後可自動填入代碼</div>
520
  </>
@@ -1077,23 +1232,80 @@
1077
  if (navigator.vibrate) navigator.vibrate(200);
1078
  }
1079
  };
1080
-
1081
  const insertSymbol = (symbol) => {
1082
- const input = inputRef.current; if (!input) return;
1083
- const start = input.selectionStart; const end = input.selectionEnd;
1084
- const text = input.value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1085
  const newText = text.substring(0, start) + symbol + text.substring(end);
1086
- setInputAnswer(newText); setCursorPos(start + symbol.length);
 
 
 
 
 
 
 
 
 
 
 
1087
  };
 
1088
  const deleteSymbol = () => {
1089
- const input = inputRef.current; if (!input) return;
1090
- const start = input.selectionStart; const end = input.selectionEnd;
1091
- const text = input.value;
1092
- if (start !== end) { setInputAnswer(text.substring(0, start) + text.substring(end)); setCursorPos(start); }
1093
- else if (start > 0) { setInputAnswer(text.substring(0, start - 1) + text.substring(end)); setCursorPos(start - 1); }
1094
- input.focus();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1095
  };
1096
- const handleSelect = (e) => setCursorPos(e.target.selectionStart);
1097
  const renderMath = (text) => <span className="font-math text-3xl md:text-4xl">{text.split(/(\^[0-9]+)/g).map((part, i) => part.startsWith("^") ? <sup key={i} className="text-xs font-bold relative -top-3 left-0.5" style={{verticalAlign: 'super'}}>{part.substring(1)}</sup> : part)}</span>;
1098
  const getDynamicKeyboard = (question) => { const vars = new Set(); (question.match(/[a-z]/g) || []).forEach(m => vars.add(m)); if (vars.size === 0) vars.add('x'); return Array.from(vars).sort(); };
1099
 
@@ -1242,8 +1454,8 @@
1242
  </div>
1243
  <div className={`absolute bottom-0 left-0 right-0 bg-[#16101a] border-t border-slate-800 p-2 z-50 transition-transform duration-500 ${chestOpen ? 'translate-y-full' : 'translate-y-0'}`}>
1244
  <div className="max-w-2xl mx-auto">
1245
- <div className="flex gap-1 mb-1.5 justify-center">{[...dynamicVars, '+', '-', '(', ')', '^2'].map(sym => <button key={sym} onClick={() => insertSymbol(sym)} className="flex-1 h-10 md:h-12 bg-[#2d2033] hover:bg-[#3e2d45] active:bg-[#251a2b] text-amber-100 rounded border-b-2 border-[#1a101f] active:border-b-0 active:translate-y-[2px] transition-all font-mono text-lg md:text-xl font-bold shadow-sm">{sym === '^2' ? <span>^<sup className="text-xs font-bold relative -top-1">2</sup></span> : sym}</button>)}<button onClick={deleteSymbol} className="flex-none w-14 h-10 md:h-12 bg-slate-700 hover:bg-slate-600 text-slate-200 rounded border-b-2 border-slate-800 active:border-b-0 active:translate-y-[2px] flex items-center justify-center"><Icons.Backspace /></button><button onClick={() => { setInputAnswer(""); setCursorPos(0); inputRef.current?.focus(); }} className="flex-none w-14 h-10 md:h-12 bg-red-900/30 hover:bg-red-900/50 text-red-400 rounded border-b-2 border-red-900/50 active:border-b-0 active:translate-y-[2px] text-xs font-bold">清除</button></div>
1246
- <div className="flex gap-1 justify-center">{['1','2','3','4','5','6','7','8','9','0'].map(num => <button key={num} onClick={() => insertSymbol(num)} className="flex-1 h-10 md:h-12 bg-[#1f1623] hover:bg-[#2d2033] active:bg-[#16101a] text-slate-400 hover:text-amber-200 rounded border-b-2 border-black active:border-b-0 active:translate-y-[2px] transition-all font-mono text-lg md:text-xl font-bold shadow-sm">{num}</button>)}</div>
1247
  </div>
1248
  </div>
1249
  </div>
 
259
  ];
260
 
261
  // --- Data: Categories & Problems ---
262
+ // Expanded Answer Bank to cover all valid commutative and sign variations
263
  const ALL_CATEGORIES = [
264
  {
265
  id: "basic",
 
267
  desc: "萬丈高樓平地起,找出共同的寶藏。",
268
  color: "from-blue-500 to-cyan-500",
269
  problems: [
270
+ {
271
+ id: 101, q: "3b^2 + 2b",
272
+ a: ["b(3b+2)", "(3b+2)b", "b(2+3b)", "(2+3b)b"],
273
+ h: "提出公因式 b"
274
+ },
275
+ {
276
+ id: 102, q: "5x^2 + 2x",
277
+ a: ["x(5x+2)", "(5x+2)x", "x(2+5x)", "(2+5x)x"],
278
+ h: "提出公因式 x"
279
+ },
280
+ {
281
+ id: 103, q: "6y^2 - 2y",
282
+ a: ["2y(3y-1)", "(3y-1)2y", "2y(-1+3y)", "(-1+3y)2y", "-2y(1-3y)", "(1-3y)(-2y)"],
283
+ h: "係數 6 和 2 都有公因數 2"
284
+ },
285
+ {
286
+ id: 104, q: "4x^2 - 8x",
287
+ a: ["4x(x-2)", "x(4x-8)", "4(x^2-2x)", "2x(2x-4)", "2(2x^2-4x)", "4x(-2+x)", "(-2+x)4x", "(4x-8)x", "(x-2)4x", "-4x(2-x)"],
288
+ h: "提出 4x"
289
+ },
290
+ {
291
+ id: 105, q: "ax + bx",
292
+ a: ["x(a+b)", "(a+b)x", "x(b+a)", "(b+a)x"],
293
+ h: "最簡單的提公因式"
294
+ },
295
+ {
296
+ id: 106, q: "3x^2 + 6x",
297
+ a: ["3x(x+2)", "x(3x+6)", "3(x^2+2x)", "3x(2+x)", "(x+2)3x", "(3x+6)x", "(2+x)3x"],
298
+ h: "提出 3x"
299
+ }
300
  ]
301
  },
302
  {
 
305
  desc: "分進合擊!尋找隱藏的括號。",
306
  color: "from-emerald-500 to-teal-500",
307
  problems: [
308
+ {
309
+ id: 201, q: "2x(x-2) + 5(x-2)",
310
+ a: ["(x-2)(2x+5)", "(2x+5)(x-2)", "(x-2)(5+2x)", "(5+2x)(x-2)", "(-x+2)(-2x-5)", "(-2x-5)(-x+2)", "-(2-x)(2x+5)", "-(2x+5)(2-x)"],
311
+ h: " (x-2) 當作一個整體"
312
+ },
313
+ {
314
+ id: 202, q: "x(3x-7) + 4(3x-7)",
315
+ a: ["(3x-7)(x+4)", "(x+4)(3x-7)", "(3x-7)(4+x)", "(4+x)(3x-7)", "(7-3x)(-x-4)", "(-x-4)(7-3x)", "-(7-3x)(x+4)", "-(x+4)(7-3x)"],
316
+ h: "提出 (3x-7)"
317
+ },
318
+ {
319
+ id: 203, q: "a(x+y) + b(x+y)",
320
+ a: ["(x+y)(a+b)", "(a+b)(x+y)", "(y+x)(a+b)", "(x+y)(b+a)", "(a+b)(y+x)", "(b+a)(x+y)", "(b+a)(y+x)"],
321
+ h: "都有 (x+y)"
322
+ },
323
+ {
324
+ id: 204, q: "x^2 + 2x + xy + 2y",
325
+ a: ["(x+2)(x+y)", "(x+y)(x+2)", "(2+x)(y+x)", "(x+y)(2+x)", "(y+x)(x+2)", "(y+x)(2+x)", "(2+x)(x+y)"],
326
+ h: "前兩項一組,後兩項一組"
327
+ },
328
+ {
329
+ id: 205, q: "ac + ad + bc + bd",
330
+ a: ["(c+d)(a+b)", "(a+b)(c+d)", "(d+c)(a+b)", "(c+d)(b+a)", "(a+b)(d+c)", "(b+a)(c+d)", "(b+a)(d+c)"],
331
+ h: "分組:a(c+d) + b(c+d)"
332
+ },
333
+ {
334
+ id: 206, q: "3x(2x-5) + (2x-5)(x+8)",
335
+ a: ["(2x-5)(4x+8)", "4(2x-5)(x+2)", "(4x+8)(2x-5)", "(2x-5)(8+4x)", "4(2x-5)(2+x)", "4(x+2)(2x-5)", "4(2+x)(2x-5)", "(5-2x)(-4x-8)", "-(5-2x)(4x+8)"],
336
+ h: "合併剩下的:3x + (x+8)"
337
+ }
338
  ]
339
  },
340
  {
 
343
  desc: "敵人的敵人就是朋友,善用負號。",
344
  color: "from-amber-500 to-orange-500",
345
  problems: [
346
+ {
347
+ id: 301, q: "x(2x-1) + (1-2x)",
348
+ a: ["(2x-1)(x-1)", "(x-1)(2x-1)", "(1-2x)(1-x)", "(1-x)(1-2x)", "-(1-2x)(x-1)", "-(x-1)(1-2x)", "-(2x-1)(1-x)"],
349
+ h: "(1-2x) 變號為 -(2x-1)"
350
+ },
351
+ {
352
+ id: 302, q: "x(a-b) + y(b-a)",
353
+ a: ["(a-b)(x-y)", "(x-y)(a-b)", "(b-a)(y-x)", "(y-x)(b-a)", "-(b-a)(x-y)", "-(x-y)(b-a)", "-(a-b)(y-x)"],
354
+ h: "(b-a) 變號為 -(a-b)"
355
+ },
356
+ {
357
+ id: 303, q: "x(2x-3) - 3(3-2x)",
358
+ a: ["(2x-3)(x+3)", "(x+3)(2x-3)", "(3-2x)(-x-3)", "(-x-3)(3-2x)", "(3+x)(2x-3)", "(2x-3)(3+x)", "-(3-2x)(x+3)"],
359
+ h: "減去負的變成加"
360
+ },
361
+ {
362
+ id: 304, q: "(x-y)^2 + 2(y-x)",
363
+ a: ["(x-y)(x-y-2)", "(x-y-2)(x-y)", "(y-x)(y-x+2)", "(y-x+2)(y-x)", "(x-y-2)(x-y)", "-(y-x)(x-y-2)", "(-x+y+2)(-x+y)"],
364
+ h: "(y-x) 提出負號變成 -(x-y)"
365
+ },
366
+ {
367
+ id: 305, q: "a(x-y) - b(y-x)",
368
+ a: ["(x-y)(a+b)", "(a+b)(x-y)", "(y-x)(-a-b)", "(-a-b)(y-x)", "(x-y)(b+a)", "(b+a)(x-y)", "-(y-x)(a+b)"],
369
+ h: "變號後提出 (x-y)"
370
+ }
371
  ]
372
  },
373
  {
 
376
  desc: "一加一減,成雙成對。",
377
  color: "from-rose-500 to-pink-500",
378
  problems: [
379
+ {
380
+ id: 401, q: "x^2 - 49",
381
+ a: ["(x+7)(x-7)", "(x-7)(x+7)", "(7+x)(x-7)", "(x-7)(7+x)", "(-x-7)(7-x)", "(7-x)(-x-7)", "-(7-x)(x+7)"],
382
+ h: "7 的平方是 49"
383
+ },
384
+ {
385
+ id: 402, q: "x^2 - 1",
386
+ a: ["(x+1)(x-1)", "(x-1)(x+1)", "(1+x)(x-1)", "(x-1)(1+x)", "(-x-1)(1-x)", "(1-x)(-x-1)", "-(1-x)(x+1)"],
387
+ h: "1 的平方還是 1"
388
+ },
389
+ {
390
+ id: 403, q: "4x^2 - 9",
391
+ a: ["(2x+3)(2x-3)", "(2x-3)(2x+3)", "(3+2x)(2x-3)", "(2x-3)(3+2x)", "(-2x-3)(3-2x)", "-(3-2x)(2x+3)"],
392
+ h: "(2x)^2 - 3^2"
393
+ },
394
+ {
395
+ id: 404, q: "25x^2 - 36y^2",
396
+ a: ["(5x+6y)(5x-6y)", "(5x-6y)(5x+6y)", "(6y+5x)(5x-6y)", "(5x-6y)(6y+5x)", "-(6y-5x)(5x+6y)"],
397
+ h: "(5x)^2 - (6y)^2"
398
+ },
399
+ {
400
+ id: 405, q: "49 - (y-1)^2",
401
+ a: [
402
+ // 基本正解: (8-y)(y+6) 及其變體
403
+ "(8-y)(y+6)", "(y+6)(8-y)",
404
+ "(6+y)(8-y)", "(8-y)(6+y)",
405
+ "(-y+8)(y+6)", "(y+6)(-y+8)",
406
+ "(-y+8)(6+y)", "(6+y)(-y+8)",
407
+
408
+ // 負負得正系列: (-y-6)(y-8)
409
+ "(-y-6)(y-8)", "(y-8)(-y-6)",
410
+ "(-6-y)(y-8)", "(y-8)(-6-y)",
411
+ "(-y-6)(-8+y)", "(-8+y)(-y-6)",
412
+
413
+ // 提出負號系列 1: -(y-8)(y+6)
414
+ "-(y-8)(y+6)", "-(y+6)(y-8)",
415
+ "-(y-8)(6+y)", "-(6+y)(y-8)",
416
+ "-(-8+y)(y+6)", "-(y+6)(-8+y)",
417
+ "-(-8+y)(6+y)", "-(6+y)(-8+y)",
418
+
419
+ // 提出負號系列 2: -(8-y)(-y-6)
420
+ "-(8-y)(-y-6)", "-(-y-6)(8-y)",
421
+ "-(8-y)(-6-y)", "-(-6-y)(8-y)"
422
+ ],
423
+ h: "A=7, B=(y-1)"
424
+ }
425
  ]
426
  },
427
  {
 
430
  desc: "頭平方,尾平方,2倍頭尾在中央。",
431
  color: "from-violet-500 to-purple-500",
432
  problems: [
433
+ {
434
+ id: 501, q: "x^2 + 2x + 1",
435
+ a: ["(x+1)^2", "(x+1)(x+1)", "(1+x)^2", "(1+x)(1+x)", "(-x-1)^2", "(-x-1)(-x-1)"],
436
+ h: "1 1^2,中間是 2*x*1"
437
+ },
438
+ {
439
+ id: 502, q: "x^2 + 8x + 16",
440
+ a: ["(x+4)^2", "(x+4)(x+4)", "(4+x)^2", "(4+x)(4+x)", "(-x-4)^2"],
441
+ h: "16 是 4^2"
442
+ },
443
+ {
444
+ id: 503, q: "x^2 - 4x + 4",
445
+ a: ["(x-2)^2", "(2-x)^2", "(x-2)(x-2)", "(2-x)(2-x)", "-(x-2)(2-x)"],
446
+ h: "中間是負號"
447
+ },
448
+ {
449
+ id: 504, q: "x^2 - 10x + 25",
450
+ a: ["(x-5)^2", "(5-x)^2", "(x-5)(x-5)", "(5-x)(5-x)"],
451
+ h: "25 是 5^2"
452
+ },
453
+ {
454
+ id: 505, q: "16x^2 - 24x + 9",
455
+ a: ["(4x-3)^2", "(3-4x)^2", "(4x-3)(4x-3)", "(3-4x)(3-4x)"],
456
+ h: "(4x)^2 和 3^2"
457
+ }
458
  ]
459
  }
460
  ];
 
547
  const [manualRoomInput, setManualRoomInput] = useState(""); // 新增:手動輸入的狀態
548
  const [roomHistory, setRoomHistory] = useState([]); // 新增:房間歷史紀錄
549
  const [selectedStudent, setSelectedStudent] = useState(null);
550
+ const [showLargeQr, setShowLargeQr] = useState(false); // 新增:控制大圖顯示狀態
551
 
552
  // 新增:載入歷史紀錄
553
  useEffect(() => {
 
584
  }
585
  };
586
 
587
+ // 新增:產生帶參數的網址與 QR Code 連結 (size 參數可調整)
588
+ const getQrUrl = (size = "150x150") => {
589
  const baseUrl = window.location.origin + window.location.pathname;
590
  const fullUrl = `${baseUrl}?room=${roomCode}`;
591
+ return `https://api.qrserver.com/v1/create-qr-code/?size=${size}&data=${encodeURIComponent(fullUrl)}`;
592
  };
593
 
594
  // Filter data by room
 
629
 
630
  return (
631
  <div className="min-h-screen flex flex-col p-4 md:p-6 relative bg-slate-900 text-slate-200 font-sans">
632
+ {/* Large QR Modal */}
633
+ {showLargeQr && (
634
+ <div className="fixed inset-0 z-[100] flex items-center justify-center bg-black/90 backdrop-blur-md p-4" onClick={() => setShowLargeQr(false)}>
635
+ <div className="bg-white p-8 rounded-3xl animate-pop text-center max-w-lg w-full shadow-2xl relative" onClick={e => e.stopPropagation()}>
636
+ <button onClick={() => setShowLargeQr(false)} className="absolute top-4 right-4 text-slate-400 hover:text-slate-600">
637
+ <Icons.Check className="rotate-45" />
638
+ </button>
639
+ <h3 className="text-2xl font-bold text-slate-800 mb-2">加入房間</h3>
640
+ <div className="text-6xl font-mono font-bold text-amber-600 tracking-widest mb-6 select-all">{roomCode}</div>
641
+ <div className="bg-white p-2 rounded-xl inline-block border-4 border-slate-100 mb-4">
642
+ <img src={getQrUrl("500x500")} alt="Large Room QR" className="w-full max-w-[400px] h-auto aspect-square object-contain" />
643
+ </div>
644
+ <p className="text-slate-500 font-bold">請學生掃描 QR Code 或輸入上方代碼</p>
645
+ <div className="text-slate-400 text-xs mt-2">點擊任意處關閉</div>
646
+ </div>
647
+ </div>
648
+ )}
649
+
650
  <div className="flex justify-between items-center mb-6">
651
  <h2 className="text-2xl font-bold flex items-center gap-2 text-amber-400"><Icons.Teacher /> 教師管理後台</h2>
652
  <button onClick={onClose} className="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-lg">返回遊戲</button>
 
661
  <div className="text-xs text-slate-400 uppercase tracking-widest mb-2">目前房間代碼</div>
662
  {roomCode ? (
663
  <>
664
+ <div className="text-4xl font-mono font-bold text-yellow-400 tracking-widest mb-4 select-all">{roomCode}</div>
665
+ <div
666
+ className="bg-white p-2 rounded-lg inline-block mx-auto cursor-zoom-in hover:scale-105 transition-transform group relative"
667
+ onClick={() => setShowLargeQr(true)}
668
+ >
669
+ <img src={getQrUrl("150x150")} alt="Room QR" className="w-32 h-32" />
670
+ <div className="absolute inset-0 flex items-center justify-center bg-black/0 group-hover:bg-black/20 transition-colors rounded-lg">
671
+ <span className="opacity-0 group-hover:opacity-100 bg-black/70 text-white text-xs px-2 py-1 rounded backdrop-blur-sm pointer-events-none">點擊放大</span>
672
+ </div>
673
  </div>
674
  <div className="mt-2 text-xs text-slate-500">學生掃描後可自動填入代碼</div>
675
  </>
 
1232
  if (navigator.vibrate) navigator.vibrate(200);
1233
  }
1234
  };
1235
+
1236
  const insertSymbol = (symbol) => {
1237
+ const input = inputRef.current;
1238
+
1239
+ // Determine insertion point: prefer current cursor, fallback to end if lost
1240
+ let start = cursorPos;
1241
+ let end = cursorPos;
1242
+
1243
+ // If input is actually focused in DOM, use its real selection properties
1244
+ if (input && document.activeElement === input) {
1245
+ start = input.selectionStart;
1246
+ end = input.selectionEnd;
1247
+ } else if (input) {
1248
+ // If not focused (e.g. lost focus to button click), assume state cursorPos is valid
1249
+ // or default to end of string if unsure
1250
+ if (start > inputAnswer.length) start = inputAnswer.length;
1251
+ end = start;
1252
+ }
1253
+
1254
+ const text = inputAnswer;
1255
  const newText = text.substring(0, start) + symbol + text.substring(end);
1256
+ const newPos = start + symbol.length;
1257
+
1258
+ setInputAnswer(newText);
1259
+ setCursorPos(newPos);
1260
+
1261
+ // Force focus and set selection after render cycle
1262
+ setTimeout(() => {
1263
+ if (inputRef.current) {
1264
+ inputRef.current.focus();
1265
+ inputRef.current.setSelectionRange(newPos, newPos);
1266
+ }
1267
+ }, 0);
1268
  };
1269
+
1270
  const deleteSymbol = () => {
1271
+ const input = inputRef.current;
1272
+
1273
+ let start = cursorPos;
1274
+ let end = cursorPos;
1275
+
1276
+ if (input && document.activeElement === input) {
1277
+ start = input.selectionStart;
1278
+ end = input.selectionEnd;
1279
+ }
1280
+
1281
+ const text = inputAnswer;
1282
+ let newText = text;
1283
+ let newPos = start;
1284
+
1285
+ if (start !== end) {
1286
+ newText = text.substring(0, start) + text.substring(end);
1287
+ newPos = start;
1288
+ } else if (start > 0) {
1289
+ newText = text.substring(0, start - 1) + text.substring(end);
1290
+ newPos = start - 1;
1291
+ }
1292
+
1293
+ setInputAnswer(newText);
1294
+ setCursorPos(newPos);
1295
+
1296
+ setTimeout(() => {
1297
+ if (inputRef.current) {
1298
+ inputRef.current.focus();
1299
+ inputRef.current.setSelectionRange(newPos, newPos);
1300
+ }
1301
+ }, 0);
1302
+ };
1303
+
1304
+ const handleSelect = (e) => {
1305
+ // Sync React state with DOM state on user interaction
1306
+ setCursorPos(e.target.selectionStart);
1307
  };
1308
+
1309
  const renderMath = (text) => <span className="font-math text-3xl md:text-4xl">{text.split(/(\^[0-9]+)/g).map((part, i) => part.startsWith("^") ? <sup key={i} className="text-xs font-bold relative -top-3 left-0.5" style={{verticalAlign: 'super'}}>{part.substring(1)}</sup> : part)}</span>;
1310
  const getDynamicKeyboard = (question) => { const vars = new Set(); (question.match(/[a-z]/g) || []).forEach(m => vars.add(m)); if (vars.size === 0) vars.add('x'); return Array.from(vars).sort(); };
1311
 
 
1454
  </div>
1455
  <div className={`absolute bottom-0 left-0 right-0 bg-[#16101a] border-t border-slate-800 p-2 z-50 transition-transform duration-500 ${chestOpen ? 'translate-y-full' : 'translate-y-0'}`}>
1456
  <div className="max-w-2xl mx-auto">
1457
+ <div className="flex gap-1 mb-1.5 justify-center">{[...dynamicVars, '+', '-', '(', ')', '^2'].map(sym => <button onMouseDown={(e) => e.preventDefault()} onTouchStart={(e) => e.preventDefault()} key={sym} onClick={() => insertSymbol(sym)} className="flex-1 h-10 md:h-12 bg-[#2d2033] hover:bg-[#3e2d45] active:bg-[#251a2b] text-amber-100 rounded border-b-2 border-[#1a101f] active:border-b-0 active:translate-y-[2px] transition-all font-mono text-lg md:text-xl font-bold shadow-sm">{sym === '^2' ? <span>^<sup className="text-xs font-bold relative -top-1">2</sup></span> : sym}</button>)}<button onMouseDown={(e) => e.preventDefault()} onTouchStart={(e) => e.preventDefault()} onClick={deleteSymbol} className="flex-none w-14 h-10 md:h-12 bg-slate-700 hover:bg-slate-600 text-slate-200 rounded border-b-2 border-slate-800 active:border-b-0 active:translate-y-[2px] flex items-center justify-center"><Icons.Backspace /></button><button onMouseDown={(e) => e.preventDefault()} onTouchStart={(e) => e.preventDefault()} onClick={() => { setInputAnswer(""); setCursorPos(0); inputRef.current?.focus(); }} className="flex-none w-14 h-10 md:h-12 bg-red-900/30 hover:bg-red-900/50 text-red-400 rounded border-b-2 border-red-900/50 active:border-b-0 active:translate-y-[2px] text-xs font-bold">清除</button></div>
1458
+ <div className="flex gap-1 justify-center">{['1','2','3','4','5','6','7','8','9','0'].map(num => <button onMouseDown={(e) => e.preventDefault()} onTouchStart={(e) => e.preventDefault()} key={num} onClick={() => insertSymbol(num)} className="flex-1 h-10 md:h-12 bg-[#1f1623] hover:bg-[#2d2033] active:bg-[#16101a] text-slate-400 hover:text-amber-200 rounded border-b-2 border-black active:border-b-0 active:translate-y-[2px] transition-all font-mono text-lg md:text-xl font-bold shadow-sm">{num}</button>)}</div>
1459
  </div>
1460
  </div>
1461
  </div>