// International indices
const INTL_INDICES = [
  { code: 'NDQ',  name: '那斯達克', en: 'NASDAQ',   value: 19586.32, chg: +152.18, pct: +0.78, region: 'us' },
  { code: 'SPX',  name: '標普500',   en: 'S&P 500',  value: 5912.46,  chg: +28.45,  pct: +0.48, region: 'us' },
  { code: 'DJI',  name: '道瓊',     en: 'Dow Jones', value: 42856.4,  chg: -45.12,  pct: -0.11, region: 'us' },
  { code: 'N225', name: '日經225',  en: 'Nikkei',    value: 38492.6,  chg: +185.4,  pct: +0.48, region: 'asia' },
  { code: 'HSI',  name: '恆生',     en: 'Hang Seng', value: 19284.3,  chg: -62.4,   pct: -0.32, region: 'asia' },
  { code: 'KOSPI',name: '韓綜',     en: 'KOSPI',     value: 2624.5,   chg: +18.2,   pct: +0.70, region: 'asia' },
  { code: 'SSE',  name: '上證',     en: 'SSE',       value: 3142.8,   chg: -8.5,    pct: -0.27, region: 'asia' },
  { code: 'SOX',  name: '費半',     en: 'PHLX SOX',  value: 5824.31,  chg: +62.18,  pct: +1.08, region: 'us' },
];

// Institutional top buys/sells
const INST_FLOWS = {
  foreign: {
    buy:  [
      { code:'2330', name:'台積電',   net:'+18,452', val:'+200.2億' },
      { code:'2317', name:'鴻海',     net:'+8,624',  val:'+15.7億' },
      { code:'2382', name:'廣達',     net:'+5,182',  val:'+14.8億' },
      { code:'2454', name:'聯發科',   net:'+3,624',  val:'+50.5億' },
      { code:'6669', name:'緯穎',     net:'+1,248',  val:'+33.4億' },
    ],
    sell: [
      { code:'2603', name:'長榮',     net:'-12,485', val:'-31.0億' },
      { code:'2891', name:'中信金',   net:'-8,624',  val:'-3.3億' },
      { code:'2412', name:'中華電',   net:'-6,182',  val:'-7.9億' },
      { code:'2002', name:'中鋼',     net:'-18,624', val:'-4.2億' },
      { code:'3008', name:'大立光',   net:'-1,248',  val:'-28.0億' },
    ],
  },
  trust: {
    buy: [
      { code:'3017', name:'奇鋐',   net:'+5,824', val:'+68.7億' },
      { code:'2049', name:'上銀',   net:'+3,180', val:'+9.9億' },
      { code:'1519', name:'華城',   net:'+2,654', val:'+10.9億' },
      { code:'3037', name:'欣興',   net:'+4,128', val:'+6.9億' },
      { code:'6669', name:'緯穎',   net:'+852',   val:'+22.8億' },
    ],
    sell: [
      { code:'2881', name:'富邦金', net:'-3,624', val:'-3.5億' },
      { code:'2882', name:'國泰金', net:'-2,180', val:'-1.7億' },
      { code:'2884', name:'玉山金', net:'-1,824', val:'-0.6億' },
    ],
  }
};

// 大盤總覽 Dashboard
const { useState, useEffect, useMemo, useRef } = React;

// ── 數字滾動 + 漲跌閃色(動效升級層 v1)──
function AnimatedPrice({ value, decimals, style }) {
  if (decimals === undefined) decimals = 2;
  var [disp, setDisp]   = useState(value);
  var [flash, setFlash] = useState('');
  var prevRef = useRef(value);
  useEffect(function() {
    var prev = prevRef.current;
    if (prev === value) return;
    prevRef.current = value;
    setFlash(value > prev ? 'px-flash-up' : 'px-flash-dn');
    var t0 = performance.now(), dur = 600, raf;
    function step(t) {
      var p = Math.min((t - t0) / dur, 1);
      var e = 1 - Math.pow(1 - p, 3); // easeOutCubic
      setDisp(prev + (value - prev) * e);
      if (p < 1) raf = requestAnimationFrame(step);
    }
    raf = requestAnimationFrame(step);
    var ft = setTimeout(function() { setFlash(''); }, 1000);
    return function() { cancelAnimationFrame(raf); clearTimeout(ft); };
  }, [value]);
  return (
    <span className={'tabular ' + flash} style={style}>
      {disp.toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals })}
    </span>
  );
}

