﻿// 企業基本面 — 公司簡介、月營收、季損益、資產負債、合理估算、同業排名、產業鏈&台美關係
const { useState, useEffect, useMemo, useRef } = React;

// ─── 資料解析 helpers ──────────────────────────────────

function parseRealtime(raw) {
  const item = (raw?.msgArray || [])[0];
  if (!item) return null;
  const price = parseFloat(item.z || item.y || 0);
  const prev  = parseFloat(item.y || 0);
  return {
    price, prev,
    chg:    +(price - prev).toFixed(2),
    pct:    prev > 0 ? +((price - prev) / prev * 100).toFixed(2) : 0,
    high:   parseFloat(item.h || price),
    low:    parseFloat(item.l || price),
    vol:    parseInt((item.v || '0').replace(/,/g, '')),
    name:   item.n || '',
    mktCap: parseFloat(item.mf || 0),
  };
}

function parseMonthlyRevenue(raw) {
  // 後端統一格式：{ data:[{revenue_year, revenue_month, revenue(千元)}], source }
  const src = raw?.source || 'finmind';
  return (raw?.data || [])
    .filter(d => d.revenue_year && d.revenue_month)
    .map(d => {
      const revK = +d.revenue || 0;   // 千元(thousands NTD)
      const revBil = +(revK / 1e5).toFixed(1);  // 億元
      return {
        ym:    `${d.revenue_year}/${String(d.revenue_month).padStart(2,'0')}`,
        year:  d.revenue_year,
        month: d.revenue_month,
        rev:   revBil,
        revRaw: revK,
        src,
      };
    })
    .sort((a, b) => a.ym < b.ym ? -1 : 1)
    .slice(-24);
}

function parseFinancials(raw) {
  // Backend returns { quarters: [{date, Revenue, GrossProfit, ...}] } (Yahoo Finance)
  return (raw?.quarters || []).slice(-8);
}

function parseBalanceSheet(raw) {
  // Backend returns { quarters: [{date, TotalAssets, TotalLiabilities, ...}] }
  return (raw?.quarters || []).slice(-8);
}

function parseDividends(raw) {
  return (raw?.dividend || [])
    .filter(d => ((d.CashEarningsDistribution || d.cash_dividend || 0) > 0 ||
                  (d.StockEarningsDistribution || d.stock_dividend || 0) > 0))
    .map(d => {
      const cash  = +(d.CashEarningsDistribution  || d.cash_dividend  || 0).toFixed(2);
      const stock = +(d.StockEarningsDistribution || d.stock_dividend || 0).toFixed(2);
      const year  = d.date?.slice(0, 4) || d.year || '';
      const exDate = d.CashExDividendTradingDate || d.StockExDividendTradingDate || d.ex_dividend_date || '';
      return { year, cash, stock, total: +(cash + stock).toFixed(2), yield: null, exDate };
    })
    .sort((a, b) => b.year - a.year)
    .slice(0, 12);
}

function parsePeerPrices(raw) {
  const out = {};
  (raw?.msgArray || []).forEach(item => {
    const code = (item.ch || '').split('.')[0].replace(/^(tse_|otc_)/, '');
    if (code) out[code] = parseFloat(item.z || item.y || 0);
  });
  return out;
}

// ─── 釋義系統：每章基本面意義（是什麼/怎麼看/留意）──────────
const FUND_NOTES = {
  tier: {
    what: '把全頁基本面濃縮成一顆印章——成長30分＋獲利25＋體質20＋估值15＋籌碼10，依總分分五段位：夯(80+)、頂級(65+)、人上人(50+)、NPC(35+)、拉完了(<35)。缺資料的項目按中性計，不會冤枉好公司。',
    how:  '先看段位與五維條哪一維最弱，再看健檢紅綠燈哪幾盞紅燈、PR 值輸同業哪一項；本益比河流告訴你「好公司現在貴不貴」——夯但在紅帶＝好公司爛價格。',
    warn: '段位只看基本面，不含技術面與題材；轉機股財報落後，段位反應會慢一兩季。',
  },
  moat: {
    what: '護城河＝讓對手搶不走高獲利的結構性優勢，共五種：無形資產（品牌/專利）、轉換成本、網絡效應、成本優勢、規模效率。護城河本身看不到，但存在時必留下數字痕跡——連續多年高 ROE、高且穩的毛利率、強現金轉換。',
    how:  '先看量化印章（寬/窄/無），再看右側技術版圖：公司握有哪些技術平台、各平台領先還是追趕——技術領先平台越多、護城河越深；寬護城河＋估值合理是長抱組合。',
    warn: '量化痕跡只有 3 年資料，技術轉折期（如被新技術路線繞道）會誤判；技術版圖為策展資料，以法說會與技術論壇為準，未建檔公司顯示五型痕跡比對。',
  },
  profile: {
    what: '認識公司是基本面分析之始——本業為何、賺誰的錢、位居產業鏈何處。看不懂生意模式的公司，再便宜也不該重倉。',
    how:  '留意主要產品線與客戶集中度；本業單純的公司財報易判讀，多角化集團則需分部門檢視。',
    warn: '業外損益（賣資產、轉投資、匯兌）不宜當常態獲利看待。',
  },
  revenue: {
    what: '台股獨步全球的揭露制度——上市櫃公司每月10日前必須公布上月營收，比季報領先一整季，是最重要的先行指標。',
    how:  '看年增率（YoY）而非月增率，避開季節性干擾；連續三個月 YoY 轉正或轉負，往往就是營運趨勢的轉折點；單月創歷史新高尤具意義。',
    warn: '營收成長不等於獲利成長，須搭配毛利率；部分產業（營建、造船）認列時點集中，單月暴增暴減屬常態。',
  },
  income: {
    what: '季報三表之首。營收減成本得毛利，再減營業費用得營業利益，加計業外後為稅後淨利與 EPS——股價長期跟著 EPS 走。',
    how:  '毛利率、營益率、淨利率同步上升即「三率三升」，是獲利結構改善的黃金訊號；EPS 應與去年同季比，而非上一季。',
    warn: '單季 EPS 暴增先查業外貢獻；本業實力看營業利益最實在。',
  },
  balance: {
    what: '公司的家底快照——資產 = 負債 + 股東權益，體質強弱與抗風險能力盡在此表。',
    how:  '負債比低於 50% 屬穩健；流動比高於 150% 短期償債無虞；每股淨值持續成長，代表公司確實在累積價值。',
    warn: '高負債不必然是壞事（金融、租賃業天生高槓桿），須與同業比較才有意義。',
  },
  analyst: {
    what: '券商與投行分析師對未來 12 個月目標價的共識，反映機構法人的集體觀點。',
    how:  '目標價的「調整方向」比絕對數字重要——連續上修代表基本面優於預期；目標價區間收斂代表共識增強。',
    warn: '分析師評等普遍偏多、且常落後股價調整；目標價是座標參考，不是保證。',
  },
  peers: {
    what: '估值唯有放在同業脈絡下才有意義——P/E、P/B、殖利率的高低，要跟做同樣生意的公司比。',
    how:  '估值低於同業平均且基本面不差，可能被低估；高於同業則需有成長率支撐，否則就是貴。',
    warn: '跨產業比 P/E 沒有意義；產業龍頭通常享有估值溢價，便宜往往有便宜的理由。',
  },
  chain: {
    what: '上中下游位置決定議價能力與景氣傳導順序——誰先感受到景氣冷暖，誰的毛利先被擠壓。',
    how:  '上游原料漲價，中游毛利先受壓；下游需求轉強，約需一至兩季才傳導至上游。觀察供應鏈可提前佈局。',
    warn: '供應鏈關係會變動，大客戶轉單是中游廠商最大的單一風險。',
  },
  ratios: {
    what: '獲利能力的體檢數字：毛利率看產品競爭力、營益率看管理效率、淨利率看最終獲利力、ROE 看股東資金運用效率。',
    how:  '趨勢比絕對值重要。ROE 長期大於 15% 且穩定，是好生意的標誌；杜邦拆解可看出 ROE 由獲利、週轉還是槓桿驅動。',
    warn: '靠舉債撐高的 ROE 品質較差；毛利率連續兩季下滑應提高警覺。',
  },
  cashflow: {
    what: '獲利的照妖鏡——損益表有應計項目可調整，現金流量表難以造假。營業現金流（OCF）是本業收到的真金白銀；自由現金流（FCF）是扣除資本支出後，真正可用來配息、還債的錢。',
    how:  'OCF 應大致跟得上稅後淨利（獲利含金量）；FCF 長年為正，配息才有底氣；擴產期 FCF 轉負屬正常投資週期。',
    warn: 'OCF 長期遠低於淨利，可能是應收帳款灌水或存貨堆積的警訊。',
  },
  dividend: {
    what: '股利政策反映公司的現金實力與對待股東的態度，是台股存股族的核心指標。',
    how:  '連續配息超過 10 年是穩定的象徵；配發率 50–70% 兼顧成長與回饋；殖利率要配合「填息」才是真報酬。',
    warn: '異常高殖利率常是股價大跌造成的假象；借錢配息不可持續。',
  },
  chips: {
    what: '三大法人買賣超是台股籌碼面核心。外資部位大、看長線基本面；投信有季底作帳行情、偏好認養中小型股；自營商短進短出參考性低。',
    how:  '外資連續買超且股價同步突破，是強勢訊號；投信開始建倉常是波段行情起點。',
    warn: '法人也會看錯，單日大買大賣可能只是調節或期現套利，「連續性」才有意義。',
  },
  equality: {
    what: '盈餘品質檢驗「帳面獲利是不是真的收到錢」。含金量＝營業現金流÷淨利；應計項目＝淨利與現金流的落差；現金轉換循環（CCC）＝錢從付出去到收回來要幾天。',
    how:  '含金量長期 ≥0.8 且應計比率低＝獲利紮實；CCC 縮短＝營運效率提升。配發率 ≤70% 且 FCF 覆蓋 ≥1 的股利才付得起。',
    warn: '獲利成長但應收/存貨天數同步暴增，是「塞貨式成長」的經典地雷訊號；配發率 >100% 是拿老本配息。',
  },
  ownership: {
    what: '外資持股比率看「存量」——法人買賣超只是每天的流量，持股％才是外資對這家公司的真實部位。',
    how:  '持股比率創一年新高且股價同步走強＝外資中期加碼；跌破長期區間下緣要警惕系統性撤出。',
    warn: '接近法定上限的股票外資買無可買，上檔動能受限；比率變動要配合股本變化解讀（增資會稀釋）。',
  },
  diag: {
    what: '三大學術驗證模型的綜合體檢：Piotroski F-Score 檢驗財務體質（9 信號）、Altman Z-Score 預警破產風險、Magic Formula 衡量「好公司是否便宜」。',
    how:  'F-Score ≥ 7 體質佳；Z-Score > 2.99 安全、< 1.8 危險；Magic Formula 高 ROC + 高盈餘殖利率為佳。',
    warn: '模型基於歷史財報，落後股價一至兩季；金融業因高槓桿天性，不適用 Altman Z。',
  },
};

function SecNote({ id }) {
  const n = FUND_NOTES[id];
  const [open, setOpen] = useState(false);
  if (!n) return null;
  return (
    <div className="sec-note">
      <button className="sec-note-btn" onClick={() => setOpen(o => !o)}>
        {open ? '▴ 收起釋義' : '▾ 釋義'}
      </button>
      {open && (
        <div className="sec-note-body">
          <p><b>是什麼</b>{n.what}</p>
          <p><b>怎麼看</b>{n.how}</p>
          <p><b>留　意</b>{n.warn}</p>
        </div>
      )}
    </div>
  );
}

// ─── 合理估算計算 ──────────────────────────────────────

function computeFairValue(financials, perRaw, bwibbu, rt, profile, bsData, cashFlow) {
  // ── TTM EPS ───────────────────────────────────────────────
  const epsRows = financials
    .map(q => q['EPS'] ?? null)
    .filter(v => v != null && isFinite(v) && v !== 0);
  if (!epsRows.length) return null;
  const ttmEps = epsRows.reduce((a, b) => a + b, 0) * (4 / epsRows.length);
  if (ttmEps <= 0) return null;

  const currentPrice = rt?.price || 0;
  const pb    = bwibbu?.pb   || 0;
  const divY  = bwibbu?.yield || 0;

  // ── 方法一：Graham Number ─────────────────────────────────
  // BPS = 現價 / P/B（直接從市場資料反推帳面價值）
  const bps    = pb > 0 && currentPrice > 0 ? currentPrice / pb : 0;
  const graham = bps > 0 && ttmEps > 0 ? +Math.sqrt(22.5 * ttmEps * bps).toFixed(0) : null;

  // ── 方法二：Lynch PEG 公允價值 ───────────────────────────
  // Fair Value = EPS × (EPS YoY 成長率% + 股利殖利率%)
  let lynch = null, epsGrowthPct = null;
  if (epsRows.length >= 8) {
    const r4 = epsRows.slice(-4).reduce((a, b) => a + b, 0);
    const p4 = epsRows.slice(-8, -4).reduce((a, b) => a + b, 0);
    if (p4 > 0) {
      epsGrowthPct = +((r4 - p4) / Math.abs(p4) * 100).toFixed(1);
      const g = Math.max(0, Math.min(epsGrowthPct, 30));
      const lynchPe = g + divY;
      if (lynchPe > 3) lynch = +(ttmEps * lynchPe).toFixed(0);
    }
  }

  // ── 方法三：5 年 DCF（自由現金流折現）────────────────────
  // FCF per share = TTM FreeCF × (EPS / TTM NI)：單位消去法
  let dcf = null, fcfPerShare = null, dcfG = null;
  if (cashFlow && cashFlow.length >= 4) {
    const last4cf = cashFlow.slice(-4).reduce((s, q) => s + (q.FreeCF || 0), 0);
    const last4ni = financials.slice(-4).reduce((s, q) => s + (q.NetIncome || 0), 0);
    if (last4cf > 0 && last4ni > 0) {
      fcfPerShare = +(last4cf * ttmEps / last4ni).toFixed(2);
      // 成長率：用 EPS YoY，保守上限 15%，下限 3%
      const g = epsGrowthPct != null
        ? Math.max(0.03, Math.min(epsGrowthPct / 100, 0.15))
        : 0.07;
      dcfG = +(g * 100).toFixed(1);
      const r = 0.10, tg = 0.03;
      let pv = 0, cf = fcfPerShare;
      for (let i = 1; i <= 5; i++) { cf *= (1 + g); pv += cf / Math.pow(1 + r, i); }
      const tv = (cf * (1 + tg)) / (r - tg);
      pv += tv / Math.pow(1 + r, 5);
      if (pv > 0) dcf = +pv.toFixed(0);
    }
  }

  // ── 方法四：Forward P/E（歷史本益比中位數 × 預期EPS）────
  // 最貼近市場：若股票維持過去交易本益比，一年後應值多少
  let forwardPe = null, peMed = null;
  const perHist = (perRaw?.per || []).map(d => +d.PER).filter(v => v > 0 && v < 300);
  if (perHist.length >= 30) {
    const sorted = [...perHist].sort((a, b) => a - b);
    peMed = +sorted[Math.floor(sorted.length * 0.5)].toFixed(1);
    const fwdEps = epsGrowthPct != null
      ? ttmEps * (1 + Math.max(0, Math.min(epsGrowthPct / 100, 0.30)))
      : ttmEps;
    forwardPe = +(fwdEps * peMed).toFixed(0);
  }

  // ── 共識中值（剔除 null，取中位數）──────────────────────
  const valid = [graham, lynch, dcf, forwardPe].filter(v => v != null && v > 0);
  const consensus = valid.length
    ? +([...valid].sort((a,b)=>a-b)[Math.floor(valid.length / 2)]).toFixed(0)
    : null;

  const upside = (v) => currentPrice > 0 && v ? +(((v - currentPrice) / currentPrice) * 100).toFixed(1) : null;
  const impliedPe = currentPrice > 0 ? +(currentPrice / ttmEps).toFixed(1) : null;

  // ── P/E 評分（佔評分 15%）────────────────────────────
  // impliedPe 為現價隱含本益比；亦可用 bwibbu.pe（TWSE 即時）
  const peVal = impliedPe || null;
  let peScore = null, peScoreLabel = '';
  if (peVal != null) {
    if (peVal < 0)        { peScore = -1; peScoreLabel = '負盈利'; }
    else if (peVal < 15)  { peScore =  2; peScoreLabel = '便宜 (<15x)'; }
    else if (peVal < 25)  { peScore =  1; peScoreLabel = '合理 (15–25x)'; }
    else                  { peScore =  0; peScoreLabel = '偏貴 (≥25x)'; }
  }

  // ── EPS YoY 評分（佔評分 15%）────────────────────────
  let epsYoyScore = null, epsYoyLabel = '';
  if (epsGrowthPct != null) {
    if (epsGrowthPct > 20)                           { epsYoyScore = 2; epsYoyLabel = '高速成長 (>20%)'; }
    else if (epsGrowthPct > 5)                       { epsYoyScore = 1; epsYoyLabel = '穩定成長 (5–20%)'; }
    else if (epsGrowthPct >= -10)                    { epsYoyScore = 0; epsYoyLabel = '持平 (-10%~5%)'; }
    else                                             { epsYoyScore = -1; epsYoyLabel = '衰退 (<-10%)'; }
  }

  return {
    ttmEps: +ttmEps.toFixed(2), impliedPe,
    graham, lynch, dcf, consensus,
    grahamUpside: upside(graham), lynchUpside: upside(lynch),
    dcfUpside: upside(dcf), consensusUpside: upside(consensus),
    bps: bps > 0 ? +bps.toFixed(1) : null, pb, divY,
    fcfPerShare, epsGrowthPct, dcfG,
    forwardPe, peMed,
    forwardPeUpside: upside(forwardPe),
    currentPrice,
    peScore, peScoreLabel, peVal,
    epsYoyScore, epsYoyLabel,
  };
}

// ─── Section 00:企業段位總評 ──────────────────────────
// 五段位：夯(≥80) / 頂級(65) / 人上人(50) / NPC(35) / 拉完了(<35)
// 純基本面評分：成長30 + 獲利25 + 體質20 + 估值15 + 籌碼10，缺資料按中性計

const TIER_DEFS = [
  { min: 80, label: '夯',     en: 'S · RED-HOT',     color: '#c23a2b', desc: '成長獲利雙引擎全開，市場當紅炸子雞' },
  { min: 65, label: '頂級',   en: 'A · ELITE',       color: '#9a7b35', desc: '體質頂尖的優等生，長期持有候選' },
  { min: 50, label: '人上人', en: 'B · ABOVE AVG',   color: '#1a2438', desc: '贏過多數同業，但仍有罩門' },
  { min: 35, label: 'NPC',    en: 'C · AVERAGE',     color: '#8a8578', desc: '平庸路人股，缺乏記憶點' },
  { min: -999, label: '拉完了', en: 'D · WASHED',    color: '#6e4a3a', desc: '基本面持續惡化，避開為上' },
];

function computeTier({ fins, bsData, cashFlow, revenue, pe, pb, dy, chipSummary, peerBwibbu, code }) {
  const goods = [], bads = [], lights = [];
  const dims = [];
  const light = (label, state) => lights.push({ label, state });

  // ── 共用中間值 ──
  const eps4  = fins.slice(-4).reduce((s, q) => s + (q.EPS || 0), 0);
  const ni4   = fins.slice(-4).reduce((s, q) => s + (q.NetIncome || 0), 0);
  const rev4  = fins.slice(-4).reduce((s, q) => s + (q.Revenue || 0), 0);
  const gp4   = fins.slice(-4).reduce((s, q) => s + (q.GrossProfit || 0), 0);
  const oi4   = fins.slice(-4).reduce((s, q) => s + (q.OperatingIncome || 0), 0);
  const bsL   = bsData[bsData.length - 1] || {};
  const equity = bsL.StockholdersEquity || 0;
  const tAssets = bsL.TotalAssets || 0;

  // 季 EPS YoY 連增季數
  let epsStreak = null;
  if (fins.length >= 5) {
    const byKey = {};
    fins.forEach(q => {
      const yr = parseInt((q.date||'').slice(0,4)), qn = Math.ceil(parseInt((q.date||'').slice(5,7)||'1')/3);
      byKey[yr + 'Q' + qn] = q.EPS;
    });
    epsStreak = 0;
    for (let i = fins.length - 1; i >= 0; i--) {
      const q = fins[i];
      const yr = parseInt((q.date||'').slice(0,4)), qn = Math.ceil(parseInt((q.date||'').slice(5,7)||'1')/3);
      const prev = byKey[(yr-1) + 'Q' + qn];
      if (prev != null && q.EPS != null && q.EPS > prev) epsStreak++;
      else break;
    }
  }

  // ════ 成長 30 ════
  let gGrow = 0;
  let epsYoy = null;
  if (fins.length >= 8) {
    const p4 = fins.slice(-8, -4).reduce((s, q) => s + (q.EPS || 0), 0);
    if (Math.abs(p4) > 0.0001) epsYoy = (eps4 - p4) / Math.abs(p4) * 100;
  }
  if (epsYoy == null) gGrow += 7.5;
  else {
    gGrow += epsYoy > 30 ? 15 : epsYoy > 15 ? 12 : epsYoy > 5 ? 9 : epsYoy > 0 ? 6 : epsYoy > -10 ? 3 : 0;
    (epsYoy > 10 ? goods : epsYoy < 0 ? bads : goods).push(
      `EPS 年增 ${epsYoy >= 0 ? '+' : ''}${epsYoy.toFixed(0)}%${epsYoy > 10 ? '' : epsYoy < 0 ? '' : '（普通）'}`);
  }
  light('近4季 EPS 年增 > 10%', epsYoy == null ? null : epsYoy > 10);

  let revYoy = null;
  const rec12 = revenue.slice(-12).reduce((a, b) => a + (b.revRaw || 0), 0);
  const prv12 = revenue.slice(-24, -12).reduce((a, b) => a + (b.revRaw || 0), 0);
  if (prv12 > 0) revYoy = (rec12 - prv12) / prv12 * 100;
  if (revYoy == null) gGrow += 5;
  else {
    gGrow += revYoy > 20 ? 10 : revYoy > 10 ? 8 : revYoy > 0 ? 6 : revYoy > -10 ? 3 : 0;
    if (revYoy > 15) goods.push(`營收 12M 年增 +${revYoy.toFixed(0)}%`);
    if (revYoy < 0)  bads.push(`營收 12M 衰退 ${revYoy.toFixed(0)}%`);
  }
  light('月營收 12M YoY 為正', revYoy == null ? null : revYoy > 0);

  if (epsStreak == null) gGrow += 2.5;
  else {
    gGrow += epsStreak >= 4 ? 5 : epsStreak === 3 ? 4 : epsStreak === 2 ? 3 : epsStreak === 1 ? 1.5 : 0;
    if (epsStreak >= 3) goods.push(`EPS 連 ${epsStreak} 季年增`);
    if (epsStreak === 0) bads.push('最新季 EPS 年減');
  }
  light('EPS 連 2 季以上年增', epsStreak == null ? null : epsStreak >= 2);
  dims.push({ name: '成長', val: gGrow, max: 30 });

  // ════ 獲利 25 ════
  let gProf = 0;
  const roe = (equity > 0 && fins.length >= 4) ? ni4 / equity * 100 : null;
  if (roe == null) gProf += 6;
  else {
    gProf += roe > 25 ? 12 : roe > 18 ? 10 : roe > 12 ? 7 : roe > 8 ? 5 : roe > 0 ? 2.5 : 0;
    if (roe > 18) goods.push(`ROE ${roe.toFixed(0)}%`);
    if (roe < 8)  bads.push(`ROE 僅 ${roe.toFixed(1)}%`);
  }
  light('ROE (TTM) > 15%', roe == null ? null : roe > 15);

  const gm = rev4 > 0 ? gp4 / rev4 * 100 : null;
  if (gm == null) gProf += 3.5;
  else {
    gProf += gm > 40 ? 7 : gm > 25 ? 5.5 : gm > 15 ? 4 : gm > 5 ? 2 : 0.5;
    if (gm > 40) goods.push(`毛利率 ${gm.toFixed(0)}%`);
  }
  light('毛利率 > 20%', gm == null ? null : gm > 20);
  light('營業利益率 > 10%', rev4 > 0 ? oi4 / rev4 * 100 > 10 : null);

  // 三率趨勢：最新季 vs 去年同季
  let triUp = null;
  if (fins.length >= 5) {
    const qN = fins[fins.length - 1];
    const yr = parseInt((qN.date||'').slice(0,4)), qn = Math.ceil(parseInt((qN.date||'').slice(5,7)||'1')/3);
    const qP = fins.find(q => {
      const y2 = parseInt((q.date||'').slice(0,4)), q2 = Math.ceil(parseInt((q.date||'').slice(5,7)||'1')/3);
      return y2 === yr - 1 && q2 === qn;
    });
    if (qP && qN.Revenue > 0 && qP.Revenue > 0) {
      triUp = 0;
      if ((qN.GrossProfit||0)/qN.Revenue > (qP.GrossProfit||0)/qP.Revenue) triUp++;
      if ((qN.OperatingIncome||0)/qN.Revenue > (qP.OperatingIncome||0)/qP.Revenue) triUp++;
      if ((qN.NetIncome||0)/qN.Revenue > (qP.NetIncome||0)/qP.Revenue) triUp++;
    }
  }
  if (triUp == null) gProf += 3;
  else {
    gProf += triUp === 3 ? 6 : triUp === 2 ? 4 : triUp === 1 ? 2 : 0;
    if (triUp === 3) goods.push('三率三升');
    if (triUp === 0) bads.push('三率齊降');
  }
  light('三率三升（vs 去年同季）', triUp == null ? null : triUp === 3);
  dims.push({ name: '獲利', val: gProf, max: 25 });

  // ════ 體質 20 ════
  let gQual = 0;
  const debtRatio = (tAssets > 0) ? (bsL.TotalLiabilities || 0) / tAssets * 100 : null;
  if (debtRatio == null) gQual += 3;
  else {
    gQual += debtRatio < 30 ? 6 : debtRatio < 50 ? 4.5 : debtRatio < 65 ? 3 : debtRatio < 80 ? 1.5 : 0;
    if (debtRatio > 65) bads.push(`負債比 ${debtRatio.toFixed(0)}%`);
  }
  light('負債比 < 50%', debtRatio == null ? null : debtRatio < 50);

  const curRatio = (bsL.CurrentLiabilities > 0) ? bsL.CurrentAssets / bsL.CurrentLiabilities : null;
  gQual += curRatio == null ? 2 : curRatio > 2 ? 4 : curRatio > 1.5 ? 3 : curRatio > 1 ? 2 : 0.5;
  light('流動比 > 1.5', curRatio == null ? null : curRatio > 1.5);

  const ocf4 = (cashFlow && cashFlow.length >= 4) ? cashFlow.slice(-4).reduce((s, q) => s + (q.OperatingCF || 0), 0) : null;
  const fcf4 = (cashFlow && cashFlow.length >= 4) ? cashFlow.slice(-4).reduce((s, q) => s + (q.FreeCF || 0), 0) : null;
  if (ocf4 == null) gQual += 2.5;
  else {
    gQual += ocf4 > 0 ? (ni4 > 0 && ocf4 > ni4 ? 5 : 4) : 0;
    if (ocf4 <= 0) bads.push('營業現金流為負');
  }
  light('營業現金流 (TTM) > 0', ocf4 == null ? null : ocf4 > 0);
  if (fcf4 == null) gQual += 2.5;
  else {
    gQual += fcf4 > 0 ? 5 : 0;
    if (fcf4 > 0 && ocf4 > 0 && ni4 > 0 && ocf4 > ni4) goods.push('現金流品質佳（OCF＞淨利）');
    if (fcf4 <= 0) bads.push('自由現金流為負');
  }
  light('自由現金流 (TTM) > 0', fcf4 == null ? null : fcf4 > 0);
  dims.push({ name: '體質', val: gQual, max: 20 });

  // ════ 估值 15（同業相對優先，無同業用絕對閾值）════
  let gVal = 0;
  const peerVals = Object.keys(peerBwibbu || {})
    .filter(c => c !== code)
    .map(c => peerBwibbu[c])
    .filter(p => p && p.pe > 0);
  const med = arr => { const s = [...arr].sort((a,b)=>a-b); return s.length ? s[Math.floor(s.length/2)] : null; };
  const peMed = peerVals.length >= 3 ? med(peerVals.map(p => p.pe)) : null;
  const pbMed = peerVals.length >= 3 ? med(peerVals.map(p => p.pb).filter(v => v > 0)) : null;

  if (pe == null || pe <= 0) gVal += 4;
  else if (peMed) {
    const ratio = pe / peMed;
    gVal += ratio < 0.7 ? 8 : ratio < 1 ? 6 : ratio < 1.3 ? 3.5 : 1;
    if (ratio < 0.8) goods.push(`P/E 比同業中位便宜 ${((1-ratio)*100).toFixed(0)}%`);
    if (ratio > 1.5) bads.push(`P/E 貴同業中位 ${((ratio-1)*100).toFixed(0)}%`);
  } else {
    gVal += pe < 12 ? 8 : pe < 18 ? 6 : pe < 28 ? 3.5 : 1;
    if (pe > 35) bads.push(`P/E ${pe.toFixed(0)}x 偏貴`);
  }
  light('P/E 低於同業中位數', (pe > 0 && peMed) ? pe < peMed : null);

  gVal += (dy == null) ? 2 : dy > 5 ? 4 : dy > 3 ? 3 : dy > 1.5 ? 2 : dy > 0 ? 1 : 0.5;
  if (dy > 5) goods.push(`殖利率 ${dy.toFixed(1)}%`);
  light('殖利率 > 3%', dy == null ? null : dy > 3);

  if (pb == null || pb <= 0) gVal += 1.5;
  else if (pbMed) gVal += pb < pbMed * 0.8 ? 3 : pb < pbMed * 1.2 ? 2 : 0.5;
  else gVal += pb < 1.5 ? 3 : pb < 3 ? 2 : pb < 6 ? 1 : 0.5;
  dims.push({ name: '估值', val: gVal, max: 15 });

  // ════ 籌碼 10（近30日三大法人）════
  let gChip = 0;
  if (!chipSummary || !chipSummary.length) gChip = 5;
  else {
    const agg = { foreign: 0, trust: 0, dealer: 0 };
    const byDate = {};
    chipSummary.forEach(r => {
      const net = ((+r.buy) || 0) - ((+r.sell) || 0);
      const n = r.name || '';
      if (n === 'Foreign_Investor' || n === 'Foreign_Dealer_Self' || /外資/.test(n)) agg.foreign += net;
      else if (n === 'Investment_Trust' || /投信/.test(n)) agg.trust += net;
      else if (n === 'Dealer_self' || n === 'Dealer_Hedging' || /自營/.test(n)) agg.dealer += net;
    });
    const total = agg.foreign + agg.trust + agg.dealer;
    gChip = (total > 0 && agg.foreign > 0 && agg.trust > 0) ? 10
          : total > 0 ? 7 : total === 0 ? 5
          : (agg.foreign < 0 && agg.trust < 0) ? 1 : 3;
    if (gChip >= 10) goods.push('外資投信 30 日同步買超');
    if (gChip <= 1)  bads.push('外資投信 30 日同步賣超');
    light('法人近 30 日淨買超', total > 0);
  }
  if (!chipSummary || !chipSummary.length) light('法人近 30 日淨買超', null);
  dims.push({ name: '籌碼', val: gChip, max: 10 });

  const score = Math.round(dims.reduce((s, d) => s + d.val, 0));
  const tier = TIER_DEFS.find(t => score >= t.min);
  return { score, tier, dims, goods: goods.slice(0, 4), bads: bads.slice(0, 3), lights, peMed, pbMed, peerN: peerVals.length };
}

