Lashtw commited on
Commit
5d47b59
·
verified ·
1 Parent(s): d8cbeaa

Upload 9 files

Browse files
src/services/classroom.js CHANGED
@@ -459,15 +459,17 @@ export async function getClassSize(roomCode) {
459
  }
460
 
461
  /**
462
- * Updates a student's monster stage
463
  * @param {string} userId
464
  * @param {number} newStage
 
465
  */
466
- export async function updateUserStage(userId, newStage) {
467
  const userRef = doc(db, USERS_COLLECTION, userId);
468
- await updateDoc(userRef, {
469
- monster_stage: newStage
470
- });
 
471
  }
472
 
473
  /**
 
459
  }
460
 
461
  /**
462
+ * Updates a student's monster stage and specific form
463
  * @param {string} userId
464
  * @param {number} newStage
465
+ * @param {string} monsterId (Optional, for persisting specific form)
466
  */
467
+ export async function updateUserMonster(userId, newStage, monsterId = null) {
468
  const userRef = doc(db, USERS_COLLECTION, userId);
469
+ const data = { monster_stage: newStage };
470
+ if (monsterId) data.monster_id = monsterId;
471
+
472
+ await updateDoc(userRef, data);
473
  }
474
 
475
  /**
src/utils/monsterUtils.js CHANGED
@@ -397,36 +397,35 @@ export function generateMonsterSVG(monster) {
397
  }
398
 
399
  /**
400
- * Determines the next monster form based on parameters
401
  * @param {number} currentStage - Current evolutionary stage (0-3)
402
  * @param {number} likes - Student's total likes
403
  * @param {number} classSize - Active class size
404
- * @param {string} [currentFam] - Current family hint (optional, for persistent lineage)
405
  * @returns {Object} Selected Monster Definition
406
  */
407
- export function getNextMonster(currentStage, likes, classSize, currentFam = null) {
408
  if (currentStage === 0) return MONSTER_DEFS.find(m => m.id === 'Egg');
409
 
410
- // Stage 1: Basic Families
 
 
 
 
 
 
 
 
411
  if (currentStage === 1) {
412
- // Simple logic for Stage 1? Or Random?
413
- // Let's use simple hash of likes or random to distribute if there's no clear indicator?
414
- // Requirements say "After beginner 5 tasks, Stage 1 unlocks".
415
- // Let's default to Dust or something based on likes?
416
- // Actually the prompt says:
417
- // "Stage 1 (Basic): Unlocks after Beginner... Three basic families: Dust, Pup, Spirit"
418
- // Let's pick based on (Likes % 3) for variety or just return one?
419
- // Let's return Spirit if Likes are high, Dust if low?
420
-
421
- // Simple distribution for now:
422
- if (likes > 5) return MONSTER_DEFS.find(m => m.id === 'L1_A'); // Spirit
423
- if (likes > 2) return MONSTER_DEFS.find(m => m.id === 'L1_B'); // Pup
424
- return MONSTER_DEFS.find(m => m.id === 'L1_C'); // Dust
425
  }
426
 
427
  // Branching Logic for Stage 2 & 3
428
  // A: High Likes ( > classSize / 2 )
429
- // B: Mid Likes ( 1 < likes <= classSize / 2 )
430
  // C: Low Likes ( <= 1 )
431
 
432
  const ratio = classSize > 0 ? likes / classSize : 0;
@@ -440,25 +439,32 @@ export function getNextMonster(currentStage, likes, classSize, currentFam = null
440
  if (classSize <= 2 && likes >= 1) tier = 'A'; // Boost for testing pairs
441
 
442
  // Filter potential monsters for this Stage
443
- const candidates = MONSTER_DEFS.filter(m => m.stage === currentStage);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
 
445
  // Filter by Tier (Suffix A/B/C)
446
- // IDs are like L2_AA, L2_AB... allow flexible matching.
447
- // Last char of ID corresponds to Tier? (A=High, B=Mid, C=Low)
448
- // Yes: L3_AAA (High), L3_CAA (High? No wait, logic in readme says:
449
- // Tier 1 (Low) -> C route.
450
- // Tier 3 (High) -> A route.
451
-
452
  const targetSuffix = tier;
453
 
454
- // If we have a current family, try to stick to it?
455
- // Implementation Plan didn't specify strict lineage tracking yet, but usually we want to.
456
-
457
- // Let's filter candidates ending in targetSuffix
458
- let match = candidates.find(m => m.id.endsWith(targetSuffix) && (!currentFam || m.fam === currentFam));
459
 
460
- // If no match (e.g. family change allowed or not found), just pick any of that Tier
461
- if (!match) match = candidates.find(m => m.id.endsWith(targetSuffix));
 
462
 
463
- return match || MONSTER_DEFS[0]; // Fallback
464
  }
 
397
  }
