worldcloud / guest.html
Lashtw's picture
Update guest.html
82326c2 verified
Raw History Blame Contribute Delete
6.37 kB
<!DOCTYPE html>
<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>