// V2: variant components — innovations + 3 card variants + style-aware Dashboard/Detail
// Globals from components.jsx (Icon, fmt, ChangePct, MarketTabs, TermTabs, etc.) + data.js

const { useState: uS2, useRef: uR2, useMemo: uM2, useEffect: uE2 } = React;

// ─── Pulse freshness dot ─────────────────────────────────────
function PulseDot({ status='ok', children, label }) {
  const color = status === 'ok' ? 'var(--ok)' : status === 'warn' ? 'var(--warn)' : 'var(--danger)';
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8, color }}>
      <span className="pulse-dot" />
      {children && <span style={{ color: 'var(--ink-3, var(--text-3))', fontSize: 11 }}>{children}</span>}
    </span>
  );
}

// ─── Market Radar (today's 3-market sentiment) ───────────────
function MarketRadar({ currentMarket }) {
  // 3 axes: us, cn, crypto. Each axis a value 0-1 (sentiment).
  const data = { us: 0.78, cn: 0.62, crypto: 0.84 };
  const r = 36;
  const cx = 46, cy = 46;
  const angles = [-90, 30, 150]; // top, bottom-right, bottom-left
  const labels = ['us', 'cn', 'crypto'];
  const pts = labels.map((k, i) => {
    const a = angles[i] * Math.PI / 180;
    const dist = data[k] * r;
    return [cx + Math.cos(a) * dist, cy + Math.sin(a) * dist];
  });
  const axesPts = angles.map(a => {
    const ra = a * Math.PI / 180;
    return [cx + Math.cos(ra) * r, cy + Math.sin(ra) * r];
  });
  const colors = ['var(--src-tech)', 'var(--up)', 'var(--accent)'];
  return (
    <div className="radar-card">
      <svg width="92" height="92" viewBox="0 0 92 92" aria-label="market sentiment radar">
        {[0.33, 0.66, 1].map((s, i) => (
          <polygon key={i}
            points={angles.map(a => {
              const ra = a * Math.PI / 180;
              return (cx + Math.cos(ra)*r*s) + ',' + (cy + Math.sin(ra)*r*s);
            }).join(' ')}
            fill="none" stroke="var(--rule, var(--border))" strokeWidth="0.5" strokeDasharray={i < 2 ? '1 2' : ''}
          />
        ))}
        {axesPts.map((p, i) => (
          <line key={i} x1={cx} y1={cy} x2={p[0]} y2={p[1]} stroke="var(--rule, var(--border))" strokeWidth="0.5"/>
        ))}
        <polygon points={pts.map(p => p.join(',')).join(' ')}
          fill="var(--accent-paper, var(--accent))" fillOpacity="0.15"
          stroke="var(--accent-paper, var(--accent))" strokeWidth="1.2"/>
        {pts.map((p, i) => (
          <circle key={i} cx={p[0]} cy={p[1]} r="2.6" fill={colors[i]} stroke="var(--paper, var(--bg-0))" strokeWidth="1"/>
        ))}
      </svg>
      <div className="legend">
        <div className="ttl">今日大局 · Market Pulse</div>
        {labels.map((k, i) => (
          <div key={k} className="li" style={{ opacity: currentMarket === k ? 1 : 0.65 }}>
            <span>
              <span className="sw" style={{ background: colors[i] }}/>
              {t('dashboard.market_' + k)}
              {currentMarket === k && <span style={{ color: 'var(--accent-paper, var(--accent))', fontSize: 9, marginLeft: 4 }}>● ACTIVE</span>}
            </span>
            <span className="v">{Math.round(data[k]*100)}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

// ─── Analyst dialogue bubbles — community chat style ────────
function AnalystBubbles({ scores, reasoning }) {
  const lang = window.__LANG || 'zh';
  const [reacts, setReacts] = React.useState({ tech: null, ai: null, expert: null });
  const [typing, setTyping] = React.useState(true);
  React.useEffect(() => {
    const t = setTimeout(() => setTyping(false), 2400);
    return () => clearTimeout(t);
  }, []);

  const items = [
    { k: 'tech',   av: '技', name: lang==='zh'?'技术派 · TechDesk':'TechDesk',
      handle: '@tech_desk', score: scores.technical, text: reasoning.technical,
      mins: 7, online: true },
    { k: 'ai',     av: 'AI', name: lang==='zh'?'AI 模型 · NeuralNet':'NeuralNet',
      handle: '@ai_quant',  score: scores.ai, text: reasoning.ai,
      mins: 4, online: true },
    { k: 'expert', av: '专', name: lang==='zh'?'外部专家 · StreetCon.':'StreetCons.',
      handle: '@street',    score: scores.expert, text: reasoning.expert,
      mins: 2, online: true },
  ];
  const EMOJI = ['👍', '🔥', '🤔', '⚠️'];
  const toggle = (k, e) => setReacts(r => ({ ...r, [k]: r[k] === e ? null : e }));

  const verdict = items.reduce((s, x) => s + x.score, 0) / items.length;
  const bullish = verdict >= 60;

  return (
    <div className="chat-room">
      <div className="chat-room-head">
        <div className="cr-title">
          <span className="cr-hash">#</span>
          <span>{lang==='zh'?'三源·分析室':'three-source-room'}</span>
        </div>
        <div className="cr-meta">
          <span className="cr-live"><span className="cr-live-dot"/>LIVE</span>
          <span>· {items.length} {lang==='zh'?'位在线':'online'}</span>
        </div>
      </div>

      <div className="chat-list">
        {items.map((it, idx) => (
          <div key={it.k} className={'chat-msg ' + it.k}
               style={{ animationDelay: (idx * 0.18) + 's' }}>
            <div className="chat-avatar">
              <span>{it.av}</span>
              {it.online && <span className="chat-online"/>}
            </div>
            <div className="chat-body">
              <div className="chat-meta">
                <span className="chat-name">{it.name}</span>
                <span className="chat-handle">{it.handle}</span>
                <span className="chat-time">{it.mins}m</span>
                <span className={'chat-score ' + (it.score >= 60 ? 'up' : it.score >= 40 ? 'mid' : 'down')}>
                  {it.score}/100
                </span>
              </div>
              <div className="chat-text">{it.text}</div>
              <div className="chat-reacts">
                {EMOJI.map(e => (
                  <button key={e}
                          className={'chat-react' + (reacts[it.k] === e ? ' on' : '')}
                          onClick={() => toggle(it.k, e)}>
                    <span className="re-e">{e}</span>
                    <span className="re-n">{(reacts[it.k] === e ? 1 : 0) + (e === '👍' ? 3 : e === '🔥' ? 5 : e === '🤔' ? 1 : 0)}</span>
                  </button>
                ))}
                <button className="chat-reply">↳ {lang==='zh'?'回复':'reply'}</button>
              </div>
            </div>
          </div>
        ))}

        {typing && (
          <div className="chat-typing">
            <div className="chat-avatar tiny"><span>融</span></div>
            <div className="typing-bubble">
              <span/><span/><span/>
            </div>
            <span className="typing-label">
              {lang==='zh'?'融合引擎正在汇总…':'fusion engine summarising…'}
            </span>
          </div>
        )}

        {!typing && (
          <div className="chat-verdict">
            <span className="cv-tag">{lang==='zh'?'融合裁定':'FUSION VERDICT'}</span>
            <span className={'cv-arrow ' + (bullish?'up':'down')}>{bullish?'▲':'▼'}</span>
            <span className="cv-text">
              {lang==='zh'
                ? (bullish ? '三方共识偏多 · 可建仓' : '分歧较大 · 建议观望')
                : (bullish ? 'consensus long · entry ok' : 'divergence · stand by')}
            </span>
            <span className="cv-num">{Math.round(verdict)}</span>
          </div>
        )}
      </div>

      <div className="chat-composer" aria-hidden="true">
        <span className="cmp-plus">+</span>
        <span className="cmp-ph">{lang==='zh'?'向分析师提问…':'message #three-source-room'}</span>
        <span className="cmp-send">↵</span>
      </div>
    </div>
  );
}

// ─── Decision Timeline (under chart) ─────────────────────────
function DecisionTimeline({ rec }) {
  const sl = rec.recommendation.stop_loss;
  const tp = rec.recommendation.take_profit;
  const entry = rec.price.current * 0.985;
  const now = rec.price.current;
  const min = Math.min(sl, entry, now, tp);
  const max = Math.max(sl, entry, now, tp);
  const range = max - min;
  const pct = v => ((v - min) / range) * 100;
  const cur = rec.price.currency;
  return (
    <div className="timeline">
      <div className="line">
        <div className="now-bar" style={{ left: pct(sl) + '%', width: (pct(now) - pct(sl)) + '%' }}/>
        <div className="pt sl" style={{ left: pct(sl) + '%', top: '50%' }}/>
        <div className="pt entry" style={{ left: pct(entry) + '%', top: '50%' }}/>
        <div className="pt now" style={{ left: pct(now) + '%', top: '50%' }}/>
        <div className="pt tp" style={{ left: pct(tp) + '%', top: '50%' }}/>
      </div>
      <div className="label" style={{ left: pct(sl) + '%', top: 30 }}>
        <span className="name" style={{ color: 'var(--down)' }}>STOP</span>
        <span className="px">{fmt.price(sl, cur)}</span>
      </div>
      <div className="label" style={{ left: pct(entry) + '%', top: 30 }}>
        <span className="name">ENTRY</span>
        <span className="px">{fmt.price(entry, cur)}</span>
      </div>
      <div className="label" style={{ left: pct(now) + '%', top: -16, color: 'var(--accent-paper, var(--accent))' }}>
        <span className="name">NOW</span>
      </div>
      <div className="label" style={{ left: pct(tp) + '%', top: 30 }}>
        <span className="name" style={{ color: 'var(--up)' }}>TARGET</span>
        <span className="px">{fmt.price(tp, cur)}</span>
      </div>
    </div>
  );
}

// ─── Contract regret panel ───────────────────────────────────
function ContractRegret({ rec }) {
  const lossPct = ((rec.price.current - rec.recommendation.stop_loss)/rec.price.current*100);
  const lang = window.__LANG || 'zh';
  return (
    <div className="contract">
      <div className="c-title">{lang === 'zh' ? '风险知情承诺书' : 'Regret Acknowledgement'}</div>
      <div className="c-sub">{lang === 'zh' ? '关于本次推荐之诚实披露 · §' + rec.symbol : 'Honest Disclosure · §' + rec.symbol}</div>

      <div className="clause">
        <span className="num">I.</span>
        {lang === 'zh' ? '本人知悉,本推荐有 ' : 'I acknowledge that this recommendation carries a '}
        <span className="hl">{fmt.pctFromUnit(rec.risk.loss_probability, { dp: 0 })}</span>
        {lang === 'zh' ? ' 的亏损概率,基于过往 ' : ' loss probability, based on '}
        <span className="hl">n = {rec.backtest.sample_size}</span>
        {lang === 'zh' ? ' 次同类信号样本统计。' : ' historical samples.'}
      </div>
      <div className="clause">
        <span className="num">II.</span>
        {lang === 'zh' ? '若触及止损 ' : 'If the stop-loss is hit ('}
        <span style={{ fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{fmt.price(rec.recommendation.stop_loss, rec.price.currency)}</span>
        {lang === 'zh' ? ',本次损失将达 ' : '), expected loss is '}
        <span className="hl">−{lossPct.toFixed(2)}%</span>
        {lang === 'zh' ? ' —— 即推荐价位至止损位之距。' : ' from current price.'}
      </div>
      <div className="clause">
        <span className="num">III.</span>
        {lang === 'zh' ? '历史最差一次同类推荐,曾下挫 ' : 'The worst historical instance of this signal drew down '}
        <span className="hl">−{rec.risk.max_drawdown_pct.toFixed(1)}%</span>
        {lang === 'zh' ? '。本工具不保证未来。' : '. Past performance is not future guarantee.'}
      </div>

      <div className="sig-block">
        <div className="sig-line">
          Felix
          <span className="lab">{lang === 'zh' ? '本人签字 / Signed' : 'Signature'}</span>
        </div>
        <svg className="stamp-mark" viewBox="0 0 64 64">
          <circle cx="32" cy="32" r="28" strokeWidth="1.5"/>
          <circle cx="32" cy="32" r="22" strokeWidth="0.8"/>
          <text x="32" y="29" textAnchor="middle" fontSize="6.5">ACKNOWLEDGED</text>
          <text x="32" y="37" textAnchor="middle" fontSize="10" fontWeight="800">{new Date(rec.generated_at).toLocaleDateString('en-CA').slice(5)}</text>
          <text x="32" y="46" textAnchor="middle" fontSize="5">RISK · REGRET</text>
        </svg>
      </div>
    </div>
  );
}

// ─── Editorial sparkline ─────────────────────────────────────
function MiniSpark({ up, width=110, height=44 }) {
  const pts = uM2(() => {
    const rnd = (() => { let s = up ? 11 : 33; return () => { s = (s*9301 + 49297) % 233280; return s/233280; }; })();
    const out = [50]; const drift = up ? 0.4 : -0.4;
    for (let i = 0; i < 22; i++) out.push(Math.max(10, Math.min(90, out[out.length-1] + (rnd() - 0.5 + drift*0.1) * 10)));
    if (up) out[out.length-1] = Math.max(out[out.length-1], out[out.length-3] + 5);
    return out;
  }, [up]);
  const min = Math.min(...pts), max = Math.max(...pts), range = max - min || 1;
  const sx = (width - 2) / (pts.length - 1);
  const d = pts.map((p, i) => (i===0?'M':'L') + (1 + i*sx).toFixed(1) + ' ' + (2 + (height - 4) * (1 - (p - min)/range)).toFixed(1)).join(' ');
  const c = up ? 'var(--up)' : 'var(--down)';
  return (
    <svg className="spark" viewBox={`0 0 ${width} ${height}`}>
      <path d={d + ` L ${width-1} ${height-1} L 1 ${height-1} Z`} fill={c} fillOpacity="0.12"/>
      <path d={d} fill="none" stroke={c} strokeWidth="1.4"/>
    </svg>
  );
}

// ─── Editorial card ──────────────────────────────────────────
function EditorialCard({ rec, index, onClick }) {
  const kicker = ['LEAD STORY', 'IN FOCUS', 'WATCH-LIST', 'CONVICTION', 'BRIEFING', 'IDEA OF THE DAY', 'DESK PICK'][index % 7];
  const lede = rec.reasoning.ai;
  return (
    <article className="ed-card" onClick={onClick}>
      <div className="kicker">
        <span>{kicker} · {t('dashboard.market_' + rec.market).toUpperCase()}</span>
        <span style={{ color: 'var(--ink-3)' }}>№ {String(index+1).padStart(2,'0')}</span>
      </div>
      <div className="head-row">
        <span className="sym">{rec.symbol}</span>
        <span className="name">{rec.name}</span>
      </div>
      <div className="body">
        <div className="lede"><em>{rec.scores.fusion}/100.</em> {lede}</div>
        <div className="pricelines">
          <div className="px">{fmt.price(rec.price.current, rec.price.currency)}</div>
          <ChangePct value={rec.price.change_pct} />
          <MiniSpark up={rec.price.change_pct >= 0}/>
        </div>
      </div>
      <div className="meta">
        <span><span className="tag">{t('recommendation.hold_period')}.</span> <span className="v">{rec.recommendation.hold_period_days}d</span></span>
        <span><span className="tag">{t('recommendation.stop_loss')}.</span> <span className="v">{fmt.price(rec.recommendation.stop_loss, rec.price.currency)}</span></span>
        <span><span className="tag">{t('backtest.win_rate')}.</span> <span className="v">{Math.round(rec.backtest.win_rate*100)}%</span> <span style={{ color: 'var(--ink-3)' }}>(n={rec.backtest.sample_size})</span></span>
        <span><span className="tag">{t('risk.loss_probability')}.</span> <span className="v" style={{ color: 'var(--up-strong)' }}>{Math.round(rec.risk.loss_probability*100)}%</span></span>
      </div>
    </article>
  );
}

// ─── Dossier card ────────────────────────────────────────────
function DossierCard({ rec, index, onClick }) {
  const stamp = rec.scores.fusion >= 80 ? 'APPROVED' : rec.scores.fusion >= 65 ? 'REVIEW' : 'CAUTION';
  const stampOk = rec.scores.fusion >= 65;
  return (
    <article className="do-card" onClick={onClick}>
      <span className="case-no">REC.{String(index+1).padStart(3, '0')} · {t('dashboard.market_' + rec.market).toUpperCase()} · {rec.term.toUpperCase()}</span>
      <svg className="stamp" viewBox="0 0 60 60">
        <circle cx="30" cy="30" r="26" strokeWidth="1.4"/>
        <circle cx="30" cy="30" r="20" strokeWidth="0.6"/>
        <text x="30" y="33" textAnchor="middle" fontSize="9" fontWeight="800" style={{ fill: stampOk ? 'var(--stamp)' : 'var(--ink-3)' }}>{stamp}</text>
      </svg>
      <div className="do-head">
        <span className="sym">{rec.symbol}</span>
        <span className="name">{rec.name}</span>
      </div>
      <div className="do-rows">
        <div className="row"><span className="lab">PX / CHG</span><span className="v">{fmt.price(rec.price.current, rec.price.currency)} <span style={{ color: rec.price.change_pct >= 0 ? 'var(--up)' : 'var(--down)' }}>{rec.price.change_pct >= 0 ? '+' : ''}{rec.price.change_pct.toFixed(2)}%</span></span></div>
        <div className="row"><span className="lab">FUSION SCORE</span><span className="v" style={{ color: 'var(--accent-paper)' }}>{rec.scores.fusion}/100</span></div>
        <div className="row"><span className="lab">TECH / AI / EXPERT</span><span className="v">{rec.scores.technical} · {rec.scores.ai} · {rec.scores.expert}</span></div>
        <div className="row"><span className="lab">SL → TP / HOLD</span><span className="v">{fmt.price(rec.recommendation.stop_loss, rec.price.currency)} → {fmt.price(rec.recommendation.take_profit, rec.price.currency)} / {rec.recommendation.hold_period_days}d</span></div>
        <div className="row"><span className="lab">BACKTEST WR (n)</span><span className="v">{Math.round(rec.backtest.win_rate*100)}% (n={rec.backtest.sample_size}){rec.backtest.sample_size < 30 && <span style={{ color: 'var(--warn)' }}> ⚠</span>}</span></div>
        <div className="row"><span className="lab">RISK / LOSS-PROB</span><span className="v" style={{ color: rec.risk.level === 'high' ? 'var(--up-strong)' : rec.risk.level === 'medium' ? 'var(--accent-paper)' : 'var(--down-strong)' }}>{rec.risk.level.toUpperCase()} / {Math.round(rec.risk.loss_probability*100)}%</span></div>
      </div>
      <div className="do-foot">
        <span style={{ color: 'var(--ink-3)' }}>// gen @ {new Date(rec.generated_at).toISOString().slice(11, 16)} UTC · Δ {rec.data_freshness_minutes}m</span>
        <span className="sig">F. Analyst</span>
      </div>
    </article>
  );
}

// ─── Poster card ─────────────────────────────────────────────
function PosterCard({ rec, index, onClick, onSwipe }) {
  const action = rec.recommendation.action;
  const actionClass = action === 'buy' ? 'buy' : action === 'watch' ? 'watch' : 'hold';
  const actionText = { buy: 'BUY', watch: 'WATCH', hold: 'HOLD' }[action];
  const [tx, setTx] = uS2(0);
  const [gone, setGone] = uS2(null);
  const startX = uR2(0);

  const onDown = (e) => {
    startX.current = e.clientX;
    const move = (ev) => setTx(ev.clientX - startX.current);
    const up = (ev) => {
      const dx = ev.clientX - startX.current;
      window.removeEventListener('pointermove', move);
      window.removeEventListener('pointerup', up);
      if (Math.abs(dx) > 110 && onSwipe) {
        setGone(dx > 0 ? 'right' : 'left');
        setTimeout(() => onSwipe(dx > 0 ? 'right' : 'left'), 260);
      } else {
        setTx(0);
      }
    };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  };

  const ignoreOp = Math.max(0, -tx / 110);
  const buyOp = Math.max(0, tx / 110);

  return (
    <div className={'swipe-wrap' + (gone ? (gone === 'left' ? ' gone-left' : ' gone-right') : '')}
         style={{ transform: gone ? undefined : `translateX(${tx}px) rotate(${tx*0.04}deg)` }}>
      <article className="po-card" onPointerDown={onDown} onClick={(e) => { if (Math.abs(tx) < 6) onClick(); }}>
        <div className={'po-top ' + actionClass}>
          <span className="action-shout">{actionText}</span>
          <div className="po-sym">
            <div className="sym">{rec.symbol}</div>
            <div className="name">{rec.name}</div>
          </div>
          <div className="conv">{rec.scores.fusion}</div>
        </div>
        <div className="po-bottom">
          <div className="po-stat">
            <div className="lab">{t('dashboard.market_' + rec.market)} · NOW</div>
            <div className="v">{fmt.price(rec.price.current, rec.price.currency)}</div>
            <div style={{ fontSize: 11, marginTop: 2 }}><ChangePct value={rec.price.change_pct} /></div>
          </div>
          <div className="po-stat">
            <div className="lab">{t('backtest.win_rate')}</div>
            <div className="v" style={{ color: rec.backtest.win_rate >= 0.6 ? 'var(--up-strong)' : 'var(--ink)' }}>
              {Math.round(rec.backtest.win_rate*100)}%
              {rec.backtest.sample_size < 30 && <span style={{ color: 'var(--warn)', fontSize: 12, marginLeft: 3 }}>⚠</span>}
            </div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 2 }}>n = {rec.backtest.sample_size}</div>
          </div>
          <div className="po-stat">
            <div className="lab">{t('regret.loss_prob')}</div>
            <div className="v" style={{ color: 'var(--up-strong)' }}>{Math.round(rec.risk.loss_probability*100)}%</div>
            <div style={{ fontSize: 10, color: 'var(--ink-3)', marginTop: 2 }}>{t('recommendation.hold_days', { days: rec.recommendation.hold_period_days })}</div>
          </div>
        </div>
        <div className="swipe-overlay" style={{ opacity: Math.max(ignoreOp, buyOp) }}>
          <span className="badge ignore" style={{ opacity: ignoreOp }}>IGNORE</span>
          <span className="badge buy" style={{ opacity: buyOp }}>BUY</span>
        </div>
      </article>
    </div>
  );
}

// ─── Card flip wrapper ───────────────────────────────────────
function FlippableCard({ rec, children }) {
  const [flipped, setFlipped] = uS2(false);
  const lang = window.__LANG || 'zh';
  // Contrarian / bear case text
  const bearCase = {
    NVDA: { zh: '估值已计入大部分 AI 乐观预期。若数据中心 capex 增速放缓或 Blackwell 良率不及预期,可能出现 -15% 以上回调。', en: 'Valuation prices in most AI optimism. A slowdown in data-center capex or Blackwell yield miss could trigger a >15% drawdown.' },
    '600519.SH': { zh: '消费分化加剧,千元价格带承压。批价若再下台阶,情绪杀估值风险偏大。', en: 'Consumer divergence intensifying. A further wholesale-price step-down could re-rate sentiment sharply.' },
    'BTC/USDT': { zh: 'ETF 资金可能因风险事件迅速流出,长期持有者获利了结风险随价格上行同步上升。', en: 'ETF flows can reverse on macro shocks; LTH profit-taking risk rises with price.' },
    AAPL: { zh: 'iPhone 周期边际效用递减,服务收入增速正在下行。Vision Pro 出货远低预期。', en: 'iPhone cycle marginal utility declines. Services growth decelerating. Vision Pro shipments well below expectations.' },
    TSLA: { zh: 'Robotaxi 兑现节点不确定性极高,核心车型毛利率持续承压。', en: 'Robotaxi delivery timeline highly uncertain. Core vehicle gross margin keeps compressing.' },
    '300750.SZ': { zh: '海外政策风险尚未充分定价。储能毛利率竞争加剧。', en: 'Overseas policy risk under-priced. Storage margin competition intensifying.' },
    'ETH/USDT': { zh: 'L2 蚕食主网价值捕获能力,ETF 资金流不及 BTC。', en: 'L2s erode mainnet value capture. ETF flows lag BTC.' },
  };
  const bc = bearCase[rec.symbol] || { zh: '反方观点暂未生成。', en: 'No contrarian view available.' };
  return (
    <div className="flip-wrap" style={{ position: 'relative' }}>
      <button className="flip-btn" onClick={(e) => { e.stopPropagation(); setFlipped(!flipped); }} aria-label="flip">
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
          <path d="M3 12a9 9 0 0 1 15.5-6.3L21 8"/><path d="M21 3v5h-5"/>
        </svg>
      </button>
      <div className="flip-inner" style={{ transform: flipped ? 'rotateY(180deg)' : 'none' }}>
        <div className="flip-face">{children}</div>
        <div className="flip-back" style={{ display: flipped ? 'flex' : 'none' }}>
          <div className="bk-ttl">
            <Icon.alert width="14" height="14"/>
            {lang === 'zh' ? '反方观点 · Bear Case' : 'Contrarian View · Bear Case'}
          </div>
          <div className="bk-text">{bc[lang]}</div>
          <div className="bk-foot">— {rec.symbol} · {lang === 'zh' ? '由 AI 红队生成,与正方平衡参考' : 'Generated by AI red team — for balanced consideration'}</div>
        </div>
      </div>
    </div>
  );
}

Object.assign(window, {
  PulseDot, MarketRadar, AnalystBubbles, DecisionTimeline, ContractRegret,
  EditorialCard, DossierCard, PosterCard, FlippableCard, MiniSpark,
});
