// 黃金交叉雷達 — MACD scanner
const { useState, useMemo, useEffect, useCallback } = React;

// CM_MacD_Ult_MTF histogram color metadata
const HIST_META = {
  aqua:   { label:'強多加速', en:'Bull Accel',   color:'#00bcd4', bg:'#e0f7fa', desc:'直方圖0軸上升中' },
  blue:   { label:'多頭衰減', en:'Bull Fade',    color:'#1e88e5', bg:'#e3f2fd', desc:'直方圖0軸上但縮減' },
  maroon: { label:'空頭止跌', en:'Bear Reverse', color:'#8b0000', bg:'#fce4ec', desc:'直方圖0軸下反彈' },
  red:    { label:'空頭加速', en:'Bear Accel',   color:'#e53935', bg:'#ffebee', desc:'直方圖0軸下繼續跌' },
};

const FILTERS = {
  signal: [
    { id:'all',           label:'全部訊號' },
    { id:'imminent',      label:'即將黃金' },
    { id:'crossed',       label:'剛剛黃金' },
    { id:'imminent-dead', label:'即將死叉' },
    { id:'crossed-dead',  label:'剛剛死叉' },
  ],
  zero: [
    { id:'all',   label:'0軸全部' },
    { id:'above', label:'0軸以上' },
    { id:'below', label:'0軸以下' },
  ],
  hist: [
    { id:'all',    label:'全部動能' },
    { id:'aqua',   label:'強多加速', color:'#00bcd4', desc:'直方圖0軸上升中（最強多頭）' },
    { id:'blue',   label:'多頭衰減', color:'#1e88e5', desc:'0軸上但直方圖縮減（留意趨緩）' },
    { id:'maroon', label:'空頭止跌', color:'#8b0000', desc:'0軸下直方圖反彈（潛在轉折）' },
    { id:'red',    label:'空頭加速', color:'#e53935', desc:'0軸下繼續擴張（強勢空頭）' },
  ],
  cat: [
    { id:'all',              label:'全部分類' },
    { id:'pillar+frontier',  label:'主流＋新興' },
    { id:'pillar',           label:'主流紅利' },
    { id:'frontier',         label:'新興趨勢' },
    { id:'heritage',         label:'傳統根基' },
  ],
};

// Smart presets — 4 組
const PRESETS = [
  {
    id: 'power-confirm',
    star: true,
    icon: '⚡',
    cn: '強力確認',
    en: 'Power Confirm',
    desc: '剛黃金 × 0軸以上 × 強多加速 × 主流+新興',
    hint: '最高品質買訊',
    filters: { signal:'crossed', zero:'above', cat:'pillar+frontier', ind:'all', hist:'aqua' },
  },
  {
    id: 'golden-prime',
    star: true,
    icon: '⭐',
    cn: '黃金佈局',
    en: 'Prime Setup',
    desc: '即將黃金 × 0軸以上 × 主流+新興',
    hint: '趨勢明確，提前佈局',
    filters: { signal:'imminent', zero:'above', cat:'pillar+frontier', ind:'all' },
  },
  {
    id: 'oversold-bounce',
    icon: '📉',
    cn: '低檔反彈',
    en: 'Oversold Bounce',
    desc: '即將黃金 × 0軸以下 × 主流+新興',
    hint: '0軸下低接反彈型',
    filters: { signal:'imminent', zero:'below', cat:'pillar+frontier', ind:'all' },
  },
  {
    id: 'death-warning',
    icon: '⚠️',
    cn: '死叉警示',
    en: 'Death Warning',
    desc: '即將死叉 × 0軸以上 × 全部',
    hint: '逃頂 / 放空參考',
    filters: { signal:'imminent-dead', zero:'above', cat:'all', ind:'all' },
  },
];

const signalMeta = {
  imminent:        { label:'即將黃金交叉', en:'Imminent Golden', color:'var(--gold)',     bg:'#fdf3e0' },
  crossed:         { label:'剛剛黃金(3日)', en:'Just Golden',    color:'var(--bull)',     bg:'#e7f2ec' },
  'imminent-dead': { label:'即將死亡交叉', en:'Imminent Death',  color:'#c07030',         bg:'#fdf0e6' },
  'crossed-dead':  { label:'剛剛死叉(3日)', en:'Just Death',    color:'var(--bear)',     bg:'#f5e6e3' },
};

// ETF / warrant filter — only keep pure 4-5 digit codes starting with 1-9
function isRegularStock(code) {
  return /^[1-9]\d{3,4}$/.test(code);
}

// Build code → { category, indId } map from data.js
var _codeMap = null;
function getCodeMap() {
  if (_codeMap) return _codeMap;
  _codeMap = {};
  (window.INDUSTRIES || []).forEach(function(ind) {
    ['upstream', 'midstream', 'downstream'].forEach(function(seg) {
      (ind.nodes[seg] || []).forEach(function(node) {
        (node.companies || []).forEach(function(c) {
          if (!_codeMap[c.code]) {
            _codeMap[c.code] = { category: ind.category, indId: ind.id };
          }
        });
      });
    });
  });
  return _codeMap;
}

// Build code → { nodeTitle, nodeTitleEn, segment } from data.js
var _nodeMap = null;
function getNodeMap() {
  if (_nodeMap) return _nodeMap;
  _nodeMap = {};
  var segLabel = { upstream:'上游', midstream:'中游', downstream:'下游' };
  (window.INDUSTRIES || []).forEach(function(ind) {
    ['upstream','midstream','downstream'].forEach(function(seg) {
      (ind.nodes[seg] || []).forEach(function(node) {
        (node.companies || []).forEach(function(c) {
          if (!_nodeMap[c.code]) {
            _nodeMap[c.code] = {
              nodeTitle:   node.title,
              nodeTitleEn: node.titleEn,
              segment:     segLabel[seg] || seg,
            };
          }
        });
      });
    });
  });
  return _nodeMap;
}

// Build code → 中文名 from data.js INDUSTRIES + localStorage TWSE list cache
var _nameMap = null;

// 全市場清單只抓一次:頁面預拉與 runScan 兩條路徑共用同一個 in-flight promise
// (體檢 2026-07-18:原本兩處各自 fetch,首訪+立刻掃描會重複拉 ~1845 檔全量 JSON)
var _listFetchPromise = null;
function fetchListOnce() {
  if (!_listFetchPromise) {
    _listFetchPromise = fetch('/api/stock?type=list&market=all')
      .then(function(r) { return r.ok ? r.json() : null; })
      .catch(function() { _listFetchPromise = null; return null; });
  }
  return _listFetchPromise;
}
function getNameMap() {
  if (_nameMap) return _nameMap;
  _nameMap = {};
  // 1. data.js INDUSTRIES (台股精選個股)
  (window.INDUSTRIES || []).forEach(function(ind) {
    ['upstream', 'midstream', 'downstream'].forEach(function(seg) {
      (ind.nodes[seg] || []).forEach(function(node) {
        (node.companies || []).forEach(function(c) {
          if (c.code && c.name) _nameMap[c.code] = c.name;
        });
      });
    });
  });
  // 2. TWSE list cache (全市場中文名)
  try {
    var cached = JSON.parse(localStorage.getItem('tw_list_cache_v1') || 'null');
    if (Array.isArray(cached)) {
      cached.forEach(function(s) {
        if (s.Code && s.Name && !_nameMap[s.Code]) _nameMap[s.Code] = s.Name;
      });
    }
  } catch (_) {}
  return _nameMap;
}

// Build industry list for filter pills: [{ id, name, category }]
function getIndustryList() {
  return (window.INDUSTRIES || []).map(function(ind) {
    return { id: ind.id, name: ind.name, nameEn: ind.nameEn, category: ind.category };
  });
}

// TWSE/OTC 官方產業別 → 分類對照（從 ISIN C_public.jsp 實際抓取）
var TWSE_SECTOR_CAT = {
  // ── heritage ───────────────────────────────────────────
  '水泥工業':'heritage','食品工業':'heritage','塑膠工業':'heritage',
  '紡織纖維':'heritage','電機機械':'heritage','電器電纜':'heritage',
  '化學工業':'heritage','玻璃陶瓷':'heritage','造紙工業':'heritage',
  '鋼鐵工業':'heritage','橡膠工業':'heritage','汽車工業':'heritage',
  '建材營造業':'heritage','航運業':'heritage',
  '觀光事業':'heritage','觀光餐旅':'heritage',  // 新舊名稱
  '貿易百貨業':'heritage','油電燃氣業':'heritage',
  '運動休閒':'heritage','居家生活':'heritage',
  '文化創意業':'heritage','其他業':'heritage',
  // ── frontier ───────────────────────────────────────────
  '化學生技醫療業':'frontier','生技醫療業':'frontier',
  '綠能環保':'frontier','農業科技業':'frontier',
  // ── pillar ─────────────────────────────────────────────
  '半導體業':'pillar','電子工業':'pillar',
  '電腦及週邊設備業':'pillar','光電業':'pillar',
  '通信網路業':'pillar','電子零組件業':'pillar',
  '電子通路業':'pillar','資訊服務業':'pillar',
  '其他電子業':'pillar','數位雲端':'pillar',
  '金融保險業':'pillar','金融業':'pillar',
};

// 關鍵字分類（最後手段，官方 sector 與 data.js 都無此代號時才用）
function keywordCat(name) {
  var n = name || '';
  if (/生技|醫療|醫材|醫藥|基因|疫苗|新藥|生醫|藥廠|製藥|健康|太陽能|風電|綠能|儲能|氫能/.test(n))
    return 'frontier';
  if (/水泥|食品|飲料|糧|農|漁|畜|牧|飼料|紡織|纖維|成衣|服飾|塑膠|橡膠|化工|化纖|石化|紙業|造紙|玻璃|陶瓷|磁磚|鋼鐵|鋼板|型鋼|金屬|螺絲|電纜|電線|機械|工具機|汽車|輪胎|自行車|船舶|貨運|倉儲|航運|貨櫃|零售|超市|量販|百貨|餐飲|飯店|旅遊|觀光|旅館|保全|清潔|環保|人力|租賃|建設|建材|建築|營造|磚|裝潢|印刷|廣告|媒體|出版/.test(n))
    return 'heritage';
  return 'pillar';
}

// Category + industry：大分類以 TWSE 官方 sector 為權威(全覆蓋~1970檔含上櫃)，
// data.js 只提供子產業 indId(官方沒有的節點)；兩者皆無才用關鍵字猜。
function guessCatInd(name, code, sectorMap) {
  var fromData = getCodeMap()[code];        // {category, indId} | undefined
  var indId = fromData ? fromData.indId : null;
  var category;
  if (sectorMap && sectorMap[code] && TWSE_SECTOR_CAT[sectorMap[code]]) {
    category = TWSE_SECTOR_CAT[sectorMap[code]];   // ① 官方權威優先
  } else if (fromData) {
    category = fromData.category;                   // ② data.js 精選
  } else {
    category = keywordCat(name);                    // ③ 關鍵字最後手段
  }
  return { category: category, indId: indId };
}