398
 
399
  /**
400
+ * Determines the next monster form based on parameters and lineage
401
  * @param {number} currentStage - Current evolutionary stage (0-3)
402
  * @param {number} likes - Student's total likes
403
  * @param {number} classSize - Active class size
404
+ * @param {string} [currentMonsterId] - ID of the current monster to determine lineage
405
  * @returns {Object} Selected Monster Definition
406
  */
407
+ export function getNextMonster(currentStage, likes, classSize, currentMonsterId = null) {
408
  if (currentStage === 0) return MONSTER_DEFS.find(m => m.id === 'Egg');
409
 
410
+ // Find current monster family if we have an ID
411
+ let currentFam = null;
412
+ if (currentMonsterId) {
413
+ const current = MONSTER_DEFS.find(m => m.id === currentMonsterId);
414
+ if (current) currentFam = current.fam; // e.g., 'Beast', 'Dust', 'Spirit'
415
+ }
416
+
417
+ // Stage 1: Basic Families (Origin -> Family)
418
+ // If coming from Egg (Stage 0), we pick a base family.
419
  if (currentStage === 1) {
420
+ // Simple distribution:
421
+ if (likes >= 5) return MONSTER_DEFS.find(m => m.id === 'L1_A'); // Spirit (Blue)
422
+ if (likes >= 2) return MONSTER_DEFS.find(m => m.id === 'L1_B'); // Beast (Yellow)
423
+ return MONSTER_DEFS.find(m => m.id === 'L1_C'); // Dust (Red)
 
 
 
 
 
 
 
 
 
424
  }
425
 
426
  // Branching Logic for Stage 2 & 3
427
  // A: High Likes ( > classSize / 2 )
428
+ // B: Normal Likes ( > 1 )
429
  // C: Low Likes ( <= 1 )
430
 
431
  const ratio = classSize > 0 ? likes / classSize : 0;
 
439
  if (classSize <= 2 && likes >= 1) tier = 'A'; // Boost for testing pairs
440
 
441
  // Filter potential monsters for this Stage
442
+ let candidates = MONSTER_DEFS.filter(m => m.stage === currentStage);
443
+
444
+ // Enforce Lineage (Family)
445
+ if (currentFam && currentFam !== 'Origin') {
446
+ // Filter candidates that belong to this Family tree
447
+ // We need to know which families map to which.
448
+ // Stage 1 -> Stage 2 Mapping:
449
+ // Dust -> Dust (Trash/Slime/Tech)
450
+ // Beast -> Beast (Wolf/Cat/Mech)
451
+ // Spirit -> Spirit (Fire/Angel/Dragon)
452
+
453
+ // In MONSTER_DEFS, 'fam' for Stage 2 is 'Dust', 'Beast', 'Spirit'.
454
+ // So we just match strict equality.
455
+ candidates = candidates.filter(m => m.fam === currentFam || m.fam.includes(currentFam));
456
+ }
457
 
458
  // Filter by Tier (Suffix A/B/C)
459
+ // IDs are like L2_AA, L2_AB...
460
+ // Last char is C(Low), B(Mid), A(High).
 
 
 
 
461
  const targetSuffix = tier;
462
 
463
+ let match = candidates.find(m => m.id.endsWith(targetSuffix));
 
 
 
 
464
 
465
+ // Fallback: If specific tier not found in this family (shouldn't happen if definitions are complete),
466
+ // pick ANY from this family.
467
+ if (!match && candidates.length > 0) match = candidates[0];
468
 
469
+ return match || MONSTER_DEFS[0]; // Absolute Fallback
470
  }
src/views/StudentView.js CHANGED
@@ -1,4 +1,4 @@
1
- import { submitPrompt, getChallenges, startChallenge, getUserProgress, getPeerPrompts, resetProgress, toggleLike, subscribeToNotifications, markNotificationRead, getClassSize, updateUserStage, getUser, subscribeToUserProgress } from "../services/classroom.js";
2
  import { generateMonsterSVG, getNextMonster, MONSTER_STAGES } from "../utils/monsterUtils.js";
