← back to Bertha

kalshi-dash/src/pages/Pulse.jsx

749 lines

import React, { useState, useEffect, useMemo } from 'react';
import { api } from '../api';

// ── Pulse Category Grid Configuration ──
const PULSE_CATEGORIES = [
  {
    name: 'Economy',
    icon: '\u{1F4B0}',
    subcategories: ['GDP', 'Inflation', 'Jobs', 'Fed Rate', 'Recession', 'Trade', 'Debt', 'Markets'],
    keywords: ['gdp', 'inflation', 'cpi', 'unemployment', 'jobs', 'payroll', 'fed', 'interest rate', 'recession', 'stock', 'sp500', 'nasdaq', 'dow', 'treasury', 'bond', 'debt', 'trade', 'tariff', 'deficit'],
    cssColor: 'var(--green)',
    dimColor: 'var(--green-dim)',
    rgbColor: [0, 195, 137],
  },
  {
    name: 'Politics',
    icon: '\u{1F3DB}',
    subcategories: ['White House', 'Congress', 'Elections', 'SCOTUS', 'Cabinet', 'Polls', 'Policy', 'Foreign'],
    keywords: ['trump', 'president', 'impeach', 'congress', 'senate', 'house', 'governor', 'election', 'democrat', 'republican', 'vote', 'biden', 'vance', 'cabinet', 'scotus', 'supreme', 'pardon', 'approval', 'poll', 'secretary', 'defense', 'attorney'],
    cssColor: 'var(--blue)',
    dimColor: 'var(--blue-dim)',
    rgbColor: [59, 130, 246],
  },
  {
    name: 'Weather',
    icon: '\u{26C8}',
    subcategories: ['Temperature', 'Storms', 'Snow', 'Rain', 'Hurricanes', 'Heat', 'Wind', 'Climate'],
    keywords: ['temperature', 'rain', 'snow', 'hurricane', 'weather', 'tornado', 'flood', 'heat', 'cold', 'storm', 'wind', 'precipitation', 'climate'],
    cssColor: 'var(--cyan)',
    dimColor: 'var(--cyan-dim)',
    rgbColor: [0, 240, 255],
  },
  {
    name: 'Technology',
    icon: '\u{1F4BB}',
    subcategories: ['AI', 'SpaceX', 'Tesla', 'Big Tech', 'Startups', 'Chips', 'Cyber', 'Social'],
    keywords: ['ai', 'spacex', 'tesla', 'apple', 'google', 'meta', 'openai', 'chatgpt', 'tech', 'rocket', 'launch'],
    cssColor: 'var(--cyan)',
    dimColor: 'var(--cyan-dim)',
    rgbColor: [0, 240, 255],
  },
  {
    name: 'Crypto',
    icon: '\u{1FA99}',
    subcategories: ['Bitcoin', 'Ethereum', 'DeFi', 'NFTs', 'Regulation', 'Mining', 'Stablecoins', 'Altcoins'],
    keywords: ['bitcoin', 'btc', 'ethereum', 'eth', 'crypto', 'token', 'blockchain', 'defi'],
    cssColor: 'var(--orange)',
    dimColor: 'var(--orange-dim)',
    rgbColor: [247, 147, 26],
  },
  {
    name: 'Sports',
    icon: '\u{1F3C8}',
    subcategories: ['NFL', 'NBA', 'MLB', 'Soccer', 'Tennis', 'UFC', 'Olympics', 'Playoffs'],
    keywords: ['nfl', 'nba', 'mlb', 'nhl', 'soccer', 'tennis', 'super bowl', 'championship', 'game', 'match', 'playoff', 'world series'],
    cssColor: 'var(--purple)',
    dimColor: 'var(--purple-dim)',
    rgbColor: [168, 85, 247],
  },
  {
    name: 'Energy',
    icon: '\u{26A1}',
    subcategories: ['Oil', 'Gas', 'Solar', 'Nuclear', 'OPEC', 'EVs', 'Grid', 'Coal'],
    keywords: ['oil', 'gas', 'energy', 'solar', 'opec', 'crude', 'coal', 'renewable'],
    cssColor: 'var(--orange)',
    dimColor: 'var(--orange-dim)',
    rgbColor: [247, 147, 26],
  },
  {
    name: 'Culture',
    icon: '\u{1F3AC}',
    subcategories: ['Movies', 'Music', 'Awards', 'TV', 'Social Media', 'Trends', 'Celebrity', 'Gaming'],
    keywords: ['oscar', 'movie', 'tv', 'celebrity', 'grammy', 'award', 'music', 'entertainment'],
    cssColor: 'var(--orange)',
    dimColor: 'var(--orange-dim)',
    rgbColor: [247, 147, 26],
  },
];

