Spaces:
Running
Running
File size: 9,575 Bytes
ae79a68 449276f 901982e ae79a68 901982e 449276f 901982e 449276f 1662a78 449276f 1662a78 449276f 901982e 449276f ae79a68 449276f 901982e ae79a68 901982e ae79a68 901982e ae79a68 66269b4 ae79a68 66269b4 ae79a68 66269b4 ae79a68 901982e b391d41 901982e ae79a68 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | import { createRoom, joinRoom, checkNicknameConflict } from "../services/classroom.js";
import { generateMonsterSVG, MONSTER_DEFS } from "../utils/monsterUtils.js";
// ... (renderLandingView remains same) ...
export function renderLandingView() {
// Select Decor Monsters
// Left: Genesis Dragon (L3_AAA), Right: Gundam (L3_BAA) - or fallbacks
const mLeft = MONSTER_DEFS.find(m => m.id === 'L3_AAA') || MONSTER_DEFS.find(m => m.stage === 3);
const mRight = MONSTER_DEFS.find(m => m.id === 'L3_BAA') || MONSTER_DEFS.find(m => m.stage === 3);
const svgLeft = generateMonsterSVG(mLeft);
const svgRight = generateMonsterSVG(mRight);
return `
<div class="min-h-screen flex flex-col items-center justify-center p-4 relative overflow-hidden">
<!-- Decor Monsters (Desktop Only) -->
<div class="absolute bottom-10 left-10 w-48 h-48 hidden lg:block pointer-events-none"
style="animation: float 6s ease-in-out infinite;">
<div class="w-full h-full drop-shadow-[0_0_15px_rgba(34,211,238,0.5)]">
${svgLeft}
</div>
</div>
<div class="absolute bottom-10 right-10 w-48 h-48 hidden lg:block pointer-events-none"
style="animation: float 8s ease-in-out infinite reverse;">
<div class="w-full h-full drop-shadow-[0_0_15px_rgba(59,130,246,0.5)]">
${svgRight}
</div>
</div>
<div class="max-w-md w-full bg-gray-600 bg-opacity-20 backdrop-blur-lg rounded-xl shadow-2xl p-8 border border-gray-700 z-10">
<h1 class="text-3xl sm:text-4xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-purple-500 mb-8 text-center tracking-tighter whitespace-nowrap">
VIBECODING-怪獸成長營
</h1>
<!-- Student Join Form -->
<div id="student-form" class="space-y-6">
<div>
<label class="block text-gray-400 text-sm font-bold mb-2">教室代碼 (Room Code)</label>
<input type="text" id="room-code-input" class="w-full bg-gray-800 text-white border border-gray-600 rounded-lg py-3 px-4 focus:outline-none focus:border-cyan-500 transition-colors" placeholder="1234">
</div>
<div>
<label class="block text-gray-400 text-sm font-bold mb-2">您的暱稱 (Nickname)</label>
<input type="text" id="nickname-input" class="w-full bg-gray-800 text-white border border-gray-600 rounded-lg py-3 px-4 focus:outline-none focus:border-purple-500 transition-colors" placeholder="小明">
</div>
<button id="join-btn" class="w-full bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white font-bold py-3 px-4 rounded-lg transform transition hover:scale-105 active:scale-95 shadow-lg shadow-cyan-500/30">
進入教室
</button>
</div>
<!-- Instructor Toggle -->
<div class="mt-8 pt-6 border-t border-gray-700 text-center">
<button id="instructor-mode-btn" class="text-gray-500 text-sm hover:text-cyan-400 transition-colors">
我是講師 (Instructor Mode)
</button>
</div>
</div>
<!-- Conflict Modal Container -->
<div id="conflict-modal-container"></div>
<style>
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
</style>
</div>
`;
}
export function setupLandingEvents(navigateTo) {
const joinBtn = document.getElementById('join-btn');
const instructorBtn = document.getElementById('instructor-mode-btn');
const handleJoin = async (roomCode, nickname, forceNew = false) => {
try {
const { userId, nickname: finalNickname } = await joinRoom(roomCode, nickname, forceNew);
// Save Session
localStorage.setItem('vibecoding_user_id', userId);
localStorage.setItem('vibecoding_room_code', roomCode);
localStorage.setItem('vibecoding_nickname', finalNickname);
navigateTo('student');
return true;
} catch (error) {
alert('加入失敗: ' + error.message);
return false;
}
};
joinBtn.addEventListener('click', async () => {
const roomCode = document.getElementById('room-code-input').value.trim();
const nickname = document.getElementById('nickname-input').value.trim();
if (!roomCode || !nickname) {
alert('請輸入教室代碼和暱稱');
return;
}
joinBtn.textContent = '檢查中...';
joinBtn.disabled = true;
try {
// Check conflicts first
const conflicts = await checkNicknameConflict(roomCode, nickname);
if (conflicts.length > 0) {
// Show Conflict Modal
showConflictModal(conflicts, nickname, roomCode, navigateTo, handleJoin);
joinBtn.textContent = '進入教室';
joinBtn.disabled = false;
return;
}
// No conflict -> direct join
await handleJoin(roomCode, nickname);
} catch (e) {
console.error(e);
alert("檢查失敗: " + e.message);
joinBtn.textContent = '進入教室';
joinBtn.disabled = false;
}
});
instructorBtn.addEventListener('click', () => {
// Clear any previous admin referer to ensure clean state
localStorage.removeItem('vibecoding_admin_referer');
navigateTo('instructor');
});
}
function showConflictModal(conflicts, originalNickname, roomCode, navigateTo, handleJoin) {
const container = document.getElementById('conflict-modal-container');
const userListHTML = conflicts.map(u => `
<button onclick="window.selectUser('${u.nickname}')"
class="w-full text-left bg-gray-700 hover:bg-gray-600 p-4 rounded-xl flex justify-between items-center group transition-all border border-gray-600 hover:border-cyan-500">
<span class="font-bold text-white group-hover:text-cyan-300 transition-colors">${u.nickname}</span>
<span class="text-xs text-gray-400 bg-gray-800 px-2 py-1 rounded">舊學員</span>
</button>
`).join('');
container.innerHTML = `
<div class="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div class="bg-gray-800 w-full max-w-md rounded-2xl border border-gray-700 shadow-2xl overflow-hidden animate-[fadeIn_0.3s_ease-out]">
<div class="p-6 border-b border-gray-700 bg-gray-900/50">
<h3 class="text-xl font-bold text-white mb-1">發現相同暱稱</h3>
<p class="text-gray-400 text-sm">教室裡已經有叫「${originalNickname}」的同學了,請問您是?</p>
</div>
<div class="p-6 space-y-3 max-h-[60vh] overflow-y-auto">
${userListHTML}
<div class="relative flex py-2 items-center">
<div class="flex-grow border-t border-gray-700"></div>
<span class="flex-shrink-0 mx-4 text-gray-500 text-xs">或者</span>
<div class="flex-grow border-t border-gray-700"></div>
</div>
<button onclick="window.createNewUser()"
class="w-full bg-gradient-to-r from-green-600 to-teal-600 hover:from-green-500 hover:to-teal-500 text-white font-bold p-4 rounded-xl shadow-lg shadow-green-900/40 transform transition hover:scale-[1.02] flex items-center justify-center space-x-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" clip-rule="evenodd" />
</svg>
<span>我是新同學,建立新分身</span>
</button>
</div>
<div class="p-4 bg-gray-900/50 border-t border-gray-700 flex justify-end">
<button onclick="document.getElementById('conflict-modal-container').innerHTML=''"
class="text-gray-400 hover:text-white text-sm px-4 py-2">
取消
</button>
</div>
</div>
</div>
`;
// Bind temporary window functions for the modal buttons
window.selectUser = async (targetNickname) => {
// Log in as existing
document.getElementById('conflict-modal-container').innerHTML = ''; // Close modal
await handleJoin(roomCode, targetNickname, false);
};
window.createNewUser = async () => {
// Create new
document.getElementById('conflict-modal-container').innerHTML = ''; // Close modal
await handleJoin(roomCode, originalNickname, true); // Force new
};
}
|