// ─── Real MACD computation from accumulated price history ────────────────────

var PRICE_HIST_KEY = 'tw_scan_prices_v3';

function loadPriceHist() {
  try { return JSON.parse(localStorage.getItem(PRICE_HIST_KEY)) || {}; }
  catch (_) { return {}; }
}
function savePriceHist(h) {
  try { localStorage.setItem(PRICE_HIST_KEY, JSON.stringify(h)); } catch (_) {}
}

// Append today's closes; only runs once per calendar day
function updatePriceHist(list) {
  var today = new Date().toISOString().slice(0, 10);
  var hist  = loadPriceHist();
  if (hist._date === today) return hist;          // already updated today

  // Skip weekends (no new data)
  var dow = new Date().getDay();
  if (dow === 0 || dow === 6) { hist._date = today; return hist; }

  hist._date = today;
  list.forEach(function(s) {
    if (!isRegularStock(s.Code)) return;
    var close = parseFloat(s.ClosingPrice) || 0;
    if (!close) return;
    if (!hist[s.Code]) hist[s.Code] = [];
    hist[s.Code].push(close);
    if (hist[s.Code].length > 90) hist[s.Code] = hist[s.Code].slice(-90);
  });
  savePriceHist(hist);
  return hist;
}

function emaArr(closes, period) {
  var k = 2 / (period + 1);
  // TradingView 慣例：用前 period 根的 SMA 作種子，消除冷啟動偏移
  var p    = Math.min(period, closes.length);
  var seed = closes.slice(0, p).reduce(function(a, b) { return a + b; }, 0) / p;
  var out  = [seed];
  for (var i = 1; i < closes.length; i++)
    out.push(closes[i] * k + out[i-1] * (1 - k));
  return out;
}

// Returns last `tail` bars of [{dif, dea, bar}], or null if not enough data
function macdTail(closes, tail) {
  if (closes.length < 35) return null;
  var ema12 = emaArr(closes, 12);
  var ema26 = emaArr(closes, 26);
  var dif   = ema12.map(function(v, i) { return v - ema26[i]; });
  var dea   = emaArr(dif, 9);
  var n = dif.length;
  var out = [];
  var start = Math.max(n - (tail || 10), 1);
  for (var i = start; i < n; i++)
    out.push({ dif: dif[i], dea: dea[i], bar: dif[i] - dea[i] });
  return out.length >= 2 ? out : null;
}

// CM_MacD_Ult_MTF histogram 4-color classifier (ChrisMoody 2014, ported)
// aqua  = rising above zero (strongest bull)
// blue  = falling above zero (bull fading)
// maroon= rising below zero (bear reversing)
// red   = falling below zero (strongest bear)
function histColor(bar, prevBar) {
  if (bar > 0)  return bar > prevBar ? 'aqua'   : 'blue';
  return             bar > prevBar ? 'maroon' : 'red';
}

// Classify signal from MACD tail; returns null if no clear signal
function classifyMACD(tail) {
  if (!tail || tail.length < 3) return null;
  var n    = tail.length;
  var last = tail[n-1], prev = tail[n-2], prev2 = tail[n-3];

  // ── CM_MacD_Ult_MTF: histogram 4-color state ──
  var hColor = histColor(last.bar, prev.bar);

  // ── Golden / death cross within last 3 bars ──
  for (var ago = 1; ago <= Math.min(3, n-1); ago++) {
    var cur = tail[n-ago], pre = tail[n-ago-1];
    if (pre.dif < pre.dea && cur.dif >= cur.dea)
      return { signal:'crossed',      daysAgo: ago, daysToX: 0,
               aboveZero: last.dif > 0 && last.dea > 0, diff: +last.bar.toFixed(4),
               histColor: hColor };
    if (pre.dif > pre.dea && cur.dif <= cur.dea)
      return { signal:'crossed-dead', daysAgo: ago, daysToX: 0,
               aboveZero: last.dif > 0 && last.dea > 0, diff: +last.bar.toFixed(4),
               histColor: hColor };
  }

  var gap      = last.dif - last.dea;
  var prevGap  = prev.dif - prev.dea;
  var prev2Gap = prev2.dif - prev2.dea;
  var delta    = gap - prevGap;
  var prevDelta = prevGap - prev2Gap;

  // ── Imminent golden ──
  if (gap < 0 && delta > 0 && prevDelta > 0) {
    var dToX = Math.max(1, Math.round(Math.abs(gap) / delta));
    if (dToX <= 5)
      return { signal:'imminent',      daysAgo: 0, daysToX: dToX,
               aboveZero: last.dif > 0 && last.dea > 0, diff: +gap.toFixed(4),
               histColor: hColor };
  }

  // ── Imminent death ──
  if (gap > 0 && delta < 0 && prevDelta < 0) {
    var dToX = Math.max(1, Math.round(Math.abs(gap) / Math.abs(delta)));
    if (dToX <= 5)
      return { signal:'imminent-dead', daysAgo: 0, daysToX: dToX,
               aboveZero: last.dif > 0 && last.dea > 0, diff: +gap.toFixed(4),
               histColor: hColor };
  }

  return null;
}

function fmtVol(raw) {
  if (!raw) return '—';
  var n = parseInt(raw, 10);
  if (isNaN(n)) return raw;
  if (n >= 100000000) return (n / 100000000).toFixed(1) + '億';
  if (n >= 10000)     return (n / 10000).toFixed(1) + '萬';
  return n.toString();
}

// fmtVol 的逆運算：「3.2億」→320000000、「1.5萬」→15000、純數字字串→數字
function parseVolStr(s) {
  if (s == null || s === '—') return 0;
  if (typeof s === 'number') return s;
  var str = String(s).trim();
  var m = str.match(/^([\d.]+)\s*(億|萬)?/);
  if (!m) return 0;
  var num = parseFloat(m[1]) || 0;
  if (m[2] === '億') return num * 100000000;
  if (m[2] === '萬') return num * 10000;
  return num;
}

const SIGNAL_ORDER = { crossed: 0, imminent: 1, 'imminent-dead': 2, 'crossed-dead': 3 };

const CAT_META = {
  pillar:   { label:'主流紅利', en:'Pillar',   color:'#b8924d', bg:'rgba(184,146,77,0.12)' },
  frontier: { label:'新興趨勢', en:'Frontier', color:'#2a6b7c', bg:'rgba(42,107,124,0.12)' },
  heritage: { label:'傳統根基', en:'Heritage', color:'#6b4c2a', bg:'rgba(107,76,42,0.12)' },
};

// ── 訊號細分：訊號別 × 0軸 × 品質命名（同一桶內品質天差地別的分開標示）──
function fineSignal(r) {
  var above = r.aboveZero;
  switch (r.signal) {
    case 'crossed':       return above ? { label:'零軸上強勢交叉', tone:'strong' }
                                       : { label:'零軸下超跌反彈', tone:'bounce' };
    case 'imminent':      return above ? { label:'即將交叉·多方醞釀', tone:'setup' }
                                       : { label:'零軸下即將轉強', tone:'watch' };
    case 'crossed-dead':  return above ? { label:'高檔死叉·逃頂警示', tone:'warn' }
                                       : { label:'弱勢續跌死叉', tone:'bear' };
    case 'imminent-dead': return above ? { label:'高檔即將死叉', tone:'warn' }
                                       : { label:'即將續弱', tone:'bear' };
    default:              return { label:'', tone:'' };
  }
}

// ── 黃金交叉買進品質分數(0–100，純規則)。死叉類非買訊，回 null 不評級 ──
// 五構面：0軸結構25 / 柱狀動能25 / 交叉新鮮度20 / 量能百分位15 / 價格確認15
function calcGoldenScore(r, volPctl) {
  if (r.signal !== 'crossed' && r.signal !== 'imminent') return null;
  // 0軸結構：DIF 在 0 軸上=確認多頭；0軸下=反彈性質
  var s0 = r.aboveZero ? 25 : 10;
  // 柱狀動能：aqua 強多加速最佳、blue 多頭衰減、maroon 空頭止跌(轉折)、red 空頭加速最差
  var sHist = ({ aqua:25, maroon:16, blue:14, red:7 })[r.histColor] || 12;
  // 交叉新鮮度：剛交叉 1 天最佳遞減；即將交叉給預期分(略低於已確認)
  var sFresh;
  if (r.signal === 'crossed') sFresh = r.daysAgo <= 1 ? 20 : r.daysAgo === 2 ? 15 : 10;
  else sFresh = r.daysToX <= 1 ? 14 : r.daysToX === 2 ? 11 : r.daysToX === 3 ? 8 : 5;
  // 量能：該檔成交量在本次掃描結果的百分位(0–1)
  var sVol = Math.round((volPctl == null ? 0.5 : volPctl) * 15);
  // 價格確認：今日漲幅 +1~4% 最理想；平盤偏弱；追高略扣
  var c = r.chg;
  var sPrice = c >= 1 && c <= 4 ? 15 : (c > 0 && c < 1) || (c > 4 && c <= 6) ? 11
             : c >= -1 && c <= 0 ? 8 : c > 6 ? 9 : 5;
  return Math.round(s0 + sHist + sFresh + sVol + sPrice);
}

const GRADE_META = {
  A: { color:'#1a7a4a', bg:'rgba(26,122,74,0.12)', border:'#1a7a4a' },
  B: { color:'#b8924d', bg:'rgba(184,146,77,0.14)', border:'#b8924d' },
  C: { color:'#6b7280', bg:'rgba(107,114,128,0.10)', border:'#9ca3af' },
  D: { color:'#9c2b2b', bg:'rgba(156,43,43,0.10)', border:'#c0603a' },
};
function gradeOf(score) {
  if (score == null) return null;
  var letter = score >= 75 ? 'A' : score >= 55 ? 'B' : score >= 35 ? 'C' : 'D';
  return Object.assign({ letter: letter, score: score }, GRADE_META[letter]);
}

// 多頭進出場估算（與 MACDExpandedRow 同公式：ATR≈價×2.5%）供智選組合用
function longTradePlan(r) {
  var atr = r.price * 0.025;
  var entry = r.signal === 'crossed' ? r.price : Math.round(r.price * 0.99);
  var sl = Math.round(entry - atr * 1.5);
  var t1 = Math.round(r.price * (r.aboveZero ? 1.06 : 1.04));
  var t2 = Math.round(r.price * (r.aboveZero ? 1.12 : 1.08));
  var rr = t1 > entry && sl < entry ? +((t1 - entry) / (entry - sl)).toFixed(1) : null;
  return { entry: entry, sl: sl, t1: t1, t2: t2, rr: rr };
}

