Spaces:
Running
Running
| <html lang="zh-TW"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>圓周率之神的眷顧 - 信徒儀表板</title> | |
| <style> | |
| :root { | |
| --primary: #d4af37; | |
| --primary-hover: #b49126; | |
| --bg-overlay: rgba(20, 15, 10, 0.85); | |
| --panel: rgba(45, 35, 25, 0.7); | |
| --border: rgba(212, 175, 55, 0.3); | |
| --accent: #fcd34d; | |
| } | |
| * { box-sizing: border-box; } | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Inter', system-ui, -apple-system, sans-serif; | |
| background: var(--bg-overlay) url('pigod.png') center center / cover fixed; | |
| background-blend-mode: overlay; | |
| color: white; | |
| min-height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .container { | |
| width: 100%; | |
| max-width: 400px; | |
| background: rgba(255, 255, 255, 0.05); | |
| backdrop-filter: blur(15px); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 1.5rem; | |
| padding: 2rem; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); | |
| animation: slideUp 0.6s cubic-bezier(0.16, 1, 0.3, 1); | |
| } | |
| @keyframes slideUp { | |
| from { opacity: 0; transform: translateY(30px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| h1 { | |
| text-align: center; | |
| font-size: 1.5rem; | |
| margin-top: 0; | |
| margin-bottom: 0.5rem; | |
| } | |
| .room-badge { | |
| display: block; | |
| text-align: center; | |
| font-size: 0.875rem; | |
| color: #cbd5e1; | |
| margin-bottom: 2rem; | |
| padding: 0.5rem; | |
| background: rgba(0,0,0,0.2); | |
| border-radius: 0.5rem; | |
| } | |
| .form-group { | |
| margin-bottom: 1.5rem; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| font-weight: 500; | |
| color: #cbd5e1; | |
| } | |
| input { | |
| width: 100%; | |
| padding: 0.875rem 1rem; | |
| background: rgba(0, 0, 0, 0.2); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| border-radius: 0.75rem; | |
| color: white; | |
| font-size: 1rem; | |
| transition: all 0.3s ease; | |
| } | |
| input:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.3); | |
| } | |
| button { | |
| background: linear-gradient(135deg, var(--primary), var(--primary-hover)); | |
| color: #111; | |
| border: 1px solid rgba(255,255,255,0.2); | |
| padding: 1rem; | |
| font-size: 1.25rem; | |
| border-radius: 0.75rem; | |
| cursor: pointer; | |
| font-weight: bold; | |
| width: 100%; | |
| transition: all 0.2s; | |
| margin-top: 1rem; | |
| box-shadow: 0 4px 6px rgba(0,0,0,0.5); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 10px 15px -3px var(--border); | |
| } | |
| button:active { | |
| transform: translateY(0); | |
| } | |
| .spinner { | |
| border: 3px solid rgba(255,255,255,0.3); | |
| border-top: 3px solid white; | |
| border-radius: 50%; | |
| width: 24px; | |
| height: 24px; | |
| animation: spin 1s linear infinite; | |
| display: none; | |
| } | |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } | |
| .success-state { | |
| display: none; | |
| text-align: center; | |
| } | |
| .success-icon { | |
| font-size: 4rem; | |
| margin-bottom: 1rem; | |
| animation: popIn 0.5s cubic-bezier(0.16, 1, 0.3, 1); | |
| } | |
| @keyframes popIn { | |
| 0% { transform: scale(0); } | |
| } | |
| .waiting-state, .result-state { display: none; text-align: center; } | |
| .rocket-icon { font-size: 4rem; animation: float 2s ease-in-out infinite; margin-bottom: 1rem;} | |
| @keyframes float { | |
| 0% { transform: translateY(0px) rotate(0deg); } | |
| 50% { transform: translateY(-15px) scale(1.1) rotate(5deg); } | |
| 100% { transform: translateY(0px) rotate(0deg); } | |
| } | |
| .result-box { | |
| background: rgba(212, 175, 55, 0.2); | |
| border: 2px solid var(--primary); | |
| padding: 1.5rem; | |
| border-radius: 1rem; | |
| margin-top: 1.5rem; | |
| } | |
| .result-box.error { | |
| background: rgba(239, 68, 68, 0.2); | |
| border-color: #ef4444; | |
| } | |
| .final-val { font-size: clamp(1.5rem, 8vw, 2.5rem); font-weight: bold; color: var(--accent); margin: 0.5rem 0; white-space: nowrap; } | |
| .trivia-box { | |
| margin-top: 1.5rem; | |
| padding: 1rem; | |
| background: rgba(0,0,0,0.3); | |
| border: 1px dashed var(--border); | |
| border-radius: 0.75rem; | |
| text-align: left; | |
| font-size: 0.9rem; | |
| line-height: 1.4; | |
| color: #cbd5e1; | |
| animation: fadeIn 0.5s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container" id="mainContainer"> | |
| <div id="inputForm"> | |
| <h1 style="color: var(--accent); text-align: center; margin-top: 0; font-size: 2rem; text-shadow: 0 2px 4px rgba(0,0,0,0.8);">祈求眷顧</h1> | |
| <p style="text-align: center; color: #d1d5db; margin-bottom: 2rem;">填寫資訊,將你的號碼獻給圓周率之神吧!</p> | |
| <span class="room-badge" id="roomBadge">載入中...</span> | |
| <div class="form-group"> | |
| <label for="studentName">信徒尊姓大名</label> | |
| <input type="text" id="studentName" placeholder="例如:王小明" required> | |
| </div> | |
| <div class="form-group"> | |
| <label id="luckyNumberLabel" for="luckyNumber">你的幸運數字 (請填滿 X 位數)</label> | |
| <!-- 注意:這裡改用 text,避免手機輸入前導零 (0007) 被瀏覽器吃掉 --> | |
| <input type="text" id="luckyNumber" inputmode="numeric" placeholder="載入中..." required> | |
| <small id="luckyNumberHint" style="color:#9ca3af; display:block; margin-top:0.5rem; font-size:0.85rem;"></small> | |
| </div> | |
| <button id="submitBtn"> | |
| <span id="btnText">送出數字</span> | |
| <div class="spinner" id="btnSpinner"></div> | |
| </button> | |
| <div class="trivia-box"> | |
| <strong style="color: var(--accent);">💡 圓周率冷知識</strong> | |
| <p id="triviaInput" style="margin: 0.5rem 0 0 0;"></p> | |
| </div> | |
| </div> | |
| <div class="success-state" id="successState"> | |
| <div class="success-icon">✅</div> | |
| <h2 style="color: var(--accent);">獻祭成功!</h2> | |
| <p>神位保留完畢,請仰望老師的大螢幕<br>準備接受神的洗禮。</p> | |
| <div class="trivia-box"> | |
| <strong style="color: var(--accent);">💡 圓周率冷知識</strong> | |
| <p id="triviaSuccess" style="margin: 0.5rem 0 0 0;"></p> | |
| </div> | |
| </div> | |
| <div class="waiting-state" id="waitingState"> | |
| <div class="rocket-icon">✨</div> | |
| <h2 style="color: var(--primary);">神諭下達中...</h2> | |
| <p>正在兩億位數的神秘軌跡中<br>尋找你的真理落點!</p> | |
| <div class="trivia-box"> | |
| <strong style="color: var(--accent);">💡 圓周率冷知識</strong> | |
| <p id="triviaWaiting" style="margin: 0.5rem 0 0 0;"></p> | |
| </div> | |
| </div> | |
| <div class="result-state" id="resultState"> | |
| <h2 style="color: var(--accent);">📜 神諭揭曉 📜</h2> | |
| <div id="resultBox" class="result-box"> | |
| <p style="margin:0; font-size:1.2rem;">神之眷顧降臨於:</p> | |
| <div class="final-val" id="finalValText"></div> | |
| <div id="rankContainer" style="display:none; margin-top: 1rem; padding-top: 1rem; border-top: 1px dashed rgba(255,255,255,0.2);"> | |
| <p style="margin:0; font-size:1.2rem; color: #d1d5db;">您的總排名為:</p> | |
| <div style="font-size: 2rem; font-weight: bold; color: #fde047; text-shadow: 0 2px 4px rgba(0,0,0,0.8);">第 <span id="rankValText"></span> 名</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Firebase SDK (Web Modules) --> | |
| <script type="module"> | |
| import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.1/firebase-app.js"; | |
| import { getFirestore, doc, collection, addDoc, serverTimestamp, onSnapshot } from "https://www.gstatic.com/firebasejs/10.8.1/firebase-firestore.js"; | |
| const firebaseConfig = { | |
| apiKey: "AIzaSyA2fxxaGAdWSR_QOH2Hm92kttGZmLDH8-w", | |
| authDomain: "pi-search-89a08.firebaseapp.com", | |
| projectId: "pi-search-89a08", | |
| storageBucket: "pi-search-89a08.firebasestorage.app", | |
| messagingSenderId: "1003005654079", | |
| appId: "1:1003005654079:web:636235436f432376d8748a" | |
| }; | |
| const piTriviaList = [ | |
| "宇宙級精準: NASA 計算整個可觀測宇宙的大小,只要用到 π 小數點後第 39 位,誤差就小於一顆氫原子!", | |
| "你的密碼都在裡面: π 是無限不循環小數,包含所有數字組合。你的生日、電話、未來的密碼,絕對都藏在裡面!", | |
| "神秘的「費曼點」: 在小數點後第 762 位,會突然出現連續六個「9」(999999)!", | |
| "最悲劇的手算: 1873年英國數學家手算了 15 年到第 707 位,結果他在第 527 位就算錯了,後面全部做白工!", | |
| "刻在墓碑的榮耀: 16世紀數學家科伊倫花一輩子只算到第 35 位,但他超驕傲,死後把這 35 個數字刻在了墓碑上。", | |
| "阿基米德的遺言: 古希臘大師阿基米德被敵軍殺死前,正在沙地上算圓周率,他最後大喊:「別踩壞我的圓!」", | |
| "祖沖之超前部署: 西元5世紀,中國數學家祖沖之就算出 π = 3.1415926,這項神級紀錄領先了世界近 900 年!", | |
| "「π」其實很年輕: 人類研究圓周率快 4000 年,但用希臘字母「π」來代表它,大約只有 300 年的歷史。", | |
| "天才的生與死: 3月14日(3.14)剛好是愛因斯坦的生日,也是黑洞大師霍金逝世的日子。", | |
| "鏡子裡的派: 寫下「3.14」拿去照鏡子,字跡會神奇地變成英文的「PIE(派)」!", | |
| "「16」的巧合: 希臘字母「π」是字母表第 16 個;英文字母的「P」也剛剛好是第 16 個!" | |
| ]; | |
| let currentTriviaIdx = Math.floor(Math.random() * piTriviaList.length); | |
| document.getElementById('triviaInput').innerText = piTriviaList[currentTriviaIdx]; | |
| // 取得網址的 room 參數 | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const roomCode = urlParams.get('room') || '未指定教室'; | |
| document.getElementById('roomBadge').innerHTML = `教室代碼:<strong>${roomCode}</strong>`; | |
| let db = null; | |
| try { | |
| if(firebaseConfig.apiKey !== "YOUR_API_KEY") { | |
| const app = initializeApp(firebaseConfig); | |
| db = getFirestore(app); | |
| } | |
| } catch(e) { console.error("Firebase 初始化失敗:", e); } | |
| // 老師設定的長度 | |
| let requiredLength = 0; | |
| // 監聽房間設定狀態 | |
| if (db && roomCode !== '未指定教室') { | |
| const roomRef = doc(db, "rooms", roomCode); | |
| onSnapshot(roomRef, (docSnap) => { | |
| if(docSnap.exists()) { | |
| const data = docSnap.data(); | |
| if(data.digitLength && requiredLength === 0) { | |
| requiredLength = data.digitLength; | |
| document.getElementById('luckyNumberLabel').innerText = `你的幸運數字 (請填滿 ${requiredLength} 位數)`; | |
| document.getElementById('luckyNumber').placeholder = `例如:${'0'.repeat(requiredLength - 1)}4`; | |
| document.getElementById('luckyNumberHint').innerText = `老師已設定尋找 ${requiredLength} 位數,開頭可以是 0 (如 ${'0'.repeat(requiredLength - 1)}4)`; | |
| document.getElementById('luckyNumber').maxLength = requiredLength; | |
| } | |
| // 如果老師按下了開始搜尋 | |
| if(data.isSearching && document.getElementById('successState').style.display === 'block') { | |
| document.getElementById('successState').style.display = 'none'; | |
| document.getElementById('waitingState').style.display = 'block'; | |
| } | |
| } | |
| }); | |
| } | |
| const submitBtn = document.getElementById('submitBtn'); | |
| const btnText = document.getElementById('btnText'); | |
| const btnSpinner = document.getElementById('btnSpinner'); | |
| submitBtn.addEventListener('click', async () => { | |
| if(firebaseConfig.apiKey === "YOUR_API_KEY"){ | |
| alert("系統錯誤:老師尚未設定 Firebase!"); | |
| return; | |
| } | |
| const name = document.getElementById('studentName').value.trim(); | |
| const number = document.getElementById('luckyNumber').value.trim(); | |
| if (!name || !number) { | |
| alert('請填寫完整姓名與幸運數字!'); | |
| return; | |
| } | |
| if (requiredLength > 0 && number.length !== requiredLength) { | |
| alert(`請輸入剛好 ${requiredLength} 位的代碼喔!\n不夠位數請在前面補 0。`); | |
| return; | |
| } | |
| // UI 狀態:載入中 | |
| submitBtn.disabled = true; | |
| btnText.style.display = 'none'; | |
| btnSpinner.style.display = 'block'; | |
| try { | |
| // 將資料寫入 Firebase 中對應教室的 Subcollection | |
| const studentsRef = collection(db, "rooms", roomCode, "students"); | |
| const docRef = await addDoc(studentsRef, { | |
| name: name, | |
| number: number, | |
| timestamp: serverTimestamp() | |
| }); | |
| // 更新冷知識 | |
| let nextTriviaIdx; | |
| do { | |
| nextTriviaIdx = Math.floor(Math.random() * piTriviaList.length); | |
| } while (nextTriviaIdx === currentTriviaIdx); | |
| document.getElementById('triviaSuccess').innerText = piTriviaList[nextTriviaIdx]; | |
| document.getElementById('triviaWaiting').innerText = piTriviaList[nextTriviaIdx]; | |
| // UI 狀態:成功 | |
| document.getElementById('inputForm').style.display = 'none'; | |
| document.getElementById('successState').style.display = 'block'; | |
| // 監聽自己的結果是否出爐 | |
| onSnapshot(doc(db, "rooms", roomCode, "students", docRef.id), (studentSnap) => { | |
| if (studentSnap.exists()) { | |
| const sData = studentSnap.data(); | |
| if (sData.status === 'done') { | |
| document.getElementById('waitingState').style.display = 'none'; | |
| document.getElementById('successState').style.display = 'none'; | |
| document.getElementById('resultState').style.display = 'block'; | |
| const rb = document.getElementById('resultBox'); | |
| if (typeof sData.resultVal === 'number') { | |
| document.getElementById('finalValText').innerText = sData.finalOutput; | |
| // 若 Firebase 已寫入 rank 資料,便可顯示總排名 | |
| if (sData.rank) { | |
| document.getElementById('rankContainer').style.display = 'block'; | |
| document.getElementById('rankValText').innerText = sData.rank; | |
| } else { | |
| document.getElementById('rankContainer').style.display = 'none'; | |
| } | |
| } else { | |
| rb.classList.add('error'); | |
| document.getElementById('finalValText').innerText = "未尋獲 QQ"; | |
| rb.querySelector('p').innerText = "可惜了,這 2 億位數中:"; | |
| } | |
| } | |
| } else { | |
| // 如果被刪除 (如老師剔除) | |
| alert("老師已將您從名單中剔除或重置。"); | |
| window.location.reload(); | |
| } | |
| }); | |
| } catch (error) { | |
| console.error("寫入發生錯誤: ", error); | |
| alert("發生錯誤,請稍後再試!\n" + error.message); | |
| // 還原 UI | |
| submitBtn.disabled = false; | |
| btnText.style.display = 'block'; | |
| btnSpinner.style.display = 'none'; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |