Package source · 數學小森林

CC BY 4.0 · please credit @SpringFairy · v1

← Game details

Take it, remix it, teach with it

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

⬇ Download entry HTML ⬇ Download complete package
AI edit prompt starter
Edit this classroom game for interactive whiteboards. Keep large touch targets and whole-class play. Tracking, logins, personal-data collection, and remote API calls are prohibited. Use the downloaded complete ZIP—not only the entry HTML shown below—and preserve its browser-ready folder structure. Preserve the educational goal. Original creator: @SpringFairy on ClassBoard Games (CC BY 4.0).
Entry HTML only · index.html · 275.2 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>卡比夢幻森林大冒險 - 10以內加減法闖關遊戲 (Switch 卡比音效版)</title>
  <style>
    :root {
      --pastel-sky: #bfe8ff;
      --pastel-green: #e2f7d8;
      --card-bg: rgba(255, 255, 255, 0.95);
      --text-main: #2d3748;
      --text-muted: #5a6b82;
      --kirby-pink: #ff85a2;
      --accent-gold: #ffb703;
      --btn-shadow: rgba(0, 0, 0, 0.15);
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      -webkit-tap-highlight-color: transparent;
      user-select: none;
    }

    body {
      min-height: 100vh;
      font-family: 'M PLUS Rounded 1c', 'Nunito', 'PingFang TC', 'Microsoft JhengHei', '微軟正黑體', sans-serif;
      background: linear-gradient(180deg, #bfe8ff 0%, #d8f3dc 45%, #b7e4c7 100%);
      color: var(--text-main);
      overflow-x: hidden;
      display: flex;
      flex-direction: column;
      align-items: center;
      position: relative;
    }

    /* 頂部輕量 HUD 狀態條 */
    .top-hud {
      width: 100%;
      max-width: 980px;
      padding: 10px 16px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      z-index: 100;
      position: relative;
    }

    .hud-left {
      display: flex;
      align-items: center;
      gap: 12px;
      flex-wrap: wrap;
    }

    .hud-badge {
      display: flex;
      align-items: center;
      gap: 6px;
      padding: 6px 14px;
      border-radius: 20px;
      background: rgba(255, 255, 255, 0.92);
      backdrop-filter: blur(6px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
      border: 2px solid white;
      font-size: 1.05rem;
      font-weight: 800;
      transition: transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275);
    }
    .hud-badge.bounce {
      transform: scale(1.22);
    }

    .badge-coins { color: #b45309; }
    .badge-streak { color: #dc2626; }
    .badge-stage { color: #15803d; }

    .hud-right {
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .icon-btn {
      width: 42px;
      height: 42px;
      border-radius: 50%;
      background: rgba(255, 255, 255, 0.92);
      border: 2px solid white;
      box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 1.25rem;
      cursor: pointer;
      transition: all 0.2s ease;
    }
    .icon-btn:hover {
      transform: scale(1.1);
      background: #ffffff;
    }
    .icon-btn:active {
      transform: scale(0.95);
    }

    .kirby-voice-btn {
      background: linear-gradient(135deg, #fff0f5, #ffd6e0);
      border-color: #ffb4cf;
      color: #e11d48;
      width: auto;
      padding: 0 14px;
      border-radius: 20px;
      font-size: 0.95rem;
      font-weight: 800;
      gap: 4px;
    }

    /* 主遊戲舞台容器 */
    .game-viewport {
      width: 100%;
      max-width: 980px;
      padding: 0 12px 30px;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 14px;
      z-index: 10;
    }

    /* =========================================================
       🌲 沉浸式森林探險場景 (Forest Adventure Stage)
       ========================================================= */
    .forest-stage {
      position: relative;
      width: 100%;
      aspect-ratio: 16 / 9;
      max-height: 520px;
      border-radius: 28px;
      overflow: hidden;
      box-shadow: 0 16px 36px rgba(34, 84, 61, 0.2), 0 0 0 5px #ffffff;
      background-color: #c7ebff;
      background-image: url('forest_bg.jpg');
      background-size: cover;
      background-position: center bottom;
      background-repeat: no-repeat;
    }

    /* 探索小徑上的 10 個發光踏石站點 (Checkpoints) */
    .checkpoint-node {
      position: absolute;
      transform: translate(-50%, -50%);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      z-index: 5;
      transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
    }

    .node-stone {
      width: 38px;
      height: 38px;
      border-radius: 50%;
      background: radial-gradient(circle at 35% 30%, #ffffff 0%, #fff3b0 50%, #fcd34d 100%);
      border: 3px solid #ffffff;
      box-shadow: 0 4px 10px rgba(0, 0, 0, 0.25), 0 0 14px rgba(254, 240, 138, 0.8);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 0.95rem;
      font-weight: 900;
      color: #78350f;
      transition: all 0.35s ease;
    }

    /* 已通關站點 */
    .checkpoint-node.passed .node-stone {
      background: radial-gradient(circle at 35% 30%, #fef08a 0%, #fbbf24 60%, #d97706 100%);
      color: #451a03;
      transform: scale(0.9);
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
    }

    /* 當前站點 */
    .checkpoint-node.active .node-stone {
      background: radial-gradient(circle at 35% 30%, #ffffff 0%, #ff85a2 50%, #e11d48 100%);
      color: white;
      border-color: #fff1f2;
      transform: scale(1.25);
      animation: activeNodePulse 1.6s infinite ease-in-out;
    }
    @keyframes activeNodePulse {
      0%, 100% { box-shadow: 0 0 10px rgba(225, 29, 72, 0.6), 0 0 24px rgba(255, 133, 162, 0.8); }
      50% { box-shadow: 0 0 18px rgba(225, 29, 72, 0.9), 0 0 36px rgba(255, 133, 162, 1); }
    }

    /* 終點城堡站點 */
    .checkpoint-node.goal .node-stone {
      width: 46px;
      height: 46px;
      border-radius: 16px;
      background: radial-gradient(circle at 30% 30%, #fdf4ff 0%, #c084fc 60%, #7c3aed 100%);
      color: white;
      font-size: 1.4rem;
      border-color: #f3e8ff;
      box-shadow: 0 4px 14px rgba(124, 58, 237, 0.45);
    }

    /* 💖 沿著森林步道前進的卡比 (可點選發出 Poyo 聲音!) */
    .kirby-actor {
      position: absolute;
      z-index: 20;
      transform: translate(-50%, -85%);
      cursor: pointer;
      transition: left 0.75s cubic-bezier(0.34, 1.56, 0.64, 1), 
                  top 0.75s cubic-bezier(0.34, 1.56, 0.64, 1),
                  transform 0.75s ease;
      filter: drop-shadow(0 8px 12px rgba(225, 29, 72, 0.35));
    }
    .kirby-actor:hover {
      filter: drop-shadow(0 0 16px rgba(255, 105, 180, 0.8));
    }

    .kirby-body-svg {
      width: 100%;
      height: 100%;
      animation: kirbyWalkFloat 1.8s infinite ease-in-out;
      pointer-events: none;
    }

    @keyframes kirbyWalkFloat {
      0%, 100% { transform: translateY(0) scale(1, 1); }
      50% { transform: translateY(-8px) scale(1.04, 0.96); }
    }

    /* 答對或被戳戳時的快樂翻跟斗大跳躍 */
    .kirby-actor.hop-spin .kirby-body-svg {
      animation: kirbyFlipTrick 0.8s ease-out forwards;
    }
    @keyframes kirbyFlipTrick {
      0% { transform: translateY(0) rotate(0deg) scale(1); }
      30% { transform: translateY(-46px) rotate(180deg) scale(1.3); }
      60% { transform: translateY(-50px) rotate(360deg) scale(1.3); }
      85% { transform: translateY(0) rotate(360deg) scale(1.15, 0.9); }
      100% { transform: translateY(0) rotate(360deg) scale(1); }
    }

    /* 卡比說話對話泡泡 (Poyo! 語音氣泡) */
    .kirby-bubble {
      position: absolute;
      top: -34px;
      left: 50%;
      transform: translateX(-50%) scale(0);
      background: white;
      color: #e11d48;
      font-size: 0.95rem;
      font-weight: 900;
      padding: 4px 12px;
      border-radius: 16px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
      border: 2px solid #ffccd5;
      white-space: nowrap;
      pointer-events: none;
      transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
      z-index: 30;
    }
    .kirby-bubble::after {
      content: '';
      position: absolute;
      bottom: -6px;
      left: 50%;
      transform: translateX(-50%);
      border-width: 6px 6px 0;
      border-style: solid;
      border-color: white transparent;
      display: block;
      width: 0;
    }
    .kirby-bubble.active {
      transform: translateX(-50%) scale(1.15);
    }

    /* 點擊卡比飄出的愛心光芒 */
    .heart-floater {
      position: absolute;
      font-size: 1.4rem;
      pointer-events: none;
      animation: heartFloat 0.9s ease-out forwards;
      z-index: 25;
    }
    @keyframes heartFloat {
      0% { opacity: 1; transform: translate(-50%, 0) scale(0.6); }
      50% { opacity: 1; transform: translate(-50%, -24px) scale(1.2); }
      100% { opacity: 0; transform: translate(-50%, -48px) scale(1); }
    }

    /* 腳下小星光腳印 */
    .footstep-sparkle {
      position: absolute;
      width: 20px;
      height: 20px;
      font-size: 1.1rem;
      pointer-events: none;
      animation: sparkleFade 0.8s ease-out forwards;
      z-index: 4;
    }
    @keyframes sparkleFade {
      0% { opacity: 1; transform: scale(0.6) translateY(0); }
      100% { opacity: 0; transform: scale(1.4) translateY(-18px); }
    }

    /* 森林頂部標示牌 */
    .forest-signpost {
      position: absolute;
      top: 14px;
      left: 16px;
      background: rgba(255, 255, 255, 0.92);
      backdrop-filter: blur(8px);
      padding: 6px 14px;
      border-radius: 18px;
      border: 2px solid white;
      font-size: 0.95rem;
      font-weight: 800;
      color: #166534;
      display: flex;
      align-items: center;
      gap: 6px;
      box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
      z-index: 25;
    }

    /* =========================================================
       📋 精靈算術操作卡片 (Math & Apple Visualizer)
       ========================================================= */
    .math-panel {
      width: 100%;
      background: var(--card-bg);
      backdrop-filter: blur(12px);
      border-radius: 28px;
      padding: 20px 18px;
      border: 4px solid #ffffff;
      box-shadow: 0 12px 32px rgba(44, 62, 80, 0.12);
      display: flex;
      flex-direction: column;
      align-items: center;
      position: relative;
    }

    .math-panel.shake {
      animation: gentleShake 0.45s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
    }
    @keyframes gentleShake {
      10%, 90% { transform: translate3d(-3px, 0, 0); }
      20%, 80% { transform: translate3d(5px, 0, 0); }
      30%, 50%, 70% { transform: translate3d(-6px, 0, 0); }
      40%, 60% { transform: translate3d(6px, 0, 0); }
    }

    /* 題目算式展示 */
    .equation-board {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      font-size: 3.4rem;
      font-weight: 900;
      color: #1e293b;
      margin-bottom: 12px;
    }
    .math-num-pill {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: 64px;
      height: 64px;
      padding: 0 16px;
      border-radius: 18px;
      background: white;
      box-shadow: 0 6px 14px rgba(0, 0, 0, 0.08);
      border: 3px solid #e2e8f0;
      color: #0f172a;
    }
    .math-op-pill {
      color: #e11d48;
      font-size: 3.6rem;
    }
    .math-target-pill {
      background: #ffe4e6;
      border-color: #f43f5e;
      color: #e11d48;
      animation: pulseMark 1.5s infinite alternate ease-in-out;
    }
    @keyframes pulseMark {
      from { transform: scale(1); }
      to { transform: scale(1.08); box-shadow: 0 6px 18px rgba(244, 63, 94, 0.3); }
    }

    /* 🍎 蘋果實物數數輔助區 */
    .apple-aid-box {
      width: 100%;
      background: linear-gradient(180deg, #f8fcf7 0%, #ecf8ea 100%);
      border: 3px dashed #86efac;
      border-radius: 20px;
      padding: 14px 12px;
      margin-bottom: 18px;
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 10px;
    }

    .apple-aid-title {
      font-size: 1.15rem;
      font-weight: 800;
      color: #166534;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      flex-wrap: wrap;
    }

    .apple-aid-subtext {
      font-size: 0.9rem;
      color: #15803d;
      background: rgba(255, 255, 255, 0.9);
      padding: 3px 14px;
      border-radius: 16px;
      border: 1px solid #bbf7d0;
    }

    .apple-tray {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 12px;
      flex-wrap: wrap;
      width: 100%;
    }

    .apple-cluster {
      display: flex;
      align-items: center;
      gap: 8px;
      flex-wrap: wrap;
      justify-content: center;
      background: rgba(255, 255, 255, 0.8);
      padding: 8px 12px;
      border-radius: 16px;
      border: 2px solid #def0d8;
    }

    .tray-op {
      font-size: 2rem;
      font-weight: 900;
      color: #059669;
    }

    /* 單顆蘋果 */
    .apple-unit {
      position: relative;
      width: 50px;
      height: 50px;
      cursor: pointer;
      transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
    }
    .apple-unit:hover {
      transform: scale(1.16);
    }
    .apple-unit:active {
      transform: scale(0.92);
    }
    .apple-unit.popped {
      animation: appleBounce 0.4s ease-out;
    }
    @keyframes appleBounce {
      0% { transform: scale(1); }
      30% { transform: scale(1.3, 0.8); }
      60% { transform: scale(0.9, 1.2); }
      100% { transform: scale(1); }
    }

    .apple-unit svg {
      width: 100%;
      height: 100%;
      filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.12));
    }

    /* 減法扣除紅叉與淡出 */
    .apple-unit.subtracted {
      opacity: 0.32;
      filter: grayscale(35%);
    }
    .apple-unit.subtracted .cross-icon {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      pointer-events: none;
    }

    /* 點擊蘋果彈出數字氣泡 */
    .apple-pop-bubble {
      position: absolute;
      top: -14px;
      left: 50%;
      transform: translateX(-50%);
      background: #ff4757;
      color: white;
      font-size: 0.85rem;
      font-weight: 900;
      width: 24px;
      height: 24px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
      border: 2px solid white;
      animation: floatFadeUp 0.8s ease-out forwards;
      pointer-events: none;
      z-index: 10;
    }
    @keyframes floatFadeUp {
      0% { opacity: 0; transform: translate(-50%, 6px) scale(0.6); }
      40% { opacity: 1; transform: translate(-50%, -8px) scale(1.15); }
      100% { opacity: 0; transform: translate(-50%, -24px) scale(1); }
    }

    /* 底部 4 個可愛大單選選項 */
    .options-row {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 16px;
      width: 100%;
      max-width: 660px;
    }

    .candy-btn {
      position: relative;
      border: none;
      outline: none;
      border-radius: 22px;
      padding: 16px 8px;
      font-size: 2.5rem;
      font-weight: 900;
      color: white;
      cursor: pointer;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 4px;
      box-shadow: 0 8px 0 var(--btn-shadow), 0 14px 20px rgba(0, 0, 0, 0.14);
      transition: all 0.12s ease;
      text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    .candy-btn:hover {
      transform: translateY(-4px);
      filter: brightness(1.06);
    }
    .candy-btn:active {
      transform: translateY(6px);
      box-shadow: 0 2px 0 var(--btn-shadow), 0 6px 10px rgba(0, 0, 0, 0.1);
    }

    .btn-theme-0 {
      background: linear-gradient(145deg, #ff6b81, #ee5253);
      --btn-shadow: #c0392b;
    }
    .btn-theme-1 {
      background: linear-gradient(145deg, #ffa502, #ff7f50);
      --btn-shadow: #d35400;
    }
    .btn-theme-2 {
      background: linear-gradient(145deg, #2ed573, #10ac84);
      --btn-shadow: #1e824c;
    }
    .btn-theme-3 {
      background: linear-gradient(145deg, #1e90ff, #3742fa);
      --btn-shadow: #2f3542;
    }

    .candy-btn-sub {
      font-size: 0.85rem;
      font-weight: 700;
      opacity: 0.9;
      letter-spacing: 0.5px;
    }

    /* 鼓勵提示 Toast */
    .toast-banner {
      position: fixed;
      bottom: 24px;
      background: rgba(30, 41, 59, 0.92);
      color: white;
      padding: 10px 24px;
      border-radius: 50px;
      font-weight: 800;
      font-size: 1.1rem;
      box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
      backdrop-filter: blur(8px);
      z-index: 999;
      opacity: 0;
      transform: translateY(20px);
      transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
      pointer-events: none;
    }
    .toast-banner.show {
      opacity: 1;
      transform: translateY(0);
    }

    /* 飛舞金幣 */
    .flying-coins-box {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      pointer-events: none;
      z-index: 1000;
    }
    .flying-coin-item {
      position: absolute;
      width: 32px;
      height: 32px;
      font-size: 1.9rem;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.75s cubic-bezier(0.2, 0.8, 0.2, 1);
      z-index: 1001;
    }

    /* 慶典彩帶 Canvas */
    #fxCanvas {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      pointer-events: none;
      z-index: 800;
    }

    /* 🏆 通關結算滿版彈窗 */
    .modal-overlay {
      position: fixed;
      top: 0;
      left: 0;
      width: 100vw;
      height: 100vh;
      background: rgba(15, 23, 42, 0.78);
      backdrop-filter: blur(10px);
      display: flex;
      align-items: center;
      justify-content: center;
      z-index: 900;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.35s ease;
      padding: 16px;
    }
    .modal-overlay.active {
      opacity: 1;
      pointer-events: auto;
    }

    .victory-box {
      background: white;
      border-radius: 36px;
      padding: 36px 28px 30px;
      width: 100%;
      max-width: 480px;
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
      box-shadow: 0 25px 60px rgba(0, 0, 0, 0.35), 0 0 0 8px #ffd166;
      transform: scale(0.8) translateY(30px);
      transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
      position: relative;
    }
    .modal-overlay.active .victory-box {
      transform: scale(1) translateY(0);
    }

    .trophy-glow {
      position: absolute;
      top: 25%;
      left: 50%;
      width: 140px;
      height: 140px;
      background: radial-gradient(circle, #fde047 20%, rgba(253, 224, 71, 0) 70%);
      transform: translate(-50%, -50%);
      border-radius: 50%;
      animation: rotateLight 8s linear infinite;
      z-index: 0;
    }
    @keyframes rotateLight {
      from { transform: translate(-50%, -50%) rotate(0deg); }
      to { transform: translate(-50%, -50%) rotate(360deg); }
    }
    .trophy-symbol {
      font-size: 5.5rem;
      position: relative;
      z-index: 1;
      animation: trophyJump 1.8s infinite ease-in-out;
    }
    @keyframes trophyJump {
      0%, 100% { transform: translateY(0); }
      50% { transform: translateY(-10px) scale(1.05); }
    }

    .victory-h1 {
      font-size: 2.2rem;
      font-weight: 900;
      color: #b45309;
      margin-top: 10px;
      margin-bottom: 6px;
    }
    .victory-desc {
      font-size: 1.15rem;
      font-weight: 800;
      color: #4b5563;
      margin-bottom: 20px;
    }

    .stats-grid {
      width: 100%;
      background: #fffbeb;
      border: 2px solid #fef3c7;
      border-radius: 20px;
      padding: 16px;
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
      margin-bottom: 22px;
    }
    .stat-cell {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 4px;
    }
    .stat-num {
      font-size: 1.8rem;
      font-weight: 900;
      color: #92400e;
    }
    .stat-tit {
      font-size: 0.85rem;
      font-weight: 700;
      color: #b45309;
    }

    .honor-tag {
      background: linear-gradient(135deg, #ec4899, #f43f5e);
      color: white;
      padding: 6px 18px;
      border-radius: 50px;
      font-size: 1.05rem;
      font-weight: 800;
      margin-bottom: 22px;
      box-shadow: 0 4px 14px rgba(244, 63, 94, 0.35);
    }

    .btn-replay {
      background: linear-gradient(135deg, #10b981, #059669);
      border: none;
      outline: none;
      color: white;
      font-size: 1.4rem;
      font-weight: 900;
      padding: 16px 36px;
      border-radius: 50px;
      cursor: pointer;
      box-shadow: 0 8px 0 #047857, 0 14px 24px rgba(16, 185, 129, 0.35);
      transition: all 0.15s ease;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .btn-replay:hover {
      transform: translateY(-3px);
      filter: brightness(1.08);
    }
    .btn-replay:active {
      transform: translateY(4px);
      box-shadow: 0 2px 0 #047857;
    }

    /* 響應式優化 */
    @media (max-width: 640px) {
      .equation-board {
        font-size: 2.6rem;
        gap: 6px;
      }
      .math-num-pill {
        min-width: 52px;
        height: 52px;
        font-size: 2.3rem;
      }
      .math-op-pill {
        font-size: 2.6rem;
      }
      .options-row {
        grid-template-columns: repeat(2, 1fr);
        gap: 12px;
      }
      .candy-btn {
        padding: 14px 6px;
        font-size: 2.1rem;
      }
      .node-stone {
        width: 32px;
        height: 32px;
        font-size: 0.8rem;
      }
      .checkpoint-node.goal .node-stone {
        width: 38px;
        height: 38px;
        font-size: 1.15rem;
      }
      .apple-unit {
        width: 44px;
        height: 44px;
      }
    }
  </style>
</head>
<body>

  <!-- 彩帶星星雨 Canvas -->
  <canvas id="fxCanvas"></canvas>

  <!-- 飛舞金幣層 -->
  <div class="flying-coins-box" id="flyingCoinsBox"></div>

  <!-- 頂部 HUD (寶箱、連勝、控制鍵) -->
  <header class="top-hud">
    <div class="hud-left">
      <div class="hud-badge badge-coins" id="coinBadge">
        <span>🪙</span>
        <span id="coinVal">0</span>
      </div>
      <div class="hud-badge badge-streak" id="streakBadge">
        <span>🔥</span>
        <span>連對 <span id="streakVal">0</span> 題</span>
      </div>
      <div class="hud-badge badge-stage">
        <span>🚩</span>
        <span id="stageBadge">第 1 / 10 關</span>
      </div>
    </div>

    <div class="hud-right">
      <button class="icon-btn kirby-voice-btn" id="btnPoyoTalk" title="點我聽卡比說話">
        <span>💖</span>
        <span>波喲!</span>
      </button>
      <button class="icon-btn" id="btnSoundToggle" title="切換音效">🔊</button>
      <button class="icon-btn" id="btnQuickReset" title="重新開始">🔄</button>
    </div>
  </header>

  <!-- 主遊戲舞台 -->
  <div class="game-viewport">

    <!-- 🌲 沉浸式森林探險小徑 (Forest Adventure Stage) -->
    <section class="forest-stage" id="forestStage">
      <!-- 標示牌 -->
      <div class="forest-signpost">
        <span>🌲 夢幻精靈森林小徑</span>
        <span id="trailStatusText">・點點看卡比會說話喔!💖</span>
      </div>

      <!-- 10 個沿著蜿蜒步道的探索踏石站點 -->
      <div id="checkpointsContainer"></div>

      <!-- 💖 在森林小徑上前進探索的卡比主角 (可點選互動) -->
      <div class="kirby-actor" id="kirbyActor" title="點點我!卡比會說話唱歌喔!">
        <div class="kirby-bubble" id="kirbyBubble">波喲!💖</div>

        <svg viewBox="0 0 100 100" class="kirby-body-svg">
          <defs>
            <radialGradient id="kirbyPinkGrad" cx="35%" cy="30%" r="65%">
              <stop offset="0%" stop-color="#ffe6ef"/>
              <stop offset="65%" stop-color="#ff9ec3"/>
              <stop offset="100%" stop-color="#f0729f"/>
            </radialGradient>
          </defs>
          <!-- 左紅鞋 -->
          <ellipse cx="28" cy="85" rx="16" ry="10" fill="#e52555" transform="rotate(-12 28 85)"/>
          <!-- 右紅鞋 -->
          <ellipse cx="72" cy="85" rx="16" ry="10" fill="#e52555" transform="rotate(12 72 85)"/>
          <!-- 左手 -->
          <ellipse cx="14" cy="54" rx="12" ry="9" fill="#ffb4cf" transform="rotate(-30 14 54)"/>
          <!-- 右手 -->
          <ellipse cx="86" cy="54" rx="12" ry="9" fill="#ffb4cf" transform="rotate(30 86 54)"/>
          <!-- 圓身 -->
          <circle cx="50" cy="50" r="38" fill="url(#kirbyPinkGrad)"/>
          <!-- 左眼 -->
          <ellipse cx="40" cy="42" rx="4.5" ry="10" fill="#15274d"/>
          <ellipse cx="40" cy="38" rx="3" ry="5.5" fill="#ffffff"/>
          <ellipse cx="40" cy="47" rx="3.5" ry="4" fill="#2d68c4"/>
          <!-- 右眼 -->
          <ellipse cx="60" cy="42" rx="4.5" ry="10" fill="#15274d"/>
          <ellipse cx="60" cy="38" rx="3" ry="5.5" fill="#ffffff"/>
          <ellipse cx="60" cy="47" rx="3.5" ry="4" fill="#2d68c4"/>
          <!-- 腮紅 -->
          <ellipse cx="28" cy="55" rx="7" ry="4" fill="#ff6897" opacity="0.85"/>
          <ellipse cx="72" cy="55" rx="7" ry="4" fill="#ff6897" opacity="0.85"/>
          <!-- 微笑 -->
          <path d="M 43 54 Q 50 65 57 54 Z" fill="#9b1338"/>
          <path d="M 45 58 Q 50 64 55 58 Z" fill="#ff5376"/>
        </svg>
      </div>
    </section>

    <!-- 📋 精靈算術操作卡片 (Math & Apple Visualizer) -->
    <main class="math-panel" id="mathPanel">
      <!-- 數學算式展示 -->
      <div class="equation-board">
        <div class="math-num-pill" id="numLeft">3</div>
        <div class="math-op-pill" id="opSymbol">+</div>
        <div class="math-num-pill" id="numRight">2</div>
        <div class="math-op-pill">=</div>
        <div class="math-num-pill math-target-pill">?</div>
      </div>

      <!-- 🍎 蘋果輔助數數區 -->
      <section class="apple-aid-box">
        <div class="apple-aid-title" id="applePrompt">
          <span>🍎 蘋果數數小精靈</span>
        </div>
        <div class="apple-tray" id="appleTray"></div>
        <div class="apple-aid-subtext">💡 點點看蘋果,可以一顆顆幫忙數數喔!</div>
      </section>

      <!-- 4 個大答題選項按鈕 -->
      <div class="options-row" id="optionsRow"></div>
    </main>

  </div>

  <!-- 提示訊息條 -->
  <div class="toast-banner" id="toastBanner">提示訊息</div>

  <!-- 🏆 通關結算滿版彈窗 -->
  <div class="modal-overlay" id="victoryModal">
    <div class="victory-box">
      <div class="trophy-glow"></div>
      <div class="trophy-symbol">🏆</div>

      <h1 class="victory-h1">森林大闖關成功!</h1>
      <p class="victory-desc">卡比跳著勝利之舞,成功抵達精靈城堡囉!💖🏰</p>

      <div class="stats-grid">
        <div class="stat-cell">
          <div class="stat-num" id="endCoins">0</div>
          <div class="stat-tit">🪙 獲得金幣</div>
        </div>
        <div class="stat-cell">
          <div class="stat-num" id="endStreak">0</div>
          <div class="stat-tit">🔥 最高連對</div>
        </div>
      </div>

      <div class="honor-tag">🌟 榮譽稱號:夢幻森林小小數學王 ⭐⭐⭐⭐⭐</div>

      <button class="btn-replay" id="btnReplay">
        <span>再玩一次</span>
        <span>🔄</span>
      </button>
    </div>
  </div>

  <script>
    /* =========================================================
       1. 任天堂 Switch 星之卡比官方風格純代碼音效與語音合成引擎
          (Nintendo Switch Kirby Audio & Voice Synthesizer)
       ========================================================= */
    class SwitchKirbyAudioEngine {
      constructor() {
        this.ctx = null;
        this.enabled = true;
      }

      initContext() {
        if (!this.ctx) {
          const AudioContext = window.AudioContext || window.webkitAudioContext;
          if (AudioContext) {
            this.ctx = new AudioContext();
          }
        }
        if (this.ctx && this.ctx.state === 'suspended') {
          this.ctx.resume();
        }
      }

      toggleAudio() {
        this.enabled = !this.enabled;
        return this.enabled;
      }

      // 💖 1. 卡比招牌萌語音:「波喲!」(Poyo! 語音共鳴合成)
      playKirbyPoyo() {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        // Vowel Formant Synthesis for "PO-YO!"
        // "P" 爆破輔音
        const pBuffer = this.ctx.createBuffer(1, Math.floor(this.ctx.sampleRate * 0.022), this.ctx.sampleRate);
        const pData = pBuffer.getChannelData(0);
        for (let i = 0; i < pData.length; i++) {
          pData[i] = (Math.random() * 2 - 1) * Math.exp(-i / (this.ctx.sampleRate * 0.005));
        }
        const pSrc = this.ctx.createBufferSource();
        pSrc.buffer = pBuffer;
        const pFilt = this.ctx.createBiquadFilter();
        pFilt.type = 'bandpass';
        pFilt.frequency.setValueAtTime(620, now);
        pFilt.Q.setValueAtTime(3.2, now);
        const pGain = this.ctx.createGain();
        pGain.gain.setValueAtTime(0.25, now);
        pSrc.connect(pFilt);
        pFilt.connect(pGain);
        pGain.connect(this.ctx.destination);
        pSrc.start(now);

        // "O-YO" 母音主音源 (雙振盪器合成)
        const osc1 = this.ctx.createOscillator();
        const osc2 = this.ctx.createOscillator();
        osc1.type = 'triangle';
        osc2.type = 'sine';

        // 卡比招牌俏皮音調滑動: 480Hz -> 610Hz -> 510Hz -> 540Hz
        [osc1, osc2].forEach(osc => {
          osc.frequency.setValueAtTime(480, now);
          osc.frequency.exponentialRampToValueAtTime(620, now + 0.08); // Po 揚起
          osc.frequency.exponentialRampToValueAtTime(510, now + 0.17); // Yo 轉折
          osc.frequency.exponentialRampToValueAtTime(540, now + 0.27);
        });

        // 雙共振峰濾波器 (模擬可愛口腔共鳴)
        const f1 = this.ctx.createBiquadFilter();
        f1.type = 'bandpass';
        f1.frequency.setValueAtTime(530, now);
        f1.Q.setValueAtTime(4.2, now);

        const f2 = this.ctx.createBiquadFilter();
        f2.type = 'bandpass';
        f2.frequency.setValueAtTime(1020, now);
        f2.Q.setValueAtTime(4.8, now);

        const vGain = this.ctx.createGain();
        vGain.gain.setValueAtTime(0.001, now);
        vGain.gain.linearRampToValueAtTime(0.32, now + 0.025);
        vGain.gain.setValueAtTime(0.30, now + 0.14);
        vGain.gain.exponentialRampToValueAtTime(0.001, now + 0.32);

        osc1.connect(f1);
        osc2.connect(f2);
        f1.connect(vGain);
        f2.connect(vGain);
        vGain.connect(this.ctx.destination);

        osc1.start(now);
        osc2.start(now);
        osc1.stop(now + 0.32);
        osc2.stop(now + 0.32);
      }

      // 💖 2. 卡比打招呼萌語音:「嗨~!」(Hi! / はーい!)
      playKirbyHi() {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        const osc = this.ctx.createOscillator();
        osc.type = 'triangle';
        // 萌萌高音向上揚: 560Hz -> 760Hz
        osc.frequency.setValueAtTime(560, now);
        osc.frequency.exponentialRampToValueAtTime(780, now + 0.22);

        const f1 = this.ctx.createBiquadFilter();
        f1.type = 'bandpass';
        // 母音從 /a/ 滑向 /i/
        f1.frequency.setValueAtTime(740, now);
        f1.frequency.exponentialRampToValueAtTime(390, now + 0.22);
        f1.Q.setValueAtTime(4, now);

        const gain = this.ctx.createGain();
        gain.gain.setValueAtTime(0.01, now);
        gain.gain.linearRampToValueAtTime(0.3, now + 0.03);
        gain.gain.exponentialRampToValueAtTime(0.001, now + 0.26);

        osc.connect(f1);
        f1.connect(gain);
        gain.connect(this.ctx.destination);

        osc.start(now);
        osc.stop(now + 0.26);
      }

      // ⭐ 3. Switch《星之卡比 探索發現》星星硬幣 (Star Coin) 水晶清脆鈴聲
      playSwitchStarCoin(delay = 0) {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime + delay;

        // 水晶鈴音三和弦層疊 (C6, G6, C7)
        const notes = [
          { f: 1046.50, t: 0.00, d: 0.32 },
          { f: 1567.98, t: 0.04, d: 0.38 },
          { f: 2093.00, t: 0.08, d: 0.45 }
        ];

        notes.forEach(n => {
          const osc = this.ctx.createOscillator();
          const mod = this.ctx.createOscillator();
          const modGain = this.ctx.createGain();
          const gain = this.ctx.createGain();

          osc.type = 'sine';
          mod.type = 'sine';
          mod.frequency.setValueAtTime(n.f * 2.2, now + n.t); // FM 水晶泛音
          modGain.gain.setValueAtTime(n.f * 0.35, now + n.t);
          modGain.gain.exponentialRampToValueAtTime(1, now + n.t + 0.12);

          mod.connect(modGain);
          modGain.connect(osc.frequency);

          osc.frequency.setValueAtTime(n.f, now + n.t);
          gain.gain.setValueAtTime(0.18, now + n.t);
          gain.gain.exponentialRampToValueAtTime(0.001, now + n.t + n.d);

          osc.connect(gain);
          gain.connect(this.ctx.destination);

          mod.start(now + n.t);
          osc.start(now + n.t);
          mod.stop(now + n.t + n.d);
          osc.stop(now + n.t + n.d);
        });
      }

      // ✨ 4. Switch 傳送之星閃耀衝刺音 (Warp Star Sparkle)
      playWarpStarWhoosh() {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        const starNotes = [1318.51, 1567.98, 1760.00, 2093.00, 2637.02];
        starNotes.forEach((freq, idx) => {
          const t = now + idx * 0.035;
          const osc = this.ctx.createOscillator();
          const gain = this.ctx.createGain();
          osc.type = 'sine';
          osc.frequency.setValueAtTime(freq, t);

          gain.gain.setValueAtTime(0.14, t);
          gain.gain.exponentialRampToValueAtTime(0.001, t + 0.22);

          osc.connect(gain);
          gain.connect(this.ctx.destination);

          osc.start(t);
          osc.stop(t + 0.22);
        });
      }

      // 🐾 5. 卡比跳躍浮空音 (Boing-Puff)
      playKirbyHopSound() {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();
        osc.type = 'sine';
        osc.frequency.setValueAtTime(360, now);
        osc.frequency.exponentialRampToValueAtTime(680, now + 0.12);

        gain.gain.setValueAtTime(0.24, now);
        gain.gain.exponentialRampToValueAtTime(0.01, now + 0.14);

        osc.connect(gain);
        gain.connect(this.ctx.destination);
        osc.start(now);
        osc.stop(now + 0.14);
      }

      // ❓ 6. 答錯時卡比疑惑輕呼聲:「波?」(柔和無挫折提示音)
      playKirbyDoubt() {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        const osc = this.ctx.createOscillator();
        const f1 = this.ctx.createBiquadFilter();
        const gain = this.ctx.createGain();

        osc.type = 'triangle';
        // 疑問音調微揚: 340Hz -> 420Hz
        osc.frequency.setValueAtTime(340, now);
        osc.frequency.exponentialRampToValueAtTime(430, now + 0.18);

        f1.type = 'bandpass';
        f1.frequency.setValueAtTime(550, now);
        f1.Q.setValueAtTime(3.5, now);

        gain.gain.setValueAtTime(0.18, now);
        gain.gain.exponentialRampToValueAtTime(0.01, now + 0.22);

        osc.connect(f1);
        f1.connect(gain);
        gain.connect(this.ctx.destination);

        osc.start(now);
        osc.stop(now + 0.22);
      }

      // 🍎 7. 點擊蘋果泡泡啵聲
      playApplePop(step = 1) {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        const osc = this.ctx.createOscillator();
        const gain = this.ctx.createGain();

        osc.type = 'sine';
        const baseFreq = 480 + (step % 10) * 45;
        osc.frequency.setValueAtTime(baseFreq, now);
        osc.frequency.exponentialRampToValueAtTime(baseFreq * 1.5, now + 0.09);

        gain.gain.setValueAtTime(0.2, now);
        gain.gain.exponentialRampToValueAtTime(0.01, now + 0.09);

        osc.connect(gain);
        gain.connect(this.ctx.destination);

        osc.start(now);
        osc.stop(now + 0.09);
      }

      // 🎵 8. 答對時的卡比快樂過關短樂句 (Kirby Mini Celebration)
      playCorrectCelebration() {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        // 先播放星星硬幣聲,再接卡比 Poyo 語音!
        this.playSwitchStarCoin(0);
        setTimeout(() => this.playKirbyPoyo(), 120);

        // 搭配經典 Green Greens 活力音階
        const melody = [
          { f: 523.25, t: 0.18, d: 0.08 }, // C5
          { f: 659.25, t: 0.26, d: 0.08 }, // E5
          { f: 783.99, t: 0.34, d: 0.08 }, // G5
          { f: 1046.50, t: 0.42, d: 0.16 } // C6
        ];

        melody.forEach(n => {
          const osc = this.ctx.createOscillator();
          const gain = this.ctx.createGain();
          osc.type = 'triangle';
          osc.frequency.setValueAtTime(n.f, now + n.t);

          gain.gain.setValueAtTime(0.22, now + n.t);
          gain.gain.exponentialRampToValueAtTime(0.001, now + n.t + n.d);

          osc.connect(gain);
          gain.connect(this.ctx.destination);

          osc.start(now + n.t);
          osc.stop(now + n.t + n.d);
        });
      }

      // 🏆 9. 經典「卡比勝利之舞」(Kirby Dance Fanfare - 全系列破關招牌曲)
      playKirbyDanceTheme() {
        if (!this.enabled) return;
        this.initContext();
        if (!this.ctx) return;
        const now = this.ctx.currentTime;

        // 官方經典三段式卡比跳舞旋律 (D 大調)
        const danceNotes = [
          // 第 1 步 (左踏步)
          { f: 587.33, t: 0.00, d: 0.10 }, // D5
          { f: 739.99, t: 0.10, d: 0.10 }, // F#5
          { f: 880.00, t: 0.20, d: 0.10 }, // A5
          { f: 1174.66, t: 0.30, d: 0.24 },// D6
          // 第 2 步 (右踏步)
          { f: 1108.73, t: 0.58, d: 0.10 },// C#6
          { f: 987.77, t: 0.68, d: 0.10 }, // B5
          { f: 880.00, t: 0.78, d: 0.10 }, // A5
          { f: 739.99, t: 0.88, d: 0.24 }, // F#5
          // 第 3 步 (歡樂轉圈翻跟斗)
          { f: 783.99, t: 1.16, d: 0.09 }, // G5
          { f: 880.00, t: 0.09 + 1.16, d: 0.09 }, // A5
          { f: 987.77, t: 0.18 + 1.16, d: 0.09 }, // B5
          { f: 783.99, t: 0.27 + 1.16, d: 0.09 }, // G5
          { f: 880.00, t: 0.36 + 1.16, d: 0.09 }, // A5
          { f: 987.77, t: 0.45 + 1.16, d: 0.09 }, // B5
          { f: 1108.73, t: 0.54 + 1.16, d: 0.14 },// C#6
          // 終曲定格亮亮相!(D6 長大調和弦)
          { f: 1174.66, t: 1.88, d: 0.65 } // D6
        ];

        danceNotes.forEach(n => {
          // 主音 (Triangle 圓潤管樂)
          const osc1 = this.ctx.createOscillator();
          const gain1 = this.ctx.createGain();
          osc1.type = 'triangle';
          osc1.frequency.setValueAtTime(n.f, now + n.t);

          gain1.gain.setValueAtTime(0.24, now + n.t);
          gain1.gain.exponentialRampToValueAtTime(0.001, now + n.t + n.d);

          osc1.connect(gain1);
          gain1.connect(this.ctx.destination);
          osc1.start(now + n.t);
          osc1.stop(now + n.t + n.d);

          // 輔助泛音 (Square 活潑跳躍音)
          const osc2 = this.ctx.createOscillator();
          const gain2 = this.ctx.createGain();
          osc2.type = 'square';
          osc2.frequency.setValueAtTime(n.f, now + n.t);

          gain2.gain.setValueAtTime(0.12, now + n.t);
          gain2.gain.exponentialRampToValueAtTime(0.001, now + n.t + n.d);

          osc2.connect(gain2);
          gain2.connect(this.ctx.destination);
          osc2.start(now + n.t);
          osc2.stop(now + n.t + n.d);
        });

        // 終曲伴隨卡比興奮的「波喲!YAY!」
        setTimeout(() => {
          this.playKirbyPoyo();
          this.playWarpStarWhoosh();
        }, 2200);
      }
    }

    const sound = new SwitchKirbyAudioEngine();

    /* =========================================================
       2. 蘋果與紅叉 SVG 生成
       ========================================================= */
    function renderAppleSvg(type = 'red') {
      const isGreen = type === 'green';
      const c1 = isGreen ? '#84cc16' : '#ef4444';
      const c2 = isGreen ? '#4d7c0f' : '#b91c1c';
      const hl = isGreen ? '#d9f99d' : '#fca5a5';

      return `
        <svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
          <path d="M 28 16 Q 30 7 38 6" stroke="#78350f" stroke-width="3.5" fill="none" stroke-linecap="round"/>
          <path d="M 32 12 Q 44 8 42 18 Q 33 18 32 12 Z" fill="#22c55e"/>
          <path d="M 30 18 C 18 16, 8 26, 9 38 C 10 49, 22 55, 30 52 C 38 55, 50 49, 51 38 C 52 26, 42 16, 30 18 Z" fill="${c1}"/>
          <path d="M 16 43 C 24 53, 36 53, 44 43 C 41 51, 19 51, 16 43 Z" fill="${c2}" opacity="0.4"/>
          <ellipse cx="20" cy="28" rx="4.5" ry="8" fill="${hl}" transform="rotate(-20 20 28)" opacity="0.8"/>
        </svg>
      `;
    }

    function renderCrossSvg() {
      return `
        <svg class="cross-icon" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg">
          <circle cx="30" cy="35" r="22" fill="rgba(255, 255, 255, 0.78)"/>
          <line x1="18" y1="23" x2="42" y2="47" stroke="#dc2626" stroke-width="6.5" stroke-linecap="round"/>
          <line x1="42" y1="23" x2="18" y2="47" stroke="#dc2626" stroke-width="6.5" stroke-linecap="round"/>
        </svg>
      `;
    }

    /* =========================================================
       3. 森林探險小徑 10 個站點座標映射 (完全精確對齊背景圖林間步道)
       ========================================================= */
    const TRAIL_CHECKPOINTS = [
      { step: 1,  x: 13, y: 88, scale: 1.18, label: "1" },  // 前景起點 (近左)
      { step: 2,  x: 27, y: 89, scale: 1.14, label: "2" },  // 林間下道 (中左)
      { step: 3,  x: 43, y: 90, scale: 1.10, label: "3" },  // 林間下道 (正中)
      { step: 4,  x: 60, y: 88, scale: 1.05, label: "4" },  // 穿過微笑紫菇 (中右)
      { step: 5,  x: 77, y: 86, scale: 0.98, label: "5" },  // 藍色笑臉菇旁 (轉折點)
      { step: 6,  x: 69, y: 76, scale: 0.88, label: "6" },  // 沿著小丘蜿蜒攀升
      { step: 7,  x: 55, y: 73, scale: 0.80, label: "7" },  // 穿過林蔭中央小坡
      { step: 8,  x: 43, y: 69, scale: 0.72, label: "8" },  // 微笑黃樹前步道
      { step: 9,  x: 48, y: 63, scale: 0.65, label: "9" },  // 迎向遠方群山山谷
      { step: 10, x: 52, y: 56, scale: 0.58, label: "🏰", isGoal: true } // 終點!精靈城堡
    ];

    /* =========================================================
       4. 彩帶與五角星雨 Canvas 特效
       ========================================================= */
    const fxCanvas = document.getElementById('fxCanvas');
    const fxCtx = fxCanvas.getContext('2d');
    let fxParticles = [];
    let fxAnimId = null;

    function resizeFx() {
      fxCanvas.width = window.innerWidth;
      fxCanvas.height = window.innerHeight;
    }
    window.addEventListener('resize', resizeFx);
    resizeFx();

    function triggerConfetti(count = 45, isMassive = false) {
      const colors = ['#ff4757', '#ffa502', '#2ed573', '#1e90ff', '#9b59b6', '#ffd32a', '#ff9ff3'];
      const total = isMassive ? 160 : count;

      for (let i = 0; i < total; i++) {
        fxParticles.push({
          x: isMassive ? Math.random() * fxCanvas.width : fxCanvas.width * 0.5 + (Math.random() - 0.5) * 280,
          y: isMassive ? Math.random() * -80 : fxCanvas.height * 0.4,
          vx: (Math.random() - 0.5) * (isMassive ? 14 : 9),
          vy: isMassive ? Math.random() * 5 + 3 : -(Math.random() * 8 + 4),
          size: Math.random() * 8 + 6,
          color: colors[Math.floor(Math.random() * colors.length)],
          rotation: Math.random() * 360,
          rotSpeed: (Math.random() - 0.5) * 10,
          shape: Math.random() > 0.4 ? 'star' : 'rect',
          gravity: 0.28,
          alpha: 1,
          decay: Math.random() * 0.015 + 0.008
        });
      }

      if (!fxAnimId) {
        animateFx();
      }
    }

    function drawStarShape(cx, cy, spikes, outerR, innerR, fill, alpha) {
      let rot = (Math.PI / 2) * 3;
      let x = cx;
      let y = cy;
      const step = Math.PI / spikes;

      fxCtx.save();
      fxCtx.globalAlpha = alpha;
      fxCtx.beginPath();
      fxCtx.moveTo(cx, cy - outerR);
      for (let i = 0; i < spikes; i++) {
        x = cx + Math.cos(rot) * outerR;
        y = cy + Math.sin(rot) * outerR;
        fxCtx.lineTo(x, y);
        rot += step;
        x = cx + Math.cos(rot) * innerR;
        y = cy + Math.sin(rot) * innerR;
        fxCtx.lineTo(x, y);
        rot += step;
      }
      fxCtx.lineTo(cx, cy - outerR);
      fxCtx.closePath();
      fxCtx.fillStyle = fill;
      fxCtx.fill();
      fxCtx.restore();
    }

    function animateFx() {
      fxCtx.clearRect(0, 0, fxCanvas.width, fxCanvas.height);

      for (let i = fxParticles.length - 1; i >= 0; i--) {
        const p = fxParticles[i];
        p.x += p.vx;
        p.y += p.vy;
        p.vy += p.gravity;
        p.rotation += p.rotSpeed;
        p.alpha -= p.decay;

        if (p.alpha <= 0 || p.y > fxCanvas.height + 40) {
          fxParticles.splice(i, 1);
          continue;
        }

        if (p.shape === 'star') {
          drawStarShape(p.x, p.y, 5, p.size, p.size / 2, p.color, p.alpha);
        } else {
          fxCtx.save();
          fxCtx.globalAlpha = p.alpha;
          fxCtx.translate(p.x, p.y);
          fxCtx.rotate((p.rotation * Math.PI) / 180);
          fxCtx.fillStyle = p.color;
          fxCtx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
          fxCtx.restore();
        }
      }

      if (fxParticles.length > 0) {
        fxAnimId = requestAnimationFrame(animateFx);
      } else {
        fxAnimId = null;
      }
    }

    /* =========================================================
       5. 飛耀金幣與腳印星光
       ========================================================= */
    function launchFlyingCoins(fromEl) {
      const fromRect = fromEl.getBoundingClientRect();
      const toRect = document.getElementById('coinBadge').getBoundingClientRect();
      const container = document.getElementById('flyingCoinsBox');

      const sX = fromRect.left + fromRect.width / 2;
      const sY = fromRect.top + fromRect.height / 2;
      const eX = toRect.left + toRect.width / 2;
      const eY = toRect.top + toRect.height / 2;

      for (let i = 0; i < 4; i++) {
        const c = document.createElement('div');
        c.className = 'flying-coin-item';
        c.innerHTML = '🪙';
        c.style.left = `${sX - 16}px`;
        c.style.top = `${sY - 16}px`;
        container.appendChild(c);

        setTimeout(() => {
          c.style.transform = `translate(${eX - sX}px, ${eY - sY}px) scale(0.65)`;
          c.style.opacity = '0.9';
          sound.playSwitchStarCoin(0.12);

          setTimeout(() => {
            c.remove();
            const badge = document.getElementById('coinBadge');
            badge.classList.remove('bounce');
            void badge.offsetWidth;
            badge.classList.add('bounce');
          }, 650);
        }, i * 80);
      }
    }

    function leaveFootstepSparkle(xPercent, yPercent) {
      const stage = document.getElementById('forestStage');
      const sparkle = document.createElement('div');
      sparkle.className = 'footstep-sparkle';
      sparkle.innerHTML = '✨';
      sparkle.style.left = `${xPercent}%`;
      sparkle.style.top = `${yPercent}%`;
      stage.appendChild(sparkle);
      setTimeout(() => sparkle.remove(), 800);
    }

    // 觸發卡比可愛對話氣泡與愛心
    function triggerKirbyChat(text = "波喲!💖") {
      const bubble = document.getElementById('kirbyBubble');
      bubble.textContent = text;
      bubble.classList.add('active');

      const actor = document.getElementById('kirbyActor');
      const heart = document.createElement('div');
      heart.className = 'heart-floater';
      heart.innerHTML = Math.random() > 0.5 ? '💖' : '⭐';
      heart.style.left = '50%';
      heart.style.top = '-20px';
      actor.appendChild(heart);

      setTimeout(() => heart.remove(), 900);
      setTimeout(() => bubble.classList.remove('active'), 1200);
    }

    /* =========================================================
       6. 遊戲狀態與森林冒險核心邏輯
       ========================================================= */
    const MAX_STAGES = 10;

    const appState = {
      stage: 1,
      coins: 0,
      streak: 0,
      maxStreak: 0,
      currentQ: null,
      isLocked: false,
      appleTaps: 0
    };

    function renderCheckpoints() {
      const container = document.getElementById('checkpointsContainer');
      container.innerHTML = '';

      TRAIL_CHECKPOINTS.forEach(pt => {
        const node = document.createElement('div');
        node.className = `checkpoint-node ${pt.isGoal ? 'goal' : ''}`;
        node.id = `ckpt-${pt.step}`;
        node.style.left = `${pt.x}%`;
        node.style.top = `${pt.y}%`;

        node.innerHTML = `
          <div class="node-stone">${pt.label}</div>
        `;

        container.appendChild(node);
      });

      updateKirbyOnTrail(false);
    }

    function updateKirbyOnTrail(animate = true) {
      const currentPt = TRAIL_CHECKPOINTS[appState.stage - 1];
      const kirby = document.getElementById('kirbyActor');

      if (!currentPt || !kirby) return;

      const baseSize = 62;
      const targetSize = baseSize * currentPt.scale;
      kirby.style.width = `${targetSize}px`;
      kirby.style.height = `${targetSize}px`;

      kirby.style.transition = animate 
        ? 'left 0.75s cubic-bezier(0.34, 1.56, 0.64, 1), top 0.75s cubic-bezier(0.34, 1.56, 0.64, 1)' 
        : 'none';

      kirby.style.left = `${currentPt.x}%`;
      kirby.style.top = `${currentPt.y}%`;

      if (animate) {
        leaveFootstepSparkle(currentPt.x, currentPt.y);
      }

      TRAIL_CHECKPOINTS.forEach(pt => {
        const node = document.getElementById(`ckpt-${pt.step}`);
        if (!node) return;
        node.classList.remove('active', 'passed');
        const stone = node.querySelector('.node-stone');

        if (pt.step < appState.stage) {
          node.classList.add('passed');
          if (!pt.isGoal) stone.innerHTML = '⭐';
        } else if (pt.step === appState.stage) {
          node.classList.add('active');
          if (!pt.isGoal) stone.innerHTML = `${pt.step}`;
        } else {
          if (!pt.isGoal) stone.innerHTML = `${pt.step}`;
        }
      });

      document.getElementById('stageBadge').textContent = `第 ${appState.stage} / ${MAX_STAGES} 關`;
      const statusText = appState.stage === 10 ? "・抵達精靈城堡!🏰" : `・前進到第 ${appState.stage} 站 🐾`;
      document.getElementById('trailStatusText').textContent = statusText;
    }

    function createMathQuestion() {
      const isAdd = Math.random() >= 0.5;
      let num1, num2, answer, operator;

      if (isAdd) {
        operator = '+';
        answer = Math.floor(Math.random() * 9) + 2;
        num1 = Math.floor(Math.random() * (answer - 1)) + 1;
        num2 = answer - num1;
      } else {
        operator = '-';
        num1 = Math.floor(Math.random() * 9) + 2;
        num2 = Math.floor(Math.random() * (num1 - 1)) + 1;
        answer = num1 - num2;
      }

      const pool = [];
      for (let i = 0; i <= 10; i++) {
        if (i !== answer) pool.push(i);
      }
      pool.sort((a, b) => Math.abs(a - answer) - Math.abs(b - answer) || (Math.random() - 0.5));
      const distractors = pool.slice(0, 3);
      const options = [answer, ...distractors].sort(() => Math.random() - 0.5);

      return { num1, num2, operator, answer, options };
    }

    function displayQuestion(q) {
      appState.currentQ = q;
      appState.isLocked = false;
      appState.appleTaps = 0;

      document.getElementById('numLeft').textContent = q.num1;
      document.getElementById('opSymbol').textContent = q.operator;
      document.getElementById('numRight').textContent = q.num2;

      const appleTray = document.getElementById('appleTray');
      const prompt = document.getElementById('applePrompt');
      appleTray.innerHTML = '';

      if (q.operator === '+') {
        prompt.innerHTML = `
          <span>🍎 ${q.num1} 顆紅蘋果</span>
          <span>➕</span>
          <span>🍏 ${q.num2} 顆青蘋果,合起來有幾顆呢?</span>
        `;

        const grp1 = document.createElement('div');
        grp1.className = 'apple-cluster';
        for (let i = 1; i <= q.num1; i++) {
          grp1.appendChild(buildAppleUnit('red', i));
        }

        const opDiv = document.createElement('div');
        opDiv.className = 'tray-op';
        opDiv.textContent = '➕';

        const grp2 = document.createElement('div');
        grp2.className = 'apple-cluster';
        for (let i = 1; i <= q.num2; i++) {
          grp2.appendChild(buildAppleUnit('green', q.num1 + i));
        }

        appleTray.appendChild(grp1);
        appleTray.appendChild(opDiv);
        appleTray.appendChild(grp2);

      } else {
        const remain = q.num1 - q.num2;
        prompt.innerHTML = `
          <span>🍎 原本有 ${q.num1} 顆蘋果</span>
          <span>➖</span>
          <span>拿走扣除 ${q.num2} 顆(打叉叉 ❌),還剩下幾顆呢?</span>
        `;

        const grp = document.createElement('div');
        grp.className = 'apple-cluster';

        for (let i = 1; i <= q.num1; i++) {
          const isTaken = i > remain;
          grp.appendChild(buildAppleUnit('red', i, isTaken));
        }

        appleTray.appendChild(grp);
      }

      displayOptions(q.options);
    }

    function buildAppleUnit(color, count, isSub = false) {
      const unit = document.createElement('div');
      unit.className = `apple-unit ${isSub ? 'subtracted' : ''}`;
      unit.innerHTML = renderAppleSvg(color);

      if (isSub) {
        unit.insertAdjacentHTML('beforeend', renderCrossSvg());
        unit.title = "扣除的蘋果 ❌";
      }

      unit.addEventListener('click', () => {
        unit.classList.remove('popped');
        void unit.offsetWidth;
        unit.classList.add('popped');

        appState.appleTaps++;
        sound.playApplePop(appState.appleTaps);

        const bubble = document.createElement('div');
        bubble.className = 'apple-pop-bubble';
        bubble.textContent = count;
        unit.appendChild(bubble);

        setTimeout(() => bubble.remove(), 800);
      });

      return unit;
    }

    function displayOptions(options) {
      const row = document.getElementById('optionsRow');
      row.innerHTML = '';

      options.forEach((opt, idx) => {
        const btn = document.createElement('button');
        btn.className = `candy-btn btn-theme-${idx % 4}`;
        btn.innerHTML = `
          <span>${opt}</span>
          <span class="candy-btn-sub">點我回答</span>
        `;

        btn.addEventListener('click', () => onChooseAnswer(opt, btn));
        row.appendChild(btn);
      });
    }

    let toastTimer = null;
    function notify(msg) {
      const t = document.getElementById('toastBanner');
      t.textContent = msg;
      t.classList.add('show');
      if (toastTimer) clearTimeout(toastTimer);
      toastTimer = setTimeout(() => t.classList.remove('show'), 1900);
    }

    // 作答判定
    function onChooseAnswer(selected, btnEl) {
      if (appState.isLocked) return;
      const q = appState.currentQ;

      if (selected === q.answer) {
        // === 答對!Switch 卡比音效與前進 ===
        appState.isLocked = true;
        appState.coins += 10;
        appState.streak += 1;
        if (appState.streak > appState.maxStreak) {
          appState.maxStreak = appState.streak;
        }

        document.getElementById('coinVal').textContent = appState.coins;
        document.getElementById('streakVal').textContent = appState.streak;

        // 🎵 Switch 卡比慶祝音效 + 卡比親口說「波喲!」(Poyo!)
        sound.playCorrectCelebration();
        launchFlyingCoins(btnEl);
        triggerConfetti(35);

        // 卡比歡樂跳躍與對話氣泡
        const actor = document.getElementById('kirbyActor');
        actor.classList.add('hop-spin');
        triggerKirbyChat("波喲!太棒了!💖");
        setTimeout(() => actor.classList.remove('hop-spin'), 800);

        const cheers = ["答對囉!卡比:波喲~!💖", "算得真快!往前走囉!🐾", "答對啦!往城堡邁進!🌟", "太厲害了!好聰明!🎉"];
        notify(cheers[Math.floor(Math.random() * cheers.length)]);

        setTimeout(() => {
          if (appState.stage < MAX_STAGES) {
            appState.stage++;
            sound.playKirbyHopSound();
            updateKirbyOnTrail(true);
            displayQuestion(createMathQuestion());
          } else {
            showFinalVictory();
          }
        }, 1100);

      } else {
        // === 答錯!卡比溫和疑惑萌聲「波?」===
        appState.streak = 0;
        document.getElementById('streakVal').textContent = appState.streak;

        sound.playKirbyDoubt();
        triggerKirbyChat("波?再試一次~💡");

        const panel = document.getElementById('mathPanel');
        panel.classList.remove('shake');
        void panel.offsetWidth;
        panel.classList.add('shake');

        notify("再數數看上面的蘋果喔,加油~💪");
      }
    }

    // 🏆 通關結算 (播放全套卡比勝利之舞!)
    function showFinalVictory() {
      // 🎵 播放官方經典「卡比勝利之舞」樂曲
      sound.playKirbyDanceTheme();
      triggerConfetti(180, true);
      setTimeout(() => triggerConfetti(120, true), 900);

      // 卡比在城堡前歡樂轉圈
      const actor = document.getElementById('kirbyActor');
      actor.classList.add('hop-spin');
      triggerKirbyChat("波喲!勝利通關!🏰🎉");
      setTimeout(() => actor.classList.remove('hop-spin'), 1200);

      document.getElementById('endCoins').textContent = appState.coins;
      document.getElementById('endStreak').textContent = appState.maxStreak;

      document.getElementById('victoryModal').classList.add('active');
    }

    function restartAdventure() {
      appState.stage = 1;
      appState.coins = 0;
      appState.streak = 0;
      appState.maxStreak = 0;
      appState.isLocked = false;

      document.getElementById('coinVal').textContent = '0';
      document.getElementById('streakVal').textContent = '0';

      document.getElementById('victoryModal').classList.remove('active');

      sound.playWarpStarWhoosh();
      renderCheckpoints();
      displayQuestion(createMathQuestion());
      notify("卡比出發囉!波喲~!🏕️");
    }

    /* =========================================================
       7. 頁面啟動與互動事件
       ========================================================= */
    document.addEventListener('DOMContentLoaded', () => {
      renderCheckpoints();
      displayQuestion(createMathQuestion());

      window.addEventListener('resize', () => {
        updateKirbyOnTrail(false);
      });

      document.getElementById('btnQuickReset').addEventListener('click', restartAdventure);
      document.getElementById('btnReplay').addEventListener('click', restartAdventure);

      const btnSound = document.getElementById('btnSoundToggle');
      btnSound.addEventListener('click', () => {
        const on = sound.toggleAudio();
        btnSound.textContent = on ? '🔊' : '🔇';
        notify(on ? '音效已開啟 🔊' : '音效已靜音 🔇');
      });

      // 💖 點擊頂部「波喲!」按鈕隨時聽卡比發聲
      const btnPoyo = document.getElementById('btnPoyoTalk');
      btnPoyo.addEventListener('click', () => {
        const words = ["波喲!💖", "嗨~!✨", "波喲波喲!🐾"];
        const chosen = words[Math.floor(Math.random() * words.length)];
        if (Math.random() > 0.5) {
          sound.playKirbyPoyo();
        } else {
          sound.playKirbyHi();
        }
        triggerKirbyChat(chosen);
      });

      // 💖 點擊畫面上的卡比本體,卡比會跳躍並說「波喲!」或「嗨~!」
      const kirbyActor = document.getElementById('kirbyActor');
      kirbyActor.addEventListener('click', () => {
        kirbyActor.classList.remove('hop-spin');
        void kirbyActor.offsetWidth;
        kirbyActor.classList.add('hop-spin');

        if (Math.random() > 0.4) {
          sound.playKirbyPoyo();
          triggerKirbyChat("波喲!(Poyo!) 💖");
        } else {
          sound.playKirbyHi();
          triggerKirbyChat("嗨~!(Hi!) ✨");
        }

        setTimeout(() => kirbyActor.classList.remove('hop-spin'), 800);
      });

      // 首次點選喚醒 Web Audio API
      document.body.addEventListener('click', () => {
        sound.initContext();
      }, { once: true });
    });
  </script>
</body>
</html>