// 智選組合挑選：買訊(有分數)按分數高→低，同產業最多 2 檔，湊最多 8 檔分散組合
function pickSmartCombo(rows, maxN, perIndustry) {
  maxN = maxN || 8; perIndustry = perIndustry || 2;
  var buys = rows.filter(function(r) { return r.score != null; })
                 .slice().sort(function(a, b) { return b.score - a.score; });
  var chosen = [], counts = {};
  for (var i = 0; i < buys.length && chosen.length < maxN; i++) {
    var r = buys[i];
    var key = r.sector || r.indName || (r.cat ? CAT_META[r.cat] && CAT_META[r.cat].label : '') || '其他';
    if ((counts[key] || 0) >= perIndustry) continue;
    counts[key] = (counts[key] || 0) + 1;
    chosen.push(r);
  }
  return chosen;
}

// 為結果補上 fine/score/grade 並排序（新掃描與讀快取共用，確保舊快取也重新評級）
function scoreAndSortRows(results) {
  var vols = results.map(function(r) { return parseVolStr(r.vol); }).sort(function(a, b) { return a - b; });
  function volPctlOf(v) {
    var raw = parseVolStr(v);
    if (vols.length < 2) return 0.5;
    var below = 0;
    for (var i = 0; i < vols.length; i++) { if (vols[i] < raw) below++; else break; }
    return below / (vols.length - 1);
  }
  results.forEach(function(r) {
    if (!r.fine) r.fine = fineSignal(r);
    r.score = calcGoldenScore(r, volPctlOf(r.vol));
    r.grade = gradeOf(r.score);
  });
  results.sort(function(a, b) {
    var aBuy = a.score != null, bBuy = b.score != null;
    if (aBuy !== bBuy) return aBuy ? -1 : 1;
    if (aBuy && bBuy) {
      if (b.score !== a.score) return b.score - a.score;
      return Math.abs(b.chg) - Math.abs(a.chg);
    }
    if (SIGNAL_ORDER[a.signal] !== SIGNAL_ORDER[b.signal])
      return SIGNAL_ORDER[a.signal] - SIGNAL_ORDER[b.signal];
    return Math.abs(b.chg) - Math.abs(a.chg);
  });
  return results;
}

// indId → 中文名
function getIndNameMap() {
  var m = {};
  (window.INDUSTRIES || []).forEach(function(ind) { m[ind.id] = ind.name; });
  return m;
}

// ── 績效追蹤 localStorage helpers ──────────────────────────
var PERF_KEY = 'scan_perf_v1';
function loadPerfRecords() {
  try { return JSON.parse(localStorage.getItem(PERF_KEY)) || []; } catch (_) { return []; }
}
function savePerfRecords(list) {
  try { localStorage.setItem(PERF_KEY, JSON.stringify(list)); } catch (_) {}
}
function isAlreadyRecorded(code, signalDate) {
  return loadPerfRecords().some(function(p) { return p.code === code && p.signalDate === signalDate; });
}
function addPerfRecord(rec) {
  var list = loadPerfRecords();
  list.unshift(rec);
  savePerfRecords(list);
}
function todayStr() {
  var d = new Date();
  return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
}