// 大盤指數區塊「⋯更多」選單：整合即時更新切換/立即刷新/清除瀏覽紀錄,取代原本三顆各自獨立的按鈕。
// 下拉互動樣式沿用 shared.jsx 的 TeamPanel(點外面關閉)。
function QuoteActionsMenu({ liveAuto, liveLoading, onToggleLive, onRefresh, onClearRecent, hasRecent }) {
  var open = useState(false);
  var setOpen = open[1]; open = open[0];
  var ref = useRef(null);

  useEffect(function() {
    if (!open) return;
    function handler(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
    document.addEventListener('mousedown', handler);
    return function() { document.removeEventListener('mousedown', handler); };
  }, [open]);

  function item(fn) {
    return function() { setOpen(false); fn(); };
  }

  return (
    <div style={{ position:'relative', display:'inline-block' }} ref={ref}>
      <button
        onClick={function() { setOpen(function(v) { return !v; }); }}
        data-tip="報價設定"
        style={{
          fontSize:14, padding:'4px 10px', border:'1px solid var(--paper-line)',
          background: open ? 'var(--paper-deep)' : 'transparent', color:'var(--ink-mute)',
          cursor:'pointer', lineHeight:1,
        }}>
        ⋯
      </button>
      {open && (
        <div style={{
          position:'absolute', top:'calc(100% + 6px)', left:0, zIndex:500,
          width:190, background:'var(--paper-bright)', border:'1px solid var(--ink)',
          boxShadow:'4px 4px 0 rgba(26,36,56,.12)',
        }}>
          <button
            onClick={item(onToggleLive)}
            style={{
              display:'block', width:'100%', textAlign:'left', padding:'9px 14px',
              background:'none', border:'none', borderBottom:'1px solid var(--paper-line)',
              cursor:'pointer', fontSize:12, fontFamily:'BiauKai,標楷體,serif', color:'var(--ink)',
            }}>
            {liveAuto ? '⏹ 停止即時更新' : '▶ 開始即時更新 (30s)'}
          </button>
          {!liveAuto && (
            <button
              onClick={item(onRefresh)}
              disabled={liveLoading}
              style={{
                display:'block', width:'100%', textAlign:'left', padding:'9px 14px',
                background:'none', border:'none', borderBottom: hasRecent ? '1px solid var(--paper-line)' : 'none',
                cursor: liveLoading ? 'default' : 'pointer', fontSize:12,
                fontFamily:'BiauKai,標楷體,serif', color: liveLoading ? 'var(--ink-mute)' : 'var(--ink)',
              }}>
              {liveLoading ? '更新中…' : '↻ 立即刷新一次'}
            </button>
          )}
          {hasRecent && (
            <button
              onClick={item(onClearRecent)}
              style={{
                display:'block', width:'100%', textAlign:'left', padding:'9px 14px',
                background:'none', border:'none', cursor:'pointer', fontSize:12,
                fontFamily:'BiauKai,標楷體,serif', color:'var(--ink)',
              }}>
              🗑 清除瀏覽紀錄
            </button>
          )}
        </div>
      )}
    </div>
  );
}

// ============ Mock data ============
const TAIEX_HIST = (() => {
  // 90 days of mock TAIEX history
  const out = [];
  let v = 22800;
  const today = new Date();
  for (let i = 89; i >= 0; i--) {
    const d = new Date(today); d.setDate(d.getDate() - i);
    const noise = (Math.sin(i * 0.21) + Math.cos(i * 0.13) * 0.7) * 80;
    const drift = (90 - i) * 8;
    v = 22800 + drift + noise;
    out.push({ date: d.toISOString().slice(5,10), value: +v.toFixed(2) });
  }
  return out;
})();

const OTC_HIST = TAIEX_HIST.map((d, i) => ({
  date: d.date, value: +(265 + i * 0.18 + Math.sin(i*0.31) * 3.2).toFixed(2)
}));

// Sector heatmap data
const SECTOR_HEAT = [
  { name: '半導體',   en: 'Semi',       chg: +2.18, vol: 'L' },
  { name: 'AI 伺服器', en: 'AI Server',  chg: +3.42, vol: 'L' },
  { name: '電動車',   en: 'EV',         chg: -0.85, vol: 'M' },
  { name: '重電',     en: 'Heavy Elec', chg: +1.86, vol: 'L' },
  { name: '散熱',     en: 'Cooling',    chg: +2.94, vol: 'M' },
  { name: '機器人',   en: 'Robot',      chg: +1.42, vol: 'M' },
  { name: '低軌衛星', en: 'LEO Sat',    chg: +1.85, vol: 'M' },
  { name: '矽光子',   en: 'Photonics',  chg: +2.11, vol: 'S' },
  { name: '工具機',   en: 'Machine',    chg: -0.32, vol: 'M' },
  { name: '航運',     en: 'Shipping',   chg: +1.65, vol: 'L' },
  { name: '金融',     en: 'Financial',  chg: +0.42, vol: 'XL' },
  { name: '生技',     en: 'Biotech',    chg: -1.24, vol: 'M' },
  { name: '面板',     en: 'Display',    chg: -0.58, vol: 'M' },
  { name: '鋼鐵',     en: 'Steel',      chg: -0.95, vol: 'S' },
  { name: '紡織',     en: 'Textile',    chg: +0.28, vol: 'S' },
  { name: '食品',     en: 'Food',       chg: +0.15, vol: 'S' },
  { name: '塑化',     en: 'Plastic',    chg: -0.42, vol: 'M' },
  { name: '自行車',   en: 'Bicycle',    chg: +0.85, vol: 'S' },
  { name: '觀光',     en: 'Tourism',    chg: -0.18, vol: 'S' },
  { name: '電信',     en: 'Telecom',    chg: +0.12, vol: 'M' },
];

// Representative stocks per sector (for real-data heatmap calculation)
var SECTOR_STOCKS = {
  '半導體':    ['2330','2454','3034','3035','2379','2449'],
  'AI 伺服器': ['2317','2382','6669','3231','2376'],
  '電動車':    ['2049','1504','6214'],
  '重電':      ['1519','1513','1514','1517'],
  '散熱':      ['3017','3324','3016','6257'],
  '機器人':    ['1515','6414','2049'],
  '低軌衛星':  ['6257','3014','3228'],
  '矽光子':    ['2329','3443','6512'],
  '工具機':    ['2395','1590','1503'],
  '航運':      ['2603','2609','2615','2630'],
  '金融':      ['2881','2882','2884','2886','2891'],
  '生技':      ['4551','6547','4123','1789'],
  '面板':      ['2409','3481'],
  '鋼鐵':      ['2002','2006','9937'],
  '紡織':      ['1402','1418'],
  '食品':      ['1201','1210'],
  '塑化':      ['1301','1303'],
  '自行車':    ['9921','5706'],
  '觀光':      ['2701','2711'],
  '電信':      ['2412','4904'],
};

// 所有產業成分股去重代號清單(供 sector_quotes 盤中即時報價批次抓取)
var SECTOR_ALL_CODES = (function() {
  var set = {};
  Object.keys(SECTOR_STOCKS).forEach(function(k) { SECTOR_STOCKS[k].forEach(function(c) { set[c] = 1; }); });
  return Object.keys(set);
})();

const TOP_GAINERS = [
  { code: '6669', name: '緯穎',     price: 2680, chg: +3.27, vol: '8.2萬', reason: 'AI 機架液冷出貨放量' },
  { code: '3017', name: '奇鋐',     price: 1180, chg: +3.32, vol: '12.5萬', reason: '液冷板訂單續強' },
  { code: '2317', name: '鴻海',     price: 182.5, chg: +2.24, vol: '32.1萬', reason: 'GB300 量產在即' },
  { code: '1519', name: '華城',     price: 412, chg: +1.98, vol: '6.8萬', reason: '美國電網訂單放量' },
  { code: '2049', name: '上銀',     price: 312.5, chg: +2.13, vol: '4.2萬', reason: '機器人題材發酵' },
];

const TOP_LOSERS = [
  { code: '3008', name: '大立光',   price: 2245, chg: -2.18, vol: '2.1萬', reason: '法說會展望保守' },
  { code: '2412', name: '中華電',   price: 128.5, chg: -0.95, vol: '8.4萬', reason: '電信費率調整影響' },
  { code: '2891', name: '中信金',   price: 38.5, chg: -0.78, vol: '24.6萬', reason: '存款戶轉移消息' },
  { code: '2603', name: '長榮',     price: 248.5, chg: -1.85, vol: '18.5萬', reason: '紅海局勢趨緩' },
  { code: '2002', name: '中鋼',     price: 22.45, chg: -0.66, vol: '36.2萬', reason: '中國鋼鐵傾銷' },
];

const MARKET_STATS = {
  advancers: 624,
  decliners: 318,
  unchanged: 88,
  limitUp: 12,
  limitDown: 3,
  totalVolume: '4,182',  // 億
  totalValue: '2,856',   // 億
  foreignBuy: '+182',    // 億
  trustBuy: '+24',
  dealerBuy: '+8',
};

// ============ Components ============

// big sparkline / area chart
function IndexChart({ data, color, label, sub, live }) {
  const W = 520, H = 180, P = 30;
  if (!data || data.length < 2) return null;
  const min = Math.min(...data.map(function(d) { return d.value; }));
  const max = Math.max(...data.map(function(d) { return d.value; }));
  const range = max - min || 1;
  const path = data.map(function(d, i) {
    const x = P + (i / (data.length - 1)) * (W - P * 2);
    const y = H - P - ((d.value - min) / range) * (H - P * 2);
    return (i === 0 ? 'M' : 'L') + ' ' + x + ' ' + y;
  }).join(' ');
  const area = path + ' L ' + (W - P) + ' ' + (H - P) + ' L ' + P + ' ' + (H - P) + ' Z';
  const last = data[data.length - 1] || { value: 0, date: '' };
  const first = data[0] || { value: 0, date: '' };
  // 大標題數字優先用即時報價(live)，否則退回90日歷史最後一筆收盤(可能是昨日收盤,非即時)
  const headlineVal = (live && live.price) ? live.price : last.value;
  const chg = (live && live.price) ? live.chgPt : last.value - first.value;
  const pct = (live && live.price) ? live.chgPct : (first.value > 0 ? (chg / first.value) * 100 : 0);

  // Safe date axis: use proportional indices regardless of array length
  var axisIndices = [
    0,
    Math.floor(data.length * 1/3),
    Math.floor(data.length * 2/3),
    data.length - 1
  ];

  return (
    <div className="idx-chart">
      <div className="idx-chart-head">
        <div>
          <div className="idx-chart-label">
            <span style={{ fontStyle:'italic' }}>{label}</span>
            <span className="kai" style={{ marginLeft: 8 }}>{sub}</span>
          </div>
          <div className="idx-chart-value num">{headlineVal.toLocaleString('en-US', {minimumFractionDigits:2})}</div>
          <div className={`idx-chart-chg num ${chg >= 0 ? 'bull-c' : 'bear-c'}`}>
            <span className={chg >= 0 ? 'arrow-up' : 'arrow-down'}></span>
            {chg >= 0 ? '+' : ''}{chg.toFixed(2)} ({pct >= 0 ? '+' : ''}{pct.toFixed(2)}%)
          </div>
        </div>
        <div className="idx-range">
          <div><span>90D High</span><b className="num">{max.toFixed(2)}</b></div>
          <div><span>90D Low</span><b className="num">{min.toFixed(2)}</b></div>
        </div>
      </div>
      <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" style={{ width:'100%', height: H, marginTop: 10 }}>
        <defs>
          <linearGradient id={`grad-${label}`} x1="0" x2="0" y1="0" y2="1">
            <stop offset="0%" stopColor={color} stopOpacity=".25" />
            <stop offset="100%" stopColor={color} stopOpacity="0" />
          </linearGradient>
        </defs>
        {/* gridlines */}
        {[0,1,2,3].map(function(i) {
          return (
            <line key={i}
              x1={P} x2={W-P}
              y1={P + (i/3) * (H - P*2)}
              y2={P + (i/3) * (H - P*2)}
              stroke="var(--paper-line)" strokeWidth=".7" strokeDasharray="2 4" />
          );
        })}
        <path d={area} fill={`url(#grad-${label})`} />
        <path d={path} fill="none" stroke={color} strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round" />
        {/* date axis — safe proportional indices */}
        {axisIndices.map(function(i) {
          return data[i] ? (
            <text key={i} x={P + (i / (data.length - 1)) * (W - P*2)} y={H - 8} textAnchor="middle"
                  fontFamily="Times New Roman" fontStyle="italic" fontSize="9" fill="var(--ink-mute)">
              {data[i].date}
            </text>
          ) : null;
        })}
      </svg>
    </div>
  );
}

// Sector heat tile — size by vol, color by chg
function SectorHeat({ sectors }) {
  const sizeMap = { XL: 'span 2 / span 2', L: 'span 2 / span 1', M: 'span 1 / span 1', S: 'span 1 / span 1' };
  const getColor = (chg) => {
    if (chg >= 3) return '#1e7a3f';
    if (chg >= 1.5) return '#3a8c4d';
    if (chg >= 0.5) return '#7ea677';
    if (chg >= 0) return '#b0b4a0';
    if (chg >= -0.5) return '#b08989';
    if (chg >= -1.5) return '#a04a4a';
    return '#7a1a1a';
  };
  return (
    <div className="heat-grid">
      {sectors.map(s => (
        <div key={s.name} className="heat-cell"
             style={{ background: getColor(s.chg), gridRow: sizeMap[s.vol]?.split(' / ')[0] || 'span 1' }}>
          <div className="heat-cell-name kai">{s.name}</div>
          <div className="heat-cell-en">{s.en}</div>
          <div className="heat-cell-chg num">{s.chg >= 0 ? '+' : ''}{s.chg.toFixed(2)}%</div>
        </div>
      ))}
    </div>
  );
}

// ============ Ruflo Agent Status Panel ============
var RUFLO_DEPARTMENTS = [
  {
    id: 'data-hub', badge: 'A', name: '數據中台', en: 'Data Hub', desc: 'API · KV · 情報收集',
    lead:    { id: 'data-lead',           role: '數據 Lead',  en: 'Data Lead',       specialty: 'stock.js · KV 快取 · health check' },
    members: [
      { id: 'data-validator',            role: '數據驗證師', en: 'Validator',         specialty: '異常偵測 · 備援 · warmup' },
      { id: 'data-pipeline',             role: '數據管道',   en: 'Pipeline',          specialty: 'FinMind · 排程抓取 · ETL' },
      { id: 'news-sentiment-analyst',    role: '新聞情緒師', en: 'Sentiment Analyst', specialty: 'NLP · RSS · 市場情緒', hive: true },
      { id: 'macro-economist',           role: '總經研究員', en: 'Macro Economist',   specialty: '匯率 · 利率 · 地緣政治', hive: true },
    ],
  },
  {
    id: 'frontend-studio', badge: 'B', name: '前端工坊', en: 'Frontend Studio', desc: 'JSX · CSS · 圖表',
    lead:    { id: 'frontend-lead',      role: '前端 Lead',   en: 'Frontend Lead',   specialty: 'shared.jsx · app.jsx · 架構' },
    members: [
      { id: 'ui-coder',                 role: 'UI 工程師',   en: 'UI Coder',         specialty: '頁面實作 · 樣式 · 字體' },
      { id: 'kline-specialist',         role: 'K線師',       en: 'KLine Spec.',      specialty: 'lightweight-charts · 蠟燭圖 · 縮放' },
      { id: 'indicator-specialist',     role: '指標師',      en: 'Indicator Spec.',  specialty: 'MACD · RSI · BB · pandas-ta' },
    ],
  },
  {
    id: 'quant-lab', badge: 'C', name: '策略研究室', en: 'Quant Lab', desc: '指標 · 選股 · 回測 · 個股分析',
    lead:    { id: 'quant-lead',        role: '量化 Lead',   en: 'Quant Lead',       specialty: 'scan.jsx · screener.jsx · 訊號' },
    members: [
      { id: 'backtester',              role: '回測師',      en: 'Backtester',        specialty: '策略驗證 · Bot4 · 期望值' },
      { id: 'tw-stock-analyst',        role: '股票分析師',  en: 'Stock Analyst',     specialty: '技術面 · 籌碼 · 個股評分', hive: true },
      { id: 'backtest-engineer',       role: '回測工程師',  en: 'Backtest Eng.',     specialty: 'MA · RSI · MACD 回測', hive: true },
      { id: 'risk-advisor',            role: '風控顧問',    en: 'Risk Advisor',      specialty: '停損 · 部位管理 · 波動率', hive: true },
      { id: 'industry-analyst',        role: '產業分析師',  en: 'Industry Analyst',  specialty: '供應鏈 · 趨勢 · 題材', hive: true },
    ],
  },
  {
    id: 'qa-gate', badge: 'D', name: '品保部', en: 'QA Gate', desc: '審查 · 安全 · 部署',
    lead:    { id: 'qa-lead',           role: '品保 Lead',  en: 'QA Lead',    specialty: '程式碼審查 · 部署驗證' },
    members: [{ id: 'security-auditor', role: '資安稽核',   en: 'Security',   specialty: 'API 安全 · CORS · 資料洩漏' }],
  },
  {
    id: 'quant-trade', badge: 'E', name: '量化交易部', en: 'Quant Trading', desc: 'SMC · 訊號 · 部位管理',
    lead:    { id: 'signal-generator',  role: '訊號師 Lead', en: 'Signal Gen.',  specialty: '買賣訊號 · 進出場條件 · pandas-ta' },
    members: [
      { id: 'smc-analyst',             role: 'SMC 分析師',  en: 'SMC Analyst',   specialty: 'OB · BOS · FVG · joshyattridge/smc' },
      { id: 'portfolio-manager',       role: '部位管理師',  en: 'Portfolio Mgr', specialty: 'Kelly公式 · 資金分配 · qlib' },
    ],
  },
];
var RUFLO_HIVE_WORKERS = [
  { id: 'tw-stock-analyst',       role: '股票分析師', en: 'Stock Analyst',      specialty: '技術面 · 籌碼 · 個股評分' },
  { id: 'news-sentiment-analyst', role: '新聞情緒師', en: 'Sentiment Analyst',  specialty: 'NLP · RSS · 市場情緒' },
  { id: 'backtest-engineer',      role: '回測工程師', en: 'Backtest Engineer',  specialty: 'MA · RSI · MACD 回測' },
  { id: 'risk-advisor',           role: '風控顧問',   en: 'Risk Advisor',       specialty: '停損 · 部位管理 · 波動率' },
  { id: 'macro-economist',        role: '總經研究員', en: 'Macro Economist',    specialty: '匯率 · 利率 · 地緣政治' },
  { id: 'industry-analyst',       role: '產業分析師', en: 'Industry Analyst',   specialty: '供應鏈 · 趨勢 · 題材' },
];

function RufloPanel({ newsItems, gainers, losers, intlData, sectorHeat }) {
  var [results,   setResults]   = useState({});
  var [running,   setRunning]   = useState(false);
  var [synthesis, setSynthesis] = useState('');
  var [tick, setTick] = useState(0);

  useEffect(function() {
    // blink 游標只在分析執行中有意義——閒置時不 tick,避免整個 RufloPanel 每 0.9s 白白重渲染
    if (!running) return;
    var t = setInterval(function() { setTick(function(n) { return n + 1; }); }, 900);
    return function() { clearInterval(t); };
  }, [running]);

  var watchlist = useMemo(function() {
    try { return JSON.parse(localStorage.getItem('tw_watchlist_v2')) || []; }
    catch (_) { return []; }
  }, []);

  async function callAgent(agentId, prompt) {
    setResults(function(prev) { return Object.assign({}, prev, { [agentId]: { status: 'running', output: '' } }); });
    try {
      var reply = await window.claudeComplete({ messages: [{ role: 'user', content: prompt }] });
      setResults(function(prev) { return Object.assign({}, prev, { [agentId]: { status: 'done', output: reply } }); });
      return reply;
    } catch (_) {
      setResults(function(prev) { return Object.assign({}, prev, { [agentId]: { status: 'error', output: '連線失敗' } }); });
      return '';
    }
  }

  async function runMorningBriefing() {
    if (running) return;
    setRunning(true);
    setSynthesis('');
    setResults({});

    var headlines = (newsItems || []).slice(0, 5).map(function(i) { return i.title; }).join('；');
    var sentiment = await callAgent('news-sentiment-analyst',
      '你是新聞情緒分析師，只輸出60字以內繁體中文，不要開場白。今日台股要聞：' + (headlines || '暫無') + '。請分析今日市場情緒偏多/偏空，並說明主因。');

    var sortedSectors = (sectorHeat || []).filter(function(s) { return s.chg != null; })
      .sort(function(a, b) { return b.chg - a.chg; });
    var sectors = sortedSectors.map(function(s) { return s.name + (s.chg >= 0 ? '+' : '') + s.chg + '%'; }).join('、');
    var industry = await callAgent('industry-analyst',
      '你是產業分析師，只輸出60字以內繁體中文，不要開場白。今日情緒：' + sentiment + '。各族群：' + (sectors || '暫無') + '。請點出今日領漲族群、落後族群及輪動方向。');

    var intl = (intlData || []).filter(function(i) { return i.chg != null; })
      .map(function(i) { return i.name + (parseFloat(i.chg) >= 0 ? '+' : '') + parseFloat(i.chg).toFixed(2) + '%'; }).join('、');
    var macro = await callAgent('macro-economist',
      '你是總經研究員，只輸出60字以內繁體中文，不要開場白。國際指數：' + (intl || '暫無') + '。產業動態：' + industry + '。請說明總體環境對台股的主要影響。');

    var g = (gainers || []).slice(0, 3).map(function(s) { return s.code + s.name + '(+' + s.chg.toFixed(1) + '%)'; }).join('、');
    var l = (losers  || []).slice(0, 3).map(function(s) { return s.code + s.name + '(' + s.chg.toFixed(1) + '%)'; }).join('、');
    var stockOut = await callAgent('tw-stock-analyst',
      '你是台股分析師，只輸出60字以內繁體中文，不要開場白。漲幅榜：' + (g || '暫無') + '。跌幅榜：' + (l || '暫無') + '。族群輪動：' + industry + '。請點出今日最值得關注的個股機會。');

    var wl = watchlist.slice(0, 5).map(function(s) { return s.code + ' ' + s.name; }).join('、');
    var risk = await callAgent('risk-advisor',
      '你是風控顧問，只輸出60字以內繁體中文，不要開場白。用戶自選股：' + (wl || '無') + '。宏觀風險：' + macro + '。個股機會：' + stockOut + '。請提出今日最需注意的風險與操作提示。');

    var synth = await callAgent('backtest-engineer',
      '你是回測工程師，只輸出80字以內繁體中文，不要開場白。以下是今日五位分析師的結論—情緒：' + sentiment + '；產業：' + industry + '；總經：' + macro + '；個股：' + stockOut + '；風控：' + risk + '。請整合成一段今日最重要的操作策略建議。');

    setSynthesis(synth);
    setRunning(false);
  }

  var blink = tick % 2 === 0;
  var anyRunning = running;
  var allDone = synthesis !== '';

  // 計算總 agent 數
  var totalAgents = RUFLO_DEPARTMENTS.reduce(function(s, d) { return s + 1 + d.members.length; }, 0);
  var hiveCount   = RUFLO_DEPARTMENTS.reduce(function(s, d) {
    return s + d.members.filter(function(m) { return m.hive; }).length;
  }, 0);

  return (
    <div className="ruflo-panel">
      {/* 面板頂部：標題列 + 晨報按鈕 */}
      <div className="ruflo-section-head" style={{ display:'flex', alignItems:'center', gap:10, flexWrap:'wrap', marginBottom:12 }}>
        <span className="ruflo-badge" style={{ background:'var(--gold)', color:'var(--ink)' }}>TEAM</span>
        <span className="kai ruflo-section-name">四部門作戰室</span>
        <span className="ruflo-id italic-en" style={{ flex:1 }}>
          4 depts · {totalAgents} agents · {hiveCount} AI workers
        </span>
        {!running && (
          <button onClick={runMorningBriefing}
            style={{ fontSize:12, padding:'4px 14px', border:'1px solid var(--gold)', background:'transparent',
                     color:'var(--gold-deep)', cursor:'pointer', fontFamily:'BiauKai,標楷體,serif', letterSpacing:'.08em' }}>
            {allDone ? '↻ 重新晨報' : '啟動晨報分析 →'}
          </button>
        )}
        {running && (
          <span className="kai" style={{ fontSize:'var(--fs-nano)', color:'var(--gold)', animation:'pulse 1.2s ease-in-out infinite' }}>
            AI 工作員運行中…
          </span>
        )}
      </div>

      {/* 四部門卡片 */}
      <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
        {RUFLO_DEPARTMENTS.map(function(dept) {
          var allAgents = [dept.lead].concat(dept.members);
          // 判斷此部門是否有任何 hive worker 正在跑或已完成
          var deptHasActivity = dept.members.some(function(m) { return m.hive && results[m.id]; });
          return (
            <div key={dept.id} style={{
              border: '1px solid ' + (deptHasActivity ? 'var(--gold)' : 'var(--paper-line)'),
              padding:'10px 14px',
              background: deptHasActivity ? 'rgba(184,146,77,0.04)' : 'rgba(26,36,56,0.02)',
              transition: 'border-color .3s, background .3s',
            }}>
              {/* 部門標題列 */}
              <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:8 }}>
                <span style={{
                  fontSize:'var(--fs-nano)', fontFamily:'Times New Roman,serif', fontWeight:'bold',
                  letterSpacing:'.15em', color:'var(--ink)', background:'var(--gold)',
                  padding:'1px 7px', flexShrink:0,
                }}>{dept.badge}</span>
                <span className="kai" style={{ fontSize:13, color:'var(--ink)' }}>{dept.name}</span>
                <span className="italic-en" style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>{dept.en}</span>
                <span style={{ flex:1 }}/>
                <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', letterSpacing:'.05em' }}>{dept.desc}</span>
              </div>

              {/* 隊員列表 */}
              <div style={{ display:'flex', flexWrap:'wrap', gap:'4px 12px' }}>
                {allAgents.map(function(a, idx) {
                  var isLead  = idx === 0;
                  var res     = a.hive ? results[a.id] : null;
                  var status  = res ? res.status : 'idle';
                  var dotBg   = isLead ? 'var(--gold)'
                              : status === 'done'    ? 'var(--bull)'
                              : status === 'running' ? 'var(--gold)'
                              : status === 'error'   ? 'var(--bear)'
                              : 'var(--ink-mute)';
                  var dotOp   = isLead        ? (blink ? 1 : 0.55)
                              : status === 'running' ? (blink ? 1 : 0.25)
                              : status === 'done'    ? 1
                              : 0.25;
                  var label   = isLead        ? 'lead'
                              : status === 'running' ? '分析中'
                              : status === 'done'    ? '✓'
                              : status === 'error'   ? '✕'
                              : a.hive ? '⚡待命' : '備用';
                  var labelClr= isLead           ? 'var(--gold-deep)'
                              : status === 'done'    ? 'var(--bull)'
                              : status === 'running' ? 'var(--gold)'
                              : status === 'error'   ? 'var(--bear)'
                              : 'var(--ink-mute)';
                  return (
                    <div key={a.id} style={{
                      display:'flex', alignItems:'center', gap:5,
                      padding:'3px 8px',
                      border:'1px solid ' + (isLead ? 'var(--gold)' : a.hive ? 'rgba(184,146,77,0.3)' : 'var(--paper-line)'),
                      background: isLead ? 'rgba(184,146,77,0.08)' : 'transparent',
                      marginBottom:2,
                    }}>
                      <div style={{
                        width:5, height:5, borderRadius:'50%', flexShrink:0,
                        background: dotBg, opacity: dotOp,
                      }}/>
                      <span className="kai" style={{ fontSize:'var(--fs-nano)', color: isLead ? 'var(--ink)' : 'var(--ink-mute)', whiteSpace:'nowrap' }}>
                        {a.role}
                      </span>
                      <span className="kai" style={{ fontSize:'var(--fs-nano)', color: labelClr, flexShrink:0 }}>{label}</span>
                    </div>
                  );
                })}
              </div>

              {/* 展開此部門 hive 分析結果 */}
              {dept.members.filter(function(m) { return m.hive && results[m.id]?.output && results[m.id]?.status === 'done'; }).map(function(a) {
                return (
                  <div key={a.id} style={{
                    marginTop:8, paddingLeft:12, borderLeft:'2px solid var(--gold)',
                    fontSize:12, color:'var(--ink)', lineHeight:1.65,
                  }} className="kai">
                    <span style={{ fontSize:'var(--fs-nano)', color:'var(--gold-deep)', marginRight:6 }}>{a.role} ·</span>
                    {results[a.id].output}
                  </div>
                );
              })}
            </div>
          );
        })}
      </div>

      {/* 底部：回測工程師綜合策略 */}
      {synthesis && (
        <div style={{ marginTop:12, padding:'12px 16px', border:'1px solid var(--gold)', background:'rgba(184,146,77,0.06)' }}>
          <div style={{ fontSize:'var(--fs-nano)', letterSpacing:'.12em', color:'var(--gold-deep)', marginBottom:6 }}>
            SYNTHESIS · 策略研究室綜合建議
          </div>
          <div className="kai" style={{ fontSize:13, lineHeight:1.8, color:'var(--ink)' }}>{synthesis}</div>
        </div>
      )}
    </div>
  );
}

