Package source · 迪士尼魔法市集:我的小小錢幣王國

CC BY 4.0 · please credit @SpringFairy · v1

← Game details

Take it, remix it, teach with it

The code below is only the package's playable entry HTML. Download the complete ZIP to edit or remix the game with all of its relative assets.

⬇ Download entry HTML ⬇ Download complete package
AI edit prompt starter
Edit this classroom game for interactive whiteboards. Keep large touch targets and whole-class play. Tracking, logins, personal-data collection, and remote API calls are prohibited. Use the downloaded complete ZIP—not only the entry HTML shown below—and preserve its browser-ready folder structure. Preserve the educational goal. Original creator: @SpringFairy on ClassBoard Games (CC BY 4.0).
Entry HTML only · index.html · 416.9 KB
<!DOCTYPE html>
<html lang="zh-TW">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>皇家歐洲小市集 - 迪士尼魔法錢幣探索</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@600;800;950&family=Noto+Serif+TC:wght@600;900&display=swap');

    * {
      box-sizing: border-box;
      user-select: none;
      -webkit-user-select: none;
    }

    body {
      margin: 0;
      padding: 0;
      background: radial-gradient(circle at center, #d8f3dc 0%, #b7e4c7 70%, #95d5b2 100%);
      font-family: 'Noto Serif TC', 'Cinzel', serif;
      touch-action: none;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      width: 100vw;
    }

    #canvas-wrapper {
      position: relative;
      width: 98vw;
      height: 96vh;
      max-width: 1024px;
      max-height: 700px;
      box-shadow: 0 15px 35px rgba(0, 0, 0, 0.25), 0 0 25px rgba(255, 215, 0, 0.5), 0 0 0 4px #ffd700, 0 0 0 8px #3b82f6;
      border-radius: 18px;
      overflow: hidden;
      background: #e0f2fe;
    }

    canvas {
      display: block;
      width: 100%;
      height: 100%;
      cursor: pointer;
    }
  </style>