// Keep original baseColor arrays for data logic compatibility
PULSE_CATEGORIES.forEach((cat, i) => {
  cat.baseColor = cat.rgbColor;
});

function getHeatColor(intensity, baseColor) {
  const [r, g, b] = baseColor;
  const alpha = Math.max(0.05, Math.min(0.9, intensity));
  return `rgba(${r},${g},${b},${alpha})`;
}

function getHeatBorder(intensity, baseColor) {
  const [r, g, b] = baseColor;
  const alpha = Math.max(0.1, Math.min(0.6, intensity * 0.8));
  return `rgba(${r},${g},${b},${alpha})`;
}

function getTextColor(intensity, baseColor) {
  const [r, g, b] = baseColor;
  if (intensity > 0.5) return `rgb(${Math.min(255, r + 60)},${Math.min(255, g + 60)},${Math.min(255, b + 60)})`;
  if (intensity > 0.2) return `rgb(${r},${g},${b})`;
  return 'var(--text-muted)';
}

// ── Pulse Cell Component ──
function PulseCell({ subcategory, intensity, signalCount, sentiment, baseColor, onClick, isSelected }) {
  const bg = getHeatColor(intensity, baseColor);
  const border = getHeatBorder(intensity, baseColor);
  const textColor = getTextColor(intensity, baseColor);
  const scanLines = intensity > 0.3;

  return (
    <div
      onClick={onClick}
      className={`relative overflow-hidden rounded-lg cursor-pointer group ${isSelected ? 'z-10' : ''}`}
      style={{
        backgroundColor: bg,
        border: isSelected
          ? '2px solid var(--cyan)'
          : `1px solid ${border}`,
        minHeight: 70,
        transform: isSelected ? 'scale(1.05)' : undefined,
        transition: 'all 200ms ease',
        boxShadow: isSelected ? '0 0 16px rgba(0,240,255,0.2)' : undefined,
      }}
    >
      {/* Thermal scan lines effect */}
      {scanLines && (
        <div
          className="absolute inset-0 opacity-10 pointer-events-none"
          style={{
            backgroundImage: `repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(255,255,255,0.03) 2px, rgba(255,255,255,0.03) 4px)`,
          }}
        />
      )}

      {/* Pulse animation for high intensity */}
      {intensity > 0.6 && (
        <div
          className="absolute inset-0 animate-pulse opacity-30 pointer-events-none"
          style={{
            background: `radial-gradient(circle at center, rgba(${baseColor.join(',')},0.4), transparent 70%)`,
            animationDuration: `${3 - intensity * 2}s`,
          }}
        />
      )}

      {/* Hover glow */}
      <div
        className="absolute inset-0 opacity-0 group-hover:opacity-100 pointer-events-none"
        style={{
          background: `radial-gradient(circle at center, rgba(${baseColor.join(',')},0.08), transparent)`,
          transition: 'opacity 150ms ease',
        }}
      />

      <div className="relative p-2.5 h-full flex flex-col justify-between z-10">
        <div>
          <p className="text-[10px] font-bold uppercase tracking-wider" style={{ color: textColor }}>
            {subcategory}
          </p>
        </div>
        <div className="flex items-end justify-between mt-1">
          <div>
            {signalCount > 0 ? (
              <p className="text-lg font-bold mono" style={{ color: textColor }}>
                {signalCount}
              </p>
            ) : (
              <p className="text-sm" style={{ color: 'var(--text-muted)' }}>--</p>
            )}
          </div>
          {sentiment !== 0 && (
            <div
              className="text-[9px] mono font-bold px-1.5 py-0.5 rounded"
              style={
                sentiment > 0
                  ? { background: 'var(--green-dim)', color: 'var(--green)' }
                  : { background: 'var(--red-dim)', color: 'var(--red)' }
              }
            >
              {sentiment > 0 ? '+' : ''}{sentiment.toFixed(2)}
            </div>
          )}
        </div>
      </div>

      {/* Intensity bar at bottom */}
      <div
        className="absolute bottom-0 left-0 right-0 h-0.5"
        style={{
          background: `linear-gradient(90deg, transparent, rgba(${baseColor.join(',')},${intensity}), transparent)`,
        }}
      />
    </div>
  );
}

