Spaces:
Running
Running
Download guest.html from Lashtw/worldcloud: direct link, hf CLI and curl.
- Browser
- Download file 6.37 kB
-
https://huggingface.co/spaces/Lashtw/worldcloud/resolve/main/guest.html
- Command line
-
hf download hf://spaces/Lashtw/worldcloud/guest.html
-
curl -L -o guest.html https://huggingface.co/spaces/Lashtw/worldcloud/resolve/main/guest.html
6.37 kB
| <html lang="zh-Hant"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>即時互動文字雲 - 參與者</title> | |
| <style> | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: 'Noto Sans TC', 'Microsoft JhengHei', sans-serif; | |
| background: linear-gradient(160deg, #FEF3C7, #FFEDD5); | |
| min-height: 100vh; display: flex; align-items: center; justify-content: center; | |
| padding: 20px; | |
| } | |
| .card { | |
| background: #fff; padding: 36px 40px; border-radius: 24px; | |
| width: 100%; max-width: 460px; | |
| box-shadow: 0 20px 50px rgba(0,0,0,.12); text-align: center; | |
| } | |
| h1 { color: #F97316; font-size: 24px; margin-bottom: 6px; } | |
| .sub { color: #64748B; font-size: 14px; margin-bottom: 26px; } | |
| label { display: block; text-align: left; font-size: 14px; color: #475569; margin-bottom: 6px; } | |
| input[type=text] { | |
| width: 100%; padding: 11px 13px; font-size: 16px; | |
| border: 1px solid #FDBA74; border-radius: 10px; outline: none; margin-bottom: 20px; | |
| } | |
| input:focus { border-color: #F97316; } | |
| .btn { | |
| width: 100%; padding: 12px; font-size: 17px; font-weight: 700; | |
| border: none; border-radius: 12px; background: #F97316; color: #fff; cursor: pointer; | |
| } | |
| .btn:disabled { background: #CBD5E1; cursor: not-allowed; } | |
| /* 加入後的輸入狀態 */ | |
| #input-section { display: none; } | |
| #question-display { | |
| display: none; background: #FFF7ED; border: 1px solid #FED7AA; border-radius: 12px; | |
| padding: 12px 15px; margin-bottom: 22px; font-size: 16px; color: #9A3412; | |
| } | |
| #lock-notice { | |
| display: none; background: #FEE2E2; border: 1px solid #FECACA; color: #991B1B; | |
| border-radius: 12px; padding: 12px; margin-bottom: 22px; font-size: 15px; | |
| } | |
| #you-sent { | |
| display: block; margin-top: 16px; font-size: 13px; color: #16A34A; min-height: 18px; | |
| } | |
| #id-suffix { margin-top: 14px; font-size: 12px; color: #94A3B8; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="card" id="join-card"> | |
| <h1>☁️ 互動文字雲</h1> | |
| <div class="sub">加入並送出你的想法</div> | |
| <!-- 加入階段 --> | |
| <div id="join-section"> | |
| <label>輸入房間代碼</label> | |
| <input type="text" id="room-input" maxlength="4" placeholder="例:1234" inputmode="numeric"> | |
| <button class="btn" id="btn-join">加入</button> | |
| </div> | |
| <!-- 已加入,送詞彙 --> | |
| <div id="input-section"> | |
| <div id="question-display"></div> | |
| <div id="lock-notice">🔒 老師已暫停接收詞彙,請稍候。</div> | |
| <label>輸入你的詞彙 / 想法</label> | |
| <input type="text" id="word-input" placeholder="輸入詞彙或 $數學式$…" autocomplete="off"> | |
| <button class="btn" id="btn-send">送出</button> | |
| <span id="you-sent"></span> | |
| <div id="id-suffix"></div> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.12.2/firebase-app.js'; | |
| import { getFirestore, doc, getDoc, onSnapshot, updateDoc, increment } from 'https://www.gstatic.com/firebasejs/10.12.2/firebase-firestore.js'; | |
| const firebaseConfig = { | |
| ApiKey: "AIzaSyAcIQYst9gFRyE2nCqCwNgCL3LG2VkBXbM", | |
| authDomain: "factor-529c9.firebaseapp.com", | |
| projectId: "factor-529c9", | |
| storageBucket: "factor-529c9.firebasestorage.app", | |
| messagingSenderId: "575829635896", | |
| appId: "1:575829635896:web:a65b6edd9d163375b087cd" | |
| }; | |
| const app = initializeApp(firebaseConfig); | |
| const db = getFirestore(app); | |
| let roomCode = null; | |
| let roomRef = null; | |
| let participantId = Date.now().toString(36); | |
| const qs = new URLSearchParams(location.search); | |
| const qRoom = qs.get('room'); | |
| if (qRoom) { document.getElementById('room-input').value = qRoom; join(); } | |
| // ---- 加入 ---- | |
| document.getElementById('btn-join').addEventListener('click', join); | |
| async function join() { | |
| const value = document.getElementById('room-input').value.trim(); | |
| if (!/^\d{4}$/.test(value)) { alert('請輸入 4 位數房間代碼'); return; } | |
| roomCode = value; | |
| roomRef = doc(db, 'rooms', roomCode); | |
| const snap = await getDoc(roomRef); | |
| if (!snap.exists()) { alert('找不到此房間,請確認代碼'); return; } | |
| document.getElementById('join-section').style.display = 'none'; | |
| document.getElementById('input-section').style.display = 'block'; | |
| document.getElementById('id-suffix').textContent = '參與者代號:' + participantId; | |
| initListeners(); | |
| initInput(); | |
| } | |
| // ---- 監聽房間狀態 ---- | |
| function initListeners() { | |
| onSnapshot(roomRef, (snap) => { | |
| const data = snap.data(); | |
| if (!data) return; | |
| const qEl = document.getElementById('question-display'); | |
| if (data.question) { qEl.textContent = '📌 ' + data.question; qEl.style.display = 'block'; } | |
| else { qEl.style.display = 'none'; } | |
| const locked = data.locked || false; | |
| document.getElementById('lock-notice').style.display = locked ? 'block' : 'none'; | |
| document.getElementById('word-input').disabled = locked; | |
| document.getElementById('btn-send').disabled = locked; | |
| }); | |
| } | |
| // ---- 送出詞彙 ---- | |
| function initInput() { | |
| document.getElementById('btn-send').addEventListener('click', submit); | |
| document.getElementById('word-input').addEventListener('keydown', e => { | |
| if (e.key === 'Enter') submit(); | |
| }); | |
| } | |
| async function submit() { | |
| const word = document.getElementById('word-input').value.trim(); | |
| if (!word) return; | |
| const snap = await getDoc(roomRef); | |
| const data = snap.data(); | |
| if (data.locked) { alert('老師已鎖定,暫停接收'); return; } | |
| // 多重送出關閉時,避免重複送出同一詞(可依需求調整) | |
| const alreadySent = data.sentBy && data.sentBy[participantId]; | |
| if (!data.allowMultiple && alreadySent) { | |
| alert('您已送出過詞彙,老師未開啟多次送出。'); | |
| return; | |
| } | |
| await updateDoc(roomRef, { | |
| [`words.${word}`]: increment(1), | |
| [`sentBy.${participantId}`]: true // 記錄已送出 | |
| }); | |
| document.getElementById('you-sent').textContent = '已送出:「' + word + '」✓'; | |
| document.getElementById('word-input').value = ''; | |
| } | |
| </script> | |
| <script> | |
| window.MathJax = { | |
| tex: { inlineMath: [['$','$'],['\\(','\\)']], displayMath: [['$$','$$'],['\\[','\\]']], processEscapes: true }, | |
| options: { skipHtmlTags: ['script','noscript','style','textarea','pre'] } | |
| }; | |
| </script> | |
| <script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script> | |
| </body> | |
| </html> | |