// 同業百分位 PR：贏過多少 % 同業（方向感知）
function computePeerPR({ pe, pb, dy, peerBwibbu, code }) {
  const peers = Object.keys(peerBwibbu || {}).filter(c => c !== code).map(c => peerBwibbu[c]).filter(Boolean);
  if (peers.length < 3) return [];
  const pr = (mine, vals, lowerBetter) => {
    const v = vals.filter(x => x > 0);
    if (mine == null || mine <= 0 || v.length < 3) return null;
    const beat = v.filter(x => lowerBetter ? mine < x : mine > x).length;
    return Math.round(beat / v.length * 100);
  };
  return [
    { label: 'P/E 便宜度', pr: pr(pe, peers.map(p => p.pe), true),  note: '越低越便宜' },
    { label: 'P/B 便宜度', pr: pr(pb, peers.map(p => p.pb), true),  note: '越低越便宜' },
    { label: '殖利率',     pr: pr(dy, peers.map(p => p.yield), false), note: '越高越好' },
  ].filter(x => x.pr != null);
}

// 本益比河流圖：FinMind PER 2年日資料 + 百分位五色帶
// field 可切 'PER'|'PBR'（perData.per 每筆本就含兩欄），P/B 河流零新資料（2026-07-20 專業化第一波）
function PERiver({ perData, pe, field = 'PER', cap = 300, titleCn = '本益比河流', unitLabel = 'P/E' }) {
  const ref = React.useRef(null);
  const rows = (perData?.per || [])
    .map(d => ({ date: d.date, per: +d[field] }))
    .filter(d => d.per > 0 && d.per < cap);
  const pcts = React.useMemo(() => {
    if (rows.length < 60) return null;
    const s = rows.map(r => r.per).sort((a, b) => a - b);
    const q = p => s[Math.floor(s.length * p)];
    return { p10: q(.1), p30: q(.3), p50: q(.5), p70: q(.7), p90: q(.9), min: s[0], max: s[s.length-1] };
  }, [rows.length]);

  const cur = pe || (rows.length ? rows[rows.length-1].per : null);
  const zone = (!pcts || !cur) ? null
    : cur <= pcts.p10 ? { t:'極度便宜', c:'var(--bull)' }
    : cur <= pcts.p30 ? { t:'便宜', c:'var(--bull)' }
    : cur <= pcts.p70 ? { t:'合理', c:'var(--gold-deep)' }
    : cur <= pcts.p90 ? { t:'偏貴', c:'#c0392b' }
    : { t:'極度昂貴', c:'var(--bear)' };

  React.useEffect(() => {
    if (!ref.current || !window.echarts || !pcts || rows.length < 60) return;
    const chart = window.echarts.init(ref.current, null, { renderer: 'svg' });
    const bands = [
      { lo: pcts.min, hi: pcts.p10, color: 'rgba(74,166,84,.22)' },
      { lo: pcts.p10, hi: pcts.p30, color: 'rgba(74,166,84,.12)' },
      { lo: pcts.p30, hi: pcts.p70, color: 'rgba(184,146,77,.10)' },
      { lo: pcts.p70, hi: pcts.p90, color: 'rgba(229,57,53,.10)' },
      { lo: pcts.p90, hi: pcts.max, color: 'rgba(229,57,53,.20)' },
    ];
    chart.setOption({
      animation: false,
      grid: { left: 44, right: 14, top: 18, bottom: 24 },
      tooltip: { trigger: 'axis', backgroundColor: '#1a2438', borderWidth: 0,
        textStyle: { color: '#fff', fontSize: 11.5 },
        formatter: ps => `${ps[0].axisValue}<br/>${unitLabel} ${(+ps[0].value).toFixed(field === 'PBR' ? 2 : 1)}x` },
      xAxis: { type: 'category', data: rows.map(r => r.date),
        axisLine: { lineStyle: { color: '#c9c2b2' } },
        axisLabel: { color: '#999', fontSize: 11.5, interval: Math.floor(rows.length / 6) },
        axisTick: { show: false } },
      yAxis: { type: 'value', min: Math.floor(pcts.min * 0.9), max: Math.ceil(pcts.max * 1.05),
        axisLabel: { color: '#999', fontSize: 11.5, formatter: '{value}x' },
        splitLine: { lineStyle: { color: '#eae4d6', type: 'dashed' } } },
      series: [{
        type: 'line', data: rows.map(r => r.per), symbol: 'none',
        lineStyle: { color: '#1a2438', width: 1.4 },
        markArea: { silent: true, data: bands.map(b => [
          { yAxis: b.lo, itemStyle: { color: b.color } }, { yAxis: b.hi }]) },
        markLine: { silent: true, symbol: 'none',
          label: { position: 'insideEndTop', fontSize: 11.5, color: '#9a7b35', formatter: '中位 {c}x' },
          data: [{ yAxis: +pcts.p50.toFixed(1), lineStyle: { color: '#b8924d', type: 'dashed', width: 1 } }] },
      }],
    });
    const ro = new ResizeObserver(() => chart.resize());
    ro.observe(ref.current);
    return () => { ro.disconnect(); chart.dispose(); };
  }, [rows.length, pcts && pcts.p50]);

  if (!pcts) return (
    <div style={{ textAlign:'center', padding:'18px 0', color:'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>
      {unitLabel} 歷史資料不足（需 60 日以上）
    </div>
  );
  return (
    <div>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', marginBottom:4 }}>
        <span className="kai" style={{ fontSize:13, letterSpacing:'.1em' }}>
          {titleCn} <span style={{ fontStyle:'italic', color:'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>{unitLabel} River · 2Y</span>
        </span>
        {zone && cur && (
          <span className="num" style={{ fontSize:13, color: zone.c, fontWeight:700 }}>
            現在 {(+cur).toFixed(field === 'PBR' ? 2 : 1)}x — {zone.t}
          </span>
        )}
      </div>
      <div ref={ref} style={{ width:'100%', height:190 }}/>
      <div style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginTop:2 }}>
        綠帶＝歷史便宜區（&lt;P30）· 金帶＝合理（P30–P70）· 紅帶＝昂貴（&gt;P70）· 金虛線＝2年中位數
      </div>
    </div>
  );
}

// ═══════════════════════════════════════════════════════════
// 專業化第一波（2026-07-20）：估值家族 / 盈餘品質 / 股權結構
// spec: docs/superpowers/specs/2026-07-20-fundamental-pro-design.md
// 原則：純計算優先、缺資料顯示「—」與原因、不估算填充
// ═══════════════════════════════════════════════════════════

// ── 估值家族：EV/EBITDA(TTM)・PEG（放在 08 財務比率趨勢頂部）──
function ValuationFamily({ fins, bsData, cashFlow, rt, ownership, pe }) {
  const v = React.useMemo(() => {
    // 收盤/凌晨時段 MIS z='-' 導致 price=0，退回昨收（prev）
    const price  = (rt?.price > 0 ? rt.price : rt?.prev) || null;
    const shares = ownership?.latest?.sharesIssued;
    const bs     = bsData[bsData.length - 1];
    const t4     = arr => arr.slice(-4);
    const opTTM  = fins.length >= 4 ? t4(fins).reduce((s, q) => s + (q.OperatingIncome || 0), 0) : null;
    const daTTM  = cashFlow.length >= 4 && cashFlow.slice(-4).some(q => q.Depreciation != null)
      ? t4(cashFlow).reduce((s, q) => s + (q.Depreciation || 0) + (q.Amortization || 0), 0) : null;
    const ebitda = (opTTM != null && daTTM != null) ? opTTM + daTTM : null;
    const mktCap = (price > 0 && shares > 0) ? price * shares : null;
    const ev = (mktCap != null && bs && bs.TotalLiabilities != null && bs.Cash != null)
      ? mktCap + bs.TotalLiabilities - bs.Cash : null;
    const evEbitda = (ev != null && ebitda > 0) ? +(ev / ebitda).toFixed(1) : null;
    let peg = null, epsG = null;
    if (fins.length >= 8 && pe > 0) {
      const cur  = t4(fins).reduce((s, q) => s + (q.EPS || 0), 0);
      const prev = fins.slice(-8, -4).reduce((s, q) => s + (q.EPS || 0), 0);
      if (prev > 0) {
        epsG = +((cur / prev - 1) * 100).toFixed(1);
        if (epsG > 0) peg = +(pe / epsG).toFixed(2);
      }
    }
    return { evEbitda, peg, epsG, mktCapE: mktCap != null ? mktCap / 1e8 : null };
  }, [fins, bsData, cashFlow, rt && rt.price, ownership, pe]);

  const cell = (label, val, judge, hint) => (
    <div style={{ flex:'1 1 150px', padding:'10px 14px', borderRight:'1px solid var(--paper-line)', textAlign:'center' }}>
      <div className="num" style={{ fontSize:'var(--fs-md)', fontWeight:700, color: judge || 'var(--ink)' }}>{val}</div>
      <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:2 }}>{label}</div>
      {hint && <div className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)', marginTop:2 }}>{hint}</div>}
    </div>
  );
  return (
    <div style={{ display:'flex', flexWrap:'wrap', border:'1px solid var(--paper-line)', background:'var(--paper-bright)', marginBottom:16 }}>
      {cell('EV / EBITDA (TTM)',
        v.evEbitda != null ? v.evEbitda + 'x' : '—',
        v.evEbitda != null ? (v.evEbitda < 10 ? 'var(--bull)' : v.evEbitda > 20 ? 'var(--bear)' : 'var(--gold-deep)') : null,
        v.evEbitda == null ? '待折舊/現金欄位（快取更新後提供）' : 'EV=市值+總負債−現金')}
      {cell('PEG', v.peg != null ? v.peg : '—',
        v.peg != null ? (v.peg < 1 ? 'var(--bull)' : v.peg > 2 ? 'var(--bear)' : 'var(--gold-deep)') : null,
        v.epsG != null ? `EPS 年增 ${v.epsG > 0 ? '+' : ''}${v.epsG}%` : 'EPS 由正轉負或資料不足')}
      {cell('市值', v.mktCapE != null ? (v.mktCapE >= 10000 ? (v.mktCapE / 10000).toFixed(2) + ' 兆' : Math.round(v.mktCapE).toLocaleString() + ' 億') : '—',
        null, v.mktCapE == null ? '待股數資料' : '現價 × 發行股數')}
    </div>
  );
}