// ── Category Row ──
function CategoryRow({ category, subcategoryData, onCellClick, selectedCell }) {
  const totalSignals = subcategoryData.reduce((s, d) => s + d.signalCount, 0);
  const maxIntensity = Math.max(...subcategoryData.map(d => d.intensity), 0);
  const avgSentiment = subcategoryData.reduce((s, d) => s + d.sentiment, 0) / subcategoryData.length;

  return (
    <div className="space-y-2">
      {/* Category Header */}
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-2">
          <span className="text-lg">{category.icon}</span>
          <h3 className="text-sm font-bold heading" style={{ color: category.cssColor }}>
            {category.name}
          </h3>
          <span className="text-[10px]" style={{ color: 'var(--text-muted)' }}>{totalSignals} signals</span>
        </div>
        <div className="flex items-center gap-3 text-[10px]">
          <span style={{ color: 'var(--text-muted)' }}>
            Peak:{' '}
            <span className="font-bold" style={{ color: category.cssColor }}>
              {(maxIntensity * 100).toFixed(0)}%
            </span>
          </span>
          <span
            className="mono"
            style={{ color: avgSentiment > 0 ? 'var(--green)' : avgSentiment < 0 ? 'var(--red)' : 'var(--text-muted)' }}
          >
            Sentiment: {avgSentiment > 0 ? '+' : ''}{avgSentiment.toFixed(3)}
          </span>
        </div>
      </div>

      {/* Subcategory Grid */}
      <div className="grid grid-cols-8 gap-2">
        {subcategoryData.map(sub => (
          <PulseCell
            key={sub.name}
            subcategory={sub.name}
            intensity={sub.intensity}
            signalCount={sub.signalCount}
            sentiment={sub.sentiment}
            baseColor={category.baseColor}
            onClick={() => onCellClick({ category: category.name, subcategory: sub.name, ...sub })}
            isSelected={selectedCell?.category === category.name && selectedCell?.subcategory === sub.name}
          />
        ))}
      </div>
    </div>
  );
}

// ── Intensity Scale ──
function IntensityScale() {
  const steps = 10;
  return (
    <div className="flex items-center gap-2">
      <span className="stat-label">Low</span>
      <div className="flex gap-px">
        {Array.from({ length: steps }).map((_, i) => (
          <div
            key={i}
            className="w-5 h-3 rounded-sm"
            style={{
              backgroundColor: `rgba(0,240,255,${(i + 1) / steps * 0.7 + 0.05})`,
            }}
          />
        ))}
      </div>
      <span className="stat-label">High</span>
    </div>
  );
}

