套件原始碼 · 數學小森林

CC BY 4.0 · 請標明 @SpringFairy · v1

← 遊戲詳情

拿走、改作、拿來教

下方程式碼只是套件可遊玩的入口 HTML。請下載完整 ZIP,才能連同所有相對素材一起編輯或改作。

⬇ 下載入口 HTML ⬇ 下載完整套件
AI 編輯提示起頭
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 · 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>