Lashtw commited on
Commit
d8cbeaa
·
verified ·
1 Parent(s): 251d5d0

Upload 9 files

Browse files
Files changed (1) hide show
  1. src/views/StudentView.js +102 -22
src/views/StudentView.js CHANGED
@@ -179,7 +179,7 @@ export async function renderStudentView() {
179
  const monster = getNextMonster(actualStage, totalLikes, classSize);
180
 
181
  return `
182
- <div class="fixed top-8 left-10 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;">
@@ -192,20 +192,15 @@ export async function renderStudentView() {
192
  </div>
193
  </div>
194
 
 
195
  <!-- Evolution Prompt -->
196
  ${canEvolve ? `
197
- <div class="absolute top-full mt-4 left-1/2 -translate-x-1/2 w-48 pointer-events-auto animate-bounce">
198
- <div class="bg-gradient-to-br from-indigo-900 to-purple-900 border-2 border-pink-500 rounded-xl p-3 shadow-[0_0_20px_rgba(236,72,153,0.6)] text-center relative">
199
- <!-- Triangle tip -->
200
- <div class="absolute -top-2 left-1/2 -translate-x-1/2 w-0 h-0 border-l-[8px] border-l-transparent border-r-[8px] border-r-transparent border-b-[8px] border-b-pink-500"></div>
201
-
202
- <p class="text-xs text-pink-200 mb-2 font-bold leading-tight">
203
- 咦,小怪獸的樣子正在發生變化...<br>是否要進化?
204
- </p>
205
- <button onclick="window.triggerEvolution(${actualStage + 1})" class="w-full bg-pink-600 hover:bg-pink-500 text-white text-xs font-bold py-1.5 rounded-lg transition-colors shadow-sm">
206
- ✨ 立即進化
207
- </button>
208
- </div>
209
  </div>
210
  ` : ''}
211
 
@@ -567,19 +562,104 @@ window.handleLike = async (progressId, targetUserId) => {
567
  }
568
  };
569
 
570
- window.triggerEvolution = async (nextStage) => {
571
- const userId = localStorage.getItem('vibecoding_user_id');
572
- if (!confirm(`是否要進化到階段 ${nextStage}?\n(進化後怪獸大小將重置)`)) return;
 
 
 
 
 
 
 
 
 
 
573
 
574
  try {
 
575
  const { updateUserStage } = await import("../services/classroom.js");
576
- await updateUserStage(userId, nextStage);
577
- alert("✨ 進化成功!");
578
- // Reload view
579
- const app = document.querySelector('#app');
580
- app.innerHTML = await renderStudentView();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  } catch (e) {
582
  console.error(e);
583
- alert("進化失敗");
 
584
  }
585
  };
 
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;">
 
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
 
 
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');
588
+
589
+ // Stop breathing animation
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
+
658
+ playFlicker();
659
+
660
  } catch (e) {
661
  console.error(e);
662
+ alert("進化失敗...");
663
+ window.location.reload();
664
  }
665
  };