0. 作品介紹

讓學生可以體驗棒球記分板賽程抽卡


27 0 0
班級經營
封面圖 封面

1. AI 提示詞

把這段指令貼給 AI,讓它幫你生成程式碼。

1.我想要幫學生做一個球員卡

2.幫我用Google 試算表建立

3.我希望有些資料我可以直接在試算表更改首頁可以直接呈現

4.需求說明(姓名、傳球、接球、守備、行動力、決策力)(可以放學生照片)

5.要把它變成程式碼我要發佈

6.請完整敘述操作步驟 越詳細越好

7.作業環境

8.Google試算表

9.Google Apps Script

10.我看不懂程式碼,請給我後端gs完整程式碼、前端index完整程式碼

11.補充:我有使用雲端硬碟回收照片或PDF或文件

12.背景我希望要有未來感、科技感

13.雷達圖最高分數10分。

2. 資料表規則

請照下面規則建立分頁與欄位。

第1分頁: 分頁1 A 欄: 隊伍 B 欄: 姓名 C 欄: 守備位置 D 欄: 個人口號 E 欄: 照片ID F 欄: 傳球 G 欄: 接球 H 欄: 守備 I 欄: 行動 J 欄: 決策 K 欄: 隊伍LOGO L 欄: 合照

📄 解析結果(自動表格)

分頁:分頁1
A 欄 B 欄 C 欄 D 欄 E 欄 F 欄 G 欄 H 欄 I 欄 J 欄 K 欄 L 欄
隊伍 姓名 守備位置 個人口號 照片ID 傳球 接球 守備 行動 決策 隊伍LOGO 合照

3. 開啟 Apps Script

從試算表開 Apps Script,才能直接用 SpreadsheetApp 讀寫資料。

  1. 試算表上方選單:擴充功能 → Apps Script
  2. 刪掉預設的程式碼(或全部選取覆蓋)
  3. 保留/建立檔案:
    • Code.gs
    • index.html

4. 貼上後端(Code.gs)

把後端程式貼到 Apps Script 的 Code.gs。