// ── Cell Detail Panel ──
function CellDetail({ cell, signals, onClose }) {
  if (!cell) return null;

  const cellSignals = signals.filter(s => {
    const lower = (s.reasoning || s.ticker || '').toLowerCase();
    return cell.matchingKeywords?.some(kw => lower.includes(kw));
  }).slice(0, 10);

  return (
    <div className="card-glow fade-in">
      <div className="flex items-start justify-between mb-3">
        <div>
          <div className="flex items-center gap-2 mb-1">
            <span className="text-lg">{cell.categoryIcon}</span>
            <h3 className="heading text-sm font-bold" style={{ color: 'var(--text)' }}>
              {cell.category} / {cell.subcategory}
            </h3>
          </div>
          <p className="text-xs" style={{ color: 'var(--text-muted)' }}>
            {cell.signalCount} signals | Intensity: {(cell.intensity * 100).toFixed(0)}% | Sentiment:{' '}
            <span style={{ color: cell.sentiment > 0 ? 'var(--green)' : cell.sentiment < 0 ? 'var(--red)' : 'var(--text-dim)' }}>
              {cell.sentiment > 0 ? '+' : ''}{cell.sentiment.toFixed(3)}
            </span>
          </p>
        </div>
        <button onClick={onClose} className="btn btn-ghost text-lg leading-none" style={{ padding: '0 6px' }}>
          &times;
        </button>
      </div>

      {cellSignals.length > 0 ? (
        <div className="space-y-2">
          <p className="stat-label mb-1">Related Signals</p>
          {cellSignals.map((sig, i) => (
            <div
              key={i}
              className="flex items-center gap-3 text-xs rounded-lg px-3 py-2"
              style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
            >
              <span
                className="badge"
                style={
                  sig.direction === 'BUY_YES'
                    ? { background: 'var(--green-dim)', color: 'var(--green)' }
                    : { background: 'var(--red-dim)', color: 'var(--red)' }
                }
              >
                {sig.direction}
              </span>
              <span className="mono truncate flex-1" style={{ color: 'var(--text-dim)' }}>{sig.ticker}</span>
              <span className="mono font-bold" style={{ color: 'var(--orange)' }}>
                {(parseFloat(sig.expected_edge) * 100).toFixed(1)}%
              </span>
              <span className="mono" style={{ color: 'var(--blue)' }}>
                {(parseFloat(sig.confidence) * 100).toFixed(0)}%
              </span>
              {sig.risk_approved ? (
                <span className="text-[9px] font-bold" style={{ color: 'var(--green)' }}>OK</span>
              ) : (
                <span className="text-[9px] font-bold" style={{ color: 'var(--red)' }}>BLOCKED</span>
              )}
            </div>
          ))}
        </div>
      ) : (
        <p className="text-sm text-center py-4" style={{ color: 'var(--text-muted)' }}>
          No matching signals for this subcategory
        </p>
      )}
    </div>
  );
}