function MACDExpandedRow({ r, colSpan }) {
  var sig    = signalMeta[r.signal];
  var hm     = r.histColor ? HIST_META[r.histColor] : null;
  var isGold = r.signal === 'crossed' || r.signal === 'imminent';
  var isBear = r.signal === 'crossed-dead' || r.signal === 'imminent-dead';

  var today = todayStr();
  var [recorded, setRecorded] = useState(function() { return isAlreadyRecorded(r.code, today); });

  // ── 進出場數字 ──────────────────────────────────────
  var atrEst = r.price * 0.025;
  var entry, sl, t1, t2, rr;
  if (isBear) {
    // 空頭反向
    entry = r.price;
    sl    = Math.round(entry + atrEst * 1.5);
    t1    = Math.round(entry * 0.94);
    t2    = Math.round(entry * 0.88);
    rr    = t1 < entry && sl > entry ? +((entry - t1) / (sl - entry)).toFixed(1) : null;
  } else {
    // 多頭
    entry = r.signal === 'crossed' ? r.price : Math.round(r.price * 0.99);
    sl    = Math.round(entry - atrEst * 1.5);
    t1    = Math.round(r.price * (r.aboveZero ? 1.06 : 1.04));
    t2    = Math.round(r.price * (r.aboveZero ? 1.12 : 1.08));
    rr    = t1 > entry && sl < entry ? +((t1 - entry) / (entry - sl)).toFixed(1) : null;
  }

  // 交叉狀態描述
  var crossDesc = '';
  if (r.signal === 'crossed')        crossDesc = '黃金交叉 ' + r.daysAgo + ' 根前';
  else if (r.signal === 'imminent')  crossDesc = '預計 ' + r.daysToX + ' 根後黃金交叉';
  else if (r.signal === 'crossed-dead') crossDesc = '死亡交叉 ' + r.daysAgo + ' 根前';
  else if (r.signal === 'imminent-dead') crossDesc = '預計 ' + r.daysToX + ' 根後死亡交叉';

  // 零軸位置描述
  var zeroDesc = r.aboveZero ? '零軸以上（強勢）' : '零軸以下（弱勢）';

  // 訊號燈顏色
  var dotColor = isGold ? 'var(--bull)' : 'var(--bear)';

  var detailUrl = '個股技術分析.html?code=' + r.code + '&tab=signal'
    + '&entry=' + entry
    + (t1 ? '&t1=' + t1 : '')
    + (t2 ? '&t2=' + t2 : '')
    + (sl ? '&sl=' + sl : '');

  return (
    <tr>
      <td colSpan={colSpan} style={{ padding:0 }}>
        <div style={{
          padding: '10px 18px 12px 18px',
          background: isGold ? 'rgba(76,175,80,0.05)' : 'rgba(239,83,80,0.05)',
          borderTop: '1px solid var(--paper-line)',
          borderBottom: '2px solid ' + (isGold ? 'rgba(76,175,80,0.2)' : 'rgba(239,83,80,0.2)'),
          display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap',
        }}>
          {/* 訊號燈 */}
          <span style={{
            display: 'inline-block', width: 8, height: 8, borderRadius: '50%',
            background: dotColor, flexShrink: 0,
          }}/>

          {/* MACD 狀態文字 */}
          <span style={{ fontFamily:'BiauKai,標楷體,serif', fontSize: 12, color: 'var(--ink)' }}>
            MACD：
            <span style={{ color: sig.color, fontWeight: 700 }}>{crossDesc}</span>
            <span style={{ color:'var(--ink-mute)', marginLeft: 8 }}>·</span>
            <span style={{ color:'var(--ink-mute)', marginLeft: 8 }}>{zeroDesc}</span>
          </span>

          {/* 直方圖顏色標籤 */}
          {hm && (
            <span style={{
              display: 'inline-flex', alignItems: 'center', gap: 4,
              fontSize: 'var(--fs-nano)', color: hm.color, fontFamily: 'Times New Roman,serif', fontStyle: 'italic',
            }}>
              <span style={{
                display:'inline-block', width:6, height:10, borderRadius:1,
                background: hm.color, flexShrink:0,
              }}/>
              {hm.label}
            </span>
          )}

          {/* MACD Diff 數值 */}
          <span style={{
            fontSize: 'var(--fs-nano)', fontFamily: 'Times New Roman,serif',
            color: r.macdDiff >= 0 ? 'var(--bull)' : 'var(--bear)',
          }}>
            DIF−DEA {r.macdDiff >= 0 ? '+' : ''}{r.macdDiff.toFixed(2)}
          </span>

          {/* 進出場數字 */}
          <span style={{ fontSize: 'var(--fs-nano)', fontFamily: 'Times New Roman,serif', display: 'inline-flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
            <span style={{ color: 'var(--ink)' }}>進場 {entry.toLocaleString()}</span>
            <span style={{ color: 'var(--ink-mute)' }}>·</span>
            <span>
              <span style={{ color: 'var(--ink-mute)', fontSize: 'var(--fs-nano)' }}>目標 </span>
              <span style={{ color: 'var(--bull)' }}>{t1.toLocaleString()}</span>
              <span style={{ color: 'var(--ink-mute)' }}> / </span>
              <span style={{ color: 'var(--bull)' }}>{t2.toLocaleString()}</span>
            </span>
            <span style={{ color: 'var(--ink-mute)' }}>·</span>
            <span>
              <span style={{ color: 'var(--ink-mute)', fontSize: 'var(--fs-nano)' }}>停損 </span>
              <span style={{ color: 'var(--bear)' }}>{sl.toLocaleString()}</span>
            </span>
            {rr !== null && (
              <>
                <span style={{ color: 'var(--ink-mute)' }}>·</span>
                <span>
                  <span style={{ color: 'var(--ink-mute)', fontSize: 'var(--fs-nano)' }}>R:R </span>
                  <span style={{ color: 'var(--gold)' }}>{rr}</span>
                </span>
              </>
            )}
          </span>

          {/* 分隔 */}
          <span style={{ color:'var(--paper-line)', margin:'0 2px' }}>|</span>

          {/* 進出場策略連結 */}
          <a
            href={detailUrl}
            onClick={function(e) { e.stopPropagation(); }}
            style={{
              fontFamily: 'BiauKai,標楷體,serif', fontSize: 12,
              color: isGold ? 'var(--bull)' : 'var(--ink-mute)',
              textDecoration: 'none',
              borderBottom: '1px dashed currentColor',
              letterSpacing: '.04em',
              whiteSpace: 'nowrap',
            }}>
            → 查看進出場策略
          </a>

          {/* TradingView 連結 */}
          <a
            href={'https://www.tradingview.com/chart/?symbol=TWSE:' + r.code + '&studies=MACD@tv-basicstudies&interval=D'}
            target="_blank"
            rel="noopener noreferrer"
            onClick={function(e) { e.stopPropagation(); }}
            style={{
              fontFamily: 'Times New Roman,serif', fontStyle: 'italic', fontSize: 'var(--fs-nano)',
              color: '#2962ff', textDecoration: 'none',
              borderBottom: '1px dashed #2962ff44',
              whiteSpace: 'nowrap',
            }}>
            ⬡ TradingView + MACD ↗
          </a>

          {/* 個股分析連結 */}
          <a
            href={'個股技術分析.html?code=' + r.code}
            onClick={function(e) { e.stopPropagation(); }}
            style={{
              fontFamily: 'Times New Roman,serif', fontStyle: 'italic', fontSize: 'var(--fs-nano)',
              color: 'var(--ink-mute)', textDecoration: 'none',
              borderBottom: '1px dashed var(--paper-line)',
              whiteSpace: 'nowrap', marginLeft: 'auto',
            }}>
            K線圖 / 基本面 ↗
          </a>

          {/* 📌 記錄此訊號 */}
          <button
            onClick={function(e) {
              e.stopPropagation();
              if (recorded) return;
              var atrEst2 = r.price * 0.025;
              var entry2, sl2, t12, t22;
              if (isBear) {
                entry2 = r.price;
                sl2    = Math.round(entry2 + atrEst2 * 1.5);
                t12    = Math.round(entry2 * 0.94);
                t22    = Math.round(entry2 * 0.88);
              } else {
                entry2 = r.signal === 'crossed' ? r.price : Math.round(r.price * 0.99);
                sl2    = Math.round(entry2 - atrEst2 * 1.5);
                t12    = Math.round(r.price * (r.aboveZero ? 1.06 : 1.04));
                t22    = Math.round(r.price * (r.aboveZero ? 1.12 : 1.08));
              }
              addPerfRecord({
                id:           Date.now(),
                code:         r.code,
                name:         r.name || r.nameZh || r.code,
                signalDate:   today,
                entryPrice:   entry2,
                t1:           t12,
                t2:           t22,
                sl:           sl2,
                signal:       isBear ? 'crossed-dead' : 'crossed',
                currentPrice: null,
                rtn:          null,
                result:       'open',
                closedAt:     null,
              });
              setRecorded(true);
            }}
            style={{
              fontFamily: 'BiauKai,標楷體,serif', fontSize: 'var(--fs-nano)',
              padding: '2px 8px', borderRadius: 3, cursor: recorded ? 'default' : 'pointer',
              border: '1px solid ' + (recorded ? 'var(--paper-line)' : 'var(--gold)'),
              background: recorded ? 'transparent' : 'rgba(184,146,77,0.1)',
              color: recorded ? 'var(--ink-mute)' : 'var(--gold)',
              whiteSpace: 'nowrap', flexShrink: 0,
            }}>
            {recorded ? '✓ 已記錄' : '📌 記錄'}
          </button>
        </div>
      </td>
    </tr>
  );
}

// ── 黃金交叉智選組合：分數最高 + 產業分散 ──────────────────
function SmartComboPanel({ rows }) {
  var [recorded, setRecorded] = useState(false);
  var combo = useMemo(function() { return pickSmartCombo(rows, 8, 2); }, [rows]);
  if (!combo.length) return null;

  // 產業分布統計
  var buckets = {};
  combo.forEach(function(r) {
    var k = r.sector || r.indName || (r.cat && CAT_META[r.cat] ? CAT_META[r.cat].label : '其他');
    buckets[k] = (buckets[k] || 0) + 1;
  });
  var industryCount = Object.keys(buckets).length;
  var avgScore = Math.round(combo.reduce(function(s, r) { return s + r.score; }, 0) / combo.length);

  function recordAll() {
    if (recorded) return;
    var sd = todayStr();
    combo.forEach(function(r) {
      if (isAlreadyRecorded(r.code, sd)) return;
      var p = longTradePlan(r);
      addPerfRecord({
        code: r.code, name: r.nameZh || r.name, signalDate: sd,
        entryPrice: p.entry, t1: p.t1, t2: p.t2, sl: p.sl,
        signal: r.signal, result: 'open',
      });
    });
    setRecorded(true);
  }

  return (
    <div style={{
      marginBottom: 22, border: '1px solid var(--gold)', background: 'var(--paper-bright)',
      boxShadow: '4px 4px 0 rgba(184,146,77,.12)',
    }}>
      <div style={{
        display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap',
        padding: '12px 18px', borderBottom: '1px solid var(--paper-line)',
        background: 'rgba(184,146,77,0.06)',
      }}>
        <span style={{ fontSize: 17 }}>⭐</span>
        <span className="kai" style={{ fontSize: 16, letterSpacing: '.1em', color: 'var(--ink)', fontWeight: 700 }}>
          黃金交叉智選組合
        </span>
        <span style={{ fontFamily: 'Times New Roman,serif', fontStyle: 'italic', fontSize: 12, color: 'var(--ink-mute)' }}>
          Smart Combo · 分數最高 × 產業分散
        </span>
        <span style={{ marginLeft: 'auto', fontFamily: 'Times New Roman,serif', fontSize: 12, color: 'var(--gold-deep)' }}>
          {combo.length} 檔 · {industryCount} 族群 · 均分 {avgScore}
        </span>
        <button
          onClick={recordAll}
          disabled={recorded}
          style={{
            fontSize: 'var(--fs-nano)', padding: '5px 12px', border: '1px solid var(--gold)',
            background: recorded ? 'var(--paper-deep)' : 'var(--gold)',
            color: recorded ? 'var(--ink-mute)' : 'var(--ink)',
            cursor: recorded ? 'default' : 'pointer', fontFamily: 'BiauKai,標楷體,serif', letterSpacing: '.06em',
          }}>
          {recorded ? '✓ 已全部記錄' : '📌 全部記錄追蹤'}
        </button>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(210px, 1fr))', gap: 10, padding: 14 }}>
        {combo.map(function(r) {
          var p = longTradePlan(r);
          var g = r.grade;
          return (
            <a key={r.code} href={'個股技術分析.html?code=' + r.code + '&tab=signal&entry=' + p.entry + '&t1=' + p.t1 + '&t2=' + p.t2 + '&sl=' + p.sl}
               style={{ textDecoration: 'none', color: 'inherit' }}>
              <div style={{
                border: '1px solid var(--paper-line)', borderLeft: '3px solid ' + (g ? g.border : 'var(--gold)'),
                background: 'var(--paper)', padding: '10px 12px', height: '100%',
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
                  {g && (
                    <span style={{
                      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
                      width: 20, height: 20, borderRadius: 3, fontFamily: 'Times New Roman,serif',
                      fontWeight: 700, fontSize: 12, color: g.color, background: g.bg, border: '1px solid ' + g.border,
                    }}>{g.letter}</span>
                  )}
                  <span style={{ fontFamily: 'Times New Roman,serif', fontWeight: 700, fontSize: 15 }}>{r.code}</span>
                  <span className="kai" style={{ fontSize: 13, color: 'var(--ink)' }}>{r.nameZh || r.name}</span>
                  <span style={{ marginLeft: 'auto', fontFamily: 'Times New Roman,serif', fontSize: 12, color: 'var(--gold-deep)' }}>{r.score}</span>
                </div>
                <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap', marginBottom: 6 }}>
                  {(r.sector || r.indName) && (
                    <span className="kai" style={{ fontSize: 'var(--fs-nano)', padding: '1px 5px', border: '1px solid var(--paper-line)', color: 'var(--ink-mute)' }}>
                      {r.sector || r.indName}
                    </span>
                  )}
                  {r.fine && r.fine.label && (
                    <span className="kai" style={{ fontSize: 'var(--fs-nano)', padding: '1px 5px', color: 'var(--ink-mute)' }}>{r.fine.label}</span>
                  )}
                </div>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontFamily: 'Times New Roman,serif', fontSize: 'var(--fs-nano)' }}>
                  <span style={{ color: 'var(--ink-mute)' }}>進 <b style={{ color: 'var(--ink)' }}>{p.entry}</b></span>
                  <span style={{ color: 'var(--bull)' }}>利 {p.t1}</span>
                  <span style={{ color: 'var(--bear)' }}>損 {p.sl}</span>
                  {p.rr != null && <span style={{ color: 'var(--ink-mute)' }}>RR {p.rr}</span>}
                </div>
              </div>
            </a>
          );
        })}
      </div>
    </div>
  );
}

function ResultRow({ r, colSpan }) {
  var [expanded, setExpanded] = useState(false);
  var sig = signalMeta[r.signal];
  var cols = colSpan || 7;
  return (
    <>
      <tr onClick={function() { setExpanded(function(v) { return !v; }); }}
          data-code={r.code}
          style={{ cursor:'pointer' }}>
        <td className="code-cell">
          <div style={{ display:'flex', alignItems:'center', gap:6 }}>
            {r.grade && (
              <span title={'黃金交叉品質 ' + r.grade.score + '/100'} style={{
                display:'inline-flex', alignItems:'center', justifyContent:'center',
                width:20, height:20, borderRadius:3, fontFamily:'Times New Roman,serif',
                fontWeight:700, fontSize:12, color:r.grade.color,
                background:r.grade.bg, border:'1px solid ' + r.grade.border, flexShrink:0,
              }}>{r.grade.letter}</span>
            )}
            <span>{r.code}</span>
          </div>
          <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:2 }}>
            {r.mkt}{r.grade ? ' · ' + r.grade.score : ''}
          </div>
        </td>
        <td>
          <span className="kai" style={{ fontSize:15, letterSpacing:'.04em' }}>{r.nameZh || r.name}</span>
          {r.nameZh && r.nameEn && (
            <span style={{ display:'block', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic', fontFamily:'Times New Roman,serif', letterSpacing:'.03em' }}>
              {r.nameEn}
            </span>
          )}
          <div style={{ display:'flex', gap:4, marginTop:4, flexWrap:'wrap', alignItems:'center' }}>
            {/* 子產業 node（最精確，來自 data.js） */}
            {r.nodeTitle && (
              <span className="kai" style={{
                fontSize:'var(--fs-nano)', padding:'1px 7px',
                border:'1px solid var(--ink)',
                color:'var(--ink)', background:'transparent',
                letterSpacing:'.04em', fontWeight:'bold',
              }}>
                {r.nodeTitle}
                {r.segment && (
                  <span style={{ fontWeight:'normal', color:'var(--ink-mute)', marginLeft:3 }}>
                    · {r.segment}
                  </span>
                )}
              </span>
            )}
            {/* TWSE 官方產業別（較廣）— 優先 sector，fallback indName */}
            {(r.sector || r.indName) && (
              <span className="kai" style={{
                fontSize:'var(--fs-nano)', padding:'1px 6px',
                border:'1px solid var(--paper-line)',
                color:'var(--ink-mute)', background:'var(--paper-bright)',
                letterSpacing:'.04em',
              }}>
                {r.sector || r.indName}
              </span>
            )}
            {/* 大分類（最廣） */}
            {r.cat && CAT_META[r.cat] && (
              <span className="kai" style={{
                fontSize:'var(--fs-nano)', padding:'1px 6px',
                border:'1px solid ' + CAT_META[r.cat].color,
                color: CAT_META[r.cat].color,
                background: CAT_META[r.cat].bg,
                letterSpacing:'.04em',
              }}>
                {CAT_META[r.cat].label}
              </span>
            )}
          </div>
        </td>
        <td>
          <span className="sig-pill" style={{ color: sig.color, background: sig.bg, borderColor: sig.color }}>
            <span className="kai">{sig.label}</span>
            <span className="sig-pill-day">
              {(r.signal === 'crossed' || r.signal === 'crossed-dead') ? `-${r.daysAgo}d` : `+${r.daysToX}d`}
            </span>
          </span>
          {r.fine && r.fine.label && (
            <span className="kai" style={{ display:'block', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:3, letterSpacing:'.03em' }}>
              {r.fine.label}
            </span>
          )}
        </td>
        <td className="right num-cell">
          {r.price.toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2})}
          {r.delayed && (
            <span title="TradingView 免費資料：上市股盤中延遲 15 分鐘" style={{
              display:'block', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic',
              fontFamily:'Times New Roman,serif',
            }}>延15分</span>
          )}
        </td>
        <td className={`right num-cell ${r.chg >= 0 ? 'bull-c' : 'bear-c'}`}>
          {r.chg >= 0 ? '+' : ''}{r.chg.toFixed(2)}%
        </td>
        <td className="right num-cell" style={{ fontSize:12, color:'var(--ink-mute)' }}>{r.vol}</td>
        <td className={`right num-cell ${r.macdDiff >= 0 ? 'bull-c' : 'bear-c'}`}>
          <span style={{ display:'inline-flex', alignItems:'center', gap:5 }}>
            {r.histColor && HIST_META[r.histColor] && (
              <span title={HIST_META[r.histColor].label} style={{
                display:'inline-block', width:8, height:14, borderRadius:2,
                background: HIST_META[r.histColor].color,
                flexShrink:0,
              }}/>
            )}
            {r.macdDiff >= 0 ? '+' : ''}{r.macdDiff.toFixed(2)}
          </span>
          <span style={{ display:'block', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:2, fontStyle:'italic' }}>
            {expanded ? '收起 ▲' : '展開 ▼'}
          </span>
        </td>
      </tr>
      {expanded && <MACDExpandedRow r={r} colSpan={cols} />}
    </>
  );
}

