Lashtw commited on
Commit
d079b10
·
verified ·
1 Parent(s): 1662a78

Upload 10 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ assets/instructor_avatar.png filter=lfs diff=lfs merge=lfs -text
assets/instructor_avatar.png ADDED

Git LFS Details

  • SHA256: 8a338d67ef034c7716cc470af8f794ed7e06b028c8590341a4b1832f59b63b2c
  • Pointer size: 131 Bytes
  • Size of remote file: 123 kB
src/views/InstructorView.js CHANGED
@@ -1,6 +1,8 @@
1
  import { createRoom, subscribeToRoom, getChallenges, resetProgress } from "../services/classroom.js";
 
2
 
3
  let cachedChallenges = [];
 
4
 
5
  export async function renderInstructorView() {
6
  // Pre-fetch challenges for table headers
@@ -65,6 +67,22 @@ export async function renderInstructorView() {
65
  </div>
66
  </div>
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  <div class="min-h-screen p-6 pb-20 bg-gray-900 text-white">
69
  <!-- Header -->
70
  <header class="flex flex-col md:flex-row justify-between items-center mb-6 bg-gray-800 p-4 rounded-xl border border-gray-700 space-y-4 md:space-y-0 sticky top-0 z-30 shadow-lg">
@@ -86,6 +104,9 @@ export async function renderInstructorView() {
86
  <div class="flex items-center"><div class="w-3 h-3 bg-red-500 animate-pulse rounded-sm mr-1"></div> 卡關 (>5m)</div>
87
  </div>
88
 
 
 
 
89
  <button id="nav-admin-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg transition-all border border-gray-600">
90
  管理題目
91
  </button>
@@ -158,6 +179,87 @@ export function setupInstructorEvents() {
158
  const dashboardContent = document.getElementById('dashboard-content');
159
  const displayRoomCode = document.getElementById('display-room-code');
160
  const navAdminBtn = document.getElementById('nav-admin-btn');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
  navAdminBtn.addEventListener('click', () => {
163
  // Save current room to return later
@@ -201,6 +303,7 @@ export function setupInstructorEvents() {
201
 
202
  // Subscribe to updates
203
  subscribeToRoom(roomCode, (students) => {
 
204
  renderTransposedHeatmap(students);
205
  });
206
  }
 
1
  import { createRoom, subscribeToRoom, getChallenges, resetProgress } from "../services/classroom.js";
2
+ import { generateMonsterSVG, getNextMonster } from "../utils/monsterUtils.js";
3
 
4
  let cachedChallenges = [];
5
+ let currentStudents = [];
6
 
7
  export async function renderInstructorView() {
8
  // Pre-fetch challenges for table headers
 
67
  </div>
68
  </div>
69
 
70
+ <!-- Group Photo Modal -->
71
+ <div id="group-photo-modal" class="fixed inset-0 bg-gray-900/95 backdrop-blur-md z-50 hidden flex flex-col items-center justify-center p-4 transition-opacity duration-300">
72
+ <button onclick="document.getElementById('group-photo-modal').classList.add('hidden')" class="absolute top-6 right-6 text-gray-400 hover:text-white text-4xl z-50">✕</button>
73
+
74
+ <div class="text-center mb-8 z-10">
75
+ <h2 class="text-3xl md:text-5xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-yellow-400 via-orange-500 to-red-500 tracking-wider drop-shadow-lg">
76
+ 大合照 CLASS PHOTO
77
+ </h2>
78
+ <p class="text-gray-400 mt-2 font-mono" id="photo-date">2026.01.27</p>
79
+ </div>
80
+
81
+ <div id="group-photo-container" class="w-full max-w-7xl flex flex-col items-center overflow-y-auto max-h-[80vh] custom-scrollbar">
82
+ <!-- Dynamic Content -->
83
+ </div>
84
+ </div>
85
+
86
  <div class="min-h-screen p-6 pb-20 bg-gray-900 text-white">
87
  <!-- Header -->
88
  <header class="flex flex-col md:flex-row justify-between items-center mb-6 bg-gray-800 p-4 rounded-xl border border-gray-700 space-y-4 md:space-y-0 sticky top-0 z-30 shadow-lg">
 
104
  <div class="flex items-center"><div class="w-3 h-3 bg-red-500 animate-pulse rounded-sm mr-1"></div> 卡關 (>5m)</div>
105
  </div>
106
 
107
+ <button id="group-photo-btn" class="bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-500 hover:to-purple-500 text-white font-bold py-2 px-4 rounded-lg transition-all shadow-lg border border-pink-400/30 flex items-center space-x-2">
108
+ <span>📸 大合照</span>
109
+ </button>
110
  <button id="nav-admin-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg transition-all border border-gray-600">
111
  管理題目
112
  </button>
 
179
  const dashboardContent = document.getElementById('dashboard-content');
180
  const displayRoomCode = document.getElementById('display-room-code');
181
  const navAdminBtn = document.getElementById('nav-admin-btn');
182
+ const groupPhotoBtn = document.getElementById('group-photo-btn');
183
+
184
+ // Group Photo Logic
185
+ groupPhotoBtn.addEventListener('click', () => {
186
+ const modal = document.getElementById('group-photo-modal');
187
+ const container = document.getElementById('group-photo-container');
188
+ const dateEl = document.getElementById('photo-date');
189
+
190
+ // Update Date
191
+ const now = new Date();
192
+ dateEl.textContent = `${now.getFullYear()}.${String(now.getMonth() + 1).padStart(2, '0')}.${String(now.getDate()).padStart(2, '0')}`;
193
+
194
+ container.innerHTML = '';
195
+
196
+ // 1. Instructor Section (Center Top)
197
+ const instructorSection = document.createElement('div');
198
+ instructorSection.className = 'flex flex-col items-center justify-center mb-10 w-full relative';
199
+ instructorSection.innerHTML = `
200
+ <div class="relative group animate-bounce-slow">
201
+ <div class="absolute inset-0 bg-yellow-500/20 blur-3xl rounded-full"></div>
202
+ <!-- Pixel Art Avatar -->
203
+ <img src="assets/instructor_avatar.png" class="relative w-40 h-40 md:w-56 md:h-56 object-contain pixel-art drop-shadow-[0_10px_20px_rgba(0,0,0,0.5)] z-10 hover:scale-105 transition-transform duration-300" alt="Instructor">
204
+
205
+ <!-- Name Tag -->
206
+ <div class="absolute -bottom-6 left-1/2 transform -translate-x-1/2 bg-black/80 backdrop-blur text-yellow-400 px-4 py-1.5 rounded-full border border-yellow-500/30 shadow-xl flex items-center space-x-2 z-20 whitespace-nowrap">
207
+ <span class="text-xl">👑</span>
208
+ <span class="font-bold text-lg">講師 (Instructor)</span>
209
+ </div>
210
+ </div>
211
+ `;
212
+ container.appendChild(instructorSection);
213
+
214
+ // 2. Students Grid
215
+ if (currentStudents.length === 0) {
216
+ const emptyMsg = document.createElement('div');
217
+ emptyMsg.className = 'text-gray-500 text-xl mt-10';
218
+ emptyMsg.textContent = '尚無學員加入...';
219
+ container.appendChild(emptyMsg);
220
+ } else {
221
+ const grid = document.createElement('div');
222
+ grid.className = 'grid grid-cols-2 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 gap-6 md:gap-8 w-full px-4 md:px-10';
223
+
224
+ currentStudents.forEach(s => {
225
+ const progressMap = s.progress || {};
226
+ const totalLikes = Object.values(progressMap).reduce((acc, p) => acc + (p.likes || 0), 0);
227
+
228
+ // Get Monster
229
+ const monster = getNextMonster(s.monster_stage || 0, totalLikes, currentStudents.length, s.monster_id);
230
+
231
+ const card = document.createElement('div');
232
+ card.className = 'flex flex-col items-center group relative';
233
+ card.innerHTML = `
234
+ <div class="relative w-24 h-24 flex items-center justify-center transform group-hover:-translate-y-2 transition-transform duration-300">
235
+ <div class="w-20 h-20 md:w-24 md:h-24 pixel-art drop-shadow-lg filter group-hover:brightness-110 transition-all">
236
+ ${generateMonsterSVG(monster)}
237
+ </div>
238
+
239
+ <!-- Floating Hearts Effect (Pseudo) -->
240
+ <div class="absolute -top-2 -right-2 opacity-0 group-hover:opacity-100 transition-opacity text-pink-400 text-xs font-bold animate-pulse">
241
+ ❤️
242
+ </div>
243
+ </div>
244
+
245
+ <div class="mt-2 text-center bg-gray-800/60 backdrop-blur rounded-lg px-3 py-1.5 border border-gray-700 group-hover:border-cyan-500/50 transition-colors w-full max-w-[120px]">
246
+ <div class="text-sm font-bold text-cyan-200 truncate">${s.nickname}</div>
247
+ <div class="flex items-center justify-center space-x-2 mt-1">
248
+ <span class="text-[10px] bg-blue-900/50 text-blue-300 px-1.5 rounded border border-blue-500/30">Lv.${(s.monster_stage || 0) + 1}</span>
249
+ <div class="flex items-center text-[10px] text-pink-400 font-bold">
250
+ <span>♥</span>
251
+ <span class="ml-0.5">${totalLikes}</span>
252
+ </div>
253
+ </div>
254
+ </div>
255
+ `;
256
+ grid.appendChild(card);
257
+ });
258
+ container.appendChild(grid);
259
+ }
260
+
261
+ modal.classList.remove('hidden');
262
+ });
263
 
264
  navAdminBtn.addEventListener('click', () => {
265
  // Save current room to return later
 
303
 
304
  // Subscribe to updates
305
  subscribeToRoom(roomCode, (students) => {
306
+ currentStudents = students;
307
  renderTransposedHeatmap(students);
308
  });
309
  }