Code.gs(後端)
function doGet(e) {
  return HtmlService.createHtmlOutputFromFile('index')
    .setTitle('第一屆枋寮世界盃 - 球員卡系統')
    .addMetaTag('viewport', 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no')
    .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}

function getPlayerData() {
  try {
    const ss = SpreadsheetApp.getActiveSpreadsheet();
    const sheet = ss.getSheetByName('球員資料') || ss.getSheets()[0];
    const data = sheet.getDataRange().getValues();
    const players = [];
    const parseScore = (val) => {
      let n = parseFloat(val);
      return isNaN(n) ? 0 : Math.min(10, Math.max(0, n));
    };
    for (let i = 1; i < data.length; i++) {
      let row = data[i];
      if (!row[1]) continue;
      const scores = [parseScore(row[5]), parseScore(row[6]), parseScore(row[7]), parseScore(row[8]), parseScore(row[9])];
      const total = scores.reduce((a, b) => a + b, 0);
      let rank, tier;
      if (total >= 40)      { rank = 'S'; tier = 'BLACK_GOLD'; }
      else if (total >= 30) { rank = 'A'; tier = 'PLATINUM'; }
      else if (total >= 20) { rank = 'B'; tier = 'GOLD_CARD'; }
      else                  { rank = 'C'; tier = 'NORMAL'; }
      players.push({
        team:      (row[0] || '未知').toString(),
        name:      row[1].toString(),
        position:  (row[2] || '選手').toString(),
        slogan:    (row[3] || '').toString(),
        photo:     formatDriveUrl(row[4], 'player'),
        pass:      scores[0],
        catch:     scores[1],
        field:     scores[2],
        mobility:  scores[3],
        decision:  scores[4],
        teamLogo:  formatDriveUrl(row[10], 'logo'),
        teamPhoto: formatDriveUrl(row[11], 'team'),
        rank:      rank,
        tier:      tier
      });
    }
    return players;
  } catch (e) {
    return { error: e.message };
  }
}

function formatDriveUrl(url, type) {
  const defaults = {
    logo:   'https://via.placeholder.com/200x200?text=LOGO',
    player: 'https://via.placeholder.com/400x500?text=NO+PHOTO',
    team:   'https://via.placeholder.com/800x400?text=TEAM+PHOTO'
  };
  const defaultImg = defaults[type] || defaults.player;

  if (!url || typeof url !== 'string' || !url.includes('drive.google.com')) return defaultImg;
  const match = url.match(/[-\w]{25,}/);
  return match ? 'https://drive.google.com/thumbnail?id=' + match[0] + '&sz=w1000' : defaultImg;
}
貼到 Apps Script 的 Code.gs 檔案

5. 貼上前端(index.html)

把前端程式貼到 Apps Script 的 index.html。

index.html(前端)
<!DOCTYPE html>
<html>
<head>
  <base target="_top">
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;700;900&family=Orbitron:wght@500;900&display=swap');
    
    :root {
      --neon-blue: #00f3ff;
      --neon-purple: #bc13fe;
      --dark-bg: #03060e;
    }
    
    body, html { height: 100vh; width: 100vw; margin: 0; padding: 0; background: var(--dark-bg); color: #fff; font-family: 'Noto Sans TC', sans-serif; overflow: hidden; }
    .bg-overlay { position: fixed; inset: 0; z-index: -1; background: radial-gradient(circle at center, rgba(3,6,14,0.7) 0%, #03060e 100%); }

    .layer { display: none; height: 100vh; width: 100vw; padding: 20px; box-sizing: border-box; position: absolute; flex-direction: column; align-items: center; }
    .layer.active { display: flex; }

    .scroll-content { width: 100%; max-width: 1200px; flex: 1; overflow-y: auto; padding: 10px 0 20px 0; }
    .grid-box { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }

    #particles-canvas { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; pointer-events: none; }

    @keyframes scan { 0% { top: -10%; } 100% { top: 110%; } }
    .scan-line { position: fixed; left: 0; width: 100%; height: 2px; background: linear-gradient(90deg, transparent, var(--neon-blue), transparent); opacity: 0.25; z-index: 1; pointer-events: none; animation: scan 6s linear infinite; }

    @keyframes flicker { 0%,100% { opacity: 1; } 92% { opacity: 1; } 93% { opacity: 0.4; } 95% { opacity: 1; } 97% { opacity: 0.6; } 98% { opacity: 1; } }
    @keyframes title-glow {
      0%,100% { text-shadow: 0 0 20px var(--neon-blue), 0 0 40px var(--neon-blue); }
      50%      { text-shadow: 0 0 40px var(--neon-blue), 0 0 80px var(--neon-blue), 0 0 120px var(--neon-blue); }
    }
    .layer1-title { font-family: 'Orbitron'; font-size: clamp(2.5rem,8vh,6.5rem); text-align: center; margin-top: 0; animation: flicker 5s infinite, title-glow 3s ease-in-out infinite; position: relative; z-index: 2; }

    @keyframes typing { from { width: 0; } to { width: 26ch; } }
    @keyframes blink { 50% { border-color: transparent; } }
    @keyframes hide-cursor { to { border-color: transparent; } }
    .layer1-sub {
      font-family: 'Orbitron'; font-size: 0.85rem; letter-spacing: 6px;
      color: var(--neon-blue); opacity: 0.7; margin-top: 12px;
      overflow: hidden; white-space: nowrap; width: 0;
      border-right: 2px solid var(--neon-blue);
      animation: typing 2.5s steps(30) 0.5s forwards, blink 0.8s step-end 0.5s 4, hide-cursor 0s 4s forwards;
      position: relative; z-index: 2;
    }

    @keyframes pulse-border {
      0%,100% { box-shadow: 0 0 15px var(--neon-blue), 0 0 30px rgba(0,243,255,0.3); }
      50%      { box-shadow: 0 0 30px var(--neon-blue), 0 0 60px rgba(0,243,255,0.5), 0 0 90px rgba(0,243,255,0.2); }
    }
    .enter-btn {
      margin-top: 5vh; padding: 18px 60px; border: 2px solid var(--neon-blue);
      background: rgba(0,243,255,0.05); color: var(--neon-blue); border-radius: 50px; cursor: pointer;
      font-family: 'Orbitron'; font-size: 1.6rem; font-weight: bold;
      animation: pulse-border 2s ease-in-out infinite;
      transition: background 0.3s, transform 0.2s; position: relative; z-index: 2;
    }
    .enter-btn:hover { background: rgba(0,243,255,0.15); transform: scale(1.05); }

    @keyframes data-scroll { 0% { transform: translateX(100%); } 100% { transform: translateX(-100%); } }
    .status-bar { position: fixed; bottom: 0; left: 0; right: 0; height: 28px; background: rgba(0,243,255,0.05); border-top: 1px solid rgba(0,243,255,0.2); overflow: hidden; z-index: 2; display: flex; align-items: center; }
    .status-text { white-space: nowrap; font-family: 'Orbitron'; font-size: 0.7rem; color: var(--neon-blue); opacity: 0.6; letter-spacing: 3px; animation: data-scroll 18s linear infinite; }

    @keyframes gold-flow { 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } }

    .card {
      position: relative; border-radius: 15px; padding: 3px;
      background: linear-gradient(135deg, #b8860b, #ffd700, #fffacd, #d4af37, #b8860b);
      background-size: 300% 300%; cursor: pointer; transition: transform 0.3s, box-shadow 0.3s;
      box-shadow: 0 4px 20px rgba(212,175,55,0.3);
    }
    .card:hover { transform: translateY(-6px); animation: gold-flow 2s ease infinite; box-shadow: 0 8px 40px rgba(255,215,0,0.7); }
    .card-inner { background: #0d0d0d; border-radius: 13px; padding: 15px; text-align: center; }
    .card-inner img { width: 100%; height: 220px; object-fit: contain; border-radius: 8px; }

    .player-card {
      background: #111; border: 3px solid #f5c400; border-radius: 16px;
      cursor: pointer; transition: transform 0.3s, box-shadow 0.3s;
      overflow: hidden; display: flex; flex-direction: column;
      box-shadow: 0 0 18px rgba(245,196,0,0.3);
    }
    .player-card:hover { transform: translateY(-6px); box-shadow: 0 0 30px rgba(245,196,0,0.6); }

    .player-badges { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 8px 8px; min-height: 0; }
    .badge-tag { font-size: 0.6rem; font-weight: 900; letter-spacing: 1px; padding: 3px 7px; border-radius: 20px; border: 1px solid currentColor; white-space: nowrap; animation: badge-glow 2s ease-in-out infinite; }
    @keyframes badge-glow { 0%,100% { box-shadow: 0 0 4px currentColor; } 50% { box-shadow: 0 0 10px currentColor, 0 0 20px currentColor; } }
    .badge-pass     { color: #00e5ff; background: rgba(0,229,255,0.1); }
    .badge-catch    { color: #69ff47; background: rgba(105,255,71,0.1); }
    .badge-field    { color: #ff9100; background: rgba(255,145,0,0.1); }
    .badge-mobility { color: #ffeb3b; background: rgba(255,235,59,0.1); }
    .badge-decision { color: #e040fb; background: rgba(224,64,251,0.1); }
    .badge-allstar  { color: #ffd700; background: rgba(255,215,0,0.15); }

    .player-card-badge { background: #f5c400; color: #000; font-weight: 900; font-size: 0.9rem; padding: 8px 12px; text-align: center; font-family: 'Noto Sans TC', sans-serif; letter-spacing: 1px; flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .player-card-photo { width: 100%; height: 280px; object-fit: cover; object-position: top center; display: block; flex-shrink: 0; background: #1a1a2e; }
    .player-card-no-photo { width: 100%; height: 280px; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #0d0d1a, #1a1a2e); flex-shrink: 0; gap: 8px; }
    .player-card-no-photo .no-photo-icon { font-size: 3.5rem; opacity: 0.25; }
    .player-card-no-photo .no-photo-label { font-family: 'Orbitron'; font-size: 0.6rem; letter-spacing: 3px; color: rgba(0,243,255,0.25); }

    .search-wrap { width: 100%; max-width: 1200px; flex-shrink: 0; margin: 0 0 12px 0; position: relative; }
    .search-wrap input { width: 100%; box-sizing: border-box; background: rgba(0,243,255,0.05); border: 1.5px solid rgba(0,243,255,0.4); border-radius: 50px; padding: 10px 20px 10px 44px; color: #fff; font-family: 'Orbitron', sans-serif; font-size: 0.95rem; outline: none; transition: border-color 0.3s, box-shadow 0.3s; }
    .search-wrap input::placeholder { color: rgba(0,243,255,0.4); }
    .search-wrap input:focus { border-color: var(--neon-blue); box-shadow: 0 0 12px rgba(0,243,255,0.3); }
    .search-icon { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: rgba(0,243,255,0.5); font-size: 1rem; pointer-events: none; }
    .search-empty { text-align: center; font-family: 'Orbitron'; color: rgba(0,243,255,0.4); font-size: 0.9rem; letter-spacing: 3px; margin-top: 40px; }

    .team-overview { width: 100%; max-width: 1200px; flex-shrink: 0; background: rgba(0,243,255,0.04); border: 1px solid rgba(0,243,255,0.2); border-radius: 14px; padding: 14px 20px; margin-bottom: 12px; box-sizing: border-box; }
    .team-overview-title { font-family: 'Orbitron'; font-size: 0.75rem; letter-spacing: 4px; color: var(--neon-blue); opacity: 0.7; margin-bottom: 10px; }
    .overview-bars { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
    .bar-item { display: flex; flex-direction: column; gap: 5px; }
    .bar-label { font-family: 'Orbitron'; font-size: 0.65rem; letter-spacing: 1px; color: rgba(0,243,255,0.7); text-align: center; }
    .bar-track { background: rgba(255,255,255,0.08); border-radius: 50px; height: 8px; overflow: hidden; }
    @keyframes bar-fill { from { width: 0%; } to { width: var(--bar-w); } }
    .bar-fill { height: 100%; border-radius: 50px; background: linear-gradient(90deg, var(--neon-blue), var(--neon-purple)); width: 0%; animation: bar-fill 1s ease forwards; box-shadow: 0 0 6px var(--neon-blue); }
    .bar-val { font-family: 'Orbitron'; font-size: 0.7rem; color: #fff; text-align: center; opacity: 0.85; }

    .topps-card { position: relative; width: 420px; flex-shrink: 0; border-radius: 18px; overflow: hidden; box-shadow: 0 30px 80px rgba(0,0,0,0.9); border: 3px solid rgba(255,255,255,0.15); }
    .topps-BLACK_GOLD { border-color: #d4af37; box-shadow: 0 30px 80px rgba(212,175,55,0.4); }
    .topps-PLATINUM   { border-color: #e0e0e0; box-shadow: 0 30px 80px rgba(220,220,220,0.4); }
    .topps-GOLD_CARD  { border-color: #ffd700; box-shadow: 0 30px 80px rgba(255,215,0,0.4); }
    .topps-NORMAL     { border-color: var(--neon-blue); box-shadow: 0 30px 80px rgba(0,243,255,0.3); }

    .topps-photo { width: 100%; aspect-ratio: 3/4; object-fit: cover; object-position: top; display: block; }
    .topps-info { position: absolute; bottom: 0; left: 0; right: 0; padding: 60px 20px 20px; background: linear-gradient(to top, rgba(0,0,0,0.95) 60%, transparent 100%); }
    .topps-name-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
    .topps-name { font-family: 'Orbitron', 'Noto Sans TC', sans-serif; font-size: clamp(1.4rem,3vw,2rem); font-weight: 900; color: #fff; text-shadow: 0 2px 8px rgba(0,0,0,0.8); letter-spacing: 1px; }
    .topps-rank-badge { font-family: 'Orbitron'; font-size: 2.2rem; font-weight: 900; padding: 4px 14px; border-radius: 8px; line-height: 1; }
    .rank-BLACK_GOLD { background: #d4af37; color: #000; }
    .rank-PLATINUM   { background: #e0e0e0; color: #000; }
    .rank-GOLD_CARD  { background: #ffd700; color: #000; }
    .rank-NORMAL     { background: var(--neon-blue); color: #000; }

    .topps-divider { height: 3px; border-radius: 2px; margin: 8px 0; }
    .div-BLACK_GOLD { background: linear-gradient(90deg, #d4af37, transparent); }
    .div-PLATINUM   { background: linear-gradient(90deg, #e0e0e0, transparent); }
    .div-GOLD_CARD  { background: linear-gradient(90deg, #ffd700, transparent); }
    .div-NORMAL     { background: linear-gradient(90deg, var(--neon-blue), transparent); }

    .topps-pos { font-size: 1rem; font-weight: 900; letter-spacing: 2px; text-transform: uppercase; opacity: 0.85; margin-bottom: 4px; }
    .topps-slogan { font-size: 0.9rem; font-style: italic; opacity: 0.7; margin-top: 4px; }

    .topps-radar-panel { width: 360px; flex-shrink: 0; display: flex; flex-direction: column; justify-content: center; padding: 10px 0; }
    .radar-title { font-family: 'Orbitron'; font-size: 1.3rem; letter-spacing: 5px; font-weight: 900; opacity: 1; text-align: center; margin-bottom: 16px; color: var(--neon-blue); text-shadow: 0 0 12px var(--neon-blue); }
    #radarChart-wrap { width: 100%; height: 360px; position: relative; }

    #layer4 { justify-content: flex-start; }
    .topps-layout { width: 100%; flex: 1; overflow-y: auto; display: flex; justify-content: center; align-items: center; gap: 40px; padding: 0 30px 30px; box-sizing: border-box; flex-wrap: wrap; }

    .btn-back { flex-shrink: 0; align-self: flex-start; background: transparent; color: var(--neon-purple); border: 2px solid var(--neon-purple); padding: 10px 30px; border-radius: 50px; cursor: pointer; margin-bottom: 12px; font-family: 'Orbitron'; font-weight: bold; transition: 0.3s; }
    .btn-back:hover { background: var(--neon-purple); color: #fff; }

    #loading { position: fixed; inset: 0; background: #000; z-index: 9999; display: flex; align-items: center; justify-content: center; font-family: 'Orbitron'; color: var(--neon-blue); font-size: 1.5rem; letter-spacing: 5px; }
    #error-msg { display: none; position: fixed; inset: 0; background: #000; z-index: 9999; flex-direction: column; align-items: center; justify-content: center; font-family: 'Orbitron'; color: #ff4444; font-size: 1.2rem; text-align: center; padding: 20px; gap: 20px; }
    #error-msg button { padding: 12px 40px; border: 2px solid #ff4444; background: transparent; color: #ff4444; border-radius: 50px; cursor: pointer; font-family: 'Orbitron'; font-size: 1rem; }
    #error-msg button:hover { background: #ff4444; color: #fff; }

    .live-ticker { position: fixed; bottom: 28px; left: 0; right: 0; height: 32px; background: rgba(188,19,254,0.08); border-top: 1px solid rgba(188,19,254,0.3); border-bottom: 1px solid rgba(188,19,254,0.3); overflow: hidden; z-index: 3; display: none; align-items: center; }
    .live-ticker.active { display: flex; }
    .ticker-label { flex-shrink: 0; background: var(--neon-purple); color: #fff; font-family: 'Orbitron'; font-size: 0.65rem; font-weight: 900; letter-spacing: 2px; padding: 0 12px; height: 100%; display: flex; align-items: center; }
    @keyframes ticker-scroll { 0% { transform: translateX(100vw); } 100% { transform: translateX(-100%); } }
    .ticker-text { white-space: nowrap; font-family: 'Orbitron'; font-size: 0.7rem; color: #e0a0ff; letter-spacing: 2px; animation: ticker-scroll 28s linear infinite; }

    .vs-btn { position: fixed; bottom: 68px; right: 24px; z-index: 10; background: linear-gradient(135deg, var(--neon-purple), #6600cc); border: none; color: #fff; font-family: 'Orbitron'; font-weight: 900; font-size: 0.85rem; letter-spacing: 2px; padding: 12px 20px; border-radius: 50px; cursor: pointer; box-shadow: 0 0 20px rgba(188,19,254,0.6); transition: transform 0.2s, box-shadow 0.2s; display: none; }
    .vs-btn.active { display: block; }
    .vs-btn:hover { transform: scale(1.08); box-shadow: 0 0 35px rgba(188,19,254,0.9); }

    .player-card.vs-selected { border-color: var(--neon-purple) !important; box-shadow: 0 0 25px rgba(188,19,254,0.8) !important; }
    .player-card.vs-selected .player-card-badge { background: var(--neon-purple); color: #fff; }

    .vs-overlay { position: fixed; inset: 0; z-index: 2000; display: none; align-items: center; justify-content: center; background: #000; overflow: hidden; }
    .vs-overlay.active { display: flex; }
    .vs-stage { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
    .vs-card-wrap { position: absolute; display: flex; flex-direction: column; align-items: center; gap: 10px; z-index: 5; }
    .vs-card-wrap.card-a { left: -260px; }
    .vs-card-wrap.card-b { right: -260px; }
    @keyframes slide-in-a { 0% { left: -260px; } 60% { left: calc(50% - 160px); } 75% { left: calc(50% - 148px); } 100% { left: calc(50% - 155px); } }
    @keyframes slide-in-b { 0% { right: -260px; } 60% { right: calc(50% - 160px); } 75% { right: calc(50% - 148px); } 100% { right: calc(50% - 155px); } }
    .vs-card-wrap.card-a.sliding { animation: slide-in-a 0.9s cubic-bezier(0.22,1,0.36,1) forwards; }
    .vs-card-wrap.card-b.sliding { animation: slide-in-b 0.9s cubic-bezier(0.22,1,0.36,1) forwards; }
    .vs-card { width: 130px; height: 172px; border-radius: 12px; overflow: hidden; position: relative; border: 3px solid #ffd700; box-shadow: 0 0 30px rgba(255,215,0,0.5); }
    .vs-card img { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }
    .vs-card-name { font-family: 'Noto Sans TC'; font-weight: 900; font-size: 0.95rem; color: #fff; text-shadow: 0 0 8px rgba(255,255,255,0.5); text-align: center; }
    .vs-card-rank { font-family: 'Orbitron'; font-size: 0.7rem; letter-spacing: 2px; color: rgba(255,255,255,0.5); text-align: center; }
    .vs-explosion { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 0; height: 0; pointer-events: none; z-index: 10; }
    .vs-spark { position: absolute; width: 6px; height: 6px; border-radius: 50%; transform-origin: center; }
    @keyframes spark-fly { 0% { transform: translate(-50%,-50%) rotate(var(--angle)) translateX(0) scale(1); opacity: 1; } 100% { transform: translate(-50%,-50%) rotate(var(--angle)) translateX(var(--dist)) scale(0); opacity: 0; } }
    .vs-flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; z-index: 8; }
    @keyframes flash-bang { 0% { opacity: 0; } 10% { opacity: 1; } 100% { opacity: 0; } }
    .vs-flash.bang { animation: flash-bang 0.5s ease forwards; }
    @keyframes vs-shake { 0%,100% { transform: translate(0,0); } 20% { transform: translate(-8px, 4px); } 40% { transform: translate(8px, -4px); } 60% { transform: translate(-6px, 6px); } 80% { transform: translate(6px, -2px); } }
    .vs-stage.shaking { animation: vs-shake 0.4s ease; }
    .vs-result { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; opacity: 0; pointer-events: none; z-index: 20; background: rgba(0,0,0,0.88); }
    @keyframes result-in { from { opacity: 0; transform: scale(0.85); } to { opacity: 1; transform: scale(1); } }
    .vs-result.show { opacity: 1; pointer-events: all; animation: result-in 0.5s cubic-bezier(0.22,1,0.36,1) forwards; }
    .vs-result-title { font-family: 'Orbitron'; font-size: 0.8rem; letter-spacing: 6px; color: var(--neon-purple); opacity: 0.8; }
    .vs-result-fighters { display: flex; align-items: flex-end; justify-content: center; gap: 16px; flex-wrap: wrap; }
    .vs-result-fighter { display: flex; flex-direction: column; align-items: center; gap: 8px; min-width: 110px; max-width: 150px; }
    .vs-result-fighter img { width: 100px; height: 132px; object-fit: cover; object-position: top; border-radius: 12px; border: 3px solid #555; }
    .vs-result-fighter.winner img { border-color: #ffd700; box-shadow: 0 0 30px rgba(255,215,0,0.8); }
    .vs-result-fighter.loser img  { filter: grayscale(60%); opacity: 0.6; }
    .vs-result-fighter-name { font-family: 'Noto Sans TC'; font-weight: 900; font-size: 1rem; }
    .vs-result-fighter.winner .vs-result-fighter-name { color: #ffd700; text-shadow: 0 0 12px #ffd700; }
    .vs-result-fighter.loser  .vs-result-fighter-name { color: rgba(255,255,255,0.4); }
    .vs-winner-pct { font-family: 'Orbitron'; font-size: 2.6rem; font-weight: 900; color: #ffd700; text-shadow: 0 0 24px #ffd700; }
    .vs-loser-pct  { font-family: 'Orbitron'; font-size: 1.6rem; font-weight: 900; color: rgba(255,255,255,0.35); }
    .vs-result-crown { font-size: 1.8rem; }
    .vs-vs-mid { font-family: 'Orbitron'; font-size: 1.6rem; font-weight: 900; color: var(--neon-purple); text-shadow: 0 0 16px var(--neon-purple); align-self: center; }
    .vs-bar-wrap { width: min(380px, 90vw); }
    .vs-bar-track { background: rgba(255,255,255,0.08); border-radius: 50px; height: 12px; overflow: hidden; }
    @keyframes vs-fill-a { from { width: 0%; } to { width: var(--pct-a); } }
    @keyframes vs-fill-b { from { width: 0%; } to { width: var(--pct-b); } }
    .vs-bar-a { height: 100%; border-radius: 50px 0 0 50px; background: linear-gradient(90deg, #ffd700, #ffaa00); animation: vs-fill-a 1s ease forwards; width: 0%; float: left; }
    .vs-bar-b { height: 100%; border-radius: 0 50px 50px 0; background: linear-gradient(90deg, #bc13fe, #6600cc); animation: vs-fill-b 1s ease forwards; width: 0%; float: right; }
    .vs-detail { display: grid; grid-template-columns: 1fr auto 1fr; gap: 5px 12px; font-size: 0.85rem; width: min(340px,90vw); }
    .vs-detail-a { text-align: right; color: #ffd700; font-weight: 700; }
    .vs-detail-label { text-align: center; color: rgba(255,255,255,0.4); font-family: 'Orbitron'; font-size: 0.65rem; letter-spacing: 1px; }
    .vs-detail-b { text-align: left; color: #e0a0ff; font-weight: 700; }
    .vs-close { background: transparent; border: 2px solid var(--neon-purple); color: var(--neon-purple); font-family: 'Orbitron'; font-size: 0.9rem; padding: 10px 40px; border-radius: 50px; cursor: pointer; transition: 0.2s; }
    .vs-close:hover { background: var(--neon-purple); color: #fff; }
    .vs-hint { position: fixed; bottom: 110px; right: 24px; font-family: 'Orbitron'; font-size: 0.65rem; letter-spacing: 2px; color: rgba(188,19,254,0.7); display: none; z-index: 10; }
    .vs-hint.active { display: block; }

    .hall-section { margin-bottom: 32px; }
    .hall-section-title { font-family: 'Orbitron'; font-size: 1rem; letter-spacing: 4px; font-weight: 900; margin-bottom: 16px; padding-bottom: 8px; border-bottom: 2px solid rgba(255,255,255,0.1); display: flex; align-items: center; gap: 10px; }
    .hall-podium { display: flex; align-items: flex-end; justify-content: center; gap: 16px; }
    .hall-player { display: flex; flex-direction: column; align-items: center; gap: 8px; cursor: pointer; transition: transform 0.25s; }
    .hall-player:hover { transform: translateY(-6px); }
    .hall-player-photo { object-fit: cover; object-position: top center; border-radius: 12px; display: block; border: 3px solid transparent; }
    .hall-rank-1 .hall-player-photo { width: 110px; height: 145px; border-color: #ffd700; box-shadow: 0 0 20px rgba(255,215,0,0.6); }
    .hall-rank-2 .hall-player-photo { width: 88px; height: 116px; border-color: #c0c0c0; box-shadow: 0 0 14px rgba(192,192,192,0.4); }
    .hall-rank-3 .hall-player-photo { width: 76px; height: 100px; border-color: #cd7f32; box-shadow: 0 0 10px rgba(205,127,50,0.4); }
    .hall-medal { font-size: 1.6rem; line-height: 1; }
    .hall-rank-2 .hall-medal, .hall-rank-3 .hall-medal { font-size: 1.2rem; }
    .hall-player-name { font-family: 'Noto Sans TC'; font-weight: 900; font-size: 0.85rem; text-align: center; }
    .hall-player-team { font-size: 0.7rem; opacity: 0.5; font-family: 'Orbitron'; letter-spacing: 1px; }
    .hall-player-score { font-family: 'Orbitron'; font-weight: 900; font-size: 1.1rem; padding: 2px 12px; border-radius: 20px; background: rgba(255,255,255,0.08); }
    .hall-rank-1 .hall-player-score { color: #ffd700; }
    .hall-rank-2 .hall-player-score { color: #c0c0c0; }
    .hall-rank-3 .hall-player-score { color: #cd7f32; }

    .pack-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.95); z-index: 2000; display: none; align-items: center; justify-content: center; flex-direction: column; gap: 20px; }
    .pack-overlay.active { display: flex; }
    @keyframes pack-shake { 0%,100% { transform: rotate(0deg) scale(1); } 10% { transform: rotate(-4deg) scale(1.05); } 20% { transform: rotate(4deg) scale(1.08); } 30% { transform: rotate(-6deg) scale(1.1); } 40% { transform: rotate(6deg) scale(1.12); } 50% { transform: rotate(-4deg) scale(1.1); } 60% { transform: rotate(4deg) scale(1.05); } 70% { transform: rotate(-2deg) scale(1); } }
    @keyframes pack-explode { 0% { transform: scale(1); opacity: 1; } 100% { transform: scale(3); opacity: 0; } }
    .pack-card-scene { perspective: 900px; width: 280px; height: 370px; }
    .pack-card-flip { width: 100%; height: 100%; position: relative; transform-style: preserve-3d; transition: transform 0.75s cubic-bezier(0.4,0,0.2,1); }
    .pack-card-flip.flipped { transform: rotateY(180deg); }
    .pack-card-back, .pack-card-face { position: absolute; inset: 0; border-radius: 16px; overflow: hidden; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
    .pack-card-face { transform: rotateY(180deg); }
    .pack-card-back svg { width: 100%; height: 100%; display: block; }
    @keyframes card-reveal { 0% { opacity: 0; transform: scale(0.9); } 100% { opacity: 1; transform: scale(1); } }
    .pack-icon { font-size: 6rem; cursor: pointer; user-select: none; filter: drop-shadow(0 0 30px #ffd700); }
    .pack-icon.shaking { animation: pack-shake 0.8s ease forwards; }
    .pack-icon.exploding { animation: pack-explode 0.4s ease forwards; }
    .pack-hint { font-family: 'Orbitron'; font-size: 0.85rem; letter-spacing: 4px; color: rgba(255,215,0,0.7); }
    .pack-card-wrap { display: none; flex-direction: column; align-items: center; gap: 16px; animation: card-reveal 0.4s ease forwards; }
    .pack-card-wrap.show { display: flex; }
    @keyframes holo-sweep { 0% { background-position: -200% center; } 100% { background-position: 200% center; } }
    .pack-holo-overlay { position: absolute; inset: 0; border-radius: 16px; pointer-events: none; background-size: 200% 100%; animation: holo-sweep 2s ease-in-out infinite; }
    .pack-card-img { width: 280px; height: 370px; object-fit: cover; object-position: top; border-radius: 16px; border: 3px solid #ffd700; box-shadow: 0 0 40px rgba(255,215,0,0.6); display: block; }
    .pack-card-name { font-family: 'Orbitron'; font-size: 1.2rem; font-weight: 900; color: #ffd700; text-shadow: 0 0 12px #ffd700; }
    .pack-card-info { font-size: 0.85rem; opacity: 0.7; letter-spacing: 2px; }
    .pack-again-btn { padding: 12px 40px; background: linear-gradient(135deg, #d4af37, #ffd700); color: #000; border: none; border-radius: 50px; cursor: pointer; font-family: 'Orbitron'; font-weight: 900; font-size: 0.9rem; transition: 0.2s; }
    .pack-again-btn:hover { transform: scale(1.06); }
    .pack-close-btn { padding: 10px 30px; background: transparent; color: rgba(255,255,255,0.5); border: 1px solid rgba(255,255,255,0.2); border-radius: 50px; cursor: pointer; font-family: 'Orbitron'; font-size: 0.8rem; transition: 0.2s; }
    .pack-close-btn:hover { color: #fff; border-color: #fff; }

    /* ══ 戰術板 ══ */
    #layer6 { justify-content: flex-start; overflow: hidden; }
    .tactical-container { width: 100%; max-width: 1400px; flex: 1; display: flex; gap: 12px; overflow: hidden; box-sizing: border-box; padding-bottom: 10px; }
    .tactical-sidebar { width: 190px; flex-shrink: 0; display: flex; flex-direction: column; gap: 8px; overflow-y: auto; }
    .tactical-sidebar-section { background: rgba(255,180,0,0.04); border: 1px solid rgba(255,180,0,0.25); border-radius: 10px; padding: 10px; }
    .tactical-sidebar-title { font-family: 'Orbitron'; font-size: 0.6rem; letter-spacing: 3px; color: #ffb300; opacity: 0.9; margin-bottom: 8px; }
    .tac-tool-btn { width: 100%; padding: 7px 10px; margin-bottom: 5px; border-radius: 8px; background: rgba(255,180,0,0.06); border: 1px solid rgba(255,180,0,0.25); color: #fff; font-family: 'Noto Sans TC'; font-size: 0.75rem; cursor: pointer; transition: all 0.2s; text-align: left; }
    .tac-tool-btn:hover, .tac-tool-btn.active { background: rgba(255,180,0,0.22); border-color: #ffb300; box-shadow: 0 0 10px rgba(255,180,0,0.35); }
    .tac-color-row { display: flex; gap: 6px; flex-wrap: wrap; }
    .tac-color-btn { width: 26px; height: 26px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; transition: all 0.2s; }
    .tac-color-btn.active { border-color: #fff; transform: scale(1.25); box-shadow: 0 0 8px currentColor; }
    .tac-size-row { display: flex; gap: 6px; }
    .tac-size-btn { flex: 1; padding: 5px 0; border-radius: 20px; cursor: pointer; border: 1px solid rgba(255,180,0,0.3); background: rgba(255,180,0,0.06); color: #fff; font-family: 'Orbitron'; font-size: 0.6rem; transition: 0.2s; text-align:center; }
    .tac-size-btn.active { background: rgba(255,180,0,0.25); border-color: #ffb300; }
    .tac-shape-btn { width: 100%; padding: 7px 10px; margin-bottom: 5px; border-radius: 8px; background: rgba(0,180,80,0.06); border: 1px solid rgba(0,180,80,0.3); color: #fff; font-family: 'Noto Sans TC'; font-size: 0.75rem; cursor: pointer; transition: all 0.2s; text-align: left; }
    .tac-shape-btn:hover, .tac-shape-btn.active { background: rgba(0,220,100,0.2); border-color: #00e064; }
    .tactical-board-wrap { flex: 1; position: relative; border-radius: 14px; overflow: hidden; border: 2px solid rgba(255,180,0,0.4); box-shadow: 0 0 30px rgba(255,180,0,0.12); }
    #tactical-canvas { display: block; width: 100%; height: 100%; cursor: crosshair; }
    .tac-action-btn { padding: 8px 14px; border-radius: 8px; cursor: pointer; font-family: 'Orbitron'; font-size: 0.62rem; letter-spacing: 1px; transition: 0.2s; border: 1px solid; }

    /* ══ 計分板 ══ */
    /* ★ 關鍵修改:layer7 改為 overflow-y: auto,讓整頁可滾動 */
    #layer7 { justify-content: flex-start; overflow-y: auto; }

    .bb-wrap {
      width: 100%; max-width: 1200px;
      display: grid;
      grid-template-columns: 280px 1fr 260px;
      gap: 10px;
      box-sizing: border-box;
      padding-bottom: 30px;
    }
    .bb-top-card {
      grid-column: 1 / 4;
      background: linear-gradient(135deg,rgba(255,60,120,0.06),rgba(0,243,255,0.04));
      border: 2px solid rgba(255,60,120,0.3); border-radius: 16px; padding: 12px 20px;
    }
    .bb-inning-bar { display:flex; align-items:center; justify-content:center; gap:7px; margin-bottom:10px; flex-wrap:wrap; }
    .bb-inning-label { font-family:'Orbitron'; font-size:0.62rem; letter-spacing:2px; color:rgba(255,255,255,0.35); }
    .bb-inning-btn { padding:4px 12px; border-radius:20px; border:1px solid rgba(255,60,120,0.3); background:rgba(255,60,120,0.04); color:rgba(255,255,255,0.4); font-family:'Orbitron'; font-size:0.62rem; cursor:pointer; transition:0.2s; }
    .bb-inning-btn.active { background:rgba(255,60,120,0.2); border-color:#ff3c78; color:#ff3c78; box-shadow:0 0 8px rgba(255,60,120,0.4); }
    .bb-score-row { display:grid; grid-template-columns:1fr auto 1fr; gap:10px; align-items:center; }
    .bb-team-block { display:flex; flex-direction:column; align-items:center; gap:6px; }
    .bb-team-name-input { background:transparent; border:none; border-bottom:2px solid rgba(255,60,120,0.3); color:#fff; font-family:'Orbitron'; font-size:0.9rem; font-weight:900; text-align:center; letter-spacing:3px; outline:none; width:100%; padding:3px 0; transition:0.2s; }
    .bb-team-name-input:focus { border-color:#ff3c78; }
    .bb-big-score { font-family:'Orbitron'; font-size:clamp(2.5rem,4.5vw,4rem); font-weight:900; line-height:1; text-align:center; min-width:100px; }
    .bb-team-home .bb-big-score { color:#00f3ff; text-shadow:0 0 25px #00f3ff; }
    .bb-team-away .bb-big-score { color:#ffb300; text-shadow:0 0 25px #ffb300; }
    .bb-manual-btns { display:flex; gap:5px; }
    .bb-manual-btn { padding:5px 10px; border-radius:50px; border:1.5px solid; background:transparent; color:#fff; font-family:'Orbitron'; font-weight:900; font-size:0.72rem; cursor:pointer; transition:0.2s; }
    .bb-team-home .bb-manual-btn { border-color:rgba(0,243,255,0.4); }
    .bb-team-home .bb-manual-btn:hover { background:rgba(0,243,255,0.15); }
    .bb-team-away .bb-manual-btn { border-color:rgba(255,180,0,0.4); }
    .bb-team-away .bb-manual-btn:hover { background:rgba(255,180,0,0.15); }
    .bb-vs-col { text-align:center; display:flex; flex-direction:column; align-items:center; gap:7px; }
    .bb-vs-text { font-family:'Orbitron'; font-size:1.1rem; font-weight:900; color:rgba(255,255,255,0.08); }
    .bb-bso-wrap { display:flex; gap:10px; justify-content:center; }
    .bb-bso-col { display:flex; flex-direction:column; align-items:center; gap:3px; }
    .bb-bso-label { font-family:'Orbitron'; font-size:0.52rem; letter-spacing:2px; color:rgba(255,255,255,0.35); }
    .bb-bso-lights { display:flex; gap:4px; }
    .bb-light { width:15px; height:15px; border-radius:50%; border:2px solid rgba(255,255,255,0.15); background:rgba(255,255,255,0.05); transition:all 0.2s; }
    .bb-light.on-strike { background:#ff4444; border-color:#ff4444; box-shadow:0 0 8px #ff4444; }
    .bb-light.on-out    { background:#ffd700; border-color:#ffd700; box-shadow:0 0 8px #ffd700; }
    .bb-bases-wrap { position:relative; width:76px; height:76px; }
    .bb-base { position:absolute; width:18px; height:18px; border:2px solid rgba(255,255,255,0.25); background:rgba(255,255,255,0.05); transform:rotate(45deg); transition:all 0.22s; }
    .bb-base.on { background:#ffd700; border-color:#ffd700; box-shadow:0 0 12px #ffd700; }
    #base-2nd { top:2px; left:28px; }
    #base-3rd { top:28px; left:2px; }
    #base-1st { top:28px; left:54px; }
    #base-home{ top:54px; left:28px; background:rgba(255,255,255,0.12); border-color:rgba(255,255,255,0.35); }
    .bb-side-label { font-family:'Orbitron'; font-size:0.55rem; letter-spacing:2px; color:rgba(255,255,255,0.3); }

    /* 左欄:打擊順序 — ★ 移除 overflow:hidden,讓清單完整展開 */
    .bb-lineup-col {
      grid-column:1; grid-row:2;
      background:rgba(0,0,0,0.3); border:1px solid rgba(255,255,255,0.07);
      border-radius:14px; padding:12px; display:flex; flex-direction:column; gap:8px;
    }
    .bb-col-title { font-family:'Orbitron'; font-size:0.62rem; letter-spacing:3px; color:rgba(255,255,255,0.3); flex-shrink:0; }
    .bb-lineup-tabs { display:flex; gap:5px; flex-shrink:0; }
    .bb-lineup-tab { flex:1; padding:6px; border-radius:20px; border:1px solid rgba(255,255,255,0.12); background:rgba(255,255,255,0.03); color:rgba(255,255,255,0.35); font-family:'Orbitron'; font-size:0.62rem; cursor:pointer; transition:0.2s; text-align:center; }
    .bb-lineup-tab.active { background:rgba(255,60,120,0.18); border-color:#ff3c78; color:#ff3c78; }
    /* ★ 隊伍選擇放大 */
    .bb-team-sel { width:100%; background:rgba(0,0,0,0.5); border:1px solid rgba(255,60,120,0.3); border-radius:10px; color:#fff; font-family:'Noto Sans TC'; font-size:1rem; padding:10px 12px; outline:none; cursor:pointer; flex-shrink:0; }
    /* ★ 打擊順序清單不再有獨立滾軸 */
    .bb-lineup-list { display:flex; flex-direction:column; gap:3px; }
    .bb-lineup-row { display:flex; align-items:center; gap:6px; padding:8px 10px; background:rgba(255,255,255,0.03); border-radius:7px; border:1.5px solid transparent; cursor:pointer; transition:all 0.18s; }
    .bb-lineup-row:hover { background:rgba(255,60,120,0.07); border-color:rgba(255,60,120,0.25); }
    .bb-lineup-row.batting-now { background:rgba(255,60,120,0.14); border-color:#ff3c78; box-shadow:0 0 8px rgba(255,60,120,0.25); }
    .bb-lineup-num { font-family:'Orbitron'; font-size:0.68rem; color:rgba(255,255,255,0.28); width:18px; text-align:center; flex-shrink:0; }
    .bb-lineup-name { flex:1; font-size:0.88rem; font-weight:700; }
    .bb-lineup-arrow { font-size:0.85rem; flex-shrink:0; opacity:0; }
    .bb-lineup-row.batting-now .bb-lineup-arrow { opacity:1; }
    .bb-advance-btn { width:100%; padding:8px; border-radius:8px; border:1px solid rgba(255,60,120,0.25); background:rgba(255,60,120,0.06); color:#ff3c78; font-family:'Orbitron'; font-size:0.6rem; cursor:pointer; transition:0.2s; flex-shrink:0; }
    .bb-advance-btn:hover { background:rgba(255,60,120,0.16); }

    /* 中欄 — ★ 移除 overflow:hidden */
    .bb-center-col { grid-column:2; grid-row:2; display:flex; flex-direction:column; gap:8px; }
    .bb-action-panel { background:rgba(255,60,120,0.04); border:1px solid rgba(255,60,120,0.25); border-radius:14px; padding:11px; flex-shrink:0; }
    .bb-batter-label { font-family:'Orbitron'; font-size:0.6rem; letter-spacing:2px; color:rgba(255,255,255,0.4); margin-bottom:8px; }
    .bb-batter-label span { color:#ffd700; font-weight:900; font-size:0.85rem; letter-spacing:1px; }
    .bb-act-group-lbl { font-family:'Orbitron'; font-size:0.48rem; letter-spacing:2px; color:rgba(255,255,255,0.22); margin-bottom:4px; }
    .bb-act-row { display:flex; gap:5px; margin-bottom:7px; flex-wrap:wrap; }
    .bb-act-btn { flex:1; min-width:48px; padding:8px 4px; border-radius:9px; border:1.5px solid; background:transparent; color:#fff; font-family:'Noto Sans TC'; font-weight:900; font-size:0.8rem; cursor:pointer; transition:all 0.15s; text-align:center; white-space:nowrap; }
    .bb-act-btn:active { transform:scale(0.9); }
    .act-1b { border-color:rgba(0,232,122,0.5); color:#00e87a; } .act-1b:hover { background:rgba(0,232,122,0.13); }
    .act-2b { border-color:rgba(0,243,255,0.5); color:#00f3ff; } .act-2b:hover { background:rgba(0,243,255,0.13); }
    .act-3b { border-color:rgba(188,19,254,0.5); color:#bc13fe; } .act-3b:hover { background:rgba(188,19,254,0.13); }
    .act-hr { border-color:rgba(255,215,0,0.6); color:#ffd700; background:rgba(255,215,0,0.04); } .act-hr:hover { background:rgba(255,215,0,0.18); box-shadow:0 0 12px rgba(255,215,0,0.35); }
    .act-bb { border-color:rgba(255,180,0,0.45); color:#ffb300; } .act-bb:hover { background:rgba(255,180,0,0.13); }
    .act-e  { border-color:rgba(188,19,254,0.4); color:#e060ff; } .act-e:hover  { background:rgba(188,19,254,0.1); }
    .act-k  { border-color:rgba(255,68,68,0.5); color:#ff6666; } .act-k:hover  { background:rgba(255,68,68,0.13); }
    .act-go { border-color:rgba(255,68,68,0.4); color:#ff9090; } .act-go:hover { background:rgba(255,68,68,0.1); }
    .act-fo { border-color:rgba(255,68,68,0.4); color:#ffa0a0; } .act-fo:hover { background:rgba(255,68,68,0.1); }
    .act-dp { border-color:rgba(255,30,30,0.65); color:#ff3333; font-size:0.7rem; } .act-dp:hover { background:rgba(255,30,30,0.16); }
    .bb-act-divider { font-family:'Orbitron'; font-size:0.45rem; letter-spacing:3px; color:rgba(255,255,255,0.15); text-align:center; margin:0 0 5px; }
    /* ★ 各局得分表格 — 移除 flex:1 和 min-height:0 */
    .bb-inning-table-card { background:rgba(0,0,0,0.28); border:1px solid rgba(255,255,255,0.07); border-radius:14px; padding:10px; overflow-x:auto; }
    .bb-table-title { font-family:'Orbitron'; font-size:0.6rem; letter-spacing:4px; color:rgba(255,255,255,0.3); margin-bottom:8px; }
    .bb-table { width:100%; border-collapse:collapse; font-family:'Orbitron'; font-size:0.75rem; min-width:400px; }
    .bb-table th { padding:5px; text-align:center; color:rgba(255,255,255,0.3); font-size:0.58rem; letter-spacing:1px; border-bottom:1px solid rgba(255,255,255,0.08); }
    .bb-table td { padding:5px; text-align:center; border-bottom:1px solid rgba(255,255,255,0.04); }
    .bb-table td.team-cell { text-align:left; font-size:0.7rem; font-family:'Noto Sans TC'; padding-left:7px; }
    .bb-table td.rhe-cell { font-weight:900; font-size:0.88rem; }
    .bb-table td.home-rhe { color:#00f3ff; }
    .bb-table td.away-rhe { color:#ffb300; }
    .bb-inn-cell { cursor:pointer; border-radius:5px; transition:0.15s; min-width:24px; }
    .bb-inn-cell:hover { background:rgba(255,255,255,0.07); }
    .bb-inn-cell.cur { background:rgba(255,60,120,0.14); color:#ff3c78; }

    /* 右欄 — ★ 移除 overflow:hidden */
    .bb-log-col { grid-column:3; grid-row:2; background:rgba(0,0,0,0.28); border:1px solid rgba(255,255,255,0.07); border-radius:14px; padding:10px; display:flex; flex-direction:column; gap:7px; }
    /* ★ 比賽紀錄清單不再有獨立滾軸 */
    .bb-log-list { display:flex; flex-direction:column; gap:3px; }
    .bb-log-item { display:flex; align-items:flex-start; gap:7px; padding:7px 9px; background:rgba(255,255,255,0.02); border-radius:7px; border-left:3px solid; flex-shrink:0; animation:bb-log-in 0.2s ease; }
    @keyframes bb-log-in { from{opacity:0;transform:translateX(5px)} to{opacity:1;transform:translateX(0)} }
    .log-hit   { border-color:#00f3ff; }
    .log-score { border-color:#ffd700; }
    .log-hr    { border-color:#ffd700; background:rgba(255,215,0,0.03); }
    .log-out   { border-color:#ff4444; }
    .log-sys   { border-color:rgba(255,255,255,0.12); }
    .bb-log-inn { font-family:'Orbitron'; font-size:0.52rem; color:rgba(255,255,255,0.25); white-space:nowrap; padding-top:2px; min-width:20px; }
    .bb-log-body { flex:1; }
    .bb-log-player { font-weight:700; font-size:0.82rem; margin-bottom:2px; }
    .bb-log-tag { display:inline-block; padding:2px 7px; border-radius:20px; font-size:0.66rem; font-weight:700; }
    .tag-hit   { background:rgba(0,243,255,0.1); color:#00f3ff; border:1px solid rgba(0,243,255,0.22); }
    .tag-score { background:rgba(255,215,0,0.1); color:#ffd700; border:1px solid rgba(255,215,0,0.28); }
    .tag-hr    { background:rgba(255,215,0,0.16); color:#ffd700; border:1px solid rgba(255,215,0,0.4); }
    .tag-out   { background:rgba(255,68,68,0.1); color:#ff7070; border:1px solid rgba(255,68,68,0.25); }
    .tag-sys   { background:rgba(255,255,255,0.04); color:rgba(255,255,255,0.35); border:1px solid rgba(255,255,255,0.1); font-size:0.6rem; }
    .bb-out-dots { display:flex; gap:3px; margin-top:2px; }
    .bb-out-dot { width:7px; height:7px; border-radius:50%; border:1.5px solid rgba(255,215,0,0.25); }
    .bb-out-dot.on { background:#ffd700; border-color:#ffd700; box-shadow:0 0 4px #ffd700; }

    /* Banners */
    @keyframes bb-score-pop { 0%{transform:scale(1)} 35%{transform:scale(1.38)} 100%{transform:scale(1)} }
    .bb-big-score.popping { animation:bb-score-pop 0.35s cubic-bezier(0.22,1,0.36,1); }
    @keyframes bb-banner-in { 0%{opacity:0;transform:translate(-50%,-50%) scale(0.7)} 12%{opacity:1;transform:translate(-50%,-50%) scale(1)} 80%{opacity:1} 100%{opacity:0;transform:translate(-50%,-50%) scale(1)} }
    .bb-banner { position:fixed; top:48%; left:50%; transform:translate(-50%,-50%); pointer-events:none; z-index:9998; display:none; text-align:center; white-space:nowrap; font-family:'Orbitron'; font-weight:900; }
    .bb-banner.show { display:block; animation:bb-banner-in 1.6s ease forwards; }
    .bb-banner-out   { font-size:1.7rem; color:#ff4444; text-shadow:0 0 20px #ff4444; }
    .bb-banner-hr    { font-size:2.2rem; color:#ffd700; text-shadow:0 0 40px #ffd700,0 0 80px #ffd700; }
    .bb-banner-side  { font-size:1.4rem; color:#ffb300; text-shadow:0 0 16px #ffb300; background:rgba(0,0,0,0.88); padding:12px 36px; border-radius:12px; border:2px solid rgba(255,180,0,0.4); }
    .bb-banner-score { font-size:1.2rem; color:#00f3ff; text-shadow:0 0 18px #00f3ff; }

    /* ══ 競賽規則 ══ */
    #layer8 { justify-content: flex-start; }
    .rules-wrap { width: 100%; max-width: 900px; flex: 1; overflow-y: auto; padding-bottom: 30px; display: flex; flex-direction: column; gap: 14px; }
    .rules-section { background: rgba(0,255,180,0.03); border: 1px solid rgba(0,255,180,0.2); border-radius: 14px; padding: 18px 22px; transition: border-color 0.2s; }
    .rules-section:hover { border-color: rgba(0,255,180,0.45); }
    .rules-section-title { font-family: 'Orbitron'; font-size: 0.85rem; font-weight: 900; letter-spacing: 3px; color: #00ffb3; margin-bottom: 14px; display: flex; align-items: center; gap: 10px; text-shadow: 0 0 10px rgba(0,255,180,0.5); }
    .rules-section-title .rule-icon { font-size: 1.3rem; }
    .rules-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 10px; }
    .rule-card { background: rgba(0,0,0,0.35); border-radius: 10px; padding: 12px 14px; border-left: 3px solid rgba(0,255,180,0.4); }
    .rule-card-label { font-family: 'Orbitron'; font-size: 0.6rem; letter-spacing: 2px; color: #00ffb3; opacity: 0.7; margin-bottom: 5px; }
    .rule-card-value { font-size: 1.05rem; font-weight: 900; color: #fff; line-height: 1.3; }
    .rule-card-sub { font-size: 0.75rem; color: rgba(255,255,255,0.5); margin-top: 4px; line-height: 1.5; }
    .rules-list { display: flex; flex-direction: column; gap: 7px; }
    .rule-item { display: flex; gap: 10px; align-items: flex-start; padding: 8px 12px; background: rgba(0,0,0,0.25); border-radius: 8px; font-size: 0.88rem; line-height: 1.6; color: rgba(255,255,255,0.85); }
    .rule-item .ri-num { font-family: 'Orbitron'; font-size: 0.65rem; color: #00ffb3; background: rgba(0,255,180,0.1); border-radius: 50%; width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; margin-top: 1px; }
    .rule-item.highlight { border-left: 3px solid #ffd700; background: rgba(255,215,0,0.06); color: #fff; font-weight: 700; }
    .rule-item.highlight .ri-num { color: #ffd700; background: rgba(255,215,0,0.15); }
    .rule-tag { display: inline-block; padding: 2px 8px; border-radius: 20px; font-family: 'Orbitron'; font-size: 0.6rem; letter-spacing: 1px; border: 1px solid; margin: 2px 3px 2px 0; }
    .tag-out-r  { color: #ff4444; border-color: rgba(255,68,68,0.4); background: rgba(255,68,68,0.08); }
    .tag-safe   { color: #00ff88; border-color: rgba(0,255,136,0.4); background: rgba(0,255,136,0.08); }
    .tag-score-r{ color: #ffd700; border-color: rgba(255,215,0,0.4); background: rgba(255,215,0,0.08); }
    .tag-foul   { color: #ff9500; border-color: rgba(255,149,0,0.4); background: rgba(255,149,0,0.08); }
    .tag-rule   { color: #00f3ff; border-color: rgba(0,243,255,0.4); background: rgba(0,243,255,0.08); }
    /* ══ 手機響應式 ══ */
    @media (max-width: 768px) {
      .grid-box { grid-template-columns: repeat(2, 1fr); gap: 12px; }
      .bb-wrap { grid-template-columns: 1fr; }
      .bb-top-card { grid-column: 1; }
      .bb-lineup-col { grid-column: 1; grid-row: auto; }
      .bb-center-col { grid-column: 1; grid-row: auto; }
      .bb-log-col { grid-column: 1; grid-row: auto; }
      .tactical-container { flex-direction: column; }
      .tactical-sidebar { width: 100%; flex-direction: row; flex-wrap: wrap; overflow-y: visible; }
      .topps-layout { flex-direction: column; align-items: center; }
      .topps-card { width: 100%; max-width: 360px; }
      .topps-radar-panel { width: 100%; max-width: 360px; }
      .overview-bars { grid-template-columns: repeat(3, 1fr); }
    }
    @media (max-width: 480px) {
      .grid-box { grid-template-columns: 1fr; gap: 10px; }
      .overview-bars { grid-template-columns: repeat(2, 1fr); }
      .enter-btn { font-size: 1.2rem; padding: 14px 40px; }
    }
  </style>
</head>
<body>
  <div class="bg-overlay"></div>
  <canvas id="particles-canvas"></canvas>
  <div class="scan-line" id="scan-line"></div>
  <div class="status-bar" id="status-bar">
    <div class="status-text">◈ SYSTEM ONLINE ◈ DATABASE CONNECTED ◈ PLAYERS LOADED ◈ FANG LIAO WORLD CUP SEASON 1 ◈ ALL TEAMS READY ◈ INITIALIZING MATCH DATA ◈ SYSTEM ONLINE ◈ DATABASE CONNECTED ◈ PLAYERS LOADED ◈ FANG LIAO WORLD CUP ◈</div>
  </div>
  <div class="live-ticker" id="live-ticker">
    <div class="ticker-label">⚡ LIVE</div>
    <div class="ticker-text" id="ticker-text"></div>
  </div>
  <div class="vs-hint" id="vs-hint"></div>
  <button class="vs-btn" id="vs-btn" onclick="toggleVsMode()">⚔ VERSUS</button>

  <!-- VS 對撞舞台 -->
  <div class="vs-overlay" id="vs-overlay">
    <div class="vs-stage" id="vs-stage">
      <div class="vs-flash" id="vs-flash"></div>
      <div class="vs-explosion" id="vs-explosion"></div>
      <div class="vs-card-wrap card-a" id="vs-card-a">
        <div class="vs-card" id="vs-card-img-a"><img id="vs-img-a" src="" alt=""></div>
        <div class="vs-card-name" id="vs-name-a"></div>
        <div class="vs-card-rank" id="vs-rank-a"></div>
      </div>
      <div class="vs-card-wrap card-b" id="vs-card-b">
        <div class="vs-card" id="vs-card-img-b"><img id="vs-img-b" src="" alt=""></div>
        <div class="vs-card-name" id="vs-name-b"></div>
        <div class="vs-card-rank" id="vs-rank-b"></div>
      </div>
      <div class="vs-result" id="vs-result">
        <div class="vs-result-title">◈ BATTLE RESULT ◈</div>
        <div class="vs-result-fighters">
          <div class="vs-result-fighter" id="vs-rf-a">
            <div class="vs-result-crown" id="vs-crown-a"></div>
            <img id="vs-rimg-a" src="" alt="">
            <div class="vs-result-fighter-name" id="vs-rname-a"></div>
            <div id="vs-rpct-a"></div>
          </div>
          <div class="vs-vs-mid">VS</div>
          <div class="vs-result-fighter" id="vs-rf-b">
            <div class="vs-result-crown" id="vs-crown-b"></div>
            <img id="vs-rimg-b" src="" alt="">
            <div class="vs-result-fighter-name" id="vs-rname-b"></div>
            <div id="vs-rpct-b"></div>
          </div>
        </div>
        <div class="vs-bar-wrap"><div class="vs-bar-track" id="vs-bar-track"></div></div>
        <div class="vs-detail" id="vs-detail"></div>
        <button class="vs-close" onclick="closeVersus()">關閉</button>
      </div>
    </div>
  </div>

  <!-- 抽卡包彈窗 -->
  <div class="pack-overlay" id="pack-overlay" onclick="event.stopPropagation()">
    <div id="pack-icon-wrap">
      <div class="pack-icon" id="pack-icon" onclick="openPack()">🎴</div>
      <div class="pack-hint">點擊卡包抽出球員</div>
    </div>
    <div class="pack-card-wrap" id="pack-card-wrap">
      <div class="pack-card-scene">
        <div class="pack-card-flip" id="pack-card-flip">
          <div class="pack-card-back">
            <svg viewBox="0 0 220 290" xmlns="http://www.w3.org/2000/svg">
              <defs>
                <linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#0a0800"/><stop offset="50%" style="stop-color:#1a1200"/><stop offset="100%" style="stop-color:#080600"/></linearGradient>
                <linearGradient id="goldGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#b8860b"/><stop offset="40%" style="stop-color:#ffd700"/><stop offset="60%" style="stop-color:#fffacd"/><stop offset="100%" style="stop-color:#b8860b"/></linearGradient>
                <linearGradient id="centerGrad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#1a1200"/><stop offset="100%" style="stop-color:#2a1e00"/></linearGradient>
              </defs>
              <rect width="220" height="290" rx="14" fill="url(#bgGrad)"/>
              <rect x="6" y="6" width="208" height="278" rx="11" fill="none" stroke="url(#goldGrad)" stroke-width="2.5"/>
              <rect x="11" y="11" width="198" height="268" rx="9" fill="none" stroke="#b8860b" stroke-width="0.8" opacity="0.5"/>
              <g fill="url(#goldGrad)">
                <polygon points="6,6 28,6 6,28" opacity="0.9"/><rect x="6" y="6" width="16" height="2.5" rx="1"/><rect x="6" y="6" width="2.5" height="16" rx="1"/><circle cx="20" cy="20" r="3.5" fill="none" stroke="url(#goldGrad)" stroke-width="1.5"/>
                <polygon points="214,6 192,6 214,28" opacity="0.9"/><rect x="198" y="6" width="16" height="2.5" rx="1"/><rect x="211.5" y="6" width="2.5" height="16" rx="1"/><circle cx="200" cy="20" r="3.5" fill="none" stroke="url(#goldGrad)" stroke-width="1.5"/>
                <polygon points="6,284 28,284 6,262" opacity="0.9"/><rect x="6" y="281.5" width="16" height="2.5" rx="1"/><rect x="6" y="268" width="2.5" height="16" rx="1"/><circle cx="20" cy="270" r="3.5" fill="none" stroke="url(#goldGrad)" stroke-width="1.5"/>
                <polygon points="214,284 192,284 214,262" opacity="0.9"/><rect x="198" y="281.5" width="16" height="2.5" rx="1"/><rect x="211.5" y="268" width="2.5" height="16" rx="1"/><circle cx="200" cy="270" r="3.5" fill="none" stroke="url(#goldGrad)" stroke-width="1.5"/>
              </g>
              <circle cx="110" cy="145" r="72" fill="url(#centerGrad)" stroke="url(#goldGrad)" stroke-width="2"/>
              <circle cx="110" cy="145" r="64" fill="none" stroke="#b8860b" stroke-width="0.8" opacity="0.6"/>
              <circle cx="110" cy="145" r="55" fill="none" stroke="url(#goldGrad)" stroke-width="1.2" opacity="0.4"/>
              <g stroke="url(#goldGrad)" stroke-width="1" fill="none" opacity="0.7">
                <line x1="110" y1="73" x2="110" y2="87"/><line x1="110" y1="203" x2="110" y2="217"/>
                <line x1="38" y1="145" x2="52" y2="145"/><line x1="168" y1="145" x2="182" y2="145"/>
                <line x1="59" y1="94" x2="69" y2="104"/><line x1="151" y1="94" x2="161" y2="104"/>
                <line x1="59" y1="196" x2="69" y2="186"/><line x1="151" y1="196" x2="161" y2="186"/>
              </g>
              <g transform="translate(110,145)">
                <polygon points="0,-38 22,-15 0,10 -22,-15" fill="url(#goldGrad)" opacity="0.9"/>
                <polygon points="0,-28 16,-10 0,5 -16,-10" fill="#0a0800"/>
                <polygon points="0,-20 10,-6 0,6 -10,-6" fill="url(#goldGrad)" opacity="0.7"/>
                <path d="M-18,18 Q0,38 18,18 Q8,28 0,26 Q-8,28 -18,18Z" fill="url(#goldGrad)" opacity="0.8"/>
                <path d="M-10,14 Q0,22 10,14" fill="none" stroke="url(#goldGrad)" stroke-width="1.5"/>
                <circle cx="-30" cy="0" r="3" fill="url(#goldGrad)"/><circle cx="30" cy="0" r="3" fill="url(#goldGrad)"/>
                <circle cx="0" cy="-42" r="3" fill="url(#goldGrad)"/><circle cx="0" cy="16" r="2" fill="url(#goldGrad)"/>
              </g>
              <text x="110" y="42" text-anchor="middle" font-family="Orbitron,sans-serif" font-size="8" fill="url(#goldGrad)" letter-spacing="4" opacity="0.8">FANG LIAO</text>
              <line x1="40" y1="47" x2="180" y2="47" stroke="url(#goldGrad)" stroke-width="0.8" opacity="0.4"/>
              <line x1="40" y1="243" x2="180" y2="243" stroke="url(#goldGrad)" stroke-width="0.8" opacity="0.4"/>
              <text x="110" y="257" text-anchor="middle" font-family="Orbitron,sans-serif" font-size="8" fill="url(#goldGrad)" letter-spacing="4" opacity="0.8">WORLD CUP</text>
              <text x="110" y="271" text-anchor="middle" font-family="Orbitron,sans-serif" font-size="6.5" fill="#b8860b" letter-spacing="3" opacity="0.6">SEASON I</text>
            </svg>
          </div>
          <div class="pack-card-face" style="position:relative;">
            <img class="pack-card-img" id="pack-card-img" src="" alt="">
          </div>
        </div>
      </div>
      <div class="pack-card-name" id="pack-card-name"></div>
      <div class="pack-card-info" id="pack-card-info"></div>
      <div style="display:flex;gap:12px;margin-top:4px;">
        <button class="pack-again-btn" onclick="resetPack()">🎴 再抽一張</button>
        <button class="pack-close-btn" onclick="document.getElementById('pack-overlay').classList.remove('active')">關閉</button>
      </div>
    </div>
  </div>

  <!-- 文字輸入浮層 -->
  <div id="tac-text-input-wrap" style="display:none;position:fixed;inset:0;z-index:500;align-items:center;justify-content:center;background:rgba(0,0,0,0.6);">
    <div style="background:#1a1a2e;border:2px solid #ffb300;border-radius:14px;padding:20px 24px;display:flex;flex-direction:column;gap:12px;min-width:280px;">
      <div style="font-family:'Orbitron';font-size:0.75rem;letter-spacing:3px;color:#ffb300;">輸入文字</div>
      <input id="tac-text-input" type="text" placeholder="輸入內容..." maxlength="20"
        style="background:rgba(255,180,0,0.06);border:1px solid rgba(255,180,0,0.4);border-radius:8px;padding:8px 12px;color:#fff;font-family:'Noto Sans TC';font-size:1rem;outline:none;"
        onkeydown="if(event.key==='Enter') confirmFloatingText()">
      <div style="display:flex;gap:8px;">
        <button onclick="confirmFloatingText()" style="flex:1;padding:8px;border-radius:8px;border:none;background:#ffb300;color:#000;font-family:'Orbitron';font-weight:900;font-size:0.8rem;cursor:pointer;">確認 → 點擊放置</button>
        <button onclick="document.getElementById('tac-text-input-wrap').style.display='none';tacFloatingText=null;" style="padding:8px 14px;border-radius:8px;border:1px solid rgba(255,255,255,0.2);background:transparent;color:rgba(255,255,255,0.5);font-size:0.8rem;cursor:pointer;">取消</button>
      </div>
    </div>
  </div>

  <div id="loading">INITIALIZING DATABASE...</div>
  <div id="error-msg">
    <div id="error-text">載入失敗</div>
    <button onclick="location.reload()">重新載入</button>
  </div>

  <!-- Layer 1:主頁 -->
  <div id="layer1" class="layer" style="justify-content:center; gap:24px;">
    <h1 class="layer1-title">第一屆<br>枋寮世界盃</h1>
    <div class="layer1-sub">FANG LIAO WORLD CUP · SEASON 1</div>
    <div style="display:flex; flex-direction:column; gap:16px; align-items:center; margin-top:8px;">
      <button class="enter-btn" onclick="showLayer(2)">ENTER DATABASE</button>
      <!-- 第一排:賽程表 + 名人堂 + 抽卡包 -->
      <div style="display:flex; gap:16px; flex-wrap:wrap; justify-content:center;">
        <button class="enter-btn" onclick="showLayer(9)" style="font-size:1.1rem; padding:12px 36px; background:rgba(0,220,255,0.05); border-color:#00dcff; color:#00dcff; animation:none; box-shadow:0 0 20px rgba(0,220,255,0.3);">📅 賽程表</button>
        <button class="enter-btn" onclick="showLayer(5)" style="font-size:1.1rem; padding:12px 36px; background:rgba(255,215,0,0.05); border-color:#ffd700; color:#ffd700; animation:none; box-shadow:0 0 20px rgba(255,215,0,0.3);">🏆 名人堂</button>
        <button class="enter-btn" onclick="showPackOverlay()" style="font-size:1.1rem; padding:12px 36px; background:rgba(255,150,0,0.05); border-color:#ff9500; color:#ff9500; animation:none; box-shadow:0 0 20px rgba(255,149,0,0.3);">🎴 抽卡包</button>
      </div>
      <!-- 第二排:戰術板 + 計分板 + 競賽規則 -->
      <div style="display:flex; gap:16px; flex-wrap:wrap; justify-content:center;">
        <button class="enter-btn" onclick="showLayer(6)" style="font-size:1.1rem; padding:12px 36px; background:rgba(255,180,0,0.05); border-color:#ffb300; color:#ffb300; animation:none; box-shadow:0 0 20px rgba(255,180,0,0.3);">⚾ 戰術板</button>
        <button class="enter-btn" onclick="showLayer(7)" style="font-size:1.1rem; padding:12px 36px; background:rgba(255,60,120,0.05); border-color:#ff3c78; color:#ff3c78; animation:none; box-shadow:0 0 20px rgba(255,60,120,0.3);">🏅 計分板</button>
        <button class="enter-btn" onclick="showLayer(8)" style="font-size:1.1rem; padding:12px 36px; background:rgba(0,255,180,0.05); border-color:#00ffb3; color:#00ffb3; animation:none; box-shadow:0 0 20px rgba(0,255,180,0.3);">📖 競賽規則</button>
      </div>
    </div>
  </div>

  <!-- Layer 9:賽程表 -->
  <div id="layer9" class="layer" style="justify-content:flex-start;">
    <div style="width:100%; max-width:1000px; display:flex; align-items:center; justify-content:space-between; flex-shrink:0; margin-bottom:12px;">
      <button class="btn-back" onclick="showLayer(1)" style="margin:0;">◀ HOME</button>
      <h2 style="font-family:'Orbitron'; color:#00dcff; margin:0; font-size:1.1rem; letter-spacing:4px; text-shadow:0 0 12px #00dcff;">📅 賽程表</h2>
      <div style="width:80px;"></div>
    </div>
    <div style="width:100%; max-width:1000px; flex:1; overflow-y:auto; padding-bottom:30px;">

      <!-- 新增賽事按鈕 -->
      <div style="display:flex; gap:10px; margin-bottom:16px; flex-wrap:wrap; align-items:center;">
        <button onclick="openAddMatch()" style="padding:8px 20px; border-radius:20px; border:1px solid rgba(0,220,255,0.5); background:rgba(0,220,255,0.08); color:#00dcff; font-family:'Orbitron'; font-size:0.7rem; cursor:pointer; transition:0.2s;" onmouseover="this.style.background='rgba(0,220,255,0.2)'" onmouseout="this.style.background='rgba(0,220,255,0.08)'">+ 新增賽事</button>
        <select id="schedule-filter" onchange="renderSchedule()" style="background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.2); color:#fff; border-radius:20px; padding:7px 14px; font-family:'Orbitron'; font-size:0.65rem; outline:none; cursor:pointer;">
          <option value="all">全部場次</option>
          <option value="pending">未開始</option>
          <option value="done">已完成</option>
        </select>
      </div>

      <!-- 賽程列表 -->
      <div id="schedule-list"></div>
    </div>
  </div>

  <!-- 新增/編輯賽事彈窗 -->
  <div id="match-modal" style="display:none; position:fixed; inset:0; z-index:3000; align-items:center; justify-content:center; background:rgba(0,0,0,0.75);">
    <div style="background:#0d1117; border:2px solid rgba(0,220,255,0.4); border-radius:16px; padding:24px; width:min(420px,90vw); display:flex; flex-direction:column; gap:14px;">
      <div style="font-family:'Orbitron'; font-size:0.85rem; letter-spacing:3px; color:#00dcff;" id="match-modal-title">新增賽事</div>
      <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
        <div style="display:flex; flex-direction:column; gap:5px;">
          <label style="font-size:0.65rem; color:rgba(255,180,0,0.7); font-family:'Orbitron';">主隊</label>
          <input id="m-home" placeholder="主隊名稱" style="background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.15); border-radius:8px; padding:8px 10px; color:#fff; font-family:'Noto Sans TC'; font-size:0.85rem; outline:none;">
        </div>
        <div style="display:flex; flex-direction:column; gap:5px;">
          <label style="font-size:0.65rem; color:rgba(255,180,0,0.7); font-family:'Orbitron';">客隊</label>
          <input id="m-away" placeholder="客隊名稱" style="background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.15); border-radius:8px; padding:8px 10px; color:#fff; font-family:'Noto Sans TC'; font-size:0.85rem; outline:none;">
        </div>
      </div>
      <div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">
        <div style="display:flex; flex-direction:column; gap:5px;">
          <label style="font-size:0.65rem; color:rgba(255,255,255,0.4); font-family:'Orbitron';">日期</label>
          <input id="m-date" type="date" style="background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.15); border-radius:8px; padding:8px 10px; color:#fff; font-family:'Orbitron'; font-size:0.8rem; outline:none;">
        </div>
        <div style="display:flex; flex-direction:column; gap:5px;">
          <label style="font-size:0.65rem; color:rgba(255,255,255,0.4); font-family:'Orbitron';">時間</label>
          <input id="m-time" type="time" style="background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.15); border-radius:8px; padding:8px 10px; color:#fff; font-family:'Orbitron'; font-size:0.8rem; outline:none;">
        </div>
      </div>
      <div style="display:flex; flex-direction:column; gap:5px;">
        <label style="font-size:0.65rem; color:rgba(255,255,255,0.4); font-family:'Orbitron';">場地</label>
        <input id="m-venue" placeholder="場地名稱(選填)" style="background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.15); border-radius:8px; padding:8px 10px; color:#fff; font-family:'Noto Sans TC'; font-size:0.85rem; outline:none;">
      </div>
      <div style="display:grid; grid-template-columns:1fr auto 1fr; gap:8px; align-items:center;">
        <div style="display:flex; flex-direction:column; gap:5px;">
          <label style="font-size:0.65rem; color:rgba(0,243,255,0.7); font-family:'Orbitron'; text-align:center;">主隊得分</label>
          <input id="m-hscore" type="number" min="0" placeholder="-" style="background:rgba(0,243,255,0.06); border:1px solid rgba(0,243,255,0.2); border-radius:8px; padding:8px 10px; color:#00f3ff; font-family:'Orbitron'; font-size:1rem; outline:none; text-align:center;">
        </div>
        <div style="font-family:'Orbitron'; color:rgba(255,255,255,0.3); font-size:1rem; text-align:center;">VS</div>
        <div style="display:flex; flex-direction:column; gap:5px;">
          <label style="font-size:0.65rem; color:rgba(255,180,0,0.7); font-family:'Orbitron'; text-align:center;">客隊得分</label>
          <input id="m-ascore" type="number" min="0" placeholder="-" style="background:rgba(255,180,0,0.06); border:1px solid rgba(255,180,0,0.2); border-radius:8px; padding:8px 10px; color:#ffb300; font-family:'Orbitron'; font-size:1rem; outline:none; text-align:center;">
        </div>
      </div>
      <div style="display:flex; gap:10px; margin-top:4px;">
        <button onclick="saveMatch()" style="flex:1; padding:10px; border-radius:10px; border:none; background:#00dcff; color:#000; font-family:'Orbitron'; font-weight:900; font-size:0.85rem; cursor:pointer;">儲存</button>
        <button onclick="closeMatchModal()" style="padding:10px 20px; border-radius:10px; border:1px solid rgba(255,255,255,0.2); background:transparent; color:rgba(255,255,255,0.5); font-size:0.85rem; cursor:pointer;">取消</button>
      </div>
    </div>
  </div>

  <!-- Layer 5:名人堂 -->
  <div id="layer5" class="layer" style="justify-content:flex-start;">
    <button class="btn-back" onclick="showLayer(1)">◀ HOME</button>
    <h2 style="flex-shrink:0; font-family:'Orbitron'; color:#ffd700; border-bottom:3px solid #ffd700; margin:0 0 20px 0; width:100%; max-width:1200px; padding-bottom:10px; text-shadow:0 0 16px #ffd700; letter-spacing:6px;">🏆 HALL OF FAME</h2>
    <div style="width:100%; max-width:1200px; flex:1; overflow-y:auto; padding-bottom:40px;">
      <div id="leaderboard-content"></div>
    </div>
  </div>

  <!-- Layer 2:隊伍 -->
  <div id="layer2" class="layer" style="justify-content:flex-start;">
    <button class="btn-back" onclick="showLayer(1)">◀ HOME</button>
    <h2 style="flex-shrink:0; font-family:'Orbitron'; color:var(--neon-blue); border-bottom:3px solid var(--neon-blue); margin:0 0 10px 0; width:100%; max-width:1200px; padding-bottom:10px; text-shadow:0 0 12px var(--neon-blue);">參賽隊伍</h2>
    <div class="search-wrap">
      <span class="search-icon">🔍</span>
      <input id="team-search" type="text" placeholder="SEARCH TEAM...">
    </div>
    <div class="scroll-content"><div id="team-grid" class="grid-box"></div></div>
  </div>

  <!-- Layer 3:球員列表 -->
  <div id="layer3" class="layer" style="justify-content:flex-start;">
    <button class="btn-back" onclick="showLayer(2)" style="flex-shrink:0;">◀ TEAMS</button>
    <div style="width:100%; max-width:1200px; flex:1; overflow-y:auto; padding-bottom:30px; box-sizing:border-box;">
      <h2 id="team-title" style="font-family:'Orbitron'; color:var(--neon-blue); border-bottom:3px solid var(--neon-blue); margin:0 0 10px 0; padding-bottom:10px;"></h2>
      <div id="team-group-photo-wrap" style="width:100%; margin-bottom:12px; border-radius:14px; overflow:hidden; border:2px solid rgba(0,243,255,0.25); display:none;">
        <img id="team-group-photo" src="" alt="全隊合照" style="width:100%; max-height:400px; display:block; object-fit:contain; object-position:center; background:#0a0a0a;">
      </div>
      <div class="team-overview" id="team-overview">
        <div class="team-overview-title">◈ TEAM POWER OVERVIEW</div>
        <div class="overview-bars" id="overview-bars"></div>
      </div>
      <div class="search-wrap" style="margin:0 0 12px 0;">
        <span class="search-icon">🔍</span>
        <input id="player-search" type="text" placeholder="SEARCH PLAYER...">
      </div>
      <div id="player-grid" class="grid-box"></div>
    </div>
  </div>

  <!-- Layer 4:球員詳情 -->
  <div id="layer4" class="layer">
    <button class="btn-back" onclick="showLayer(3)">◀ ROSTER</button>
    <div class="topps-layout">
      <div id="topps-card" class="topps-card topps-NORMAL">
        <img id="det-photo" class="topps-photo" src="" alt="球員照片">
        <div class="topps-info">
          <div class="topps-name-row">
            <div id="det-name" class="topps-name"></div>
            <div id="det-rank-badge" class="topps-rank-badge rank-NORMAL">S</div>
          </div>
          <div id="topps-div" class="topps-divider div-NORMAL"></div>
          <div id="det-pos" class="topps-pos"></div>
          <div id="det-slogan" class="topps-slogan"></div>
        </div>
      </div>
      <div class="topps-radar-panel">
        <div class="radar-title">PLAYER STATS</div>
        <div id="radarChart-wrap"><canvas id="radarChart"></canvas></div>
      </div>
    </div>
  </div>

  <!-- Layer 6:戰術板 -->
  <div id="layer6" class="layer">
    <div style="width:100%; max-width:1400px; display:flex; align-items:center; justify-content:space-between; flex-shrink:0; margin-bottom:10px;">
      <button class="btn-back" onclick="showLayer(1)" style="margin:0;">◀ HOME</button>
      <h2 style="font-family:'Orbitron'; color:#ffb300; margin:0; font-size:1.1rem; letter-spacing:4px; text-shadow:0 0 12px #ffb300;">⚾ BASEBALL TACTICAL BOARD</h2>
      <div style="display:flex; gap:8px;">
        <button class="tac-action-btn" onclick="clearTactical()" style="color:#ff4444; border-color:rgba(255,68,68,0.4); background:rgba(255,68,68,0.06);">🗑 清除</button>
        <button class="tac-action-btn" onclick="undoTactical()" style="color:#ffd700; border-color:rgba(255,215,0,0.4); background:rgba(255,215,0,0.06);">↩ 上一步</button>
      </div>
    </div>
    <div class="tactical-container">
      <div class="tactical-sidebar">
        <div class="tactical-sidebar-section">
          <div class="tactical-sidebar-title">◈ 繪圖工具</div>
          <button class="tac-tool-btn active" id="tool-pen" onclick="setTacTool('pen')">✏️ 畫筆</button>
          <button class="tac-tool-btn" id="tool-arrow" onclick="setTacTool('arrow')">➡️ 箭頭</button>
          <button class="tac-tool-btn" id="tool-erase" onclick="setTacTool('erase')">🧹 橡皮擦</button>
        </div>
        <div class="tactical-sidebar-section">
          <div class="tactical-sidebar-title">◈ 顏色</div>
          <div class="tac-color-row">
            <div class="tac-color-btn active" style="background:#ff3333;" onclick="setTacColor('#ff3333',this)" title="紅"></div>
            <div class="tac-color-btn" style="background:#00f3ff;" onclick="setTacColor('#00f3ff',this)" title="藍"></div>
            <div class="tac-color-btn" style="background:#ffd700;" onclick="setTacColor('#ffd700',this)" title="黃"></div>
            <div class="tac-color-btn" style="background:#00ff80;" onclick="setTacColor('#00ff80',this)" title="綠"></div>
            <div class="tac-color-btn" style="background:#ff7700;" onclick="setTacColor('#ff7700',this)" title="橙"></div>
            <div class="tac-color-btn" style="background:#fff;" onclick="setTacColor('#ffffff',this)" title="白"></div>
          </div>
        </div>
        <div class="tactical-sidebar-section">
          <div class="tactical-sidebar-title">◈ 線條粗細</div>
          <div class="tac-size-row">
            <button class="tac-size-btn" onclick="setTacSize(2,this)">細</button>
            <button class="tac-size-btn active" onclick="setTacSize(4,this)">中</button>
            <button class="tac-size-btn" onclick="setTacSize(8,this)">粗</button>
          </div>
        </div>
        <div class="tactical-sidebar-section">
          <div class="tactical-sidebar-title">◈ 球員標記</div>
          <button class="tac-shape-btn" id="tool-batter" onclick="setTacTool('batter')">🟡 打者</button>
          <button class="tac-shape-btn" id="tool-runner" onclick="setTacTool('runner')">🔵 跑者</button>
          <button class="tac-shape-btn" id="tool-fielder" onclick="setTacTool('fielder')">🔴 守備員</button>
          <button class="tac-shape-btn" id="tool-ball" onclick="setTacTool('ball')">⚪ 球</button>
        </div>
        <div class="tactical-sidebar-section">
          <div class="tactical-sidebar-title">◈ 文字</div>
          <button class="tac-tool-btn" id="tool-text" onclick="setTacTool('text')">🔤 加入文字</button>
        </div>
        <div class="tactical-sidebar-section">
          <div class="tactical-sidebar-title">◈ 場景</div>
          <button class="tac-tool-btn" onclick="drawBaseballField('full')">全場</button>
          <button class="tac-tool-btn" onclick="drawBaseballField('infield')">內野特寫</button>
        </div>
      </div>
      <div class="tactical-board-wrap">
        <canvas id="tactical-canvas"></canvas>
      </div>
    </div>
  </div>

  <!-- Layer 7:計分板(自動化版) -->
  <div id="layer7" class="layer">
    <!-- Flash Banners -->
    <div class="bb-banner bb-banner-out"   id="bb-banner-out"></div>
    <div class="bb-banner bb-banner-hr"    id="bb-banner-hr">🏠 HOME RUN 🏠</div>
    <div class="bb-banner bb-banner-side"  id="bb-banner-side">三出局!攻守交換</div>
    <div class="bb-banner bb-banner-score" id="bb-banner-score"></div>

    <div style="width:100%; max-width:1200px; display:flex; align-items:center; justify-content:space-between; flex-shrink:0; margin-bottom:8px;">
      <button class="btn-back" onclick="showLayer(1)" style="margin:0;">◀ HOME</button>
      <h2 style="font-family:'Orbitron'; color:#ff3c78; margin:0; font-size:1.1rem; letter-spacing:4px; text-shadow:0 0 12px #ff3c78;">⚾ BASEBALL SCOREBOARD</h2>
      <button class="tac-action-btn" onclick="resetScoreboard()" style="color:#ff4444; border-color:rgba(255,68,68,0.4); background:rgba(255,68,68,0.06);">🔄 重置</button>
    </div>

    <div class="bb-wrap">
      <!-- TOP:大計分 + BSO + 壘包 -->
      <div class="bb-top-card">
        <div class="bb-inning-bar">
          <span class="bb-inning-label">局次:</span>
          <button class="bb-inning-btn active" onclick="bbSetInning(1)" id="inning-btn-1">1</button>
          <button class="bb-inning-btn" onclick="bbSetInning(2)" id="inning-btn-2">2</button>
          <button class="bb-inning-btn" onclick="bbSetInning(3)" id="inning-btn-3">3</button>
          <button class="bb-inning-btn" onclick="bbSetInning(4)" id="inning-btn-4">4</button>
          <button class="bb-inning-btn" onclick="bbSetInning(5)" id="inning-btn-5">5</button>
          <button class="bb-inning-btn" onclick="bbSetInning(6)" id="inning-btn-6">6</button>
          <button class="bb-inning-btn" onclick="bbSetInning(7)" id="inning-btn-7">EX</button>
        </div>
        <div class="bb-score-row">
          <div class="bb-team-block bb-team-home">
            <div style="font-family:'Orbitron'; font-size:0.52rem; letter-spacing:3px; color:rgba(0,243,255,0.45);">主隊</div>
            <input class="bb-team-name-input" id="bb-home-name" value="主隊" maxlength="10" oninput="bbRenderInningTable()">
            <div class="bb-big-score" id="bb-home-score">0</div>
            <div class="bb-manual-btns">
              <button class="bb-manual-btn" onclick="bbManualScore('home',-1)">−</button>
              <button class="bb-manual-btn" onclick="bbManualScore('home',1)">+1</button>
            </div>
          </div>
          <div class="bb-vs-col">
            <div class="bb-vs-text">VS</div>
            <div class="bb-bso-wrap">
              <div class="bb-bso-col">
                <div class="bb-bso-label">STRIKE</div>
                <div class="bb-bso-lights" id="bb-strike-lights"></div>
              </div>
              <div class="bb-bso-col">
                <div class="bb-bso-label">OUT</div>
                <div class="bb-bso-lights" id="bb-out-lights"></div>
              </div>
            </div>
            <div class="bb-bases-wrap">
              <div class="bb-base" id="base-2nd" title="二壘"></div>
              <div class="bb-base" id="base-3rd" title="三壘"></div>
              <div class="bb-base" id="base-1st" title="一壘"></div>
              <div class="bb-base" id="base-home" title="本壘"></div>
            </div>
            <div class="bb-side-label" id="bb-side-label">主隊攻擊中</div>
          </div>
          <div class="bb-team-block bb-team-away">
            <div style="font-family:'Orbitron'; font-size:0.52rem; letter-spacing:3px; color:rgba(255,180,0,0.45);">客隊</div>
            <input class="bb-team-name-input" id="bb-away-name" value="客隊" maxlength="10" oninput="bbRenderInningTable()">
            <div class="bb-big-score" id="bb-away-score">0</div>
            <div class="bb-manual-btns">
              <button class="bb-manual-btn" onclick="bbManualScore('away',-1)">−</button>
              <button class="bb-manual-btn" onclick="bbManualScore('away',1)">+1</button>
            </div>
          </div>
        </div>
      </div>

      <!-- LEFT:打擊順序 -->
      <div class="bb-lineup-col">
        <div class="bb-col-title">◈ 打擊順序</div>
        <div class="bb-lineup-tabs">
          <button class="bb-lineup-tab active" onclick="bbSwitchLineup('home')" id="lineup-tab-home">主隊</button>
          <button class="bb-lineup-tab" onclick="bbSwitchLineup('away')" id="lineup-tab-away">客隊</button>
        </div>
        <select class="bb-team-sel" id="bb-team-sel" onchange="bbOnTeamChange()">
          <option value="">-- 選隊伍 --</option>
        </select>
        <div class="bb-lineup-list" id="bb-lineup-list"></div>
        <button class="bb-advance-btn" onclick="bbManualAdvance()">▶ 手動推進打者</button>
      </div>

      <!-- CENTER:動作面板 + 各局得分 -->
      <div class="bb-center-col">
        <div class="bb-action-panel">
          <div class="bb-batter-label">打者:<span id="bb-batter-display">---</span></div>
          <div class="bb-act-group-lbl">── 安打 / 上壘 ──</div>
          <div class="bb-act-row">
            <button class="bb-act-btn act-1b" onclick="bbDoPlay('1b')">一壘打</button>
            <button class="bb-act-btn act-2b" onclick="bbDoPlay('2b')">二壘打</button>
            <button class="bb-act-btn act-3b" onclick="bbDoPlay('3b')">三壘打</button>
            <button class="bb-act-btn act-hr" onclick="bbDoPlay('hr')">🏠 全壘打</button>
          </div>
          <div class="bb-act-divider">─── OUT ───</div>
          <div class="bb-act-row">
            <button class="bb-act-btn act-k"  onclick="bbDoPlay('k')">三振 K</button>
            <button class="bb-act-btn act-go" onclick="bbDoPlay('go')">滾地 Out</button>
            <button class="bb-act-btn act-fo" onclick="bbDoPlay('fo')">飛球 Out</button>
            <button class="bb-act-btn act-dp" onclick="bbDoPlay('dp')">雙殺 DP</button>
          </div>
        </div>
        <div class="bb-inning-table-card">
          <div class="bb-table-title">◈ 各局得分</div>
          <table class="bb-table">
            <thead><tr>
              <th>隊伍</th>
              <th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>EX</th>
              <th style="border-left:2px solid rgba(255,255,255,0.1);">R</th>
            </tr></thead>
            <tbody id="bb-inning-tbody"></tbody>
          </table>
        </div>
      </div>

      <!-- RIGHT:比賽紀錄 -->
      <div class="bb-log-col">
        <div class="bb-col-title">◈ 比賽紀錄</div>
        <div class="bb-log-list" id="bb-log-list">
          <div style="text-align:center;font-family:'Orbitron';font-size:0.62rem;color:rgba(255,255,255,0.15);padding:16px;">尚無紀錄</div>
        </div>
      </div>
    </div>
  </div>

  <!-- Layer 8:競賽規則 -->
  <div id="layer8" class="layer">
    <div style="width:100%; max-width:900px; display:flex; align-items:center; justify-content:space-between; flex-shrink:0; margin-bottom:12px;">
      <button class="btn-back" onclick="showLayer(1)" style="margin:0;">◀ HOME</button>
      <h2 style="font-family:'Orbitron'; color:#00ffb3; margin:0; font-size:1.1rem; letter-spacing:4px; text-shadow:0 0 12px #00ffb3;">📖 樂樂棒球競賽規則</h2>
      <div style="font-family:'Orbitron'; font-size:0.6rem; color:rgba(0,255,180,0.4); letter-spacing:2px;">中華民國樂樂棒球推廣協會</div>
    </div>
    <div class="rules-wrap">
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">🏟️</span> 比賽場地</div>
        <div class="rules-grid">
          <div class="rule-card"><div class="rule-card-label">壘間距離</div><div class="rule-card-value">14 公尺</div><div class="rule-card-sub">運動場使用 11號T用球</div></div>
          <div class="rule-card"><div class="rule-card-label">外野距離</div><div class="rule-card-value">40 公尺以上</div><div class="rule-card-sub">界外線到球場外線 8m 以上</div></div>
          <div class="rule-card"><div class="rule-card-label">打擊圈</div><div class="rule-card-value">半徑 3 公尺</div><div class="rule-card-sub">以本壘板後角為中心設置</div></div>
          <div class="rule-card"><div class="rule-card-label">擊球座位置</div><div class="rule-card-value">本壘板後方 50cm~1m</div><div class="rule-card-sub">劃線寬度均為 5cm</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">👥</span> 守備位置(9位)</div>
        <div class="rules-grid">
          <div class="rule-card"><div class="rule-card-value" style="font-size:0.85rem; line-height:1.8;">本壘手・一壘手・二壘手・三壘手<br>第一游擊手(可守投手丘)<br>第二游擊手<br>左外野手・中外野手・右外野手</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">⚾</span> 比賽進行</div>
        <div class="rules-list">
          <div class="rule-item highlight"><div class="ri-num">★</div><div>全隊打擊者都擊球完畢後,才進行攻防交替(每隊攻防兩次)</div></div>
          <div class="rule-item"><div class="ri-num">1</div><div>殘留在壘上的跑壘員,下一局可繼續從殘壘接跑(最後一局除外)</div></div>
          <div class="rule-item"><div class="ri-num">2</div><div>比賽採積分制:<span class="rule-tag tag-score-r">勝 2分</span><span class="rule-tag tag-rule">和 1分</span><span class="rule-tag tag-out-r">敗 0分</span></div></div>
          <div class="rule-item"><div class="ri-num">3</div><div>預賽採和局制,決賽需出勝負。積分相同時依序比較:① 兩隊勝負關係 ② 總得分較多者 ③ 總失分較少者 ④ 抽籤</div></div>
          <div class="rule-item highlight"><div class="ri-num">★</div><div>突破僵局賽:滿壘開始(第一、二、三棒分站三、二、一壘),第四~六棒打擊,得分多者勝</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">🏃</span> 得分與跑壘</div>
        <div class="rules-list">
          <div class="rule-item highlight"><div class="ri-num">★</div><div>跑壘員在一局結束前,按照 <strong>一壘 → 二壘 → 三壘 → 本壘</strong> 順序觸壘,判得一分</div></div>
          <div class="rule-item"><div class="ri-num">1</div><div>打擊者擊球後成為跑壘員,必須按順序依次跑壘</div></div>
          <div class="rule-item"><div class="ri-num">2</div><div>跑壘員進行滑壘時,<span class="rule-tag tag-out-r">通過二壘、三壘壘包時不得離壘</span>,若離壘可觸殺出局</div></div>
          <div class="rule-item"><div class="ri-num">3</div><div>兩名以上跑壘員不得同時占有同一壘。先上壘者擁有壘權</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">🏏</span> 打擊規則</div>
        <div class="rules-list">
          <div class="rule-item"><div class="ri-num">1</div><div><span class="rule-tag tag-foul">好球</span> 揮棒落空・擊中球座(界外球)・擊成界外球・有觸球意圖・<strong>主軸腳移動兩步以上</strong></div></div>
          <div class="rule-item highlight"><div class="ri-num">★</div><div>打擊者必須在主審發出「開始」口令後 <strong>10秒內</strong> 擊球,否則判好球</div></div>
          <div class="rule-item"><div class="ri-num">2</div><div><span class="rule-tag tag-out-r">打擊者出局</span> 第二個好球後再犯好球條件・界外高飛球被捕接・使用違規球棒・打擊犯規</div></div>
          <div class="rule-item"><div class="ri-num">3</div><div><span class="rule-tag tag-safe">擊球無效</span> 主審已宣布暫停時擊球 → 不計次數,繼續重打</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">📐</span> 界內球 / 界外球</div>
        <div class="rules-list">
          <div class="rule-item"><div class="ri-num">1</div><div><span class="rule-tag tag-safe">界內球</span> 球停在界內區・球從內場彈跳到外場前通過一壘或三壘上空・球觸及一壘或三壘壘包</div></div>
          <div class="rule-item"><div class="ri-num">2</div><div><span class="rule-tag tag-foul">界外球</span> 界內球以外的所有球。落在打擊圈內的球是界外球(但打擊圈線包含在界內球區域)</div></div>
          <div class="rule-item"><div class="ri-num">3</div><div>界內或界外的判定,由<strong>球與邊界線的關係</strong>決定,不是以防守隊員觸球時的位置決定</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">🚫</span> 跑壘員出局情況</div>
        <div class="rules-list">
          <div class="rule-item"><div class="ri-num">1</div><div>跑壘員離開壘包,被防守隊員觸殺時</div></div>
          <div class="rule-item"><div class="ri-num">2</div><div>為了逃避觸殺,跑離了距離壘間線兩側 <strong>1m 以上</strong>的範圍時</div></div>
          <div class="rule-item"><div class="ri-num">3</div><div>高飛球或平飛球被野手捕接住時</div></div>
          <div class="rule-item"><div class="ri-num">4</div><div>跑壘員在跑到一壘之前,防守隊員持球觸碰一壘壘包時</div></div>
          <div class="rule-item"><div class="ri-num">5</div><div>跑壘員超越了尚未出局的前一個跑壘員時</div></div>
          <div class="rule-item highlight"><div class="ri-num">★</div><div>跑壘員進行滑壘時,通過二壘、三壘壘包時不得離壘,若離壘可觸殺出局</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">✅</span> 安全上壘(特殊情況)</div>
        <div class="rules-list">
          <div class="rule-item"><div class="ri-num">1</div><div>本壘手或其他防守隊員妨礙打擊者擊球時 → 打擊者安全上一壘</div></div>
          <div class="rule-item"><div class="ri-num">2</div><div>防守隊員捕球後送球越過界外球界線 → 原有壘基礎上安全上到下一壘</div></div>
          <div class="rule-item"><div class="ri-num">3</div><div>跑壘受到妨礙時 → 原則上安全上到下一壘</div></div>
          <div class="rule-item"><div class="ri-num">4</div><div>防守球員故意用分指手套、合指手套、球帽等拋碰送球 → 安全上到下兩個壘;故意拋碰界內球 → 安全上到下三個壘</div></div>
        </div>
      </div>
      <div class="rules-section">
        <div class="rules-section-title"><span class="rule-icon">👨‍⚖️</span> 裁判員</div>
        <div class="rules-list">
          <div class="rule-item"><div class="ri-num">1</div><div>原則上實行<strong>兩人制</strong>:主審(主判本壘及三壘周圍)+ 壘審(主判一壘及二壘周圍)</div></div>
          <div class="rule-item"><div class="ri-num">2</div><div>壘審位置在一壘手後方之界外線上</div></div>
          <div class="rule-item"><div class="ri-num">3</div><div>主審站在打擊者正側面,在打擊者或本壘手放好球後,宣佈「開始比賽」</div></div>
          <div class="rule-item highlight"><div class="ri-num">★</div><div>只有該隊的<strong>隊長和副隊長</strong>能夠對裁判提出抗議</div></div>
        </div>
      </div>
    </div>
  </div>
  <script>
    let players = [];
    let chart = null;
    let currentTeam = '';

    // ── 粒子背景 ──
    const canvas = document.getElementById('particles-canvas');
    const ctx = canvas.getContext('2d');
    let particles = [];
    function resizeCanvas() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }
    resizeCanvas();
    window.addEventListener('resize', resizeCanvas);
    function createParticle() { return { x: Math.random()*canvas.width, y: Math.random()*canvas.height, vx: (Math.random()-0.5)*0.6, vy: (Math.random()-0.5)*0.6, size: Math.random()*1.8+0.5, opacity: Math.random()*0.6+0.1, color: Math.random()>0.7?'#bc13fe':'#00f3ff' }; }
    for (let i=0;i<120;i++) particles.push(createParticle());
    function drawParticles() {
      ctx.clearRect(0,0,canvas.width,canvas.height);
      if (!document.getElementById('layer1').classList.contains('active')) { requestAnimationFrame(drawParticles); return; }
      particles.forEach((p,i) => {
        p.x+=p.vx; p.y+=p.vy;
        if(p.x<0||p.x>canvas.width) p.vx*=-1;
        if(p.y<0||p.y>canvas.height) p.vy*=-1;
        particles.forEach((q,j) => {
          if(j<=i) return;
          const dx=p.x-q.x, dy=p.y-q.y, dist=Math.sqrt(dx*dx+dy*dy);
          if(dist<120) { ctx.beginPath(); ctx.strokeStyle=`rgba(0,243,255,${0.15*(1-dist/120)})`; ctx.lineWidth=0.5; ctx.moveTo(p.x,p.y); ctx.lineTo(q.x,q.y); ctx.stroke(); }
        });
        ctx.beginPath(); ctx.arc(p.x,p.y,p.size,0,Math.PI*2); ctx.globalAlpha=p.opacity; ctx.fillStyle=p.color; ctx.fill(); ctx.globalAlpha=1;
      });
      requestAnimationFrame(drawParticles);
    }
    drawParticles();

    function toggleLayer1Deco(show) {
      ['scan-line','status-bar'].forEach(id => {
        document.getElementById(id).style.display = show ? '' : 'none';
      });
    }

    function showLayer(n) {
      document.querySelectorAll('.layer').forEach(l => l.classList.remove('active'));
      document.getElementById('layer'+n).classList.add('active');
      toggleLayer1Deco(n===1);
      if(n===2) { document.getElementById('team-search').value=''; renderTeams(); }
      if(n===3) {
        document.getElementById('player-search').value='';
        renderPlayers(currentTeam);
        if(!vsMode) { vsSelected=[]; document.getElementById('vs-btn').classList.add('active'); document.getElementById('vs-btn').textContent='⚔ VERSUS'; document.getElementById('vs-btn').style.background=''; document.getElementById('vs-hint').classList.remove('active'); }
        else { document.getElementById('vs-btn').classList.add('active'); setTimeout(()=>reapplyVsSelected(),50); }
      } else {
        if(vsMode&&n===2) { document.getElementById('vs-btn').classList.add('active'); const hint=document.getElementById('vs-hint'); hint.classList.add('active'); hint.textContent=vsSelected.length===0?'選一個隊伍挑選第一位球員':`✓ ${vsSelected[0]},選一個隊伍挑第二位`; }
        else { document.getElementById('vs-btn').classList.remove('active'); document.getElementById('vs-hint').classList.remove('active'); if(n!==2) vsMode=false; }
      }
      if(n===5) renderLeaderboard();
      if(n===6) initTacticalBoard();
      if(n===7) bbInitScoreboard();
      if(n===9) renderSchedule();
    }

    window.onload = () => {
      toggleLayer1Deco(false);
      document.getElementById('team-search').addEventListener('input', e=>filterTeams(e.target.value.trim()));
      document.getElementById('player-search').addEventListener('input', e=>filterPlayers(e.target.value.trim()));
      google.script.run
        .withSuccessHandler(data => {
          if(!Array.isArray(data)) { showError(data&&data.error?data.error:'資料格式錯誤'); return; }
          players = data;
          document.getElementById('loading').style.display='none';
          showLayer(1);
          renderTeams();
          initTicker();
        })
        .withFailureHandler(err => { showError('連線失敗:'+(err.message||err)); })
        .getPlayerData();
    };

    function showError(msg) { document.getElementById('loading').style.display='none'; document.getElementById('error-text').innerText=msg; document.getElementById('error-msg').style.display='flex'; }
    function escapeHtml(str) { if(!str) return ''; return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;'); }

    function renderTeams() {
      const teams = {}; players.forEach(p=>{ if(!teams[p.team]) teams[p.team]=p.teamLogo; }); buildTeamCards(teams);
    }
    function filterTeams(keyword) {
      const teams={}; players.forEach(p=>{ if(!teams[p.team]) teams[p.team]=p.teamLogo; });
      if(!keyword) { buildTeamCards(teams); return; }
      const filtered={}; Object.keys(teams).forEach(t=>{ if(t.toLowerCase().includes(keyword.toLowerCase())) filtered[t]=teams[t]; }); buildTeamCards(filtered);
    }
    function buildTeamCards(teams) {
      const container=document.getElementById('team-grid'); container.innerHTML='';
      const keys=Object.keys(teams);
      if(keys.length===0) { container.innerHTML='<div class="search-empty">NO TEAMS FOUND</div>'; return; }
      keys.forEach(t => {
        const card=document.createElement('div'); card.className='card';
        card.innerHTML=`<div class="card-inner"><img src="${escapeHtml(teams[t])}" onerror="this.src='https://via.placeholder.com/300'"><h3 style="font-family:'Orbitron'; margin-top:15px;">${escapeHtml(t)}</h3></div>`;
        card.addEventListener('click',()=>{ currentTeam=t; document.getElementById('team-title').innerText=t; document.getElementById('player-search').value=''; renderPlayers(t); renderTeamOverview(t); renderTeamGroupPhoto(t); showLayer(3); });
        container.appendChild(card);
      });
    }

    function renderLeaderboard() {
      const categories=[{key:'pass',label:'傳球王',icon:'🏆',color:'#00f3ff'},{key:'catch',label:'接球達人',icon:'🤲',color:'#bc13fe'},{key:'field',label:'防守鐵壁',icon:'🛡️',color:'#ffd700'},{key:'mobility',label:'速度之星',icon:'⚡',color:'#ff6b35'},{key:'decision',label:'決策大師',icon:'🧠',color:'#00ff88'}];
      const container=document.getElementById('leaderboard-content');
      container.innerHTML=categories.map(cat=>{
        const top3=[...players].sort((a,b)=>b[cat.key]-a[cat.key]).slice(0,3);
        const medals=['🥇','🥈','🥉']; const rankClass=['hall-rank-1','hall-rank-2','hall-rank-3'];
        const order=[top3[1],top3[0],top3[2]].filter(Boolean);
        const orderClass=top3[1]?[rankClass[1],rankClass[0],rankClass[2]]:[rankClass[0],rankClass[2]];
        const orderMedal=top3[1]?[medals[1],medals[0],medals[2]]:[medals[0],medals[2]];
        return `<div class="hall-section"><div class="hall-section-title" style="color:${cat.color};border-color:${cat.color}33;"><span style="font-size:1.4rem;">${cat.icon}</span> ${cat.label}</div><div class="hall-podium">${order.map((p,i)=>`<div class="hall-player ${orderClass[i]}" onclick="goToPlayer('${escapeHtml(p.name)}')"><div class="hall-medal">${orderMedal[i]}</div><img class="hall-player-photo" src="${escapeHtml(p.photo)}" onerror="this.style.opacity='0.2'" alt="${escapeHtml(p.name)}"><div class="hall-player-name">${escapeHtml(p.name)}</div><div class="hall-player-team">${escapeHtml(p.team)}</div><div class="hall-player-score">${p[cat.key]}</div></div>`).join('')}</div></div>`;
      }).join('');
    }

    function goToPlayer(name) {
      const p=players.find(x=>x.name===name); if(!p) return;
      currentTeam=p.team; document.getElementById('team-title').innerText=p.team;
      renderPlayers(p.team); renderTeamOverview(p.team); renderTeamGroupPhoto(p.team); showLayer(3);
      setTimeout(()=>showDetail(name),100);
    }
    function renderTeamGroupPhoto(t) {
      const wrap=document.getElementById('team-group-photo-wrap'); const img=document.getElementById('team-group-photo');
      const tp=players.find(p=>p.team===t&&p.teamPhoto&&p.teamPhoto.trim());
      if(tp) { img.src=tp.teamPhoto; img.onerror=()=>{ wrap.style.display='none'; }; wrap.style.display='block'; } else { wrap.style.display='none'; }
    }
    function renderTeamOverview(t) {
      const tp=players.filter(p=>p.team===t); if(tp.length===0) return;
      const labels=['傳球','接球','守備','行動','決策']; const keys=['pass','catch','field','mobility','decision'];
      const avgs=keys.map(k=>{ const sum=tp.reduce((a,p)=>a+(Number(p[k])||0),0); return Math.round((sum/tp.length)*10)/10; });
      document.getElementById('overview-bars').innerHTML=labels.map((lb,i)=>{ const pct=(avgs[i]/10*100).toFixed(1); return `<div class="bar-item"><div class="bar-label">${lb}</div><div class="bar-track"><div class="bar-fill" style="--bar-w:${pct}%;"></div></div><div class="bar-val">${avgs[i]}</div></div>`; }).join('');
    }
    function renderPlayers(t) { buildPlayerCards(players.filter(p=>p.team===t)); }
    function filterPlayers(keyword) {
      const tp=players.filter(p=>p.team===currentTeam);
      if(!keyword) { buildPlayerCards(tp); return; }
      buildPlayerCards(tp.filter(p=>p.name.toLowerCase().includes(keyword.toLowerCase())||p.position.toLowerCase().includes(keyword.toLowerCase())));
    }
    function getBadges(p) {
      const badges=[];
      if(p.pass>=9) badges.push({cls:'badge-pass',text:'精準砲火 🎯'});
      if(p.catch>=9) badges.push({cls:'badge-catch',text:'穩接高手 🤲'});
      if(p.field>=9) badges.push({cls:'badge-field',text:'銅牆鐵壁 🛡️'});
      if(p.mobility>=9) badges.push({cls:'badge-mobility',text:'枋寮閃電 ⚡'});
      if(p.decision>=9) badges.push({cls:'badge-decision',text:'棋盤大師 🧠'});
      const total=p.pass+p.catch+p.field+p.mobility+p.decision;
      if(total>=45) badges.push({cls:'badge-allstar',text:'全明星 ★'});
      return badges;
    }
    function buildPlayerCards(list) {
      const container=document.getElementById('player-grid'); container.innerHTML='';
      if(list.length===0) { container.innerHTML='<div class="search-empty">NO PLAYERS FOUND</div>'; return; }
      list.forEach(p=>{
        const card=document.createElement('div'); card.className='player-card';
        let photoHtml;
        if(p.photo&&p.photo.trim()!=='') {
          photoHtml=`<img class="player-card-photo" src="${escapeHtml(p.photo)}" alt="${escapeHtml(p.name)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex';"><div class="player-card-no-photo" style="display:none;"><div class="no-photo-icon">👤</div><div class="no-photo-label">NO PHOTO</div></div>`;
        } else {
          photoHtml=`<div class="player-card-no-photo"><div class="no-photo-icon">👤</div><div class="no-photo-label">NO PHOTO</div></div>`;
        }
        const badges=getBadges(p);
        const badgesHtml=badges.length>0?`<div class="player-badges">${badges.map(b=>`<span class="badge-tag ${b.cls}">${b.text}</span>`).join('')}</div>`:'';
        card.innerHTML=`<div class="player-card-badge">${escapeHtml(p.rank)} RANK|${escapeHtml(p.position)}|${escapeHtml(p.name)}</div>${photoHtml}${badgesHtml}`;
        card.addEventListener('click',()=>{ if(vsMode){toggleVsSelect(p.name);return;} showDetail(p.name); });
        card.addEventListener('contextmenu',e=>{e.preventDefault();if(vsMode)toggleVsSelect(p.name);});
        let pressTimer;
        card.addEventListener('touchstart',()=>{pressTimer=setTimeout(()=>{if(vsMode)toggleVsSelect(p.name);},500);});
        card.addEventListener('touchend',()=>clearTimeout(pressTimer));
        card.addEventListener('touchmove',()=>clearTimeout(pressTimer));
        container.appendChild(card);
      });
    }

    function showDetail(name) {
      const p=players.find(x=>x.name===name); if(!p) return;
      const toppsCard=document.getElementById('topps-card'); toppsCard.className='topps-card topps-'+p.tier;
      document.getElementById('det-rank-badge').className='topps-rank-badge rank-'+p.tier; document.getElementById('det-rank-badge').innerText=p.rank;
      document.getElementById('topps-div').className='topps-divider div-'+p.tier;
      document.getElementById('det-photo').src=p.photo; document.getElementById('det-name').innerText=p.name;
      document.getElementById('det-pos').innerText=p.position; document.getElementById('det-slogan').innerText=p.slogan?`" ${p.slogan} "`:'';
      showLayer(4);
      if(chart){chart.destroy();chart=null;}
      document.getElementById('radarChart-wrap').innerHTML='<canvas id="radarChart"></canvas>';
      const isDark=p.tier==='BLACK_GOLD'||p.tier==='NORMAL';
      const mainColor=isDark?'#00f3ff':'#5e00d9'; const gridColor=isDark?'rgba(255,255,255,0.35)':'rgba(0,0,0,0.25)';
      const radarCanvas=document.getElementById('radarChart');
      const grad=radarCanvas.getContext('2d').createRadialGradient(radarCanvas.width/2,radarCanvas.height/2,0,radarCanvas.width/2,radarCanvas.height/2,radarCanvas.width/2);
      if(isDark){grad.addColorStop(0,'rgba(0,243,255,0.9)');grad.addColorStop(0.5,'rgba(0,243,255,0.5)');grad.addColorStop(1,'rgba(0,243,255,0.1)');}
      else{grad.addColorStop(0,'rgba(94,0,217,0.9)');grad.addColorStop(0.5,'rgba(94,0,217,0.5)');grad.addColorStop(1,'rgba(94,0,217,0.1)');}
      const keys=['pass','catch','field','mobility','decision'];
      const leagueAvg=keys.map(k=>{ const sum=players.reduce((a,x)=>a+(Number(x[k])||0),0); return players.length?Math.round(sum/players.length*10)/10:0; });
      chart=new Chart(radarCanvas.getContext('2d'),{type:'radar',data:{labels:['傳球','接球','守備','行動','決策'],datasets:[{label:'全聯賽平均',data:leagueAvg,backgroundColor:'rgba(255,255,255,0.05)',borderColor:'rgba(255,255,255,0.25)',borderWidth:2,borderDash:[4,4],pointBackgroundColor:'rgba(255,255,255,0.3)',pointBorderColor:'transparent',pointRadius:4,fill:true},{label:p.name,data:[p.pass,p.catch,p.field,p.mobility,p.decision],backgroundColor:grad,borderColor:mainColor,borderWidth:6,pointBackgroundColor:mainColor,pointBorderColor:'#fff',pointRadius:7,pointHoverRadius:10,fill:true}]},options:{responsive:true,maintainAspectRatio:false,animation:{duration:1200,easing:'easeOutQuart'},scales:{r:{min:0,max:10,grid:{color:gridColor,lineWidth:2},angleLines:{color:gridColor,lineWidth:2},ticks:{display:false,stepSize:2},pointLabels:{color:mainColor,font:{size:19,weight:'900'},padding:20}}},plugins:{legend:{display:true,position:'bottom',labels:{color:'rgba(255,255,255,0.5)',font:{size:11,family:'Orbitron'},boxWidth:14,padding:12}}}}});
    }

    function initTicker() {
      const msgs=[]; const categories=[{key:'pass',label:'傳球王',icon:'🏆'},{key:'catch',label:'接球達人',icon:'🤲'},{key:'field',label:'防守鐵壁',icon:'🛡️'},{key:'mobility',label:'速度之星',icon:'⚡'},{key:'decision',label:'決策大師',icon:'🧠'}];
      categories.forEach(cat=>{ const top3=[...players].sort((a,b)=>b[cat.key]-a[cat.key]).slice(0,3); if(top3[0]){const names=top3.map((p,i)=>`${'🥇🥈🥉'[i]} ${p.name}(${p[cat.key]}分)`).join(' · '); msgs.push(`${cat.icon} ${cat.label}排行:${names}`);}});
      const ranked=[...players].sort((a,b)=>{const avg=p=>p.pass+p.catch+p.field+p.mobility+p.decision;return avg(b)-avg(a);});
      if(ranked[0]) msgs.push(`🌟 本屆綜合 MVP:${ranked[0].name}(${ranked[0].team})總分 ${ranked[0].pass+ranked[0].catch+ranked[0].field+ranked[0].mobility+ranked[0].decision} 分`);
      if(ranked.length>=3) msgs.push(`🔥 頂尖三強:${ranked.slice(0,3).map(p=>p.name).join(' · ')} 稱霸全聯賽排行榜`);
      document.getElementById('ticker-text').textContent=msgs.map(m=>`◈ ${m} `).join('   ');
      document.getElementById('live-ticker').classList.add('active');
    }

    // ── Versus ──
    let vsSelected=[], vsMode=false;
    function reapplyVsSelected() { document.querySelectorAll('.player-card').forEach(card=>{ const badge=card.querySelector('.player-card-badge'); if(!badge) return; const cardName=badge.textContent.split('|')[2]||''; card.classList.toggle('vs-selected',vsSelected.includes(cardName.trim())); }); }
    function toggleVsMode() {
      vsMode=!vsMode; vsSelected=[]; document.querySelectorAll('.player-card').forEach(c=>c.classList.remove('vs-selected'));
      const btn=document.getElementById('vs-btn'); const hint=document.getElementById('vs-hint');
      if(vsMode){btn.textContent='✕ 取消';btn.style.background='linear-gradient(135deg,#555,#333)';hint.textContent='點選第一位球員';hint.classList.add('active');}
      else{btn.textContent='⚔ VERSUS';btn.style.background='';hint.classList.remove('active');}
    }
    function toggleVsSelect(name) {
      if(!vsMode) return; const idx=vsSelected.indexOf(name);
      if(idx>-1) vsSelected.splice(idx,1); else { if(vsSelected.length>=2) return; vsSelected.push(name); }
      document.querySelectorAll('.player-card').forEach(card=>{ const badge=card.querySelector('.player-card-badge'); if(!badge) return; const cardName=badge.textContent.split('|')[2]||''; card.classList.toggle('vs-selected',vsSelected.includes(cardName.trim())); });
      const hint=document.getElementById('vs-hint');
      if(vsSelected.length===0) hint.textContent='點選第一位球員';
      else if(vsSelected.length===1) hint.textContent=`✓ ${vsSelected[0]},再選一位`;
      else { hint.textContent='對戰開始!'; setTimeout(()=>triggerVersus(),400); }
    }
    function triggerVersus() {
      if(vsSelected.length<2){document.getElementById('vs-hint').textContent='請先點選兩名球員!';return;}
      const pa=players.find(p=>p.name===vsSelected[0]); const pb=players.find(p=>p.name===vsSelected[1]);
      if(!pa||!pb) return;
      const keys=['pass','catch','field','mobility','decision']; const labels=['傳球','接球','守備','行動','決策'];
      const scoreA=keys.reduce((s,k)=>s+pa[k],0); const scoreB=keys.reduce((s,k)=>s+pb[k],0);
      const total=scoreA+scoreB||1; const pctA=Math.round(scoreA/total*100); const pctB=100-pctA; const isAWin=pctA>=pctB;
      document.getElementById('vs-img-a').src=pa.photo; document.getElementById('vs-name-a').textContent=pa.name; document.getElementById('vs-rank-a').textContent=pa.rank+' · '+pa.team;
      document.getElementById('vs-img-b').src=pb.photo; document.getElementById('vs-name-b').textContent=pb.name; document.getElementById('vs-rank-b').textContent=pb.rank+' · '+pb.team;
      document.getElementById('vs-card-img-a').style.borderColor='#ffd700'; document.getElementById('vs-card-img-b').style.borderColor='#bc13fe';
      const cardA=document.getElementById('vs-card-a'); const cardB=document.getElementById('vs-card-b'); const stage=document.getElementById('vs-stage'); const result=document.getElementById('vs-result'); const flash=document.getElementById('vs-flash'); const explo=document.getElementById('vs-explosion');
      cardA.className='vs-card-wrap card-a'; cardB.className='vs-card-wrap card-b'; cardA.style.opacity='1'; cardB.style.opacity='1'; result.classList.remove('show'); flash.classList.remove('bang'); explo.innerHTML=''; stage.classList.remove('shaking');
      document.getElementById('vs-overlay').classList.add('active');
      setTimeout(()=>{ cardA.classList.add('sliding'); cardB.classList.add('sliding'); },150);
      setTimeout(()=>{
        stage.classList.add('shaking'); setTimeout(()=>stage.classList.remove('shaking'),420); flash.classList.add('bang'); explo.innerHTML='';
        const colors=['#ffd700','#ff6600','#bc13fe','#00f3ff','#fff','#ff3366'];
        for(let i=0;i<28;i++){const s=document.createElement('div');s.className='vs-spark';const angle=(360/28)*i;const dist=80+Math.random()*120;const color=colors[Math.floor(Math.random()*colors.length)];s.style.cssText=`background:${color};box-shadow:0 0 6px ${color};--angle:${angle}deg;--dist:${dist}px;animation:spark-fly ${0.5+Math.random()*0.4}s ease-out forwards;`;explo.appendChild(s);}
        setTimeout(()=>{cardA.style.transition='all 0.4s ease';cardB.style.transition='all 0.4s ease';cardA.style.opacity='0';cardB.style.opacity='0';},300);
        setTimeout(()=>{
          document.getElementById('vs-rimg-a').src=pa.photo; document.getElementById('vs-rname-a').textContent=pa.name; document.getElementById('vs-rimg-b').src=pb.photo; document.getElementById('vs-rname-b').textContent=pb.name;
          const rfA=document.getElementById('vs-rf-a'); const rfB=document.getElementById('vs-rf-b');
          rfA.className='vs-result-fighter '+(isAWin?'winner':'loser'); rfB.className='vs-result-fighter '+(!isAWin?'winner':'loser');
          document.getElementById('vs-crown-a').textContent=isAWin?'🏆':''; document.getElementById('vs-crown-b').textContent=!isAWin?'🏆':'';
          document.getElementById('vs-rpct-a').innerHTML=isAWin?`<div class="vs-winner-pct">${pctA}%</div>`:`<div class="vs-loser-pct">${pctA}%</div>`;
          document.getElementById('vs-rpct-b').innerHTML=!isAWin?`<div class="vs-winner-pct">${pctB}%</div>`:`<div class="vs-loser-pct">${pctB}%</div>`;
          document.getElementById('vs-bar-track').innerHTML=`<div class="vs-bar-a" style="--pct-a:${pctA}%;"></div><div class="vs-bar-b" style="--pct-b:${pctB}%;"></div>`;
          document.getElementById('vs-detail').innerHTML=keys.map((k,i)=>{const aVal=pa[k],bVal=pb[k];const aStyle=aVal>bVal?'style="font-size:1.05rem;color:#ffd700;"':'';const bStyle=bVal>aVal?'style="font-size:1.05rem;color:#e0a0ff;"':'';return `<div class="vs-detail-a" ${aStyle}>${aVal}</div><div class="vs-detail-label">${labels[i]}</div><div class="vs-detail-b" ${bStyle}>${bVal}</div>`;}).join('');
          result.classList.add('show');
        },700);
      },1100);
    }
    function closeVersus() {
      document.getElementById('vs-overlay').classList.remove('active'); document.getElementById('vs-result').classList.remove('show');
      vsMode=false; vsSelected=[]; document.querySelectorAll('.player-card').forEach(c=>c.classList.remove('vs-selected'));
      const btn=document.getElementById('vs-btn'); btn.textContent='⚔ VERSUS'; btn.style.background=''; document.getElementById('vs-hint').classList.remove('active');
    }

    // ── 抽卡包 ──
    let packBusy = false;
    function showPackOverlay() { resetPack(); document.getElementById('pack-overlay').classList.add('active'); }
    function openPack() {
      if(packBusy) return;
      const icon=document.getElementById('pack-icon'); if(icon.classList.contains('shaking')) return;
      packBusy = true;
      icon.classList.add('shaking');
      setTimeout(()=>{ icon.classList.remove('shaking'); icon.classList.add('exploding'); setTimeout(()=>{ document.getElementById('pack-icon-wrap').style.display='none'; revealRandomPlayer(); packBusy = false; },380); },820);
    }
    function revealRandomPlayer() {
      if(!players.length) return;
      const weighted=[]; players.forEach(p=>{ const w=p.tier==='BLACK_GOLD'?1:p.tier==='PLATINUM'?2:p.tier==='GOLD_CARD'?3:4; for(let i=0;i<w;i++) weighted.push(p); });
      const p=weighted[Math.floor(Math.random()*weighted.length)];
      const img=document.getElementById('pack-card-img'); const flip=document.getElementById('pack-card-flip'); const wrap=document.getElementById('pack-card-wrap');
      flip.classList.remove('flipped'); img.className='pack-card-img'; flip.querySelectorAll('.pack-holo-overlay').forEach(el=>el.remove());
      img.src=p.photo||''; img.style.borderColor=p.tier==='BLACK_GOLD'?'#d4af37':p.tier==='PLATINUM'?'#e0e0e0':'#ffd700';
      img.style.boxShadow=p.tier==='BLACK_GOLD'?'0 0 50px rgba(212,175,55,0.9)':p.tier==='PLATINUM'?'0 0 50px rgba(200,200,220,0.8)':'0 0 40px rgba(255,215,0,0.6)';
      document.getElementById('pack-card-name').textContent=p.name; document.getElementById('pack-card-info').textContent=`${p.rank} RANK · ${p.position} · ${p.team}`;
      if(p.tier==='BLACK_GOLD'||p.tier==='PLATINUM'){const holoColor=p.tier==='BLACK_GOLD'?'rgba(255,215,0,0.4) 44%, rgba(0,243,255,0.4) 50%, rgba(255,150,0,0.35) 56%':'rgba(220,220,220,0.4) 44%, rgba(180,180,255,0.45) 50%, rgba(188,19,254,0.35) 56%';const overlay=document.createElement('div');overlay.className='pack-holo-overlay';overlay.style.cssText=`position:absolute;inset:0;border-radius:16px;pointer-events:none;background:linear-gradient(105deg,transparent 35%,${holoColor},transparent 65%);background-size:200% 100%;animation:holo-sweep 2s ease-in-out infinite;`;img.insertAdjacentElement('afterend',overlay);}
      wrap.classList.add('show'); document.getElementById('pack-card-name').style.opacity='0'; document.getElementById('pack-card-info').style.opacity='0';
      flip.onclick=(e)=>{ e.stopPropagation(); if(flip.classList.contains('flipped')) return; flip.classList.add('flipped'); setTimeout(()=>{ document.getElementById('pack-card-name').style.opacity='1'; document.getElementById('pack-card-info').style.opacity='1'; },750); };
    }
    function resetPack() {
      document.getElementById('pack-icon-wrap').style.display=''; const icon=document.getElementById('pack-icon'); icon.classList.remove('shaking','exploding');
      const flip=document.getElementById('pack-card-flip'); flip.classList.remove('flipped'); flip.style.transform=''; flip.style.animation=''; flip.onclick=null; flip.querySelectorAll('.pack-holo-overlay').forEach(el=>el.remove());
      const wrap=document.getElementById('pack-card-wrap'); wrap.classList.remove('show'); document.getElementById('pack-card-name').style.opacity='1'; document.getElementById('pack-card-info').style.opacity='1';
    }

    // ════════════════════════════════════════
    //  戰術板邏輯
    // ════════════════════════════════════════
    let tacCanvas, tacCtx;
    let tacTool = 'pen', tacColor = '#ff3333', tacSize = 4;
    let tacDrawing = false, tacStartX, tacStartY;
    let tacHistory = [], tacCurrentPath = [];
    let tacSnapshot = null;
    let tacFieldMode = 'full';
    let tacInited = false;

    function initTacticalBoard() {
      tacCanvas = document.getElementById('tactical-canvas');
      const wrap = tacCanvas.parentElement;
      tacCanvas.width = wrap.clientWidth;
      tacCanvas.height = wrap.clientHeight;
      tacCtx = tacCanvas.getContext('2d');
      tacHistory = [];
      drawBaseballField('full');
      if (!tacInited) {
        tacInited = true;
        tacCanvas.addEventListener('mousedown', tacPointerDown);
        tacCanvas.addEventListener('mousemove', tacPointerMove);
        tacCanvas.addEventListener('mouseup', tacPointerUp);
        tacCanvas.addEventListener('mouseleave', tacPointerUp);
        tacCanvas.addEventListener('touchstart', e=>{e.preventDefault();tacPointerDown(e.touches[0]);},{passive:false});
        tacCanvas.addEventListener('touchmove', e=>{e.preventDefault();tacPointerMove(e.touches[0]);},{passive:false});
        tacCanvas.addEventListener('touchend', e=>{e.preventDefault();tacPointerUp();},{passive:false});
      }
    }
    function drawBaseballField(mode) {
      tacFieldMode = mode;
      const w = tacCanvas.width, h = tacCanvas.height;
      const c = tacCtx;
      c.clearRect(0,0,w,h);
      if (mode === 'infield') { drawInfieldClose(w, h, c); } else { drawFullField(w, h, c); }
      tacSnapshot = c.getImageData(0,0,w,h);
      tacHistory = [];
    }
    function drawStickFigure(c, x, y, sz, color) {
      color = color || '#fff';
      c.strokeStyle = color; c.fillStyle = color; c.lineWidth = Math.max(1.5, sz * 0.18);
      c.beginPath(); c.arc(x, y, sz * 0.22, 0, Math.PI*2); c.fill();
      c.beginPath(); c.moveTo(x, y + sz*0.22); c.lineTo(x, y + sz*0.7); c.stroke();
      c.beginPath(); c.moveTo(x - sz*0.3, y + sz*0.38); c.lineTo(x + sz*0.3, y + sz*0.38); c.stroke();
      c.beginPath(); c.moveTo(x, y + sz*0.7); c.lineTo(x - sz*0.25, y + sz); c.stroke();
      c.beginPath(); c.moveTo(x, y + sz*0.7); c.lineTo(x + sz*0.25, y + sz); c.stroke();
    }
    function drawFielderLabel(c, x, y, label, sz) {
      drawStickFigure(c, x, y, sz, '#fff');
      const labelY = y + sz * 1.22;
      c.font = `bold ${Math.max(10, sz*0.85)}px Noto Sans TC,sans-serif`;
      c.textAlign = 'center'; c.textBaseline = 'top';
      const tw = c.measureText(label).width;
      c.fillStyle = 'rgba(0,0,0,0.65)'; c.fillRect(x - tw/2 - 4, labelY - 1, tw + 8, Math.max(12, sz*0.9) + 2);
      c.fillStyle = '#fff'; c.fillText(label, x, labelY);
      c.textAlign = 'left'; c.textBaseline = 'alphabetic';
    }
    function drawFullField(w, h, c) {
      const cx = w / 2;
      const homeY = h * 0.88;
      const bd = Math.min(w, h) * 0.30;
      const baseH = { x: cx, y: homeY };
      const base1 = { x: cx + bd, y: homeY - bd };
      const base2 = { x: cx, y: homeY - bd*2 };
      const base3 = { x: cx - bd, y: homeY - bd };
      c.fillStyle = '#6abf40';
      c.fillRect(0, 0, w, h);
      const foulLineLen = Math.max(w, h) * 1.2;
      const arcR = bd * 2.15;
      const leftAngle  = Math.atan2(base3.y - baseH.y, base3.x - baseH.x) + Math.PI;
      const rightAngle = Math.atan2(base1.y - baseH.y, base1.x - baseH.x);
      c.save();
      c.beginPath();
      c.moveTo(baseH.x, baseH.y);
      c.lineTo(baseH.x + Math.cos(leftAngle + Math.PI) * foulLineLen, baseH.y + Math.sin(leftAngle + Math.PI) * foulLineLen);
      c.arc(baseH.x, baseH.y, foulLineLen, leftAngle + Math.PI, rightAngle, false);
      c.closePath();
      c.fillStyle = '#5aaf30';
      c.fill();
      c.restore();
      c.save();
      c.beginPath();
      c.moveTo(baseH.x, baseH.y);
      c.lineTo(baseH.x - foulLineLen, baseH.y - foulLineLen);
      c.arc(baseH.x, baseH.y, arcR, Math.PI * 1.25, Math.PI * 1.75, false);
      c.lineTo(baseH.x, baseH.y);
      c.closePath();
      c.fillStyle = '#6abf40';
      c.fill();
      c.restore();
      c.beginPath(); c.moveTo(baseH.x, baseH.y); c.lineTo(base1.x, base1.y); c.lineTo(base2.x, base2.y); c.lineTo(base3.x, base3.y); c.closePath();
      c.fillStyle = '#d4622a'; c.fill(); c.strokeStyle = '#000'; c.lineWidth = 2; c.stroke();
      c.beginPath(); c.arc(baseH.x, baseH.y, arcR, Math.PI * 1.22, Math.PI * 1.78, false); c.strokeStyle = '#000'; c.lineWidth = 3; c.stroke();
      c.strokeStyle='#000'; c.lineWidth=2;
      const lx = baseH.x + Math.cos(Math.PI * 1.25) * foulLineLen;
      const ly = baseH.y + Math.sin(Math.PI * 1.25) * foulLineLen;
      c.beginPath(); c.moveTo(baseH.x, baseH.y); c.lineTo(lx, ly); c.stroke();
      const rx = baseH.x + Math.cos(Math.PI * 1.75) * foulLineLen;
      const ry = baseH.y + Math.sin(Math.PI * 1.75) * foulLineLen;
      c.beginPath(); c.moveTo(baseH.x, baseH.y); c.lineTo(rx, ry); c.stroke();
      const pitchX = cx, pitchY = homeY - bd;
      c.beginPath(); c.ellipse(pitchX, pitchY, bd*0.13, bd*0.11, 0, 0, Math.PI*2); c.fillStyle = '#c45820'; c.fill(); c.strokeStyle='#000'; c.lineWidth=1; c.stroke();
      c.fillStyle='#fff'; c.fillRect(pitchX-bd*0.06, pitchY-bd*0.025, bd*0.12, bd*0.05);
      c.strokeStyle='#fff'; c.lineWidth=2;
      [[baseH,base1],[base1,base2],[base2,base3],[base3,baseH]].forEach(([a,b])=>{ c.beginPath(); c.moveTo(a.x,a.y); c.lineTo(b.x,b.y); c.stroke(); });
      c.beginPath(); c.arc(baseH.x, baseH.y, bd*0.22, Math.PI, 0, false); c.strokeStyle='rgba(0,0,0,0.5)'; c.lineWidth=1.5; c.setLineDash([4,4]); c.stroke(); c.setLineDash([]);
      const bsz_full = Math.max(9, bd * 0.075);
      drawFieldBase(c, base2.x, base2.y, bsz_full); drawFieldBase(c, base1.x, base1.y, bsz_full); drawFieldBase(c, base3.x, base3.y, bsz_full);
      drawHomeBase(c, baseH.x, baseH.y, bsz_full * 1.1);
      // ── 守備位置人形標記 ──
      const fsz = Math.max(12, bd * 0.11);
      drawFielderLabel(c, cx,            homeY - bd*2.75, '中外野手', fsz);
      drawFielderLabel(c, cx + bd*1.45,  homeY - bd*2.15, '右外野手', fsz);
      drawFielderLabel(c, cx - bd*1.45,  homeY - bd*2.15, '左外野手', fsz);
      drawFielderLabel(c, cx + bd*0.32,   homeY - bd*1.85, '二壘手',   fsz);
      drawFielderLabel(c, cx - bd*0.32,   homeY - bd*1.85, '游擊手',   fsz);
      drawFielderLabel(c, cx + bd*0.88,   homeY - bd*1.48, '一壘手',   fsz);
      drawFielderLabel(c, cx - bd*0.88,   homeY - bd*1.48, '三壘手',   fsz);
      drawFielderLabel(c, pitchX,         pitchY,          '投手',     fsz);
      drawFielderLabel(c, cx,             homeY + bd*0.08, '捕手',     fsz);
    }
    function drawInfieldClose(w, h, c) {
      const cx = w / 2; const homeY = h * 0.88; const bd = Math.min(w, h) * 0.38;
      const baseH = { x: cx, y: homeY }; const base1 = { x: cx + bd, y: homeY - bd };
      const base2 = { x: cx, y: homeY - bd*2 }; const base3 = { x: cx - bd, y: homeY - bd };
      c.fillStyle = '#6abf40'; c.fillRect(0,0,w,h);
      const foulLineLen = Math.max(w,h)*1.2;
      c.strokeStyle='#000'; c.lineWidth=2;
      c.beginPath(); c.moveTo(baseH.x,baseH.y); c.lineTo(baseH.x+Math.cos(Math.PI*1.25)*foulLineLen, baseH.y+Math.sin(Math.PI*1.25)*foulLineLen); c.stroke();
      c.beginPath(); c.moveTo(baseH.x,baseH.y); c.lineTo(baseH.x+Math.cos(Math.PI*1.75)*foulLineLen, baseH.y+Math.sin(Math.PI*1.75)*foulLineLen); c.stroke();
      c.beginPath(); c.moveTo(baseH.x, baseH.y); c.lineTo(base1.x, base1.y); c.lineTo(base2.x, base2.y); c.lineTo(base3.x, base3.y); c.closePath();
      c.fillStyle='#d4622a'; c.fill(); c.strokeStyle='#000'; c.lineWidth=2; c.stroke();
      const pitchX=cx, pitchY=homeY-bd;
      c.beginPath(); c.ellipse(pitchX, pitchY, bd*0.13, bd*0.1, 0, 0, Math.PI*2); c.fillStyle='#c45820'; c.fill(); c.strokeStyle='#000'; c.lineWidth=1; c.stroke();
      c.fillStyle='#fff'; c.fillRect(pitchX-bd*0.07, pitchY-bd*0.03, bd*0.14, bd*0.06);
      c.strokeStyle='#fff'; c.lineWidth=2;
      [[baseH,base1],[base1,base2],[base2,base3],[base3,baseH]].forEach(([a,b])=>{ c.beginPath(); c.moveTo(a.x,a.y); c.lineTo(b.x,b.y); c.stroke(); });
      c.beginPath(); c.arc(baseH.x, baseH.y, bd*0.22, Math.PI, 0, false); c.strokeStyle='rgba(0,0,0,0.5)'; c.lineWidth=1.5; c.setLineDash([4,4]); c.stroke(); c.setLineDash([]);
      const bsz = Math.max(11, bd * 0.09);
      drawFieldBase(c, base2.x, base2.y, bsz); drawFieldBase(c, base1.x, base1.y, bsz); drawFieldBase(c, base3.x, base3.y, bsz);
      drawHomeBase(c, baseH.x, baseH.y, bsz*1.1);
      const ifsz = Math.max(11, bd * 0.09);
      drawFielderLabel(c, cx + bd*0.32,   homeY - bd*1.85, '二壘手', ifsz);
      drawFielderLabel(c, cx - bd*0.32,   homeY - bd*1.85, '游擊手', ifsz);
      drawFielderLabel(c, cx + bd*0.88,   homeY - bd*1.48, '一壘手', ifsz);
      drawFielderLabel(c, cx - bd*0.88,   homeY - bd*1.48, '三壘手', ifsz);
      drawFielderLabel(c, pitchX,         pitchY,          '投手',   ifsz);
      drawFielderLabel(c, cx,             homeY + bd*0.08, '捕手',   ifsz);
    }
    function drawFieldBase(c, x, y, sz) {
      c.save(); c.translate(x, y); c.rotate(Math.PI / 4);
      c.fillStyle = '#e07820'; c.fillRect(-sz*1.15, -sz*1.15, sz*2.3, sz*2.3); c.strokeStyle = '#000'; c.lineWidth = 1.5; c.strokeRect(-sz*1.15, -sz*1.15, sz*2.3, sz*2.3);
      c.fillStyle = '#fff'; c.fillRect(-sz*0.55, -sz*0.55, sz*1.1, sz*1.1); c.strokeStyle = 'rgba(0,0,0,0.4)'; c.lineWidth = 1; c.strokeRect(-sz*0.55, -sz*0.55, sz*1.1, sz*1.1);
      c.restore();
    }
    function drawHomeBase(c, x, y, sz) {
      c.save(); c.translate(x, y); c.rotate(Math.PI / 4);
      c.fillStyle = '#cc2200'; c.fillRect(-sz*1.15, -sz*1.15, sz*2.3, sz*2.3); c.strokeStyle = '#000'; c.lineWidth = 1.5; c.strokeRect(-sz*1.15, -sz*1.15, sz*2.3, sz*2.3);
      c.fillStyle = '#fff'; c.fillRect(-sz*0.55, -sz*0.55, sz*1.1, sz*1.1); c.strokeStyle = 'rgba(0,0,0,0.4)'; c.lineWidth = 1; c.strokeRect(-sz*0.55, -sz*0.55, sz*1.1, sz*1.1);
      c.restore();
    }
    function getTacPos(e) {
      const rect = tacCanvas.getBoundingClientRect();
      return { x:(e.clientX-rect.left)*(tacCanvas.width/rect.width), y:(e.clientY-rect.top)*(tacCanvas.height/rect.height) };
    }
    // ── 文字工具:浮動預覽 ──
    let tacFloatingText = null; // { text, x, y }

    function startFloatingText() {
      // 先跳出輸入框取得文字
      const wrap = document.getElementById('tac-text-input-wrap');
      wrap.style.display = 'flex';
      document.getElementById('tac-text-input').value = '';
      document.getElementById('tac-text-input').focus();
    }

    function confirmFloatingText() {
      const val = document.getElementById('tac-text-input').value.trim();
      document.getElementById('tac-text-input-wrap').style.display = 'none';
      if(!val) { tacFloatingText = null; return; }
      tacFloatingText = { text: val, x: tacCanvas.width/2, y: tacCanvas.height/2 };
      tacCanvas.style.cursor = 'move';
      renderFloatingText();
    }

    function renderFloatingText() {
      if(!tacFloatingText) return;
      // 重繪底圖再疊文字預覽
      if(tacSnapshot) tacCtx.putImageData(tacSnapshot, 0, 0);
      const fontSize = tacSize*4+12;
      tacCtx.font = `bold ${fontSize}px Noto Sans TC,sans-serif`;
      tacCtx.shadowBlur = 0;
      tacCtx.globalAlpha = 0.7;
      tacCtx.strokeStyle = '#fff';
      tacCtx.lineWidth = 4;
      tacCtx.strokeText(tacFloatingText.text, tacFloatingText.x, tacFloatingText.y);
      tacCtx.fillStyle = '#000';
      tacCtx.fillText(tacFloatingText.text, tacFloatingText.x, tacFloatingText.y);
      tacCtx.globalAlpha = 1;
    }

    function placeFloatingText(x, y) {
      if(!tacFloatingText) return;
      tacFloatingText.x = x; tacFloatingText.y = y;
      // 正式繪製
      if(tacSnapshot) tacCtx.putImageData(tacSnapshot, 0, 0);
      const fontSize = tacSize*4+12;
      tacCtx.font = `bold ${fontSize}px Noto Sans TC,sans-serif`;
      tacCtx.shadowBlur = 0;
      tacCtx.strokeStyle = '#fff';
      tacCtx.lineWidth = 4;
      tacCtx.strokeText(tacFloatingText.text, x, y);
      tacCtx.fillStyle = '#000';
      tacCtx.fillText(tacFloatingText.text, x, y);
      saveHistory();
      tacFloatingText = null;
      tacCanvas.style.cursor = 'crosshair';
    }

    function tacPointerDown(e) {
      const {x,y} = getTacPos(e);

      // 若有浮動文字,點擊確認放置
      if(tacFloatingText) { placeFloatingText(x, y); return; }

      tacDrawing=true; tacStartX=x; tacStartY=y; tacCurrentPath=[{x,y}];
      if(tacTool==='text'){
        tacDrawing=false;
        startFloatingText();
        return;
      }
      if(['batter','runner','fielder','ball','cone'].includes(tacTool)){ tacDrawing=false; drawBBShape(x,y); saveHistory(); return; }
      if(tacTool==='pen'){ tacCtx.beginPath(); tacCtx.moveTo(x,y); tacCtx.strokeStyle=tacColor; tacCtx.lineWidth=tacSize; tacCtx.lineCap='round'; tacCtx.lineJoin='round'; tacCtx.shadowColor=tacColor; tacCtx.shadowBlur=tacSize*1.5; }
    }
    function tacPointerMove(e) {
      // 浮動文字跟隨滑鼠
      if(tacFloatingText) {
        const {x,y} = getTacPos(e);
        tacFloatingText.x = x; tacFloatingText.y = y;
        renderFloatingText();
        return;
      }
      if(!tacDrawing) return;
      const {x,y}=getTacPos(e); tacCurrentPath.push({x,y});
      if(tacTool==='pen'){ tacCtx.lineTo(x,y); tacCtx.stroke(); tacCtx.shadowBlur=0; }
      else if(tacTool==='erase'){ tacCtx.clearRect(x-tacSize*3,y-tacSize*3,tacSize*6,tacSize*6); }
      else if(tacTool==='arrow'){ tacCtx.putImageData(tacSnapshot,0,0); drawArrow(tacStartX,tacStartY,x,y,tacColor,tacSize); }
    }
    function tacPointerUp() {
      if(!tacDrawing) return; tacDrawing=false;
      if(tacTool==='arrow'&&tacCurrentPath.length>1){ const last=tacCurrentPath[tacCurrentPath.length-1]; drawArrow(tacStartX,tacStartY,last.x,last.y,tacColor,tacSize); }
      tacCtx.shadowBlur=0; saveHistory();
    }
    function drawArrow(x1,y1,x2,y2,color,size) {
      const c=tacCtx, headLen=size*5+10, angle=Math.atan2(y2-y1,x2-x1);
      c.beginPath(); c.moveTo(x1,y1); c.lineTo(x2,y2); c.strokeStyle=color; c.lineWidth=size; c.lineCap='round'; c.shadowColor=color; c.shadowBlur=size*1.5; c.stroke();
      c.beginPath(); c.moveTo(x2,y2); c.lineTo(x2-headLen*Math.cos(angle-Math.PI/6),y2-headLen*Math.sin(angle-Math.PI/6)); c.lineTo(x2-headLen*Math.cos(angle+Math.PI/6),y2-headLen*Math.sin(angle+Math.PI/6)); c.closePath(); c.fillStyle=color; c.fill(); c.shadowBlur=0;
    }
    function drawBBShape(x,y) {
      const c=tacCtx; const r=tacSize*3+13; c.shadowBlur=14;
      if(tacTool==='batter'){ c.shadowColor='#ffd700'; c.beginPath(); c.arc(x,y,r,0,Math.PI*2); c.fillStyle='rgba(220,160,0,0.9)'; c.fill(); c.strokeStyle='#fff'; c.lineWidth=2; c.stroke(); c.fillStyle='#000'; c.font=`bold ${r}px sans-serif`; c.textAlign='center'; c.textBaseline='middle'; c.fillText('打',x,y); }
      else if(tacTool==='runner'){ c.shadowColor='#00aaff'; c.beginPath(); c.arc(x,y,r,0,Math.PI*2); c.fillStyle='rgba(0,100,220,0.9)'; c.fill(); c.strokeStyle='#00f3ff'; c.lineWidth=2; c.stroke(); c.fillStyle='#fff'; c.font=`bold ${r}px sans-serif`; c.textAlign='center'; c.textBaseline='middle'; c.fillText('跑',x,y); }
      else if(tacTool==='fielder'){ c.shadowColor='#ff4444'; c.beginPath(); c.arc(x,y,r,0,Math.PI*2); c.fillStyle='rgba(200,20,20,0.9)'; c.fill(); c.strokeStyle='#ff8888'; c.lineWidth=2; c.stroke(); c.fillStyle='#fff'; c.font=`bold ${r}px sans-serif`; c.textAlign='center'; c.textBaseline='middle'; c.fillText('守',x,y); }
      else if(tacTool==='ball'){
        // 橘色樂樂棒球
        const br = r * 0.75;
        // 球體漸層
        const grad = c.createRadialGradient(x - br*0.3, y - br*0.3, br*0.05, x, y, br);
        grad.addColorStop(0, '#ffb060');
        grad.addColorStop(0.5, '#ff7a20');
        grad.addColorStop(1, '#cc4a00');
        c.beginPath(); c.arc(x, y, br, 0, Math.PI*2);
        c.fillStyle = grad; c.fill();
        c.strokeStyle = 'rgba(0,0,0,0.25)'; c.lineWidth = 1.5; c.stroke();
        // 縫線(橘色球上的深色壓紋線條)
        c.strokeStyle = 'rgba(150,40,0,0.5)'; c.lineWidth = 1.2;
        // 上弧縫線
        c.beginPath(); c.arc(x, y - br*0.15, br*0.7, Math.PI*1.15, Math.PI*1.85, false); c.stroke();
        // 下弧縫線
        c.beginPath(); c.arc(x, y + br*0.15, br*0.7, Math.PI*0.15, Math.PI*0.85, false); c.stroke();
      }
      else if(tacTool==='cone'){ c.shadowColor='#ff8800'; c.beginPath(); c.moveTo(x,y-r); c.lineTo(x+r*0.8,y+r*0.6); c.lineTo(x-r*0.8,y+r*0.6); c.closePath(); c.fillStyle='#ff8800'; c.fill(); c.strokeStyle='#ffcc00'; c.lineWidth=2; c.stroke(); }
      c.shadowBlur=0; c.textAlign='left'; c.textBaseline='alphabetic';
    }
    function saveHistory() { tacHistory.push(tacCtx.getImageData(0,0,tacCanvas.width,tacCanvas.height)); if(tacHistory.length>30) tacHistory.shift(); tacSnapshot=tacCtx.getImageData(0,0,tacCanvas.width,tacCanvas.height); }
    function undoTactical() { if(tacHistory.length===0){drawBaseballField(tacFieldMode);return;} tacHistory.pop(); if(tacHistory.length>0){ tacCtx.putImageData(tacHistory[tacHistory.length-1],0,0); tacSnapshot=tacCtx.getImageData(0,0,tacCanvas.width,tacCanvas.height); } else drawBaseballField(tacFieldMode); }
    function clearTactical() { tacHistory=[]; drawBaseballField(tacFieldMode); }
    function setTacTool(tool) { tacTool=tool; document.querySelectorAll('.tac-tool-btn,.tac-shape-btn').forEach(b=>b.classList.remove('active')); const btn=document.getElementById('tool-'+tool); if(btn) btn.classList.add('active'); if(tacCanvas) tacCanvas.style.cursor=tool==='erase'?'cell':tool==='text'?'text':'crosshair'; }
    function setTacColor(color, el) { tacColor=color; document.querySelectorAll('.tac-color-btn').forEach(b=>b.classList.remove('active')); if(el) el.classList.add('active'); }
    function setTacSize(size, el) { tacSize=size; document.querySelectorAll('.tac-size-btn').forEach(b=>b.classList.remove('active')); if(el) el.classList.add('active'); }

    // ════════════════════════════════════════
    //  計分板邏輯 - 全自動版
    // ════════════════════════════════════════
    const BB_INN = 7;
    let bbHomeScore=0, bbAwayScore=0;
    let bbCurInn=1;
    let bbInnScores={ home:new Array(BB_INN).fill(null), away:new Array(BB_INN).fill(null) };
    let bbStrikes=0, bbOuts=0;
    let bbBases={ '1st':null, '2nd':null, '3rd':null };
    let bbLog=[];
    let bbLineupSide='home';
    let bbCurSide='home';
    let bbLineup={
      home: Array.from({length:9},(_,i)=>({name:`第${i+1}棒`})),
      away: Array.from({length:9},(_,i)=>({name:`第${i+1}棒`}))
    };
    let bbBatterIdx={ home:0, away:0 };
    let bbLineupTeam={ home:'', away:'' };
    let _bbBT={};

    function bbInitScoreboard() {
      bbPopulateTeamSel();
      bbRenderBSO();
      bbRenderBases();
      bbRenderLineup();
      bbRenderInningTable();
      bbUpdateBatterDisplay();
      bbUpdateSideLabel();
    }

    function bbPopulateTeamSel() {
      const sel=document.getElementById('bb-team-sel'); if(!sel) return;
      sel.innerHTML='<option value="">-- 選隊伍 --</option>';
      [...new Set(players.map(p=>p.team))].sort().forEach(t=>{
        const o=document.createElement('option'); o.value=t; o.textContent=t;
        if(t===bbLineupTeam[bbLineupSide]) o.selected=true;
        sel.appendChild(o);
      });
    }

    function bbOnTeamChange() {
      const team=document.getElementById('bb-team-sel').value;
      bbLineupTeam[bbLineupSide]=team;
      if(team){
        const tp=players.filter(p=>p.team===team);
        bbLineup[bbLineupSide]=Array.from({length:9},(_,i)=>({name:tp[i]?.name||`第${i+1}棒`}));
        bbBatterIdx[bbLineupSide]=0;
        const nameInput=document.getElementById(bbLineupSide==='home'?'bb-home-name':'bb-away-name');
        if(nameInput) nameInput.value=team;
      }
      bbRenderLineup(); bbUpdateBatterDisplay(); bbRenderInningTable(); bbUpdateSideLabel();
    }

    function bbDoPlay(type) {
      const batter=bbGetBatterName();
      if(type==='hr')            bbHandleHR(batter);
      else if(type==='1b')       bbHandleHit(1,'一壘打',batter);
      else if(type==='2b')       bbHandleHit(2,'二壘打',batter);
      else if(type==='3b')       bbHandleHit(3,'三壘打',batter);
      else if(type==='bb')       bbHandleBB(batter);
      else if(type==='e')        bbHandleHit(1,'失誤 E',batter);
      else if(type==='k')        bbHandleOut(batter,'三振 K');
      else if(type==='go')       bbHandleOut(batter,'滾地 Out');
      else if(type==='fo')       bbHandleOut(batter,'高飛 Out');
      else if(type==='dp')       bbHandleDP(batter);
      bbRenderBSO(); bbRenderBases(); bbRenderInningTable(); bbRenderLineup(); bbUpdateBatterDisplay();
    }

    function bbPushBases(n, batter) {
      const scored=[];
      for(const b of ['3rd','2nd','1st']){
        if(!bbBases[b]) continue;
        const dest=bbCalcDest(b,n);
        if(dest==='home'){ scored.push(bbBases[b]); bbBases[b]=null; }
        else { bbBases[dest]=bbBases[b]; bbBases[b]=null; }
      }
      const batterDest=bbCalcDest('home',n);
      if(batterDest!=='home') bbBases[batterDest]=batter;
      return scored;
    }

    function bbHandleBB(batter) {
      const scored=[];
      if(bbBases['1st'] && bbBases['2nd'] && bbBases['3rd']){
        scored.push(bbBases['3rd']);
        bbBases['3rd']=bbBases['2nd'];
        bbBases['2nd']=bbBases['1st'];
        bbBases['1st']=batter;
      } else if(bbBases['1st'] && bbBases['2nd']){
        bbBases['3rd']=bbBases['2nd'];
        bbBases['2nd']=bbBases['1st'];
        bbBases['1st']=batter;
      } else if(bbBases['1st']){
        bbBases['2nd']=bbBases['1st'];
        bbBases['1st']=batter;
      } else {
        bbBases['1st']=batter;
      }
      scored.forEach(r=>{ bbDoScore(); bbAddLog('score',r,'四壞保送後得分 🏠'); bbShowBanner('score',r+' 得分!'); });
      bbAddLog('hit',batter,'四壞保送 BB');
      bbNextBatter();
    }

    function bbCalcDest(from, n) {
      const map={'home':0,'1st':1,'2nd':2,'3rd':3};
      const back=[null,'1st','2nd','3rd','home'];
      const pos=map[from]; const np=pos+n;
      return np>=4?'home':(back[np]||'home');
    }

    function bbHandleHit(n, label, batter) {
      const scored=bbPushBases(n,batter);
      scored.forEach(r=>{ bbDoScore(); bbAddLog('score',r,'得分回本壘 🏠'); bbShowBanner('score',r+' 得分!'); });
      bbAddLog('hit',batter,label);
      bbNextBatter();
    }

    function bbHandleHR(batter) {
      const runners=[];
      for(const b of ['3rd','2nd','1st']){ if(bbBases[b]){runners.push(bbBases[b]); bbBases[b]=null;} }
      runners.push(batter);
      runners.forEach(()=>bbDoScore());
      bbAddLog('hr',batter,`全壘打 🏠 +${runners.length}分`);
      bbShowBanner('hr');
      bbNextBatter();
    }

    function bbHandleOut(batter, label) {
      bbOuts++;
      bbAddLog('out',batter,label);
      bbShowBanner('out',label);
      if(bbOuts>=3) bbSideRetired();
      else bbNextBatter();
    }

    function bbHandleDP(batter) {
      let removed=false;
      for(const b of ['3rd','2nd','1st']){
        if(bbBases[b]&&!removed){ bbAddLog('out',bbBases[b],'雙殺前位出局'); bbBases[b]=null; removed=true; }
      }
      bbOuts=Math.min(3,bbOuts+2);
      bbAddLog('out',batter,'雙殺 DP');
      bbShowBanner('out','雙殺!');
      if(bbOuts>=3) bbSideRetired();
      else bbNextBatter();
    }

    function bbSideRetired() {
      bbAddLog('sys','','三出局!攻守交換');
      bbShowBanner('side');
      setTimeout(()=>{
        bbOuts=0; bbStrikes=0;
        bbBases={'1st':null,'2nd':null,'3rd':null};
        const prevSide=bbCurSide;
        bbCurSide=bbCurSide==='home'?'away':'home';
        // 客隊打完換回主隊 → 自動進入下一局
        if(prevSide==='away' && bbCurInn<BB_INN){
          bbCurInn++;
          for(let i=1;i<=BB_INN;i++) document.getElementById('inning-btn-'+i)?.classList.toggle('active',i===bbCurInn);
          bbAddLog('sys','',`第 ${bbCurInn===BB_INN?'延長':bbCurInn} 局開始`);
        }
        bbLineupSide=bbCurSide;
        document.getElementById('lineup-tab-home').classList.toggle('active',bbLineupSide==='home');
        document.getElementById('lineup-tab-away').classList.toggle('active',bbLineupSide==='away');
        const sel=document.getElementById('bb-team-sel');
        if(sel) sel.value=bbLineupTeam[bbLineupSide]||'';
        bbRenderBSO(); bbRenderBases(); bbRenderLineup(); bbRenderInningTable(); bbUpdateBatterDisplay(); bbUpdateSideLabel();
      },1800);
    }

    function bbDoScore() {
      if(bbCurSide==='home'){
        bbHomeScore++;
        bbInnScores.home[bbCurInn-1]=(bbInnScores.home[bbCurInn-1]||0)+1;
        document.getElementById('bb-home-score').textContent=bbHomeScore;
        bbPopScore('bb-home-score');
      } else {
        bbAwayScore++;
        bbInnScores.away[bbCurInn-1]=(bbInnScores.away[bbCurInn-1]||0)+1;
        document.getElementById('bb-away-score').textContent=bbAwayScore;
        bbPopScore('bb-away-score');
      }
    }

    function bbManualScore(side,delta) {
      if(side==='home'){ bbHomeScore=Math.max(0,bbHomeScore+delta); document.getElementById('bb-home-score').textContent=bbHomeScore; if(delta>0)bbPopScore('bb-home-score'); }
      else { bbAwayScore=Math.max(0,bbAwayScore+delta); document.getElementById('bb-away-score').textContent=bbAwayScore; if(delta>0)bbPopScore('bb-away-score'); }
      bbRenderInningTable();
    }
    function bbPopScore(id){ const el=document.getElementById(id); el.classList.remove('popping'); void el.offsetWidth; el.classList.add('popping'); }

    function bbGetBatterName(){ return bbLineup[bbCurSide][bbBatterIdx[bbCurSide]]?.name||`第${bbBatterIdx[bbCurSide]+1}棒`; }
    function bbNextBatter(){ bbStrikes=0; bbBatterIdx[bbCurSide]=(bbBatterIdx[bbCurSide]+1)%9; bbUpdateBatterDisplay(); }
    function bbManualAdvance(){ bbBatterIdx[bbLineupSide]=(bbBatterIdx[bbLineupSide]+1)%9; bbRenderLineup(); bbUpdateBatterDisplay(); }
    function bbUpdateBatterDisplay(){ const el=document.getElementById('bb-batter-display'); if(el) el.textContent=bbGetBatterName(); }
    function bbUpdateSideLabel(){
      const el=document.getElementById('bb-side-label'); if(!el) return;
      const name=bbCurSide==='home'?(document.getElementById('bb-home-name').value||'主隊'):(document.getElementById('bb-away-name').value||'客隊');
      el.textContent=name+' 攻擊中';
      el.style.color=bbCurSide==='home'?'rgba(0,243,255,0.5)':'rgba(255,180,0,0.5)';
    }

    function bbSwitchLineup(side){
      bbLineupSide=side;
      document.getElementById('lineup-tab-home').classList.toggle('active',side==='home');
      document.getElementById('lineup-tab-away').classList.toggle('active',side==='away');
      const sel=document.getElementById('bb-team-sel'); if(sel) sel.value=bbLineupTeam[side]||'';
      bbRenderLineup(); bbUpdateBatterDisplay();
    }

    function bbRenderBSO(){
      const sl=document.getElementById('bb-strike-lights');
      const ol=document.getElementById('bb-out-lights');
      if(!sl||!ol) return;
      sl.innerHTML='';
      for(let i=1;i<=3;i++){
        const d=document.createElement('div'); d.className='bb-light'+(i<=bbStrikes?' on-strike':'');
        d.title=`好球 ${i}`; d.onclick=(()=>{const n=i; return ()=>{ bbStrikes=(bbStrikes>=n?n-1:n); if(bbStrikes>=3){bbHandleOut(bbGetBatterName(),'三振');} bbRenderBSO(); };})(i);
        sl.appendChild(d);
      }
      ol.innerHTML='';
      for(let i=1;i<=3;i++){
        const d=document.createElement('div'); d.className='bb-light'+(i<=bbOuts?' on-out':'');
        d.title=`出局 ${i}`; d.onclick=(()=>{const n=i; return ()=>{ bbOuts=(bbOuts>=n?n-1:n); bbRenderBSO(); if(bbOuts>=3)bbSideRetired(); };})(i);
        ol.appendChild(d);
      }
    }

    function bbRenderBases(){
      ['1st','2nd','3rd'].forEach(b=>{
        const el=document.getElementById('base-'+b);
        if(el){ el.classList.toggle('on',!!bbBases[b]); el.title=bbBases[b]||b; }
      });
    }

    function bbSetInning(n){
      bbCurInn=n;
      for(let i=1;i<=7;i++) document.getElementById('inning-btn-'+i).classList.toggle('active',i===n);
      bbAddLog('sys','',`第 ${n===7?'延長':n} 局開始`);
      bbRenderInningTable();
    }

    function bbRenderInningTable(){
      const tbody=document.getElementById('bb-inning-tbody'); if(!tbody) return;
      tbody.innerHTML='';
      ['home','away'].forEach(side=>{
        const tr=document.createElement('tr');
        const name=side==='home'?(document.getElementById('bb-home-name')?.value||'主隊'):(document.getElementById('bb-away-name')?.value||'客隊');
        const total=side==='home'?bbHomeScore:bbAwayScore;
        let html=`<td class="team-cell">${escapeHtml(name)}</td>`;
        for(let i=0;i<BB_INN;i++){
          const v=bbInnScores[side][i];
          const isCur=i+1===bbCurInn;
          html+=`<td class="bb-inn-cell${isCur?' cur':''}" onclick="bbEditCell('${side}',${i})">${v===null?'-':v}</td>`;
        }
        html+=`<td class="rhe-cell ${side==='home'?'home-rhe':'away-rhe'}" style="border-left:2px solid rgba(255,255,255,0.1);">${total}</td>`;
        tr.innerHTML=html; tbody.appendChild(tr);
      });
    }

    function bbEditCell(side,i){
      const cur=bbInnScores[side][i];
      const v=prompt(`修改第${i+1===7?'延長':i+1}局(${side==='home'?'主隊':'客隊'})得分:`,cur===null?0:cur);
      if(v===null) return;
      const n=parseInt(v); if(isNaN(n)||n<0) return;
      bbInnScores[side][i]=n;
      bbHomeScore=bbInnScores.home.reduce((a,v)=>a+(v||0),0);
      bbAwayScore=bbInnScores.away.reduce((a,v)=>a+(v||0),0);
      document.getElementById('bb-home-score').textContent=bbHomeScore;
      document.getElementById('bb-away-score').textContent=bbAwayScore;
      bbRenderInningTable();
    }

    function bbRenderLineup(){
      const list=document.getElementById('bb-lineup-list'); if(!list) return;
      list.innerHTML='';
      const cur=bbBatterIdx[bbLineupSide];
      bbLineup[bbLineupSide].forEach((p,i)=>{
        const row=document.createElement('div');
        row.className='bb-lineup-row'+(i===cur?' batting-now':'');
        row.onclick=()=>{ bbBatterIdx[bbLineupSide]=i; bbRenderLineup(); bbUpdateBatterDisplay(); };
        row.innerHTML=`<span class="bb-lineup-num">${i+1}</span><span class="bb-lineup-name">${escapeHtml(p.name)}</span><span class="bb-lineup-arrow">🏏</span>`;
        list.appendChild(row);
      });
    }

    function bbAddLog(type, player, result){
      const inn=bbCurInn===7?'EX':bbCurInn;
      const ord=bbBatterIdx[bbCurSide]+1;
      bbLog.unshift({type,player,result,outs:bbOuts,inn,ord});
      bbRenderLog();
    }
    function bbRenderLog(){
      const list=document.getElementById('bb-log-list'); if(!list) return;
      if(!bbLog.length){ list.innerHTML='<div style="text-align:center;font-family:\'Orbitron\';font-size:0.62rem;color:rgba(255,255,255,0.15);padding:16px;">尚無紀錄</div>'; return; }
      list.innerHTML=bbLog.slice(0,60).map(item=>{
        if(item.type==='sys') return `<div class="bb-log-item log-sys"><span class="bb-log-inn">${item.inn}</span><div class="bb-log-body"><span class="bb-log-tag tag-sys">${escapeHtml(item.result)}</span></div></div>`;
        const tagCls={'hit':'tag-hit','score':'tag-score','hr':'tag-hr','out':'tag-out'}[item.type]||'tag-sys';
        const lcls={'hit':'log-hit','score':'log-score','hr':'log-hr','out':'log-out'}[item.type]||'log-sys';
        const dots=[1,2,3].map(i=>`<div class="bb-out-dot${i<=item.outs?' on':''}"></div>`).join('');
        return `<div class="bb-log-item ${lcls}"><span class="bb-log-inn">${item.inn}</span><div class="bb-log-body"><div class="bb-log-player"><span style="font-size:0.58rem;color:rgba(255,255,255,0.28);margin-right:3px;">${item.ord}棒</span>${escapeHtml(item.player)}</div><span class="bb-log-tag ${tagCls}">${escapeHtml(item.result)}</span><div class="bb-out-dots">${dots}</div></div></div>`;
      }).join('');
    }

    function bbShowBanner(type, msg){
      const ids={out:'bb-banner-out',hr:'bb-banner-hr',side:'bb-banner-side',score:'bb-banner-score'};
      const el=document.getElementById(ids[type]); if(!el) return;
      if(type==='out'||type==='score') el.textContent=msg||'';
      el.classList.remove('show'); void el.offsetWidth;
      clearTimeout(_bbBT[type]);
      el.classList.add('show');
      _bbBT[type]=setTimeout(()=>el.classList.remove('show'),1600);
    }

    function resetScoreboard(){
      if(!confirm('確定要重置計分板嗎?')) return;
      bbHomeScore=0; bbAwayScore=0;
      document.getElementById('bb-home-score').textContent='0';
      document.getElementById('bb-away-score').textContent='0';
      document.getElementById('bb-home-name').value='主隊';
      document.getElementById('bb-away-name').value='客隊';
      bbInnScores={home:new Array(BB_INN).fill(null),away:new Array(BB_INN).fill(null)};
      bbStrikes=0; bbOuts=0;
      bbBases={'1st':null,'2nd':null,'3rd':null};
      bbLog=[]; bbBatterIdx={home:0,away:0};
      bbCurSide='home'; bbLineupSide='home';
      bbCurInn=1;
      bbLineupTeam={home:'',away:''};
      bbLineup={home:Array.from({length:9},(_,i)=>({name:`第${i+1}棒`})),away:Array.from({length:9},(_,i)=>({name:`第${i+1}棒`}))};
      document.getElementById('lineup-tab-home').classList.add('active');
      document.getElementById('lineup-tab-away').classList.remove('active');
      const sel=document.getElementById('bb-team-sel'); if(sel) sel.value='';
      for(let i=1;i<=7;i++) document.getElementById('inning-btn-'+i)?.classList.toggle('active',i===1);
      bbRenderBSO(); bbRenderBases(); bbRenderLineup(); bbRenderInningTable(); bbRenderLog();
      bbUpdateBatterDisplay(); bbUpdateSideLabel();
    }

    // ════════════════════════════════════════
    //  賽程表邏輯
    // ════════════════════════════════════════
    let scheduleData = [];
    let editingMatchIdx = null;

    function openAddMatch() {
      editingMatchIdx = null;
      document.getElementById('match-modal-title').textContent = '新增賽事';
      document.getElementById('m-home').value = '';
      document.getElementById('m-away').value = '';
      document.getElementById('m-date').value = '';
      document.getElementById('m-time').value = '';
      document.getElementById('m-venue').value = '';
      document.getElementById('m-hscore').value = '';
      document.getElementById('m-ascore').value = '';
      document.getElementById('match-modal').style.display = 'flex';
    }

    function openEditMatch(idx) {
      editingMatchIdx = idx;
      const m = scheduleData[idx];
      document.getElementById('match-modal-title').textContent = '編輯賽事';
      document.getElementById('m-home').value = m.home || '';
      document.getElementById('m-away').value = m.away || '';
      document.getElementById('m-date').value = m.date || '';
      document.getElementById('m-time').value = m.time || '';
      document.getElementById('m-venue').value = m.venue || '';
      document.getElementById('m-hscore').value = m.hscore !== null && m.hscore !== undefined ? m.hscore : '';
      document.getElementById('m-ascore').value = m.ascore !== null && m.ascore !== undefined ? m.ascore : '';
      document.getElementById('match-modal').style.display = 'flex';
    }

    function closeMatchModal() {
      document.getElementById('match-modal').style.display = 'none';
    }

    function saveMatch() {
      const home = document.getElementById('m-home').value.trim();
      const away = document.getElementById('m-away').value.trim();
      if(!home || !away) { alert('請填寫主隊和客隊名稱'); return; }
      const hscore = document.getElementById('m-hscore').value;
      const ascore = document.getElementById('m-ascore').value;
      const match = {
        home, away,
        date: document.getElementById('m-date').value,
        time: document.getElementById('m-time').value,
        venue: document.getElementById('m-venue').value.trim(),
        hscore: hscore !== '' ? parseInt(hscore) : null,
        ascore: ascore !== '' ? parseInt(ascore) : null,
      };
      if(editingMatchIdx !== null) {
        scheduleData[editingMatchIdx] = match;
      } else {
        scheduleData.push(match);
      }
      closeMatchModal();
      renderSchedule();
    }

    function deleteMatch(idx) {
      if(!confirm('確定刪除這場賽事?')) return;
      scheduleData.splice(idx, 1);
      renderSchedule();
    }

    function renderSchedule() {
      const filter = document.getElementById('schedule-filter').value;
      const list = document.getElementById('schedule-list');
      if(!list) return;

      let data = scheduleData.map((m,i) => ({...m, idx:i}));
      if(filter === 'done') data = data.filter(m => m.hscore !== null && m.ascore !== null);
      if(filter === 'pending') data = data.filter(m => m.hscore === null || m.ascore === null);

      if(data.length === 0) {
        list.innerHTML = '<div style="text-align:center; font-family:\'Orbitron\'; font-size:0.8rem; color:rgba(255,255,255,0.2); padding:40px;">尚無賽程</div>';
        return;
      }

      // 依日期分組
      const groups = {};
      data.forEach(m => {
        const key = m.date || '日期未定';
        if(!groups[key]) groups[key] = [];
        groups[key].push(m);
      });

      list.innerHTML = Object.keys(groups).sort().map(date => {
        const label = date === '日期未定' ? '日期未定' : formatDate(date);
        const matches = groups[date].map(m => {
          const done = m.hscore !== null && m.ascore !== null;
          const hWin = done && m.hscore > m.ascore;
          const aWin = done && m.ascore > m.hscore;
          return `
          <div style="display:grid; grid-template-columns:1fr auto 1fr; gap:12px; align-items:center; padding:14px 18px; background:rgba(255,255,255,0.03); border-radius:12px; border:1px solid rgba(255,255,255,0.07); margin-bottom:8px; transition:0.2s;" onmouseover="this.style.borderColor='rgba(0,220,255,0.25)'" onmouseout="this.style.borderColor='rgba(255,255,255,0.07)'">
            <div style="display:flex; flex-direction:column; align-items:flex-end; gap:4px;">
              <div style="font-family:'Noto Sans TC'; font-weight:900; font-size:1rem; color:${hWin?'#ffd700':'#fff'};">${escapeHtml(m.home)}</div>
              ${done ? `<div style="font-family:'Orbitron'; font-size:1.8rem; font-weight:900; color:${hWin?'#00f3ff':'rgba(255,255,255,0.4)'};">${m.hscore}</div>` : ''}
            </div>
            <div style="display:flex; flex-direction:column; align-items:center; gap:6px;">
              ${done ? '<div style="font-family:\'Orbitron\'; font-size:0.6rem; letter-spacing:2px; color:rgba(255,255,255,0.3);">FINAL</div>' : `<div style="font-family:'Orbitron'; font-size:0.75rem; color:rgba(255,255,255,0.5);">${m.time||'--:--'}</div>`}
              <div style="font-family:'Orbitron'; font-size:1rem; color:rgba(255,255,255,0.2);">VS</div>
              ${m.venue ? `<div style="font-size:0.65rem; color:rgba(255,255,255,0.3); font-family:'Noto Sans TC';">📍${escapeHtml(m.venue)}</div>` : ''}
            </div>
            <div style="display:flex; flex-direction:column; align-items:flex-start; gap:4px;">
              <div style="font-family:'Noto Sans TC'; font-weight:900; font-size:1rem; color:${aWin?'#ffd700':'#fff'};">${escapeHtml(m.away)}</div>
              ${done ? `<div style="font-family:'Orbitron'; font-size:1.8rem; font-weight:900; color:${aWin?'#ffb300':'rgba(255,255,255,0.4)'};">${m.ascore}</div>` : ''}
            </div>
            <div style="grid-column:1/4; display:flex; justify-content:flex-end; gap:6px; margin-top:4px;">
              <button onclick="openEditMatch(${m.idx})" style="padding:4px 14px; border-radius:20px; border:1px solid rgba(255,255,255,0.15); background:transparent; color:rgba(255,255,255,0.4); font-size:0.65rem; cursor:pointer; font-family:'Orbitron';">✏ 編輯</button>
              <button onclick="deleteMatch(${m.idx})" style="padding:4px 14px; border-radius:20px; border:1px solid rgba(255,68,68,0.3); background:transparent; color:rgba(255,100,100,0.6); font-size:0.65rem; cursor:pointer; font-family:'Orbitron';">✕ 刪除</button>
            </div>
          </div>`;
        }).join('');

        return `<div style="margin-bottom:20px;">
          <div style="font-family:'Orbitron'; font-size:0.7rem; letter-spacing:3px; color:rgba(0,220,255,0.6); margin-bottom:10px; padding-bottom:6px; border-bottom:1px solid rgba(0,220,255,0.15);">📅 ${label}</div>
          ${matches}
        </div>`;
      }).join('');
    }

    function formatDate(dateStr) {
      const d = new Date(dateStr);
      if(isNaN(d)) return dateStr;
      const days = ['日','一','二','三','四','五','六'];
      return `${d.getFullYear()}/${String(d.getMonth()+1).padStart(2,'0')}/${String(d.getDate()).padStart(2,'0')} (週${days[d.getDay()]})`;
    }

  </script>
</body>
</html>
貼到 Apps Script 的 index.html 檔案

6. 部署

部署成 Web App 後,就會拿到可分享網址。

  1. Apps Script 右上:部署 → 新部署
  2. 類型選:Web app
  3. 執行身分:你
  4. 存取權限:任何人
  5. 完成後複製網址

💬 評論區

老師們的交流與提問

載入評論中...