function ZoneSection({ title, titleEn, badge, rows, accentColor }) {
  if (rows.length === 0) return null;
  return (
    <div style={{ marginBottom: 28 }}>
      <div style={{
        display:'flex', alignItems:'center', gap: 12,
        padding: '10px 18px', marginBottom: 0,
        background: accentColor + '18',
        borderLeft: `3px solid ${accentColor}`,
        borderRadius: '2px 2px 0 0',
      }}>
        <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:18, color: accentColor }}>{badge}</span>
        <span className="kai" style={{ fontSize:15, color:'var(--ink)', letterSpacing:'.12em' }}>{title}</span>
        <span style={{ fontStyle:'italic', fontSize:12, color:'var(--ink-mute)', marginLeft:4 }}>{titleEn}</span>
        <span style={{
          marginLeft:'auto', fontFamily:'Times New Roman,serif', fontSize:12,
          color: accentColor, background: accentColor+'22',
          padding:'2px 10px', borderRadius:2
        }}>{rows.length} hits</span>
      </div>
      <table className="ed-table scan-table" style={{ marginTop:0 }}>
        <thead>
          <tr>
            <th>Code</th>
            <th>Name · 產業</th>
            <th>Signal</th>
            <th className="right">Price</th>
            <th className="right">Chg %</th>
            <th className="right">Volume</th>
            <th className="right">MACD Diff</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(function(r) { return <ResultRow key={r.code} r={r} colSpan={7} />; })}
        </tbody>
      </table>
    </div>
  );
}

// ── Kelly 公式計算 ─────────────────────────────────────────────
function calcKelly(records) {
  var closed = records.filter(function(r) { return r.result === 'win' || r.result === 'loss'; });
  if (closed.length < 5) return null;

  var wins   = closed.filter(function(r) { return r.result === 'win'; });
  var losses = closed.filter(function(r) { return r.result === 'loss'; });

  var W = wins.length / closed.length;
  var L = 1 - W;

  var avgWin  = wins.length   ? wins.reduce(function(s,r)   { return s + Math.abs(r.rtn||0); }, 0) / wins.length   : 0;
  var avgLoss = losses.length ? losses.reduce(function(s,r) { return s + Math.abs(r.rtn||0); }, 0) / losses.length : 0;

  if (!avgLoss || !avgWin) return null;
  var B = avgWin / avgLoss;

  var fullKelly = Math.max(0, W - L / B);
  var halfKelly = fullKelly / 2;

  return {
    W:          +(W * 100).toFixed(1),
    B:          +B.toFixed(2),
    avgWin:     +avgWin.toFixed(2),
    avgLoss:    +avgLoss.toFixed(2),
    fullKelly:  +(fullKelly * 100).toFixed(1),
    halfKelly:  +(halfKelly * 100).toFixed(1),
    sampleSize: closed.length,
  };
}