3
 
4
 
@@ -149,58 +149,82 @@ export async function renderStudentView() {
149
  if (counts[2] >= 5 && potentialStage >= 1) potentialStage = 2;
150
  if (counts[3] >= 5 && potentialStage >= 2) potentialStage = 3;
151
 
152
- // 2. Get Actual Stage
153
  const actualStage = userProfile.monster_stage || 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  // 3. Display Logic
156
  const canEvolve = potentialStage > actualStage;
157
 
158
- // Growth: Base Scale 1.0.
159
- // Grows with TOTAL completed tasks. e.g. 0.05 per task.
160
- // Also resets effective growth if we evolve?
161
- // User said: "Monster size should grow every time a question is answered correctly"
162
- // And "Level also rise".
163
-
164
- // Let's make base scale depend on tasks completed SINCE last evolution?
165
- // Or just total tasks.
166
- // If I evolve, I probably want to start small-ish again?
167
- // But "Stage 2" monster should probably be bigger than "Stage 1 Egg".
168
- // Let's use a simple global scalar:
169
  const growthFactor = 0.08;
170
  const baseScale = 1.0;
171
- // Adjust for stage so high stage monsters aren't tiny initially?
172
- // Actually, let's just make it grow linearly based on total questions.
173
- // But if I evolve, does it shrink?
174
- // User request: "If don't evolve... keep getting bigger"
175
- // Implicitly, evolving might reset the 'extra' growth or change the base form.
176
- // Let's just use Total Completed for scale.
177
  const currentScale = baseScale + (totalCompleted * growthFactor);
178
 
179
- const monster = getNextMonster(actualStage, totalLikes, classSize);
 
 
 
 
 
 
 
 
 
 
 
180
 
181
  return `
182
- <div id="monster-container-fixed" class="fixed top-24 left-12 sm:left-32 z-50 flex flex-col items-center group pointer-events-none sm:pointer-events-auto">
183
- <!-- Monster -->
184
- <div class="pixel-art-container relative transform transition-transform duration-500 ease-out origin-center hover:scale-110" style="transform: scale(${currentScale});">
 
 
185
  <div class="pixel-monster w-28 h-28 drop-shadow-2xl filter" style="animation: breathe 3s infinite ease-in-out;">
186
  ${generateMonsterSVG(monster)}
187
  </div>
188
 
189
- <!-- Level Indicator (Total Quests) -->
190
  <div class="absolute -bottom-2 -right-2 bg-gray-900/90 text-xs text-yellow-400 px-2 py-0.5 rounded-full border border-yellow-500/50 font-mono font-bold transform scale-75 origin-top-left whitespace-nowrap">
191
  Lv.${1 + totalCompleted}
192
  </div>
193
  </div>
194
 
195
- <!-- Evolution Prompt -->
196
  <!-- Evolution Prompt -->
197
  ${canEvolve ? `
198
- <div id="evolution-prompt" class="absolute top-full mt-4 w-40 pointer-events-auto animate-bounce">
199
- <button onclick="window.triggerEvolution(${actualStage}, ${actualStage + 1}, ${totalLikes}, ${classSize})"
200
- class="w-full bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-500 hover:to-purple-500 text-white p-3 rounded-2xl shadow-[0_0_15px_rgba(236,72,153,0.6)] border border-white/20 transition-all hover:scale-105 active:scale-95 flex flex-col items-center gap-1 group-btn">
201
- <span class="text-xs text-pink-100 font-bold">小怪獸要進化了!</span>
202
- <span class="text-sm font-black text-white group-btn-hover:text-yellow-300">⚡ 點擊進化</span>
203
- </button>
 
 
 
 
204
  </div>
205
  ` : ''}
206
 
@@ -220,6 +244,11 @@ export async function renderStudentView() {
220
  0%, 100% { transform: translateY(0); filter: brightness(1); }
221
  50% { transform: translateY(-3px); filter: brightness(1.1); }
222
  }
 
 
 
 
 
223
  </style>
224
  `;
225
  };
@@ -562,26 +591,18 @@ window.handleLike = async (progressId, targetUserId) => {
562
  }