// ── National Overview Bar ──
function NationalOverview({ categoryStats }) {
  const maxSignals = Math.max(...categoryStats.map(c => c.totalSignals), 1);

  return (
    <div className="card">
      <div className="flex items-center gap-2 mb-4">
        <h2 className="section-title">National Activity Overview</h2>
        <span className="section-subtitle">Signal density across categories</span>
      </div>
      <div className="space-y-2">
        {categoryStats.sort((a, b) => b.totalSignals - a.totalSignals).map(cat => {
          const pct = (cat.totalSignals / maxSignals) * 100;
          return (
            <div key={cat.name} className="flex items-center gap-3">
              <span className="text-sm w-5">{cat.icon}</span>
              <span className="text-xs font-medium w-20 heading" style={{ color: cat.cssColor || `rgb(${cat.baseColor.join(',')})` }}>
                {cat.name}
              </span>
              <div
                className="flex-1 h-5 rounded-full overflow-hidden relative"
                style={{ background: 'var(--surface-el)' }}
              >
                <div
                  className="h-full rounded-full transition-all duration-1000 relative overflow-hidden"
                  style={{
                    width: `${Math.max(pct, 2)}%`,
                    background: `linear-gradient(90deg, rgba(${cat.baseColor.join(',')},0.3), rgba(${cat.baseColor.join(',')},0.6))`,
                  }}
                >
                  {/* Animated shimmer */}
                  <div
                    className="absolute inset-0"
                    style={{
                      background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.1) 50%, transparent 100%)',
                      animation: 'shimmer 2s infinite',
                    }}
                  />
                </div>
                <span
                  className="absolute right-2 top-0 bottom-0 flex items-center text-[10px] mono"
                  style={{ color: 'var(--text-dim)' }}
                >
                  {cat.totalSignals}
                </span>
              </div>
              <span
                className="text-[10px] mono w-12 text-right"
                style={{ color: cat.avgSentiment > 0 ? 'var(--green)' : cat.avgSentiment < 0 ? 'var(--red)' : 'var(--text-muted)' }}
              >
                {cat.avgSentiment > 0 ? '+' : ''}{cat.avgSentiment.toFixed(2)}
              </span>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ── Main Pulse Page ──
export default function Pulse() {
  const [data, setData] = useState(null);
  const [signals, setSignals] = useState(null);
  const [signalsDash, setSignalsDash] = useState(null);
  const [loading, setLoading] = useState(true);
  const [selectedCell, setSelectedCell] = useState(null);
  const [timeWindow, setTimeWindow] = useState('24h');

  useEffect(() => {
    load();
    const i = setInterval(load, 30000);
    return () => clearInterval(i);
  }, []);

  async function load() {
    try {
      const [d, s, sd] = await Promise.all([
        api('/dashboard'),
        api('/signals'),
        api('/signals/dashboard'),
      ]);
      setData(d);
      setSignals(s?.signals || []);
      setSignalsDash(sd);
    } catch {}
    setLoading(false);
  }

  const { categoryData, categoryStats, overallStats } = useMemo(() => {
    if (!signals) return { categoryData: [], categoryStats: [], overallStats: {} };

    const allSignals = signals || [];
    const maxSubSignals = Math.max(
      ...PULSE_CATEGORIES.flatMap(cat =>
        cat.subcategories.map(sub => {
          const keywords = cat.keywords.filter(kw => sub.toLowerCase().includes(kw.split(' ')[0]) || kw.includes(sub.toLowerCase()));
          const matching = allSignals.filter(s => {
            const lower = (s.reasoning || s.ticker || '').toLowerCase();
            return cat.keywords.some(kw => lower.includes(kw));
          });
          return matching.length;
        })
      ),
      1
    );

    const catData = PULSE_CATEGORIES.map(category => {
      const categorySignals = allSignals.filter(s => {
        const lower = (s.reasoning || s.ticker || '').toLowerCase();
        return category.keywords.some(kw => lower.includes(kw));
      });

      const totalCatSignals = categorySignals.length;
      const avgCatSentiment = categorySignals.length > 0
        ? categorySignals.reduce((s, sig) => {
            const sentSum = (sig.sources || []).reduce((ss, src) => ss + (src.sentiment || 0), 0);
            return s + sentSum;
          }, 0) / Math.max(categorySignals.length, 1)
        : 0;

      const subcategoryData = category.subcategories.map(sub => {
        const subKeywords = category.keywords.filter(kw => {
          const subLower = sub.toLowerCase();
          return kw.includes(subLower) || subLower.includes(kw.split(' ')[0]);
        });

        let matchingSignals;
        if (subKeywords.length > 0) {
          matchingSignals = allSignals.filter(s => {
            const lower = (s.reasoning || s.ticker || '').toLowerCase();
            return subKeywords.some(kw => lower.includes(kw));
          });
        } else {
          const hash = sub.split('').reduce((h, c) => ((h << 5) - h + c.charCodeAt(0)) | 0, 0);
          matchingSignals = categorySignals.filter((_, i) => i % category.subcategories.length === Math.abs(hash) % category.subcategories.length);
        }

        const signalCount = matchingSignals.length;
        const sentiment = matchingSignals.reduce((s, sig) => {
          const sentSum = (sig.sources || []).reduce((ss, src) => ss + (src.sentiment || 0), 0);
          return s + sentSum;
        }, 0) / Math.max(matchingSignals.length, 1);

        const intensity = Math.min(1, signalCount / Math.max(maxSubSignals * 0.5, 1));

        return {
          name: sub,
          signalCount,
          sentiment,
          intensity,
          matchingKeywords: subKeywords.length > 0 ? subKeywords : category.keywords,
          categoryIcon: category.icon,
        };
      });

      return { category, subcategoryData };
    });

    const catStats = PULSE_CATEGORIES.map(cat => {
      const catSignals = allSignals.filter(s => {
        const lower = (s.reasoning || s.ticker || '').toLowerCase();
        return cat.keywords.some(kw => lower.includes(kw));
      });
      return {
        name: cat.name,
        icon: cat.icon,
        baseColor: cat.baseColor,
        cssColor: cat.cssColor,
        totalSignals: catSignals.length,
        avgSentiment: catSignals.length > 0
          ? catSignals.reduce((s, sig) => s + (sig.sources || []).reduce((ss, src) => ss + (src.sentiment || 0), 0), 0) / catSignals.length
          : 0,
      };
    });

    const overall = {
      totalSignals: allSignals.length,
      buyYes: allSignals.filter(s => s.direction === 'BUY_YES').length,
      buyNo: allSignals.filter(s => s.direction === 'BUY_NO').length,
      approved: allSignals.filter(s => s.risk_approved).length,
      avgEdge: allSignals.length > 0 ? allSignals.reduce((s, sig) => s + parseFloat(sig.expected_edge || 0), 0) / allSignals.length : 0,
      avgConf: allSignals.length > 0 ? allSignals.reduce((s, sig) => s + parseFloat(sig.confidence || 0), 0) / allSignals.length : 0,
      hottest: [...catStats].sort((a, b) => b.totalSignals - a.totalSignals)[0]?.name || 'None',
    };

    return { categoryData: catData, categoryStats: catStats, overallStats: overall };
  }, [signals]);

  if (loading) return (
    <div className="flex items-center justify-center h-64">
      <div
        className="w-8 h-8 rounded-full border-2 border-t-transparent animate-spin"
        style={{ borderColor: 'var(--cyan)', borderTopColor: 'transparent' }}
      />
    </div>
  );

  const overallStatsConfig = [
    { label: 'Total Signals',   value: overallStats.totalSignals || 0,                              color: 'var(--cyan)' },
    { label: 'BUY YES',         value: overallStats.buyYes || 0,                                     color: 'var(--green)' },
    { label: 'BUY NO',          value: overallStats.buyNo || 0,                                      color: 'var(--red)' },
    { label: 'Approved',        value: overallStats.approved || 0,                                   color: 'var(--green)' },
    { label: 'Avg Edge',        value: `${((overallStats.avgEdge || 0) * 100).toFixed(1)}%`,         color: 'var(--orange)' },
    { label: 'Avg Confidence',  value: `${((overallStats.avgConf || 0) * 100).toFixed(0)}%`,         color: 'var(--blue)' },
    { label: 'Hottest Sector',  value: overallStats.hottest,                                         color: 'var(--red)' },
  ];

  return (
    <div className="space-y-6">
      {/* Shimmer + hover scale CSS */}
      <style>{`
        @keyframes shimmer {
          0% { transform: translateX(-100%); }
          100% { transform: translateX(200%); }
        }
      `}</style>

      {/* Page Header */}
      <div className="flex items-center justify-between">
        <div>
          <h1 className="heading text-2xl font-bold flex items-center gap-3">
            {/* Live pulsing indicator */}
            <span
              className="inline-flex items-center justify-center w-3 h-3 rounded-full pulse-cyan"
              style={{ backgroundColor: 'var(--cyan)', flexShrink: 0 }}
            />
            <span className="gradient-cyan">Pulse of America</span>
          </h1>
          <p className="section-subtitle mt-1">
            Sentiment and activity heatmap across American prediction markets
          </p>
        </div>
        <div className="flex items-center gap-3">
          <div
            className="flex items-center gap-0.5 rounded-lg p-0.5"
            style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
          >
            {['24h', '7d', '30d'].map(tw => (
              <button
                key={tw}
                onClick={() => setTimeWindow(tw)}
                className="btn btn-sm"
                style={timeWindow === tw
                  ? { background: 'var(--cyan)', color: 'var(--bg)', fontWeight: 700 }
                  : { background: 'transparent', color: 'var(--text-muted)' }
                }
              >
                {tw}
              </button>
            ))}
          </div>
          <button onClick={load} className="btn btn-o btn-sm">Refresh</button>
        </div>
      </div>

      {/* Overall Stats */}
      <div className="grid grid-cols-7 gap-3">
        {overallStatsConfig.map(s => (
          <div key={s.label} className="card">
            <p className="stat-label mb-1">{s.label}</p>
            <p className="stat-v text-lg" style={{ color: s.color }}>{s.value}</p>
          </div>
        ))}
      </div>

      {/* National Overview */}
      <NationalOverview categoryStats={categoryStats} />

      {/* Live Signal Heatmap Grid */}
      <div className="card p-4">
        <div className="flex items-center justify-between mb-5">
          <div className="flex items-center gap-2">
            <div
              className="w-2 h-2 rounded-full animate-pulse"
              style={{ backgroundColor: 'var(--red)' }}
            />
            <h2 className="section-title">Live Signal Heatmap</h2>
          </div>
          <IntensityScale />
        </div>

        <div className="space-y-5">
          {categoryData.map(({ category, subcategoryData }) => (
            <CategoryRow
              key={category.name}
              category={category}
              subcategoryData={subcategoryData}
              onCellClick={cell => setSelectedCell(
                selectedCell?.category === cell.category && selectedCell?.subcategory === cell.subcategory
                  ? null
                  : cell
              )}
              selectedCell={selectedCell}
            />
          ))}
        </div>
      </div>

      {/* Cell Detail */}
      {selectedCell && (
        <CellDetail
          cell={selectedCell}
          signals={signals || []}
          onClose={() => setSelectedCell(null)}
        />
      )}

      {/* Signal Source Breakdown */}
      <div className="card">
        <div className="flex items-center gap-2 mb-4">
          <h2 className="section-title">Signal Source Breakdown</h2>
        </div>
        <div className="grid grid-cols-4 gap-3">
          {(signalsDash?.sourceBreakdown || []).slice(0, 8).map(src => {
            const sent = parseFloat(src.avg_sent) || 0;
            const count = parseInt(src.cnt) || 0;
            return (
              <div
                key={src.source}
                className="rounded-lg p-3"
                style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
              >
                <div className="flex items-center justify-between mb-2">
                  <span className="text-xs font-bold heading" style={{ color: 'var(--text-dim)' }}>
                    {src.source.replace('_', ' ').toUpperCase()}
                  </span>
                  <span className="mono text-xs font-bold" style={{ color: 'var(--orange)' }}>{count}</span>
                </div>
                <div className="flex items-center gap-2">
                  <div className="progress-bar flex-1">
                    <div
                      className="progress-fill"
                      style={{
                        width: `${Math.min(100, Math.abs(sent) * 5000 + 10)}%`,
                        background: sent > 0 ? 'var(--green)' : sent < 0 ? 'var(--red)' : 'var(--text-muted)',
                      }}
                    />
                  </div>
                  <span
                    className="text-[10px] mono"
                    style={{ color: sent > 0 ? 'var(--green)' : sent < 0 ? 'var(--red)' : 'var(--text-muted)' }}
                  >
                    {sent > 0 ? '+' : ''}{sent.toFixed(3)}
                  </span>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* Market Pulse Metrics from Dashboard */}
      {data && (
        <div className="card">
          <div className="flex items-center gap-2 mb-4">
            <h2 className="section-title">Market Pulse Metrics</h2>
          </div>
          <div className="grid grid-cols-5 gap-3 text-center">
            {[
              {
                label: 'Balance',
                value: `$${data.balance ? (data.balance / 100).toLocaleString(undefined, { minimumFractionDigits: 0 }) : '0'}`,
                color: 'var(--green)',
              },
              {
                label: "Today's P&L",
                value: `${(data.daily_pnl || 0) >= 0 ? '+' : ''}$${((data.daily_pnl || 0) / 100).toFixed(2)}`,
                color: (data.daily_pnl || 0) >= 0 ? 'var(--green)' : 'var(--red)',
              },
              {
                label: "Today's Trades",
                value: (data.today_trades || 0).toLocaleString(),
                color: 'var(--orange)',
              },
              {
                label: 'Win Rate',
                value: `${data.today_trades > 0 ? ((data.today_wins / data.today_trades) * 100).toFixed(1) : '0'}%`,
                color: data.today_trades > 0 && (data.today_wins / data.today_trades * 100) >= 50
                  ? 'var(--green)'
                  : 'var(--red)',
              },
              {
                label: 'Markets Tracked',
                value: data.markets_tracked || 0,
                color: 'var(--purple)',
              },
            ].map(s => (
              <div
                key={s.label}
                className="rounded-lg p-3"
                style={{ background: 'var(--surface-el)', border: '1px solid var(--border)' }}
              >
                <p className="stat-label mb-1">{s.label}</p>
                <p className="stat-v text-lg" style={{ color: s.color }}>{s.value}</p>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}