// ── 績效追蹤面板 ─────────────────────────────────────────────
function PerfPanel({ scanResults }) {
  var [open, setOpen]       = useState(false);
  var [records, setRecords] = useState(loadPerfRecords);
  var [tick, setTick]       = useState(0);

  // 每次展開或 scanResults 有資料時，批次更新 currentPrice + result
  useEffect(function() {
    if (!records.length) return;
    var priceMap = {};
    (scanResults || []).forEach(function(r) { priceMap[r.code] = r.price; });
    if (!Object.keys(priceMap).length) return;
    var updated = false;
    var next = records.map(function(p) {
      var cp = priceMap[p.code];
      if (cp == null) return p;
      var rtn    = +((cp - p.entryPrice) / p.entryPrice * 100).toFixed(2);
      if (p.signal === 'crossed-dead') rtn = +((p.entryPrice - cp) / p.entryPrice * 100).toFixed(2);
      var result = p.result === 'win' || p.result === 'loss' ? p.result : 'open';
      if (result === 'open') {
        if (p.signal === 'crossed-dead') {
          if (cp <= p.t1) result = 'win';
          else if (cp >= p.sl) result = 'loss';
        } else {
          if (cp >= p.t1) result = 'win';
          else if (cp <= p.sl) result = 'loss';
        }
      }
      var closedAt = (result !== 'open' && !p.closedAt) ? todayStr() : p.closedAt;
      var changed = p.currentPrice !== cp || p.rtn !== rtn || p.result !== result;
      if (changed) updated = true;
      return Object.assign({}, p, { currentPrice: cp, rtn: rtn, result: result, closedAt: closedAt });
    });
    if (updated) { savePerfRecords(next); setRecords(next); }
  }, [scanResults, tick]);

  // 展開時重新載入（以防其他展開列有新記錄）
  function handleToggle() {
    if (!open) { setRecords(loadPerfRecords()); setTick(function(t) { return t + 1; }); }
    setOpen(function(v) { return !v; });
  }

  function handleClear() {
    if (!window.confirm('確認清除所有績效記錄？')) return;
    savePerfRecords([]);
    setRecords([]);
  }

  var winCount  = records.filter(function(p) { return p.result === 'win'; }).length;
  var lossCount = records.filter(function(p) { return p.result === 'loss'; }).length;
  var openCount = records.filter(function(p) { return p.result === 'open'; }).length;
  var closedRtns = records.filter(function(p) { return p.result !== 'open' && p.rtn != null; }).map(function(p) { return p.rtn; });
  var avgRtn = closedRtns.length ? +(closedRtns.reduce(function(s,v) { return s+v; }, 0) / closedRtns.length).toFixed(1) : null;
  var winRate = (winCount + lossCount) > 0 ? +((winCount / (winCount + lossCount)) * 100).toFixed(1) : null;

  var kelly = calcKelly(records);

  var resultLabel = { win: '達標 ✓', loss: '觸停 ✗', open: '持倉中 ●' };
  var resultColor = { win: 'var(--bull)', loss: 'var(--bear)', open: 'var(--gold)' };

  return (
    <div style={{
      position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 900,
      fontFamily: 'BiauKai,標楷體,serif',
      boxShadow: '0 -2px 12px rgba(26,36,56,0.18)',
    }}>
      {/* Header bar — always visible */}
      <div
        onClick={handleToggle}
        style={{
          background: 'var(--ink)', color: 'var(--paper)', padding: '6px 16px',
          display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
          userSelect: 'none',
        }}>
        <span style={{ color: 'var(--gold)', fontSize: 14, fontWeight: 700, letterSpacing: '.08em' }}>
          MACD 策略績效追蹤
        </span>
        {records.length > 0 && (
          <span style={{ fontSize: 12, color: 'rgba(246,241,228,0.7)' }}>
            {records.length} 筆
            {winRate !== null && (' · 勝率 ' + winRate + '%')}
            {avgRtn !== null && (' · 均報酬 ' + (avgRtn >= 0 ? '+' : '') + avgRtn + '%')}
          </span>
        )}
        <span style={{ marginLeft: 'auto', fontSize: 12, color: 'rgba(246,241,228,0.5)' }}>
          {open ? '▼ 收起' : '▲ 展開'}
        </span>
      </div>

      {/* Panel body */}
      {open && (
        <div style={{
          background: 'var(--paper)', borderTop: '2px solid var(--gold)',
          maxHeight: 320, overflowY: 'auto',
          padding: '10px 16px 12px',
        }}>
          {/* Summary row */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 16, marginBottom: 8, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 12, color: 'var(--ink)' }}>
              已記錄 <b>{records.length}</b> 筆
              {' · '}<span style={{ color: 'var(--bull)' }}>勝 {winCount}</span>
              {' / '}<span style={{ color: 'var(--bear)' }}>敗 {lossCount}</span>
              {' / '}<span style={{ color: 'var(--gold)' }}>持倉中 {openCount}</span>
              {winRate !== null && (' · 勝率 ' + winRate + '%')}
              {avgRtn !== null && (' · 平均報酬 ' + (avgRtn >= 0 ? '+' : '') + avgRtn + '%')}
            </span>
            {records.length > 0 && (
              <button
                onClick={handleClear}
                style={{
                  marginLeft: 'auto', fontSize: 'var(--fs-nano)', padding: '2px 8px',
                  border: '1px solid var(--bear)', borderRadius: 3,
                  background: 'transparent', color: 'var(--bear)', cursor: 'pointer',
                  fontFamily: 'BiauKai,標楷體,serif',
                }}>
                清除全部
              </button>
            )}
          </div>

          {/* Kelly 公式部位建議 */}
          {kelly && (
            <div style={{
              marginTop: 8, padding: '8px 12px',
              background: 'rgba(184,146,77,0.08)',
              border: '1px solid rgba(184,146,77,0.3)',
              borderRadius: 4,
              display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap',
            }}>
              <span style={{ fontFamily:'BiauKai,標楷體,serif', fontSize:12, color:'var(--gold)' }}>
                Kelly 公式
              </span>
              <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink)', fontFamily:'Times New Roman,serif' }}>
                W={kelly.W}% · B={kelly.B} · 均盈/虧={kelly.avgWin}%/{kelly.avgLoss}%
              </span>
              <span style={{ fontSize:'var(--fs-nano)' }}>
                <span style={{ color:'var(--ink-mute)' }}>全Kelly </span>
                <span style={{ color:'var(--gold)', fontWeight:700 }}>{kelly.fullKelly}%</span>
                <span style={{ color:'var(--ink-mute)', marginLeft:8 }}>½Kelly（建議）</span>
                <span style={{ color:'var(--bull)', fontWeight:700 }}> {kelly.halfKelly}%</span>
              </span>
              <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>
                樣本 {kelly.sampleSize} 筆（需≥5筆才顯示）
              </span>
            </div>
          )}
          {!kelly && records.length >= 1 && records.filter(function(r) { return r.result==='win'||r.result==='loss'; }).length < 5 && (
            <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:4, fontStyle:'italic' }}>
              Kelly 公式需至少 5 筆已平倉記錄才顯示建議（目前 {records.filter(function(r) { return r.result==='win'||r.result==='loss'; }).length} 筆）
            </div>
          )}

          {records.length === 0 ? (
            <div style={{ textAlign: 'center', color: 'var(--ink-mute)', fontSize: 12, padding: '16px 0' }}>
              尚無記錄 — 在展開列點「📌 記錄」即可追蹤進場後表現
            </div>
          ) : (
            <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
              <thead>
                <tr style={{ borderBottom: '1px solid var(--paper-line)', color: 'var(--ink-mute)', fontSize: 'var(--fs-nano)' }}>
                  <th style={{ textAlign:'left',  padding:'3px 6px', fontWeight:400 }}>代號</th>
                  <th style={{ textAlign:'left',  padding:'3px 6px', fontWeight:400 }}>名稱</th>
                  <th style={{ textAlign:'center',padding:'3px 6px', fontWeight:400 }}>訊號日</th>
                  <th style={{ textAlign:'right', padding:'3px 6px', fontWeight:400 }}>進場</th>
                  <th style={{ textAlign:'right', padding:'3px 6px', fontWeight:400 }}>目標T1</th>
                  <th style={{ textAlign:'right', padding:'3px 6px', fontWeight:400 }}>停損</th>
                  <th style={{ textAlign:'right', padding:'3px 6px', fontWeight:400 }}>現價</th>
                  <th style={{ textAlign:'right', padding:'3px 6px', fontWeight:400 }}>報酬%</th>
                  <th style={{ textAlign:'center',padding:'3px 6px', fontWeight:400 }}>狀態</th>
                </tr>
              </thead>
              <tbody>
                {records.map(function(p) {
                  var rtnColor = p.rtn == null ? 'var(--ink-mute)' : (p.rtn >= 0 ? 'var(--bull)' : 'var(--bear)');
                  var res = p.result || 'open';
                  return (
                    <tr key={p.id} style={{ borderBottom: '1px solid rgba(26,36,56,0.06)', lineHeight: '32px' }}>
                      <td style={{ padding:'0 6px', color:'var(--ink)', fontFamily:'Times New Roman,serif' }}>{p.code}</td>
                      <td style={{ padding:'0 6px', color:'var(--ink)', maxWidth:80, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{p.name}</td>
                      <td style={{ padding:'0 6px', color:'var(--ink-mute)', textAlign:'center' }}>{p.signalDate ? p.signalDate.slice(5).replace('-','/') : '—'}</td>
                      <td style={{ padding:'0 6px', textAlign:'right', fontFamily:'Times New Roman,serif' }}>{p.entryPrice.toLocaleString()}</td>
                      <td style={{ padding:'0 6px', textAlign:'right', fontFamily:'Times New Roman,serif', color:'var(--bull)' }}>{p.t1.toLocaleString()}</td>
                      <td style={{ padding:'0 6px', textAlign:'right', fontFamily:'Times New Roman,serif', color:'var(--bear)' }}>{p.sl.toLocaleString()}</td>
                      <td style={{ padding:'0 6px', textAlign:'right', fontFamily:'Times New Roman,serif', color:'var(--ink)' }}>{p.currentPrice != null ? p.currentPrice.toLocaleString() : '—'}</td>
                      <td style={{ padding:'0 6px', textAlign:'right', fontFamily:'Times New Roman,serif', color: rtnColor }}>
                        {p.rtn != null ? (p.rtn >= 0 ? '+' : '') + p.rtn + '%' : '—'}
                      </td>
                      <td style={{ padding:'0 6px', textAlign:'center', color: resultColor[res] }}>{resultLabel[res]}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
          {/* Kelly 說明 */}
          <div style={{ marginTop:10, fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic', lineHeight:1.6 }}>
            ½Kelly 為實務建議倉位比例，可依風險承受度調整。W=勝率、B=賠率（均盈/均虧）。
          </div>
        </div>
      )}
    </div>
  );
}

function ScanApp({ embedded }) {
  var [codeSearch,   setCodeSearch]   = useState('');
  var [signalFilter, setSignalFilter] = useState('imminent');
  var [zeroFilter,   setZeroFilter]   = useState('above');
  var [catFilter,    setCatFilter]    = useState('pillar+frontier');
  var [indFilter,    setIndFilter]    = useState('all');
  var [histFilter,   setHistFilter]   = useState('all');
  var [activePreset, setActivePreset] = useState('golden-prime');

  function applyPreset(preset) {
    setSignalFilter(preset.filters.signal);
    setZeroFilter(preset.filters.zero || 'all');
    setCatFilter(preset.filters.cat);
    setIndFilter(preset.filters.ind);
    setHistFilter(preset.filters.hist || 'all');
    setActivePreset(preset.id);
  }

  // 智能聯動：黃金訊號自動切到 0軸以上，死叉切到以上（逃頂更常見）
  useEffect(function() {
    if (activePreset) return;
    if (signalFilter === 'crossed' || signalFilter === 'imminent') {
      if (zeroFilter === 'below') setZeroFilter('all');
    }
    if (signalFilter === 'crossed-dead' || signalFilter === 'imminent-dead') {
      if (zeroFilter === 'below') setZeroFilter('all');
    }
  }, [signalFilter]);
  function clearPreset() { setActivePreset(null); }
  var [scanning, setScanning] = useState(false);
  var [scannedCount, setScannedCount] = useState(0);
  var [scanResults, setScanResults] = useState([]);
  var [totalUniverse, setTotalUniverse] = useState(0);
  var [updatedAt, setUpdatedAt] = useState('');
  var [error, setError] = useState('');
  var [fromCache, setFromCache] = useState(false);
  var [histDays, setHistDays] = useState(0);

  // 同步從 localStorage 初始化 sectorMap，確保 runScan 第一次執行時就有產業資料
  var [sectorMap, setSectorMap] = useState(function() {
    var SECTOR_KEY = 'tw_sector_v3';
    try {
      var cached = JSON.parse(localStorage.getItem(SECTOR_KEY));
      if (cached && cached._ts && Date.now() - cached._ts < 30 * 86400 * 1000) return cached;
    } catch (_) {}
    return {};
  });

  var SCAN_CACHE_KEY = 'tw_scan_cache_v2';  // v2 = 真實 MACD 快取，廢棄舊 v1

  // 背景補充：若 localStorage 沒有 sectorMap，非同步 fetch
  useEffect(function() {
    var SECTOR_KEY = 'tw_sector_v3';
    if (Object.keys(sectorMap).length > 1) return;
    fetch('/industry_codes.json')
      .then(function(r) { return r.json(); })
      .then(function(data) {
        data._ts = Date.now();
        setSectorMap(data);
        try { localStorage.setItem(SECTOR_KEY, JSON.stringify(data)); } catch (_) {}
      })
      .catch(function() {});
  }, []);

  // sectorMap 到位後重新分類：修正首次掃描時官方產業資料還沒載入而暫用猜測的結果
  useEffect(function() {
    if (Object.keys(sectorMap).length <= 1) return;
    var indNmMap = getIndNameMap();
    setScanResults(function(prev) {
      if (!prev.length) return prev;
      return prev.map(function(r) {
        var ci = guessCatInd(r.nameEn || r.name, r.code, sectorMap);
        return Object.assign({}, r, {
          cat: ci.category, ind: ci.indId,
          sector: sectorMap[r.code] || r.sector || '',
          indName: ci.indId ? (indNmMap[ci.indId] || '') : (r.indName || ''),
        });
      });
    });
  }, [sectorMap]);

  // 頁面載入時就預拉 TWSE 全市場中文名（讓 getNameMap 在掃描前就準備好）
  useEffect(function() {
    var LIST_KEY = 'tw_list_cache_v1';
    var LIST_DATE_KEY = 'tw_list_cache_date';
    var today = new Date().toISOString().slice(0, 10);
    if (localStorage.getItem(LIST_DATE_KEY) === today) return; // 今天已更新
    fetchListOnce()
      .then(function(list) {
        if (!Array.isArray(list) || list.length < 100) return;
        try {
          localStorage.setItem(LIST_KEY, JSON.stringify(list));
          localStorage.setItem(LIST_DATE_KEY, today);
        } catch (_) {}
        _nameMap = null;  // 讓下次 getNameMap() 重建
        _nodeMap = null;  // 同步清除，避免子產業標籤與名稱不一致
        // 若已有掃描結果，補上中文名
        setScanResults(function(prev) {
          if (!prev.length) return prev;
          var nm = getNameMap();
          return prev.map(function(r) {
            var zhName = nm[r.code] || '';
            return zhName && !r.nameZh
              ? Object.assign({}, r, { nameZh: zhName, name: zhName })
              : r;
          });
        });
      })
      .catch(function() {});
  }, []);

  var runScan = useCallback(function() {
    setScanning(true);
    setError('');
    setScannedCount(0);
    setScanResults([]);
    setTotalUniverse(0);

    // 預先快取 TWSE 中文名稱（只要 localStorage 沒有就拉；fetchListOnce 保證與
    // 頁面載入的預拉共用同一個 in-flight 請求，不會重複打全市場清單）
    if (!localStorage.getItem('tw_list_cache_v1')) {
      fetchListOnce()
        .then(function(list) {
          if (Array.isArray(list)) {
            try { localStorage.setItem('tw_list_cache_v1', JSON.stringify(list)); } catch (_) {}
            _nameMap = null; // reset cache so next access picks up new data
          }
        }).catch(function() {});
    }

    // 單次呼叫 TradingView Scanner — 後端已計算 MACD(12,26,9) 1D
    var scanCtrl = new AbortController();
    var scanTid  = setTimeout(function() { scanCtrl.abort(); }, 15000);
    fetch('/api/stock?type=tw_macd_scan', { signal: scanCtrl.signal })
      .then(function(r) {
        clearTimeout(scanTid);
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      })
      .then(function(data) {
        if (!Array.isArray(data)) throw new Error('bad data');

        setTotalUniverse(data.length);
        setScannedCount(data.length);

        // 補充產業分類 + 中文名稱（TradingView 回傳英文，覆蓋為中文）
        var nm       = getNameMap();
        var indNmMap = getIndNameMap();
        var nodeMap  = getNodeMap();
        var results = data.map(function(item) {
          var ci      = guessCatInd(item.name, item.code, sectorMap);
          var zhName  = nm[item.code] || '';
          var sector  = sectorMap[item.code] || '';
          var indName = ci.indId ? (indNmMap[ci.indId] || '') : '';
          var nd      = nodeMap[item.code] || {};
          return Object.assign({}, item, {
            cat:          ci.category,
            ind:          ci.indId,
            nameZh:       zhName,
            nameEn:       item.name,
            name:         zhName || item.name,
            sector:       sector,
            indName:      indName,
            nodeTitle:    nd.nodeTitle    || '',
            nodeTitleEn:  nd.nodeTitleEn  || '',
            segment:      nd.segment      || '',
            fine:         fineSignal(item),
          });
        });

        scoreAndSortRows(results);   // 補 fine/score/grade + 排序（買訊按品質分數）

        var now    = new Date();
        var nowStr = now.toLocaleTimeString('en-US', { hour12: false });
        var label  = (now.getMonth()+1) + '/' + now.getDate() + ' ' + nowStr;

        if (results.length > 0) {
          setScanResults(results);
          setUpdatedAt(nowStr);
          setFromCache(false);
          try { localStorage.setItem(SCAN_CACHE_KEY, JSON.stringify({ results: results, updatedAt: label })); } catch (_) {}
        } else {
          try {
            var cached = JSON.parse(localStorage.getItem(SCAN_CACHE_KEY));
            if (cached && cached.results && cached.results.length > 0) {
              setScanResults(scoreAndSortRows(cached.results.slice()));
              setUpdatedAt(cached.updatedAt);
              setFromCache(true);
            } else {
              setScanResults([]);
              setUpdatedAt(nowStr);
              setFromCache(false);
            }
          } catch (_) { setScanResults([]); }
        }
        setScanning(false);
      })
      .catch(function(err) {
        clearTimeout(scanTid);
        try {
          var cached = JSON.parse(localStorage.getItem(SCAN_CACHE_KEY));
          if (cached && cached.results && cached.results.length > 0) {
            setScanResults(scoreAndSortRows(cached.results.slice()));
            setUpdatedAt(cached.updatedAt);
            setFromCache(true);
          }
        } catch (_) {}
        var msg = err.name === 'AbortError' ? '掃描逾時 (30s)，請稍後重試' : '無法取得資料：' + err.message;
        setError(msg);
        setScanning(false);
      });
  }, [sectorMap]);

  // 不自動掃描 — 等用戶點按鈕，避免頁面載入時 button 卡在 disabled
  useEffect(function() {
    // 有快取就先顯示，不擋住 UI
    try {
      var cached = JSON.parse(localStorage.getItem(SCAN_CACHE_KEY));
      if (cached && cached.results && cached.results.length > 0) {
        setScanResults(scoreAndSortRows(cached.results.slice()));
        setUpdatedAt(cached.updatedAt);
        setFromCache(true);
        setTotalUniverse(cached.results.length);
        setScannedCount(cached.results.length);
      }
    } catch (_) {}
  }, []);

  // URL ?highlight=2330 → 自動捲動到對應列
  useEffect(function() {
    var hl = new URLSearchParams(window.location.search).get('highlight');
    if (!hl || !scanResults.length) return;
    var idx = filtered.findIndex(function(r) { return r.code === hl; });
    if (idx >= 0) {
      setTimeout(function() {
        var el = document.querySelector('[data-code="' + hl + '"]');
        if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
      }, 300);
    }
  }, [scanResults]);

  var filtered = useMemo(function() {
    var result = scanResults.filter(function(r) {
      var sigOk  = signalFilter === 'all' || r.signal === signalFilter;
      var zeroOk = zeroFilter === 'all' ||
                   (zeroFilter === 'above' && r.aboveZero) ||
                   (zeroFilter === 'below' && !r.aboveZero);
      var catOk  = catFilter === 'all' ||
                   r.cat === catFilter ||
                   (catFilter === 'pillar+frontier' && (r.cat === 'pillar' || r.cat === 'frontier'));
      var indOk  = indFilter === 'all' || r.ind === indFilter;
      var histOk = histFilter === 'all' || r.histColor === histFilter;
      return sigOk && zeroOk && catOk && indOk && histOk;
    });
    if (codeSearch) {
      var q = codeSearch.toUpperCase();
      result = result.filter(function(r) {
        return r.code.includes(q) || (r.name || '').includes(codeSearch) || (r.nameZh || '').includes(codeSearch);
      });
    }
    return result;
  }, [scanResults, signalFilter, zeroFilter, catFilter, indFilter, codeSearch]);

  // Sub-industry pills for selected category
  var subIndustries = useMemo(function() {
    if (catFilter === 'all') return [];
    return getIndustryList().filter(function(i) { return i.category === catFilter; });
  }, [catFilter]);

  var aboveRows = useMemo(function() { return filtered.filter(function(r) { return r.aboveZero; }); }, [filtered]);
  var belowRows = useMemo(function() { return filtered.filter(function(r) { return !r.aboveZero; }); }, [filtered]);

  // Desk Strip：發佈訊號掃描狀態到四分頁共用狀態帶（雷達區嵌入時）
  useEffect(function() {
    if (!window.QhStrip) return;
    window.QhStrip.set('signals', [
      { label: '宇宙', value: totalUniverse > 0 ? totalUniverse.toLocaleString('en-US') : '—' },
      { label: '掃描', value: scannedCount.toLocaleString('en-US') },
      { label: '命中', value: filtered.length, tone: 'gold' },
      { label: '0軸以上', value: aboveRows.length, tone: 'up' },
      { label: '0軸以下', value: belowRows.length, tone: 'dn' },
    ]);
  }, [totalUniverse, scannedCount, filtered.length, aboveRows.length, belowRows.length]);

  // When zero filter is set, show flat list; when 'all', show two sections
  var showSections = zeroFilter === 'all';

  return (
    <div className={embedded ? '' : 'shell'}>
      {!embedded && <window.Masthead />}
      {!embedded && <window.TickerTape />}
      {!embedded && <window.NavBar active="scan" />}

      <section className="cover" style={{ paddingTop: embedded ? 12 : 32, marginBottom: 18 }}>
        <div>
          <div className="eyebrow">Cross Radar · 訊號雷達</div>
          <h1 style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            {window.GoldenCrossIcon && (
              <window.GoldenCrossIcon size={30} color="var(--gold-deep)" strokeWidth={1.4} />
            )}
            <span>
              <span style={{ fontStyle:'italic' }}>Golden</span> Cross <span className="amp">&amp;</span> Death
              <span className="kai-title">黃 金 交 叉 雷 達</span>
            </span>
          </h1>
          <p className="cover-lede">
            掃描全市場 {totalUniverse > 0 ? totalUniverse.toLocaleString('en-US') : '…'} 檔個股,以 <b style={{ color:'var(--ink)' }}>MACD(12,26,9)</b> 判定
            <b style={{ color:'var(--ink)' }}> DIF / DEA</b> 是否即將或已產生黃金 / 死亡交叉,並依 <b style={{ color:'var(--ink)' }}>0 軸位置</b> 分層顯示。
            <span style={{ display:'block', marginTop:6, fontSize:12, color:'var(--ink-mute)', fontStyle:'italic' }}>
              訊號以<b style={{ color:'var(--ink)' }}>已收盤日 K</b> 為準(對齊 TradingView 確認交叉);盤中不提前計入今日未完成 K,故不會誤報今日剛交叉。上市股報價為 TradingView 15 分延遲。
            </span>
          </p>
        </div>
        <div className="cover-side">
          <h3>Scanner Status <span className="kai" style={{fontSize:13, color:'var(--ink-mute)', letterSpacing:'.18em'}}>· 雷達狀態</span></h3>
          <div className="scan-status">
            <div className="scan-row">
              <span className="scan-lbl">Universe</span>
              <span className="scan-val num">{totalUniverse > 0 ? totalUniverse.toLocaleString('en-US') : '—'}</span>
            </div>
            <div className="scan-row">
              <span className="scan-lbl">Scanned</span>
              <span className="scan-val num">{scannedCount.toLocaleString('en-US')}</span>
            </div>
            <div className="scan-row">
              <span className="scan-lbl">Hits</span>
              <span className="scan-val num bull-c">{filtered.length}</span>
            </div>
            <div className="scan-row">
              <span className="scan-lbl">0軸以上</span>
              <span className="scan-val num" style={{ color:'var(--bull)' }}>{aboveRows.length}</span>
            </div>
            <div className="scan-row">
              <span className="scan-lbl">0軸以下</span>
              <span className="scan-val num" style={{ color:'var(--bear)' }}>{belowRows.length}</span>
            </div>
            <div className="scan-row">
              <span className="scan-lbl">MACD 資料</span>
              <span className="scan-val num" style={{ color: 'var(--bull)' }}>
                TradingView 1D
              </span>
            </div>
            {updatedAt && (
              <div className="scan-row">
                <span className="scan-lbl">{fromCache ? '上次交易日' : 'Updated'}</span>
                <span className="scan-val" style={{ fontSize: 12, fontStyle: 'italic',
                  color: fromCache ? 'var(--gold-deep)' : 'var(--ink-mute)' }}>
                  {updatedAt}{fromCache ? ' · 閉市中' : ''}
                </span>
              </div>
            )}
            <div className="scan-progress">
              <div className="scan-progress-fill"
                   style={{ width: totalUniverse > 0 ? `${Math.min(100, scannedCount / totalUniverse * 100)}%` : '0%' }}/>
            </div>
            {error && <div style={{ fontSize: 12, color: 'var(--bear)', marginBottom: 8 }}>{error}</div>}
            <button className="scan-button" disabled={scanning} onClick={runScan}>
              {scanning ? '掃描中 · scanning…' : '重新掃描 · re-scan'}
            </button>
          </div>
        </div>
      </section>

      {/* Smart Presets */}
      <div style={{ marginBottom: 18 }}>
        <div style={{
          display:'flex', alignItems:'baseline', gap:10, marginBottom:12,
          borderBottom:'1px solid var(--paper-line)', paddingBottom:8,
        }}>
          <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:15, color:'var(--ink)' }}>Smart Presets</span>
          <span className="kai" style={{ fontSize:13, color:'var(--ink-mute)', letterSpacing:'.12em' }}>智選組合</span>
        </div>
        <div style={{ display:'grid', gridTemplateColumns:'repeat(2,1fr)', gap:10 }}>
          {PRESETS.map(function(p) {
            var isActive = activePreset === p.id;
            return (
              <div key={p.id}
                   onClick={function() { applyPreset(p); }}
                   style={{
                     padding:'14px 16px', cursor:'pointer',
                     border: isActive ? '2px solid var(--gold-deep)' : '1px solid var(--paper-line)',
                     background: isActive ? 'var(--paper)' : 'var(--paper-bright)',
                     transition:'all .15s',
                     position:'relative',
                   }}>
                {p.star && (
                  <div style={{
                    position:'absolute', top:6, right:8,
                    fontSize:'var(--fs-nano)', fontFamily:'Times New Roman,serif',
                    color:'var(--gold-deep)', letterSpacing:'.08em',
                    background:'var(--gold)', color:'var(--paper)',
                    padding:'1px 6px', borderRadius:1,
                  }}>{p.hint}</div>
                )}
                <div style={{ fontSize:20, marginBottom:6 }}>{p.icon}</div>
                <div style={{ fontFamily:'BiauKai,標楷體,serif', fontSize:14, fontWeight:700, letterSpacing:'.1em', color:'var(--ink)', marginBottom:4 }}>
                  {p.cn}
                </div>
                <div style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginBottom:6, letterSpacing:'.08em' }}>
                  {p.en}
                </div>
                <div style={{ fontFamily:'BiauKai,標楷體,serif', fontSize:'var(--fs-nano)', color:'var(--ink-soft)', lineHeight:1.7 }}>
                  {p.desc}
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* 黃金交叉智選組合（分數最高 × 產業分散） */}
      <SmartComboPanel rows={scanResults} />

      {/* Filters */}
      <div className="scan-filters">
        <div className="filter-row">
          <div className="filter-label">
            <span style={{ fontStyle:'italic' }}>Search</span>
            <span className="kai" style={{ marginLeft: 8 }}>搜尋</span>
          </div>
          <div className="filter-pills">
            <input
              type="text"
              placeholder="代號/名稱..."
              value={codeSearch}
              onChange={function(e) { setCodeSearch(e.target.value.trim()); }}
              style={{
                width: 100, padding: '3px 8px', fontSize: 12,
                border: '1px solid var(--paper-line)', borderRadius: 3,
                background: 'var(--paper)', color: 'var(--ink)',
                fontFamily: 'BiauKai,標楷體,serif',
              }}
            />
          </div>
        </div>
        <div className="filter-row">
          <div className="filter-label">
            <span style={{ fontStyle:'italic' }}>Signal</span>
            <span className="kai" style={{ marginLeft: 8 }}>訊號</span>
          </div>
          <div className="filter-pills">
            {FILTERS.signal.map(function(s) {
              return (
                <button key={s.id}
                        className={`filter-pill ${signalFilter === s.id ? 'active' : ''}`}
                        onClick={function() { setSignalFilter(s.id); clearPreset(); }}>
                  <span className="kai">{s.label}</span>
                </button>
              );
            })}
          </div>
        </div>
        <div className="filter-row">
          <div className="filter-label">
            <span style={{ fontStyle:'italic' }}>Zero</span>
            <span className="kai" style={{ marginLeft: 8 }}>0 軸</span>
          </div>
          <div className="filter-pills">
            {FILTERS.zero.map(function(z) {
              return (
                <button key={z.id}
                        className={`filter-pill ${zeroFilter === z.id ? 'active' : ''}`}
                        onClick={function() { setZeroFilter(z.id); clearPreset(); }}>
                  <span className="kai">{z.label}</span>
                </button>
              );
            })}
          </div>
        </div>
        <div className="filter-row">
          <div className="filter-label">
            <span style={{ fontStyle:'italic' }}>Hist</span>
            <span className="kai" style={{ marginLeft: 8 }}>動能色</span>
          </div>
          <div className="filter-pills">
            {FILTERS.hist.map(function(h) {
              var isActive = histFilter === h.id;
              return (
                <button key={h.id}
                        className={`filter-pill ${isActive ? 'active' : ''}`}
                        onClick={function() { setHistFilter(h.id); clearPreset(); }}
                        title={h.desc || ''}
                        style={isActive && h.color ? {
                          borderColor: h.color, color: h.color,
                          background: h.color + '22',
                        } : {}}>
                  {h.color && (
                    <span style={{
                      display:'inline-block', width:5, height:10, borderRadius:1,
                      background: h.color, marginRight:4, verticalAlign:'middle', flexShrink:0,
                      opacity: isActive ? 1 : 0.6,
                    }}/>
                  )}
                  <span className="kai">{h.label}</span>
                </button>
              );
            })}
          </div>
        </div>
        <div className="filter-row">
          <div className="filter-label">
            <span style={{ fontStyle:'italic' }}>Category</span>
            <span className="kai" style={{ marginLeft: 8 }}>大分類</span>
          </div>
          <div className="filter-pills">
            {FILTERS.cat.map(function(c) {
              return (
                <button key={c.id}
                        className={`filter-pill ${catFilter === c.id ? 'active' : ''}`}
                        onClick={function() { setCatFilter(c.id); setIndFilter('all'); clearPreset(); }}>
                  <span className="kai">{c.label}</span>
                  <span className="pill-en"> · {c.en}</span>
                </button>
              );
            })}
          </div>
        </div>

        {subIndustries.length > 0 && (
          <div className="filter-row" style={{ paddingLeft: 8, borderLeft: '2px solid var(--gold)', marginLeft: 4 }}>
            <div className="filter-label">
              <span style={{ fontStyle:'italic' }}>Industry</span>
              <span className="kai" style={{ marginLeft: 8 }}>子產業</span>
            </div>
            <div className="filter-pills">
              <button
                className={`filter-pill ${indFilter === 'all' ? 'active' : ''}`}
                onClick={function() { setIndFilter('all'); }}>
                <span className="kai">全部</span>
              </button>
              {subIndustries.map(function(ind) {
                return (
                  <button key={ind.id}
                          className={`filter-pill ${indFilter === ind.id ? 'active' : ''}`}
                          onClick={function() { setIndFilter(ind.id); }}>
                    <span className="kai">{ind.name}</span>
                    <span className="pill-en"> · {ind.nameEn}</span>
                  </button>
                );
              })}
            </div>
          </div>
        )}
      </div>

      {/* Results */}
      <div className="frame-card" style={{ marginTop: 18 }}>
        <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
        <div className="result-head">
          <h3>
            <span className="ornament" style={{color:'var(--gold)', fontSize:18}}>❦</span>
            <span className="kai">命中清單</span>
            <span className="result-en italic-en">Hits · {filtered.length}</span>
          </h3>
          <div className="result-meta">
            {updatedAt ? `Updated ${updatedAt}` : (scanning ? '掃描中…' : '—')}
          </div>
        </div>

        {scanning && scanResults.length === 0 ? (
          <div style={{ padding: 48, textAlign:'center', color:'var(--ink-mute)', fontStyle:'italic' }}>
            正在掃描全市場 · scanning market…
          </div>
        ) : showSections ? (
          <>
            <ZoneSection
              title="0 軸以上" titleEn="Above Zero Line"
              badge="▲"
              rows={aboveRows}
              accentColor="var(--bull)"
            />
            <ZoneSection
              title="0 軸以下" titleEn="Below Zero Line"
              badge="▼"
              rows={belowRows}
              accentColor="var(--bear)"
            />
          </>
        ) : (
          <table className="ed-table scan-table">
            <thead>
              <tr>
                <th>Code</th>
                <th>Name</th>
                <th>Signal</th>
                <th>Market</th>
                <th className="right">Price</th>
                <th className="right">Chg %</th>
                <th className="right">Volume</th>
                <th className="right">MACD Diff</th>
              </tr>
            </thead>
            <tbody>
              {filtered.map(function(r) { return <ResultRow key={r.code} r={r} colSpan={8} />; })}
            </tbody>
          </table>
        )}

        {!scanning && filtered.length === 0 && scanResults.length > 0 && (
          <div style={{ padding: 40, textAlign:'center' }}>
            <div style={{ color:'var(--ink-mute)', fontStyle:'italic', marginBottom:8 }}>
              目前篩選條件無符合個股
            </div>
            <div className="kai" style={{ fontSize:12, color:'var(--ink-mute)' }}>
              建議：放寬訊號至「全部」或取消「0軸以上」限制；<br/>
              非交易時段資料為前一收盤，漲跌幅較小時訊號偏少屬正常。
            </div>
            {codeSearch && (
              <a href={'個股技術分析.html?code=' + codeSearch}
                 style={{ color:'var(--gold)', fontSize:13, fontFamily:'BiauKai,標楷體,serif', display:'block', marginTop:10 }}>
                → 直接查看 {codeSearch} 個股分析
              </a>
            )}
          </div>
        )}
        {!scanning && scanResults.length === 0 && !error && (
          <div style={{ padding: 40, textAlign:'center', color:'var(--ink-mute)', fontStyle:'italic' }}>
            今日無 MACD 交叉訊號，請於交易日收盤後重新掃描
          </div>
        )}
      </div>

      {/* Legend */}
      <div className="feature-grid" style={{ marginTop: 28 }}>
        <div className="feature">
          <span className="feature-num">G↑</span>
          <h4>0軸以上黃金叉<em>Golden Above Zero</em></h4>
          <p>DIF 與 DEA 皆在零軸之上發生交叉,多頭趨勢強烈,為最強買入訊號。</p>
        </div>
        <div className="feature">
          <span className="feature-num">G↓</span>
          <h4>0軸以下黃金叉<em>Golden Below Zero</em></h4>
          <p>空頭區間的反彈交叉,觀察能否站上零軸,適合短線操作,風險較高。</p>
        </div>
        <div className="feature">
          <span className="feature-num">D</span>
          <h4>死叉 / 0軸位置<em>Death Cross Zone</em></h4>
          <p>0軸以上死叉為趨勢轉弱警告;0軸以下死叉為空頭延續,殺傷力最強。</p>
        </div>
      </div>

      {!embedded && <window.FooterBar />}

      {/* MACD 績效追蹤面板 — fixed bottom */}
      <PerfPanel scanResults={scanResults} />
    </div>
  );
}
window.ScanApp = ScanApp;

const _scanRoot = document.getElementById('root');
if (_scanRoot) ReactDOM.createRoot(_scanRoot).render(<ScanApp />);