563
  };
564
 
565
- window.triggerEvolution = async (currentStage, nextStage, likes, classSize) => {
566
  // 1. Hide Prompt
567
  const prompt = document.getElementById('evolution-prompt');
568
  if (prompt) prompt.style.display = 'none';
569
 
570
- // 2. Prepare Animation Data
571
- // We need Next Monster Data
572
- // We can't easily import logic here if not exposed, but we exported getNextMonster.
573
- // We need to re-import or use the one in scope if available.
574
- // Fortunately setupStudentEvents is a module, but this function is on window.
575
- // We need to pass data or use a helper.
576
- // Ideally we should move getNextMonster to a global helper or fetch it.
577
- // Let's use dynamic import to be safe and robust.
578
-
579
  try {
580
  const { getNextMonster, generateMonsterSVG, MONSTER_STAGES } = await import("../utils/monsterUtils.js");
581
- const { updateUserStage } = await import("../services/classroom.js");
582
 
583
- const currentMonster = getNextMonster(currentStage, likes, classSize);
584
- const nextMonster = getNextMonster(nextStage, likes, classSize);
 
585
 
586
  const container = document.querySelector('#monster-container-fixed .pixel-monster');
587
  const containerWrapper = document.querySelector('#monster-container-fixed .pixel-art-container');
@@ -590,68 +611,47 @@ window.triggerEvolution = async (currentStage, nextStage, likes, classSize) => {
590
  container.style.animation = 'none';
591
 
592
  // --- ANIMATION SEQUENCE ---
593
- // flicker count
594
  let count = 0;
595
- const maxFlickers = 10;
596
- let speed = 300; // start slow
597
 
598
  const svgCurrent = generateMonsterSVG(currentMonster);
599
  const svgNext = generateMonsterSVG(nextMonster);
600
 
601
- // Helper to set Content and Style
602
  const setFrame = (svg, isSilhouette) => {
603
  container.innerHTML = svg;
604
- container.style.filter = isSilhouette ? 'brightness(0) invert(1)' : 'none'; // White silhouette? User said 'silhouette' usually black or white. Let's try Black (brightness 0)
605
- if (isSilhouette) container.style.filter = 'brightness(0)';
606
  };
607
 
608
  const playFlicker = () => {
609
- // Alternate
610
  const isNext = count % 2 === 1;
611
  setFrame(isNext ? svgNext : svgCurrent, true);
612
-
613
  count++;
614
 
615
  if (count < maxFlickers) {
616
- // Speed up
617
- speed *= 0.8;
618
  setTimeout(playFlicker, speed);
619
  } else {
620
  // Final Reveal
621
  setTimeout(() => {
622
- // Pause on Next Silhouette
623
- setFrame(svgNext, true);
624
 
625
  setTimeout(() => {
626
- // Reveal Color with flash
627
- containerWrapper.style.transition = 'filter 0.5s ease-out';
628
- containerWrapper.style.filter = 'drop-shadow(0 0 20px #ffffff)'; // Flash
629
 
630
- setFrame(svgNext, false); // Color
631
 
632
  setTimeout(async () => {
633
  containerWrapper.style.filter = 'none';
634
- // DB Update
635
  const userId = localStorage.getItem('vibecoding_user_id');
636
- await updateUserStage(userId, nextStage);
637
- // Reload
638
- const app = document.querySelector('#app');
639
- // We need to re-import renderStudentView? It's exported.
640
- // But we are inside window function.
641
- // Just generic reload for now or try to re-render if accessible.
642
- // renderStudentView is not global.
643
- // Let's reload page to be cleanest or rely on the subscribeToUserProgress which might flicker?
644
- // subscribeToUserProgress listens to PROGRESS collection, not USERS collection (where monster scale is).
645
- // So we MUST reload or manually fetch profile.
646
- // Simple reload:
647
- // window.location.reload();
648
- // Or better: triggering the view re-render.
649
- // Note: We don't have access to 'renderStudentView' function here easily unless we attached it to window.
650
- // Let's reload to ensure clean state.
651
  window.location.reload();
652
- }, 1000);
653
- }, 800);
654
- }, 200);
655
  }
656
  };
657
 
 
1
+ import { submitPrompt, getChallenges, startChallenge, getUserProgress, getPeerPrompts, resetProgress, toggleLike, subscribeToNotifications, markNotificationRead, getClassSize, updateUserMonster, getUser, subscribeToUserProgress } from "../services/classroom.js";
2
  import { generateMonsterSVG, getNextMonster, MONSTER_STAGES } from "../utils/monsterUtils.js";
3
 
4
 
 
149
  if (counts[2] >= 5 && potentialStage >= 1) potentialStage = 2;
150
  if (counts[3] >= 5 && potentialStage >= 2) potentialStage = 3;
151
 
152
+ // 2. Get Actual Stage & ID
153
  const actualStage = userProfile.monster_stage || 0;
154
+ const actualMonsterId = userProfile.monster_id || 'Egg';
155
+
156
+ // --- REGRESSION LOGIC (Auto-Devolve) ---
157
+ // If actual stage > potential stage, it means tasks were reset/rejected.
158
+ // We must devolve.
159
+ if (actualStage > potentialStage) {
160
+ // Devolve immediately (or show animation? Immediate for compliance)
161
+ // We need to call DB update. But we are in render function.
162
+ // Side-effect in render is bad, but necessary for self-correction.
163
+ // Let's debounce or check if we haven't already corrected.
164
+ // console.warn("Devolving from", actualStage, "to", potentialStage);
165
+ // Trigger update async
166
+ updateUserMonster(userId, potentialStage, null).then(() => {
167
+ // Reload to reflect
168
+ // window.location.reload(); // Might cause loop if not careful?
169
+ // If update succeeds, the subscription will fire?
170
+ // No, subscription listens to PROGRESS, not USER profile updates unless we subscribe to USER too.
171
+ // We currently verify profile on load.
172
+ // Let's just force a reload once to correct it.
173
+ /* setTimeout(() => window.location.reload(), 500); */
174
+ });
175
+ // For THIS render, show potential stage
176
+ // return renderMonsterSection(currentUserProgress, classSize, { ...userProfile, monster_stage: potentialStage });
177
+ }
178
 
179
  // 3. Display Logic
180
  const canEvolve = potentialStage > actualStage;
181
 
182
+ // Scale Logic
 
 
 
 
 
 
 
 
 
 
183
  const growthFactor = 0.08;
184
  const baseScale = 1.0;
 
 
 
 
 
 
185
  const currentScale = baseScale + (totalCompleted * growthFactor);
186
 
187
+ // Get Monster Data (Preserve Lineage)
188
+ // If we have an actual ID, use it for display until evolution.
189
+ // If we are about to evolve, we preview next? No, current.
190
+ let monster = getNextMonster(actualStage, 0, 0, actualMonsterId); // Just lookup current by ID/Stage?
191
+ if (actualMonsterId && actualMonsterId !== 'Egg') {
192
+ // If we have a specific ID stored, try to use it directly
193
+ const stored = MONSTER_DEFS.find(m => m.id === actualMonsterId);
194
+ if (stored) monster = stored;
195
+ } else {
196
+ // Fallback for Egg or legacy data without ID
197
+ monster = getNextMonster(actualStage, totalLikes, classSize);
198
+ }
199
 
200
  return `
201
+ <div id="monster-container-fixed" class="fixed top-24 left-1/2 -translate-x-1/2 z-50 flex flex-col items-center group pointer-events-none sm:pointer-events-auto w-40 h-40">
202
+ <!-- Walking Container -->
203
+ <div class="pixel-art-container relative transform transition-transform duration-500 ease-out origin-center hover:scale-110"
204
+ style="transform: scale(${currentScale}); animation: walk-float 6s ease-in-out infinite;">
205
+
206
  <div class="pixel-monster w-28 h-28 drop-shadow-2xl filter" style="animation: breathe 3s infinite ease-in-out;">
207
  ${generateMonsterSVG(monster)}
208
  </div>
209
 
210
+ <!-- Level Indicator -->
211
  <div class="absolute -bottom-2 -right-2 bg-gray-900/90 text-xs text-yellow-400 px-2 py-0.5 rounded-full border border-yellow-500/50 font-mono font-bold transform scale-75 origin-top-left whitespace-nowrap">
212
  Lv.${1 + totalCompleted}
213
  </div>
214
  </div>
215
 
 
216
  <!-- Evolution Prompt -->
217
  ${canEvolve ? `
218
+ <div id="evolution-prompt" class="absolute top-full mt-2 pointer-events-auto animate-bounce z-50">
219
+ <div class="flex flex-col items-center">
220
+ <div class="bg-gray-900/90 text-pink-200 text-xs py-2 px-3 rounded-xl border border-pink-500/30 shadow-lg text-center font-bold mb-1 backdrop-blur-sm whitespace-nowrap">
221
+ 咦,小怪獸的樣子<br>正在發生變化...
222
+ </div>
223
+ <button onclick="window.triggerEvolution(${actualStage}, ${actualStage + 1}, ${totalLikes}, ${classSize}, '${monster.id}')"
224
+ class="bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-500 hover:to-purple-500 text-white text-sm font-black py-2 px-6 rounded-full shadow-[0_0_15px_rgba(236,72,153,0.8)] border border-white/30 transition-all hover:scale-110 active:scale-95">
225
+ 進化!
226
+ </button>
227
+ </div>
228
  </div>
229
  ` : ''}
230
 
 
244
  0%, 100% { transform: translateY(0); filter: brightness(1); }
245
  50% { transform: translateY(-3px); filter: brightness(1.1); }
246
  }
247
+ @keyframes walk-float {
248
+ 0%, 100% { transform: translateX(0) scale(${currentScale}); }
249
+ 25% { transform: translateX(-10px) rotate(-2deg) scale(${currentScale}); }
250
+ 75% { transform: translateX(10px) rotate(2deg) scale(${currentScale}); }
251
+ }
252
  </style>
253
  `;
254
  };
 
591
  }
592
  };
