Исходники пакета · 迪士尼魔法市集:我的小小錢幣王國

CC BY 4.0 · укажите @SpringFairy · v1

← Сведения об игре

Берите, делайте ремикс, учите с ним

Код ниже — только играемый входной HTML пакета. Скачайте полный ZIP, чтобы править или ремикшировать игру со всеми относительными ресурсами.

⬇ Скачать входной HTML ⬇ Скачать полный пакет
Заготовка подсказки для ИИ
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).
Только входной HTML · 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>