</head>
<body>

  <div id="canvas-wrapper">
    <canvas id="gameCanvas"></canvas>
  </div>

  <script>
    let audioCtx = null;
    function initAudio() {
      if (!audioCtx) {
        audioCtx = new (window.AudioContext || window.webkitAudioContext)();
      }
      if (audioCtx && audioCtx.state === 'suspended') {
        audioCtx.resume();
      }
    }

    function playTone(freq, type, duration, gainVal = 0.15) {
      try {
        initAudio();
        if (!audioCtx) return;
        const osc = audioCtx.createOscillator();
        const gain = audioCtx.createGain();
        osc.type = type;
        osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
        gain.gain.setValueAtTime(gainVal, audioCtx.currentTime);
        gain.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + duration);
        osc.connect(gain);
        gain.connect(audioCtx.destination);
        osc.start();
        osc.stop(audioCtx.currentTime + duration);
      } catch (err) {
        console.warn('Audio play restricted:', err);
      }
    }

    function soundCoin() {
      playTone(1320, 'sine', 0.12, 0.2);
      setTimeout(() => playTone(1760, 'sine', 0.18, 0.15), 60);
    }

    // 歡樂華麗的迪士尼慶典進行曲 (雙層鐘琴主奏 + 管弦和弦 + 奇妙仙子星芒仙塵)
    function playDisneyCelebrationTheme() {
      initAudio();
      if (!audioCtx) return;

      const now = audioCtx.currentTime;

      // 輔助:鐘琴與木琴清脆雙振盪器 (基音 + 泛音,自帶歡慶明亮質感)
      function playBell(freq, timeOffset, duration, vol = 0.22) {
        try {
          const osc1 = audioCtx.createOscillator();
          const osc2 = audioCtx.createOscillator();
          const gain = audioCtx.createGain();

          osc1.type = 'triangle';
          osc1.frequency.setValueAtTime(freq, now + timeOffset);

          osc2.type = 'sine';
          osc2.frequency.setValueAtTime(freq * 2, now + timeOffset);

          gain.gain.setValueAtTime(vol, now + timeOffset);
          gain.gain.exponentialRampToValueAtTime(0.0001, now + timeOffset + duration);

          osc1.connect(gain);
          osc2.connect(gain);
          gain.connect(audioCtx.destination);

          osc1.start(now + timeOffset);
          osc2.start(now + timeOffset);
          osc1.stop(now + timeOffset + duration);
          osc2.stop(now + timeOffset + duration);
        } catch (e) {}
      }

      // 輔助:伴奏慶典和弦 (溫暖管弦烘托歡樂氣氛)
      function playHarmony(freqs, timeOffset, duration, vol = 0.08) {
        freqs.forEach(f => {
          try {
            const osc = audioCtx.createOscillator();
            const gain = audioCtx.createGain();
            osc.type = 'sine';
            osc.frequency.setValueAtTime(f, now + timeOffset);
            gain.gain.setValueAtTime(vol, now + timeOffset);
            gain.gain.exponentialRampToValueAtTime(0.0001, now + timeOffset + duration);
            osc.connect(gain);
            gain.connect(audioCtx.destination);
            osc.start(now + timeOffset);
            osc.stop(now + timeOffset + duration);
          } catch (e) {}
        });
      }

      // 1. 歡樂主題主旋律 (高亢輕快的進行曲跳躍音型)
      const melody = [
        { f: 523.25, t: 0.00, d: 0.16 }, // C5
        { f: 659.25, t: 0.14, d: 0.16 }, // E5
        { f: 783.99, t: 0.28, d: 0.22 }, // G5
        { f: 1046.5, t: 0.44, d: 0.35 }, // C6 (歡樂跳躍高音!)

        { f: 783.99, t: 0.72, d: 0.15 }, // G5
        { f: 880.00, t: 0.85, d: 0.15 }, // A5
        { f: 987.77, t: 0.98, d: 0.16 }, // B5
        { f: 1046.5, t: 1.12, d: 0.16 }, // C6
        { f: 1174.6, t: 1.26, d: 0.18 }, // D6
        { f: 1318.5, t: 1.42, d: 0.38 }, // E6 (慶祝頂峰!)

        { f: 1174.6, t: 1.72, d: 0.16 }, // D6
        { f: 1046.5, t: 1.86, d: 0.22 }, // C6
        { f: 1567.9, t: 2.02, d: 0.26 }, // G6 (驚喜歡呼音!)

        // 終曲大齊鳴
        { f: 1046.5, t: 2.22, d: 0.75 }  // C6 (大勝利長音)
      ];

      melody.forEach(m => playBell(m.f, m.t, m.d, 0.25));

      // 2. 豐富伴奏和弦 (C -> F -> G7 -> 大C歡樂大和弦)
      playHarmony([261.63, 329.63, 392.00], 0.00, 0.68, 0.10); // C Major
      playHarmony([349.23, 440.00, 523.25], 0.72, 0.68, 0.10); // F Major
      playHarmony([392.00, 493.88, 587.33], 1.26, 0.88, 0.11); // G Major
      playHarmony([261.63, 392.00, 523.25, 659.25, 1046.5], 2.22, 1.05, 0.14); // Grand Finale C Major

      // 3. 迪士尼奇妙仙子星芒仙塵音 (Pixie Dust Glissando 亮晶晶滑音)
      const pixieNotes = [1567.98, 1760.00, 1975.53, 2093.00, 2349.32, 2637.02, 3135.96, 3520.00];
      pixieNotes.forEach((pf, idx) => {
        playBell(pf, 2.30 + idx * 0.07, 0.38, 0.09);
      });
    }

    function soundSuccess() {
      playDisneyCelebrationTheme();
    }

    function soundFailure() {
      playTone(280, 'sawtooth', 0.18, 0.18);
      setTimeout(() => playTone(210, 'sawtooth', 0.26, 0.2), 140);
    }

    let currentUtterance = null;
    function speak(text, onComplete = null, customRate = null) {
      if ('speechSynthesis' in window) {
        window.speechSynthesis.cancel();
        const utterance = new SpeechSynthesisUtterance(text);
        currentUtterance = utterance; // 鎖定引用,防止 Chromium 垃圾回收提前截斷語音
        utterance.lang = 'zh-TW';
        utterance.rate = customRate || 0.85;  // 預設適中語速,亦可自訂更慢節奏
        utterance.pitch = 1.18; // 親切明朗微揚童音

        let done = false;
        function finish() {
          if (done) return;
          done = true;
          if (currentUtterance === utterance) {
            currentUtterance = null;
          }
          if (typeof onComplete === 'function') {
            onComplete();
          }
        }

        utterance.onend = finish;
        utterance.onerror = finish;
        window.speechSynthesis.speak(utterance);
      } else {
        if (typeof onComplete === 'function') {
          onComplete();
        }
      }
    }

    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    const V_WIDTH = 960;
    const V_HEIGHT = 640;

    let scale = 1;
    let offsetX = 0;
    let offsetY = 0;

    function resizeCanvas() {
      const rect = canvas.parentElement.getBoundingClientRect();
      const dpr = window.devicePixelRatio || 1;
      
      canvas.width = rect.width * dpr;
      canvas.height = rect.height * dpr;

      const scaleX = rect.width / V_WIDTH;
      const scaleY = rect.height / V_HEIGHT;
      scale = Math.min(scaleX, scaleY);

      offsetX = (rect.width - V_WIDTH * scale) / 2;
      offsetY = (rect.height - V_HEIGHT * scale) / 2;

      ctx.setTransform(dpr * scale, 0, 0, dpr * scale, dpr * offsetX, dpr * offsetY);
    }

    window.addEventListener('resize', resizeCanvas);

    let currentStage = 3; // 預設進入第 III 關:市集買賣
    let feedback = { text: '', color: 'good', timer: 0 };
    let particles = [];
    let fireworks = [];
    let nextFireworkTime = 0;
    let coinDisplaySide = 'reverse'; // 'reverse': 面額面 (1, 5, 50圓), 'obverse': 正面 (肖像面)

    // 星芒輔助函式 (修復先前遺漏)
    function drawStar(ctx, cx, cy, spikes, outerRadius, innerRadius) {
      let rot = (Math.PI / 2) * 3;
      let x = cx;
      let y = cy;
      const step = Math.PI / spikes;

      ctx.beginPath();
      ctx.moveTo(cx, cy - outerRadius);
      for (let i = 0; i < spikes; i++) {
        x = cx + Math.cos(rot) * outerRadius;
        y = cy + Math.sin(rot) * outerRadius;
        ctx.lineTo(x, y);
        rot += step;

        x = cx + Math.cos(rot) * innerRadius;
        y = cy + Math.sin(rot) * innerRadius;
        ctx.lineTo(x, y);
        rot += step;
      }
      ctx.lineTo(cx, cy - outerRadius);
      ctx.closePath();
      ctx.fill();
    }

    // 粒子系統
    function addSparkles(x, y, count = 35) {
      const colors = ['#ffd700', '#ff80bf', '#80d8ff', '#b388ff', '#ffffff', '#fff59d'];
      for (let i = 0; i < count; i++) {
        const angle = Math.random() * Math.PI * 2;
        const speed = 1.6 + Math.random() * 4.2;
        particles.push({
          x: x,
          y: y,
          vx: Math.cos(angle) * speed,
          vy: Math.sin(angle) * speed - 1.2,
          color: colors[Math.floor(Math.random() * colors.length)],
          size: 3 + Math.random() * 3.5,
          life: 1.0,
          decay: 0.015 + Math.random() * 0.02,
          isStar: Math.random() > 0.35,
          rotation: Math.random() * Math.PI,
          rotSpeed: (Math.random() - 0.5) * 0.2
        });
      }
    }

    function updateAndDrawParticles(ctx) {
      for (let i = particles.length - 1; i >= 0; i--) {
        const p = particles[i];
        p.x += p.vx;
        p.y += p.vy;
        p.vy += 0.12;
        p.life -= p.decay;

        if (p.life <= 0) {
          particles.splice(i, 1);
        } else {
          ctx.save();
          ctx.fillStyle = p.color;
          ctx.globalAlpha = Math.max(0, p.life);
          if (p.isStar) {
            p.rotation += p.rotSpeed;
            ctx.translate(p.x, p.y);
            ctx.rotate(p.rotation);
            drawStar(ctx, 0, 0, 5, p.size * 1.4, p.size * 0.6);
          } else {
            ctx.beginPath();
            ctx.arc(p.x, p.y, p.size * p.life, 0, Math.PI * 2);
            ctx.fill();
          }
          ctx.restore();
        }
      }
    }

    function createFirework() {
      const fx = 120 + Math.random() * (V_WIDTH - 240);
      const fy = 80 + Math.random() * 160;
      const palette = [
        ['#ff477e', '#ff85a1', '#ffffff'],
        ['#ffd700', '#fff3b0', '#ffffff'],
        ['#00d2d3', '#54a0ff', '#ffffff'],
        ['#a55eea', '#d1d8e0', '#ffffff'],
        ['#ff9f43', '#feca57', '#ffffff']
      ];
      const colors = palette[Math.floor(Math.random() * palette.length)];
      const sparkCount = 36 + Math.floor(Math.random() * 20);
      const isMickey = Math.random() < 0.3; // 30% 機率爆出米奇形狀煙火
      
      for (let i = 0; i < sparkCount; i++) {
        const angle = (Math.PI * 2 * i) / sparkCount + (Math.random() - 0.5) * 0.2;
        const speed = 1.8 + Math.random() * 3.4;
        fireworks.push({
          x: fx,
          y: fy,
          vx: Math.cos(angle) * speed,
          vy: Math.sin(angle) * speed,
          color: colors[Math.floor(Math.random() * colors.length)],
          size: 2.6 + Math.random() * 2.6,
          life: 1.0,
          decay: 0.016 + Math.random() * 0.018,
          isStar: Math.random() > 0.45
        });
      }

      // 米奇煙火的雙耳
      if (isMickey) {
        [-24, 24].forEach(earX => {
          for (let k = 0; k < 12; k++) {
            const ea = (Math.PI * 2 * k) / 12;
            const es = 1.2 + Math.random() * 1.5;
            fireworks.push({
              x: fx + earX,
              y: fy - 26,
              vx: Math.cos(ea) * es,
              vy: Math.sin(ea) * es,
              color: '#ffd700',
              size: 2.4,
              life: 0.9,
              decay: 0.02,
              isStar: true
            });
          }
        });
      }
    }

    function updateAndDrawFireworks(ctx) {
      if (Date.now() > nextFireworkTime) {
        createFirework();
        nextFireworkTime = Date.now() + 1100 + Math.random() * 1100;
      }

      for (let i = fireworks.length - 1; i >= 0; i--) {
        const f = fireworks[i];
        f.x += f.vx;
        f.y += f.vy;
        f.vy += 0.045;
        f.life -= f.decay;

        if (f.life <= 0) {
          fireworks.splice(i, 1);
        } else {
          ctx.save();
          ctx.globalAlpha = Math.max(0, f.life);
          ctx.fillStyle = f.color;
          ctx.shadowColor = f.color;
          ctx.shadowBlur = 10;
          if (f.isStar) {
            drawStar(ctx, f.x, f.y, 4, f.size * 1.5, f.size * 0.5);
          } else {
            ctx.beginPath();
            ctx.arc(f.x, f.y, f.size, 0, Math.PI * 2);
            ctx.fill();
          }
          ctx.restore();
        }
      }
    }

    // 繪製明亮陽光版迪士尼樂園經典奇幻城堡與歡樂園區背景
    function drawDisneylandBackground(ctx) {
      ctx.save();

      // 1. 晴朗明亮的迪士尼蔚藍天空漸層
      const skyGrad = ctx.createLinearGradient(0, 0, 0, V_HEIGHT);
      skyGrad.addColorStop(0, '#5390eb');    // 晴朗天藍
      skyGrad.addColorStop(0.35, '#7ebbff'); // 陽光淺藍
      skyGrad.addColorStop(0.65, '#bfe3ff'); // 明亮天空
      skyGrad.addColorStop(0.85, '#e0f2fe'); // 清新晨光白藍
      skyGrad.addColorStop(1, '#fef08a');    // 暖暖金色日光
      ctx.fillStyle = skyGrad;
      ctx.fillRect(0, 0, V_WIDTH, V_HEIGHT);

      // 2. 陽光光芒射線 (Sunburst Rays)
      ctx.save();
      const sunX = V_WIDTH / 2;
      const sunY = 70;
      ctx.strokeStyle = 'rgba(255, 255, 255, 0.22)';
      ctx.lineWidth = 14;
      for (let a = 0; a < Math.PI * 2; a += Math.PI / 8) {
        ctx.beginPath();
        ctx.moveTo(sunX, sunY);
        ctx.lineTo(sunX + Math.cos(a) * 550, sunY + Math.sin(a) * 550);
        ctx.stroke();
      }
      ctx.restore();

      // 3. 蓬鬆可愛的迪士尼白雲 (Fluffy Fairytale Clouds)
      function drawPuffyCloud(cx, cy, scale) {
        ctx.save();
        ctx.fillStyle = 'rgba(255, 255, 255, 0.88)';
        ctx.beginPath();
        ctx.arc(cx, cy, 26 * scale, 0, Math.PI * 2);
        ctx.arc(cx + 25 * scale, cy - 10 * scale, 32 * scale, 0, Math.PI * 2);
        ctx.arc(cx + 55 * scale, cy, 24 * scale, 0, Math.PI * 2);
        ctx.arc(cx + 26 * scale, cy + 12 * scale, 22 * scale, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }
      drawPuffyCloud(90, 80, 0.9);
      drawPuffyCloud(230, 110, 0.75);
      drawPuffyCloud(720, 75, 0.85);
      drawPuffyCloud(860, 105, 0.95);

      // 4. 金色奇妙仙子星芒魔法弧線 (Pixie Dust Arc)
      ctx.save();
      ctx.strokeStyle = 'rgba(255, 193, 7, 0.75)';
      ctx.lineWidth = 3.5;
      ctx.setLineDash([5, 6]);
      ctx.beginPath();
      ctx.moveTo(120, 270);
      ctx.quadraticCurveTo(V_WIDTH / 2 - 20, 45, V_WIDTH - 140, 250);
      ctx.stroke();
      ctx.setLineDash([]);

      const arcPoints = [0.15, 0.32, 0.48, 0.65, 0.82];
      arcPoints.forEach(t => {
        const ax = (1 - t) * (1 - t) * 120 + 2 * (1 - t) * t * (V_WIDTH / 2 - 20) + t * t * (V_WIDTH - 140);
        const ay = (1 - t) * (1 - t) * 270 + 2 * (1 - t) * t * 45 + t * t * 250;
        ctx.fillStyle = '#ffffff';
        drawStar(ctx, ax, ay, 4, 7, 2.5);
        ctx.fillStyle = '#ffb300';
        drawStar(ctx, ax, ay, 4, 4.5, 1.5);
      });
      drawStar(ctx, V_WIDTH - 140, 250, 5, 11, 4.5);
      ctx.restore();

      // 5. 陽光下明亮奪目的迪士尼奇幻城堡 (Bright Cinderella Castle)
      const cx = V_WIDTH / 2;
      const baseY = 360;

      // 城堡周圍金色晨曦光環
      const castleAura = ctx.createRadialGradient(cx, baseY - 120, 30, cx, baseY - 120, 280);
      castleAura.addColorStop(0, 'rgba(255, 255, 255, 0.75)');
      castleAura.addColorStop(0.5, 'rgba(255, 235, 160, 0.35)');
      castleAura.addColorStop(1, 'rgba(255, 255, 255, 0)');
      ctx.fillStyle = castleAura;
      ctx.beginPath();
      ctx.arc(cx, baseY - 120, 280, 0, Math.PI * 2);
      ctx.fill();

      // 遠方淡粉紫尖塔
      [-170, -115, 115, 170].forEach((ox, i) => {
        const towerW = 28;
        const towerH = 120 + (i % 2) * 25;
        ctx.fillStyle = '#fce7f3'; // 柔和淺粉白牆
        ctx.fillRect(cx + ox - towerW / 2, baseY - towerH, towerW, towerH);
        // 亮藍屋頂
        ctx.fillStyle = '#2563eb';
        ctx.beginPath();
        ctx.moveTo(cx + ox, baseY - towerH - 55);
        ctx.lineTo(cx + ox - towerW / 2 - 3, baseY - towerH);
        ctx.lineTo(cx + ox + towerW / 2 + 3, baseY - towerH);
        ctx.closePath();
        ctx.fill();
        // 金色尖塔小旗
        ctx.strokeStyle = '#f59e0b';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(cx + ox, baseY - towerH - 55);
        ctx.lineTo(cx + ox, baseY - towerH - 70);
        ctx.stroke();
      });

      // 城堡主體底座與淺象牙白磚牆
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(cx - 160, baseY - 80, 320, 80);
      ctx.fillStyle = '#f1f5f9';
      for (let bx = cx - 155; bx < cx + 155; bx += 22) {
        ctx.fillRect(bx, baseY - 94, 12, 14);
      }
      ctx.fillStyle = '#e2e8f0';
      ctx.fillRect(cx - 160, baseY - 25, 320, 6);

      // 左主副塔 (Left Royal Tower)
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(cx - 96, baseY - 160, 48, 160);
      ctx.fillStyle = '#f8fafc';
      ctx.fillRect(cx - 96, baseY - 160, 10, 160);
      ctx.fillStyle = '#1d4ed8'; // 蔚藍屋頂
      ctx.beginPath();
      ctx.moveTo(cx - 72, baseY - 245);
      ctx.lineTo(cx - 100, baseY - 160);
      ctx.lineTo(cx - 44, baseY - 160);
      ctx.closePath();
      ctx.fill();
      // 金旗
      ctx.strokeStyle = '#f59e0b';
      ctx.lineWidth = 2.4;
      ctx.beginPath();
      ctx.moveTo(cx - 72, baseY - 245);
      ctx.lineTo(cx - 72, baseY - 265);
      ctx.stroke();
      ctx.fillStyle = '#fbbf24';
      ctx.beginPath();
      ctx.moveTo(cx - 72, baseY - 265);
      ctx.lineTo(cx - 86, baseY - 257);
      ctx.lineTo(cx - 72, baseY - 250);
      ctx.fill();

      // 右主副塔 (Right Royal Tower)
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(cx + 48, baseY - 160, 48, 160);
      ctx.fillStyle = '#f8fafc';
      ctx.fillRect(cx + 86, baseY - 160, 10, 160);
      ctx.fillStyle = '#1d4ed8';
      ctx.beginPath();
      ctx.moveTo(cx + 72, baseY - 245);
      ctx.lineTo(cx + 44, baseY - 160);
      ctx.lineTo(cx + 100, baseY - 160);
      ctx.closePath();
      ctx.fill();
      // 金旗
      ctx.strokeStyle = '#f59e0b';
      ctx.lineWidth = 2.4;
      ctx.beginPath();
      ctx.moveTo(cx + 72, baseY - 245);
      ctx.lineTo(cx + 72, baseY - 265);
      ctx.stroke();
      ctx.fillStyle = '#fbbf24';
      ctx.beginPath();
      ctx.moveTo(cx + 72, baseY - 265);
      ctx.lineTo(cx + 86, baseY - 257);
      ctx.lineTo(cx + 72, baseY - 250);
      ctx.fill();

      // 中央高聳主鐘樓塔 (Grand Central Spire & Clock Tower)
      ctx.fillStyle = '#ffffff';
      ctx.fillRect(cx - 42, baseY - 200, 84, 200);
      ctx.fillStyle = '#fbbf24';
      ctx.fillRect(cx - 42, baseY - 170, 84, 6);
      ctx.fillRect(cx - 42, baseY - 120, 84, 6);

      // 中央主蔚藍尖塔
      ctx.fillStyle = '#1e40af';
      ctx.beginPath();
      ctx.moveTo(cx, baseY - 295);
      ctx.lineTo(cx - 46, baseY - 200);
      ctx.lineTo(cx + 46, baseY - 200);
      ctx.closePath();
      ctx.fill();

      // 金色高聳旗桿與大燕尾旗
      ctx.strokeStyle = '#f59e0b';
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.moveTo(cx, baseY - 295);
      ctx.lineTo(cx, baseY - 324);
      ctx.stroke();
      ctx.fillStyle = '#f59e0b';
      ctx.beginPath();
      ctx.moveTo(cx, baseY - 324);
      ctx.lineTo(cx + 22, baseY - 314);
      ctx.lineTo(cx, baseY - 304);
      ctx.fill();

      // 迪士尼城堡陽光金光大拱門
      ctx.fillStyle = '#fef08a';
      ctx.beginPath();
      ctx.arc(cx, baseY - 30, 24, Math.PI, 0);
      ctx.lineTo(cx + 24, baseY);
      ctx.lineTo(cx - 24, baseY);
      ctx.closePath();
      ctx.fill();

      // 鐘樓圓形金光花窗
      ctx.beginPath();
      ctx.arc(cx, baseY - 150, 16, 0, Math.PI * 2);
      ctx.fill();
      ctx.strokeStyle = '#d97706';
      ctx.lineWidth = 2.5;
      ctx.stroke();

      // 6. 明亮繽紛的米奇造型氣球
      function drawMickeyBalloon(bx, by, size, color) {
        ctx.save();
        ctx.fillStyle = color;
        ctx.shadowColor = 'rgba(0,0,0,0.15)';
        ctx.shadowBlur = 6;
        ctx.beginPath();
        ctx.arc(bx, by, size, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(bx - size * 0.72, by - size * 0.72, size * 0.5, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(bx + size * 0.72, by - size * 0.72, size * 0.5, 0, Math.PI * 2);
        ctx.fill();
        // 氣球亮光
        ctx.fillStyle = 'rgba(255, 255, 255, 0.65)';
        ctx.beginPath();
        ctx.arc(bx - size * 0.35, by - size * 0.35, size * 0.28, 0, Math.PI * 2);
        ctx.fill();
        // 氣球牽引白線
        ctx.strokeStyle = 'rgba(100, 116, 139, 0.65)';
        ctx.lineWidth = 1.2;
        ctx.beginPath();
        ctx.moveTo(bx, by + size);
        ctx.quadraticCurveTo(bx + 4, by + size + 16, bx - 2, by + size + 35);
        ctx.stroke();
        ctx.restore();
      }

      drawMickeyBalloon(105, 190, 15, '#ef4444'); // 明亮紅米奇氣球
      drawMickeyBalloon(140, 170, 12, '#22c55e'); // 鮮綠米奇氣球
      drawMickeyBalloon(825, 175, 15, '#3b82f6'); // 亮藍米奇氣球
      drawMickeyBalloon(865, 195, 12, '#f59e0b'); // 鮮黃米奇氣球

      // 7. 城堡基座青翠童話小草坡
      ctx.fillStyle = '#4ade80';
      ctx.beginPath();
      ctx.ellipse(cx - 210, baseY + 10, 140, 42, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.beginPath();
      ctx.ellipse(cx + 210, baseY + 10, 140, 42, 0, 0, Math.PI * 2);
      ctx.fill();
      // 小米奇造型綠雕
      function drawMickeyTopiary(tx, ty, r) {
        ctx.fillStyle = '#16a34a';
        ctx.beginPath();
        ctx.arc(tx, ty, r, 0, Math.PI * 2);
        ctx.arc(tx - r * 0.7, ty - r * 0.7, r * 0.48, 0, Math.PI * 2);
        ctx.arc(tx + r * 0.7, ty - r * 0.7, r * 0.48, 0, Math.PI * 2);
        ctx.fill();
      }
      drawMickeyTopiary(cx - 240, baseY - 12, 16);
      drawMickeyTopiary(cx + 240, baseY - 12, 16);

      // 8. 迪士尼樂園明亮彩色三角旗與彩燈串 (Bunting Flags)
      ctx.strokeStyle = 'rgba(71, 85, 105, 0.4)';
      ctx.lineWidth = 1.6;
      ctx.beginPath();
      ctx.moveTo(30, 108);
      ctx.quadraticCurveTo(V_WIDTH / 4, 138, V_WIDTH / 2, 108);
      ctx.quadraticCurveTo((V_WIDTH * 3) / 4, 138, V_WIDTH - 30, 108);
      ctx.stroke();

      const flagCount = 26;
      for (let i = 0; i <= flagCount; i++) {
        const t = i / flagCount;
        let lx, ly;
        if (t <= 0.5) {
          const lt = t * 2;
          lx = (1 - lt) * (1 - lt) * 30 + 2 * (1 - lt) * lt * (V_WIDTH / 4) + lt * lt * (V_WIDTH / 2);
          ly = (1 - lt) * (1 - lt) * 108 + 2 * (1 - lt) * lt * 138 + lt * lt * 108;
        } else {
          const lt = (t - 0.5) * 2;
          lx = (1 - lt) * (1 - lt) * (V_WIDTH / 2) + 2 * (1 - lt) * lt * ((V_WIDTH * 3) / 4) + lt * lt * (V_WIDTH - 30);
          ly = (1 - lt) * (1 - lt) * 108 + 2 * (1 - lt) * lt * 138 + lt * lt * 108;
        }
        const colors = ['#ef4444', '#f59e0b', '#22c55e', '#3b82f6', '#ec4899', '#a855f7'];
        ctx.fillStyle = colors[i % colors.length];
        ctx.beginPath();
        // 懸掛歡樂三角彩旗
        ctx.moveTo(lx - 5, ly);
        ctx.lineTo(lx + 5, ly);
        ctx.lineTo(lx, ly + 10);
        ctx.closePath();
        ctx.fill();
      }

      // 9. 陽光慶典彩帶與星芒
      updateAndDrawFireworks(ctx);

      ctx.restore();
    }

    function drawEuropeanAwning(ctx, width) {
      const stripWidth = 40;
      const strips = Math.ceil(width / stripWidth);
      ctx.save();
      for (let i = 0; i < strips; i++) {
        ctx.fillStyle = i % 2 === 0 ? '#7a1c29' : '#faf5eb';
        ctx.beginPath();
        ctx.rect(i * stripWidth, 0, stripWidth, 42);
        ctx.fill();

        ctx.beginPath();
        ctx.arc(i * stripWidth + stripWidth / 2, 42, stripWidth / 2, 0, Math.PI);
        ctx.fill();
        ctx.strokeStyle = '#4a0e16';
        ctx.lineWidth = 1.5;
        ctx.stroke();
      }

      ctx.fillStyle = '#cfa144';
      ctx.fillRect(0, 40, width, 4);
      ctx.restore();
    }

    // 羊皮紙魔法橫幅繪製 (修復先前遺漏)
    function drawScrollBanner(ctx, cx, cy, w, h, title, subtitle) {
      ctx.save();
      ctx.translate(cx, cy);

      ctx.fillStyle = 'rgba(255, 252, 240, 0.94)';
      ctx.strokeStyle = '#d4af37';
      ctx.lineWidth = 3;
      ctx.shadowColor = 'rgba(255, 215, 0, 0.45)';
      ctx.shadowBlur = 14;
      ctx.beginPath();
      ctx.roundRect(-w / 2, -h / 2, w, h, 16);
      ctx.fill();
      ctx.stroke();
      ctx.shadowColor = 'transparent';

      ctx.strokeStyle = 'rgba(212, 175, 55, 0.45)';
      ctx.lineWidth = 1.2;
      ctx.beginPath();
      ctx.roundRect(-w / 2 + 5, -h / 2 + 5, w - 10, h - 10, 12);
      ctx.stroke();

      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      if (subtitle) {
        ctx.font = '900 20px "Noto Serif TC", serif';
        ctx.fillStyle = '#220a40';
        ctx.fillText(title, 0, -h * 0.2);
        ctx.font = '700 15px "Noto Serif TC", serif';
        ctx.fillStyle = '#6a4a10';
        ctx.fillText(subtitle, 0, h * 0.25);
      } else {
        ctx.font = '900 22px "Noto Serif TC", serif';
        ctx.fillStyle = '#220a40';
        ctx.fillText(title, 0, 1);
      }
      ctx.restore();
    }

    // 繪製米奇圖案徽章
    function drawMickeyIcon(ctx, x, y, size, color = '#1e293b') {
      ctx.save();
      ctx.fillStyle = color;
      ctx.beginPath();
      ctx.arc(x, y, size, 0, Math.PI * 2);
      ctx.fill();
      ctx.beginPath();
      ctx.arc(x - size * 0.78, y - size * 0.78, size * 0.52, 0, Math.PI * 2);
      ctx.fill();
      ctx.beginPath();
      ctx.arc(x + size * 0.78, y - size * 0.78, size * 0.52, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    // 繪製米妮經典粉紅波點蝴蝶結
    function drawMinnieBow(ctx, x, y, scale = 1) {
      ctx.save();
      ctx.translate(x, y);
      ctx.scale(scale, scale);

      // 左側緞帶結
      ctx.fillStyle = '#f43f5e';
      ctx.strokeStyle = '#be123c';
      ctx.lineWidth = 1.6;
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.bezierCurveTo(-8, -12, -18, -10, -16, 0);
      ctx.bezierCurveTo(-18, 10, -8, 12, 0, 0);
      ctx.fill();
      ctx.stroke();

      // 右側緞帶結
      ctx.beginPath();
      ctx.moveTo(0, 0);
      ctx.bezierCurveTo(8, -12, 18, -10, 16, 0);
      ctx.bezierCurveTo(18, 10, 8, 12, 0, 0);
      ctx.fill();
      ctx.stroke();

      // 白色波點
      ctx.fillStyle = '#ffffff';
      [-11, 11].forEach(px => {
        ctx.beginPath();
        ctx.arc(px, -2, 1.8, 0, Math.PI * 2);
        ctx.fill();
      });

      // 中央結心
      ctx.fillStyle = '#e11d48';
      ctx.beginPath();
      ctx.ellipse(0, 0, 4.5, 5.5, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.stroke();

      ctx.restore();
    }

    // 繪製米妮圖案徽章 (米奇耳 + 經典粉紅蝴蝶結)
    function drawMinnieIcon(ctx, x, y, size) {
      ctx.save();
      drawMickeyIcon(ctx, x, y, size, '#db2777');
      drawMinnieBow(ctx, x, y - size * 0.85, size * 0.08);
      ctx.restore();
    }

    const navButtons = [
      { stage: 1, text: "🏰 第 I 關:迪士尼魔法錢幣", x: 245, y: 76, w: 230, h: 38 },
      { stage: 2, text: "🎀 第 II 關:十位與個位數", x: 490, y: 76, w: 220, h: 38 },
      { stage: 3, text: "🎪 第 III 關:市集買賣", x: 725, y: 76, w: 210, h: 38 }
    ];

    function drawNavHeader(ctx) {
      drawEuropeanAwning(ctx, V_WIDTH);

      navButtons.forEach(btn => {

        const isActive = currentStage === btn.stage;
        ctx.save();
        ctx.beginPath();
        ctx.roundRect(btn.x - btn.w / 2, btn.y - btn.h / 2, btn.w, btn.h, 19);

        if (isActive) {
          ctx.fillStyle = '#2a3d66';
          ctx.shadowColor = 'rgba(0,0,0,0.3)';
          ctx.shadowBlur = 8;
          ctx.shadowOffsetY = 3;
          ctx.fill();
          ctx.strokeStyle = '#cfa144';
          ctx.lineWidth = 2.5;
          ctx.stroke();

          ctx.fillStyle = '#fdf4dc';
          ctx.font = '900 15px "Noto Serif TC", serif';
        } else {
          ctx.fillStyle = '#f8f2e4';
          ctx.fill();
          ctx.strokeStyle = '#b8a68d';
          ctx.lineWidth = 1.5;
          ctx.stroke();

          ctx.fillStyle = '#4a3d36';
          ctx.font = '700 14px "Noto Serif TC", serif';
        }

        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(btn.text, btn.x, btn.y + 1);
        ctx.restore();
      });
    }

    function drawRealTaiwanPortrait(ctx, type, radius, metalColor) {
      ctx.save();
      ctx.fillStyle = metalColor.reliefDark;

      if (type === 'chiang') {
        ctx.beginPath();
        ctx.arc(-radius * 0.05, -radius * 0.22, radius * 0.32, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.moveTo(-radius * 0.08, -radius * 0.4);
        ctx.quadraticCurveTo(-radius * 0.45, -radius * 0.22, -radius * 0.44, -radius * 0.12);
        ctx.lineTo(-radius * 0.35, -radius * 0.08);
        ctx.lineTo(-radius * 0.38, -radius * 0.02);
        ctx.quadraticCurveTo(-radius * 0.36, radius * 0.12, -radius * 0.18, radius * 0.18);
        ctx.lineTo(radius * 0.12, radius * 0.24);
        ctx.lineTo(radius * 0.24, -radius * 0.15);
        ctx.closePath();
        ctx.fill();

        ctx.beginPath();
        ctx.moveTo(-radius * 0.32, radius * 0.22);
        ctx.lineTo(radius * 0.35, radius * 0.28);
        ctx.lineTo(radius * 0.28, radius * 0.48);
        ctx.lineTo(-radius * 0.25, radius * 0.48);
        ctx.closePath();
        ctx.fill();

        ctx.fillStyle = metalColor.reliefLight;
        ctx.beginPath();
        ctx.arc(-radius * 0.07, -radius * 0.24, radius * 0.26, 0, Math.PI * 2);
        ctx.fill();
      } else if (type === 'sun') {
        ctx.beginPath();
        ctx.arc(0, -radius * 0.18, radius * 0.35, 0, Math.PI * 2);
        ctx.fill();

        ctx.beginPath();
        ctx.moveTo(-radius * 0.5, radius * 0.46);
        ctx.lineTo(-radius * 0.22, radius * 0.16);
        ctx.lineTo(0, radius * 0.32);
        ctx.lineTo(radius * 0.22, radius * 0.16);
        ctx.lineTo(radius * 0.5, radius * 0.46);
        ctx.closePath();
        ctx.fill();

        ctx.fillRect(-radius * 0.18, -radius * 0.05, radius * 0.36, radius * 0.08);

        ctx.fillStyle = metalColor.reliefLight;
        ctx.beginPath();
        ctx.arc(0, -radius * 0.2, radius * 0.28, 0, Math.PI * 2);
        ctx.fill();
      }
      ctx.restore();
    }

    function drawDetailedCoin(ctx, x, y, val, radius, isPressed = false) {
      ctx.save();
      ctx.translate(x, isPressed ? y + 3 : y);

      ctx.shadowColor = 'rgba(0, 0, 0, 0.45)';
      ctx.shadowBlur = isPressed ? 4 : 9;
      ctx.shadowOffsetY = isPressed ? 2 : 5;

      ctx.beginPath();
      ctx.arc(0, 0, radius, 0, Math.PI * 2);

      const isReverse = (coinDisplaySide === 'reverse');

      if (val === 1) {
        // 1 元:真實新臺幣紅銅幣 (直徑 20mm)
        const grad = ctx.createRadialGradient(-radius * 0.35, -radius * 0.35, radius * 0.08, 0, 0, radius);
        grad.addColorStop(0, '#ffa889');
        grad.addColorStop(0.28, '#e06f47');
        grad.addColorStop(0.72, '#b24320');
        grad.addColorStop(0.96, '#7d260e');
        grad.addColorStop(1, '#501708');
        ctx.fillStyle = grad;
        ctx.fill();
        ctx.shadowColor = 'transparent';

        ctx.strokeStyle = '#611b08';
        ctx.lineWidth = Math.max(1.8, radius * 0.065);
        ctx.stroke();

        ctx.beginPath();
        ctx.arc(0, 0, radius * 0.91, 0, Math.PI * 2);
        ctx.strokeStyle = 'rgba(255, 206, 186, 0.5)';
        ctx.lineWidth = Math.max(1, radius * 0.035);
        ctx.stroke();

        if (isReverse) {
          if (radius >= 22) {
            const teeth = 36;
            ctx.fillStyle = 'rgba(255, 215, 195, 0.45)';
            for (let i = 0; i < teeth; i++) {
              const a = (i * Math.PI * 2) / teeth;
              const ox = Math.cos(a) * (radius * 0.86);
              const oy = Math.sin(a) * (radius * 0.86);
              ctx.beginPath();
              ctx.arc(ox, oy, Math.max(1, radius * 0.035), 0, Math.PI * 2);
              ctx.fill();
            }
          }

          const textSize = Math.round(radius * 0.32);
          ctx.font = `900 ${textSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillStyle = '#421104';
          ctx.fillText('壹圓', 0, -radius * 0.38 + 1.2);
          ctx.fillStyle = '#ffded0';
          ctx.fillText('壹圓', 0, -radius * 0.38);

          const numSize = Math.round(radius * 0.72);
          ctx.font = `900 ${numSize}px "Cinzel", "Times New Roman", serif`;
          ctx.fillStyle = '#471305';
          ctx.fillText('1', 0, radius * 0.18 + 1.5);
          ctx.fillStyle = '#ffeedd';
          ctx.fillText('1', 0, radius * 0.18);
        } else {
          drawRealTaiwanPortrait(ctx, 'chiang', radius, { reliefDark: '#4a1406', reliefLight: '#ffbeaa' });
          const textSize = Math.round(radius * 0.22);
          ctx.font = `900 ${textSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.fillStyle = '#421104';
          ctx.fillText('中華民國', 0, -radius * 0.65 + 1);
          ctx.fillStyle = '#ffeedd';
          ctx.fillText('中華民國', 0, -radius * 0.65);
        }

      } else if (val === 5) {
        // 5 元:真實新臺幣鎳白幣 (直徑 22mm)
        const grad = ctx.createRadialGradient(-radius * 0.35, -radius * 0.35, radius * 0.08, 0, 0, radius);
        grad.addColorStop(0, '#ffffff');
        grad.addColorStop(0.25, '#f0f3f6');
        grad.addColorStop(0.65, '#c5ccd3');
        grad.addColorStop(0.9, '#8f99a3');
        grad.addColorStop(1, '#5f6770');
        ctx.fillStyle = grad;
        ctx.fill();
        ctx.shadowColor = 'transparent';

        ctx.strokeStyle = '#4e555d';
        ctx.lineWidth = Math.max(2, radius * 0.07);
        ctx.stroke();

        ctx.beginPath();
        ctx.arc(0, 0, radius * 0.9, 0, Math.PI * 2);
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.85)';
        ctx.lineWidth = Math.max(1, radius * 0.04);
        ctx.stroke();

        if (isReverse) {
          if (radius >= 22) {
            ctx.save();
            ctx.strokeStyle = 'rgba(92, 100, 109, 0.55)';
            ctx.lineWidth = Math.max(1, radius * 0.035);
            for (let i = 0; i < 8; i++) {
              const a = (i * Math.PI * 2) / 8;
              const kx = Math.cos(a) * (radius * 0.77);
              const ky = Math.sin(a) * (radius * 0.77);
              ctx.beginPath();
              ctx.arc(kx, ky, radius * 0.08, 0, Math.PI * 1.5);
              ctx.stroke();
            }
            ctx.restore();
          }

          const textSize = Math.round(radius * 0.32);
          ctx.font = `900 ${textSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillStyle = '#424850';
          ctx.fillText('伍圓', 0, -radius * 0.38 + 1.2);
          ctx.fillStyle = '#ffffff';
          ctx.fillText('伍圓', 0, -radius * 0.38);

          const numSize = Math.round(radius * 0.72);
          ctx.font = `900 ${numSize}px "Cinzel", "Times New Roman", serif`;
          ctx.fillStyle = '#424850';
          ctx.fillText('5', 0, radius * 0.18 + 1.5);
          ctx.fillStyle = '#ffffff';
          ctx.fillText('5', 0, radius * 0.18);
        } else {
          drawRealTaiwanPortrait(ctx, 'chiang', radius, { reliefDark: '#3e444c', reliefLight: '#ffffff' });
          const textSize = Math.round(radius * 0.22);
          ctx.font = `900 ${textSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.fillStyle = '#424850';
          ctx.fillText('中華民國', 0, -radius * 0.65 + 1);
          ctx.fillStyle = '#ffffff';
          ctx.fillText('中華民國', 0, -radius * 0.65);
        }

      } else if (val === 10) {
        // 10 元:真實新臺幣鎳銅合金幣 (直徑 26mm,介於 5 元與 50 元之間)
        const grad = ctx.createRadialGradient(-radius * 0.35, -radius * 0.35, radius * 0.08, 0, 0, radius);
        grad.addColorStop(0, '#ffffff');
        grad.addColorStop(0.24, '#edf2f7');
        grad.addColorStop(0.66, '#b8c3cd');
        grad.addColorStop(0.92, '#818d99');
        grad.addColorStop(1, '#56606a');
        ctx.fillStyle = grad;
        ctx.fill();
        ctx.shadowColor = 'transparent';

        // 外緣滾齒與邊框
        ctx.strokeStyle = '#48525b';
        ctx.lineWidth = Math.max(2, radius * 0.068);
        ctx.stroke();

        ctx.beginPath();
        ctx.arc(0, 0, radius * 0.91, 0, Math.PI * 2);
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.88)';
        ctx.lineWidth = Math.max(1, radius * 0.038);
        ctx.stroke();

        if (isReverse) {
          // 防偽同心微紋內圈
          if (radius >= 24) {
            ctx.save();
            ctx.strokeStyle = 'rgba(100, 110, 122, 0.4)';
            ctx.lineWidth = 1;
            ctx.beginPath();
            ctx.arc(0, radius * 0.08, radius * 0.42, 0, Math.PI * 2);
            ctx.stroke();
            ctx.restore();
          }

          // 上方「10圓」字樣
          const topSize = Math.round(radius * 0.28);
          ctx.font = `900 ${topSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillStyle = '#3c434a';
          ctx.fillText('10圓', 0, -radius * 0.45 + 1.2);
          ctx.fillStyle = '#ffffff';
          ctx.fillText('10圓', 0, -radius * 0.45);

          // 中央大「10」
          const numSize = Math.round(radius * 0.68);
          ctx.font = `900 ${numSize}px "Cinzel", "Times New Roman", serif`;
          ctx.fillStyle = '#3c434a';
          ctx.fillText('10', 0, radius * 0.16 + 1.5);
          ctx.fillStyle = '#ffffff';
          ctx.fillText('10', 0, radius * 0.16);
        } else {
          // 正面國父孫中山先生半身肖像
          drawRealTaiwanPortrait(ctx, 'sun', radius, { reliefDark: '#3a4149', reliefLight: '#ffffff' });
          const textSize = Math.round(radius * 0.22);
          ctx.font = `900 ${textSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.fillStyle = '#3c434a';
          ctx.fillText('中華民國', 0, -radius * 0.65 + 1);
          ctx.fillStyle = '#ffffff';
          ctx.fillText('中華民國', 0, -radius * 0.65);
        }

      } else if (val === 50) {
        // 50 元:真實新臺幣金黃大幣 (直徑 28mm)
        const grad = ctx.createRadialGradient(-radius * 0.32, -radius * 0.32, radius * 0.06, 0, 0, radius);
        grad.addColorStop(0, '#fffbe0');
        grad.addColorStop(0.22, '#fedc65');
        grad.addColorStop(0.68, '#d49b14');
        grad.addColorStop(0.92, '#966a05');
        grad.addColorStop(1, '#664500');
        ctx.fillStyle = grad;
        ctx.fill();
        ctx.shadowColor = 'transparent';

        ctx.save();
        ctx.strokeStyle = '#734e00';
        ctx.lineWidth = Math.max(2, radius * 0.06);
        ctx.setLineDash([Math.max(2.5, radius * 0.07), Math.max(2, radius * 0.055)]);
        ctx.beginPath();
        ctx.arc(0, 0, radius * 0.94, 0, Math.PI * 2);
        ctx.stroke();
        ctx.restore();

        ctx.beginPath();
        ctx.arc(0, 0, radius * 0.84, 0, Math.PI * 2);
        ctx.strokeStyle = 'rgba(255, 245, 170, 0.75)';
        ctx.lineWidth = Math.max(1.2, radius * 0.035);
        ctx.stroke();

        if (isReverse) {
          const topTextSize = Math.round(radius * 0.24);
          ctx.font = `900 ${topTextSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillStyle = '#523400';
          ctx.fillText('50圓', 0, -radius * 0.58 + 1.2);
          ctx.fillStyle = '#fffce0';
          ctx.fillText('50圓', 0, -radius * 0.58);

          // 稻穗浮雕
          if (radius >= 26) {
            ctx.save();
            function drawRiceEarStalk(side) {
              const grainCount = 5;
              for (let i = 0; i < grainCount; i++) {
                const t = i / (grainCount - 1);
                const angle = side * (0.35 + t * 0.7) * Math.PI;
                const gr = radius * 0.65;
                const gx = Math.cos(angle) * gr;
                const gy = Math.sin(angle) * gr * 0.82;

                ctx.save();
                ctx.translate(gx, gy);
                ctx.rotate(angle + (side > 0 ? 0.95 : -0.95));

                ctx.fillStyle = '#6e4800';
                ctx.beginPath();
                ctx.ellipse(0, 1, radius * 0.095, radius * 0.048, 0, 0, Math.PI * 2);
                ctx.fill();

                ctx.fillStyle = '#fffca8';
                ctx.beginPath();
                ctx.ellipse(0, 0, radius * 0.09, radius * 0.042, 0, 0, Math.PI * 2);
                ctx.fill();
                ctx.restore();
              }
            }
            drawRiceEarStalk(1);
            drawRiceEarStalk(-1);
            ctx.restore();
          }

          // 防偽光學橢圓窗
          if (radius >= 26) {
            ctx.save();
            ctx.fillStyle = 'rgba(107, 72, 0, 0.22)';
            ctx.strokeStyle = 'rgba(255, 230, 130, 0.7)';
            ctx.lineWidth = 1.2;
            ctx.beginPath();
            ctx.ellipse(0, -radius * 0.05, radius * 0.44, radius * 0.32, 0, 0, Math.PI * 2);
            ctx.fill();
            ctx.stroke();

            ctx.strokeStyle = 'rgba(255, 240, 150, 0.32)';
            ctx.lineWidth = 0.8;
            for (let ly = -radius * 0.28; ly <= radius * 0.18; ly += radius * 0.055) {
              ctx.beginPath();
              ctx.moveTo(-radius * 0.32, ly);
              ctx.lineTo(radius * 0.32, ly);
              ctx.stroke();
            }

            ctx.font = `900 ${Math.round(radius * 0.38)}px "Cinzel", serif`;
            ctx.fillStyle = '#fffdf0';
            ctx.fillText('50', 0, -radius * 0.05);
            ctx.restore();
          }

          // 盲人觸摸點
          if (radius >= 26) {
            ctx.fillStyle = '#523400';
            const dotOffsets = [-radius * 0.22, 0, radius * 0.22];
            dotOffsets.forEach(dx => {
              ctx.beginPath();
              ctx.arc(dx, radius * 0.58 + 1, radius * 0.042, 0, Math.PI * 2);
              ctx.fill();
            });
            ctx.fillStyle = '#fffce0';
            dotOffsets.forEach(dx => {
              ctx.beginPath();
              ctx.arc(dx, radius * 0.58, radius * 0.04, 0, Math.PI * 2);
              ctx.fill();
            });
          }
        } else {
          drawRealTaiwanPortrait(ctx, 'sun', radius, { reliefDark: '#5e3c00', reliefLight: '#fff7ba' });
          const textSize = Math.round(radius * 0.22);
          ctx.font = `900 ${textSize}px "Noto Serif TC", serif`;
          ctx.textAlign = 'center';
          ctx.fillStyle = '#523400';
          ctx.fillText('中華民國', 0, -radius * 0.65 + 1);
          ctx.fillStyle = '#fffce0';
          ctx.fillText('中華民國', 0, -radius * 0.65);
        }
      }

      ctx.restore();
    }

    // 第一關資料
    let s1Coins = [];
    let s1TargetCoin = { val: 50, name: "50 元", spoken: "五十元", hint: "金色大硬幣,背面有金黃稻穗" };
    let s1Locked = false;

    function initStage1() {
      s1Locked = false;
      // 依幣面真實直徑等比排列:1元(小), 5元(中), 10元(大銀幣), 50元(特大金幣)
      s1Coins = [
        { val: 1, name: "1 元", spoken: "一元", x: 190, y: 395, r: 42 },
        { val: 5, name: "5 元", spoken: "五元", x: 370, y: 392, r: 48 },
        { val: 10, name: "10 元", spoken: "十元", x: 560, y: 388, r: 56 },
        { val: 50, name: "50 元", spoken: "五十元", x: 765, y: 380, r: 66 }
      ];
      const targets = [
        { val: 1, name: "1 元", spoken: "一元", hint: "找找看最小顆、紅銅色、上面寫著 1 的硬幣喔!" },
        { val: 5, name: "5 元", spoken: "五元", hint: "找找看銀白色、比 1 元大一點、上面寫著 5 的硬幣!" },
        { val: 10, name: "10 元", spoken: "十元", hint: "找找看銀白色、比 5 元更大、上面寫著 10 的硬幣喔!" },
        { val: 50, name: "50 元", spoken: "五十元", hint: "找找看最大顆、金黃色、兩旁有金黃稻穗的 50 元金幣!" }
      ];
      s1TargetCoin = targets[Math.floor(Math.random() * targets.length)];
      speak(`請幫忙找出${s1TargetCoin.spoken}硬幣!`);
    }

    // 第二關資料:1-100 金額產生庫 (大、中、小三層洗牌牌組,不連續重複)
    const s2SmallPool = [1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20];
    const s2MidPool = [21, 23, 25, 26, 28, 30, 32, 35, 36, 38, 40, 42, 45, 47, 50, 52, 55, 56, 58, 60];
    const s2LargePool = [62, 65, 67, 70, 72, 75, 76, 78, 80, 82, 85, 86, 88, 90, 92, 95, 96, 98, 99, 100];

    let s2Decks = {
      small: [],
      mid: [],
      large: []
    };
    let s2TierCycle = ['small', 'large', 'mid', 'small', 'mid', 'large'];
    let s2TierIndex = 0;
    let s2LastGoal = null;

    function s2Shuffle(arr) {
      const copy = [...arr];
      for (let i = copy.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [copy[i], copy[j]] = [copy[j], copy[i]];
      }
      return copy;
    }

    function getNextS2Goal() {
      // 輪替大、中、小金額階層,確保題目有大有小、跳躍豐富且不一直重複
      const tier = s2TierCycle[s2TierIndex % s2TierCycle.length];
      s2TierIndex++;

      if (!s2Decks[tier] || s2Decks[tier].length === 0) {
        let pool = (tier === 'small') ? s2SmallPool : (tier === 'mid' ? s2MidPool : s2LargePool);
        let shuffled = s2Shuffle(pool);
        // 避免重新洗牌後抽出的第一題與上一題相同
        if (shuffled[0] === s2LastGoal && shuffled.length > 1) {
          [shuffled[0], shuffled[1]] = [shuffled[1], shuffled[0]];
        }
        s2Decks[tier] = shuffled;
      }

      let goal = s2Decks[tier].pop();
      if (goal === s2LastGoal && s2Decks[tier].length > 0) {
        const alt = s2Decks[tier].pop();
        s2Decks[tier].push(goal);
        goal = alt;
      }
      s2LastGoal = goal;
      return goal;
    }

    let s2Goal = 56;
    let s2TensCoins = []; // 可放 50元 與 10元
    let s2OnesCoins = []; // 可放 5元 與 1元
    let s2Locked = false;

    function initStage2() {
      s2Locked = false;
      s2Goal = getNextS2Goal();
      s2TensCoins = [];
      s2OnesCoins = [];
      feedback.text = '';

      const tensGoal = Math.floor(s2Goal / 10) * 10;
      const onesGoal = s2Goal % 10;
      let speechPrompt = `金額 ${s2Goal} 元,請在十位放入十位硬幣,個位放入個位硬幣`;
      if (tensGoal === 0) {
        speechPrompt = `金額 ${s2Goal} 元,十位不用放,請在個位放入個位硬幣`;
      } else if (onesGoal === 0) {
        speechPrompt = `金額 ${s2Goal} 元,請在十位放入十位硬幣,個位不用放`;
      }
      speak(speechPrompt, null, 0.72);
    }

    // 第三關資料:迪士尼卡通人物走進店裡購買食材 (櫃台、食材標價1-100元、答對放煙火說謝謝光臨)
    const ingredientCategories = [
      {
        categoryName: "🥛 每日新鮮乳品與農場食材",
        items: [
          { id: 101, name: "優質農場鮮雞蛋", price: 5, icon: "🥚" },
          { id: 102, name: "特級純純鮮奶", price: 18, icon: "🥛" },
          { id: 103, name: "發酵草飼奶油", price: 28, icon: "🧈" },
          { id: 104, name: "切達濃郁起司", price: 45, icon: "🧀" },
          { id: 105, name: "皇家鮮奶油大桶", price: 68, icon: "🍦" }
        ]
      },
      {
        categoryName: "🍎 迪士尼果園鮮採蔬果",
        items: [
          { id: 201, name: "陽光脆胡蘿蔔", price: 4, icon: "🥕" },
          { id: 202, name: "香甜蜜富士蘋果", price: 9, icon: "🍎" },
          { id: 203, name: "白蘑菇鮮採包", price: 15, icon: "🍄" },
          { id: 204, name: "鮮摘特級紅草莓", price: 36, icon: "🍓" },
          { id: 205, name: "小熊百花純蜂蜜", price: 75, icon: "🍯" }
        ]
      },
      {
        categoryName: "🌾 奇幻烘焙穀物與頂級調料",
        items: [
          { id: 301, name: "黃金多汁香檸檬", price: 3, icon: "🍋" },
          { id: 302, name: "特級白白有機麵粉", price: 12, icon: "🌾" },
          { id: 303, name: "綜合堅果烘焙包", price: 32, icon: "🌰" },
          { id: 304, name: "比利時黑巧克力磚", price: 56, icon: "🍫" },
          { id: 305, name: "頂級加拿大楓糖漿", price: 88, icon: "🍁" }
        ]
      },
      {
        categoryName: "🎉 皇家盛宴特選豪華食材",
        items: [
          { id: 401, name: "鮮甜紫皮洋蔥", price: 6, icon: "🧅" },
          { id: 402, name: "高山野生藍莓盒", price: 22, icon: "🫐" },
          { id: 403, name: "金色萬聖大南瓜", price: 48, icon: "🎃" },
          { id: 404, name: "進口紅寶石櫻桃", price: 65, icon: "🍒" },
          { id: 405, name: "慶典海陸食材大禮盒", price: 100, icon: "🦞" }
        ]
      }
    ];

    // 迪士尼經典卡通人物小顧客陣容
    const disneyCustomers = [
      { id: 'minnie', name: '米妮', type: 'minnie', tag: '🎀 迪士尼米妮', quote: '老闆,我想買草莓做蛋糕!', targetIdx: 3 },
      { id: 'donald', name: '唐老鴨', type: 'donald', tag: '⚓ 迪士尼唐老鴨', quote: '呱呱!我要買麵粉做煎餅!', targetIdx: 1 },
      { id: 'pooh', name: '小熊維尼', type: 'pooh', tag: '🍯 迪士尼維尼', quote: '我想買甜甜的百花蜂蜜!', targetIdx: 4 },
      { id: 'goofy', name: '高飛狗', type: 'goofy', tag: '🎩 迪士尼高飛', quote: '啊哈!我要買大南瓜煮濃湯!', targetIdx: 2 },
      { id: 'tigger', name: '跳跳虎', type: 'tigger', tag: '🐯 迪士尼跳跳虎', quote: '跳跳~我想買濃郁起司!', targetIdx: 3 },
      { id: 'daisy', name: '黛西', type: 'daisy', tag: '💜 迪士尼黛西', quote: '老闆,我想買進口紅櫻桃!', targetIdx: 3 },
      { id: 'chip', name: '奇奇蒂蒂', type: 'chip', tag: '🌰 迪士尼松鼠', quote: '吱吱!我們要買綜合堅果!', targetIdx: 2 }
    ];

    let currentCategoryIndex = 0;
    let currentCustomerIndex = 0;
    let currentMarketItems = [];
    let currentCustomer = disneyCustomers[0];
    let selectedShopItem = ingredientCategories[0].items[0];
    let insertedRegisterCoins = [];
    let s3Locked = false;
    let s3Celebrating = false;

    // 卡通人物走進店裡動畫變數
    let s3CustomerWalkX = 125;
    let s3CustomerTargetX = 125;
    let s3CustomerIsWalking = true;
    let s3WalkStep = 0;

    function setupStage3Scene(catIdx, custIdx) {
      currentCategoryIndex = catIdx % ingredientCategories.length;
      currentCustomerIndex = custIdx % disneyCustomers.length;
      currentCustomer = disneyCustomers[currentCustomerIndex];

      const category = ingredientCategories[currentCategoryIndex];
      const startX = 136;
      const gap = 172;
      currentMarketItems = category.items.map((item, idx) => ({
        ...item,
        x: Math.round(startX + idx * gap),
        y: 228,
        w: 156,
        h: 98
      }));

      const targetIdx = currentCustomer.targetIdx % currentMarketItems.length;
      selectedShopItem = currentMarketItems[targetIdx];
      insertedRegisterCoins = [];
      s3Locked = false;
      s3Celebrating = false;

      // 觸發走進店裡面動畫
      s3CustomerWalkX = 125;
s3CustomerIsWalking = false;
      s3CustomerTargetX = 125;
      s3CustomerIsWalking = true;
      s3WalkStep = 0;
    }

    function initStage3() {
      setupStage3Scene(currentCategoryIndex, currentCustomerIndex);
      feedback.text = '';
      speak(`歡迎光臨迪士尼食材店!${currentCustomer.name} 走進店裡想買【${selectedShopItem.name}】,標價是 ${selectedShopItem.price} 元,請在櫃台托盤放入剛剛好的錢幣!`);
    }

    function advanceStage3Scene() {
      s3CustomerWalkX = -40; s3CustomerIsWalking = true;
      // 輪替到下一位迪士尼卡通人物走進店裡,並換下一批食材(全部換圖片!)
      currentCustomerIndex = (currentCustomerIndex + 1) % disneyCustomers.length;
      currentCategoryIndex = (currentCategoryIndex + 1) % ingredientCategories.length;
      setupStage3Scene(currentCategoryIndex, currentCustomerIndex);
      feedback.text = '';
      speak(`下一位客人來囉!${currentCustomer.name} 走進店裡想買【${selectedShopItem.name}】,標價是 ${selectedShopItem.price} 元!`);
    }

    function renderStage1Canvas(ctx) {
      drawScrollBanner(
        ctx,
        V_WIDTH / 2,
        150,
        680,
        74,
        `請幫忙找出【${s1TargetCoin.name}】硬幣`,
        `米奇提示:${s1TargetCoin.hint}`
      );

      // 明亮版迪士尼魔法托盤基座 (暖白奶黃雲石 + 金雕邊框)
      ctx.save();
      ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
      ctx.strokeStyle = '#f59e0b';
      ctx.lineWidth = 3.5;
      ctx.shadowColor = 'rgba(245, 158, 11, 0.35)';
      ctx.shadowBlur = 18;
      ctx.beginPath();
      ctx.roundRect(130, 275, 700, 230, 28);
      ctx.fill();
      ctx.stroke();
      ctx.shadowColor = 'transparent';

      // 經典米奇標誌金雕徽章
      function drawMickeyIcon(mx, my, size) {
        ctx.fillStyle = '#f59e0b';
        ctx.beginPath();
        ctx.arc(mx, my, size, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(mx - size * 0.9, my - size * 0.9, size * 0.6, 0, Math.PI * 2);
        ctx.fill();
        ctx.beginPath();
        ctx.arc(mx + size * 0.9, my - size * 0.9, size * 0.6, 0, Math.PI * 2);
        ctx.fill();
      }

      drawMickeyIcon(175, 310, 12);
      drawMickeyIcon(785, 310, 12);

      ctx.fillStyle = '#b45309';
      ctx.font = '900 16px "Noto Serif TC", serif';
      ctx.textAlign = 'center';
      ctx.fillText("✨ DISNEYLAND MAGICAL COIN PEDESTAL ✨", V_WIDTH / 2, 310);

      // 各硬幣下方的柔和陽光光環與本體
      s1Coins.forEach(c => {
        ctx.save();
        const aura = ctx.createRadialGradient(c.x, c.y, 10, c.x, c.y, c.r + 25);
        aura.addColorStop(0, 'rgba(253, 224, 71, 0.65)');
        aura.addColorStop(0.65, 'rgba(254, 240, 138, 0.3)');
        aura.addColorStop(1, 'rgba(255, 255, 255, 0)');
        ctx.fillStyle = aura;
        ctx.beginPath();
        ctx.arc(c.x, c.y, c.r + 25, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();

        drawDetailedCoin(ctx, c.x, c.y, c.val, c.r, false);
      });
      ctx.restore();
    }

    function renderStage2Canvas(ctx) {
      const tensGoal = Math.floor(s2Goal / 10) * 10;
      const onesGoal = s2Goal % 10;

      let s2SubTitle = `請在十位放入十位硬幣 (50/10元) | 個位放入個位硬幣 (5/1元)`;
      if (tensGoal === 0) {
        s2SubTitle = `十位為 0 不用放 | 請在個位放入 ${onesGoal} 元 (5/1元)`;
      } else if (onesGoal === 0) {
        s2SubTitle = `請在十位放入 ${tensGoal} 元 (50/10元) | 個位為 0 不用放`;
      }

      // 橫幅標題改為 「🎯 金額:XX 元」(移除「目標」兩字)
      drawScrollBanner(
        ctx,
        V_WIDTH / 2,
        145,
        710,
        74,
        `🎯 金額:${s2Goal} 元`,
        s2SubTitle
      );

      ctx.save();
      // 左側:米奇十位盤 (標題純粹為 「十位」)
      ctx.fillStyle = 'rgba(240, 246, 255, 0.94)';
      ctx.strokeStyle = '#2563eb';
      ctx.lineWidth = 3.5;
      ctx.beginPath();
      ctx.roundRect(110, 205, 350, 235, 18);
      ctx.fill();
      ctx.stroke();

      // 盤頂裝飾條與米奇圖案
      ctx.fillStyle = '#dbeafe';
      ctx.beginPath();
      ctx.roundRect(113, 208, 344, 40, [15, 15, 0, 0]);
      ctx.fill();

      drawMickeyIcon(ctx, 230, 228, 12, '#1e40af');

      const tensSum = s2TensCoins.reduce((a, b) => a + b, 0);
      ctx.fillStyle = '#1e3a8a';
      ctx.font = '900 20px "Noto Serif TC", serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText("十位", 285, 228);
      ctx.font = '700 14px "Noto Serif TC", serif';
      ctx.fillStyle = '#2563eb';
      ctx.fillText(`(${tensSum} 元)`, 340, 228);

      if (s2TensCoins.length === 0) {
        ctx.fillStyle = tensGoal === 0 ? '#16a34a' : '#94a3b8';
        ctx.font = 'italic 15px "Noto Serif TC", serif';
        ctx.fillText(tensGoal === 0 ? "✨ 十位為 0,不用放硬幣喔!" : "請點擊下方 50 或 10 元放入十位...", 285, 325);
      } else {
        // 十位盤硬幣排列展示 (5欄網格排列,整齊不超出,支援 50 與 10)
        s2TensCoins.forEach((val, idx) => {
          const col = idx % 5;
          const row = Math.floor(idx / 5);
          const px = 150 + col * 66;
          const py = 285 + row * 54;
          drawDetailedCoin(ctx, px, py, val, val === 50 ? 28 : 25);
        });
      }

      // 右側:米妮個位盤 (標題純粹為 「個位」)
      ctx.fillStyle = 'rgba(255, 245, 247, 0.94)';
      ctx.strokeStyle = '#db2777';
      ctx.lineWidth = 3.5;
      ctx.beginPath();
      ctx.roundRect(495, 205, 355, 235, 18);
      ctx.fill();
      ctx.stroke();

      // 盤頂裝飾條與米妮圖案
      ctx.fillStyle = '#fce7f3';
      ctx.beginPath();
      ctx.roundRect(498, 208, 349, 40, [15, 15, 0, 0]);
      ctx.fill();

      drawMinnieIcon(ctx, 615, 228, 12);

      const onesSum = s2OnesCoins.reduce((a, b) => a + b, 0);
      ctx.fillStyle = '#9d174d';
      ctx.font = '900 20px "Noto Serif TC", serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText("個位", 672, 228);
      ctx.font = '700 14px "Noto Serif TC", serif';
      ctx.fillStyle = '#db2777';
      ctx.fillText(`(${onesSum} 元)`, 725, 228);

      if (s2OnesCoins.length === 0) {
        ctx.fillStyle = onesGoal === 0 ? '#16a34a' : '#f472b6';
        ctx.font = 'italic 15px "Noto Serif TC", serif';
        ctx.fillText(onesGoal === 0 ? "✨ 個位為 0,不用放硬幣喔!" : "請點擊下方 5 或 1 元放入個位...", 672, 325);
      } else {
        // 個位盤硬幣排列展示 (5欄網格排列,整齊不超出,支援 5 與 1)
        s2OnesCoins.forEach((val, idx) => {
          const col = idx % 5;
          const row = Math.floor(idx / 5);
          const px = 536 + col * 66;
          const py = 285 + row * 54;
          drawDetailedCoin(ctx, px, py, val, val === 5 ? 24 : 21);
        });
      }
      ctx.restore();

      // 下方硬幣分配列:提供 50、10、5、1 元完整四種硬幣!
      ctx.save();
      ctx.fillStyle = 'rgba(255, 252, 245, 0.95)';
      ctx.beginPath();
      ctx.roundRect(110, 455, 740, 95, 16);
      ctx.fill();
      ctx.strokeStyle = '#cfa144';
      ctx.lineWidth = 2.5;
      ctx.stroke();

      ctx.fillStyle = '#5c4b3f';
      ctx.font = '900 15px "Noto Serif TC", serif';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      ctx.fillText("點擊硬幣放入:", 125, 502);

      // 50 元 (進十位盤)
      drawDetailedCoin(ctx, 268, 502, 50, 32);
      // 10 元 (進十位盤)
      drawDetailedCoin(ctx, 350, 502, 10, 28);
      // 5 元 (進個位盤)
      drawDetailedCoin(ctx, 428, 502, 5, 25);
      // 1 元 (進個位盤)
      drawDetailedCoin(ctx, 498, 502, 1, 21);

      drawCanvasButton(ctx, 642, 502, 110, 44, "確認檢查", '#1e3a8a', '#ffd700');
      drawCanvasButton(ctx, 768, 502, 92, 44, "重置盤面", '#be123c', '#ffe4e6');
      ctx.restore();
    }

    // 繪製小對話氣泡輔助函式
    function drawSpeechBubble(ctx, x, y, w, h, text, isCelebrating, arrowSide = 'bottom') {
      ctx.save();
      ctx.fillStyle = isCelebrating ? '#fef3c7' : 'rgba(255, 255, 255, 0.95)';
      ctx.strokeStyle = isCelebrating ? '#f59e0b' : '#94a3b8';
      ctx.lineWidth = 2;
      ctx.shadowColor = isCelebrating ? 'rgba(245, 158, 11, 0.4)' : 'rgba(0,0,0,0.12)';
      ctx.shadowBlur = 6;

      ctx.beginPath();
      ctx.roundRect(x - w / 2, y - h / 2, w, h, 8);
      ctx.fill();
      ctx.stroke();
      ctx.shadowColor = 'transparent';

      // 氣泡尖角
      ctx.beginPath();
      if (arrowSide === 'bottom') {
        ctx.moveTo(x - 6, y + h / 2);
        ctx.lineTo(x, y + h / 2 + 7);
        ctx.lineTo(x + 6, y + h / 2);
      }
      ctx.fillStyle = isCelebrating ? '#fef3c7' : 'rgba(255, 255, 255, 0.95)';
      ctx.fill();
      ctx.stroke();

      ctx.fillStyle = isCelebrating ? '#b45309' : '#334155';
      ctx.font = '900 12px "Noto Serif TC", serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(text, x, y + 1);
      ctx.restore();
    }


    // 繪製小顧客的點餐/購物對話框 (食材改為圖片,與貨架標價圖片完全一模一樣!)
    function drawCustomerSpeechBubble(ctx, x, y, selectedItem, isCelebrating, isWalking) {
      if (isCelebrating) {
        drawSpeechBubble(ctx, x, y, 140, 32, "買到了!謝謝!", true, 'bottom');
        return;
      }
      if (isWalking) {
        drawSpeechBubble(ctx, x, y, 140, 30, "走進店裡囉~", false, 'bottom');
        return;
      }

      // 正在櫃台選購/點餐:包含【我想買】 + 【食材圖示圖片】 + 【標價元】
      const w = 160;
      const h = 42;
      ctx.save();
      // 對話氣泡背景 (白玉微暖 + 迪士尼金框)
      ctx.fillStyle = 'rgba(255, 255, 255, 0.98)';
      ctx.strokeStyle = '#cda250';
      ctx.lineWidth = 2.2;
      ctx.shadowColor = 'rgba(0, 0, 0, 0.15)';
      ctx.shadowBlur = 8;
      ctx.beginPath();
      ctx.roundRect(x - w / 2, y - h / 2, w, h, 12);
      ctx.fill();
      ctx.stroke();
      ctx.shadowColor = 'transparent';

      // 氣泡底部小三角指示箭頭 (指向人物頭部)
      ctx.beginPath();
      ctx.moveTo(x - 6, y + h / 2);
      ctx.lineTo(x, y + h / 2 + 8);
      ctx.lineTo(x + 6, y + h / 2);
      ctx.fillStyle = 'rgba(255, 255, 255, 0.98)';
      ctx.fill();
      ctx.stroke();

      // 文字:「我想買」
      ctx.fillStyle = '#1e293b';
      ctx.font = '900 12px "Noto Serif TC", serif';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      ctx.fillText("我想買", x - w / 2 + 10, y + 1);

      // 食材圖示微光襯底圓盤
      ctx.fillStyle = 'rgba(254, 243, 199, 0.65)';
      ctx.beginPath();
      ctx.arc(x - 2, y, 16, 0, Math.PI * 2);
      ctx.fill();

      // 食材圖片 (與標價貨架完全一模一樣的 28px Emoji 圖示!)
      ctx.font = '28px "Apple Color Emoji", "Segoe UI Emoji", sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(selectedItem.icon, x - 2, y);

      // 價格標籤
      ctx.fillStyle = '#b91c1c';
      ctx.font = '900 13px "Cinzel", "Noto Serif TC", serif';
      ctx.textAlign = 'right';
      ctx.textBaseline = 'middle';
      ctx.fillText(selectedItem.price + "元", x + w / 2 - 10, y + 1);

      ctx.restore();
    }


    // 繪製小顧客的點餐/購物對話框 (食材改為圖片,與貨架標價圖片完全一模一樣!)
    function drawCustomerSpeechBubble(ctx, x, y, selectedItem, isCelebrating) {
      if (isCelebrating) {
        drawSpeechBubble(ctx, x, y, 140, 32, "買到了!謝謝!", true, 'bottom');
        return;
      }

      // 包含【我想買】 + 【食材圖示圖片】 + 【標價元】
      const w = 160;
      const h = 42;
      ctx.save();
      // 對話氣泡背景 (白玉微暖 + 迪士尼金框)
      ctx.fillStyle = 'rgba(255, 255, 255, 0.98)';
      ctx.strokeStyle = '#cda250';
      ctx.lineWidth = 2.2;
      ctx.shadowColor = 'rgba(0, 0, 0, 0.15)';
      ctx.shadowBlur = 8;
      ctx.beginPath();
      ctx.roundRect(x - w / 2, y - h / 2, w, h, 12);
      ctx.fill();
      ctx.stroke();
      ctx.shadowColor = 'transparent';

      // 氣泡底部小三角指示箭頭 (指向人物頭部)
      ctx.beginPath();
      ctx.moveTo(x - 6, y + h / 2);
      ctx.lineTo(x, y + h / 2 + 8);
      ctx.lineTo(x + 6, y + h / 2);
      ctx.fillStyle = 'rgba(255, 255, 255, 0.98)';
      ctx.fill();
      ctx.stroke();

      // 文字:「我想買」
      ctx.fillStyle = '#1e293b';
      ctx.font = '900 12px "Noto Serif TC", serif';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      ctx.fillText("我想買", x - w / 2 + 10, y + 1);

      // 食材圖示微光襯底圓盤
      ctx.fillStyle = 'rgba(254, 243, 199, 0.65)';
      ctx.beginPath();
      ctx.arc(x - 2, y, 16, 0, Math.PI * 2);
      ctx.fill();

      // 食材圖片 (與標價貨架完全一模一樣的 28px Emoji 圖示!)
      ctx.font = '28px "Apple Color Emoji", "Segoe UI Emoji", sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(selectedItem.icon, x - 2, y);

      // 價格標籤
      ctx.fillStyle = '#b91c1c';
      ctx.font = '900 13px "Cinzel", "Noto Serif TC", serif';
      ctx.textAlign = 'right';
      ctx.textBaseline = 'middle';
      ctx.fillText(selectedItem.price + "元", x + w / 2 - 10, y + 1);

      ctx.restore();
    }

    // 繪製迪士尼卡通人物小顧客頭部 (Disney Customer Head - 只要頭部,不需要身體)
    function drawMarketCustomer(ctx, cx, cy, customer, isCelebrating, isWalking) {
      ctx.save();
      const bobY = isWalking ? Math.abs(Math.sin(s3WalkStep * 3)) * 6 : Math.sin(Date.now() / 350) * 2;
      const hx = cx;
      const hy = cy - bobY;

      // 1. 迪士尼角色專屬頭部特徵繪製 (只要頭部,不需要身體)
      if (customer.type === 'minnie') {
        // 米妮:兩隻大黑耳、桃色臉龐、長睫毛大眼、黑鼻、經典紅底白點大蝴蝶結
        ctx.fillStyle = '#1e293b';
        ctx.beginPath();
        ctx.arc(hx - 18, hy - 18, 13, 0, Math.PI * 2);
        ctx.arc(hx + 18, hy - 18, 13, 0, Math.PI * 2);
        ctx.arc(hx, hy - 2, 18, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#fed7aa';
        ctx.beginPath();
        ctx.ellipse(hx, hy + 2, 13, 12, 0, 0, Math.PI * 2);
        ctx.fill();
        // 睫毛與雙眼
        ctx.fillStyle = '#0f172a';
        ctx.beginPath();
        ctx.arc(hx - 4, hy - 1, 2.4, 0, Math.PI * 2);
        ctx.arc(hx + 4, hy - 1, 2.4, 0, Math.PI * 2);
        ctx.arc(hx, hy + 4, 2.4, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = '#0f172a';
        ctx.lineWidth = 1.2;
        ctx.beginPath();
        ctx.moveTo(hx - 6, hy - 4); ctx.lineTo(hx - 9, hy - 7);
        ctx.moveTo(hx + 6, hy - 4); ctx.lineTo(hx + 9, hy - 7);
        ctx.stroke();
        drawMinnieBow(ctx, hx, hy - 20, 0.95);

      } else if (customer.type === 'donald') {
        // 唐老鴨:白色羽毛頭、水手帽與飄帶、藍色靈動大眼、招牌黃色扁鴨嘴
        ctx.fillStyle = '#ffffff';
        ctx.strokeStyle = '#cbd5e1';
        ctx.lineWidth = 1.5;
        ctx.beginPath();
        ctx.arc(hx, hy - 2, 18, 0, Math.PI * 2);
        ctx.fill();
        ctx.stroke();
        // 水手帽
        ctx.fillStyle = '#1d4ed8';
        ctx.beginPath();
        ctx.ellipse(hx, hy - 18, 14, 6, -0.1, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#0f172a';
        ctx.beginPath();
        ctx.moveTo(hx + 10, hy - 18); ctx.lineTo(hx + 24, hy - 22); ctx.lineTo(hx + 20, hy - 14);
        ctx.closePath();
        ctx.fill();
        // 藍眼與鴨嘴
        ctx.fillStyle = '#2563eb';
        ctx.beginPath();
        ctx.ellipse(hx - 4, hy - 4, 2.6, 4.2, 0, 0, Math.PI * 2);
        ctx.ellipse(hx + 4, hy - 4, 2.6, 4.2, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#f59e0b';
        ctx.beginPath();
        ctx.ellipse(hx, hy + 6, 15, 8, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = '#d97706';
        ctx.lineWidth = 1.2;
        ctx.stroke();

      } else if (customer.type === 'pooh') {
        // 小熊維尼:圓滾滾金黃小熊頭、可愛圓耳朵、憨厚黑圓眼與小鼻
        ctx.fillStyle = '#f59e0b';
        ctx.beginPath();
        ctx.arc(hx - 15, hy - 15, 9, 0, Math.PI * 2);
        ctx.arc(hx + 15, hy - 15, 9, 0, Math.PI * 2);
        ctx.arc(hx, hy - 2, 19, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#fbbf24';
        ctx.beginPath();
        ctx.ellipse(hx, hy + 3, 11, 8, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#451a03';
        ctx.beginPath();
        ctx.arc(hx - 5, hy - 2, 2.2, 0, Math.PI * 2);
        ctx.arc(hx + 5, hy - 2, 2.2, 0, Math.PI * 2);
        ctx.arc(hx, hy + 3, 2.8, 0, Math.PI * 2);
        ctx.fill();

      } else if (customer.type === 'goofy') {
        // 高飛狗:長條下垂大耳、經典高頂綠帽、凸凸長鼻子與兩顆可愛大門牙
        ctx.fillStyle = '#1e293b';
        ctx.beginPath();
        ctx.ellipse(hx - 22, hy + 2, 6, 20, 0.2, 0, Math.PI * 2);
        ctx.ellipse(hx + 22, hy + 2, 6, 20, -0.2, 0, Math.PI * 2);
        ctx.arc(hx, hy - 2, 17, 0, Math.PI * 2);
        ctx.fill();
        // 綠帽子
        ctx.fillStyle = '#16a34a';
        ctx.beginPath();
        ctx.roundRect(hx - 9, hy - 36, 18, 20, [6, 6, 2, 2]);
        ctx.fill();
        ctx.fillStyle = '#0f172a';
        ctx.fillRect(hx - 11, hy - 20, 22, 3);
        // 長吻與大門牙
        ctx.fillStyle = '#fed7aa';
        ctx.beginPath();
        ctx.ellipse(hx, hy + 6, 12, 9, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#0f172a';
        ctx.beginPath();
        ctx.arc(hx, hy + 3, 3, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(hx - 3.5, hy + 9, 3, 4.5);
        ctx.fillRect(hx + 0.5, hy + 9, 3, 4.5);

      } else if (customer.type === 'tigger') {
        // 跳跳虎:亮橘色虎紋小圓耳、黑色虎斑條紋、粉嫩俏皮鼻子
        ctx.fillStyle = '#ea580c';
        ctx.beginPath();
        ctx.arc(hx - 15, hy - 15, 8, 0, Math.PI * 2);
        ctx.arc(hx + 15, hy - 15, 8, 0, Math.PI * 2);
        ctx.arc(hx, hy - 2, 19, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#0f172a';
        ctx.beginPath();
        ctx.moveTo(hx, hy - 16); ctx.lineTo(hx, hy - 11);
        ctx.moveTo(hx - 16, hy - 2); ctx.lineTo(hx - 9, hy);
        ctx.moveTo(hx + 16, hy - 2); ctx.lineTo(hx + 9, hy);
        ctx.stroke();
        ctx.fillStyle = '#ffedd5';
        ctx.beginPath();
        ctx.ellipse(hx, hy + 4, 12, 8, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#f43f5e';
        ctx.beginPath();
        ctx.arc(hx, hy + 2, 2.8, 0, Math.PI * 2);
        ctx.fill();

      } else if (customer.type === 'daisy') {
        // 黛西:優雅白色羽毛頭、薰衣草紫大蝴蝶結、深紫長睫毛、溫柔鵝黃鴨嘴
        ctx.fillStyle = '#ffffff';
        ctx.strokeStyle = '#cbd5e1';
        ctx.lineWidth = 1.5;
        ctx.beginPath();
        ctx.arc(hx, hy - 2, 18, 0, Math.PI * 2);
        ctx.fill();
        ctx.stroke();
        // 紫蝴蝶結
        ctx.fillStyle = '#c084fc';
        ctx.beginPath();
        ctx.ellipse(hx - 11, hy - 19, 9, 6, -0.3, 0, Math.PI * 2);
        ctx.ellipse(hx + 11, hy - 19, 9, 6, 0.3, 0, Math.PI * 2);
        ctx.arc(hx, hy - 19, 4, 0, Math.PI * 2);
        ctx.fill();
        // 睫毛與鵝黃鴨嘴
        ctx.fillStyle = '#9333ea';
        ctx.beginPath();
        ctx.ellipse(hx - 4, hy - 4, 2.6, 4.2, 0, 0, Math.PI * 2);
        ctx.ellipse(hx + 4, hy - 4, 2.6, 4.2, 0, 0, Math.PI * 2);
        ctx.fill();
        ctx.strokeStyle = '#581c87';
        ctx.lineWidth = 1.2;
        ctx.beginPath();
        ctx.moveTo(hx - 7, hy - 6); ctx.lineTo(hx - 10, hy - 9);
        ctx.moveTo(hx + 7, hy - 6); ctx.lineTo(hx + 10, hy - 9);
        ctx.stroke();
        ctx.fillStyle = '#f59e0b';
        ctx.beginPath();
        ctx.ellipse(hx, hy + 6, 14, 7, 0, 0, Math.PI * 2);
        ctx.fill();

      } else {
        // 奇奇蒂蒂:栗子深棕小毛頭、米白雙頰與紅色小鼻頭
        ctx.fillStyle = '#78350f';
        ctx.beginPath();
        ctx.arc(hx - 15, hy - 15, 7, 0, Math.PI * 2);
        ctx.arc(hx + 15, hy - 15, 7, 0, Math.PI * 2);
        ctx.arc(hx, hy - 2, 18, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#fef3c7';
        ctx.beginPath();
        ctx.arc(hx - 7, hy + 2, 7, 0, Math.PI * 2);
        ctx.arc(hx + 7, hy + 2, 7, 0, Math.PI * 2);
        ctx.fill();
        ctx.fillStyle = '#dc2626';
        ctx.beginPath();
        ctx.arc(hx, hy + 1, 3.5, 0, Math.PI * 2);
        ctx.fill();
      }

      // 2. 對話框 (物品改為圖片,與貨架標價圖片完全一模一樣!)
      drawCustomerSpeechBubble(ctx, cx, hy - 46, selectedShopItem, isCelebrating);

      // 3. 底部角色名牌標籤 (Pill Tag)
      ctx.fillStyle = '#ffe4e6';
      ctx.strokeStyle = '#e11d48';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.roundRect(cx - 50, hy + 24, 100, 20, 10);
      ctx.fill();
      ctx.stroke();

      ctx.fillStyle = '#9f1239';
      ctx.font = '900 11px "Noto Serif TC", serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(customer.name, cx, hy + 34);

      ctx.restore();
    }

    // 繪製市集店長老闆頭部 (Shopkeeper Boss Head - 米奇店長,只要頭部,不需要身體,完全不被櫃台擋住!)
    function drawShopkeeperBoss(ctx, bx, by, isCelebrating) {
      ctx.save();
      const bobY = Math.sin(Date.now() / 380) * 2;
      const hx = bx;
      const hy = by - bobY;

      // 1. 米奇經典黑色大圓耳與頭部
      ctx.fillStyle = '#1e293b';
      ctx.beginPath();
      ctx.arc(hx - 20, hy - 18, 14, 0, Math.PI * 2);
      ctx.arc(hx + 20, hy - 18, 14, 0, Math.PI * 2);
      ctx.arc(hx, hy - 2, 19, 0, Math.PI * 2);
      ctx.fill();

      // 桃色臉龐
      ctx.fillStyle = '#fed7aa';
      ctx.beginPath();
      ctx.ellipse(hx, hy + 2, 14, 12, 0, 0, Math.PI * 2);
      ctx.fill();

      // 雙眼與黑色鼻頭
      ctx.fillStyle = '#0f172a';
      ctx.beginPath();
      ctx.arc(hx - 4.5, hy - 1, 2.7, 0, Math.PI * 2);
      ctx.arc(hx + 4.5, hy - 1, 2.7, 0, Math.PI * 2);
      ctx.arc(hx, hy + 3.5, 2.8, 0, Math.PI * 2);
      ctx.fill();

      // 親切笑容
      ctx.strokeStyle = '#0f172a';
      ctx.lineWidth = 1.4;
      ctx.beginPath();
      ctx.arc(hx, hy + 4, 5.5, 0.1 * Math.PI, 0.9 * Math.PI);
      ctx.stroke();

      // 高頂主廚帽 (Toque Blanche)
      ctx.fillStyle = '#ffffff';
      ctx.strokeStyle = '#cbd5e1';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.roundRect(hx - 16, hy - 40, 32, 22, [10, 10, 2, 2]);
      ctx.fill();
      ctx.stroke();

      // 主廚帽金邊緞帶
      ctx.fillStyle = '#f59e0b';
      ctx.fillRect(hx - 16, hy - 20, 32, 4);

      if (isCelebrating) {
        // 慶祝金星塵
        ctx.fillStyle = '#f59e0b';
        ctx.font = '14px sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText("*", hx - 30, hy - 28);
        ctx.fillText("*", hx + 30, hy - 28);
      }

      // 2. 對話框 (答對只說謝謝光臨!)
      const bossSpeechText = isCelebrating
        ? "謝謝光臨!"
        : "歡迎光臨!";
      drawSpeechBubble(ctx, bx, hy - 54, 136, 26, bossSpeechText, isCelebrating, 'bottom');

      // 3. 店長名牌標籤 (Pill Tag)
      ctx.fillStyle = '#dbeafe';
      ctx.strokeStyle = '#2563eb';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.roundRect(bx - 55, hy + 24, 110, 20, 10);
      ctx.fill();
      ctx.stroke();

      ctx.fillStyle = '#1e3a8a';
      ctx.font = '900 11px "Noto Serif TC", serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText("店長:米奇", bx, hy + 34);

      ctx.restore();
    }

    // 繪製店內實體櫃台 (Store Counter - 精緻實木檯面與嵌入托盤,不遮擋右側老闆)
    function drawStoreCounter(ctx, paidSum) {
      ctx.save();
      const counterX = 205;
      const counterY = 312;
      const counterW = 485;
      const counterH = 144;

      // 1. 櫃台下半部:古典直條木紋壁板
      const gradFace = ctx.createLinearGradient(0, counterY + 20, 0, counterY + counterH);
      gradFace.addColorStop(0, '#533418');
      gradFace.addColorStop(1, '#321c0b');
      ctx.fillStyle = gradFace;
      ctx.strokeStyle = '#231306';
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.roundRect(counterX, counterY + 20, counterW, counterH - 20, [0, 0, 12, 12]);
      ctx.fill();
      ctx.stroke();

      // 櫃台壁板垂直溝槽裝飾
      ctx.strokeStyle = 'rgba(0, 0, 0, 0.25)';
      ctx.lineWidth = 2;
      for (let lx = counterX + 40; lx < counterX + counterW; lx += 45) {
        ctx.beginPath();
        ctx.moveTo(lx, counterY + 22);
        ctx.lineTo(lx, counterY + counterH - 4);
        ctx.stroke();
      }

      // 2. 櫃面上半部:拋光實木檯面
      const gradTop = ctx.createLinearGradient(0, counterY, 0, counterY + 22);
      gradTop.addColorStop(0, '#b87c44');
      gradTop.addColorStop(0.5, '#8f5627');
      gradTop.addColorStop(1, '#653b17');
      ctx.fillStyle = gradTop;
      ctx.beginPath();
      ctx.roundRect(counterX - 6, counterY, counterW + 12, 22, 6);
      ctx.fill();

      ctx.strokeStyle = '#d4af37';
      ctx.lineWidth = 2;
      ctx.stroke();

      // 4. 櫃台收銀機 (Antique Cash Register)
      const regX = counterX + counterW - 48; // ~642
      const regY = counterY + 50; // ~362
      ctx.fillStyle = '#92400e';
      ctx.strokeStyle = '#f59e0b';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.roundRect(regX - 28, regY - 32, 56, 68, 8);
      ctx.fill();
      ctx.stroke();

      // 收銀機頂部金額顯示綠色螢幕
      ctx.fillStyle = '#064e3b';
      ctx.beginPath();
      ctx.roundRect(regX - 22, regY - 28, 44, 16, 4);
      ctx.fill();
      ctx.fillStyle = '#34d399';
      ctx.font = '900 11px "Cinzel", monospace';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(`$ ${selectedShopItem.price}`, regX, regY - 20);

      // 收銀機按鍵
      ctx.fillStyle = '#fde68a';
      for (let r = 0; r < 3; r++) {
        for (let c = 0; c < 3; c++) {
          ctx.beginPath();
          ctx.arc(regX - 14 + c * 14, regY - 4 + r * 9, 2.5, 0, Math.PI * 2);
          ctx.fill();
        }
      }

      // 錢抽把手
      ctx.fillStyle = '#d97706';
      ctx.fillRect(regX - 16, regY + 25, 32, 4);

      // 5. 櫃台叮咚服務鈴 (Brass Service Bell)
      const bellX = regX - 52; // ~590
      const bellY = counterY + 24; // ~336
      ctx.fillStyle = '#78350f';
      ctx.fillRect(bellX - 12, bellY + 5, 24, 4);
      ctx.fillStyle = '#f59e0b';
      ctx.strokeStyle = '#d97706';
      ctx.lineWidth = 1.5;
      ctx.beginPath();
      ctx.arc(bellX, bellY + 5, 11, Math.PI, 0);
      ctx.fill();
      ctx.stroke();
      ctx.fillStyle = '#ef4444';
      ctx.fillRect(bellX - 2, bellY - 8, 4, 4);

      // 6. 嵌入式結帳木托盤 (Embedded Checkout Coin Tray)
      const trayX = counterX + 12; // 217
      const trayY = counterY + 12; // 324
      const trayW = 345;
      const trayH = 120;

      ctx.fillStyle = 'rgba(250, 245, 235, 0.98)';
      ctx.strokeStyle = '#a26a38';
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.roundRect(trayX, trayY, trayW, trayH, 10);
      ctx.fill();
      ctx.stroke();

      // 托盤頂部標題列
      ctx.fillStyle = 'rgba(224, 206, 182, 0.92)';
      ctx.beginPath();
      ctx.roundRect(trayX + 2, trayY + 2, trayW - 4, 28, [8, 8, 0, 0]);
      ctx.fill();

      ctx.fillStyle = '#4a382c';
      ctx.font = '900 12px "Noto Serif TC", serif';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      ctx.fillText(`🛒 托盤(標價:${selectedShopItem.price}元, 已放:`, trayX + 8, trayY + 16);

      ctx.fillStyle = '#b91c1c';
      ctx.font = '900 16px "Cinzel", serif';
      ctx.fillText(`${paidSum} 元`, trayX + 185, trayY + 16);
      ctx.fillStyle = '#4a382c';
      ctx.font = '900 12px "Noto Serif TC", serif';
      ctx.fillText(`)`, trayX + 242, trayY + 16);

      // 托盤即時比對狀態
      ctx.textAlign = 'right';
      if (paidSum === selectedShopItem.price) {
        ctx.fillStyle = '#15803d';
        ctx.font = '900 11px "Noto Serif TC", serif';
        ctx.fillText("✨剛剛好!請結帳", trayX + trayW - 6, trayY + 16);
      } else if (paidSum < selectedShopItem.price) {
        ctx.fillStyle = '#b45309';
        ctx.font = '700 11px "Noto Serif TC", serif';
        ctx.fillText(`(差${selectedShopItem.price - paidSum}元)`, trayX + trayW - 6, trayY + 16);
      } else {
        ctx.fillStyle = '#dc2626';
        ctx.font = '700 11px "Noto Serif TC", serif';
        ctx.fillText(`⚠️多${paidSum - selectedShopItem.price}元`, trayX + trayW - 6, trayY + 16);
      }

      // 托盤內硬幣展示
      if (insertedRegisterCoins.length === 0) {
        ctx.fillStyle = '#8f7d6a';
        ctx.font = 'italic 12px "Noto Serif TC", serif';
        ctx.textAlign = 'center';
        ctx.fillText("點下方 50、10、5、1 元放入托盤(點幣退回)...", trayX + trayW / 2, trayY + 70);
      } else {
        insertedRegisterCoins.forEach((val, idx) => {
          const px = trayX + 35 + (idx % 5) * 65;
          const py = trayY + 52 + Math.floor(idx / 5) * 42;
          drawDetailedCoin(ctx, px, py, val, val === 50 ? 25 : val === 10 ? 22 : val === 5 ? 20 : 17);
        });
      }

      ctx.restore();
    }

    function renderStage3Canvas(ctx) {
      // 1. 頂部羊皮紙魔法橫幅
      const category = ingredientCategories[currentCategoryIndex];
      drawScrollBanner(
        ctx,
        V_WIDTH / 2,
        138,
        750,
        52,
        `🏪 迪士尼食材專賣店:【${currentCustomer.name}】來買食材囉!`,
        `【${category.categoryName}】食材標價清楚透明,請在櫃台托盤放入剛剛好的錢幣!`
      );

      // 2. 食材展示陳列架 (5 件食材,每件均有清晰大標價 1-100 元)
      ctx.save();
      ctx.fillStyle = 'rgba(248, 241, 227, 0.94)';
      ctx.strokeStyle = '#cda250';
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      ctx.roundRect(50, 172, 860, 115, 12);
      ctx.fill();
      ctx.stroke();

      currentMarketItems.forEach(item => {
        const isSel = item.id === selectedShopItem.id;
        ctx.save();
        ctx.beginPath();
        ctx.roundRect(item.x - item.w / 2, item.y - item.h / 2, item.w, item.h, 12);

        if (isSel) {
          ctx.fillStyle = 'rgba(255, 252, 235, 0.98)';
          ctx.strokeStyle = '#f59e0b';
          ctx.lineWidth = 3.5;
          ctx.shadowColor = 'rgba(245, 158, 11, 0.45)';
          ctx.shadowBlur = 12;
        } else {
          ctx.fillStyle = 'rgba(255, 255, 255, 0.92)';
          ctx.strokeStyle = '#cbd5e1';
          ctx.lineWidth = 1.8;
        }
        ctx.fill();
        ctx.stroke();
        ctx.shadowColor = 'transparent';

        // 選購中標籤
        if (isSel) {
          ctx.fillStyle = '#dc2626';
          ctx.beginPath();
          ctx.roundRect(item.x - 30, item.y - item.h / 2 + 4, 60, 16, 8);
          ctx.fill();
          ctx.fillStyle = '#ffffff';
          ctx.font = '900 10px "Noto Serif TC", serif';
          ctx.textAlign = 'center';
          ctx.textBaseline = 'middle';
          ctx.fillText("🎯 購買中", item.x, item.y - item.h / 2 + 12);
        }

        // 食材 Emoji 圖示
        ctx.font = '32px "Apple Color Emoji", "Segoe UI Emoji", sans-serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(item.icon, item.x, item.y - 14);

        // 食材名稱
        ctx.font = '700 13px "Noto Serif TC", serif';
        ctx.fillStyle = '#1e293b';
        ctx.fillText(item.name, item.x, item.y + 14);

        // 食材醒目標價 (目標 2: 食材都有標價錢 1-100元)
        ctx.fillStyle = isSel ? '#dc2626' : '#b45309';
        ctx.beginPath();
        ctx.roundRect(item.x - 56, item.y + 26, 112, 19, 9);
        ctx.fill();

        ctx.fillStyle = '#ffffff';
        ctx.font = '900 12px "Noto Serif TC", serif';
        ctx.fillText(`💰 標價:${item.price} 元`, item.x, item.y + 35);
        ctx.restore();
      });
      ctx.restore();

      // 3. 結帳實體櫃台 (Store Counter) - 位於中間 x=205~690,不遮擋老闆!
      const paidSum = insertedRegisterCoins.reduce((a, b) => a + b, 0);
      drawStoreCounter(ctx, paidSum);

      // 4. 店長米奇老闆站在右側專屬區 (Shopkeeper Boss) - 有手有腳、完全不被擋住!
      drawShopkeeperBoss(ctx, 820, 370, s3Celebrating);

      // 5. 走進店裡面的小顧客 (Customer) - 有手有腳、提菜籃、邁步走進店裡
      drawMarketCustomer(ctx, s3CustomerWalkX, 375, currentCustomer, s3Celebrating, s3CustomerIsWalking);

      // 6. 下方收銀台付款硬幣列:必須完整包含 50、10、5、1 元!
      ctx.save();
      ctx.fillStyle = 'rgba(255, 252, 245, 0.96)';
      ctx.strokeStyle = '#cfa144';
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      ctx.roundRect(50, 466, 860, 92, 16);
      ctx.fill();
      ctx.stroke();

      ctx.fillStyle = '#5c4a3d';
      ctx.font = '900 15px "Noto Serif TC", serif';
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      ctx.fillText("🪙 點擊付款:", 75, 512);

      // 50 元 (大金幣)
      drawDetailedCoin(ctx, 205, 512, 50, 29);
      // 10 元 (大銀幣)
      drawDetailedCoin(ctx, 285, 512, 10, 26);
      // 5 元 (中銀幣)
      drawDetailedCoin(ctx, 360, 512, 5, 23);
      // 1 元 (小銅幣)
      drawDetailedCoin(ctx, 430, 512, 1, 19);

      drawCanvasButton(ctx, 615, 512, 140, 46, "確認結帳", '#1b5e20', '#c8e6c9');
      drawCanvasButton(ctx, 770, 512, 120, 46, "清除托盤", '#be123c', '#ffe4e6');
      ctx.restore();
    }

    function drawCanvasButton(ctx, x, y, w, h, text, bgColor, textColor) {
      ctx.save();
      ctx.beginPath();
      ctx.roundRect(x - w / 2, y - h / 2, w, h, 10);
      ctx.fillStyle = bgColor;
      ctx.shadowColor = 'rgba(0,0,0,0.3)';
      ctx.shadowBlur = 4;
      ctx.shadowOffsetY = 2;
      ctx.fill();
      ctx.shadowColor = 'transparent';

      ctx.strokeStyle = '#cfa144';
      ctx.lineWidth = 2;
      ctx.stroke();

      ctx.fillStyle = textColor;
      ctx.font = '900 16px "Noto Serif TC", serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(text, x, y + 1);
      ctx.restore();
    }

    function drawFeedbackRibbon(ctx) {
      if (feedback.timer > 0) {
        ctx.save();
        ctx.fillStyle = feedback.color === 'good' ? '#2e7d32' : '#b71c1c';
        ctx.shadowColor = 'rgba(0,0,0,0.4)';
        ctx.shadowBlur = 8;
        ctx.beginPath();
        ctx.roundRect(V_WIDTH / 2 - 320, 570, 640, 50, 25);
        ctx.fill();

        ctx.strokeStyle = '#ffd700';
        ctx.lineWidth = 2;
        ctx.stroke();

        ctx.fillStyle = '#ffffff';
        ctx.font = '900 18px "Noto Serif TC", serif';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(feedback.text, V_WIDTH / 2, 595);
        ctx.restore();

        feedback.timer--;
      }
    }

    function getCanvasCoordinates(e) {
      const rect = canvas.getBoundingClientRect();
      const clientX = e.clientX || (e.touches && e.touches[0] ? e.touches[0].clientX : 0);
      const clientY = e.clientY || (e.touches && e.touches[0] ? e.touches[0].clientY : 0);

      const dpr = window.devicePixelRatio || 1;
      const x = (clientX - rect.left - offsetX) / scale;
      const y = (clientY - rect.top - offsetY) / scale;
      return { x, y };
    }

    function handlePointerDown(e) {
      initAudio();
      const { x, y } = getCanvasCoordinates(e);

      // 檢查頂部導航按鈕
      for (let btn of navButtons) {
        if (Math.abs(x - btn.x) <= btn.w / 2 && Math.abs(y - btn.y) <= btn.h / 2) {
          soundCoin();
          currentStage = btn.stage;
          if (currentStage === 1) initStage1();
          else if (currentStage === 2) initStage2();
          else if (currentStage === 3) initStage3();
          return;
        }
      }

      // 第一關互動
      if (currentStage === 1) {
        if (s1Locked) return;
        for (let c of s1Coins) {
          const dist = Math.hypot(x - c.x, y - c.y);
          if (dist <= c.r) {
            soundCoin();
            if (c.val === s1TargetCoin.val) {
              s1Locked = true;
              soundSuccess();
              addSparkles(c.x, c.y, 55);
              createFirework();
              setTimeout(createFirework, 400);
              setTimeout(createFirework, 850);

              const fullSuccessText = `謝謝你幫忙找到 ${c.name} 硬幣!🎉`;
              feedback = {
                text: fullSuccessText,
                color: 'good',
                timer: 260
              };

              // 語音完整念出「謝謝你幫忙找到X元硬幣!」
              const spokenSuccess = `謝謝你幫忙找到${c.spoken}硬幣!`;

              let transitioned = false;
              function nextQuestion() {
                if (transitioned) return;
                transitioned = true;
                clearTimeout(fallbackTimer);
                setTimeout(() => {
                  initStage1();
                }, 900); // 語音完整念完後留 0.9 秒聆聽慶祝音樂餘韻,再切換下一題
              }

              const fallbackTimer = setTimeout(nextQuestion, 4200);
              speak(spokenSuccess, nextQuestion);

            } else {
              soundFailure();
              feedback = {
                text: `請再試試看!`,
                color: 'bad',
                timer: 110
              };
              speak(`請再試試看!`);
            }
            return;
          }
        }
      }

      // 第二關互動
      else if (currentStage === 2) {
        if (s2Locked) return;

        // 1. 點擊下方硬幣
        // 點擊 50 元 (進十位盤)
        if (Math.hypot(x - 268, y - 502) <= 32) {
          soundCoin();
          s2TensCoins.push(50);
          return;
        }
        // 點擊 10 元 (進十位盤)
        if (Math.hypot(x - 350, y - 502) <= 28) {
          soundCoin();
          s2TensCoins.push(10);
          return;
        }
        // 點擊 5 元 (進個位盤)
        if (Math.hypot(x - 428, y - 502) <= 25) {
          soundCoin();
          s2OnesCoins.push(5);
          return;
        }
        // 點擊 1 元 (進個位盤)
        if (Math.hypot(x - 498, y - 502) <= 21) {
          soundCoin();
          s2OnesCoins.push(1);
          return;
        }

        // 2. 點擊盤面可取出/撤回硬幣
        // 點擊米奇十位盤區 (取出最後一個十位硬幣)
        if (x >= 110 && x <= 460 && y >= 250 && y <= 440) {
          if (s2TensCoins.length > 0) {
            soundCoin();
            s2TensCoins.pop();
            return;
          }
        }
        // 點擊米妮個位盤區 (取出最後一個個位硬幣)
        if (x >= 495 && x <= 850 && y >= 250 && y <= 440) {
          if (s2OnesCoins.length > 0) {
            soundCoin();
            s2OnesCoins.pop();
            return;
          }
        }

        // 3. 確認檢查按鈕
        if (Math.abs(x - 642) <= 110 / 2 && Math.abs(y - 502) <= 44 / 2) {
          const tensSum = s2TensCoins.reduce((a, b) => a + b, 0);
          const onesSum = s2OnesCoins.reduce((a, b) => a + b, 0);
          const tensGoal = Math.floor(s2Goal / 10) * 10;
          const onesGoal = s2Goal % 10;
          const total = tensSum + onesSum;

          if (tensSum === tensGoal && onesSum === onesGoal) {
            s2Locked = true;
            soundSuccess(); // 答對只播放歡樂迪士尼慶典主題旋律!
            addSparkles(V_WIDTH / 2, 300, 80); // 滿天金色星塵
            createFirework();
            setTimeout(createFirework, 350);
            setTimeout(createFirework, 750);
            feedback = {
              text: `🎉 太棒了!金額完全正確!`,
              color: 'good',
              timer: 220
            };
            // 答對完全刪除說話,只享受歡樂的迪士尼音樂與星塵
            setTimeout(() => {
              initStage2();
            }, 2600);
          } else {
            soundFailure();
            if (tensSum !== tensGoal) {
              const msg = tensGoal === 0
                ? `十位目前是 ${tensSum} 元,不需要放硬幣喔!`
                : `十位目前是 ${tensSum} 元,需要 ${tensGoal} 元喔!`;
              feedback = {
                text: msg,
                color: 'bad',
                timer: 130
              };
              speak(tensGoal === 0 ? `十位目前是 ${tensSum} 元,不需要放硬幣喔!` : `十位目前是 ${tensSum} 元,需要放 ${tensGoal} 元喔!`, null, 0.75);
            } else {
              const msg = onesGoal === 0
                ? `個位目前是 ${onesSum} 元,不需要放硬幣喔!`
                : `個位目前是 ${onesSum} 元,需要 ${onesGoal} 元喔!`;
              feedback = {
                text: msg,
                color: 'bad',
                timer: 130
              };
              speak(onesGoal === 0 ? `個位目前是 ${onesSum} 元,不需要放硬幣喔!` : `個位目前是 ${onesSum} 元,需要放 ${onesGoal} 元喔!`, null, 0.75);
            }
          }
          return;
        }

        // 4. 重置盤面按鈕
        if (Math.abs(x - 768) <= 92 / 2 && Math.abs(y - 502) <= 44 / 2) {
          soundCoin();
          s2TensCoins = [];
          s2OnesCoins = [];
          feedback.text = '';
          return;
        }
      }

      // 第三關互動 (市集買賣)
      else if (currentStage === 3) {
        if (s3Locked) return;

        // 1. 點選架上商品
        for (let item of currentMarketItems) {
          if (Math.abs(x - item.x) <= item.w / 2 && Math.abs(y - item.y) <= item.h / 2) {
            soundCoin();
            selectedShopItem = item;
            insertedRegisterCoins = [];
            feedback.text = '';
            speak(`${currentCustomer.name} 想買 ${item.name},價格是 ${item.price} 元!`);
            return;
          }
        }

        // 2. 點選付款硬幣 (完整支援 50, 10, 5, 1 元)
        // 50 元 (大金幣)
        if (Math.hypot(x - 205, y - 512) <= 32) {
          soundCoin();
          insertedRegisterCoins.push(50);
          return;
        }
        // 10 元 (大銀幣)
        if (Math.hypot(x - 285, y - 512) <= 28) {
          soundCoin();
          insertedRegisterCoins.push(10);
          return;
        }
        // 5 元 (中銀幣)
        if (Math.hypot(x - 360, y - 512) <= 25) {
          soundCoin();
          insertedRegisterCoins.push(5);
          return;
        }
        // 1 元 (小銅幣)
        if (Math.hypot(x - 430, y - 512) <= 21) {
          soundCoin();
          insertedRegisterCoins.push(1);
          return;
        }

        // 3. 點擊結帳木托盤區 (可撤回/退回最後一個硬幣)
        if (x >= 217 && x <= 562 && y >= 324 && y <= 444) {
          if (insertedRegisterCoins.length > 0) {
            soundCoin();
            insertedRegisterCoins.pop();
            return;
          }
        }

        // 4. 確認結帳按鈕
        if (Math.abs(x - 615) <= 140 / 2 && Math.abs(y - 512) <= 46 / 2) {
          const sum = insertedRegisterCoins.reduce((a, b) => a + b, 0);
          if (sum === selectedShopItem.price) {
            s3Locked = true;
            s3Celebrating = true;
            soundSuccess(); // 播放歡樂迪士尼慶典旋律!
            addSparkles(V_WIDTH / 2, 330, 80); // 滿天金色星塵

            // 答對放煙火的圖案 (多重絢麗煙火連環綻放!)
            createFirework();
            setTimeout(createFirework, 250);
            setTimeout(createFirework, 550);
            setTimeout(createFirework, 850);
            setTimeout(createFirework, 1200);

            feedback = {
              text: `🎉 謝謝光臨!`,
              color: 'good',
              timer: 240
            };

            // 答對只要說「謝謝光臨」就好!
            speak("謝謝光臨!", null, 0.9);

            // 付完錢全部再換一次圖片(換客人、換整批商品!)
            setTimeout(() => {
              advanceStage3Scene();
            }, 2800);
          } else if (sum > selectedShopItem.price) {
            soundFailure();
            const diff = sum - selectedShopItem.price;
            feedback = {
              text: `付了 ${sum} 元,多付了 ${diff} 元!要練習付「剛剛好」的金額!`,
              color: 'bad',
              timer: 130
            };
            speak(`付太多錢囉,多付了 ${diff} 元,請練習給剛剛好`, null, 0.75);
          } else {
            soundFailure();
            const diff = selectedShopItem.price - sum;
            feedback = {
              text: `目前才 ${sum} 元,托盤還短少 ${diff} 元喔!`,
              color: 'bad',
              timer: 130
            };
            speak(`金額還不夠,還差 ${diff} 元喔`, null, 0.75);
          }
          return;
        }

        // 5. 清除托盤按鈕
        if (Math.abs(x - 770) <= 120 / 2 && Math.abs(y - 512) <= 46 / 2) {
          soundCoin();
          insertedRegisterCoins = [];
          feedback.text = '';
          return;
        }
      }
    }

    canvas.addEventListener('pointerdown', handlePointerDown);

    function gameLoop() {
      // 0. 第三關卡通人物走進店裡動畫更新
      if (currentStage === 3 && s3CustomerIsWalking) {
        s3CustomerWalkX += 3.8;
        s3WalkStep += 0.22;
        if (Math.random() < 0.25) {
          addSparkles(s3CustomerWalkX - 10, 420, 2);
        }
        if (s3CustomerWalkX >= s3CustomerTargetX) {
          s3CustomerWalkX = s3CustomerTargetX;
          s3CustomerIsWalking = false;
        }
      }

      // 1. 迪士尼樂園天際線、奇幻城堡與煙火背景
      drawDisneylandBackground(ctx);

      // 2. 歐風遮陽棚與頂部導航欄
      drawNavHeader(ctx);

      // 3. 渲染對應關卡
      if (currentStage === 1) {
        renderStage1Canvas(ctx);
      } else if (currentStage === 2) {
        renderStage2Canvas(ctx);
      } else if (currentStage === 3) {
        renderStage3Canvas(ctx);
      }

      // 4. 回饋橫幅與魔法粒子
      drawFeedbackRibbon(ctx);
      updateAndDrawParticles(ctx);

      requestAnimationFrame(gameLoop);
    }

    // 立即啟動與自適應 (預設進入第 III 關:市集買賣)
    function startApp() {
      resizeCanvas();
      initStage3();
      requestAnimationFrame(gameLoop);
    }

    window.addEventListener('load', startApp);
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
      startApp();
    }
  </script>
</body>
</html>