// ── Reverse DCF：現價隱含成長率反推（第二波，2026-07-20）──
// 二段式：前 5 年成長 g、永續 2.5%、折現 9%。對現價解 g（二分法），
// 跟「過去一年實際 FCF 成長」對照——市場要求 vs 公司實績，一眼看出貴或便宜的「原因」。
function ReverseDCF({ cashFlow, rt, ownership }) {
  const v = React.useMemo(() => {
    const price  = (rt?.price > 0 ? rt.price : rt?.prev) || null;   // 收盤時段退回昨收
    const shares = ownership?.latest?.sharesIssued;
    if (!(price > 0) || !(shares > 0) || cashFlow.length < 8) return { reason: cashFlow.length < 8 ? '需 8 季現金流資料' : '待股價/股數資料' };
    const fcfTTM  = cashFlow.slice(-4).reduce((s, q) => s + (q.FreeCF || 0), 0);
    const fcfPrev = cashFlow.slice(-8, -4).reduce((s, q) => s + (q.FreeCF || 0), 0);
    if (fcfTTM <= 0) return { reason: 'FCF 為負，反推無意義（燒錢期公司請看營收動能）' };
    const actualG = fcfPrev > 0 ? +((fcfTTM / fcfPrev - 1) * 100).toFixed(1) : null;

    const R = 0.09, GT = 0.025, N = 5;
    const fcf0 = fcfTTM / shares;   // 每股 FCF
    const valueAt = g => {
      let pv = 0, f = fcf0;
      for (let t = 1; t <= N; t++) { f *= (1 + g); pv += f / Math.pow(1 + R, t); }
      return pv + (f * (1 + GT)) / (R - GT) / Math.pow(1 + R, N);
    };
    // 二分法解 g（valueAt 單調遞增）
    let lo = -0.5, hi = 1.0;
    if (valueAt(hi) < price) return { reason: '現價高到 5 年 100% 年成長都撐不住（市場定價已脫離 FCF 框架）', actualG };
    if (valueAt(lo) > price) return { impliedG: -50, actualG, fcf0 };
    for (let i = 0; i < 60; i++) {
      const mid = (lo + hi) / 2;
      if (valueAt(mid) < price) lo = mid; else hi = mid;
    }
    return { impliedG: +((lo + hi) / 2 * 100).toFixed(1), actualG, fcf0: +fcf0.toFixed(2) };
  }, [cashFlow, rt && rt.price, ownership]);

  const gap = (v.impliedG != null && v.actualG != null) ? +(v.impliedG - v.actualG).toFixed(1) : null;
  const verdictTxt = gap == null ? null
    : gap <= -5 ? { t: '市場要求低於實績——定價保守', c: 'var(--bull)' }
    : gap <= 5  ? { t: '市場要求貼近實績——定價合理', c: 'var(--gold-deep)' }
    : gap <= 15 ? { t: '市場要求高於實績——成長必須加速才撐得住', c: '#c0392b' }
    : { t: '市場要求遠超實績——高度樂觀定價', c: 'var(--bear)' };

  return (
    <div style={{ border:'1px solid var(--paper-line)', background:'var(--paper-bright)', padding:'12px 16px', marginBottom:16 }}>
      <div style={{ display:'flex', justifyContent:'space-between', alignItems:'baseline', flexWrap:'wrap', gap:8 }}>
        <span className="kai" style={{ fontSize:'var(--fs-sm)', letterSpacing:'.1em' }}>
          現價隱含成長率 <span style={{ fontStyle:'italic', fontFamily:'var(--font-en)', fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>Reverse DCF</span>
        </span>
        <span className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)' }}>二段式：5年成長＋永續2.5% · 折現9% · 基於每股FCF</span>
      </div>
      {v.impliedG == null ? (
        <div className="kai" style={{ fontSize:'var(--fs-xs)', color:'var(--ink-mute)', marginTop:8 }}>—（{v.reason}）</div>
      ) : (
        <div style={{ display:'flex', flexWrap:'wrap', gap:22, alignItems:'center', marginTop:8 }}>
          <div style={{ textAlign:'center' }}>
            <div className="num" style={{ fontSize:'var(--fs-num)', fontWeight:700, lineHeight:1.05, color:'var(--ink)' }}>
              {v.impliedG > 0 ? '+' : ''}{v.impliedG}%
            </div>
            <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>市場隱含 FCF 年成長<br/>（未來 5 年）</div>
          </div>
          <div style={{ fontSize:20, color:'var(--ink-faint)' }}>vs</div>
          <div style={{ textAlign:'center' }}>
            <div className="num" style={{ fontSize:'var(--fs-num)', fontWeight:700, lineHeight:1.05, color:'var(--ink-soft)' }}>
              {v.actualG != null ? (v.actualG > 0 ? '+' : '') + v.actualG + '%' : '—'}
            </div>
            <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>過去一年實際 FCF 成長<br/>（TTM vs 前一 TTM）</div>
          </div>
          {verdictTxt && (
            <div className="kai" style={{ flex:'1 1 200px', fontSize:'var(--fs-xs)', color: verdictTxt.c, lineHeight:1.8 }}>
              ◈ {verdictTxt.t}
              <div style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)', marginTop:2 }}>
                差距 {gap > 0 ? '+' : ''}{gap} pp。反推是「市場在想什麼」的鏡子，不是目標價。
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ── 資本配置面板：管理層拿錢做什麼（第二波，2026-07-20）──
function CapitalAllocation({ cashFlow, fins }) {
  const ref = React.useRef(null);
  const rows = React.useMemo(() => {
    const revByDate = {};
    fins.forEach(f => { revByDate[f.date] = f.Revenue; });
    return cashFlow
      .filter(c => c.CapEx != null)
      .map(c => ({
        qLabel: `${c.date.slice(2, 4)}Q${Math.ceil(+c.date.slice(5, 7) / 3)}`,
        capex: Math.abs(c.CapEx) / 1e8,
        depr: c.Depreciation != null ? c.Depreciation / 1e8 : null,
        capexRev: revByDate[c.date] > 0 ? +(Math.abs(c.CapEx) / revByDate[c.date] * 100).toFixed(1) : null,
        fcfMargin: (revByDate[c.date] > 0 && c.FreeCF != null) ? +(c.FreeCF / revByDate[c.date] * 100).toFixed(1) : null,
        fin: c.FinancingCF != null ? c.FinancingCF / 1e8 : null,
      }));
  }, [cashFlow, fins]);

  const hasDepr = rows.some(r => r.depr != null);
  const t4 = rows.slice(-4);
  const reinvest = hasDepr && t4.length === 4 && t4.every(r => r.depr > 0)
    ? +(t4.reduce((s, r) => s + r.capex, 0) / t4.reduce((s, r) => s + r.depr, 0)).toFixed(2) : null;
  const finTTM = t4.length === 4 && t4.every(r => r.fin != null)
    ? +t4.reduce((s, r) => s + r.fin, 0).toFixed(0) : null;
  const fcfMarginTTM = (() => {
    const withM = t4.filter(r => r.fcfMargin != null);
    return withM.length === 4 ? +(withM.reduce((s, r) => s + r.fcfMargin, 0) / 4).toFixed(1) : null;
  })();

  React.useEffect(() => {
    if (!ref.current || !window.echarts || rows.length < 4) return;
    const chart = window.echarts.init(ref.current, null, { renderer: 'svg' });
    chart.setOption({
      animation: false,
      grid: { left: 52, right: 46, top: 28, bottom: 26 },
      legend: { top: 0, textStyle: { fontSize: 11.5, fontFamily: "'BiauKai','標楷體',serif", color: '#6a6f80' } },
      tooltip: { trigger: 'axis', backgroundColor: '#1a2438', borderWidth: 0, textStyle: { color: '#fff', fontSize: 11.5 } },
      xAxis: { type: 'category', data: rows.map(r => r.qLabel),
        axisLine: { lineStyle: { color: '#c9c2b2' } }, axisLabel: { color: '#999', fontSize: 11.5 }, axisTick: { show: false } },
      yAxis: [
        { type: 'value', name: '億元', nameTextStyle: { fontSize: 11.5, color: '#999' },
          axisLabel: { color: '#999', fontSize: 11.5 }, splitLine: { lineStyle: { color: '#eae4d6', type: 'dashed' } } },
        { type: 'value', name: '% 營收', nameTextStyle: { fontSize: 11.5, color: '#999' },
          axisLabel: { color: '#999', fontSize: 11.5, formatter: '{value}%' }, splitLine: { show: false } },
      ],
      series: [
        { name: '資本支出', type: 'bar', data: rows.map(r => +r.capex.toFixed(0)), barWidth: '30%', itemStyle: { color: '#2c5e8e' } },
        ...(hasDepr ? [{ name: '折舊攤提', type: 'bar', data: rows.map(r => r.depr != null ? +r.depr.toFixed(0) : null), barWidth: '30%', itemStyle: { color: '#c9c2b2' } }] : []),
        { name: 'FCF 利潤率', type: 'line', yAxisIndex: 1, data: rows.map(r => r.fcfMargin),
          symbol: 'circle', symbolSize: 4, lineStyle: { color: '#b8924d', width: 1.8 } },
      ],
    });
    const ro = new ResizeObserver(() => chart.resize());
    ro.observe(ref.current);
    return () => { ro.disconnect(); chart.dispose(); };
  }, [rows, hasDepr]);

  if (rows.length < 4) return null;

  return (
    <div style={{ marginTop: 18, borderTop: '1px dashed var(--paper-line)', paddingTop: 12 }}>
      <div className="kai" style={{ fontSize:'var(--fs-sm)', letterSpacing:'.1em', marginBottom:8 }}>
        資本配置 <span style={{ fontStyle:'italic', fontFamily:'var(--font-en)', fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>Capital Allocation — 管理層拿錢做什麼</span>
      </div>
      <div style={{ display:'flex', flexWrap:'wrap', border:'1px solid var(--paper-line)', background:'var(--paper-bright)', marginBottom:10 }}>
        {[
          ['再投資倍數', reinvest != null ? reinvest + 'x' : '—',
            reinvest == null ? null : reinvest > 1.5 ? '#2c5e8e' : reinvest < 0.8 ? 'var(--gold-deep)' : 'var(--ink)',
            reinvest == null ? '待折舊資料' : reinvest > 1.5 ? '積極擴張（CapEx 遠超折舊）' : reinvest < 0.8 ? '收割期（投資低於折舊）' : '維持性投資'],
          ['FCF 利潤率 (TTM)', fcfMarginTTM != null ? fcfMarginTTM + '%' : '—',
            fcfMarginTTM == null ? null : fcfMarginTTM >= 15 ? 'var(--bull)' : fcfMarginTTM < 5 ? 'var(--bear)' : 'var(--gold-deep)',
            '每 100 元營收留下的自由現金'],
          ['融資現金流 (TTM)', finTTM != null ? (finTTM > 0 ? '+' : '') + finTTM.toLocaleString() + ' 億' : '—',
            finTTM == null ? null : finTTM < 0 ? 'var(--bull)' : '#c0392b',
            finTTM == null ? '—' : finTTM < 0 ? '淨回饋股東/還債' : '淨對外籌資'],
        ].map(([l, v2, c, hint]) => (
          <div key={l} style={{ flex:'1 1 160px', padding:'10px 14px', textAlign:'center', borderRight:'1px solid var(--paper-line)' }}>
            <div className="num" style={{ fontSize:'var(--fs-md)', fontWeight:700, color: c || 'var(--ink)' }}>{v2}</div>
            <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:2 }}>{l}</div>
            <div className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)', marginTop:2 }}>{hint}</div>
          </div>
        ))}
      </div>
      <div ref={ref} style={{ width:'100%', height:210 }} />
      <div className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)', marginTop:4 }}>
        再投資倍數＝資本支出÷折舊攤提：&gt;1.5 押注未來、&lt;0.8 進入收割。融資現金流為負通常是配息/還債/庫藏股（好事），為正代表向市場伸手要錢。
      </div>
    </div>
  );
}

// ── 盈餘品質：含金量・應計項目・現金轉換循環・股利可持續性 ──
function EarningsQualitySection({ fins, bsData, cashFlow, dividends, ownership }) {
  const chartRef = React.useRef(null);
  const q = React.useMemo(() => {
    const byDate = {};
    fins.forEach(f => { byDate[f.date] = { date: f.date, ...f }; });
    bsData.forEach(b => { if (byDate[b.date]) Object.assign(byDate[b.date], b); });
    cashFlow.forEach(c => { if (byDate[c.date]) Object.assign(byDate[c.date], c); });
    const rows = Object.values(byDate)
      .filter(r => r.Revenue > 0)
      .sort((a, b) => a.date < b.date ? -1 : 1)
      .map(r => {
        const cogs = (r.Revenue != null && r.GrossProfit != null) ? r.Revenue - r.GrossProfit : null;
        const dso = (r.AccountsReceivable != null && r.Revenue > 0) ? r.AccountsReceivable / r.Revenue * 90 : null;
        const dio = (r.Inventories != null && cogs > 0) ? r.Inventories / cogs * 90 : null;
        const dpo = (r.AccountsPayable != null && cogs > 0) ? r.AccountsPayable / cogs * 90 : null;
        return {
          date: r.date,
          qLabel: `${r.date.slice(2, 4)}Q${Math.ceil(+r.date.slice(5, 7) / 3)}`,
          cashRatio: (r.OperatingCF != null && r.NetIncome > 0) ? +(r.OperatingCF / r.NetIncome).toFixed(2) : null,
          accrual: (r.OperatingCF != null && r.NetIncome != null && r.TotalAssets > 0)
            ? +((r.NetIncome - r.OperatingCF) / r.TotalAssets * 100).toFixed(2) : null,
          dso: dso != null ? +dso.toFixed(0) : null,
          dio: dio != null ? +dio.toFixed(0) : null,
          dpo: dpo != null ? +dpo.toFixed(0) : null,
          ccc: (dso != null && dio != null && dpo != null) ? +(dso + dio - dpo).toFixed(0) : null,
        };
      });
    return rows;
  }, [fins, bsData, cashFlow]);

  const hasCCC = q.some(r => r.ccc != null);
  const last = q[q.length - 1] || {};
  const prev4 = q[q.length - 5] || {};

  // 判定燈
  const lamps = [];
  if (last.cashRatio != null) lamps.push({
    ok: last.cashRatio >= 0.8, warn: last.cashRatio >= 0.5 && last.cashRatio < 0.8,
    txt: `盈餘含金量 ${last.cashRatio}（營業現金流 / 稅後淨利）` });
  if (last.accrual != null) lamps.push({
    ok: last.accrual <= 0, warn: last.accrual > 0 && last.accrual <= 3,
    txt: `應計項目比率 ${last.accrual > 0 ? '+' : ''}${last.accrual}%（越低越好）` });
  if (hasCCC && last.ccc != null && prev4.ccc != null) lamps.push({
    ok: last.ccc <= prev4.ccc, warn: last.ccc - prev4.ccc <= 10,
    txt: `現金轉換循環 ${last.ccc} 天（一年前 ${prev4.ccc} 天）` });

  // 股利可持續性
  const sustain = React.useMemo(() => {
    const shares = ownership?.latest?.sharesIssued;
    const years = {};
    fins.forEach(f => {
      const y = f.date.slice(0, 4);
      (years[y] = years[y] || []).push(f.EPS || 0);
    });
    const rows = (dividends || []).slice(-3).map(d => {
      const eps = years[d.year] && years[d.year].length === 4 ? years[d.year].reduce((a, b) => a + b, 0) : null;
      return { year: d.year, cash: d.cash, eps: eps != null ? +eps.toFixed(2) : null,
               payout: (eps > 0 && d.cash > 0) ? +(d.cash / eps * 100).toFixed(0) : null };
    });
    let fcfCover = null;
    const lastDiv = (dividends || []).slice(-1)[0];
    if (shares > 0 && lastDiv && lastDiv.cash > 0 && cashFlow.length >= 4) {
      const fcfTTM = cashFlow.slice(-4).reduce((s, c) => s + (c.FreeCF || 0), 0);
      const payTotal = lastDiv.cash * shares;
      if (payTotal > 0) fcfCover = +(fcfTTM / payTotal).toFixed(2);
    }
    return { rows, fcfCover };
  }, [fins, dividends, cashFlow, ownership]);

  React.useEffect(() => {
    if (!chartRef.current || !window.echarts || q.length < 4) return;
    const chart = window.echarts.init(chartRef.current, null, { renderer: 'svg' });
    chart.setOption({
      animation: false,
      grid: { left: 46, right: hasCCC ? 46 : 14, top: 26, bottom: 26 },
      legend: { top: 0, textStyle: { fontSize: 11.5, fontFamily: "'BiauKai','標楷體',serif", color: '#6a6f80' } },
      tooltip: { trigger: 'axis', backgroundColor: '#1a2438', borderWidth: 0, textStyle: { color: '#fff', fontSize: 11.5 } },
      xAxis: { type: 'category', data: q.map(r => r.qLabel),
        axisLine: { lineStyle: { color: '#c9c2b2' } }, axisLabel: { color: '#999', fontSize: 11.5 }, axisTick: { show: false } },
      yAxis: [
        { type: 'value', name: '含金量', nameTextStyle: { fontSize: 11.5, color: '#999' },
          axisLabel: { color: '#999', fontSize: 11.5 }, splitLine: { lineStyle: { color: '#eae4d6', type: 'dashed' } } },
        ...(hasCCC ? [{ type: 'value', name: 'CCC天', nameTextStyle: { fontSize: 11.5, color: '#999' },
          axisLabel: { color: '#999', fontSize: 11.5 }, splitLine: { show: false } }] : []),
      ],
      series: [
        { name: '盈餘含金量', type: 'bar', data: q.map(r => r.cashRatio), barWidth: '42%',
          itemStyle: { color: p => (p.value != null && p.value >= 0.8) ? '#4aaa6e' : '#c79d5e' } },
        { name: '基準 1.0', type: 'line', data: q.map(() => 1), symbol: 'none',
          lineStyle: { color: '#9c2b2b', type: 'dashed', width: 1 } },
        ...(hasCCC ? [{ name: '現金轉換循環', type: 'line', yAxisIndex: 1, data: q.map(r => r.ccc),
          symbol: 'circle', symbolSize: 4, lineStyle: { color: '#1a2438', width: 1.6 } }] : []),
      ],
    });
    const ro = new ResizeObserver(() => chart.resize());
    ro.observe(chartRef.current);
    return () => { ro.disconnect(); chart.dispose(); };
  }, [q, hasCCC]);

  if (q.length < 4) return <div style={{ color:'var(--ink-mute)', fontSize:'var(--fs-xs)', padding:'12px 0' }} className="kai">季報資料不足，無法評估盈餘品質。</div>;

  return (
    <div>
      {/* 判定燈列 */}
      <div style={{ display:'flex', flexWrap:'wrap', gap:10, marginBottom:12 }}>
        {lamps.map((l, i) => (
          <span key={i} className="kai" style={{
            fontSize:'var(--fs-xs)', padding:'4px 12px',
            border:'1px solid ' + (l.ok ? 'rgba(42,107,76,.4)' : l.warn ? 'rgba(184,146,77,.5)' : 'rgba(156,43,43,.45)'),
            color: l.ok ? 'var(--bull)' : l.warn ? 'var(--gold-deep)' : 'var(--bear)',
            background: l.ok ? 'rgba(42,107,76,.06)' : l.warn ? 'rgba(184,146,77,.07)' : 'rgba(156,43,43,.06)',
          }}>{l.ok ? '●' : l.warn ? '◐' : '○'} {l.txt}</span>
        ))}
      </div>
      <div ref={chartRef} style={{ width:'100%', height:220 }} />
      {/* CCC 拆解表 */}
      {hasCCC ? (
        <div style={{ overflowX:'auto', marginTop:10 }}>
          <table className="fund-table" style={{ fontVariantNumeric:'tabular-nums' }}>
            <thead><tr><th style={{textAlign:'left'}}>季度</th><th>應收天數 DSO</th><th>存貨天數 DIO</th><th>應付天數 DPO</th><th>現金轉換循環</th><th>應計比率</th></tr></thead>
            <tbody>
              {q.slice(-4).map(r => (
                <tr key={r.date}>
                  <td style={{textAlign:'left', fontFamily:'var(--font-cn)'}}>{r.qLabel}</td>
                  <td>{r.dso ?? '—'}</td><td>{r.dio ?? '—'}</td><td>{r.dpo ?? '—'}</td>
                  <td style={{fontWeight:700}}>{r.ccc ?? '—'}</td>
                  <td style={{color: r.accrual > 3 ? 'var(--bear)' : r.accrual > 0 ? 'var(--gold-deep)' : 'var(--bull)'}}>{r.accrual != null ? (r.accrual > 0 ? '+' : '') + r.accrual + '%' : '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      ) : (
        <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:8 }}>
          ※ 應收/存貨/應付明細待資料快取更新後提供（新欄位已上線，最長 6 小時）。
        </div>
      )}
      {/* 股利可持續性 */}
      <div style={{ marginTop:18, borderTop:'1px dashed var(--paper-line)', paddingTop:12 }}>
        <div className="kai" style={{ fontSize:'var(--fs-sm)', letterSpacing:'.1em', marginBottom:8 }}>
          股利可持續性 <span style={{ fontStyle:'italic', fontFamily:'var(--font-en)', fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>Dividend Sustainability</span>
        </div>
        <div style={{ display:'flex', flexWrap:'wrap', gap:16, alignItems:'flex-start' }}>
          <table className="fund-table" style={{ width:'auto', minWidth:280 }}>
            <thead><tr><th style={{textAlign:'left'}}>盈餘年度</th><th>現金股利</th><th>年度 EPS</th><th>配發率</th></tr></thead>
            <tbody>
              {sustain.rows.map(r => (
                <tr key={r.year}>
                  <td style={{textAlign:'left'}}>{r.year}</td>
                  <td>{r.cash}</td>
                  <td>{r.eps ?? '—'}</td>
                  <td style={{ color: r.payout == null ? 'var(--ink-mute)' : r.payout <= 70 ? 'var(--bull)' : r.payout <= 100 ? 'var(--gold-deep)' : 'var(--bear)', fontWeight:700 }}>
                    {r.payout != null ? r.payout + '%' : '—'}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
          <div style={{ flex:'1 1 200px' }}>
            <div className="num" style={{ fontSize:'var(--fs-num)', fontWeight:700, lineHeight:1.1,
              color: sustain.fcfCover == null ? 'var(--ink-mute)' : sustain.fcfCover >= 1.5 ? 'var(--bull)' : sustain.fcfCover >= 1 ? 'var(--gold-deep)' : 'var(--bear)' }}>
              {sustain.fcfCover != null ? sustain.fcfCover + 'x' : '—'}
            </div>
            <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:4 }}>
              自由現金流覆蓋倍數<br/>（近4季 FCF ÷ 最近年度現金股利總額）<br/>
              {sustain.fcfCover == null ? '待股數/股利資料' : sustain.fcfCover >= 1 ? '付得起' : '⚠ FCF 蓋不住股利——靠借錢或老本配息'}
            </div>
          </div>
        </div>
        <div className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)', marginTop:6 }}>
          ※ 配發率以盈餘所屬年度近似對應；配發率 &gt;100% 或 FCF 覆蓋 &lt;1 為「假殖利率」警訊。
        </div>
      </div>
    </div>
  );
}

// ── 股權結構：外資持股比率 1 年趨勢（type=ownership）──
function OwnershipSection({ ownership, ownErr }) {
  const ref = React.useRef(null);
  const rows = ownership?.foreign || [];
  React.useEffect(() => {
    if (!ref.current || !window.echarts || rows.length < 8) return;
    const chart = window.echarts.init(ref.current, null, { renderer: 'svg' });
    const vals = rows.map(r => r.ratio);
    chart.setOption({
      animation: false,
      grid: { left: 46, right: 14, top: 16, bottom: 26 },
      tooltip: { trigger: 'axis', backgroundColor: '#1a2438', borderWidth: 0, textStyle: { color: '#fff', fontSize: 11.5 },
        formatter: ps => `${ps[0].axisValue}<br/>外資持股 <b>${(+ps[0].value).toFixed(2)}%</b>` },
      xAxis: { type: 'category', data: rows.map(r => r.date.slice(2)),
        axisLine: { lineStyle: { color: '#c9c2b2' } }, axisLabel: { color: '#999', fontSize: 11.5, interval: Math.floor(rows.length / 6) }, axisTick: { show: false } },
      yAxis: { type: 'value', min: Math.floor(Math.min(...vals) - 1), max: Math.ceil(Math.max(...vals) + 1),
        axisLabel: { color: '#999', fontSize: 11.5, formatter: '{value}%' },
        splitLine: { lineStyle: { color: '#eae4d6', type: 'dashed' } } },
      series: [{
        type: 'line', data: vals, symbol: 'none',
        lineStyle: { color: '#b8924d', width: 2 },
        areaStyle: { color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
          colorStops: [{ offset: 0, color: 'rgba(184,146,77,.22)' }, { offset: 1, color: 'rgba(184,146,77,.02)' }] } },
      }],
    });
    const ro = new ResizeObserver(() => chart.resize());
    ro.observe(ref.current);
    return () => { ro.disconnect(); chart.dispose(); };
  }, [rows.length]);

  if (ownErr) return <div className="kai" style={{ color:'var(--ink-mute)', fontSize:'var(--fs-xs)', padding:'12px 0' }}>外資持股資料暫時無法取得（{ownErr}）。</div>;
  if (!ownership) return <div className="kai" style={{ color:'var(--ink-mute)', fontSize:'var(--fs-xs)', padding:'12px 0' }}>載入中…</div>;
  if (rows.length < 8) return <div className="kai" style={{ color:'var(--ink-mute)', fontSize:'var(--fs-xs)', padding:'12px 0' }}>外資持股歷史不足。</div>;

  const first = rows[0], lastR = rows[rows.length - 1];
  const chgPp = +(lastR.ratio - first.ratio).toFixed(2);
  return (
    <div>
      <div style={{ display:'flex', flexWrap:'wrap', gap:0, border:'1px solid var(--paper-line)', background:'var(--paper-bright)', marginBottom:12 }}>
        {[
          ['最新外資持股', lastR.ratio.toFixed(2) + '%', null],
          ['一年變化', (chgPp > 0 ? '+' : '') + chgPp + ' pp', chgPp > 0 ? 'var(--bull)' : chgPp < 0 ? 'var(--bear)' : null],
          ['法定上限', ownership.latest?.upperLimit != null ? ownership.latest.upperLimit + '%' : '—', null],
          ['資料日', ownership.latest?.date || '—', null],
        ].map(([l, v, c]) => (
          <div key={l} style={{ flex:'1 1 120px', padding:'10px 12px', textAlign:'center', borderRight:'1px solid var(--paper-line)' }}>
            <div className="num" style={{ fontSize:'var(--fs-md)', fontWeight:700, color: c || 'var(--ink)' }}>{v}</div>
            <div className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:2 }}>{l}</div>
          </div>
        ))}
      </div>
      <div ref={ref} style={{ width:'100%', height:200 }} />
      <div className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)', marginTop:4 }}>
        外資持股％看「存量」（法人買賣超是流量）——持股比率創高/破底常是趨勢級訊號。
        集保大戶分級（400張以上持股比）需付費資料源，暫不提供、不以估算充數。
      </div>
    </div>
  );
}

const TIER_BT_KEY = 'tw_tier_bt_v1';
function loadTierBtRecords() { try { return JSON.parse(localStorage.getItem(TIER_BT_KEY)) || []; } catch { return []; } }
function saveTierBtRecords(list) { try { localStorage.setItem(TIER_BT_KEY, JSON.stringify(list)); } catch {} }

function TierBacktestPanel({ code, score, tierLabel, currentPrice }) {
  const [records, setRecords] = React.useState(loadTierBtRecords);
  const today = new Date().toISOString().slice(0, 10);

  function record() {
    if (!currentPrice) return;
    const existing = records.find(r => r.code === code && r.date === today);
    if (existing) return;
    const next = [{ id: Date.now(), code, date: today, score, tierLabel, price: currentPrice }, ...records].slice(0, 100);
    saveTierBtRecords(next); setRecords(next);
  }

  const codeRecs = records.filter(r => r.code === code);
  const allWithReturn = records.filter(r => r.price > 0 && currentPrice > 0 && r.code === code).map(r => ({
    ...r,
    daysSince: Math.round((Date.now() - new Date(r.date).getTime()) / 86400000),
    rtn: +((currentPrice - r.price) / r.price * 100).toFixed(2),
  }));

  // 全品種段位 vs 報酬統計（只有 90 天以上的記錄才有意義）
  const allRecs = records.filter(r => r.price > 0);
  const tierStats = {};
  allRecs.forEach(r => {
    const pxNow = r.code === code ? currentPrice : null; // 只有當前股票有現價
    if (!pxNow) return;
    const rtn = (pxNow - r.price) / r.price * 100;
    const days = Math.round((Date.now() - new Date(r.date).getTime()) / 86400000);
    if (days < 30) return; // 30天以下太短不計
    if (!tierStats[r.tierLabel]) tierStats[r.tierLabel] = { count: 0, wins: 0, total: 0 };
    tierStats[r.tierLabel].count++;
    tierStats[r.tierLabel].total += rtn;
    if (rtn > 0) tierStats[r.tierLabel].wins++;
  });

  const alreadyRecordedToday = codeRecs.some(r => r.date === today);

  return (
    <div style={{ marginTop: 18, borderTop: '1px dashed var(--paper-line)', paddingTop: 14 }}>
      <div style={{ display:'flex', alignItems:'center', gap:12, marginBottom:10 }}>
        <span className="kai" style={{ fontSize:13, color:'var(--ink)', letterSpacing:'.1em' }}>
          段位驗證追蹤
        </span>
        <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize: 'var(--fs-xs)', color:'var(--ink-mute)' }}>
          Tier Validation · 記錄段位 → 3個月後對比報酬
        </span>
        <button onClick={record} disabled={alreadyRecordedToday || !currentPrice}
                style={{
                  marginLeft:'auto', fontFamily:'BiauKai,標楷體,serif', fontSize: 'var(--fs-xs)',
                  padding:'2px 10px', cursor: alreadyRecordedToday ? 'default' : 'pointer',
                  border:'1px solid var(--gold)', borderRadius:2,
                  background: alreadyRecordedToday ? 'transparent' : 'rgba(184,146,77,0.1)',
                  color: alreadyRecordedToday ? 'var(--ink-mute)' : 'var(--gold)',
                }}>
          {alreadyRecordedToday ? '✓ 今日已記錄' : '📌 記錄今日段位'}
        </button>
      </div>
      {allWithReturn.length > 0 ? (
        <table style={{ width:'100%', borderCollapse:'collapse', fontSize: 'var(--fs-xs)', fontFamily:'Times New Roman,serif' }}>
          <thead>
            <tr style={{ borderBottom:'1px solid var(--paper-line)', color:'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>
              <th style={{ textAlign:'left', padding:'2px 6px', fontWeight:400 }}>記錄日</th>
              <th style={{ textAlign:'center', padding:'2px 6px', fontWeight:400 }}>段位</th>
              <th style={{ textAlign:'right', padding:'2px 6px', fontWeight:400 }}>當時股價</th>
              <th style={{ textAlign:'right', padding:'2px 6px', fontWeight:400 }}>天數</th>
              <th style={{ textAlign:'right', padding:'2px 6px', fontWeight:400 }}>報酬%</th>
            </tr>
          </thead>
          <tbody>
            {allWithReturn.map(r => (
              <tr key={r.id} style={{ borderBottom:'1px solid rgba(26,36,56,0.05)' }}>
                <td style={{ padding:'3px 6px', color:'var(--ink-mute)' }}>{r.date}</td>
                <td style={{ padding:'3px 6px', textAlign:'center' }}>
                  <span className="kai" style={{ fontSize: 'var(--fs-xs)', fontWeight:700 }}>{r.tierLabel}</span>
                  <span style={{ color:'var(--ink-mute)', marginLeft:4 }}>{r.score}</span>
                </td>
                <td style={{ padding:'3px 6px', textAlign:'right' }}>{r.price.toLocaleString()}</td>
                <td style={{ padding:'3px 6px', textAlign:'right', color:'var(--ink-mute)' }}>{r.daysSince}d</td>
                <td style={{ padding:'3px 6px', textAlign:'right', fontWeight:700,
                             color: r.rtn >= 0 ? 'var(--bull)' : 'var(--bear)' }}>
                  {r.rtn >= 0 ? '+' : ''}{r.rtn}%
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      ) : (
        <div style={{ color:'var(--ink-mute)', fontFamily:'BiauKai,標楷體,serif', fontSize: 'var(--fs-xs)', paddingLeft:4 }}>
          {codeRecs.length > 0
            ? `已記錄 ${codeRecs.length} 筆，稍後回來查看報酬（建議 30 天後對比）`
            : '尚無記錄 — 點「記錄今日段位」，30 天後回來看段位是否預測了報酬'}
        </div>
      )}
      <div style={{ marginTop:8, fontFamily:'BiauKai,標楷體,serif', fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', fontStyle:'italic' }}>
        本功能只記錄當前股票。記錄跨越 30+ 天才顯示報酬。最多保留 100 筆，純本機 localStorage。
      </div>
    </div>
  );
}

function TierSection({ fins, bsData, cashFlow, revenue, perData, pe, pb, dy, chipSummary, peerBwibbu, code, currentPrice }) {
  const tierRes = React.useMemo(
    () => computeTier({ fins, bsData, cashFlow, revenue, pe, pb, dy, chipSummary, peerBwibbu, code }),
    [fins, bsData, cashFlow, revenue, pe, pb, dy, chipSummary, peerBwibbu, code]);
  const prs = React.useMemo(
    () => computePeerPR({ pe, pb, dy, peerBwibbu, code }),
    [pe, pb, dy, peerBwibbu, code]);
  const { score, tier, dims, goods, bads, lights } = tierRes;
  const passN = lights.filter(l => l.state === true).length;
  const validN = lights.filter(l => l.state != null).length;

  return (
    <section className="fund-section">
      <div className="fund-section-head">
        <span className="sec-num">00</span>
        <div className="fund-section-title-wrap">
          <h2>企業段位</h2>
          <span className="sec-en">Verdict at a Glance</span><SecNote id="tier"/>
        </div>
      </div>
      <div className="tier-grid">
        {/* 印章 + 五維 */}
        <div className="tier-seal-card">
          <div className="tier-seal" style={{ borderColor: tier.color, color: tier.color }}>
            <span className="tier-seal-label kai">{tier.label}</span>
            <span className="tier-seal-score num">{score}</span>
          </div>
          <div className="tier-seal-en">{tier.en}</div>
          <div className="tier-seal-desc kai">{tier.desc}</div>
          <div className="tier-dims">
            {dims.map(d => (
              <div key={d.name} className="tier-dim">
                <span className="tier-dim-name kai">{d.name}</span>
                <span className="tier-dim-bar">
                  <span className="tier-dim-fill" style={{
                    width: `${Math.min(100, d.val / d.max * 100)}%`,
                    background: d.val / d.max >= 0.65 ? 'var(--bull)' : d.val / d.max >= 0.4 ? 'var(--gold)' : 'var(--bear)',
                  }}/>
                </span>
                <span className="tier-dim-val num">{Math.round(d.val)}/{d.max}</span>
              </div>
            ))}
          </div>
          {(goods.length > 0 || bads.length > 0) && (
            <div className="tier-why">
              {goods.map((g, i) => <span key={'g'+i} className="tier-chip good">✓ {g}</span>)}
              {bads.map((b, i)  => <span key={'b'+i} className="tier-chip bad">✗ {b}</span>)}
            </div>
          )}
          {prs.length > 0 && (
            <div className="tier-prs">
              {prs.map((p, i) => (
                <span key={i} className="tier-pr" data-tip={p.note}>
                  {p.label} <b className="num" style={{ color: p.pr >= 70 ? 'var(--bull)' : p.pr >= 40 ? 'var(--gold-deep)' : 'var(--bear)' }}>PR {p.pr}</b>
                </span>
              ))}
              <span style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)' }}>贏過 PR% 的同業</span>
            </div>
          )}
        </div>
        {/* 紅綠燈 + 河流圖 */}
        <div className="tier-right">
          <div className="tier-lights-head">
            <span className="kai" style={{ fontSize:13, letterSpacing:'.1em' }}>
              健檢紅綠燈 <span style={{ fontStyle:'italic', color:'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>Checklist</span>
            </span>
            <span className="num" style={{ fontSize:15, fontWeight:700,
              color: passN / Math.max(1, validN) >= 0.65 ? 'var(--bull)' : passN / Math.max(1, validN) >= 0.4 ? 'var(--gold-deep)' : 'var(--bear)' }}>
              {passN}<small style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', fontWeight:400 }}>/{validN} 過關</small>
            </span>
          </div>
          <div className="tier-lights">
            {lights.map((l, i) => (
              <div key={i} className="tier-light">
                <span className="tl-dot" style={{
                  background: l.state === true ? 'var(--bull)' : l.state === false ? 'var(--bear)' : '#c9c2b2',
                }}/>
                <span className="tl-label" style={{ color: l.state == null ? 'var(--ink-mute)' : 'var(--ink)' }}>{l.label}</span>
              </div>
            ))}
          </div>
          <div style={{ borderTop:'1px solid var(--paper-line)', marginTop:10, paddingTop:10 }}>
            <PERiver perData={perData} pe={pe}/>
          </div>
          <div style={{ borderTop:'1px solid var(--paper-line)', marginTop:10, paddingTop:10 }}>
            <PERiver perData={perData} pe={pb} field="PBR" cap={60} titleCn="股價淨值比河流" unitLabel="P/B"/>
          </div>
        </div>
      </div>
      <TierBacktestPanel code={code} score={score} tierLabel={tier.label} currentPrice={currentPrice} />
    </section>
  );
}

// ─── Section M:企業護城河 ─────────────────────────────
// 護城河看不到，但痕跡量得到：獲利持續力30 + 定價權30 + 現金製造力20 + 資本效率20
// ≥70 寬護城河 / ≥45 窄護城河 / <45 無護城河；技術方向質化判讀交給 AI

const MOAT_DEFS = [
  { min: 70, label: '寬護城河', en: 'WIDE MOAT',   color: '#9a7b35', desc: '結構性優勢深厚，對手短期攻不進來' },
  { min: 45, label: '窄護城河', en: 'NARROW MOAT', color: '#1a2438', desc: '有優勢但壁壘不深，需持續監控侵蝕' },
  { min: -1, label: '無護城河', en: 'NO MOAT',     color: '#8a8578', desc: '人人能做的生意，獲利隨景氣來去' },
];

function computeMoat({ fins, bsData, cashFlow }) {
  const dims = [], goods = [], bads = [];
  const ni4  = fins.slice(-4).reduce((s, q) => s + (q.NetIncome || 0), 0);
  const rev4 = fins.slice(-4).reduce((s, q) => s + (q.Revenue || 0), 0);

  // ── 獲利持續力 30:年化 ROE ≥15% 連續季數（好一季是運氣，好十二季是護城河）──
  let sPersist = 15, roeStreak = null;
  const bsByDate = {};
  bsData.forEach(b => { bsByDate[b.date] = b; });
  const roeQ = fins.map(q => {
    const eq = bsByDate[q.date] && bsByDate[q.date].StockholdersEquity;
    return (eq > 0 && q.NetIncome != null) ? q.NetIncome / eq * 4 * 100 : null;
  }).filter(v => v != null);
  if (roeQ.length >= 4) {
    roeStreak = 0;
    for (let i = roeQ.length - 1; i >= 0; i--) { if (roeQ[i] >= 15) roeStreak++; else break; }
    sPersist = roeStreak >= 12 ? 30 : roeStreak >= 8 ? 25 : roeStreak >= 6 ? 20 : roeStreak >= 4 ? 14 : roeStreak >= 2 ? 8 : roeStreak === 1 ? 4 : 0;
    if (roeStreak >= 8) goods.push(`ROE 連 ${roeStreak} 季年化 ≥15%`);
    if (roeStreak === 0) bads.push('ROE 未達 15% 門檻');
  }
  dims.push({ name: '獲利持續力', val: sPersist, max: 30 });

  // ── 定價權 30:毛利率水準 + 穩定度（能漲價不掉客戶，且不隨景氣劇烈波動）──
  let sPricing = 15;
  const gmQ = fins.map(q => q.Revenue > 0 ? (q.GrossProfit || 0) / q.Revenue * 100 : null).filter(v => v != null);
  const gmTTM = rev4 > 0 ? fins.slice(-4).reduce((s, q) => s + (q.GrossProfit || 0), 0) / rev4 * 100 : null;
  let gmStd = null;
  if (gmTTM != null && gmQ.length >= 8) {
    const g8 = gmQ.slice(-8);
    const avg = g8.reduce((a, b) => a + b, 0) / g8.length;
    gmStd = Math.sqrt(g8.reduce((a, b) => a + (b - avg) ** 2, 0) / g8.length);
    sPricing = (gmTTM > 50 ? 16 : gmTTM > 35 ? 13 : gmTTM > 25 ? 9 : gmTTM > 15 ? 5 : 1)
             + (gmStd < 2 ? 14 : gmStd < 4 ? 10 : gmStd < 6 ? 6 : gmStd < 8 ? 3 : 0);
    if (gmTTM > 40 && gmStd < 4) goods.push(`毛利率 ${gmTTM.toFixed(0)}% 且穩定（±${gmStd.toFixed(1)}pp）`);
    if (gmStd >= 8) bads.push(`毛利率波動大（±${gmStd.toFixed(1)}pp）`);
    if (gmTTM <= 15) bads.push(`毛利率僅 ${gmTTM.toFixed(1)}%`);
  }
  dims.push({ name: '定價權', val: sPricing, max: 30 });

  // ── 現金製造力 20:FCF/淨利轉換率（賺的是真錢還是紙上富貴）──
  let sCash = 10, fcfConv = null;
  if (cashFlow && cashFlow.length >= 4 && ni4 > 0) {
    const fcf4 = cashFlow.slice(-4).reduce((s, q) => s + (q.FreeCF || 0), 0);
    fcfConv = fcf4 / ni4 * 100;
    sCash = fcfConv >= 90 ? 20 : fcfConv >= 60 ? 15 : fcfConv >= 30 ? 10 : fcfConv > 0 ? 6 : 0;
    if (fcfConv >= 80) goods.push(`FCF/淨利 ${fcfConv.toFixed(0)}%，現金製造機`);
    if (fcfConv <= 0)  bads.push('自由現金流為負');
  }
  dims.push({ name: '現金製造力', val: sCash, max: 20 });

  // ── 資本效率 20:ROA + 資本支出強度（不用一直餵錢就能守住優勢）──
  let sCapital = 10, roa = null, capexPct = null;
  const bsL = bsData[bsData.length - 1] || {};
  if (bsL.TotalAssets > 0 && fins.length >= 4) {
    roa = ni4 / bsL.TotalAssets * 100;
    let p1 = roa > 12 ? 12 : roa > 8 ? 9 : roa > 4 ? 6 : roa > 1 ? 3 : 0;
    let p2 = 4;
    if (cashFlow && cashFlow.length >= 4 && rev4 > 0) {
      const capx4 = Math.abs(cashFlow.slice(-4).reduce((s, q) => s + (q.CapEx || 0), 0));
      capexPct = capx4 / rev4 * 100;
      p2 = capexPct < 5 ? 8 : capexPct < 12 ? 6 : capexPct < 25 ? 3 : 1;
      if (capexPct >= 25) bads.push(`資本支出吃掉營收 ${capexPct.toFixed(0)}%（重資產）`);
    }
    sCapital = p1 + p2;
    if (roa > 10) goods.push(`ROA ${roa.toFixed(0)}%`);
  }
  dims.push({ name: '資本效率', val: sCapital, max: 20 });

  const score = Math.round(dims.reduce((s, d) => s + d.val, 0));
  const tier = MOAT_DEFS.find(t => score >= t.min);
  return { score, tier, dims, goods: goods.slice(0, 4), bads: bads.slice(0, 3),
           roeStreak, gmTTM, gmStd, fcfConv, roa, capexPct };
}

// 五型護城河痕跡比對（純規則，不用 AI）：每型列「典型財報痕跡」與該公司吻合度
function moatTypeMatch(m, profile) {
  const has = v => v != null;
  const cp = profile?.chainPos || '';
  return [
    {
      type: '無形資產', en: 'Intangibles', icon: '◈',
      hint: '專利/品牌帶來高毛利且不易被殺價',
      rule: '毛利率 >40% 且波動 <4pp',
      state: (has(m.gmTTM) && has(m.gmStd)) ? (m.gmTTM > 40 && m.gmStd < 4) : null,
      evid: has(m.gmTTM) ? `毛利率 ${m.gmTTM.toFixed(0)}%（±${has(m.gmStd) ? m.gmStd.toFixed(1) : '?'}pp）` : '資料不足',
    },
    {
      type: '轉換成本', en: 'Switching Costs', icon: '⚙',
      hint: '客戶換供應商很痛，獲利黏著穩定',
      rule: 'ROE 連 8 季 ≥15% 且毛利率波動 <3pp',
      state: (has(m.roeStreak) && has(m.gmStd)) ? (m.roeStreak >= 8 && m.gmStd < 3) : null,
      evid: has(m.roeStreak) ? `ROE 連 ${m.roeStreak} 季達標` : '資料不足',
    },
    {
      type: '成本優勢', en: 'Cost Advantage', icon: '◎',
      hint: '毛利不高也能賺贏同業（規模採購/製程效率）',
      rule: '毛利率 <30% 但 ROA >8%',
      state: (has(m.gmTTM) && has(m.roa)) ? (m.gmTTM < 30 && m.roa > 8) : null,
      evid: has(m.roa) ? `ROA ${m.roa.toFixed(1)}%` : '資料不足',
    },
    {
      type: '規模效率', en: 'Efficient Scale', icon: '▣',
      hint: '重資產築起進入壁壘，新玩家蓋不起',
      rule: '資本支出 >15% 營收且 ROE 仍連 4 季達標',
      state: (has(m.capexPct) && has(m.roeStreak)) ? (m.capexPct >= 15 && m.roeStreak >= 4) : null,
      evid: has(m.capexPct) ? `資本支出占營收 ${m.capexPct.toFixed(0)}%${cp === 'upstream' ? ' · 供應鏈上游' : ''}` : '資料不足',
    },
    {
      type: '網絡效應', en: 'Network Effect', icon: '✦',
      hint: '越多人用越好用（平台/通路密度）',
      rule: '財報量不到，需看用戶數/店數等營運指標',
      state: null,
      evid: '財報無法偵測',
    },
  ];
}

// 技術版圖狀態標籤
const TECH_STATUS = {
  lead:  { label: '技術領先', color: '#9a7b35', bg: 'rgba(184,146,77,.10)' },
  par:   { label: '同步競爭', color: '#1a2438', bg: 'rgba(26,36,56,.06)' },
  chase: { label: '追趕中',   color: '#8a8578', bg: 'rgba(138,133,120,.10)' },
};

function MoatSection({ code, profile, fins, bsData, cashFlow, rtName }) {
  const moat = React.useMemo(() => computeMoat({ fins, bsData, cashFlow }), [fins, bsData, cashFlow]);
  const types = React.useMemo(() => moatTypeMatch(moat, profile), [moat, profile]);
  const matched = types.filter(t => t.state === true);
  const tech = window.getTechMoat ? window.getTechMoat(code) : null;

  return (
    <section className="fund-section">
      <div className="fund-section-head">
        <span className="sec-num">M</span>
        <div className="fund-section-title-wrap">
          <h2>企業護城河</h2>
          <span className="sec-en">Economic Moat</span><SecNote id="moat"/>
        </div>
      </div>
      <div className="tier-grid">
        {/* 護城河印章 + 四維痕跡 */}
        <div className="tier-seal-card">
          <div className="tier-seal" style={{ borderColor: moat.tier.color, color: moat.tier.color }}>
            <span className="tier-seal-label kai" style={{ fontSize: 26 }}>{moat.tier.label}</span>
            <span className="tier-seal-score num">{moat.score}</span>
          </div>
          <div className="tier-seal-en">{moat.tier.en}</div>
          <div className="tier-seal-desc kai">{moat.tier.desc}</div>
          <div className="tier-dims">
            {moat.dims.map(d => (
              <div key={d.name} className="tier-dim">
                <span className="tier-dim-name kai" style={{ width: 70 }}>{d.name}</span>
                <span className="tier-dim-bar">
                  <span className="tier-dim-fill" style={{
                    width: `${Math.min(100, d.val / d.max * 100)}%`,
                    background: d.val / d.max >= 0.65 ? 'var(--bull)' : d.val / d.max >= 0.4 ? 'var(--gold)' : 'var(--bear)',
                  }}/>
                </span>
                <span className="tier-dim-val num">{Math.round(d.val)}/{d.max}</span>
              </div>
            ))}
          </div>
          {(moat.goods.length > 0 || moat.bads.length > 0) && (
            <div className="tier-why">
              {moat.goods.map((g, i) => <span key={'g'+i} className="tier-chip good">✓ {g}</span>)}
              {moat.bads.map((b, i)  => <span key={'b'+i} className="tier-chip bad">✗ {b}</span>)}
            </div>
          )}
          <div style={{ fontSize: 'var(--fs-xs)', color: 'var(--ink-mute)', borderTop: '1px dashed var(--paper-line)', paddingTop: 8, lineHeight: 1.6 }}>
            護城河量化痕跡：獲利持續力（ROE 連續≥15% 季數）· 定價權（毛利率水準＋穩定度）·
            現金製造力（FCF/淨利）· 資本效率（ROA＋資本支出強度）
          </div>
        </div>
        {/* 右側：有建檔→技術版圖；未建檔→五型痕跡比對 fallback */}
        {tech ? (
        <div className="tier-right">
          <div className="tier-lights-head">
            <span className="kai" style={{ fontSize: 13, letterSpacing: '.1em' }}>
              技術版圖 <span style={{ fontStyle: 'italic', color: 'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>Technology Platforms</span>
            </span>
            <span className="kai" style={{ fontSize: 'var(--fs-xs)', color: 'var(--gold-deep)' }}>{tech.headline}</span>
          </div>
          <div className="techm-list">
            {tech.domains.map((d, i) => {
              const st = TECH_STATUS[d.status] || TECH_STATUS.par;
              return (
                <div key={i} className={`techm-card ${d.status === 'lead' ? 'lead' : ''}`}>
                  <div className="techm-head">
                    <span className="techm-en">{d.en}</span>
                    <span className="techm-zh kai">{d.zh}</span>
                    <span className="techm-status" style={{ color: st.color, background: st.bg, borderColor: st.color }}>
                      {st.label}
                    </span>
                  </div>
                  <div className="techm-note kai">{d.note}</div>
                  {d.rivals && (
                    <div className="techm-rivals">對手：{d.rivals}</div>
                  )}
                </div>
              );
            })}
          </div>
          <div style={{ fontSize: 'var(--fs-xs)', color: 'var(--ink-mute)', borderTop: '1px dashed var(--paper-line)', paddingTop: 8, marginTop: 10, lineHeight: 1.6 }}>
            策展資料（法說會／技術論壇後更新）· 領先＝規格主導或市佔第一 · 同步＝第一梯隊競爭 · 追趕＝落後對手一個世代以上
          </div>
        </div>
        ) : (
        <div className="tier-right">
          <div className="tier-lights-head">
            <span className="kai" style={{ fontSize: 13, letterSpacing: '.1em' }}>
              五型護城河比對 <span style={{ fontStyle: 'italic', color: 'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>Moat Sources</span>
            </span>
            <span className="kai" style={{ fontSize: 'var(--fs-xs)', color: matched.length ? 'var(--gold-deep)' : 'var(--ink-mute)' }}>
              {matched.length ? `痕跡吻合：${matched.map(t => t.type).join('、')}` : '無明顯吻合型'}
            </span>
          </div>
          <div className="moat-types">
            {types.map(t => (
              <div key={t.type} className={`moat-type ${t.state === true ? 'hit' : ''}`}>
                <span className="moat-type-icon" style={{
                  color: t.state === true ? 'var(--gold-deep)' : 'var(--ink-mute)',
                }}>{t.icon}</span>
                <div className="moat-type-main">
                  <div className="moat-type-head">
                    <span className="kai moat-type-name">{t.type}</span>
                    <span className="moat-type-en">{t.en}</span>
                    <span className="moat-type-state" style={{
                      color: t.state === true ? 'var(--bull)' : t.state === false ? 'var(--ink-mute)' : '#b3ac9c',
                    }}>
                      {t.state === true ? '✓ 痕跡吻合' : t.state === false ? '— 不明顯' : '？量不到'}
                    </span>
                  </div>
                  <div className="moat-type-hint kai">{t.hint}</div>
                  <div className="moat-type-rule">
                    判定：{t.rule} <span style={{ color: 'var(--ink-soft)' }}>· {t.evid}</span>
                  </div>
                </div>
              </div>
            ))}
          </div>
          <div style={{ fontSize: 'var(--fs-xs)', color: 'var(--ink-mute)', borderTop: '1px dashed var(--paper-line)', paddingTop: 8, marginTop: 10, lineHeight: 1.6 }}>
            純財報規則比對，非定論——網絡效應與新技術轉折需質化判斷；此公司技術版圖尚未建檔，建檔後將顯示技術平台拆解。
          </div>
        </div>
        )}
      </div>
    </section>
  );
}

// ─── 月營收長條圖 ──────────────────────────────────────

function RevenueChart({ data }) {
  if (!data.length) return (
    <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
      <div style={{fontSize:24, marginBottom:8}}>📊</div>
      <div>暫無月營收資料</div>
      <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
    </div>
  );
  const [hover, setHover] = useState(null);
  const W = 900, H = 180, PL = 52, PR = 10, PT = 30, PB = 28;
  const maxRev = Math.max(...data.map(d => d.rev));
  const stepX = (W - PL - PR) / data.length;
  const bW = stepX * 0.65;
  const yOf = v => PT + (1 - v / (maxRev * 1.1)) * (H - PT - PB);

  // YoY coloring
  const colored = data.map((d, i) => {
    const prev = data.find(p => p.year === d.year - 1 && p.month === d.month);
    const yoy = prev ? (d.revRaw - prev.revRaw) / prev.revRaw : null;
    return { ...d, yoy };
  });

  return (
    <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none"
         style={{ width:'100%', height:'auto', fontFamily:'Times New Roman', display:'block' }}
         onMouseLeave={() => setHover(null)}>
      {/* Grid */}
      {[0, 0.33, 0.67, 1].map((r, i) => {
        const v = maxRev * 1.1 * r;
        const y = yOf(v);
        return (
          <g key={i}>
            <line x1={PL} x2={W-PR} y1={y} y2={y} stroke="#e8e3d8" strokeWidth=".5" strokeDasharray="2 4"/>
            <text x={PL-4} y={y+3} textAnchor="end" fontSize="9" fill="#999">{v.toFixed(0)}億</text>
          </g>
        );
      })}
      {/* 金幣疊柱體:YoY 正成長=金幣 / 衰退=銅幣 / 最新月=頂面金圈 */}
      <defs>
        <linearGradient id="rcGold" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%"  stopColor="#7a5c22"/>
          <stop offset="22%" stopColor="#d4b370"/>
          <stop offset="50%" stopColor="#efd79b"/>
          <stop offset="78%" stopColor="#c79d5e"/>
          <stop offset="100%" stopColor="#7a5c22"/>
        </linearGradient>
        <linearGradient id="rcCopper" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%"  stopColor="#6e3a28"/>
          <stop offset="22%" stopColor="#c98a66"/>
          <stop offset="50%" stopColor="#e6b496"/>
          <stop offset="78%" stopColor="#b06f4e"/>
          <stop offset="100%" stopColor="#6e3a28"/>
        </linearGradient>
      </defs>
      {colored.map((d, i) => {
        const x = PL + stepX * i + stepX/2 - bW/2;
        const y = yOf(d.rev);
        const isHover = hover === i;
        const isLatest = i === data.length - 1;
        const yoyPos = d.yoy == null ? true : d.yoy >= 0;
        const grad    = yoyPos ? 'url(#rcGold)'  : 'url(#rcCopper)';
        const edge    = yoyPos ? '#6e5320' : '#5e3322';
        const topFace = yoyPos ? '#f2e2af' : '#eec3a8';
        const rx = bW / 2;
        const ry = Math.min(4, bW * 0.14);
        const gap = ry * 1.55;                 // 每枚硬幣厚度
        const baseY = H - PB - ry;             // 底層硬幣中心
        const hPx = Math.max(gap, baseY - y);
        const nCoins = Math.max(1, Math.round(hPx / gap));
        const cx = x + rx;
        const topY = baseY - (nCoins - 1) * gap;
        const coins = [];
        for (let k = 0; k < nCoins; k++) {
          coins.push(
            <ellipse key={k} cx={cx} cy={baseY - k * gap} rx={rx} ry={ry}
                     fill={grad} stroke={edge} strokeWidth=".55"/>
          );
        }
        return (
          <g key={i} onMouseEnter={() => setHover(i)} style={{ cursor:'default' }}
             opacity={isHover || isLatest ? 1 : 0.82}>
            {coins}
            {/* 頂面硬幣:亮面 + 內圈鑄紋 */}
            <ellipse cx={cx} cy={topY} rx={rx} ry={ry}
                     fill={topFace} stroke={edge} strokeWidth=".7"/>
            <ellipse cx={cx} cy={topY} rx={rx * 0.62} ry={ry * 0.62}
                     fill="none" stroke={edge} strokeWidth=".5" opacity=".55"/>
            {isLatest && (
              <ellipse cx={cx} cy={topY} rx={rx} ry={ry}
                       fill="none" stroke="#b8924d" strokeWidth="1.4" opacity=".9"/>
            )}
            {isHover && (
              <g>
                <rect x={x - 20} y={y - 38} width={bW + 40} height={32}
                      fill="#1a2438" rx="2"/>
                <text x={x + bW/2} y={y - 22} textAnchor="middle"
                      fontSize="10" fill="#fff" fontWeight="700">{d.ym}</text>
                <text x={x + bW/2} y={y - 10} textAnchor="middle"
                      fontSize="10" fill="#fff">
                  {d.rev}億{d.yoy != null ? ` · YoY ${d.yoy >= 0 ? '+' : ''}${(d.yoy*100).toFixed(1)}%` : ''}
                </text>
              </g>
            )}
          </g>
        );
      })}
      {/* X-axis labels — every 3 months */}
      {data.map((d, i) => {
        if (i % 3 !== 0 && i !== data.length - 1) return null;
        const x = PL + stepX * (i + 0.5);
        return (
          <text key={i} x={x} y={H-6} textAnchor="middle" fontSize="9" fill="#999">
            {d.ym}
          </text>
        );
      })}
    </svg>
  );
}

// ─── 季損益圖（小型柱狀） ─────────────────────────────

function QuarterlyChart({ quarters, field, label, color }) {
  if (!quarters.length) return null;
  const vals = quarters.map(q => q[field] || 0);
  const max = Math.max(...vals.map(Math.abs));
  if (max === 0) return null;
  const W = 420, H = 100, PL = 8, PR = 8, PT = 14, PB = 20;
  const stepX = (W - PL - PR) / vals.length;
  const bW = stepX * 0.6;
  const mid = PT + (H - PT - PB) / 2;
  const yOf = v => mid - (v / max) * ((H - PT - PB) / 2);

  // 建立「去年同季」對照表：key = "YYYYQN" → value
  const isEps = field === 'EPS';
  const qKeyOf = (q) => {
    const d = q.date || '';
    const yr = parseInt(d.slice(0,4));
    const qn = Math.ceil(parseInt(d.slice(5,7) || '1') / 3);
    return `${yr}Q${qn}`;
  };
  const qValMap = {};
  quarters.forEach(q => { qValMap[qKeyOf(q)] = q[field] ?? null; });

  return (
    <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none"
         style={{ width:'100%', height:'auto', display:'block' }}>
      <text x={PL} y={10} fontSize="9" fill="#999" fontStyle="italic">{label}</text>
      <line x1={PL} x2={W-PR} y1={mid} y2={mid} stroke="#e8e3d8" strokeWidth=".7"/>
      {/* 鈔票疊柱體:獲利往上疊、虧損往下疊銅紅鈔 */}
      {vals.map((v, i) => {
        const x = PL + stepX * i + stepX/2 - bW/2;
        const pos = v >= 0;
        const tint = pos ? color : '#c87050';
        const hPx = Math.max(2.6, Math.abs(mid - yOf(v)));
        const noteH = 2.4, gap = 3.1;
        const n = Math.max(1, Math.round(hPx / gap));
        const notes = [];
        for (let k = 0; k < n; k++) {
          const ny = pos ? mid - (k + 1) * gap : mid + k * gap + 0.7;
          // 交錯微偏移做出手疊鈔票感,最上層擺正
          const off = k === n - 1 ? 0 : (k % 2 ? 0.9 : -0.9);
          notes.push(
            <g key={k}>
              <rect x={x + off} y={ny} width={bW} height={noteH} rx=".6"
                    fill="var(--paper-bright)" stroke={tint} strokeWidth=".55"/>
              <rect x={x + off} y={ny} width={bW} height={noteH} rx=".6"
                    fill={tint} opacity=".22"/>
            </g>
          );
        }
        const ty = pos ? mid - n * gap : mid + (n - 1) * gap + 0.7;
        return (
          <g key={i} opacity=".95">
            {notes}
            {/* 最上層鈔票:內框 + 中央圓章 */}
            {bW > 14 && (
              <g>
                <rect x={x + 1.6} y={ty + 0.5} width={bW - 3.2} height={noteH - 1} rx=".4"
                      fill="none" stroke={tint} strokeWidth=".35" opacity=".8"/>
                <circle cx={x + bW/2} cy={ty + noteH/2} r=".7" fill={tint} opacity=".75"/>
              </g>
            )}
          </g>
        );
      })}
      {/* EPS 去年同季對比虛線 */}
      {isEps && quarters.map((q, i) => {
        const d = q.date || '';
        const yr = parseInt(d.slice(0,4));
        const qn = Math.ceil(parseInt(d.slice(5,7) || '1') / 3);
        const prevKey = `${yr - 1}Q${qn}`;
        const prevVal = qValMap[prevKey];
        if (prevVal == null) return null;
        const x0 = PL + stepX * i + stepX/2 - bW/2;
        const x1 = x0 + bW;
        const py = yOf(prevVal);
        // 限制在圖表繪製區域內
        if (py < PT || py > PT + (H - PT - PB)) return null;
        return (
          <g key={`prev-${i}`}>
            <line x1={x0} x2={x1} y1={py} y2={py}
                  stroke="var(--gold)" strokeWidth="1.5" strokeDasharray="3 2" opacity=".85"/>
            <text x={x1 + 2} y={py + 3} fontSize="9" fill="var(--gold)" opacity=".8">
              {prevVal.toFixed(2)}
            </text>
          </g>
        );
      })}
      {quarters.map((q, i) => {
        const x = PL + stepX * (i + 0.5);
        const d = q.date || '';
        const label = d.slice(2,4) + 'Q' + (Math.ceil(parseInt(d.slice(5,7))/3));
        return (
          <text key={i} x={x} y={H-4} textAnchor="middle" fontSize="8" fill="#999">{label}</text>
        );
      })}
    </svg>
  );
}

// ─── EPS 專屬圖：年增配色柱 + 去年同季虛線 + YoY% 折線（ECharts）───

function EPSChart({ quarters }) {
  const ref = React.useRef(null);
  const rows = quarters.map(q => {
    const d = q.date || '';
    const yr = parseInt(d.slice(0, 4));
    const qn = Math.ceil(parseInt(d.slice(5, 7) || '1') / 3);
    return { label: String(yr).slice(2) + 'Q' + qn, key: yr + 'Q' + qn, prevKey: (yr - 1) + 'Q' + qn, eps: q.EPS ?? null };
  });
  const map = {};
  rows.forEach(r => { map[r.key] = r.eps; });
  const data = rows.map(r => {
    const prev = map[r.prevKey] ?? null;
    const yoy = (prev != null && Math.abs(prev) > 0.0001 && r.eps != null)
      ? +((r.eps - prev) / Math.abs(prev) * 100).toFixed(1) : null;
    return { label: r.label, eps: r.eps, prev, yoy };
  });
  let streak = 0;
  for (let i = data.length - 1; i >= 0; i--) { if (data[i].yoy != null && data[i].yoy > 0) streak++; else break; }
  const ttm = quarters.slice(-4).reduce((s, q) => s + (q.EPS || 0), 0);
  const lastYoy = data.length ? data[data.length - 1].yoy : null;

  React.useEffect(() => {
    if (!ref.current || !window.echarts || !data.length) return;
    const chart = echarts.init(ref.current, null, { renderer: 'svg' });
    chart.setOption({
      backgroundColor: 'transparent',
      tooltip: {
        trigger: 'axis',
        backgroundColor: '#1a2438', borderColor: '#b8924d', borderWidth: 1,
        textStyle: { color: '#f6f1e4', fontSize: 11.5, fontFamily: 'Times New Roman, serif' },
        formatter: function(params) {
          const i = params[0].dataIndex, d = data[i];
          let s = '<b>' + d.label + '</b><br/>';
          s += 'EPS: ' + (d.eps != null ? d.eps.toFixed(2) + ' 元' : '—') + '<br/>';
          if (d.prev != null) s += '去年同季: ' + d.prev.toFixed(2) + ' 元<br/>';
          if (d.yoy != null) s += '<span style="color:' + (d.yoy >= 0 ? '#4aaa6e' : '#dd6060') + '">YoY ' + (d.yoy >= 0 ? '+' : '') + d.yoy + '%</span>';
          return s;
        },
      },
      grid: { left: 38, right: 40, top: 10, bottom: 22 },
      xAxis: {
        type: 'category', data: data.map(d => d.label),
        axisLine: { lineStyle: { color: '#d8cdb0' } }, axisTick: { show: false },
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' },
      },
      yAxis: [
        { type: 'value',
          axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' },
          splitLine: { lineStyle: { color: '#d8cdb0', type: 'dashed' } }, axisLine: { show: false } },
        { type: 'value',
          axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#b8924d', formatter: '{value}%' },
          splitLine: { show: false }, axisLine: { show: false } },
      ],
      series: [
        { name: 'EPS', type: 'bar', barMaxWidth: 18,
          data: data.map(d => ({ value: d.eps,
            itemStyle: { color: d.yoy == null ? '#2c5e8e' : d.yoy >= 0 ? '#2a6b4c' : '#9c2b2b', borderRadius: [2, 2, 0, 0] } })) },
        { name: '去年同季', type: 'line', symbol: 'none',
          data: data.map(d => d.prev),
          lineStyle: { width: 1.5, color: '#b8924d', type: 'dashed' }, connectNulls: false },
        { name: 'YoY%', type: 'line', yAxisIndex: 1, symbol: 'circle', symbolSize: 4,
          data: data.map(d => d.yoy),
          lineStyle: { width: 1.5, color: '#8a6a2a' }, itemStyle: { color: '#8a6a2a' }, connectNulls: false },
      ],
    });
    const ro = new ResizeObserver(() => chart.resize());
    ro.observe(ref.current);
    return () => { chart.dispose(); ro.disconnect(); };
  }, [data.map(d => d.eps).join(',')]);

  return (
    <div>
      <div style={{ fontSize: 'var(--fs-xs)', fontStyle: 'italic', color: '#999', marginBottom: 2 }}>
        EPS(元) — 柱色:年增綠/年減紅 · 金虛線:去年同季 · 細線:YoY%(右軸)
      </div>
      <div ref={ref} style={{ width: '100%', height: 150 }} />
      <div style={{ display: 'flex', gap: 12, marginTop: 4, fontSize: 'var(--fs-xs)', fontFamily: 'Times New Roman,serif', flexWrap: 'wrap' }}>
        <span><span style={{ fontStyle: 'italic', color: 'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>TTM </span>
          <b>{ttm.toFixed(2)}</b> 元</span>
        {lastYoy != null && (
          <span><span style={{ fontStyle: 'italic', color: 'var(--ink-mute)', fontSize: 'var(--fs-xs)' }}>最新季 YoY </span>
            <b style={{ color: lastYoy >= 0 ? 'var(--bull)' : 'var(--bear)' }}>{lastYoy >= 0 ? '+' : ''}{lastYoy}%</b></span>
        )}
        {streak >= 2 && (
          <span className="kai" style={{ color: 'var(--bull)', fontSize: 'var(--fs-xs)' }}>◎ 連 {streak} 季年增</span>
        )}
      </div>
    </div>
  );
}

// ─── 同業估值地圖：P/E × P/B 散佈（ECharts）─────────────────

function PeerValueMap({ rows }) {
  const ref = React.useRef(null);
  const pts = rows.filter(r => r.pe != null && r.pb != null && r.pe > 0 && r.pe < 100);
  const median = arr => {
    const s = [...arr].sort((a, b) => a - b), n = s.length;
    return n ? (n % 2 ? s[(n - 1) / 2] : (s[n / 2 - 1] + s[n / 2]) / 2) : null;
  };
  const medPe = median(pts.map(p => p.pe)), medPb = median(pts.map(p => p.pb));

  React.useEffect(() => {
    if (!ref.current || !window.echarts || pts.length < 3) return;
    const chart = echarts.init(ref.current, null, { renderer: 'svg' });
    chart.setOption({
      backgroundColor: 'transparent',
      tooltip: {
        backgroundColor: '#1a2438', borderColor: '#b8924d', borderWidth: 1,
        textStyle: { color: '#f6f1e4', fontSize: 11.5, fontFamily: 'Times New Roman, serif' },
        formatter: p => '<b>' + p.data.name + '</b><br/>P/E ' + p.data.value[0].toFixed(1) + 'x · P/B ' + p.data.value[1].toFixed(2) + 'x'
          + (p.data.yld ? '<br/>殖利率 ' + p.data.yld.toFixed(2) + '%' : ''),
      },
      grid: { left: 44, right: 16, top: 14, bottom: 34 },
      xAxis: {
        type: 'value', name: 'P/E', nameTextStyle: { fontSize: 11.5, color: '#6a6f80', fontFamily: 'Times New Roman,serif' },
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' },
        splitLine: { lineStyle: { color: '#e8e3d8', type: 'dashed' } }, scale: true,
      },
      yAxis: {
        type: 'value', name: 'P/B', nameTextStyle: { fontSize: 11.5, color: '#6a6f80', fontFamily: 'Times New Roman,serif' },
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' },
        splitLine: { lineStyle: { color: '#e8e3d8', type: 'dashed' } }, scale: true,
      },
      series: [{
        type: 'scatter',
        data: pts.map(p => ({
          name: p.name, yld: p.yld,
          value: [p.pe, p.pb],
          symbolSize: p.isSelf ? 16 : 10,
          itemStyle: p.isSelf
            ? { color: '#b8924d', borderColor: '#8a6a2a', borderWidth: 2 }
            : { color: 'rgba(26,36,56,0.55)' },
          label: { show: true, position: 'top', fontSize: 11.5, fontFamily: 'BiauKai,標楷體,serif',
                   color: p.isSelf ? '#8a6a2a' : '#6a6f80', formatter: p.name },
        })),
        markLine: {
          silent: true, symbol: 'none',
          lineStyle: { color: '#b8924d', type: 'dashed', opacity: 0.6 },
          label: { fontSize: 11.5, color: '#8a6a2a', fontFamily: 'Times New Roman,serif', formatter: '{b}' },
          data: [
            medPe != null ? { xAxis: medPe, name: '中位P/E' } : null,
            medPb != null ? { yAxis: medPb, name: '中位P/B' } : null,
          ].filter(Boolean),
        },
      }],
    });
    const ro = new ResizeObserver(() => chart.resize());
    ro.observe(ref.current);
    return () => { chart.dispose(); ro.disconnect(); };
  }, [pts.map(p => p.pe + ',' + p.pb).join(';')]);

  if (pts.length < 3) return null;
  return (
    <div style={{ marginTop: 18, paddingTop: 14, borderTop: '1px dashed var(--paper-line)' }}>
      <div style={{ fontFamily: 'Times New Roman,serif', fontStyle: 'italic', fontSize: 'var(--fs-xs)',
                    color: 'var(--ink-mute)', marginBottom: 4, letterSpacing: '.05em' }}>
        Valuation Map · 估值地圖
      </div>
      <div className="kai" style={{ fontSize: 'var(--fs-xs)', color: 'var(--ink-soft)', marginBottom: 8 }}>
        左下角（雙低）= 估值便宜區；右上角 = 估值昂貴區。金點為本檔，虛線為同業中位數。
      </div>
      <div ref={ref} style={{ width: '100%', height: 240 }} />
    </div>
  );
}

// ─── 同業排名表 ───────────────────────────────────────

function PeerDot({ color }) {
  return <span style={{ display:'inline-block', width:7, height:7, borderRadius:'50%',
    background: color, marginRight:4, flexShrink:0 }}/>;
}

function MiniSparkline({ prices }) {
  if (!prices || prices.length < 2) {
    return <svg width="30" height="16" style={{display:'block'}}><line x1="0" y1="8" x2="30" y2="8" stroke="var(--paper-line)" strokeWidth="1"/></svg>;
  }
  const W = 30, H = 16, pad = 2;
  const mn = Math.min(...prices), mx = Math.max(...prices);
  const range = mx - mn || 1;
  const n = prices.length;
  const xOf = i => pad + (i / (n - 1)) * (W - pad * 2);
  const yOf = v => H - pad - ((v - mn) / range) * (H - pad * 2);
  const pts = prices.map((v, i) => `${xOf(i).toFixed(1)},${yOf(v).toFixed(1)}`).join(' ');
  const rising = prices[prices.length - 1] > prices[0];
  const flat   = prices[prices.length - 1] === prices[0];
  const color  = flat ? 'var(--ink-mute)' : rising ? 'var(--bull)' : 'var(--bear)';
  return (
    <svg width={W} height={H} style={{display:'block',flexShrink:0}}>
      <polyline points={pts} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"/>
      <circle cx={xOf(n-1).toFixed(1)} cy={yOf(prices[n-1]).toFixed(1)} r="2" fill={color}/>
    </svg>
  );
}

function PeerTable({ code, profile, peerPrices, perData, peerBwibbu, peerMini = {} }) {
  const peers = profile?.peers || [];
  if (!peers.length) return (
    <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
      <div style={{fontSize:24, marginBottom:8}}>📊</div>
      <div>暫無同業比較資料</div>
      <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
    </div>
  );

  // 自身 P/E 來自 bwibbu_batch 或 perData
  const selfBw = peerBwibbu[code];
  const latestPer = (perData?.per || []).slice(-1)[0];
  const selfPe = selfBw?.pe ?? latestPer?.PER ?? null;
  const selfPb = selfBw?.pb ?? latestPer?.PBR ?? null;
  const selfYield = selfBw?.yield ?? null;

  const allCodes = [code, ...peers];
  const rows = allCodes.map((c, i) => {
    const isSelf = c === code;
    const bw = peerBwibbu[c] || {};
    const price = peerPrices[c] || 0;
    const pe  = isSelf ? selfPe  : (bw.pe  ?? null);
    const pb  = isSelf ? selfPb  : (bw.pb  ?? null);
    const yld = isSelf ? selfYield : (bw.yield ?? null);
    const name = isSelf ? (profile.name || c) : (bw.name || c);
    return { code: c, name, price, pe, pb, yld, isSelf };
  });

  // 排序：P/E 由低到高（低估值優先）
  const validPe = rows.filter(r => r.pe != null);
  const peRanked = [...validPe].sort((a, b) => a.pe - b.pe);

  const maxPrice = Math.max(...rows.map(r => r.price || 0), 1);
  const maxPe    = Math.max(...rows.filter(r => r.pe).map(r => r.pe), 1);
  const maxPb    = Math.max(...rows.filter(r => r.pb).map(r => r.pb), 1);

  // 同業中位數 + 本檔溢/折價
  const medianOf = arr => {
    const s = [...arr].sort((a, b) => a - b), n = s.length;
    return n ? +(n % 2 ? s[(n - 1) / 2] : (s[n / 2 - 1] + s[n / 2]) / 2).toFixed(2) : null;
  };
  const medPe = medianOf(rows.filter(r => r.pe  != null && r.pe > 0).map(r => r.pe));
  const medPb = medianOf(rows.filter(r => r.pb  != null && r.pb > 0).map(r => r.pb));
  const medYl = medianOf(rows.filter(r => r.yld != null && r.yld > 0).map(r => r.yld));
  const premChip = (selfV, medV, lowIsCheap) => {
    if (selfV == null || medV == null || medV === 0) return null;
    const diff = (selfV - medV) / medV * 100;
    if (Math.abs(diff) < 3) return { txt: '≈中位數', color: 'var(--ink-mute)' };
    const cheap = lowIsCheap ? diff < 0 : diff > 0;
    return { txt: (diff > 0 ? '+' : '') + diff.toFixed(0) + '% vs 同業', color: cheap ? 'var(--bull)' : '#c0392b' };
  };
  const peChip = premChip(selfPe, medPe, true);
  const pbChip = premChip(selfPb, medPb, true);
  const ylChip = premChip(selfYield, medYl, false);

  return (
    <div className="fund-table-wrap">
      {/* 同業中位數摘要 + 本檔相對位置 */}
      {(medPe != null || medPb != null) && (
        <div style={{ display:'flex', flexWrap:'wrap', gap:14, marginBottom:14,
                      padding:'10px 14px', border:'1px solid var(--paper-line)', background:'var(--paper-bright)' }}>
          {[
            { k:'P/E 中位數', v: medPe != null ? medPe + 'x' : '—', chip: peChip },
            { k:'P/B 中位數', v: medPb != null ? medPb + 'x' : '—', chip: pbChip },
            { k:'殖利率中位數', v: medYl != null ? medYl + '%' : '—', chip: ylChip },
          ].map((it, i) => (
            <div key={i} style={{ display:'flex', alignItems:'baseline', gap:6 }}>
              <span className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)' }}>{it.k}</span>
              <span className="num" style={{ fontSize:15, fontWeight:700 }}>{it.v}</span>
              {it.chip && (
                <span style={{ fontSize: 'var(--fs-xs)', fontFamily:'Times New Roman,serif', fontStyle:'italic', color: it.chip.color }}>
                  本檔 {it.chip.txt}
                </span>
              )}
            </div>
          ))}
        </div>
      )}
      <table className="fund-table" style={{ tableLayout:'fixed' }}>
        <colgroup>
          <col style={{ width:40 }}/>
          <col style={{ width:'30%' }}/>
          <col style={{ width:38 }}/>
          <col/>
          <col/>
          <col/>
          <col/>
        </colgroup>
        <thead>
          <tr>
            <th style={{ textAlign:'center' }}>排</th>
            <th style={{ textAlign:'left' }}>公司</th>
            <th style={{ textAlign:'center' }}>走勢</th>
            <th>股價</th>
            <th>P/E <small style={{fontWeight:400}}>本益比</small></th>
            <th>P/B <small style={{fontWeight:400}}>淨值比</small></th>
            <th>殖利率</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((r, i) => {
            const peRank = peRanked.findIndex(x => x.code === r.code);
            const isLowest = peRank === 0;
            return (
              <tr key={r.code}
                  className={r.isSelf ? 'peer-self' : ''}
                  style={{ cursor: r.isSelf ? 'default' : 'pointer' }}
                  onClick={() => { if (!r.isSelf) location.href = `個股技術分析.html?code=${r.code}`; }}>

                {/* 排名 */}
                <td style={{ textAlign:'center', fontFamily:'Times New Roman,serif',
                  fontStyle:'italic', fontSize:15,
                  color: r.isSelf ? 'var(--gold-deep)' : 'var(--ink-mute)' }}>
                  {r.isSelf ? '★' : `#${i + 1}`}
                </td>

                {/* 公司 */}
                <td>
                  <div style={{ fontFamily:'Times New Roman,serif', fontSize: 'var(--fs-xs)',
                    color:'var(--ink-mute)', letterSpacing:'.05em' }}>{r.code}</div>
                  <div style={{ fontFamily:'BiauKai,標楷體,serif', fontSize:14,
                    color:'var(--ink)', letterSpacing:'.06em', marginTop:2 }}>
                    {r.name}
                    {r.isSelf && <span style={{ marginLeft:5, fontSize: 'var(--fs-xs)',
                      color:'var(--gold-deep)', fontFamily:'Times New Roman,serif',
                      fontStyle:'italic' }}>this co.</span>}
                  </div>
                </td>

                {/* 走勢迷你圖 + 5日漲跌% */}
                <td style={{ textAlign:'center', padding:'4px 2px' }}>
                  <MiniSparkline prices={peerMini[r.code] || []}/>
                  {(() => {
                    const ps = peerMini[r.code] || [];
                    if (ps.length < 2 || !ps[0]) return null;
                    const pct = (ps[ps.length-1] - ps[0]) / ps[0] * 100;
                    return (
                      <div style={{ fontSize: 'var(--fs-xs)', fontFamily:'Times New Roman,serif', marginTop:1,
                                    color: pct > 0 ? 'var(--bull)' : pct < 0 ? 'var(--bear)' : 'var(--ink-mute)' }}>
                        {pct > 0 ? '+' : ''}{pct.toFixed(1)}%
                      </div>
                    );
                  })()}
                </td>

                {/* 股價 + bar */}
                <td>
                  {r.price > 0 ? (
                    <div>
                      <div style={{ fontFamily:'Times New Roman,serif', fontSize:14,
                        fontWeight:600, color:'var(--ink)', letterSpacing:'.03em' }}>
                        {r.price.toLocaleString('en-US', { minimumFractionDigits:2 })}
                      </div>
                      <div style={{ marginTop:4, height:4, borderRadius:2,
                        background:'var(--paper-line)', overflow:'hidden' }}>
                        <div style={{ height:'100%', borderRadius:2,
                          width: `${(r.price / maxPrice) * 100}%`,
                          background: r.isSelf ? 'var(--gold)' : 'var(--ink-soft)' }}/>
                      </div>
                    </div>
                  ) : <span style={{color:'var(--ink-mute)'}}>—</span>}
                </td>

                {/* P/E */}
                <td>
                  {r.pe != null ? (
                    <div>
                      <div style={{ display:'flex', alignItems:'center' }}>
                        {isLowest && <PeerDot color="var(--bull)"/>}
                        <span style={{ fontFamily:'Times New Roman,serif', fontSize:15,
                          fontWeight:600, color: isLowest ? 'var(--bull)' : 'var(--ink)' }}>
                          {r.pe.toFixed(1)}
                        </span>
                        <span style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginLeft:2 }}>x</span>
                      </div>
                      <div style={{ marginTop:3, height:3, borderRadius:2,
                        background:'var(--paper-line)', overflow:'hidden' }}>
                        <div style={{ height:'100%',
                          width:`${Math.min(100, (r.pe / maxPe) * 100)}%`,
                          background: r.pe < (maxPe * 0.6) ? 'var(--bull)' : 'var(--gold)',
                          borderRadius:2 }}/>
                      </div>
                    </div>
                  ) : <span style={{color:'var(--ink-mute)',fontSize: 'var(--fs-xs)'}}>—</span>}
                </td>

                {/* P/B */}
                <td>
                  {r.pb != null ? (
                    <div style={{ fontFamily:'Times New Roman,serif', fontSize:14,
                      color:'var(--ink)' }}>
                      {r.pb.toFixed(2)}
                      <span style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginLeft:2}}>x</span>
                    </div>
                  ) : <span style={{color:'var(--ink-mute)',fontSize: 'var(--fs-xs)'}}>—</span>}
                </td>

                {/* 殖利率 */}
                <td>
                  {r.yld != null && r.yld > 0 ? (
                    <span style={{ fontFamily:'Times New Roman,serif', fontSize:14,
                      color: r.yld >= 4 ? 'var(--bull)' : 'var(--ink)' }}>
                      {r.yld.toFixed(2)}
                      <span style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginLeft:1}}>%</span>
                    </span>
                  ) : <span style={{color:'var(--ink-mute)',fontSize: 'var(--fs-xs)'}}>—</span>}
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
      <div style={{ display:'flex', gap:16, marginTop:10, fontSize: 'var(--fs-xs)',
        color:'var(--ink-mute)', fontStyle:'italic', fontFamily:'Times New Roman,serif',
        alignItems:'center' }}>
        <span><PeerDot color="var(--bull)"/>最低本益比</span>
        <span>數據來源：TWSE BWIBBU · TPEx · FinMind</span>
      </div>

      {/* 估值地圖 P/E × P/B 散佈 */}
      <PeerValueMap rows={rows}/>
    </div>
  );
}

// ─── 產業鏈位置 ────────────────────────────────────────

function SupplyChainMap({ code, profile }) {
  if (!profile) return null;
  const suppliers = profile.twSuppliers || [];
  const customers = profile.twCustomers || [];
  const chainLabels = { upstream:'上游 · Upstream', midstream:'中游 · Midstream', downstream:'下游 · Downstream' };

  return (
    <div>
      <div className="fund-chain-wrap">
        {/* 上游 */}
        <div className="fund-chain-col">
          <div className="fund-chain-label">上游 · Suppliers</div>
          {suppliers.length > 0 ? suppliers.map((s, i) => {
            const [sc, ...rest] = s.split(' ');
            return (
              <div key={i} className="fund-chain-node" style={{cursor:'pointer'}}
                   onClick={() => { location.href = `企業基本面.html?code=${sc}`; }}>
                <div className="cn-code">{sc}</div>
                <div className="cn-name">{rest.join(' ')}</div>
              </div>
            );
          }) : (
            <div className="fund-chain-node" style={{opacity:.4}}>
              <div className="cn-name">—</div>
            </div>
          )}
        </div>
        <div className="fund-chain-arrow">→</div>
        {/* 本公司 */}
        <div className="fund-chain-col">
          <div className="fund-chain-label">本公司 · This Company</div>
          <div className="fund-chain-node self">
            <div className="cn-code">{code}</div>
            <div className="cn-name">{profile.name}</div>
            <div style={{fontSize: 'var(--fs-xs)',color:'var(--gold-deep)',marginTop:4,fontStyle:'italic'}}>
              {chainLabels[profile.chainPos] || profile.chainPos}
            </div>
          </div>
        </div>
        <div className="fund-chain-arrow">→</div>
        {/* 下游 */}
        <div className="fund-chain-col">
          <div className="fund-chain-label">下游 · Customers</div>
          {customers.length > 0 ? customers.map((c, i) => {
            const [cc, ...rest] = c.split(' ');
            return (
              <div key={i} className="fund-chain-node" style={{cursor:'pointer'}}
                   onClick={() => { location.href = `企業基本面.html?code=${cc}`; }}>
                <div className="cn-code">{cc}</div>
                <div className="cn-name">{rest.join(' ')}</div>
              </div>
            );
          }) : (
            <div className="fund-chain-node" style={{opacity:.4}}>
              <div className="cn-name">—</div>
            </div>
          )}
        </div>
      </div>

      {/* US 關係 */}
      {(profile.usRelations || []).length > 0 && (
        <div style={{marginTop:28}}>
          <div style={{
            display:'flex', alignItems:'baseline', gap:10,
            borderBottom:'1px solid var(--paper-line)', paddingBottom:8, marginBottom:16,
          }}>
            <span style={{fontFamily:'Times New Roman,serif',fontStyle:'italic',fontSize:14,color:'var(--ink)'}}>
              US Connections
            </span>
            <span className="kai" style={{fontSize:13,color:'var(--ink-mute)',letterSpacing:'.1em'}}>
              台美企業關係
            </span>
          </div>
          <div className="fund-us-grid">
            {profile.usRelations.map((rel, i) => (
              <div key={i} className={`fund-us-card level-${rel.level}`}>
                <div className="fund-us-card-head">
                  <span className="fund-us-ticker">{rel.code}</span>
                  <span className="fund-us-name">{rel.name}</span>
                  <span className={`fund-us-role ${rel.role}`}>
                    {{customer:'客戶',supplier:'供應商',partner:'夥伴',competitor:'競爭'}[rel.role] || rel.role}
                  </span>
                </div>
                <div className="fund-us-detail">{rel.detail}</div>
                <div className={`fund-us-level level-${rel.level}`}>
                  <span/>
                  {({critical:'關鍵依賴',high:'高度相關',medium:'中度相關',low:'低度相關'})[rel.level] || rel.level}
                </div>
              </div>
            ))}
          </div>
          {profile.summary && (
            <div className="fund-summary-box">
              <p>{profile.summary}</p>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─── 財務比率趨勢 ─────────────────────────────────────────

function RatioPanel({ fins, bsData }) {
  if (!fins.length) return (
    <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
      <div style={{fontSize:24, marginBottom:8}}>📊</div>
      <div>暫無財務比率資料</div>
      <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
    </div>
  );

  // Build a date-keyed map for bsData for ROE calculation
  const bsMap = {};
  bsData.forEach(q => { if (q.date) bsMap[q.date] = q; });

  const quarters = fins.map(q => {
    const rev  = q.Revenue        || 0;
    const gp   = q.GrossProfit    || 0;
    const op   = q.OperatingIncome|| 0;
    const ni   = q.NetIncome      || 0;
    const grossMargin   = rev > 0 ? +((gp / rev) * 100).toFixed(1) : null;
    const opMargin      = rev > 0 ? +((op / rev) * 100).toFixed(1) : null;
    const netMargin     = rev > 0 ? +((ni / rev) * 100).toFixed(1) : null;
    // ROE: annualized single-quarter (×4)
    const bsQ = bsMap[q.date];
    const equity = bsQ?.StockholdersEquity || 0;
    const roe = equity > 0 ? +((ni / equity) * 4 * 100).toFixed(1) : null;
    const d = q.date || '';
    const label = d.slice(0,4) + 'Q' + Math.ceil(parseInt(d.slice(5,7)) / 3);
    return { label, grossMargin, opMargin, netMargin, roe };
  });

  function colorClass(v) {
    if (v == null) return '';
    if (v >= 20)  return 'ratio-green';
    if (v < 0)    return 'ratio-red';
    return '';
  }

  const ROWS = [
    { key: 'grossMargin', label: '毛利率' },
    { key: 'opMargin',    label: '營益率' },
    { key: 'netMargin',   label: '淨利率' },
    { key: 'roe',         label: 'ROE(年化)' },
  ];

  // 三率三升偵測：最新季毛利率/營益率/淨利率均高於上一季
  const qN = quarters.length;
  const triRise = qN >= 2 && ['grossMargin','opMargin','netMargin'].every(k =>
    quarters[qN-1][k] != null && quarters[qN-2][k] != null && quarters[qN-1][k] > quarters[qN-2][k]);
  const triFall = qN >= 2 && ['grossMargin','opMargin','netMargin'].every(k =>
    quarters[qN-1][k] != null && quarters[qN-2][k] != null && quarters[qN-1][k] < quarters[qN-2][k]);

  // 三率走勢 ECharts 折線圖
  const marginRef = React.useRef(null);
  React.useEffect(() => {
    if (!marginRef.current || !window.echarts || quarters.length < 2) return;
    const chart = echarts.init(marginRef.current, null, { renderer: 'svg' });
    const series = [
      { name: '毛利率', key: 'grossMargin', color: '#b8924d' },
      { name: '營益率', key: 'opMargin',    color: '#2c5e8e' },
      { name: '淨利率', key: 'netMargin',   color: '#2a6b4c' },
    ];
    chart.setOption({
      backgroundColor: 'transparent',
      tooltip: {
        trigger: 'axis',
        backgroundColor: '#1a2438', borderColor: '#b8924d', borderWidth: 1,
        textStyle: { color: '#f6f1e4', fontSize: 11.5, fontFamily: 'Times New Roman, serif' },
        valueFormatter: v => v != null ? v + '%' : '—',
      },
      legend: {
        data: series.map(s => s.name), bottom: 0,
        textStyle: { fontSize: 11.5, fontFamily: 'BiauKai,標楷體,serif', color: '#6a6f80' },
        itemWidth: 14, itemHeight: 8,
      },
      grid: { left: 44, right: 12, top: 14, bottom: 42 },
      xAxis: {
        type: 'category', data: quarters.map(q => q.label.slice(2)),
        axisLine: { lineStyle: { color: '#d8cdb0' } }, axisTick: { show: false },
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' },
      },
      yAxis: {
        type: 'value',
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80', formatter: '{value}%' },
        splitLine: { lineStyle: { color: '#d8cdb0', type: 'dashed' } }, axisLine: { show: false },
      },
      series: series.map(s => ({
        name: s.name, type: 'line', symbol: 'circle', symbolSize: 5,
        data: quarters.map(q => q[s.key]),
        lineStyle: { width: 2, color: s.color }, itemStyle: { color: s.color },
      })),
    });
    const ro = new ResizeObserver(() => chart.resize());
    ro.observe(marginRef.current);
    return () => { chart.dispose(); ro.disconnect(); };
  }, [quarters.map(q => q.grossMargin).join(',')]);

  // 杜邦分析：ROE = 淨利率 × 總資產週轉率 × 權益乘數（近4季 vs 前4季）
  const dupont = (() => {
    if (fins.length < 8 || bsData.length < 5) return null;
    const sum = (arr, k) => arr.reduce((s, q) => s + (q[k] || 0), 0);
    const mk = (fq, bsQ) => {
      const rev = sum(fq, 'Revenue'), ni = sum(fq, 'NetIncome');
      const assets = bsQ?.TotalAssets || 0, equity = bsQ?.StockholdersEquity || 0;
      if (rev <= 0 || assets <= 0 || equity <= 0) return null;
      return {
        netMargin: ni / rev * 100,
        turnover:  rev / assets,
        leverage:  assets / equity,
        roe:       ni / equity * 100,
      };
    };
    const now  = mk(fins.slice(-4), bsData[bsData.length-1]);
    const prev = mk(fins.slice(-8, -4), bsData[bsData.length-5]);
    return now ? { now, prev } : null;
  })();

  return (
    <div>
      {/* 三率三升 badge */}
      {(triRise || triFall) && (
        <div style={{
          display:'inline-flex', alignItems:'center', gap:6, padding:'4px 12px', marginBottom:12,
          border:'1px solid ' + (triRise ? 'var(--bull)' : 'var(--bear)'),
          background: triRise ? 'rgba(30,173,137,0.07)' : 'rgba(200,112,80,0.07)', fontSize: 'var(--fs-xs)' }}>
          <span className="kai" style={{ color: triRise ? 'var(--bull)' : 'var(--bear)', fontWeight:700 }}>
            {triRise ? '◎ 三率三升 — 獲利結構全面改善' : '▽ 三率齊降 — 獲利結構轉弱，留意'}
          </span>
        </div>
      )}

      {/* 三率走勢圖 */}
      {quarters.length >= 2 && (
        <div className="fund-chart-wrap" style={{ padding:'8px 0 14px' }}>
          <div ref={marginRef} style={{ width:'100%', height:'210px' }} />
        </div>
      )}

      <div className="fund-table-wrap">
        <table className="fund-table ratio-table">
          <thead>
            <tr>
              <th style={{textAlign:'left'}}>比率</th>
              {quarters.map((q, i) => <th key={i}>{q.label}</th>)}
            </tr>
          </thead>
          <tbody>
            {ROWS.map(row => (
              <tr key={row.key}>
                <td>{row.label}</td>
                {quarters.map((q, i) => {
                  const v = q[row.key];
                  return (
                    <td key={i} className={colorClass(v)}>
                      {v != null ? v + '%' : '—'}
                    </td>
                  );
                })}
              </tr>
            ))}
          </tbody>
        </table>
        <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:8,fontStyle:'italic'}}>
          * 毛利率 / 營益率 / 淨利率均以單季計算。ROE 為單季淨利 ÷ 期末股東權益 × 4（年化）。
        </div>
      </div>

      {/* 杜邦分析 DuPont */}
      {dupont && (
        <div style={{ marginTop:20, paddingTop:16, borderTop:'1px dashed var(--paper-line)' }}>
          <div style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize: 'var(--fs-xs)',
                        color:'var(--ink-mute)', marginBottom:4, letterSpacing:'.05em' }}>
            DuPont Analysis · 杜邦拆解
          </div>
          <div className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-soft)', marginBottom:12 }}>
            ROE = 淨利率 × 資產週轉率 × 權益乘數 —— 看出獲利靠「賺得多、轉得快、還是借得兇」
          </div>
          <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fit,minmax(130px,1fr))', gap:12 }}>
            {[
              { label:'ROE（近4季）', v: dupont.now.roe,       p: dupont.prev?.roe,       unit:'%',  main:true,
                good: v => v >= 15 },
              { label:'淨利率',       v: dupont.now.netMargin, p: dupont.prev?.netMargin, unit:'%',
                good: v => v >= 10 },
              { label:'資產週轉率',   v: dupont.now.turnover,  p: dupont.prev?.turnover,  unit:'次',
                good: v => v >= 0.6 },
              { label:'權益乘數',     v: dupont.now.leverage,  p: dupont.prev?.leverage,  unit:'倍',
                good: v => v <= 2.5, invert:true },
            ].map((it, i) => {
              const delta = it.p != null ? it.v - it.p : null;
              const arrow = delta == null ? '' : Math.abs(delta) < 0.005 ? '' : delta > 0 ? ' ▲' : ' ▼';
              const arrowGood = delta != null && (it.invert ? delta < 0 : delta > 0);
              return (
                <div key={i} style={{
                  border:'1px solid ' + (it.main ? 'var(--gold)' : 'var(--paper-line)'),
                  background: it.main ? 'rgba(184,146,77,0.05)' : 'var(--paper-bright)',
                  padding:'10px 12px' }}>
                  <div className="kai" style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginBottom:4 }}>{it.label}</div>
                  <div style={{ display:'flex', alignItems:'baseline', gap:4 }}>
                    <span className="num" style={{ fontSize:20, fontWeight:700,
                      color: it.good(it.v) ? 'var(--bull)' : 'var(--ink)' }}>
                      {it.v.toFixed(it.unit === '%' ? 1 : 2)}
                    </span>
                    <span style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)' }}>{it.unit}</span>
                    {arrow && (
                      <span style={{ fontSize: 'var(--fs-xs)', color: arrowGood ? 'var(--bull)' : 'var(--bear)' }}>{arrow}</span>
                    )}
                  </div>
                  {it.p != null && (
                    <div style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-faint)', fontStyle:'italic', marginTop:2 }}>
                      前4季 {it.p.toFixed(it.unit === '%' ? 1 : 2)}{it.unit}
                    </div>
                  )}
                </div>
              );
            })}
          </div>
          <div style={{ fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginTop:10, fontStyle:'italic' }}>
            * 淨利率升 → 產品賺錢；週轉率升 → 經營效率高；權益乘數升 → 槓桿加大（ROE 含金量下降）。
          </div>
        </div>
      )}
    </div>
  );
}

// ─── 股利記錄 ─────────────────────────────────────────────

function DividendSection({ dividends }) {
  if (!dividends.length) return (
    <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
      <div style={{fontSize:24, marginBottom:8}}>📊</div>
      <div>暫無股利記錄</div>
      <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
    </div>
  );

  // Summary: cumulative cash over last N years
  const yearsSet = [...new Set(dividends.map(d => d.year))].sort((a,b)=>b-a);
  const totalCash = dividends.reduce((s, d) => s + d.cash, 0);
  const nYears    = yearsSet.length;

  // Trend arrow: compare last two years total dividends
  const yr0 = yearsSet[0], yr1 = yearsSet[1];
  const cash0 = dividends.filter(d => d.year === yr0).reduce((s,d)=>s+d.cash,0);
  const cash1 = yr1 ? dividends.filter(d => d.year === yr1).reduce((s,d)=>s+d.cash,0) : null;
  const trend  = cash1 != null && cash0 > cash1 ? '↑' : cash1 != null && cash0 < cash1 ? '↓' : '→';
  const trendColor = trend === '↑' ? 'var(--bull)' : trend === '↓' ? 'var(--bear)' : 'var(--ink-mute)';

  return (
    <div>
      <div className="div-summary-bar">
        <span>近 {nYears} 年累計現金股利</span>
        <span style={{fontWeight:700,fontSize:18,color:'var(--ink)',marginLeft:12}}>{totalCash.toFixed(2)} 元</span>
        <span style={{color:trendColor,marginLeft:10,fontSize:16}}>{trend}</span>
        <span style={{marginLeft:'auto',fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>共 {dividends.length} 筆記錄</span>
      </div>
      <div className="fund-table-wrap">
        <table className="fund-table div-table">
          <thead>
            <tr>
              <th style={{textAlign:'left'}}>年度</th>
              <th style={{textAlign:'left'}}>期別</th>
              <th>現金股利</th>
              <th>股票股利</th>
              <th>合計</th>
              <th>殖利率</th>
              <th style={{textAlign:'left'}}>除息日</th>
            </tr>
          </thead>
          <tbody>
            {dividends.map((d, i) => {
              const prev = dividends[i + 1];
              const cashTrend = prev ? (d.cash > prev.cash ? '↑' : d.cash < prev.cash ? '↓' : '') : '';
              const cashTrendColor = cashTrend === '↑' ? 'var(--bull)' : 'var(--bear)';
              return (
                <tr key={i}>
                  <td style={{textAlign:'left',fontFamily:'Times New Roman,serif',letterSpacing:'.06em'}}>{d.year}</td>
                  <td style={{textAlign:'left',fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>{d.period_type}</td>
                  <td style={{fontWeight: d.cash > 0 ? 600 : 400, color: d.cash > 0 ? 'var(--bull)' : 'var(--ink-mute)'}}>
                    {d.cash > 0 ? d.cash.toFixed(2) : '—'}
                    {cashTrend && <span style={{color:cashTrendColor,marginLeft:4,fontSize: 'var(--fs-xs)'}}>{cashTrend}</span>}
                  </td>
                  <td style={{color: d.stock > 0 ? 'var(--ink)' : 'var(--ink-mute)'}}>
                    {d.stock > 0 ? d.stock.toFixed(2) : '—'}
                  </td>
                  <td style={{fontWeight:600}}>{d.total.toFixed(2)}</td>
                  <td style={{color: d.yield >= 5 ? 'var(--bull)' : 'var(--ink)'}}>
                    {d.yield != null ? d.yield.toFixed(2) + '%' : '—'}
                  </td>
                  <td style={{textAlign:'left',fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>{d.exDate || '—'}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ─── 現金流量表 ──────────────────────────────────────────

function CashFlowSection({ quarters }) {
  if (!quarters.length) return (
    <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
      <div style={{fontSize:24, marginBottom:8}}>📊</div>
      <div>暫無現金流量資料</div>
      <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
    </div>
  );

  const last4 = quarters.slice(-4);
  const ocf4  = last4.reduce((s, q) => s + (q.OperatingCF || 0), 0);
  const fcf4  = last4.reduce((s, q) => s + (q.FreeCF     || 0), 0);
  const fmt   = v => v != null ? (v / 1e8).toFixed(1) : '—';
  const toB   = v => v != null ? parseFloat((v / 1e8).toFixed(2)) : null;

  const chartRef = React.useRef(null);
  React.useEffect(() => {
    if (!chartRef.current || !window.echarts || !quarters.length) return;
    const chart = echarts.init(chartRef.current, null, { renderer: 'svg' });
    const labels = quarters.map(q => {
      const d = q.date || '';
      return d.slice(2,4) + 'Q' + Math.ceil(parseInt(d.slice(5,7)) / 3);
    });
    chart.setOption({
      backgroundColor: 'transparent',
      tooltip: {
        trigger: 'axis',
        backgroundColor: '#1a2438',
        borderColor: '#b8924d',
        borderWidth: 1,
        textStyle: { color: '#f6f1e4', fontSize: 11.5, fontFamily: 'Times New Roman, serif' },
        formatter: function(params) {
          var s = '<b style="font-family:Times New Roman">' + params[0].name + '</b><br/>';
          params.forEach(function(p) {
            var v = p.value;
            var col = v == null ? '#9a9789' : v >= 0 ? '#4aaa6e' : '#dd6060';
            s += '<span style="color:' + col + '">● ' + p.seriesName + ': ' + (v != null ? v.toFixed(2) + ' 億' : '—') + '</span><br/>';
          });
          return s;
        }
      },
      legend: {
        data: ['營業CF', '自由CF', '資本支出'],
        bottom: 0,
        textStyle: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' },
        itemWidth: 12, itemHeight: 8
      },
      grid: { left: 52, right: 12, top: 12, bottom: 44 },
      xAxis: {
        type: 'category', data: labels,
        axisLine: { lineStyle: { color: '#d8cdb0' } },
        axisTick: { show: false },
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' }
      },
      yAxis: {
        type: 'value',
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80', formatter: '{value}億' },
        splitLine: { lineStyle: { color: '#d8cdb0', type: 'dashed' } },
        axisLine: { show: false }
      },
      series: [
        {
          name: '營業CF', type: 'bar', barMaxWidth: 16, barGap: '8%',
          data: quarters.map(q => toB(q.OperatingCF)),
          itemStyle: { color: function(p) { return p.value >= 0 ? '#2a6b4c' : '#9c2b2b'; }, borderRadius: [2,2,0,0] }
        },
        {
          name: '自由CF', type: 'bar', barMaxWidth: 16,
          data: quarters.map(q => toB(q.FreeCF)),
          itemStyle: { color: function(p) { return p.value >= 0 ? '#2c5e8e' : '#c06030'; }, borderRadius: [2,2,0,0] }
        },
        {
          name: '資本支出', type: 'bar', barMaxWidth: 16,
          data: quarters.map(q => toB(q.CapEx)),
          itemStyle: { color: '#a0998a', borderRadius: [0,0,2,2] }
        }
      ]
    });
    var ro = new ResizeObserver(function() { chart.resize(); });
    ro.observe(chartRef.current);
    return function() { chart.dispose(); ro.disconnect(); };
  }, [quarters.length, quarters[0] && quarters[0].date, quarters[quarters.length-1] && quarters[quarters.length-1].date]);

  return (
    <div>
      <div className="fund-stat-bar" style={{marginBottom:16}}>
        <div className="fund-stat-bar-item">
          <span className="k">近4季累計 OCF</span>
          <span className="v" style={{color: ocf4 >= 0 ? 'var(--bull)' : 'var(--bear)'}}>
            {fmt(ocf4)} <small style={{fontSize:13,fontWeight:400}}>億</small>
          </span>
        </div>
        <div className="fund-stat-bar-item">
          <span className="k">近4季累計 FCF</span>
          <span className="v" style={{color: fcf4 >= 0 ? 'var(--bull)' : 'var(--bear)'}}>
            {fmt(fcf4)} <small style={{fontSize:13,fontWeight:400}}>億</small>
          </span>
        </div>
        <div className="fund-stat-bar-item">
          <span className="k">最新季 OCF</span>
          <span className="v" style={{color: (quarters[quarters.length-1]?.OperatingCF||0) >= 0 ? 'var(--bull)' : 'var(--bear)'}}>
            {fmt(quarters[quarters.length-1]?.OperatingCF)} <small style={{fontSize:13,fontWeight:400}}>億</small>
          </span>
        </div>
        <div className="fund-stat-bar-item">
          <span className="k">最新季 FCF</span>
          <span className="v" style={{color: (quarters[quarters.length-1]?.FreeCF||0) >= 0 ? 'var(--bull)' : 'var(--bear)'}}>
            {fmt(quarters[quarters.length-1]?.FreeCF)} <small style={{fontSize:13,fontWeight:400}}>億</small>
          </span>
        </div>
      </div>

      <div className="fund-chart-wrap" style={{padding:'8px 0 16px'}}>
        <div ref={chartRef} style={{width:'100%', height:'200px'}} />
      </div>

      <div className="fund-table-wrap">
        <table className="fund-table cf-table">
          <thead>
            <tr>
              <th style={{textAlign:'left'}}>季度</th>
              <th>營業CF</th>
              <th>投資CF</th>
              <th>融資CF</th>
              <th>資本支出</th>
              <th>自由CF</th>
            </tr>
          </thead>
          <tbody>
            {quarters.map((q, i) => {
              const d = q.date || '';
              const lbl = d.slice(0,4) + 'Q' + Math.ceil(parseInt(d.slice(5,7))/3);
              return (
                <tr key={i}>
                  <td style={{textAlign:'left',fontFamily:'Times New Roman,serif',letterSpacing:'.06em'}}>{lbl}</td>
                  <td className={q.OperatingCF != null ? (q.OperatingCF >= 0 ? 'pos' : 'neg') : ''}>{fmt(q.OperatingCF)}</td>
                  <td className={q.InvestingCF != null ? (q.InvestingCF >= 0 ? 'pos' : 'neg') : ''}>{fmt(q.InvestingCF)}</td>
                  <td className={q.FinancingCF != null ? (q.FinancingCF >= 0 ? 'pos' : 'neg') : ''}>{fmt(q.FinancingCF)}</td>
                  <td style={{color:'var(--ink-mute)'}}>{fmt(q.CapEx)}</td>
                  <td className={q.FreeCF != null ? (q.FreeCF >= 0 ? 'pos' : 'neg') : ''} style={{fontWeight:600}}>{fmt(q.FreeCF)}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:8,fontStyle:'italic'}}>
        * 自由CF = 營業CF + 資本支出（資本支出通常為負值）。單位：億元。來源：FinMind。
      </div>
    </div>
  );
}

// ─── 法人籌碼 ─────────────────────────────────────────────

function ChipSection({ chipSummary }) {
  if (!chipSummary.length) return (
    <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
      <div style={{fontSize:24, marginBottom:8}}>📊</div>
      <div>暫無法人籌碼資料</div>
      <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
    </div>
  );

  const last20 = chipSummary.slice(-20);
  const last10 = chipSummary.slice(-10);
  const cumForeign = last10.reduce((s,d) => s + d.foreign, 0);
  const cumTrust   = last10.reduce((s,d) => s + d.trust,   0);
  const cumDealer  = last10.reduce((s,d) => s + d.dealer,  0);
  const cumTotal   = cumForeign + cumTrust + cumDealer;

  const fmtLot = v => {
    const abs = Math.abs(v);
    if (abs >= 1e8) return (v/1e8).toFixed(1) + '億';
    if (abs >= 1e4) return (v/1e4).toFixed(0) + '萬';
    return v.toLocaleString() + '張';
  };

  const chartRef = React.useRef(null);
  React.useEffect(() => {
    if (!chartRef.current || !window.echarts || !last20.length) return;
    const chart = echarts.init(chartRef.current, null, { renderer: 'svg' });
    const labels = last20.map(d => d.date.slice(5));
    chart.setOption({
      backgroundColor: 'transparent',
      tooltip: {
        trigger: 'axis',
        axisPointer: { type: 'shadow' },
        backgroundColor: '#1a2438',
        borderColor: '#b8924d',
        borderWidth: 1,
        textStyle: { color: '#f6f1e4', fontSize: 11.5, fontFamily: 'Times New Roman, serif' },
        formatter: function(params) {
          var idx = params[0].dataIndex;
          var s = '<b style="font-family:Times New Roman">' + (last20[idx] ? last20[idx].date : '') + '</b><br/>';
          params.forEach(function(p) {
            var v = p.value;
            if (p.seriesName === '合計') {
              s += '<span style="color:' + (v >= 0 ? '#4aaa6e' : '#dd6060') + '">● 合計: ' + v.toLocaleString() + ' 張</span><br/>';
            } else {
              s += '<span style="color:' + p.color + '">● ' + p.seriesName + ': ' + v.toLocaleString() + ' 張</span><br/>';
            }
          });
          return s;
        }
      },
      legend: {
        data: ['外資', '投信', '自營商', '合計'],
        top: 0,
        textStyle: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80' },
        itemWidth: 12, itemHeight: 8
      },
      grid: { left: 60, right: 12, top: 28, bottom: 48 },
      xAxis: {
        type: 'category', data: labels,
        axisLine: { lineStyle: { color: '#d8cdb0' } },
        axisTick: { show: false },
        axisLabel: { fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80', rotate: 35, interval: 1 }
      },
      yAxis: {
        type: 'value',
        axisLabel: {
          fontSize: 11.5, fontFamily: 'Times New Roman, serif', color: '#6a6f80',
          formatter: function(v) {
            var a = Math.abs(v);
            if (a >= 1e4) return (v/1e4).toFixed(0) + '萬';
            return v.toLocaleString();
          }
        },
        splitLine: { lineStyle: { color: '#d8cdb0', type: 'dashed' } },
        axisLine: { show: false }
      },
      series: [
        {
          name: '外資', type: 'bar', stack: 'chip', barMaxWidth: 22,
          data: last20.map(d => d.foreign),
          itemStyle: { color: '#2c5e8e' }
        },
        {
          name: '投信', type: 'bar', stack: 'chip', barMaxWidth: 22,
          data: last20.map(d => d.trust),
          itemStyle: { color: '#b8924d' }
        },
        {
          name: '自營商', type: 'bar', stack: 'chip', barMaxWidth: 22,
          data: last20.map(d => d.dealer),
          itemStyle: { color: '#4a7a4e' }
        },
        {
          name: '合計', type: 'line', z: 10,
          data: last20.map(d => d.total),
          lineStyle: { width: 2, color: '#1a2438' },
          itemStyle: { color: '#1a2438' },
          symbol: 'circle', symbolSize: 4
        }
      ]
    });
    var ro = new ResizeObserver(function() { chart.resize(); });
    ro.observe(chartRef.current);
    return function() { chart.dispose(); ro.disconnect(); };
  }, [chipSummary.length, last20[0] && last20[0].date, last20[last20.length-1] && last20[last20.length-1].date]);

  return (
    <div>
      <div className="fund-stat-bar" style={{marginBottom:16}}>
        <div className="fund-stat-bar-item">
          <span className="k">外資 近10日累計</span>
          <span className="v" style={{color: cumForeign >= 0 ? 'var(--bull)' : 'var(--bear)'}}>{fmtLot(cumForeign)}</span>
        </div>
        <div className="fund-stat-bar-item">
          <span className="k">投信 近10日累計</span>
          <span className="v" style={{color: cumTrust >= 0 ? 'var(--bull)' : 'var(--bear)'}}>{fmtLot(cumTrust)}</span>
        </div>
        <div className="fund-stat-bar-item">
          <span className="k">自營商 近10日累計</span>
          <span className="v" style={{color: cumDealer >= 0 ? 'var(--bull)' : 'var(--bear)'}}>{fmtLot(cumDealer)}</span>
        </div>
        <div className="fund-stat-bar-item">
          <span className="k">三大法人合計</span>
          <span className="v" style={{color: cumTotal >= 0 ? 'var(--bull)' : 'var(--bear)', fontWeight:700}}>{fmtLot(cumTotal)}</span>
        </div>
      </div>

      <div className="fund-chart-wrap" style={{padding:'8px 0 16px'}}>
        <div ref={chartRef} style={{width:'100%', height:'260px'}} />
      </div>

      <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:8,fontStyle:'italic'}}>
        * 數據來源：FinMind。近20交易日；堆疊柱＝外資/投信/自營商分貢獻，折線＝三大合計。單位：張。
      </div>
    </div>
  );
}

// ─── 企業健診 Diagnostics ────────────────────────────────
function DiagnosticsSection({ fins, bsData, cashFlow, rt, bwibbu }) {
  // ── Piotroski 7 signals ──
  var psigs = { p1:0,p2:0,p3:0,p4:0,p5:0,p6:0,p7:0 };
  var pScore = 0;
  if (fins.length >= 4 && bsData.length >= 2) {
    var bsN = bsData[bsData.length-1], bsP = bsData[bsData.length-2];
    var taN=bsN.TotalAssets||0, tAP=bsP.TotalAssets||0;
    var caN=bsN.CurrentAssets||0, clN=bsN.CurrentLiabilities||0;
    var cAP=bsP.CurrentAssets||0, cLP=bsP.CurrentLiabilities||0;
    var eqN=bsN.StockholdersEquity||0, eqP=bsP.StockholdersEquity||0;
    var niN4=fins.slice(-4).reduce(function(s,q){return s+(q.NetIncome||0);},0);
    var niP4=fins.length>=8?fins.slice(-8,-4).reduce(function(s,q){return s+(q.NetIncome||0);},0):0;
    var rvN4=fins.slice(-4).reduce(function(s,q){return s+(q.Revenue||0);},0);
    var rvP4=fins.length>=8?fins.slice(-8,-4).reduce(function(s,q){return s+(q.Revenue||0);},0):0;
    var gpN4=fins.slice(-4).reduce(function(s,q){return s+(q.GrossProfit||0);},0);
    var gpP4=fins.length>=8?fins.slice(-8,-4).reduce(function(s,q){return s+(q.GrossProfit||0);},0):0;
    var roaN=taN>0?niN4/taN:0, roaP=tAP>0?niP4/tAP:0;
    if (roaN>0)           psigs.p1=1;
    if (roaN>roaP&&tAP>0) psigs.p2=1;
    if (cashFlow&&cashFlow.length) {
      var cfoTTM=cashFlow.slice(-4).reduce(function(s,q){return s+(q.OperatingCF||0);},0);
      if (cfoTTM>0)                          psigs.p3=1;
      if (taN>0&&cfoTTM/taN>roaN)            psigs.p4=1;
    }
    var crN=clN>0?caN/clN:0, crP=cLP>0?cAP/cLP:0;
    if (crN>crP&&crP>0)                      psigs.p5=1;
    var gmN=rvN4>0?gpN4/rvN4:0, gmP=rvP4>0?gpP4/rvP4:0;
    if (gmN>gmP&&rvP4>0)                     psigs.p6=1;
    var atN=taN>0?rvN4/taN:0, atP=tAP>0?rvP4/tAP:0;
    if (atN>atP&&atP>0)                      psigs.p7=1;
    pScore=psigs.p1+psigs.p2+psigs.p3+psigs.p4+psigs.p5+psigs.p6+psigs.p7;
  }

  // ── Altman Z' ──
  var altZ = null;
  if (fins.length>=4 && bsData.length>=1) {
    var bsL=bsData[bsData.length-1];
    var ta=bsL.TotalAssets,ca=bsL.CurrentAssets,cl2=bsL.CurrentLiabilities;
    var tl=bsL.TotalLiabilities,eq=bsL.StockholdersEquity;
    if (ta&&ta>0) {
      var r4rev=fins.slice(-4).reduce(function(s,q){return s+(q.Revenue||0);},0);
      var r4oi =fins.slice(-4).reduce(function(s,q){return s+(q.OperatingIncome||0);},0);
      var r4ni =fins.slice(-4).reduce(function(s,q){return s+(q.NetIncome||0);},0);
      var x1=ca&&cl2?(ca-cl2)/ta:0;
      var x2=eq?eq/ta:0;
      var x3=r4oi?r4oi/ta:0;
      var x4=tl&&tl>0?eq/tl:0;
      var x5=r4rev?r4rev/ta:0;
      altZ=+(0.717*x1+0.847*x2+3.107*x3+0.420*x4+0.998*x5).toFixed(2);
    }
  }
  var azZone = altZ==null?null:altZ>2.99?{label:'安全區',color:'var(--bull)'}:altZ>1.8?{label:'灰色觀察區',color:'var(--gold)'}:{label:'財務危機區',color:'var(--bear)'};

  // ── Magic Formula (近似) ──
  var mfRoc=null, mfEY=null;
  if (fins.length>=4 && bsData.length>=1) {
    var bsL2=bsData[bsData.length-1];
    var oi4=fins.slice(-4).reduce(function(s,q){return s+(q.OperatingIncome||0);},0);
    var ncWC=(bsL2.CurrentAssets||0)-(bsL2.CurrentLiabilities||0);
    var ppe = Math.max(0,(bsL2.TotalAssets||0)-(bsL2.CurrentAssets||0)-(bsL2.StockholdersEquity||0)*0.2);
    if (ncWC+ppe>0) mfRoc=+(oi4/(ncWC+ppe)*100).toFixed(1);
    var mktCap = rt&&rt.mktCap ? rt.mktCap : 0;
    var ev=mktCap+(bsL2.TotalLiabilities||0)-(bsL2.CurrentAssets||0);
    if (ev>0&&oi4>0) mfEY=+(oi4/ev*100).toFixed(1);
  }

  // ── Quality Acceleration sparkline data ──
  var roeArr=[], omArr=[];
  if (fins.length>=4 && bsData.length>=2) {
    var maxQ=Math.min(fins.length, bsData.length);
    for (var i=0;i<maxQ;i++) {
      var q=fins[fins.length-maxQ+i], bs2=bsData[bsData.length-maxQ+i];
      var e=bs2.StockholdersEquity||0;
      roeArr.push(e>0?(q.NetIncome||0)/e*100:0);
      omArr.push(q.Revenue>0?(q.OperatingIncome||0)/q.Revenue*100:0);
    }
  }

  function MiniSparkline({ arr, color, label }) {
    if (!arr || arr.length < 2) return <span style={{color:'var(--ink-mute)',fontSize: 'var(--fs-xs)'}}>資料不足</span>;
    var min=Math.min(...arr), max=Math.max(...arr);
    var range=max-min||1;
    var W=160, H=44, P=4;
    var pts=arr.map(function(v,i){
      var x=P+(i/(arr.length-1))*(W-P*2);
      var y=H-P-((v-min)/range)*(H-P*2);
      return x+','+y;
    }).join(' ');
    var last=arr[arr.length-1], prev=arr[arr.length-2];
    var up=last>=prev;
    return (
      <div style={{display:'flex',alignItems:'center',gap:12}}>
        <div>
          <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginBottom:2}}>{label}</div>
          <div className="num" style={{fontSize:18,fontWeight:700,color:up?'var(--bull)':'var(--bear)'}}>
            {last.toFixed(1)}%{up?' ↑':' ↓'}
          </div>
        </div>
        <svg width={W} height={H} style={{overflow:'visible'}}>
          <polyline points={pts} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round"/>
          {arr.map(function(v,i){
            var x=P+(i/(arr.length-1))*(W-P*2);
            var y=H-P-((v-min)/range)*(H-P*2);
            return <circle key={i} cx={x} cy={y} r="2.5" fill={color} opacity={i===arr.length-1?1:0.4}/>;
          })}
        </svg>
      </div>
    );
  }

  var psigMeta = [
    {key:'p1',label:'ROA > 0',desc:'本業獲利能力'},
    {key:'p2',label:'ΔROA ↑',desc:'ROA 年度改善'},
    {key:'p3',label:'CFO > 0',desc:'現金流為正'},
    {key:'p4',label:'現金品質',desc:'CFO/TA > ROA'},
    {key:'p5',label:'ΔCurrentRatio ↑',desc:'流動比率改善'},
    {key:'p6',label:'ΔGrossMargin ↑',desc:'毛利率提升'},
    {key:'p7',label:'ΔAssetTurnover ↑',desc:'資產運用效率提升'},
  ];

  return (
    <div>
      {/* Piotroski */}
      <div style={{marginBottom:28}}>
        <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:14}}>
          <span style={{fontFamily:'Times New Roman,serif',fontStyle:'italic',fontSize:13,color:'var(--ink-mute)'}}>Piotroski F-Score</span>
          <span className="num" style={{fontSize:28,fontWeight:700,color:pScore>=6?'var(--bull)':pScore>=4?'var(--gold)':'var(--bear)'}}>{pScore}</span>
          <span style={{fontSize:13,color:'var(--ink-mute)'}}>/7</span>
          <span className="kai" style={{fontSize: 'var(--fs-xs)',color:pScore>=6?'var(--bull)':pScore>=4?'var(--gold)':'var(--bear)'}}>
            {pScore>=6?'財務健全':pScore>=4?'財務普通':'財務警示'}
          </span>
        </div>
        <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(180px,1fr))',gap:8}}>
          {psigMeta.map(function(s){
            var pass=psigs[s.key]===1;
            return (
              <div key={s.key} style={{
                padding:'8px 12px',
                border:'1px solid '+(pass?'var(--bull)':'var(--paper-line)'),
                background:pass?'rgba(30,173,137,0.05)':'transparent',
              }}>
                <div style={{display:'flex',alignItems:'center',gap:6,marginBottom:3}}>
                  <span style={{fontSize:14,color:pass?'var(--bull)':'var(--bear)'}}>{pass?'✓':'✗'}</span>
                  <span className="kai" style={{fontSize: 'var(--fs-xs)',color:pass?'var(--ink)':'var(--ink-mute)'}}>{s.label}</span>
                </div>
                <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>{s.desc}</div>
              </div>
            );
          })}
        </div>
        {fins.length<4&&<div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:8,fontStyle:'italic'}}>需要至少 4 季損益 + 2 期資產負債表資料</div>}
      </div>

      {/* Altman Z-Score */}
      {altZ != null && (
        <div style={{marginBottom:28}}>
          <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:10}}>
            <span style={{fontFamily:'Times New Roman,serif',fontStyle:'italic',fontSize:13,color:'var(--ink-mute)'}}>Altman Z'-Score</span>
            <span className="num" style={{fontSize:28,fontWeight:700,color:azZone.color}}>{altZ}</span>
            <span className="kai" style={{fontSize: 'var(--fs-xs)',color:azZone.color}}>{azZone.label}</span>
          </div>
          <div style={{position:'relative',height:18,background:'var(--paper-line)',borderRadius:3,maxWidth:320}}>
            {[{v:1.8,c:'var(--bear)'},{v:2.99,c:'var(--gold)'}].map(function(m){
              return <div key={m.v} style={{
                position:'absolute',left:(Math.min(m.v,5)/5*100)+'%',top:0,width:2,height:'100%',background:m.c,opacity:.7
              }}/>;
            })}
            <div style={{
              position:'absolute',
              left:(Math.min(Math.max(altZ,0),5)/5*100)+'%',
              top:-3,transform:'translateX(-50%)',
              width:10,height:24,background:azZone.color,borderRadius:2,
            }}/>
          </div>
          <div style={{display:'flex',justifyContent:'space-between',maxWidth:320,marginTop:4,fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>
            <span>0 危機</span><span>1.8 灰色</span><span>2.99 安全 →5</span>
          </div>
        </div>
      )}

      {/* Magic Formula */}
      {(mfRoc != null || mfEY != null) && (
        <div style={{marginBottom:28}}>
          <div style={{fontSize:13,fontFamily:'Times New Roman,serif',fontStyle:'italic',color:'var(--ink-mute)',marginBottom:10}}>
            Magic Formula（Greenblatt 近似）
          </div>
          <div className="fund-metric-tiles">
            {mfRoc != null && (
              <div className="fund-metric-tile" style={{'--tile-color':'var(--gold)'}}>
                <div className="mt-label">ROC 資本報酬率</div>
                <div className="mt-value num" style={{color:mfRoc>15?'var(--bull)':mfRoc>8?'var(--gold)':'var(--bear)'}}>{mfRoc}%</div>
                <div className="mt-sub">EBIT / (NWC + Fixed Assets)</div>
              </div>
            )}
            {mfEY != null && (
              <div className="fund-metric-tile" style={{'--tile-color':'var(--gold)'}}>
                <div className="mt-label">EY 盈餘殖利率</div>
                <div className="mt-value num" style={{color:mfEY>8?'var(--bull)':mfEY>4?'var(--gold)':'var(--bear)'}}>{mfEY}%</div>
                <div className="mt-sub">EBIT / Enterprise Value</div>
              </div>
            )}
          </div>
          <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:6,fontStyle:'italic'}}>
            EV = 市值 + 總負債 − 流動資產（估算）。ROC 越高 + EY 越高 = 越符合 Magic Formula 選股標準。
          </div>
        </div>
      )}

      {/* Quality Acceleration */}
      {(roeArr.length >= 4 || omArr.length >= 4) && (
        <div>
          <div style={{fontSize:13,fontFamily:'Times New Roman,serif',fontStyle:'italic',color:'var(--ink-mute)',marginBottom:12}}>
            Quality Acceleration · 質量加速度
          </div>
          <div style={{display:'flex',flexWrap:'wrap',gap:32}}>
            {roeArr.length>=4&&<MiniSparkline arr={roeArr} color="var(--bull)" label="ROE 季度走勢 (%)"/>}
            {omArr.length>=4&&<MiniSparkline arr={omArr} color="var(--gold)" label="營業利益率走勢 (%)"/>}
          </div>
        </div>
      )}
    </div>
  );
}

// ─── 主 App ─────────────────────────────────────────────

function FundamentalApp({ externalCode } = {}) {
  const params = new URLSearchParams(window.location.search);
  const initCode = externalCode || params.get('code') || '2330';
  const [code, setCode] = useState(initCode);
  const [inputCode, setInputCode] = useState(initCode);

  // 當外部 code 改變時同步（從技術分析 tab 切換個股）
  useEffect(() => {
    if (externalCode && externalCode !== code) {
      setCode(externalCode);
      setInputCode(externalCode);
    }
  }, [externalCode]);

  const [loading,    setLoading]    = useState(true);
  const [rt,         setRt]         = useState(null);
  const [revenue,    setRevenue]    = useState([]);
  const [fins,       setFins]       = useState([]);
  const [bsData,     setBsData]     = useState([]);
  const [perData,    setPerData]    = useState({});
  const [bwibbu,     setBwibbu]     = useState(null);
  const [peerPrices, setPeerPrices] = useState({});
  const [peerBwibbu, setPeerBwibbu] = useState({}); // 同業 P/E / P/B
  const [peerMini,   setPeerMini]   = useState({}); // 同業近5日收盤價走勢
  const [analystData, setAnalystData] = useState(null);
  const [earningsData,setEarningsData]= useState([]);
  const [fmpMetrics,  setFmpMetrics]  = useState(null);
  const [cashFlow,   setCashFlow]   = useState([]);
  const [chipSummary,setChipSummary]= useState([]);
  const [dividends,  setDividends]  = useState([]);
  const [ownership,  setOwnership]  = useState(null);   // 外資持股 1 年趨勢（專業化第一波）
  const [ownErr,     setOwnErr]     = useState('');

  useEffect(() => {
    setOwnership(null); setOwnErr('');
    fetch(`/api/stock?type=ownership&code=${code}`)
      .then(r => r.json())
      .then(d => { d.error ? setOwnErr(d.error) : setOwnership(d); })
      .catch(e => setOwnErr(e.message));
  }, [code]);

  // 前端 ADR 對照表（server-side TW_ADR_MAP 的子集，供前端直接查詢 FMP）
  const FE_ADR_MAP = { '2330': 'TSM', '2303': 'UMC', '2412': 'CHT', '3711': 'ASX', '2409': 'AUO', '3697': 'HIMX', '8150': 'IMOS' };
  const adrSymbol = FE_ADR_MAP[code] || null;

  const profile = window.getCompanyProfile?.(code) || null;

  useEffect(() => {
    setLoading(true);
    setRt(null); setRevenue([]); setFins([]); setBsData([]); setPerData({}); setBwibbu(null);
    setPeerBwibbu({}); setPeerMini({}); setAnalystData(null); setEarningsData([]); setFmpMetrics(null);
    setCashFlow([]); setChipSummary([]); setDividends([]);

    const peerCodes = profile?.peers || [];
    const allCodes  = [code, ...peerCodes].join(',');

    Promise.allSettled([
      fetch(`/api/stock?type=realtime&code=${code}`).then(r => r.json()),
      // 6 種 FinMind 資料合併成單一 stock_bundle 請求(2026-07-18 體檢:
      // 原本 fan-out 6 路 HTTP,server 端同 KV 快取,月營收/基本面並新增快取)
      fetch(`/api/stock?type=stock_bundle&code=${code}`).then(r => r.json()),
      fetch(`/api/stock?type=bwibbu&code=${code}`).then(r => r.json()),
      fetch(`/api/stock?type=peer_prices&codes=${allCodes}`).then(r => r.json()),
      // 批次同業 P/E / P/B
      peerCodes.length
        ? fetch(`/api/stock?type=bwibbu_batch&codes=${allCodes}`).then(r => r.json())
        : Promise.resolve({}),
      // 機構分析師目標價（FMP）
      fetch(`/api/stock?type=analyst_targets&code=${code}&_cb=${new Date().toISOString().slice(0,10).replace(/-/g,'')}`).then(r => r.json()),
      // EPS 驚喜記錄（FMP，僅 ADR 股票）
      adrSymbol
        ? fetch(`/api/stock?type=fmp_earnings&symbol=${adrSymbol}`).then(r => r.json())
        : Promise.resolve(null),
      // FMP 年度關鍵指標（EV/EBITDA、葛拉漢數字、市值）
      adrSymbol
        ? fetch(`/api/stock?type=fmp_key_metrics&symbol=${adrSymbol}`).then(r => r.json())
        : Promise.resolve(null),
    ]).then(([rtR, bundleR, bwR, peerR, peerBwR, analystR, earningsR, metricsR]) => {
      if (rtR.status   === 'fulfilled') setRt(parseRealtime(rtR.value));
      if (bundleR.status === 'fulfilled' && bundleR.value) {
        const b = bundleR.value;
        if (b.revenue)      setRevenue(parseMonthlyRevenue(b.revenue));
        if (b.financials)   setFins(parseFinancials(b.financials));
        if (b.balanceSheet) setBsData(parseBalanceSheet(b.balanceSheet));
        if (b.fundamental) {
          setPerData(b.fundamental);
          setDividends(parseDividends(b.fundamental));
        }
        setCashFlow((b.cashflow?.quarters || []).slice(-8));
        setChipSummary(b.chipSummary?.data || []);
      }
      if (bwR.status   === 'fulfilled') setBwibbu(bwR.value?.pe != null ? bwR.value : null);
      if (peerR.status === 'fulfilled') setPeerPrices(parsePeerPrices(peerR.value));
      if (peerBwR.status   === 'fulfilled') setPeerBwibbu(peerBwR.value || {});
      if (analystR.status === 'fulfilled' && ['fmp','fmp_tw','adr_implied','sa','tradingview'].includes(analystR.value?.source)) setAnalystData(analystR.value);
      if (earningsR?.status === 'fulfilled' && Array.isArray(earningsR.value?.data)) setEarningsData(earningsR.value.data.slice(0, 4));
      if (metricsR?.status === 'fulfilled' && Array.isArray(metricsR.value?.data) && metricsR.value.data.length > 0) setFmpMetrics(metricsR.value.data[0]);
      setLoading(false);

      // 非同步補充：同業近 5 日走勢迷你圖（較慢，不阻塞主要載入）
      const miniCodes = [code, ...(profile?.peers || [])].slice(0, 8).join(',');
      if (miniCodes) {
        fetch(`/api/stock?type=peer_mini&codes=${miniCodes}`)
          .then(r => r.json())
          .then(data => { if (data && typeof data === 'object') setPeerMini(data); })
          .catch(() => {});
      }
    });
  }, [code]);

  // 全站刷新鈕：只重抓即時報價與同業報價（不重載財報，避免整頁閃白）
  const qhN = window.useQhRefresh ? window.useQhRefresh() : 0;
  useEffect(() => {
    if (!qhN) return;
    fetch(`/api/stock?type=realtime&code=${code}`).then(r => r.json())
      .then(v => setRt(parseRealtime(v))).catch(() => {});
    const allCodes = [code, ...(profile?.peers || [])].join(',');
    fetch(`/api/stock?type=peer_prices&codes=${allCodes}`).then(r => r.json())
      .then(v => setPeerPrices(parsePeerPrices(v))).catch(() => {});
  }, [qhN]);

  const fv = useMemo(() => computeFairValue(fins, perData, bwibbu, rt, profile, bsData, cashFlow), [fins, perData, bwibbu, rt, profile, bsData, cashFlow]);

  function goCode(c) {
    const trimmed = c.trim().replace(/\D/g, '').slice(0, 6);
    if (!trimmed) return;
    const newUrl = new URL(window.location.href);
    newUrl.searchParams.set('code', trimmed);
    window.history.pushState({}, '', newUrl);
    setCode(trimmed);
    setInputCode(trimmed);
  }

  // BS key metrics
  const latestBs = bsData[bsData.length - 1] || {};
  const totalAssets  = latestBs['TotalAssets']      || latestBs['資產總計']    || 0;
  const totalEquity  = latestBs['StockholdersEquity']|| latestBs['權益總計']   || 0;
  const totalLiab    = latestBs['TotalLiabilities']  || latestBs['負債總計']   || 0;
  const currAssets   = latestBs['CurrentAssets']     || 0;
  const currLiab     = latestBs['CurrentLiabilities']|| 0;
  const debtEquity   = totalEquity > 0 ? +(totalLiab / totalEquity).toFixed(2) : null;
  const currentRatio = currLiab   > 0 ? +(currAssets / currLiab).toFixed(2)   : null;

  // ROE from latest quarter financials
  const latestFin = fins[fins.length - 1] || {};
  const netIncome = latestFin['NetIncome'] || latestFin['AfterTaxProfit'] || 0;
  const roe = totalEquity > 0 ? +((netIncome / totalEquity) * 4 * 100).toFixed(1) : null;

  const rtName   = rt?.name || profile?.name || code;
  const rtPrice  = rt?.price || 0;
  const rtChg    = rt?.chg  || 0;
  const rtPct    = rt?.pct  || 0;

  // P/E, P/B, 殖利率：優先 TWSE BWIBBU（即時、免費），備援 FinMind PER
  const latestPer = (perData?.per || []).slice(-1)[0];
  const pe = bwibbu?.pe  ?? (latestPer?.PER  || null);
  const pb = bwibbu?.pb  ?? (latestPer?.PBR  || null);
  const dy = bwibbu?.yield > 0 ? bwibbu.yield : (latestPer?.dividend_yield || null);
  const peSource = bwibbu ? `TWSE BWIBBU · ${bwibbu.period || bwibbu.date || ''}` : 'FinMind PER';

  // Revenue trend (latest vs year-ago)
  const recentRev  = revenue.slice(-12).reduce((a, b) => a + b.revRaw, 0);
  const prevRev    = revenue.slice(-24, -12).reduce((a, b) => a + b.revRaw, 0);
  const revYoy12m  = prevRev > 0 ? +(((recentRev - prevRev) / prevRev) * 100).toFixed(1) : null;

  const FIN_FIELDS = [
    { key:'Revenue',           label:'營業收入',   unit:'億' },
    { key:'GrossProfit',       label:'毛利',       unit:'億' },
    { key:'OperatingIncome',   label:'營業利益',   unit:'億' },
    { key:'NetIncome',         label:'本期淨利',   unit:'億' },
    { key:'EPS',               label:'EPS',        unit:'元' },
  ];

  const grossMargin = (q) => {
    const rev = q.Revenue || 0;
    const gp  = q.GrossProfit || 0;
    return rev > 0 ? +((gp / rev) * 100).toFixed(1) : null;
  };

  // 嵌入模式：由 stock.jsx 提供 externalCode，不渲染 Masthead/TickerTape/NavBar/FooterBar
  const embedded = !!externalCode;

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

      {/* Search：嵌入模式下隱藏（由 stock.jsx 的搜尋欄控制 code） */}
      {!embedded && (
      <div className="fund-search-wrap">
        <input
          className="fund-search-input"
          type="text" inputMode="numeric"
          placeholder="股票代號…"
          value={inputCode}
          onChange={e => setInputCode(e.target.value)}
          onKeyDown={e => e.key === 'Enter' && goCode(inputCode)}
        />
        <button className="fund-search-btn" onClick={() => goCode(inputCode)}>查　詢</button>
      </div>
      )}

      {/* ── Cover Header ───────────────────────────────── */}
      <div className="fund-cover">
        <span className="fund-cover-ornament">❦</span>
        <div className="fund-cover-left">
          <div className="fund-eyebrow">
            Formosa &amp; Co. · Fundamentals &amp; Valuation
            {profile?.sector ? ` · ${profile.sector}` : ''}
          </div>
          <div className="fund-stock-meta">
            <span className="fund-code">{code}</span>
            <span className="fund-name">{rtName}</span>
            {profile?.nameEn && <span className="fund-name-en">{profile.nameEn}</span>}
            {profile?.sector && <span className="fund-sector-tag">{profile.sector}</span>}
          </div>
          <div className="fund-price-row">
            <span className="fund-price">{rtPrice > 0 ? rtPrice.toLocaleString() : '—'}</span>
            {rtPrice > 0 && (
              <span className={`fund-chg ${rtChg >= 0 ? 'up' : 'dn'}`}>
                {rtChg >= 0 ? '+' : ''}{rtChg} &nbsp;({rtPct >= 0 ? '+' : ''}{rtPct}%)
              </span>
            )}
          </div>
          {/* Metric Tiles */}
          <div className="fund-metric-tiles">
            {pe && (
              <div className={`fund-metric-tile tile-gold`}>
                <span className="mt-label">本益比 P/E</span>
                <span className="mt-value">{pe.toFixed(1)}<small style={{fontSize:13,fontWeight:400,marginLeft:2}}>x</small></span>
                <span className="mt-sub">{bwibbu ? 'TWSE BWIBBU' : 'FinMind'}</span>
              </div>
            )}
            {pb && (
              <div className="fund-metric-tile">
                <span className="mt-label">淨值比 P/B</span>
                <span className="mt-value">{pb.toFixed(2)}<small style={{fontSize:13,fontWeight:400,marginLeft:2}}>x</small></span>
                <span className="mt-sub">Book Value</span>
              </div>
            )}
            {dy && (
              <div className="fund-metric-tile tile-bull">
                <span className="mt-label">殖利率 Yield</span>
                <span className="mt-value">{dy.toFixed(2)}<small style={{fontSize:13,fontWeight:400,marginLeft:1}}>%</small></span>
                <span className="mt-sub">Dividend</span>
              </div>
            )}
            {revYoy12m != null && (
              <div className={`fund-metric-tile ${revYoy12m >= 0 ? 'tile-bull' : 'tile-bear'}`}>
                <span className="mt-label">營收 YoY</span>
                <span className={`mt-value ${revYoy12m >= 0 ? 'bull' : 'bear'}`}>
                  {revYoy12m >= 0 ? '+' : ''}{revYoy12m}<small style={{fontSize:13,fontWeight:400,marginLeft:1}}>%</small>
                </span>
                <span className="mt-sub">近 12 個月</span>
              </div>
            )}
            {roe && (
              <div className="fund-metric-tile">
                <span className="mt-label">ROE 年化</span>
                <span className="mt-value">{roe}<small style={{fontSize:13,fontWeight:400,marginLeft:1}}>%</small></span>
                <span className="mt-sub">Return on Equity</span>
              </div>
            )}
            {profile?.founded && (
              <div className="fund-metric-tile">
                <span className="mt-label">Founded</span>
                <span className="mt-value" style={{fontSize:18}}>{profile.founded}</span>
                <span className="mt-sub">成立年份</span>
              </div>
            )}
          </div>
        </div>
        <div className="fund-cover-right">
          <a className="fund-nav-btn primary" href={`個股技術分析.html?code=${code}`}>技術圖表 →</a>
          <a className="fund-nav-btn" href="黃金交叉雷達.html">雷達掃描</a>
          <a className="fund-nav-btn" href="多因子篩選.html">多因子篩選</a>
        </div>
      </div>

      {loading && <div className="fund-loading">資料載入中，請稍候…</div>}

      {/* ── 0. 企業段位總評 ───────────────────────────── */}
      {!loading && fins.length > 0 && (
        <TierSection
          fins={fins} bsData={bsData} cashFlow={cashFlow} revenue={revenue}
          perData={perData} pe={pe} pb={pb} dy={dy}
          chipSummary={chipSummary} peerBwibbu={peerBwibbu} code={code}
          currentPrice={rt?.price || 0}
        />
      )}

      {/* ── M. 企業護城河 ─────────────────────────────── */}
      {!loading && fins.length >= 4 && (
        <MoatSection
          code={code} profile={profile} fins={fins} bsData={bsData}
          cashFlow={cashFlow} rtName={rtName}
        />
      )}

      {/* ── 1. 企業簡介 ───────────────────────────────── */}
      {profile && (
        <section className="fund-section">
          <div className="fund-section-head">
            <span className="sec-num">01</span>
            <div className="fund-section-title-wrap">
              <h2>企業簡介</h2>
              <span className="sec-en">Company Profile</span><SecNote id="profile"/>
            </div>
          </div>
          <div className="fund-profile-grid">
            <div>
              <p className="fund-desc">{profile.desc}</p>
              <div className="fund-biz-tags">
                {(profile.business || []).map((b, i) => (
                  <span key={i} className="fund-biz-tag">{b}</span>
                ))}
              </div>
            </div>
            <div className="fund-meta-list">
              {profile.nameEn    && <div className="fund-meta-row"><span className="fund-meta-key">英文名稱</span><span className="fund-meta-val">{profile.nameEn}</span></div>}
              {profile.sector    && <div className="fund-meta-row"><span className="fund-meta-key">所屬產業</span><span className="fund-meta-val">{profile.sector}</span></div>}
              {profile.founded   && <div className="fund-meta-row"><span className="fund-meta-key">成立年份</span><span className="fund-meta-val">{profile.founded} 年</span></div>}
              {profile.hq        && <div className="fund-meta-row"><span className="fund-meta-key">總部</span><span className="fund-meta-val">{profile.hq}</span></div>}
              {profile.employees && <div className="fund-meta-row"><span className="fund-meta-key">員工人數</span><span className="fund-meta-val">{profile.employees}</span></div>}
              {profile.chainPos  && <div className="fund-meta-row"><span className="fund-meta-key">供應鏈位置</span><span className="fund-meta-val">{{upstream:'上游',midstream:'中游',downstream:'下游'}[profile.chainPos] || profile.chainPos}</span></div>}
            </div>
          </div>
        </section>
      )}

      {/* ── 2. 月營收趨勢 ─────────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">02</span>
          <div className="fund-section-title-wrap">
            <h2>月營收趨勢</h2>
            <span className="sec-en">Monthly Revenue</span><SecNote id="revenue"/>
          </div>
          {revenue.length > 0 && (
            <span className="sec-badge">
              近 {revenue.length} 月 · {revenue[0]?.src === 'mops' ? 'MOPS' : 'FinMind'}
            </span>
          )}
        </div>
        <div className="fund-chart-wrap">
          {revenue.length === 0 && !loading
            ? <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}><div style={{fontSize:24, marginBottom:8}}>📊</div><div>暫無月營收資料</div><div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div></div>
            : <RevenueChart data={revenue} />}
        </div>
        {revenue.length > 0 && (() => {
          const last3  = revenue.slice(-3).reduce((s, d) => s + d.revRaw, 0);
          const prev3  = revenue.slice(-6,-3).reduce((s, d) => s + d.revRaw, 0);
          const q3Chg  = prev3 > 0 ? +(((last3 - prev3) / prev3) * 100).toFixed(1) : null;
          const last   = revenue[revenue.length - 1];
          return (
            <div className="fund-stat-bar">
              <div className="fund-stat-bar-item">
                <span className="k">最新月份</span>
                <span className="v">{last.ym}</span>
              </div>
              <div className="fund-stat-bar-item">
                <span className="k">單月營收</span>
                <span className="v" style={{color:'var(--ink)'}}>{last.rev} <small style={{fontSize:13,fontWeight:400}}>億</small></span>
              </div>
              {q3Chg != null && (
                <div className="fund-stat-bar-item">
                  <span className="k">近 3M vs 前 3M</span>
                  <span className="v" style={{color: q3Chg >= 0 ? 'var(--bull)' : 'var(--bear)'}}>
                    {q3Chg >= 0 ? '+' : ''}{q3Chg}%
                  </span>
                </div>
              )}
              {revYoy12m != null && (
                <div className="fund-stat-bar-item">
                  <span className="k">近 12M YoY</span>
                  <span className="v" style={{color: revYoy12m >= 0 ? 'var(--bull)' : 'var(--bear)'}}>
                    {revYoy12m >= 0 ? '+' : ''}{revYoy12m}%
                  </span>
                </div>
              )}
            </div>
          );
        })()}
      </section>

      {/* ── 3. 季度損益 ───────────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">03</span>
          <div className="fund-section-title-wrap">
            <h2>季度損益</h2>
            <span className="sec-en">Quarterly P&amp;L</span><SecNote id="income"/>
          </div>
          {fins.length > 0 && <span className="sec-badge">近 {fins.length} 季</span>}
        </div>
        {fins.length === 0 ? (
          <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
            <div style={{fontSize:24, marginBottom:8}}>📊</div>
            <div>{loading ? '季損益資料載入中…' : '暫無季損益資料'}</div>
            <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
          </div>
        ) : (
          <>
            <div className="fund-two-col" style={{marginBottom:16}}>
              <QuarterlyChart quarters={fins} field="Revenue"         label="營業收入(億)" color="#1a2438"/>
              <QuarterlyChart quarters={fins} field="OperatingIncome" label="營業利益(億)" color="#b8924d"/>
              <QuarterlyChart quarters={fins} field="NetIncome"       label="本期淨利(億)" color="#4a7a4e"/>
              <EPSChart quarters={fins}/>
            </div>
            <div className="fund-table-wrap">
              <table className="fund-table">
                <thead>
                  <tr>
                    <th style={{textAlign:'left'}}>項目</th>
                    {fins.map((q, i) => {
                      const d = q.date || '';
                      const qLabel = d.slice(0,4) + 'Q' + (Math.ceil(parseInt(d.slice(5,7))/3));
                      return <th key={i}>{qLabel}</th>;
                    })}
                  </tr>
                </thead>
                <tbody>
                  {FIN_FIELDS.map(f => (
                    <tr key={f.key} className={f.key === 'EPS' ? 'highlight' : ''}>
                      <td>{f.label}</td>
                      {fins.map((q, i) => {
                        const v = q[f.key] || null;
                        const prev = fins[i - 1];
                        const trend = prev && prev[f.key] != null && v != null
                          ? (v > prev[f.key] ? 'trend-up' : v < prev[f.key] ? 'trend-dn' : '')
                          : '';
                        return (
                          <td key={i} className={`${trend} ${v != null && v >= 0 ? '' : 'neg'}`}>
                            {v != null ? (f.unit === '元' ? v.toFixed(2) : (v/1e8).toFixed(2)) : '—'}
                          </td>
                        );
                      })}
                    </tr>
                  ))}
                  <tr>
                    <td>毛利率</td>
                    {fins.map((q, i) => {
                      const gm = grossMargin(q);
                      return <td key={i} className={gm != null ? (gm >= 40 ? 'pos' : gm >= 20 ? '' : 'neg') : ''}>
                        {gm != null ? gm + '%' : '—'}
                      </td>;
                    })}
                  </tr>
                </tbody>
              </table>
            </div>
          </>
        )}
      </section>

      {/* ── 4. 資產負債摘要 ───────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">04</span>
          <div className="fund-section-title-wrap">
            <h2>資產負債摘要</h2>
            <span className="sec-en">Balance Sheet</span><SecNote id="balance"/>
          </div>
        </div>
        {bsData.length === 0 ? (
          <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
            <div style={{fontSize:24, marginBottom:8}}>📊</div>
            <div>{loading ? '資產負債資料載入中…' : '暫無資產負債資料'}</div>
            <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>資料來源：FinMind / TWSE</div>
          </div>
        ) : (
          <>
            <div className="fund-stat-bar" style={{marginBottom:16}}>
              {totalAssets > 0 && <div className="fund-stat-bar-item"><span className="k">Total Assets</span><span className="v">{(totalAssets/1e8).toFixed(0)}<small style={{fontSize:13,fontWeight:400}}> 億</small></span></div>}
              {totalEquity> 0 && <div className="fund-stat-bar-item"><span className="k">Equity</span><span className="v">{(totalEquity/1e8).toFixed(0)}<small style={{fontSize:13,fontWeight:400}}> 億</small></span></div>}
              {debtEquity  != null && <div className="fund-stat-bar-item"><span className="k">D/E Ratio</span><span className="v">{debtEquity}<small style={{fontSize:13,fontWeight:400}}>x</small></span></div>}
              {currentRatio!= null && <div className="fund-stat-bar-item"><span className="k">Current Ratio</span><span className="v">{currentRatio}<small style={{fontSize:13,fontWeight:400}}>x</small></span></div>}
              {roe != null && <div className="fund-stat-bar-item"><span className="k">ROE 年化</span><span className="v" style={{color: roe >= 15 ? 'var(--bull)' : 'var(--ink)'}}>{roe}<small style={{fontSize:13,fontWeight:400}}>%</small></span></div>}
            </div>
            <div className="fund-table-wrap">
              <table className="fund-table">
                <thead>
                  <tr>
                    <th style={{textAlign:'left'}}>項目</th>
                    {bsData.slice(-6).map((q, i) => {
                      const d = q.date || '';
                      return <th key={i}>{d.slice(0,7)}</th>;
                    })}
                  </tr>
                </thead>
                <tbody>
                  {[
                    { key:'TotalAssets',         label:'資產總計' },
                    { key:'CurrentAssets',        label:'流動資產' },
                    { key:'StockholdersEquity',   label:'股東權益' },
                    { key:'TotalLiabilities',     label:'負債總計' },
                    { key:'CurrentLiabilities',   label:'流動負債' },
                  ].map(f => (
                    <tr key={f.key}>
                      <td>{f.label}</td>
                      {bsData.slice(-6).map((q, i) => {
                        const v = q[f.key];
                        return <td key={i}>{v != null ? (v/1e8).toFixed(0)+'億' : '—'}</td>;
                      })}
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </>
        )}
      </section>

      {/* ── 5. 合理估算 ───────────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">05</span>
          <div className="fund-section-title-wrap">
            <h2>機構估值 · 分析師共識</h2>
            <span className="sec-en">Institutional Valuation · Analyst Consensus</span><SecNote id="analyst"/>
          </div>
          <span className="sec-badge" style={{background:'var(--paper-line)',color:'var(--ink-mute)'}}>FMP / TradingView · 僅供參考</span>
        </div>

        {analystData ? (
          // ── 有機構分析師資料（FMP）或 ADR 含意報價 ─────────
          <div>
            {/* 來源說明欄 */}
            <div style={{
              display:'flex', flexWrap:'wrap', gap:12, alignItems:'center',
              padding:'10px 16px', background:'var(--paper)', border:'1px solid var(--paper-line)',
              marginBottom:16, fontSize: 'var(--fs-xs)',
            }}>
              {analystData.source === 'tradingview' ? (<>
                <span style={{fontFamily:'Times New Roman,serif', fontStyle:'italic', color:'var(--gold-deep)', fontSize:13, fontWeight:600}}>
                  {analystData.symbol}
                </span>
                <span style={{color:'var(--ink-mute)'}}>台幣報價 · TradingView 機構共識</span>
                {analystData.analysts && (
                  <span style={{marginLeft:'auto', color:'var(--ink-mute)'}}>
                    <b style={{color:'var(--ink)'}}>{analystData.analysts}</b> 位機構分析師
                  </span>
                )}
              </>) : analystData.source === 'fmp_tw' ? (<>
                <span style={{fontFamily:'Times New Roman,serif', fontStyle:'italic', color:'var(--gold-deep)', fontSize:13, fontWeight:600}}>
                  {analystData.symbol}
                </span>
                <span style={{color:'var(--ink-mute)'}}>台幣報價 · Financial Modeling Prep</span>
              </>) : analystData.source === 'sa' ? (<>
                <span style={{fontFamily:'Times New Roman,serif', fontStyle:'italic', color:'var(--gold-deep)', fontSize:13, fontWeight:600}}>
                  {analystData.adr}
                </span>
                <span style={{color:'var(--ink-mute)'}}>美股 ADR · {analystData.note}</span>
                <span style={{marginLeft:'auto', color:'var(--ink-mute)'}}>匯率 {analystData.usdtwd} · Stock Analysis / S&P Global</span>
              </>) : analystData.source === 'adr_implied' ? (<>
                <span style={{fontFamily:'Times New Roman,serif', fontStyle:'italic', color:'var(--gold-deep)', fontSize:13, fontWeight:600}}>
                  {analystData.adr}
                </span>
                <span style={{color:'var(--ink-mute)'}}>美股 ADR 即時含意 · {analystData.note}</span>
                <span style={{marginLeft:'auto', color:'var(--ink-mute)'}}>匯率 {analystData.usdtwd} USD/TWD</span>
              </>) : (<>
                <span style={{fontFamily:'Times New Roman,serif', fontStyle:'italic', color:'var(--gold-deep)', fontSize:13, fontWeight:600}}>
                  {analystData.adr}
                </span>
                <span style={{color:'var(--ink-mute)'}}>美股 ADR · {analystData.note}</span>
              </>)}
              {analystData.source !== 'adr_implied' && analystData.source !== 'sa' && analystData.summary?.allTimeCount > 0 && (
                <span style={{marginLeft:'auto', color:'var(--ink-mute)'}}>
                  累計 <b style={{color:'var(--ink)'}}>{analystData.summary.allTimeCount}</b> 位機構分析師
                </span>
              )}
              {analystData.source === 'sa' && analystData.numAnalysts && (
                <span style={{color:'var(--ink-mute)'}}>
                  累計 <b style={{color:'var(--ink)'}}>{analystData.numAnalysts}</b> 位機構分析師
                </span>
              )}
            </div>

            {/* TradingView 共識目標價卡片（上市櫃全覆蓋）*/}
            {analystData.source === 'tradingview' && (() => {
              const d = analystData;
              const cur = rt?.price || d.tvClose || 0;
              const up = cur > 0 && d.targetAvg > 0 ? +(((d.targetAvg - cur) / cur) * 100).toFixed(1) : null;
              const rec = d.recMark == null ? null
                : d.recMark < 1.6 ? { t: '強力買進', c: 'var(--bull)' }
                : d.recMark < 2.6 ? { t: '買進', c: 'var(--bull)' }
                : d.recMark < 3.4 ? { t: '持有', c: 'var(--gold-deep)' }
                : d.recMark < 4.2 ? { t: '賣出', c: 'var(--bear)' }
                : { t: '強力賣出', c: 'var(--bear)' };
              const lo = d.targetLow, hi = d.targetHigh;
              const pct = v => (lo != null && hi != null && hi > lo) ? Math.max(0, Math.min(100, (v - lo) / (hi - lo) * 100)) : null;
              return (<>
                <div className="fund-valuation">
                  <div className="fund-val-card base" data-letter="¤">
                    <div className="fund-val-method">Current Price</div>
                    <div className="fund-val-label">現 價</div>
                    <div className="fund-val-price" style={{fontSize:26}}>{cur > 0 ? cur.toLocaleString() : '—'}</div>
                    <div className="fund-val-basis">TWSE/TPEx 即時</div>
                  </div>
                  <div className={`fund-val-card ${up != null && up >= 0 ? 'bull' : 'bear'}`} data-letter="◎">
                    <div className="fund-val-method">Consensus Target</div>
                    <div className="fund-val-label">共 識 目 標 均 價</div>
                    <div className="fund-val-price" style={{fontSize:30,fontWeight:800}}>{d.targetAvg?.toLocaleString() ?? '—'}</div>
                    <div className={`fund-val-upside ${up != null ? (up >= 0 ? 'pos' : 'neg') : ''}`}>
                      {up != null ? `${up >= 0 ? '+' : ''}${up}% 上檔空間` : ''}
                    </div>
                    <div className="fund-val-basis">{d.analysts ? `${d.analysts} 位分析師均值` : '機構共識'}</div>
                  </div>
                  <div className="fund-val-card" data-letter="★">
                    <div className="fund-val-method">Analyst Rating</div>
                    <div className="fund-val-label">機 構 評 級</div>
                    <div className="fund-val-price" style={{fontSize:24, color: rec ? rec.c : 'var(--ink)'}}>{rec ? rec.t : '—'}</div>
                    <div className="fund-val-basis">{d.recMark != null ? `評級均值 ${d.recMark}（1=強買 5=強賣）` : ''}</div>
                  </div>
                </div>
                {lo != null && hi != null && hi > lo && (
                  <div style={{margin:'14px 4px 4px', padding:'12px 16px', border:'1px solid var(--paper-line)', background:'var(--paper)'}}>
                    <div style={{display:'flex', justifyContent:'space-between', fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginBottom:6}}>
                      <span>最低目標 <b className="num" style={{color:'var(--bear)'}}>{lo.toLocaleString()}</b></span>
                      <span className="kai" style={{letterSpacing:'.1em'}}>分析師目標價區間</span>
                      <span>最高目標 <b className="num" style={{color:'var(--bull)'}}>{hi.toLocaleString()}</b></span>
                    </div>
                    <div style={{position:'relative', height:8, background:'linear-gradient(90deg, rgba(229,57,53,.18), rgba(184,146,77,.18), rgba(74,166,84,.18))'}}>
                      {pct(d.targetAvg) != null && (
                        <span style={{position:'absolute', left:`${pct(d.targetAvg)}%`, top:-3, width:2, height:14, background:'var(--gold-deep)'}} title={`均價 ${d.targetAvg}`}/>
                      )}
                      {cur > 0 && pct(cur) != null && (
                        <span style={{position:'absolute', left:`${pct(cur)}%`, top:-5, transform:'translateX(-50%)', width:0, height:0,
                          borderLeft:'5px solid transparent', borderRight:'5px solid transparent', borderTop:'7px solid var(--ink)'}} title={`現價 ${cur}`}/>
                      )}
                    </div>
                    <div style={{display:'flex', justifyContent:'space-between', fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginTop:5}}>
                      <span>▾ 墨色＝現價位置</span>
                      <span>金線＝共識均價</span>
                    </div>
                  </div>
                )}
              </>);
            })()}

            {/* ADR 即時含意卡片（無 FMP 分析師資料的 ADR 股）*/}
            {analystData.source === 'adr_implied' && (() => {
              const d = analystData;
              const cur = rt?.price || 0;
              const diff = cur > 0 && d.impliedTWD > 0 ? +(((d.impliedTWD - cur) / cur) * 100).toFixed(1) : null;
              const high52twd = d.high52 ? Math.round(d.high52 * d.usdtwd / d.ratio) : null;
              const low52twd  = d.low52  ? Math.round(d.low52  * d.usdtwd / d.ratio) : null;
              return (<>
                <div className="fund-valuation">
                  <div className="fund-val-card base" data-letter="$">
                    <div className="fund-val-method">ADR Current Price</div>
                    <div className="fund-val-label">A D R 現 價</div>
                    <div className="fund-val-price" style={{fontSize:26}}>${d.adrPrice?.toFixed(2) ?? '—'}</div>
                    <div className={`fund-val-upside ${d.chgPct != null ? (d.chgPct >= 0 ? 'pos' : 'neg') : ''}`}>
                      {d.chgPct != null ? `${d.chgPct >= 0 ? '+' : ''}${d.chgPct}% 今日` : 'USD'}
                    </div>
                    <div className="fund-val-basis">NYSE/OTC 即時報價</div>
                  </div>
                  <div className={`fund-val-card ${diff != null && diff >= 0 ? 'bull' : 'bear'}`} data-letter="≈">
                    <div className="fund-val-method">Implied TWD Price</div>
                    <div className="fund-val-label">換 算 台 幣</div>
                    <div className="fund-val-price" style={{fontSize:30,fontWeight:800}}>{d.impliedTWD?.toLocaleString() ?? '—'}</div>
                    <div className={`fund-val-upside ${diff != null ? (diff >= 0 ? 'pos' : 'neg') : ''}`}>
                      {diff != null ? `${diff >= 0 ? '↑' : '↓'} ${Math.abs(diff)}% vs 現價` : ''}
                    </div>
                    <div className="fund-val-basis">${d.adrPrice} × {d.usdtwd} ÷ {d.ratio}</div>
                  </div>
                  {high52twd && low52twd && (
                    <div className="fund-val-card bear" data-letter="52">
                      <div className="fund-val-method">52-Week Range (Implied)</div>
                      <div className="fund-val-label">52 週 區 間</div>
                      <div className="fund-val-price" style={{fontSize:20,fontWeight:700}}>
                        {low52twd.toLocaleString()} – {high52twd.toLocaleString()}
                      </div>
                      <div className="fund-val-upside" style={{color:'var(--ink-mute)'}}>換算台幣區間</div>
                      <div className="fund-val-basis">${d.low52?.toFixed(2)} – ${d.high52?.toFixed(2)} USD</div>
                    </div>
                  )}
                </div>
                <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:12,fontFamily:'BiauKai,標楷體, serif',lineHeight:1.8}}>
                  ※ ADR 含意台幣 = {d.adr} USD 現價 × {d.usdtwd} ÷ {d.ratio}（{d.note}）。ADR 溢折價因流動性、匯率差異存在，台幣數字僅供參考。本工具不構成投資建議。
                </div>
              </>);
            })()}

            {/* 目標價三欄（FMP + SA 共用，adr_implied 除外）*/}
            {analystData.source !== 'adr_implied' && analystData.summary && (() => {
              const s = analystData.summary;
              const cur = rt?.price || 0;
              const upPct = (twd) => cur > 0 && twd > 0 ? +(((twd - cur) / cur) * 100).toFixed(1) : null;
              const qPct  = upPct(s.lastQtrTargetTWD);
              const yPct  = upPct(s.lastYrTargetTWD);
              const isFmpTw = analystData.source === 'fmp_tw';
              const isSa    = analystData.source === 'sa';
              const adr     = analystData.adr || '';
              const fxRate  = analystData.usdtwd;
              const ratio   = analystData.ratio;
              const adrFormula = (usdVal) => usdVal != null
                ? `${adr} $${usdVal.toFixed(0)} USD × ${fxRate} ÷ ${ratio}股`
                : null;
              // SA 子標籤：保守(low) / 共識(consensus) / 樂觀(high)
              const card1Method = isSa ? 'Low Target · Conservative'   : 'All-time Avg · Reference';
              const card1Label  = isSa ? '保 守 目 標'                  : '歷 史 均 值';
              const card1Sub    = isSa ? `保守目標 · ${s.allTimeCount} 位分析師` : `累計 ${s.allTimeCount} 份報告均值`;
              const card2Method = isSa ? 'Consensus · All Analysts'    : 'Last 12M · Consensus';
              const card2Label  = isSa ? '共 識 均 值'                  : '近 一 年 共 識';
              const card3Method = isSa ? 'High Target · Upside'        : 'Latest Quarter · Target';
              const card3Label  = isSa ? '樂 觀 目 標'                  : '最 新 目 標 價';
              return (
                <div>
                  {/* 評等 + 分析師分布（僅 SA）*/}
                  {isSa && analystData.saRating && (
                    <div style={{display:'flex',flexWrap:'wrap',gap:12,alignItems:'center',padding:'8px 14px',background:'var(--paper)',border:'1px solid var(--paper-line)',borderRadius:2,marginBottom:12,fontSize: 'var(--fs-xs)'}}>
                      {(() => {
                        const ratingColor = {'Strong Buy':'var(--bull)','Buy':'var(--bull)','Hold':'var(--gold)','Sell':'var(--bear)','Strong Sell':'var(--bear)'}[analystData.saRating]||'var(--ink)';
                        const c = analystData.counts;
                        return (<>
                          <span style={{fontFamily:'Times New Roman,serif',fontStyle:'italic',color:'var(--ink-mute)',fontSize: 'var(--fs-xs)',letterSpacing:'.05em'}}>Analyst Consensus</span>
                          <span style={{fontWeight:800,fontSize:14,color:ratingColor}}>{analystData.saRating}</span>
                          {c && <span style={{color:'var(--ink-mute)'}}>▲{c.strongBuy+c.buy} 買 · {c.hold} 持 · ▼{c.sell+c.strongSell} 賣</span>}
                          <span style={{marginLeft:'auto',color:'var(--ink-mute)'}}>{analystData.numAnalysts} 位 · S&amp;P Global</span>
                        </>);
                      })()}
                    </div>
                  )}
                  <div className="fund-valuation">
                    <div className="fund-val-card bear" data-letter="H">
                      <div className="fund-val-method">{card1Method}</div>
                      <div className="fund-val-label">{card1Label}</div>
                      <div className="fund-val-price" style={{fontSize:22}}>
                        {s.allTimeTargetTWD?.toLocaleString() ?? '—'}
                      </div>
                      <div className="fund-val-upside" style={{color:'var(--ink-mute)'}}>
                        {card1Sub}
                      </div>
                      {isFmpTw ? (
                        <div className="fund-val-basis" style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>台幣直接目標價 · {analystData.symbol}</div>
                      ) : (
                        <div className="fund-val-basis" style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>
                          {adrFormula(s.allTimeTargetUSD) ?? '—'}
                          <br/>≈ NT${s.allTimeTargetTWD?.toLocaleString()}
                        </div>
                      )}
                    </div>
                    <div className="fund-val-card base" data-letter="Y">
                      <div className="fund-val-method">{card2Method}</div>
                      <div className="fund-val-label">{card2Label}</div>
                      <div className="fund-val-price" style={{fontSize:26}}>
                        {s.lastYrTargetTWD?.toLocaleString() ?? '—'}
                      </div>
                      <div className={`fund-val-upside ${yPct != null ? (yPct >= 0 ? 'pos' : 'neg') : ''}`}>
                        {yPct != null ? `${yPct >= 0 ? '↑' : '↓'} ${Math.abs(yPct)}% vs 現價` : (isSa ? '分析師均值' : '近 12 個月均值')}
                      </div>
                      {isFmpTw ? (
                        <div className="fund-val-basis" style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>台幣直接目標價 · {analystData.symbol}</div>
                      ) : (
                        <div className="fund-val-basis" style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>
                          {adrFormula(s.lastYrTargetUSD) ?? `$${s.lastYrTargetUSD?.toFixed(0)} USD`}
                          <br/>≈ NT${s.lastYrTargetTWD?.toLocaleString()}
                        </div>
                      )}
                    </div>
                    <div className="fund-val-card bull" data-letter="↑">
                      <div className="fund-val-method">{card3Method}</div>
                      <div className="fund-val-label">{card3Label}</div>
                      <div className="fund-val-price" style={{fontSize:30, fontWeight:800}}>
                        {s.lastQtrTargetTWD ? s.lastQtrTargetTWD.toLocaleString() : '—'}
                      </div>
                      <div className={`fund-val-upside ${qPct != null ? (qPct >= 0 ? 'pos' : 'neg') : ''}`}
                           style={{fontSize:15, fontWeight:700}}>
                        {qPct != null
                          ? `${qPct >= 0 ? '↑' : '↓'} ${Math.abs(qPct)}% 上漲空間`
                          : (isSa ? '樂觀目標' : `最新 ${s.lastQtrCount} 份機構共識`)}
                      </div>
                      {isFmpTw ? (
                        <div className="fund-val-basis" style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>台幣直接目標價 · {s.lastQtrCount} 份報告</div>
                      ) : (
                        <div className="fund-val-basis" style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)'}}>
                          {adrFormula(s.lastQtrTargetUSD) ?? `$${s.lastQtrTargetUSD?.toFixed(0)} USD`}
                          <br/>≈ NT${s.lastQtrTargetTWD?.toLocaleString()}{!isSa ? ` · ${s.lastQtrCount} 份報告` : ''}
                        </div>
                      )}
                    </div>
                  </div>
                  {isSa && (
                    <div style={{fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginTop:12, fontFamily:'BiauKai,標楷體, serif', lineHeight:1.8}}>
                      ※ 資料來源：Stock Analysis（S&P Global / CapIQ）。保守 = 最低目標價，共識 = 分析師均值，樂觀 = 最高目標價。台幣換算：{adr} USD × {fxRate} ÷ {ratio}（{analystData.note}）。ADR 與本地股存在溢價差異，台幣數字僅供參考。本工具不構成投資建議。
                    </div>
                  )}
                </div>
              );
            })()}

            {/* EPS 預估表 */}
            {analystData.source !== 'adr_implied' && analystData.source !== 'sa' && analystData.estimates?.length > 0 && (
              <div style={{marginTop:16}}>
                <div style={{
                  fontFamily:'Times New Roman,serif', fontStyle:'italic',
                  fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginBottom:8, letterSpacing:'.05em',
                }}>
                  分析師 EPS 預估（{analystData.adr} ADR，USD；匯率 {analystData.usdtwd}）
                </div>
                <div className="fund-table-wrap">
                  <table className="fund-table" style={{tableLayout:'fixed'}}>
                    <colgroup>
                      <col style={{width:'20%'}}/><col/><col/><col/><col style={{width:'16%'}}/>
                    </colgroup>
                    <thead>
                      <tr>
                        <th>年度</th>
                        <th>EPS 保守</th>
                        <th>EPS 共識</th>
                        <th>EPS 樂觀</th>
                        <th>分析師數</th>
                      </tr>
                    </thead>
                    <tbody>
                      {analystData.estimates.map(e => (
                        <tr key={e.year}>
                          <td style={{fontFamily:'Times New Roman,serif', fontStyle:'italic'}}>{e.year}</td>
                          <td style={{color:'var(--bear)'}}>{e.epsLow != null ? `$${e.epsLow}` : '—'}</td>
                          <td style={{fontWeight:700, color:'var(--ink)'}}>{e.epsAvg != null ? `$${e.epsAvg}` : '—'}</td>
                          <td style={{color:'var(--bull)'}}>{e.epsHigh != null ? `$${e.epsHigh}` : '—'}</td>
                          <td style={{color:'var(--ink-mute)', textAlign:'center'}}>{e.numAnalysts || '—'}</td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </div>
            )}

            {/* ─── EPS 驚喜記錄 ───────────────────────────── */}
            {analystData.source !== 'adr_implied' && analystData.source !== 'sa' && earningsData.length > 0 && (
              <div style={{marginTop:20}}>
                <div style={{
                  fontFamily:'Times New Roman,serif', fontStyle:'italic',
                  fontSize:13, color:'var(--gold-deep)', marginBottom:10, letterSpacing:'.05em', fontWeight:600,
                }}>
                  EPS Surprise · 實際 vs 預估（{analystData.adr} ADR，USD）
                </div>
                <div className="fund-table-wrap">
                  <table className="fund-table" style={{tableLayout:'fixed'}}>
                    <colgroup>
                      <col style={{width:'22%'}}/><col style={{width:'22%'}}/><col style={{width:'22%'}}/><col/>
                    </colgroup>
                    <thead>
                      <tr>
                        <th style={{textAlign:'left'}}>季度</th>
                        <th>預估 EPS</th>
                        <th>實際 EPS</th>
                        <th>驚喜率</th>
                      </tr>
                    </thead>
                    <tbody>
                      {earningsData.map((e, i) => {
                        const est  = e.epsEstimated ?? e.estimatedEps ?? null;
                        const act  = e.epsActual    ?? e.actualEps    ?? null;
                        const surp = (est != null && act != null && est !== 0)
                          ? +(((act - est) / Math.abs(est)) * 100).toFixed(1)
                          : null;
                        const beat = surp != null && surp >= 0;
                        // 季度標籤：FMP 回傳 date "2025-09-30" → 2025 Q3
                        const d = e.date || e.period || '';
                        const yr = d.slice(0, 4);
                        const mo = parseInt(d.slice(5, 7) || '0');
                        const qn = mo > 0 ? 'Q' + Math.ceil(mo / 3) : '';
                        const label = yr && qn ? `${yr} ${qn}` : d.slice(0, 7) || `#${i+1}`;
                        return (
                          <tr key={i}>
                            <td style={{fontFamily:'Times New Roman,serif', fontStyle:'italic'}}>{label}</td>
                            <td style={{color:'var(--ink-mute)'}}>
                              {est != null ? `$${est.toFixed(2)}` : '—'}
                            </td>
                            <td style={{fontWeight:700, color: act != null ? (beat ? 'var(--bull)' : 'var(--bear)') : 'var(--ink)'}}>
                              {act != null ? `$${act.toFixed(2)}` : '—'}
                            </td>
                            <td style={{
                              fontWeight:700,
                              color: surp != null ? (beat ? 'var(--bull)' : 'var(--bear)') : 'var(--ink-mute)',
                            }}>
                              {surp != null
                                ? <>{beat ? '▲' : '▼'} {beat ? '+' : ''}{surp}%</>
                                : '—'}
                            </td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
                <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:6,fontStyle:'italic'}}>
                  * Beat（綠）= 實際 EPS 超出預估；Miss（紅）= 未達預估。來源：FMP earnings-surprises。
                </div>
              </div>
            )}

            {/* 分析師評等升降紀錄 */}
            {analystData.source !== 'adr_implied' && analystData.source !== 'sa' && analystData.grades?.length > 0 && (
              <div style={{marginTop:14}}>
                <div style={{fontFamily:'Times New Roman,serif',fontStyle:'italic',fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginBottom:8,letterSpacing:'.05em'}}>
                  Analyst Rating Changes · 評等異動（近 5 筆）
                </div>
                <div className="fund-table-wrap">
                  <table className="fund-table">
                    <thead><tr><th>日期</th><th>機構</th><th>動作</th><th>前</th><th>後</th></tr></thead>
                    <tbody>
                      {analystData.grades.map((g, i) => {
                        const isUp = g.action?.toLowerCase().includes('upgrade') || g.action?.toLowerCase().includes('init');
                        const isDn = g.action?.toLowerCase().includes('downgrade');
                        return (
                          <tr key={i}>
                            <td style={{fontFamily:'Times New Roman,serif',fontStyle:'italic',color:'var(--ink-mute)'}}>{g.date}</td>
                            <td style={{fontWeight:600}}>{g.firm}</td>
                            <td style={{color: isUp?'var(--bull)': isDn?'var(--bear)':'var(--ink-soft)', fontWeight:700}}>
                              {isUp ? '↑ ' : isDn ? '↓ ' : ''}{g.action}
                            </td>
                            <td style={{color:'var(--ink-mute)'}}>{g.from || '—'}</td>
                            <td style={{fontWeight:700}}>{g.to || '—'}</td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
              </div>
            )}

            {analystData.source !== 'adr_implied' && analystData.source !== 'sa' && (
              <div style={{fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginTop:12, fontFamily:'BiauKai,標楷體, serif', lineHeight:1.8}}>
                {analystData.source === 'fmp_tw'
                  ? `※ 資料來源：Financial Modeling Prep（FMP）機構分析師共識。目標價以台幣計價，直接取自 ${analystData.symbol} 報告。本工具不構成投資建議。`
                  : `※ 資料來源：Financial Modeling Prep（FMP）機構分析師共識。台幣目標價換算公式：${analystData.adr} USD 目標 × ${analystData.usdtwd} ÷ ${analystData.ratio}（${analystData.note}）。ADR 與本地股存在溢價差異，台幣數字僅供參考。本工具不構成投資建議。`
                }
              </div>
            )}
          </div>
        ) : !fv ? (
          <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
            <div style={{fontSize:24, marginBottom:8}}>📊</div>
            <div>暫無估值資料</div>
            <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>需有 4 季以上 EPS 記錄才能估算。資料來源：FinMind / TWSE</div>
          </div>
        ) : (
          // ── 三方法內在價值估算（無 ADR 的一般股）──────────────
          <>
            {/* 數據摘要列 */}
            <div style={{display:'flex',flexWrap:'wrap',gap:16,alignItems:'center',padding:'10px 16px',background:'var(--paper)',border:'1px solid var(--paper-line)',marginBottom:16}}>
              <div className="fund-kv"><span className="k">TTM EPS</span><span className="v">{fv.ttmEps} 元</span></div>
              {fv.impliedPe   && <div className="fund-kv"><span className="k">現價隱含 P/E</span><span className="v" style={{fontWeight:700}}>{fv.impliedPe}x</span></div>}
              {fv.bps         && <div className="fund-kv"><span className="k">BPS（帳面/股）</span><span className="v">{fv.bps} 元</span></div>}
              {fv.epsGrowthPct != null && <div className="fund-kv"><span className="k">EPS YoY 成長</span><span className="v" style={{color:fv.epsGrowthPct>=0?'var(--bull)':'var(--bear)'}}>{fv.epsGrowthPct >= 0 ? '+' : ''}{fv.epsGrowthPct}%</span></div>}
              {fv.divY > 0    && <div className="fund-kv"><span className="k">殖利率</span><span className="v">{fv.divY.toFixed(2)}%</span></div>}
              {fv.fcfPerShare  && <div className="fund-kv"><span className="k">TTM FCF/股</span><span className="v">{fv.fcfPerShare} 元</span></div>}
            </div>
            {/* P/E 與 EPS YoY 評分列 */}
            {(fv.peScore != null || fv.epsYoyScore != null) && (
              <div style={{display:'flex',flexWrap:'wrap',gap:10,marginBottom:14}}>
                {fv.peScore != null && (() => {
                  const s = fv.peScore;
                  const col = s >= 2 ? 'var(--bull)' : s === 1 ? 'var(--gold)' : s === 0 ? 'var(--ink-mute)' : 'var(--bear)';
                  const badge = s >= 2 ? '+2' : s === 1 ? '+1' : s === 0 ? '0' : '-1';
                  return (
                    <div style={{display:'flex',alignItems:'center',gap:8,padding:'6px 12px',background:'var(--paper)',border:'1px solid var(--paper-line)',flex:'1 1 auto',minWidth:180}}>
                      <div style={{fontFamily:'Times New Roman,serif',fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',letterSpacing:'.06em',fontStyle:'italic',minWidth:80}}>P/E 估值</div>
                      <div style={{fontFamily:'Times New Roman,serif',fontSize:13,color:'var(--ink)',flex:1}}>{fv.peScoreLabel}</div>
                      <div style={{fontFamily:'Times New Roman,serif',fontSize:15,fontWeight:700,color:col,minWidth:24,textAlign:'right'}}>{badge}</div>
                    </div>
                  );
                })()}
                {fv.epsYoyScore != null && (() => {
                  const s = fv.epsYoyScore;
                  const col = s >= 2 ? 'var(--bull)' : s === 1 ? 'var(--gold)' : s === 0 ? 'var(--ink-mute)' : 'var(--bear)';
                  const badge = s >= 2 ? '+2' : s === 1 ? '+1' : s === 0 ? '0' : '-1';
                  return (
                    <div style={{display:'flex',alignItems:'center',gap:8,padding:'6px 12px',background:'var(--paper)',border:'1px solid var(--paper-line)',flex:'1 1 auto',minWidth:180}}>
                      <div style={{fontFamily:'Times New Roman,serif',fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',letterSpacing:'.06em',fontStyle:'italic',minWidth:80}}>EPS YoY</div>
                      <div style={{fontFamily:'Times New Roman,serif',fontSize:13,color:'var(--ink)',flex:1}}>{fv.epsYoyLabel}</div>
                      <div style={{fontFamily:'Times New Roman,serif',fontSize:15,fontWeight:700,color:col,minWidth:24,textAlign:'right'}}>{badge}</div>
                    </div>
                  );
                })()}
              </div>
            )}
            {/* 三方法估值卡片 */}
            <div className="fund-valuation">
              {/* Graham Number */}
              <div className="fund-val-card bear" data-letter="G">
                <div className="fund-val-method">Benjamin Graham</div>
                <div className="fund-val-label">葛 拉 漢 底 限</div>
                <div className="fund-val-price">{fv.graham ? fv.graham.toLocaleString() : '—'}</div>
                <div className={`fund-val-upside ${fv.grahamUpside >= 0 ? 'pos' : 'neg'}`}>
                  {fv.grahamUpside != null ? `${fv.grahamUpside >= 0 ? '↑' : '↓'} ${Math.abs(fv.grahamUpside)}%` : '資料不足'}
                </div>
                <div className="fund-val-basis">√(22.5 × EPS × BPS)</div>
              </div>
              {/* Lynch PEG */}
              <div className="fund-val-card base" data-letter="L">
                <div className="fund-val-method">Peter Lynch · PEG</div>
                <div className="fund-val-label">成 長 公 允 值</div>
                <div className="fund-val-price">{fv.lynch ? fv.lynch.toLocaleString() : '—'}</div>
                <div className={`fund-val-upside ${fv.lynchUpside >= 0 ? 'pos' : 'neg'}`}>
                  {fv.lynchUpside != null ? `${fv.lynchUpside >= 0 ? '↑' : '↓'} ${Math.abs(fv.lynchUpside)}%` : '成長率資料不足'}
                </div>
                <div className="fund-val-basis">EPS × (成長率{fv.epsGrowthPct != null ? ` ${fv.epsGrowthPct}` : ''}% + 殖利率{fv.divY ? ` ${fv.divY.toFixed(1)}` : ''}%)</div>
              </div>
              {/* DCF */}
              <div className="fund-val-card bull" data-letter="D">
                <div className="fund-val-method">5-Year DCF · r=10%</div>
                <div className="fund-val-label">現 金 流 折 現</div>
                <div className="fund-val-price" style={{fontSize:fv.dcf ? 28 : 20, fontWeight:800}}>{fv.dcf ? fv.dcf.toLocaleString() : '—'}</div>
                <div className={`fund-val-upside ${fv.dcfUpside >= 0 ? 'pos' : 'neg'}`} style={{fontSize:15,fontWeight:700}}>
                  {fv.dcfUpside != null ? `${fv.dcfUpside >= 0 ? '↑' : '↓'} ${Math.abs(fv.dcfUpside)}% 空間` : 'FCF 資料不足'}
                </div>
                <div className="fund-val-basis">FCF 成長{fv.dcfG != null ? ` ${fv.dcfG}` : ''}% · 終值 g=3%</div>
              </div>
              {/* Forward P/E — 最貼近市場定價 */}
              {fv.forwardPe && (
                <div className="fund-val-card base" data-letter="F" style={{borderTop:'2px solid var(--gold)'}}>
                  <div className="fund-val-method">Forward P/E · 市場法</div>
                  <div className="fund-val-label">預 期 本 益 比</div>
                  <div className="fund-val-price" style={{fontSize:28,fontWeight:800}}>{fv.forwardPe.toLocaleString()}</div>
                  <div className={`fund-val-upside ${fv.forwardPeUpside >= 0 ? 'pos' : 'neg'}`} style={{fontSize:15,fontWeight:700}}>
                    {fv.forwardPeUpside != null ? `${fv.forwardPeUpside >= 0 ? '↑' : '↓'} ${Math.abs(fv.forwardPeUpside)}%` : ''}
                  </div>
                  <div className="fund-val-basis">歷史中位 P/E {fv.peMed}x × 預期EPS</div>
                </div>
              )}
            </div>
            {/* 共識中值 */}
            {fv.consensus && (
              <div style={{marginTop:12,padding:'12px 16px',background:'var(--paper)',border:'1px solid var(--ink)',display:'flex',alignItems:'center',gap:16,flexWrap:'wrap'}}>
                <div style={{fontFamily:'Times New Roman,serif',fontStyle:'italic',fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',letterSpacing:'.08em'}}>三方法共識中值</div>
                <div style={{fontFamily:'Times New Roman,serif',fontSize:26,fontWeight:700}}>{fv.consensus.toLocaleString()} <span style={{fontSize:14,color:'var(--ink-mute)'}}>元</span></div>
                {fv.consensusUpside != null && (
                  <div style={{fontFamily:'Times New Roman,serif',fontSize:15,fontWeight:700,color:fv.consensusUpside>=0?'var(--bull)':'var(--bear)'}}>
                    {fv.consensusUpside >= 0 ? '↑' : '↓'} {Math.abs(fv.consensusUpside)}% vs 現價 {fv.currentPrice?.toLocaleString()}
                  </div>
                )}
              </div>
            )}
            <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:10,fontFamily:'BiauKai,標楷體, serif',lineHeight:1.8}}>
              ※ Graham Number = √(22.5 × TTM EPS × BPS)，為深度價值底限；Lynch PEG 以 EPS 年增率加股利殖利率定價；DCF 採 5 年自由現金流折現，折現率 10%、終值成長 3%。三方法均為理論估值，非機構報告，僅供量化參考，不構成投資建議。
            </div>
          </>
        )}
      </section>

      {/* ── 6. 同業排名 ───────────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">06</span>
          <div className="fund-section-title-wrap">
            <h2>同業排名</h2>
            <span className="sec-en">Peer Ranking</span><SecNote id="peers"/>
          </div>
        </div>
        {profile ? (
          <PeerTable code={code} profile={profile} peerPrices={peerPrices} perData={perData} peerBwibbu={peerBwibbu} peerMini={peerMini} />
        ) : (
          <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
            <div style={{fontSize:24, marginBottom:8}}>📊</div>
            <div>暫無同業比較資料</div>
            <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>此代號尚未收錄於企業資料庫。資料來源：FinMind / TWSE</div>
          </div>
        )}
      </section>

      {/* ── 7. 產業鏈 & 台美關係 ─────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">07</span>
          <div className="fund-section-title-wrap">
            <h2>產業鏈位置 &amp; 台美企業關係</h2>
            <span className="sec-en">Supply Chain &amp; US Connections</span><SecNote id="chain"/>
          </div>
        </div>
        {profile ? (
          <SupplyChainMap code={code} profile={profile} />
        ) : (
          <div style={{textAlign:'center', padding:'24px 0', color:'var(--ink-mute)', fontSize:13}}>
            <div style={{fontSize:24, marginBottom:8}}>📊</div>
            <div>暫無產業鏈資料</div>
            <div style={{fontSize: 'var(--fs-xs)', marginTop:4, opacity:.6}}>此代號尚未收錄於企業資料庫。資料來源：FinMind / TWSE</div>
          </div>
        )}
      </section>

      {/* ── 8. 財務比率趨勢 ───────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">08</span>
          <div className="fund-section-title-wrap">
            <h2>財務比率趨勢</h2>
            <span className="sec-en">Financial Ratios</span><SecNote id="ratios"/>
          </div>
          {fins.length > 0 && <span className="sec-badge">近 {fins.length} 季</span>}
        </div>
        {/* 估值家族（2026-07-20 專業化第一波）：EV/EBITDA · PEG · 市值 */}
        <ValuationFamily fins={fins} bsData={bsData} cashFlow={cashFlow} rt={rt} ownership={ownership} pe={pe} />
        {/* Reverse DCF（第二波）：現價隱含成長 vs 實際成長 */}
        <ReverseDCF cashFlow={cashFlow} rt={rt} ownership={ownership} />
        <RatioPanel fins={fins} bsData={bsData} />

        {/* ─── FMP 年度關鍵指標（EV/EBITDA、葛拉漢數字、市值） ── */}
        {fmpMetrics && (() => {
          const m = Array.isArray(fmpMetrics) ? fmpMetrics[0] : fmpMetrics;
          if (!m) return null;
          const evEbitda = m.evToEBITDA != null ? +m.evToEBITDA.toFixed(1) : null;
          const graham   = m.grahamNumber != null ? +m.grahamNumber : null;
          // 葛拉漢數字為 USD（ADR），換算台幣：× usdtwd ÷ ratio
          const ratio   = analystData?.ratio    || 1;
          const usdtwd  = analystData?.usdtwd   || 30;
          const grahamTWD = graham != null ? Math.round(graham * usdtwd / ratio) : null;
          const mktCap  = m.marketCap != null ? m.marketCap : null;
          const mktCapB = mktCap != null ? (mktCap / 1e9).toFixed(0) : null;
          return (
            <div style={{marginTop:20, paddingTop:16, borderTop:'1px dashed var(--paper-line)'}}>
              <div style={{
                fontFamily:'Times New Roman,serif', fontStyle:'italic',
                fontSize: 'var(--fs-xs)', color:'var(--ink-mute)', marginBottom:12, letterSpacing:'.05em',
              }}>
                FMP · 年度資料（{analystData?.adr || adrSymbol}）
              </div>
              <div style={{display:'flex', flexWrap:'wrap', gap:20}}>
                {evEbitda != null && (
                  <div className="fund-kv">
                    <span className="k">EV / EBITDA</span>
                    <span className="v">{evEbitda} <small style={{fontSize: 'var(--fs-xs)',fontWeight:400}}>倍</small></span>
                  </div>
                )}
                {grahamTWD != null && (
                  <div className="fund-kv">
                    <span className="k">葛拉漢數字</span>
                    <span className="v">NT$ {grahamTWD.toLocaleString()}</span>
                  </div>
                )}
                {graham != null && (
                  <div className="fund-kv">
                    <span className="k">葛拉漢（USD）</span>
                    <span className="v">${graham.toFixed(2)}</span>
                  </div>
                )}
                {mktCapB != null && (
                  <div className="fund-kv">
                    <span className="k">市值</span>
                    <span className="v">${mktCapB}B <small style={{fontSize: 'var(--fs-xs)',fontWeight:400}}>USD</small></span>
                  </div>
                )}
              </div>
              <div style={{fontSize: 'var(--fs-xs)',color:'var(--ink-mute)',marginTop:8,fontStyle:'italic'}}>
                * 葛拉漢數字換算：USD × {usdtwd} ÷ {ratio}（{analystData?.note || 'ADR 比例'}）。FMP 年度資料，非即時。
              </div>
            </div>
          );
        })()}
      </section>

      {/* ── 9. 現金流量表 ─────────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">09</span>
          <div className="fund-section-title-wrap">
            <h2>現金流量表</h2>
            <span className="sec-en">Cash Flow Statement</span><SecNote id="cashflow"/>
          </div>
          {cashFlow.length > 0 && <span className="sec-badge">近 {cashFlow.length} 季</span>}
        </div>
        <CashFlowSection quarters={cashFlow} />
        {/* 資本配置（第二波）：CapEx vs 折舊 · FCF 利潤率 · 融資流向 */}
        <CapitalAllocation cashFlow={cashFlow} fins={fins} />
      </section>

      {/* ── 10. 盈餘品質（2026-07-20 專業化第一波）──────── */}
      {fins.length >= 4 && (
        <section className="fund-section">
          <div className="fund-section-head">
            <span className="sec-num">10</span>
            <div className="fund-section-title-wrap">
              <h2>盈餘品質</h2>
              <span className="sec-en">Earnings Quality</span><SecNote id="equality"/>
            </div>
            <span className="sec-badge">含金量 · 應計 · CCC · 股利可持續</span>
          </div>
          <EarningsQualitySection fins={fins} bsData={bsData} cashFlow={cashFlow} dividends={dividends} ownership={ownership} />
        </section>
      )}

      {/* ── 11. 股利記錄 ──────────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">11</span>
          <div className="fund-section-title-wrap">
            <h2>股利記錄</h2>
            <span className="sec-en">Dividend History</span><SecNote id="dividend"/>
          </div>
          {dividends.length > 0 && <span className="sec-badge">近 {dividends.length} 筆</span>}
        </div>
        <DividendSection dividends={dividends} />
      </section>

      {/* ── 12. 法人籌碼 ──────────────────────────────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">12</span>
          <div className="fund-section-title-wrap">
            <h2>法人籌碼</h2>
            <span className="sec-en">Institutional Flow</span><SecNote id="chips"/>
          </div>
          {chipSummary.length > 0 && <span className="sec-badge">近 {chipSummary.length} 日</span>}
        </div>
        <ChipSection chipSummary={chipSummary} />
      </section>

      {/* ── 13. 股權結構（2026-07-20 專業化第一波）──────── */}
      <section className="fund-section">
        <div className="fund-section-head">
          <span className="sec-num">13</span>
          <div className="fund-section-title-wrap">
            <h2>股權結構</h2>
            <span className="sec-en">Ownership Structure</span><SecNote id="ownership"/>
          </div>
          {ownership?.latest && <span className="sec-badge">外資 {ownership.latest.ratio}%</span>}
        </div>
        <OwnershipSection ownership={ownership} ownErr={ownErr} />
      </section>

      {/* ── 14. 企業健診 ──────────────────────────────── */}
      {(fins.length >= 4 || bsData.length >= 2) && (
        <section className="fund-section">
          <div className="fund-section-head">
            <span className="sec-num">14</span>
            <div className="fund-section-title-wrap">
              <h2>企業健診</h2>
              <span className="sec-en">Quality Diagnostics</span><SecNote id="diag"/>
            </div>
            <span className="sec-badge">Piotroski · Altman · Magic Formula</span>
          </div>
          <DiagnosticsSection fins={fins} bsData={bsData} cashFlow={cashFlow} rt={rt} bwibbu={bwibbu} />
        </section>
      )}

      {!embedded && <window.FooterBar />}
    </div>
  );
}

// 暴露給 stock.jsx 的 tab 模式使用
window.FundamentalApp = FundamentalApp;

// 獨立頁面模式：企業基本面.html 用專用容器 id（仿 radar-zone 的 zone-root 模式），
// 避免在個股技術分析.html（同樣有 #root、由 stock.jsx 掛載）重複 createRoot。
if (document.getElementById('fundamental-root')) {
  ReactDOM.createRoot(document.getElementById('fundamental-root')).render(<FundamentalApp />);
}