// parse TWSE list response into MoversTable row format
function listToMovers(list, top, desc) {
  return list
    .filter(function(s) { return parseFloat(s.ClosingPrice) > 0 && s.Change !== undefined; })
    .map(function(s) {
      var close  = parseFloat(s.ClosingPrice) || 0;
      var change = parseFloat(s.Change) || 0;
      var prev   = close - change;
      var pct    = prev > 0 ? (change / prev) * 100 : 0;
      var vol    = s.TradeVolume ? (parseInt(s.TradeVolume.replace(/,/g,'')) / 1000).toFixed(1) + '萬' : '-';
      return { code: s.Code, name: s.Name, price: close, chg: pct, vol: vol, reason: '' };
    })
    .sort(function(a, b) { return desc ? b.chg - a.chg : a.chg - b.chg; })
    .slice(0, top);
}

// ============ App ============
function App() {
  const today = new Date();
  const dateStr = today.toLocaleDateString('zh-TW', { year:'numeric', month:'long', day:'numeric', weekday:'long' });

  // Live data state — initialized with mock fallback
  var [gainers,    setGainers]    = useState(TOP_GAINERS);
  var [losers,     setLosers]     = useState(TOP_LOSERS);
  var [intlData,   setIntlData]   = useState(INTL_INDICES);
  var [newsItems,  setNewsItems]  = useState(null);
  var [newsLoading, setNewsLoading] = useState(true);
  var [briefing,   setBriefing]   = useState(null);
  var [mStats,     setMStats]     = useState(MARKET_STATS);
  var [taiexHist,  setTaiexHist]  = useState(TAIEX_HIST);
  var [otcHist,    setOtcHist]    = useState(OTC_HIST);
  var [eodChgMap,  setEodChgMap]  = useState({});
  var [sectorQuotes, setSectorQuotes] = useState({});
  var [chipData,   setChipData]   = useState(null);
  var [liveIndex,  setLiveIndex]  = useState(null);
  var [liveLoading, setLiveLoading] = useState(false);
  var [liveAuto,   setLiveAuto]   = useState(false);
  var [liveUpdated, setLiveUpdated] = useState(null);
  var [liveStale,  setLiveStale]  = useState(false);
  var [rtCache,    setRtCache]    = useState(function() {
    return window.rtCacheRead ? window.rtCacheRead() : {};
  });
  var qhN = window.useQhRefresh ? window.useQhRefresh() : 0;  // 全站刷新鈕計數

  function doFetchLiveIndex() {
    setLiveLoading(true);
    fetch('/api/stock?type=taiex')
      .then(function(r) { return r.json(); })
      .then(function(d) {
        if (d.msgArray && d.msgArray.length) {
          setLiveIndex(d.msgArray);
          setLiveUpdated(new Date());
          setLiveStale(false);
        } else {
          setLiveStale(true);   // 上游暫時無資料,沿用前次數值但標記為未更新
        }
        setLiveLoading(false);
      })
      .catch(function() { setLiveStale(true); setLiveLoading(false); });
  }

  // QuoteHub v2:自動模式訂閱主迴圈;手動模式維持載入/全站刷新時抓一次
  useEffect(function() {
    if (!liveAuto) { doFetchLiveIndex(); return; }
    return window.QuoteHub.subscribe({ type: 'taiex', onData: function(d) {
      if (d.msgArray && d.msgArray.length) {
        setLiveIndex(d.msgArray);
        setLiveUpdated(new Date());
        setLiveStale(false);
      }
      setLiveLoading(false);
    } });
  }, [liveAuto, qhN]);

  // 監聽個股分析頁的即時報價廣播
  useEffect(function() {
    try {
      var ch = new BroadcastChannel('tw_rt_update');
      ch.onmessage = function(e) {
        var d = e.data;
        if (d && d.code) {
          setRtCache(function(prev) {
            return Object.assign({}, prev, { [d.code]: d });
          });
        }
      };
      return function() { ch.close(); };
    } catch(_) {}
    // localStorage polling fallback（同分頁切換時）
    var pollT = setInterval(function() {
      setRtCache(window.rtCacheRead ? window.rtCacheRead() : {});
    }, 5000);
    return function() { clearInterval(pollT); };
  }, []);

  // Recently Viewed 區塊即時化：把顯示中的個股訂閱 QuoteHub batch_realtime,
  // 盤中跟全站一起更新、盤後顯示統一收盤快照(取代原本各檔「上次瀏覽時」的散亂舊值)。
  // dep 用代號集合字串,只有「看過的股票清單」變動才重訂閱,價格/時間更新不觸發重訂。
  var rtCodesKey = Object.keys(rtCache).sort().join(',');
  useEffect(function() {
    if (!rtCodesKey || !window.QuoteHub) return;
    var codes = rtCodesKey.split(',').slice(0, 20);   // batch_realtime 每批上限 20
    return window.QuoteHub.subscribe({
      type: 'batch_realtime', codes: codes,
      onData: function(d) {
        var existing = window.rtCacheRead ? window.rtCacheRead() : {};
        var touched = false;
        (d.msgArray || []).forEach(function(item) {
          var code = (item.c || '').trim() ||
            (item.ch || '').replace(/^(tse_|otc_)/, '').replace(/\.two?$/, '');
          var price = parseFloat(item.z);
          if (!(price > 0)) price = parseFloat(item.y);   // 無成交(如盤後)用昨收
          var prev = parseFloat(item.y) || price;
          if (code && price > 0 && window.rtCacheWrite) {
            var nm = (item.n && item.n.trim()) || (existing[code] && existing[code].name) || code;
            window.rtCacheWrite(code, price, prev, nm);
            touched = true;
          }
        });
        if (touched) setRtCache(window.rtCacheRead ? window.rtCacheRead() : {});
      }
    });
  }, [rtCodesKey]);

  // 產業熱力盤中即時化：QuoteHub v2 訂閱,主迴圈自動更新(原本只在刷新鈕時重抓)
  useEffect(function() {
    return window.QuoteHub.subscribe({
      type: 'sector_quotes', codes: SECTOR_ALL_CODES,
      onData: function(d) {
        if (!d.msgArray || !d.msgArray.length) return;
        var map = {};
        d.msgArray.forEach(function(m) {
          var code = (m.ch || '').replace(/^(tse|otc)_/, '').replace(/\.tw$/, '');
          var price = parseFloat(m.z) || parseFloat(m.b) || 0;
          var prev  = parseFloat(m.y) || 0;
          if (!code || !price || !prev) return;
          map[code] = (price - prev) / prev * 100;
        });
        setSectorQuotes(map);
      }
    });
  }, []);

  // 產業熱力：盤中優先用即時報價，缺漏的成分股退回EOD收盤%(避免單一成分股缺資料拖累整組平均)
  var sectorHeat = useMemo(function() {
    return SECTOR_HEAT.map(function(s) {
      var codes = SECTOR_STOCKS[s.name] || [];
      var chgs = codes.map(function(c) {
        if (sectorQuotes[c] != null) return sectorQuotes[c];
        if (eodChgMap[c] != null) return eodChgMap[c];
        return null;
      }).filter(function(v) { return v != null; });
      if (!chgs.length) return s;
      var avg = chgs.reduce(function(a, b) { return a + b; }, 0) / chgs.length;
      return Object.assign({}, s, { chg: parseFloat(avg.toFixed(2)) });
    });
  }, [sectorQuotes, eodChgMap]);

  useEffect(function() {
    fetch('/api/stock?type=chip_top')
      .then(function(r) { return r.json(); })
      .then(function(d) { if (d.ok) setChipData(d); })
      .catch(function() {});
  }, [qhN]);

  useEffect(function() {
    // Stock list → gainers / losers / market stats / sector heatmap
    fetch(window.qhBustUrl('/api/stock?type=list&market=twse', qhN), qhN > 0 ? { cache: 'no-store' } : undefined)
      .then(function(r) { return r.json(); })
      .then(function(list) {
        if (!Array.isArray(list) || !list.length) return;
        var valid = list.filter(function(s) { return parseFloat(s.ClosingPrice) > 0; });
        var adv = valid.filter(function(s) { return parseFloat(s.Change) > 0; }).length;
        var dec = valid.filter(function(s) { return parseFloat(s.Change) < 0; }).length;
        var unc = valid.filter(function(s) { return parseFloat(s.Change) === 0; }).length;
        setMStats(function(prev) { return Object.assign({}, prev, { advancers: adv, decliners: dec, unchanged: unc }); });
        setGainers(listToMovers(valid, 5, true));
        setLosers(listToMovers(valid, 5, false));

        // EOD 收盤漲跌% map(盤中當作備援,盤後/MIS被擋時使用)
        var eodMap = {};
        valid.forEach(function(x) {
          var close = parseFloat(x.ClosingPrice) || 1;
          var change = parseFloat(x.Change) || 0;
          var prev = close - change;
          eodMap[x.Code] = prev > 0 ? change / prev * 100 : 0;
        });
        setEodChgMap(eodMap);
      })
      .catch(function() {});

    // TAIEX / OTC 90-day history
    fetch('/api/stock?type=taiex_hist')
      .then(function(r) { return r.json(); })
      .then(function(d) {
        if (d.taiex && Array.isArray(d.taiex) && d.taiex.length >= 2) {
          setTaiexHist(d.taiex);
        }
        if (d.otc && Array.isArray(d.otc) && d.otc.length >= 2) {
          setOtcHist(d.otc);
        }
      })
      .catch(function() {});

    // International indices
    fetch('/api/stock?type=intl')
      .then(function(r) { return r.json(); })
      .then(function(d) { if (Array.isArray(d) && d.length > 0) setIntlData(d); })
      .catch(function() {});

    // Daily AI briefing (KV-cached, fast)
    fetch('/api/stock?type=briefing')
      .then(function(r) { return r.json(); })
      .then(function(d) { if (d.text) setBriefing(d.text); })
      .catch(function() {});

    // Market news — fetch first, with 8s timeout
    var newsCtrl = new AbortController();
    var newsTimeout = setTimeout(function() { newsCtrl.abort(); }, 8000);
    fetch('/api/stock?type=news&code=market&name=' + encodeURIComponent('台股 科技進步 合作 AI 半導體'), { signal: newsCtrl.signal })
      .then(function(r) { return r.json(); })
      .then(function(d) {
        clearTimeout(newsTimeout);
        if (d.items && d.items.length > 0) setNewsItems(d.items.slice(0, 3));
        setNewsLoading(false);
      })
      .catch(function() { clearTimeout(newsTimeout); setNewsLoading(false); });
  }, [qhN]);

  // 本週事件曆 — 只顯示今天及之後的事件
  function getThisWeekCalendar() {
    var now = new Date();
    var todayMidnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
    var dow = now.getDay();
    var monday = new Date(now); monday.setDate(now.getDate() - (dow === 0 ? 6 : dow - 1));
    function dayOf(offset) {
      var d = new Date(monday); d.setDate(monday.getDate() + offset);
      return { label: (d.getMonth()+1) + '/' + d.getDate(), ts: new Date(d.getFullYear(), d.getMonth(), d.getDate()) };
    }
    var days = ['一','二','三','四','五'];
    var ALL = [
      { ...dayOf(0), day: days[0], kai: '台灣外銷訂單',       en: 'TW Export Orders',     imp: 'M', region: '台' },
      { ...dayOf(0), day: days[0], kai: '美國消費者信心指數',  en: 'US Consumer Confidence',imp: 'M', region: '美' },
      { ...dayOf(1), day: days[1], kai: '美國耐久財訂單',      en: 'US Durable Goods',      imp: 'M', region: '美' },
      { ...dayOf(1), day: days[1], kai: '台積電海外法說',       en: 'TSMC Investor Day',     imp: 'H', region: '台' },
      { ...dayOf(2), day: days[2], kai: '美國 GDP 初估值',     en: 'US GDP Q1 Adv.',        imp: 'H', region: '美' },
      { ...dayOf(2), day: days[2], kai: 'Fed 褐皮書',          en: 'Fed Beige Book',         imp: 'M', region: '美' },
      { ...dayOf(3), day: days[3], kai: '美國 PCE 物價指數',   en: 'US PCE Inflation',      imp: 'H', region: '美' },
      { ...dayOf(3), day: days[3], kai: '台灣 CPI 月報',       en: 'TW CPI Report',         imp: 'M', region: '台' },
      { ...dayOf(4), day: days[4], kai: '歐元區 CPI 初值',     en: 'EU CPI Flash',          imp: 'M', region: '歐' },
      { ...dayOf(4), day: days[4], kai: '中國製造業 PMI',      en: 'China NBS PMI',         imp: 'M', region: '陸' },
    ];
    // 只保留今天及以後的事件
    return ALL.filter(function(e) { return e.ts >= todayMidnight; })
              .map(function(e) { return { date: e.label, day: e.day, kai: e.kai, en: e.en, imp: e.imp, region: e.region }; });
  }
  var weekCalendar = getThisWeekCalendar();

  return (
    <>

        {/* Hero */}
        <section className="cover" style={{ paddingTop: 32 }}>
          <div>
            <div className="eyebrow">Daily Market Bulletin · {dateStr}</div>
            <h1>
              <span style={{ fontStyle:'italic' }}>The</span> Market <span className="amp">Today</span>
              <span className="kai-title">大 盤 風 雲 錄</span>
            </h1>
            <p className="cover-lede">
              <b style={{color:'var(--ink)'}}>加權指數</b>收 23,512 點,漲 148 點,
              成交量 {MARKET_STATS.totalValue} 億元;
              <b style={{color:'var(--ink)'}}>外資</b>買超 182 億,
              <b style={{color:'var(--ink)'}}>投信</b>連 6 日買超,
              半導體、散熱、AI 伺服器領漲。
            </p>
          </div>
          <div className="cover-side">
            <h3>Today's Pulse <span className="kai" style={{fontSize:13, color:'var(--ink-mute)', letterSpacing:'.18em'}}>· 今日脈搏</span></h3>
            <div className="cover-stats" style={{ gridTemplateColumns:'1fr 1fr 1fr' }}>
              <div className="cover-stat">
                <div className="lbl">Advancers</div>
                <div className="val num bull-c">{mStats.advancers}</div>
              </div>
              <div className="cover-stat">
                <div className="lbl">Decliners</div>
                <div className="val num bear-c">{mStats.decliners}</div>
              </div>
              <div className="cover-stat">
                <div className="lbl">Unchanged</div>
                <div className="val num">{mStats.unchanged}</div>
              </div>
              <div className="cover-stat">
                <div className="lbl">Limit Up</div>
                <div className="val num bull-c">{MARKET_STATS.limitUp}</div>
              </div>
              <div className="cover-stat">
                <div className="lbl">Limit Down</div>
                <div className="val num bear-c">{MARKET_STATS.limitDown}</div>
              </div>
              <div className="cover-stat">
                <div className="lbl">Value (B)</div>
                <div className="val num">{MARKET_STATS.totalValue}<small>億</small></div>
              </div>
            </div>
            <div style={{ marginTop:18, paddingTop:14, borderTop:'1px solid var(--paper-line)', display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:14 }}>
              <div>
                <div className="lbl" style={{ fontSize:'var(--fs-nano)', letterSpacing:'.18em', color:'var(--ink-mute)' }}>外資 FOREIGN</div>
                <div className="num bull-c" style={{ fontSize:18, fontWeight:700 }}>{MARKET_STATS.foreignBuy} 億</div>
              </div>
              <div>
                <div className="lbl" style={{ fontSize:'var(--fs-nano)', letterSpacing:'.18em', color:'var(--ink-mute)' }}>投信 TRUST</div>
                <div className="num bull-c" style={{ fontSize:18, fontWeight:700 }}>{MARKET_STATS.trustBuy} 億</div>
              </div>
              <div>
                <div className="lbl" style={{ fontSize:'var(--fs-nano)', letterSpacing:'.18em', color:'var(--ink-mute)' }}>自營 DEALER</div>
                <div className="num bull-c" style={{ fontSize:18, fontWeight:700 }}>{MARKET_STATS.dealerBuy} 億</div>
              </div>
            </div>
          </div>
        </section>

        {/* Indices side-by-side */}
        <window.SectionHead
          kicker="Indices Overview"
          title="大盤指數"
          titleEn="Indices"
          sub="90-day trailing · 90 日走勢"
        />

        {/* Live quote bar */}
        <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:14, flexWrap:'wrap' }}>
          <QuoteActionsMenu
            liveAuto={liveAuto}
            liveLoading={liveLoading}
            hasRecent={Object.keys(rtCache).some(function(k) { return rtCache[k].price > 0; })}
            onToggleLive={function() { if (!liveAuto) { doFetchLiveIndex(); } setLiveAuto(function(v) { return !v; }); }}
            onRefresh={function() { window.QuoteHub ? window.QuoteHub.refreshAll() : doFetchLiveIndex(); }}
            onClearRecent={function() { localStorage.removeItem('tw_rt_cache_v1'); setRtCache({}); }}
          />
          {liveUpdated && (
            <span style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontFamily:'Times New Roman,serif', fontStyle:'italic' }}>
              更新 {liveUpdated.toLocaleTimeString('zh-TW', { hour:'2-digit', minute:'2-digit', second:'2-digit' })}
              {liveAuto && (
                <span style={{ marginLeft:6, color:'var(--gold)', animation:'pulse 1.4s ease-in-out infinite' }}>● LIVE</span>
              )}
            </span>
          )}
          {liveStale && (
            <span style={{ fontSize:'var(--fs-nano)', color:'var(--bear)', fontFamily:'Times New Roman,serif', fontStyle:'italic' }}>
              ⚠ 指數未取得最新資料，顯示為前次快照
            </span>
          )}
        </div>

        {liveLoading && (!liveIndex || !liveIndex.length) && (
          <div style={{ display:'flex', gap:12, marginBottom:18, flexWrap:'wrap' }}>
            {[0,1].map(function(i) {
              return (
                <div key={i} className="sk-card">
                  <div className="sk sk-line w40" />
                  <div className="sk sk-num" style={{ margin:'8px 0 6px' }} />
                  <div className="sk sk-line w60" />
                </div>
              );
            })}
          </div>
        )}
        {liveIndex && liveIndex.length > 0 && (
          <div style={{ display:'flex', gap:12, marginBottom:18, flexWrap:'wrap' }}>
            {liveIndex.map(function(idx) {
              var price  = parseFloat(idx.z) || 0;
              var prev   = parseFloat(idx.y) || price;
              var chgPt  = price > 0 ? price - prev : 0;
              var chgPct = parseFloat(idx.v) || (prev > 0 ? chgPt / prev * 100 : 0);
              var isUp   = chgPt >= 0;
              var closed = !price || idx.z === '-';
              var label  = idx.ex === 'tse' ? 'TAIEX 加權' : idx.ex === 'otc' ? 'OTC 櫃買' : (idx.n || idx.ch || '');
              var color  = idx.ex === 'tse' ? 'var(--gold)' : '#2c5e8e';
              return (
                <div key={idx.ch || label} style={{
                  flex:'1 1 200px', padding:'12px 18px',
                  border:'1px solid ' + (isUp && !closed ? 'var(--bull)' : closed ? 'var(--paper-line)' : 'var(--bear)'),
                  background:'rgba(26,36,56,0.03)', position:'relative',
                }}>
                  <div style={{ fontSize:'var(--fs-nano)', letterSpacing:'.14em', color:'var(--ink-mute)', fontFamily:'Times New Roman,serif', fontStyle:'italic', marginBottom:4 }}>
                    {label}
                  </div>
                  {closed ? (
                    <div style={{ fontSize:13, color:'var(--ink-mute)', fontStyle:'italic' }}>休市中 / 暫無報價</div>
                  ) : (
                    <>
                      <div style={{ fontSize:26, fontWeight:700, fontFamily:'Times New Roman,serif', color:'var(--ink)', letterSpacing:'.02em' }}>
                        <AnimatedPrice value={price} />
                      </div>
                      <div style={{ fontSize:13, fontFamily:'Times New Roman,serif', color: isUp ? 'var(--bull)' : 'var(--bear)', marginTop:2 }}>
                        {isUp ? '▲' : '▼'} {isUp ? '+' : ''}{chgPt.toFixed(2)} ({isUp ? '+' : ''}{chgPct.toFixed(2)}%)
                      </div>
                      <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', marginTop:4 }}>
                        昨收 {prev.toLocaleString('en-US', { minimumFractionDigits:2 })}
                      </div>
                    </>
                  )}
                  <div style={{ position:'absolute', top:8, right:12, width:6, height:6, borderRadius:'50%',
                    background: closed ? 'var(--ink-mute)' : isUp ? 'var(--bull)' : 'var(--bear)',
                    boxShadow: liveAuto && !closed ? '0 0 6px ' + (isUp ? 'var(--bull)' : 'var(--bear)') : 'none',
                  }}/>
                </div>
              );
            })}
          </div>
        )}

        {/* 個股即時共享報價 */}
        {(function() {
          var entries = Object.keys(rtCache)
            .map(function(k) { return rtCache[k]; })
            .filter(function(e) { return e.price > 0; })
            .sort(function(a, b) { return b.ts - a.ts; })
            .slice(0, 8);
          if (!entries.length) return null;
          return (
            <div style={{ marginBottom: 16 }}>
              <div style={{ display:'flex', alignItems:'baseline', gap:10, marginBottom:8 }}>
                <span style={{ fontFamily:'Times New Roman,serif', fontStyle:'italic', fontSize:'var(--fs-nano)', letterSpacing:'.14em', color:'var(--gold)' }}>
                  Recently Viewed Stocks
                </span>
                <span style={{ fontFamily:'BiauKai,標楷體,serif', fontSize:'var(--fs-nano)', color:'var(--ink-mute)' }}>個股最近即時報價</span>
              </div>
              <div style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
                {entries.map(function(e) {
                  var chg  = e.prev > 0 ? e.price - e.prev : 0;
                  var pct  = e.prev > 0 ? chg / e.prev * 100 : 0;
                  var isUp = chg >= 0;
                  var age  = Date.now() - e.ts;
                  var fresh = age < 120000; // 2分鐘內為新鮮
                  return (
                    <a key={e.code} href={'個股技術分析.html?code=' + e.code}
                      style={{ textDecoration:'none', color:'inherit' }}>
                      <div style={{
                        padding:'8px 14px', border:'1px solid var(--paper-line)',
                        borderLeft:'3px solid ' + (isUp ? 'var(--bull)' : 'var(--bear)'),
                        background:'var(--paper-bright)', minWidth:120,
                        position:'relative',
                      }}>
                        {fresh && (
                          <div style={{ position:'absolute', top:5, right:6, width:5, height:5, borderRadius:'50%',
                            background: isUp ? 'var(--bull)' : 'var(--bear)', opacity:.8 }}/>
                        )}
                        <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontFamily:'BiauKai,標楷體,serif', marginBottom:2 }}>
                          {e.code} {e.name !== e.code ? e.name : ''}
                        </div>
                        <div style={{ fontSize:18, fontWeight:700, fontFamily:'Times New Roman,serif', color:'var(--ink)' }}>
                          {e.price.toFixed(2)}
                        </div>
                        <div style={{ fontSize:'var(--fs-nano)', fontFamily:'Times New Roman,serif', color: isUp ? 'var(--bull)' : 'var(--bear)' }}>
                          {isUp ? '▲' : '▼'} {Math.abs(chg).toFixed(2)} ({isUp?'+':''}{pct.toFixed(2)}%)
                        </div>
                        <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontFamily:'Times New Roman,serif', fontStyle:'italic', marginTop:2 }}>
                          {new Date(e.ts).toLocaleTimeString('zh-TW', { hour:'2-digit', minute:'2-digit' })}
                        </div>
                      </div>
                    </a>
                  );
                })}
              </div>
            </div>
          );
        })()}

        {(function() {
          var liveTse = (liveIndex || []).find(function(idx) { return idx.ex === 'tse'; });
          var liveOtc = (liveIndex || []).find(function(idx) { return idx.ex === 'otc'; });
          var toLive = function(idx) {
            if (!idx || idx.z === '-') return null;
            var price = parseFloat(idx.z) || 0;
            var prev  = parseFloat(idx.y) || price;
            if (!price) return null;
            return { price: price, chgPt: price - prev, chgPct: parseFloat(idx.v) || (prev > 0 ? (price - prev) / prev * 100 : 0) };
          };
          var liveTseV = toLive(liveTse), liveOtcV = toLive(liveOtc);
          return (
            <div className="dash-grid-2">
              <div className="frame-card">
                <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
                <IndexChart data={taiexHist} color="#b8924d" label="TAIEX" sub="加權指數" live={liveTseV} />
              </div>
              <div className="frame-card">
                <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
                <IndexChart data={otcHist} color="#2c5e8e" label="OTC" sub="櫃買指數" live={liveOtcV} />
              </div>
            </div>
          );
        })()}

        {/* Sector heatmap */}
        <window.SectionHead
          kicker="Sector Performance"
          title="產業熱力"
          titleEn="Heatmap"
          sub="Cell size = volume · Color = today's change"
        />
        <div className="frame-card">
          <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
          <SectorHeat sectors={sectorHeat} />
          <div className="heat-scale">
            <span>≤-1.5%</span>
            <div className="scale-bar"></div>
            <span>≥+3%</span>
          </div>
        </div>

        {/* Top movers — gainers / losers */}
        <window.SectionHead
          kicker="Most Active"
          title="今日焦點"
          titleEn="Movers"
          sub="Daily top gainers & losers"
        />
        <div className="dash-grid-2">
          <MoversTable title="漲幅榜" titleEn="Top Gainers" rows={gainers} accent="bull-c" />
          <MoversTable title="跌幅榜" titleEn="Top Losers"  rows={losers}  accent="bear-c" />
        </div>

        {/* AI Daily Briefing */}
        {briefing && (
          <>
            <window.SectionHead
              kicker="AI Editor · Formosa &amp; Co."
              title="今日編輯簡報"
              titleEn="Daily Briefing"
              sub="由 AI 編輯生成，每日更新"
            />
            <div className="frame-card" style={{ padding:'20px 24px', marginBottom:24, whiteSpace:'pre-line', lineHeight:1.8, fontSize:14 }}>
              <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
              <div className="kai" style={{ color:'var(--ink)' }}>{briefing}</div>
            </div>
          </>
        )}

        {/* Editor's notes */}
        <window.SectionHead
          kicker="Editor's Briefing"
          title="今日要聞"
          titleEn="Briefing"
          sub="Tech · AI · Partnerships"
        />
        <div className="feature-grid">
          {newsItems ? newsItems.map(function(item, i) {
            var sc = item.sentiment;
            var scColor = sc === 'bull' ? 'var(--bull)' : sc === 'bear' ? 'var(--bear)' : 'var(--ink-mute)';
            var scLabel = sc === 'bull' ? '利多' : sc === 'bear' ? '利空' : sc === 'neutral' ? '中性' : null;
            return (
              <div key={i} className="feature">
                <span className="feature-num">{['I.','II.','III.'][i]}</span>
                <h4 className="kai">
                  <a href={item.link} target="_blank" rel="noopener noreferrer"
                     style={{ color:'inherit', textDecoration:'none' }}>
                    {item.title}
                  </a>
                </h4>
                <p style={{ fontStyle:'italic', fontSize:12, color:'var(--ink-mute)', display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
                  <span>{item.source} · {item.pubDate ? new Date(item.pubDate).toLocaleTimeString('zh-TW', { hour:'2-digit', minute:'2-digit' }) : ''}</span>
                  {scLabel && (
                    <span style={{ fontSize:'var(--fs-nano)', padding:'1px 6px', border:'1px solid '+scColor, color:scColor, borderRadius:2, fontStyle:'normal', letterSpacing:'.05em' }}>
                      {scLabel}
                    </span>
                  )}
                </p>
              </div>
            );
          }) : newsLoading ? (
            // Skeleton loading cards
            [0,1,2].map(function(i) {
              return (
                <div key={i} className="feature" style={{ opacity: 0.5 + i*0.1 }}>
                  <span className="feature-num" style={{ color:'var(--paper-line)' }}>{['I.','II.','III.'][i]}</span>
                  <div style={{ height:16, background:'var(--paper-line)', borderRadius:1, marginBottom:10, width:'90%', animation:'pulse 1.5s ease-in-out infinite' }}/>
                  <div style={{ height:12, background:'var(--paper-line)', borderRadius:1, marginBottom:6, width:'70%', animation:'pulse 1.5s ease-in-out infinite' }}/>
                  <div style={{ height:10, background:'var(--paper-line)', borderRadius:1, width:'40%', animation:'pulse 1.5s ease-in-out infinite' }}/>
                </div>
              );
            })
          ) : (
            <div className="feature" style={{ gridColumn:'1/-1' }}>
              <span className="feature-num">—</span>
              <h4 className="kai">今日要聞暫時無法取得<em>News unavailable</em></h4>
              <p>請稍後重新整理頁面。</p>
            </div>
          )}
        </div>

        {/* International indices */}
        <window.SectionHead
          kicker="Global Markets"
          title="國際盤對照"
          titleEn="World"
          sub="Major equity & semiconductor indices"
        />
        <div className="intl-grid">
          {intlData.map(idx => (
            <div key={idx.code} className="intl-card">
              <div className="intl-region">{idx.region === 'us' ? '美' : idx.region === 'asia' ? '亞' : idx.region}</div>
              <div className="intl-name">
                <span className="kai">{idx.name}</span>
                <span className="intl-en italic-en">{idx.en}</span>
              </div>
              <div className="intl-val num">{idx.value.toLocaleString('en-US', {minimumFractionDigits:2})}</div>
              <div className={`intl-chg num ${idx.chg >= 0 ? 'bull-c' : 'bear-c'}`}>
                <span className={idx.chg >= 0 ? 'arrow-up' : 'arrow-down'}></span>
                {idx.chg >= 0 ? '+' : ''}{idx.chg.toFixed(2)}
                <span className="intl-pct">({idx.pct >= 0 ? '+' : ''}{idx.pct.toFixed(2)}%)</span>
              </div>
            </div>
          ))}
        </div>

        {/* Institutional flows — live from TWSE T86 */}
        <window.SectionHead
          kicker="Institutional Flows"
          title="法人籌碼排行"
          titleEn="Smart Money"
          sub={chipData ? `${chipData.date.slice(0,4)}/${chipData.date.slice(4,6)}/${chipData.date.slice(6,8)} 三大法人買賣超 Top 5` : '今日三大法人買賣超前五大'}
        />
        {(function() {
          function fmtSh(n) {
            var abs = Math.abs(n), s = n >= 0 ? '+' : '-';
            if (abs >= 100000000) return s + (abs/100000000).toFixed(1) + '億股';
            if (abs >= 10000) return s + (abs/10000).toFixed(0) + '萬股';
            return s + abs.toLocaleString() + '股';
          }
          var src = chipData || null;
          var fBuy  = src ? src.buy_top.slice(0,5).map(r => ({ code:r.code, name:r.name.trim(), net:fmtSh(r.foreign_net), val:fmtSh(r.total_net) })) : INST_FLOWS.foreign.buy;
          var fSell = src ? src.sell_top.slice(0,5).map(r => ({ code:r.code, name:r.name.trim(), net:fmtSh(r.foreign_net), val:fmtSh(r.total_net) })) : INST_FLOWS.foreign.sell;
          var tBuy  = src ? [...src.buy_top].sort((a,b)=>b.trust_net-a.trust_net).slice(0,5).map(r => ({ code:r.code, name:r.name.trim(), net:fmtSh(r.trust_net), val:fmtSh(r.total_net) })) : INST_FLOWS.trust.buy;
          var tSell = src ? [...src.sell_top].sort((a,b)=>a.trust_net-b.trust_net).slice(0,5).map(r => ({ code:r.code, name:r.name.trim(), net:fmtSh(r.trust_net), val:fmtSh(r.total_net) })) : INST_FLOWS.trust.sell;
          return (
            <>
              <div className="dash-grid-2">
                <InstFlowCard title="外資買超" titleEn="Foreign Top Buys"  rows={fBuy}  accent="bull-c" />
                <InstFlowCard title="外資賣超" titleEn="Foreign Top Sells" rows={fSell} accent="bear-c" />
              </div>
              <div className="dash-grid-2" style={{ marginTop:14 }}>
                <InstFlowCard title="投信買超" titleEn="Trust Top Buys"   rows={tBuy}  accent="bull-c" />
                <InstFlowCard title="投信賣超" titleEn="Trust Top Sells"  rows={tSell} accent="bear-c" />
              </div>
              <div style={{ textAlign:'right', marginTop:8 }}>
                <a href="籌碼雷達.html" style={{ fontSize:12, color:'var(--gold)', textDecoration:'none', opacity:.7 }}>完整排行 Top 30 →</a>
              </div>
            </>
          );
        })()}

        {/* Economic calendar */}
        <window.SectionHead
          kicker="Economic Calendar"
          title="本週事件曆"
          titleEn="Calendar"
          sub="重要經濟數據與企業財報"
        />
        <div className="frame-card">
          <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
          {weekCalendar.length === 0 ? (
            <div style={{ padding:'28px 24px', textAlign:'center', color:'var(--ink-mute)', fontStyle:'italic', fontFamily:'Times New Roman,serif' }}>
              本週事件已全數結束 — 下週事件曆將於週一更新。
              <br/><span className="kai" style={{ fontSize:12, letterSpacing:'.1em' }}>This week's events have concluded.</span>
            </div>
          ) : (
            <table className="ed-table calendar-table">
              <thead>
                <tr>
                  <th>Date</th>
                  <th>Day</th>
                  <th>Region</th>
                  <th>Event</th>
                  <th className="right">Impact</th>
                </tr>
              </thead>
              <tbody>
                {weekCalendar.map(function(e, i) {
                  var isToday = e.date === ((new Date().getMonth()+1) + '/' + new Date().getDate());
                  return (
                    <tr key={i} style={{ background: isToday ? 'rgba(184,146,77,.07)' : '' }}>
                      <td className="code-cell num-cell">
                        {e.date}
                        {isToday && <span style={{ marginLeft:6, fontSize:'var(--fs-nano)', color:'var(--gold-deep)', fontFamily:'BiauKai,serif', letterSpacing:'.08em' }}>今日</span>}
                      </td>
                      <td className="kai" style={{ fontSize:13 }}>週{e.day}</td>
                      <td><span className="region-tag">{e.region}</span></td>
                      <td>
                        <div className="kai" style={{ fontSize:14, letterSpacing:'.06em', fontWeight:600 }}>{e.kai}</div>
                        <div style={{ fontSize:'var(--fs-nano)', color:'var(--ink-mute)', fontStyle:'italic' }}>{e.en}</div>
                      </td>
                      <td className="right">
                        <span className={`impact-dot impact-${e.imp}`}>{e.imp === 'H' ? '高' : e.imp === 'M' ? '中' : '低'}</span>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          )}
        </div>

        {/* Ruflo Agent Status Panel */}
        <window.SectionHead
          kicker="AI Intelligence Layer"
          title="智能工作員"
          titleEn="Ruflo Agents"
          sub="Swarm · Hive-mind · Real-time analysis crew"
        />
        <RufloPanel newsItems={newsItems} gainers={gainers} losers={losers} intlData={intlData} sectorHeat={sectorHeat} />

        <window.FooterBar />
    </>
  );
}

function InstFlowCard({ title, titleEn, rows, accent }) {
  return (
    <div className="frame-card">
      <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
      <div className="movers-head">
        <h3>
          <span className="kai">{title}</span>
          <span className="movers-en">{titleEn}</span>
        </h3>
      </div>
      <table className="ed-table" style={{ fontSize: 13 }}>
        <thead>
          <tr>
            <th>Code</th>
            <th>Name</th>
            <th className="right">Net Shares</th>
            <th className="right">Value</th>
          </tr>
        </thead>
        <tbody>
          {rows.map((r, i) => (
            <tr key={r.code + i} onClick={() => location.href = `個股技術分析.html?code=${r.code}`} style={{cursor:'pointer'}}>
              <td className="code-cell">{r.code}</td>
              <td className="kai">{r.name}</td>
              <td className={`right num-cell ${accent}`}>{r.net}</td>
              <td className={`right num-cell ${accent}`}>{r.val}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function MoversTable({ title, titleEn, rows, accent }) {
  return (
    <div className="frame-card">
      <span className="corner-tl"/><span className="corner-tr"/><span className="corner-bl"/><span className="corner-br"/>
      <div className="movers-head">
        <h3>
          <span className="kai">{title}</span>
          <span className="movers-en">{titleEn}</span>
        </h3>
        <a href="多因子篩選.html" className="movers-more">more →</a>
      </div>
      <table className="ed-table">
        <thead>
          <tr>
            <th>Code</th>
            <th>Name</th>
            <th className="right">Price</th>
            <th className="right">Chg %</th>
            <th>Catalyst</th>
          </tr>
        </thead>
        <tbody>
          {rows.map(r => (
            <tr key={r.code} onClick={() => location.href = `個股技術分析.html?code=${r.code}`} style={{cursor:'pointer'}}>
              <td className="code-cell">{r.code}</td>
              <td className="kai">{r.name}</td>
              <td className="right num-cell">{r.price.toLocaleString('en-US')}</td>
              <td className={`right num-cell ${accent}`}>
                <span className={r.chg >= 0 ? 'arrow-up' : 'arrow-down'}></span>
                {r.chg >= 0 ? '+' : ''}{r.chg.toFixed(2)}%
              </td>
              <td className="kai" style={{ fontSize:12, color:'var(--ink-soft)' }}>{r.reason}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

window.TWDashboardContent = App;