593
 
594
+ window.triggerEvolution = async (currentStage, nextStage, likes, classSize, currentMonsterId) => {
595
  // 1. Hide Prompt
596
  const prompt = document.getElementById('evolution-prompt');
597
  if (prompt) prompt.style.display = 'none';
598
 
 
 
 
 
 
 
 
 
 
599
  try {
600
  const { getNextMonster, generateMonsterSVG, MONSTER_STAGES } = await import("../utils/monsterUtils.js");
601
+ const { updateUserMonster } = await import("../services/classroom.js");
602
 
603
+ // Calculate Next Monster with Lineage
604
+ const currentMonster = getNextMonster(currentStage, likes, classSize, currentMonsterId);
605
+ const nextMonster = getNextMonster(nextStage, likes, classSize, currentMonsterId);
606
 
607
  const container = document.querySelector('#monster-container-fixed .pixel-monster');
608
  const containerWrapper = document.querySelector('#monster-container-fixed .pixel-art-container');
 
611
  container.style.animation = 'none';
612
 
613
  // --- ANIMATION SEQUENCE ---
 
614
  let count = 0;
615
+ const maxFlickers = 12; // Increased duration
616
+ let speed = 300;
617
 
618
  const svgCurrent = generateMonsterSVG(currentMonster);
619
  const svgNext = generateMonsterSVG(nextMonster);
620
 
 
621
  const setFrame = (svg, isSilhouette) => {
622
  container.innerHTML = svg;
623
+ container.style.filter = isSilhouette ? 'brightness(0)' : 'none';
 
624
  };
625
 
626
  const playFlicker = () => {
 
627
  const isNext = count % 2 === 1;
628
  setFrame(isNext ? svgNext : svgCurrent, true);
 
629
  count++;
630
 
631
  if (count < maxFlickers) {
632
+ speed *= 0.85;
 
633
  setTimeout(playFlicker, speed);
634
  } else {
635
  // Final Reveal
636
  setTimeout(() => {
637
+ setFrame(svgNext, true); // Hold silhouette
 
638
 
639
  setTimeout(() => {
640
+ // Reveal Color with Flash
641
+ containerWrapper.style.transition = 'filter 0.8s ease-out';
642
+ containerWrapper.style.filter = 'drop-shadow(0 0 30px #ffffff) brightness(1.5)';
643
 
644
+ setFrame(svgNext, false);
645
 
646
  setTimeout(async () => {
647
  containerWrapper.style.filter = 'none';
648
+ // DB Update with Monster ID
649
  const userId = localStorage.getItem('vibecoding_user_id');
650
+ await updateUserMonster(userId, nextStage, nextMonster.id);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  window.location.reload();
652
+ }, 1200);
653
+ }, 1000);
654
+ }, 300);
655
  }
656
  };
657