// Open Data Eval — Metadata Scorecard v5.0
// Reads from window.SCORECARD_DATA (generated by eval/metadata_eval.py)
// Uses global React + ReactDOM — no bundler required

const { useState, useMemo } = React;
const DATA = window.SCORECARD_DATA || [];

// ─── constants ───────────────────────────────────────────────────
const USECASES = [
  {
    key: "actRecog", label: "Action Recognition", field: "actRecog",
    desc: "Temporal activity understanding — classifying and localizing actions in egocentric video",
    profile: { critical:["RGB video"], important:["Audio","Narrations/captions","Optical flow"], bonus:["Eye gaze","Hand pose annotations","IMU"] },
  },
  {
    key: "handObj", label: "Hand-Object Interaction", field: "handPose",
    desc: "Hand pose, grasping, and manipulation analysis from first-person view",
    profile: { critical:["RGB video","Hand pose annotations"], important:["Depth (RGB-D)","Depth (stereo)"], bonus:["Tactile","IMU","Force/torque"] },
  },
  {
    key: "nav", label: "Navigation", field: "nav",
    desc: "Navigation, 3D reconstruction, SLAM, and scene-level reasoning",
    profile: { critical:["RGB video","IMU"], important:["Depth (RGB-D)","SLAM/odometry","3D point clouds"], bonus:["LiDAR","Depth (stereo)","Eye gaze"] },
  },
];

const DIMS = [
  { key: "technical",     label: "Technical",     compute: d => avg([d.fps, d.res]),  desc: "Frame rate and resolution quality" },
  { key: "scale",         label: "Scale",         compute: d => d.scale,              desc: "Hours, environments, and participant diversity" },
  { key: "annotation",    label: "Annotation",    compute: d => d.annCov,             desc: "Annotation coverage across the dataset" },
  { key: "accessibility", label: "Accessibility", compute: d => avg([d.lic, d.acc]),  desc: "License clarity and ease of access" },
  { key: "tooling",       label: "Tooling",       compute: d => avg([d.dl, d.doc]),   desc: "Dataloader support and documentation quality" },
];

// ─── hooks ───────────────────────────────────────────────────────
function useIsMobile() {
  const [mob, setMob] = useState(window.innerWidth < 600);
  React.useEffect(() => {
    const h = () => setMob(window.innerWidth < 600);
    window.addEventListener("resize", h);
    return () => window.removeEventListener("resize", h);
  }, []);
  return mob;
}

// ─── helpers ─────────────────────────────────────────────────────
function avg(vals) {
  const nonNull = vals.filter(v => v != null);
  if (!nonNull.length) return null;
  return Math.round(nonNull.reduce((a, b) => a + b, 0) / nonNull.length * 1000) / 1000;
}
function scoreColor(v) {
  if (v == null) return "var(--c-null)";
  if (v >= 0.7)  return "var(--c-green)";
  if (v >= 0.4)  return "var(--c-amber)";
  return "var(--c-red)";
}
function scoreBg(v) {
  if (v == null) return "var(--c-track)";
  if (v >= 0.7)  return "var(--c-green-bg)";
  if (v >= 0.4)  return "var(--c-amber-bg)";
  return "var(--c-red-bg)";
}
function completenessColor(v) {
  if (v == null) return "var(--c-null)";
  if (v >= 0.8)  return "var(--c-green)";
  if (v >= 0.55) return "var(--c-amber)";
  return "var(--c-red)";
}
function fmtPct(v) { return v == null ? "—" : `${Math.round(v * 100)}`; }
function fmtNum(v) {
  if (v == null) return "—";
  const n = Number(v);
  if (isNaN(n)) return "—";
  if (n >= 10000) return `${Math.round(n / 1000)}k`;
  if (n >= 1000)  return `${(n / 1000).toFixed(1)}k`;
  return Math.round(n).toString();
}

// ─── completion ring ─────────────────────────────────────────────
function CompletionRing({ value }) {
  const size = 42, stroke = 3.5, r = (size - stroke) / 2;
  const circ = 2 * Math.PI * r;
  const dash = value != null ? circ * Math.min(value, 1) : 0;
  const col = completenessColor(value);
  return (
    <div style={{ position:"relative", width:size, height:size, opacity:0.85 }}>
      <svg width={size} height={size} style={{ transform:"rotate(-90deg)", display:"block" }}>
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--c-track)" strokeWidth={stroke} />
        <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={col} strokeWidth={stroke}
          strokeDasharray={`${dash} ${circ}`} strokeLinecap="round" />
      </svg>
      <div style={{ position:"absolute", inset:0, display:"flex", alignItems:"center", justifyContent:"center" }}>
        <span style={{ fontSize:10, fontWeight:600, color:col, lineHeight:1, whiteSpace:"nowrap" }}>
          {value != null ? `${Math.round(value * 100)}%` : "—"}
        </span>
      </div>
    </div>
  );
}

// ─── collapsible section ─────────────────────────────────────────
function Section({ title, color, open, onToggle, children }) {
  return (
    <div style={{
      borderLeft: `3px solid ${color || "var(--c-border)"}`,
      paddingLeft: 12,
      marginBottom: 16,
    }}>
      <button onClick={onToggle} style={{ width:"100%", display:"flex", alignItems:"center",
        justifyContent:"space-between", padding:"8px 0", background:"none", border:"none",
        cursor:"pointer", textAlign:"left" }}>
        <span style={{ fontSize:11, fontWeight:600, color:"var(--c-text-1)",
          letterSpacing:"0.3px" }}>{title}</span>
        <span style={{ fontSize:12, color:"var(--c-text-3)", lineHeight:1, display:"inline-block",
          transform:open ? "rotate(180deg)" : "none", transition:"transform 0.15s" }}>▾</span>
      </button>
      {open && <div style={{ paddingBottom:12 }}>{children}</div>}
    </div>
  );
}

// ─── tech stamp (44px rounded square) ────────────────────────────
function TechStamp({ score, display, label, claimed }) {
  const isNull  = score == null;
  const isGreen = !isNull && score >= 0.95;
  const isAmber = !isNull && score >= 0.5 && !isGreen;
  const bg   = isNull  ? "linear-gradient(160deg,#f0f2f5 0%,#e8eaee 100%)"
             : isGreen ? "linear-gradient(160deg,#ecfdf5 0%,#d1fae5 100%)"
             : isAmber ? "linear-gradient(160deg,#fffbeb 0%,#fef3c7 100%)"
             :           "linear-gradient(160deg,#fef2f2 0%,#fee2e2 100%)";
  const bord = isNull ? "var(--c-border)"  : isGreen ? "var(--c-green)"  : isAmber ? "var(--c-amber)"  : "var(--c-red)";
  const col  = isNull ? "var(--c-text-3)"  : isGreen ? "var(--c-green)"  : isAmber ? "var(--c-amber)"  : "var(--c-red)";
  return (
    <div style={{ display:"flex", flexDirection:"column", alignItems:"center", gap:4 }}>
      <div style={{ position:"relative", width:44, height:44, borderRadius:10,
        background:bg, border:`1.5px solid ${bord}`,
        boxShadow:"var(--shadow-stamp)",
        display:"flex", alignItems:"center", justifyContent:"center" }}>
        <span style={{ fontSize:10, fontWeight:700, color:col, lineHeight:1, userSelect:"none" }}>
          {isNull ? "—" : display}
        </span>
        {isGreen && (
          <div style={{ position:"absolute", top:-4, right:-4, width:14, height:14,
            borderRadius:"50%", background:"var(--c-green)", display:"flex",
            alignItems:"center", justifyContent:"center", border:"2px solid var(--c-surface)" }}>
            <span style={{ color:"white", fontSize:8, fontWeight:700, lineHeight:1 }}>✓</span>
          </div>
        )}
      </div>
      <span style={{ fontSize:10, fontWeight:500, color:"var(--c-text-2)" }}>{label}</span>
      {claimed != null && (
        <span style={{ fontSize:9, color:"var(--c-amber)", lineHeight:1, whiteSpace:"nowrap" }}>
          claimed: {claimed}
        </span>
      )}
    </div>
  );
}

// ─── device info ─────────────────────────────────────────────────
function DeviceInfo({ d }) {
  const [expanded, setExpanded] = useState(false);
  const raw = (d.captureDevice || "").trim();

  const lensStr = (() => {
    if (!d.lensType) return null;
    if (d.lensType === "varies") return "Mixed lenses";
    const cap = d.lensType.charAt(0).toUpperCase() + d.lensType.slice(1);
    return d.fovDegrees ? `${cap} ${d.fovDegrees}°` : cap;
  })();

  const suffix = lensStr ? ` · ${lensStr}` : "";
  const wrap = { marginTop:10, fontSize:12, color:"var(--c-text-3)", lineHeight:1.5 };
  const btn  = { fontSize:12, color:"var(--c-blue)", background:"none", border:"none",
    cursor:"pointer", padding:0, fontFamily:"inherit",
    textDecoration:"underline", textDecorationStyle:"dotted" };

  if (!raw) {
    return (
      <div style={{ ...wrap, fontStyle:"italic" }}>
        Device not documented{lensStr ? ` · ${lensStr}` : ""}
      </div>
    );
  }

  if (expanded) {
    return (
      <div style={wrap}>
        {raw}{suffix}{" "}
        <button style={btn} onClick={() => setExpanded(false)}>collapse</button>
      </div>
    );
  }

  const plusIdx = raw.indexOf(" + ");
  if (plusIdx !== -1) {
    const parts = raw.split(" + ");
    const primary = parts[0].replace(/\s*\(.*$/, "").trim();
    const n = parts.length - 1;
    return (
      <div style={wrap}>
        {primary}{" "}
        <button style={btn} onClick={() => setExpanded(true)}>
          +{n} other{n > 1 ? "s" : ""}
        </button>
        {suffix}
      </div>
    );
  }

  if (raw.length > 50) {
    return (
      <div style={wrap}>
        {raw.slice(0, 47).trim()}…{" "}
        <button style={btn} onClick={() => setExpanded(true)}>show more</button>
        {suffix}
      </div>
    );
  }

  return <div style={wrap}>{raw}{suffix}</div>;
}

// ─── tooltip ─────────────────────────────────────────────────────
function Tooltip({ text, children }) {
  const [show, setShow] = useState(false);
  return (
    <span style={{ position:"relative", display:"inline-flex" }}
      onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
      {children}
      {show && (
        <div style={{ position:"absolute", bottom:"calc(100% + 8px)", left:"50%", transform:"translateX(-50%)",
          background:"#1a1f2e", color:"rgba(255,255,255,0.92)", fontSize:11, padding:"7px 11px",
          borderRadius:7, zIndex:100, lineHeight:1.45, boxShadow:"0 4px 16px rgba(15,17,23,0.18)",
          pointerEvents:"none", maxWidth:240, whiteSpace:"normal", textAlign:"center",
          letterSpacing:"0.1px" }}>
          {text}
        </div>
      )}
    </span>
  );
}

// ─── radar chart ─────────────────────────────────────────────────
function RadarChart({ d, size = 200 }) {
  const pad = 40;
  const full = size + pad * 2;
  const cx = full / 2, cy = full / 2, r = size * 0.35;
  const n = DIMS.length;
  const angleStep = (2 * Math.PI) / n;
  const offset = -Math.PI / 2;
  function point(i, val) {
    const angle = offset + i * angleStep;
    return { x: cx + r * val * Math.cos(angle), y: cy + r * val * Math.sin(angle) };
  }
  const pts = DIMS.map((dim, i) => {
    const v = dim.compute(d);
    return point(i, v != null ? v : 0);
  });
  const col = "#2563eb";
  return (
    <svg width={full} height={full} viewBox={`0 0 ${full} ${full}`}>
      {[0.25, 0.5, 0.75, 1.0].map(level => (
        <polygon key={level}
          points={DIMS.map((_, i) => { const p = point(i, level); return `${p.x},${p.y}`; }).join(" ")}
          fill="none" stroke="#d1d5db" strokeWidth={1.0} opacity={0.7} />
      ))}
      {DIMS.map((_, i) => {
        const p = point(i, 1);
        return <line key={i} x1={cx} y1={cy} x2={p.x} y2={p.y} stroke="#d1d5db" strokeWidth={0.5} opacity={0.6} />;
      })}
      <polygon points={pts.map(p => `${p.x},${p.y}`).join(" ")}
        fill={col} fillOpacity={0.18} stroke={col} strokeWidth={1.5} />
      {pts.map((p, i) => <circle key={i} cx={p.x} cy={p.y} r={3.5} fill={col} />)}
      {DIMS.map((dim, i) => {
        const angle = offset + i * angleStep;
        const lx = cx + (r + 28) * Math.cos(angle);
        const ly = cy + (r + 28) * Math.sin(angle);
        return (
          <text key={dim.key} x={lx} y={ly} textAnchor="middle" dominantBaseline="central"
            style={{ fontSize:9, fill:"var(--c-text-2)", fontFamily:"var(--font)", fontWeight:600 }}>
            {dim.label}
          </text>
        );
      })}
    </svg>
  );
}

// ─── score breakdown bars ─────────────────────────────────────────
function ScoreBreakdown({ d }) {
  return (
    <div>
      {DIMS.map(dim => {
        const v = dim.compute(d);
        const pct = v != null ? Math.round(v * 100) : 0;
        return (
          <div key={dim.key} style={{ display:"flex", alignItems:"center", gap:10, marginBottom:8 }}>
            <Tooltip text={dim.desc}>
              <span style={{ width:64, fontSize:10, fontWeight:600, color:"var(--c-text-2)", cursor:"help" }}>
                {dim.label}
              </span>
            </Tooltip>
            <div style={{ flex:1, height:6, borderRadius:3, background:"var(--c-track)", overflow:"hidden" }}>
              {v != null && (
                <div style={{ width:`${pct}%`, height:"100%", borderRadius:3,
                  background:scoreColor(v), transition:"width 0.4s ease" }} />
              )}
            </div>
            <span style={{ width:24, fontSize:11, fontWeight:700, color:scoreColor(v),
              fontVariantNumeric:"tabular-nums", textAlign:"right" }}>
              {fmtPct(v)}
            </span>
          </div>
        );
      })}
    </div>
  );
}

// ─── downstream fit section ───────────────────────────────────────
function DownstreamFitSection({ d }) {
  const [active, setActive] = useState(null);
  const present = new Set(d.modalities || []);
  const TIERS = [
    { key:"critical",  label:"Critical"  },
    { key:"important", label:"Important" },
    { key:"bonus",     label:"Bonus"     },
  ];
  return (
    <div>
      <div style={{ display:"flex", gap:6, flexWrap:"wrap", marginBottom: active ? 12 : 0 }}>
        {USECASES.map(uc => {
          const v = d[uc.field];
          const isActive = active === uc.key;
          return (
            <Tooltip key={uc.key} text={uc.desc}>
              <button onClick={() => setActive(isActive ? null : uc.key)}
                style={{ fontSize:11, padding:"5px 11px", borderRadius:20, cursor:"pointer",
                  background: isActive ? scoreBg(v) : "var(--c-surface)",
                  color: isActive ? scoreColor(v) : "var(--c-text-2)",
                  border: `1.5px solid ${isActive ? scoreColor(v) : "var(--c-border-strong)"}`,
                  fontWeight: isActive ? 600 : 500, lineHeight:1.4,
                  boxShadow: isActive ? "0 1px 3px rgba(0,0,0,0.1)" : "none" }}>
                {uc.label}&thinsp;{fmtPct(v)}
              </button>
            </Tooltip>
          );
        })}
      </div>
      {active && (() => {
        const uc = USECASES.find(u => u.key === active);
        if (!uc) return null;
        return (
          <div style={{ display:"flex", flexDirection:"column", gap:7 }}>
            {TIERS.map(tier => {
              const mods = uc.profile[tier.key] || [];
              if (!mods.length) return null;
              return (
                <div key={tier.key} style={{ display:"flex", gap:10, alignItems:"flex-start" }}>
                  <span style={{ fontSize:10, fontWeight:600, color:"var(--c-text-3)",
                    width:52, flexShrink:0, paddingTop:3 }}>
                    {tier.label}
                  </span>
                  <div style={{ display:"flex", gap:4, flexWrap:"wrap" }}>
                    {mods.map(m => {
                      const has = present.has(m);
                      return (
                        <span key={m} style={{ fontSize:10, fontWeight:500, padding:"2px 8px", borderRadius:20,
                          background: has ? "var(--c-green-bg)" : "var(--c-track)",
                          color: has ? "var(--c-green)" : "var(--c-text-3)",
                          border: has ? "1px solid rgba(5,150,105,0.3)" : "1px solid transparent",
                          textDecoration: has ? "none" : "line-through" }}>
                          {m}
                        </span>
                      );
                    })}
                  </div>
                </div>
              );
            })}
          </div>
        );
      })()}
    </div>
  );
}

// ─── scale hero ──────────────────────────────────────────────────
function ScaleHero({ d }) {
  const hrsFmt = d.hrs ? Number(d.hrs).toLocaleString() : null;
  const parts = [
    d.envDivCount > 0 ? `${Math.round(d.envDivCount).toLocaleString()} env` : null,
    d.demoParts != null ? `${Math.round(d.demoParts).toLocaleString()} ppl` : null,
    d.demoGeo != null ? `${d.demoGeo} countr${Number(d.demoGeo) === 1 ? "y" : "ies"}` : null,
  ].filter(Boolean);
  return (
    <div style={{ textAlign:"center", padding:"12px 8px 14px",
      background:"var(--c-surface-2)", borderRadius:10, marginBottom:10 }}>
      <div style={{ fontSize:24, fontWeight:700, lineHeight:1, letterSpacing:"-0.5px",
        color: hrsFmt ? "var(--c-text-1)" : "var(--c-text-3)" }}>
        {hrsFmt ?? "—"}
      </div>
      <div style={{ fontSize:11, fontWeight:500, color:"var(--c-text-3)", marginTop:3,
        textTransform:"uppercase", letterSpacing:"0.6px" }}>hours</div>
      {parts.length > 0 && (
        <div style={{ fontSize:13, color:"var(--c-text-2)", marginTop:10, lineHeight:1.4 }}>
          {parts.join(" · ")}
        </div>
      )}
      {d.dlEffHPG != null && (
        <div style={{ marginTop:10, display:"flex", justifyContent:"center" }}>
          <span style={{ fontSize:11, padding:"3px 10px", borderRadius:20, fontWeight:600,
            background:scoreBg(d.dlEff), color:scoreColor(d.dlEff) }}>
            {d.dlEffHPG.toFixed(3)} h/GB
          </span>
        </div>
      )}
    </div>
  );
}

// ─── calibration checklist ────────────────────────────────────────
// false = gray ✗ (not provided), null = gray ? (unknown), true = green ✓
// red ✗ is reserved for future "claimed but broken" state
function CalChecklist({ d }) {
  const items = [
    { key:"calIntrinsics", label:"Intrinsics" },
    { key:"calDistortion", label:"Distortion" },
    { key:"calExtrinsics", label:"Extrinsics" },
    { key:"calValidated",  label:"Validated"  },
  ];
  function Icon({ v }) {
    const box = {
      width:16, height:16, borderRadius:4, display:"inline-flex",
      alignItems:"center", justifyContent:"center", flexShrink:0,
    };
    if (v === null || v === undefined) {
      return (
        <span style={{ ...box, background:"var(--c-track)",
          border:"1.5px dashed var(--c-border-strong)" }}>
          <span style={{ fontSize:9, color:"var(--c-text-3)", lineHeight:1 }}>?</span>
        </span>
      );
    }
    return v
      ? (
        <span style={{ ...box, background:"var(--c-green-bg)",
          border:"1.5px solid var(--c-green)" }}>
          <span style={{ fontSize:9, fontWeight:800, color:"var(--c-green)", lineHeight:1 }}>✓</span>
        </span>
      ) : (
        <span style={{ ...box, background:"var(--c-track)",
          border:"1.5px solid var(--c-border-strong)" }}>
          <span style={{ fontSize:9, fontWeight:600, color:"var(--c-text-3)", lineHeight:1 }}>✗</span>
        </span>
      );
  }
  return (
    <div style={{ background:"var(--c-surface-2)", borderRadius:8, padding:"10px 12px", marginBottom:8 }}>
      <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap:"8px 16px" }}>
        {items.map(it => (
          <div key={it.key} style={{ display:"flex", alignItems:"center", gap:8 }}>
            <Icon v={d[it.key]} />
            <span style={{ fontSize:11, fontWeight:500, color:"var(--c-text-2)" }}>{it.label}</span>
          </div>
        ))}
      </div>
      {d.camCalNotes && (
        <div style={{ fontSize:10, color:"var(--c-text-3)", lineHeight:1.5, marginTop:8 }}>{d.camCalNotes}</div>
      )}
    </div>
  );
}

// ─── annotation coverage ring ─────────────────────────────────────
function AnnotCov({ d }) {
  const v = d.annCov;
  const size = 40, stroke = 3.5, r = (size - stroke) / 2;
  const circ = 2 * Math.PI * r;
  const dash = v != null ? circ * Math.min(v, 1) : 0;
  const col  = scoreColor(v);
  const pending = !v && d.annCovSource === "pending_paper_review";
  return (
    <div style={{ display:"flex", alignItems:"center", gap:14 }}>
      <div style={{ position:"relative", width:size, height:size, flexShrink:0 }}>
        <svg width={size} height={size} style={{ transform:"rotate(-90deg)", display:"block" }}>
          <circle cx={size/2} cy={size/2} r={r} fill="none" stroke="var(--c-track)" strokeWidth={stroke} />
          {v != null && (
            <circle cx={size/2} cy={size/2} r={r} fill="none" stroke={col} strokeWidth={stroke}
              strokeDasharray={`${dash} ${circ}`} strokeLinecap="round" />
          )}
        </svg>
        <div style={{ position:"absolute", inset:0, display:"flex", alignItems:"center", justifyContent:"center" }}>
          <span style={{ fontSize:9, fontWeight:700, color: v != null ? col : "var(--c-text-3)" }}>
            {v != null ? `${Math.round(v * 100)}%` : "?"}
          </span>
        </div>
      </div>
      <div style={{ flex:1, fontSize:11, color:"var(--c-text-2)", lineHeight:1.5 }}>
        {d.annCovNotes
          ? d.annCovNotes
          : <span style={{ fontStyle:"italic", color:"var(--c-text-3)" }}>{pending ? "Pending research" : "Not reported"}</span>}
      </div>
    </div>
  );
}

// ─── accessibility row ────────────────────────────────────────────
function AccessRow({ label, value, score }) {
  const col = scoreColor(score);
  return (
    <div style={{ display:"flex", alignItems:"center", gap:10, padding:"6px 0",
      borderBottom:"1px solid var(--c-track)" }}>
      <span style={{ width:6, height:6, borderRadius:2, flexShrink:0,
        background: value ? col : "var(--c-null)" }} />
      <span style={{ fontSize:11, fontWeight:500, color:"var(--c-text-3)", width:88, flexShrink:0 }}>{label}</span>
      <span style={{ fontSize:12, fontWeight:500, flex:1,
        color: value ? "var(--c-text-1)" : "var(--c-text-3)" }}>
        {value || "—"}
      </span>
    </div>
  );
}

// ─── hardware row ─────────────────────────────────────────────────
function HardwareRow({ label, value }) {
  return (
    <div style={{ display:"flex", gap:12 }}>
      <span style={{ width:76, flexShrink:0, fontSize:11, fontWeight:500,
        color:"var(--c-text-3)", textAlign:"right" }}>{label}</span>
      <span style={{ flex:1, fontSize:12, fontWeight:500,
        color:"var(--c-text-1)", lineHeight:1.5 }} title={value}>{value}</span>
    </div>
  );
}

// ─── verification panel (below card body) ────────────────────────
function VerificationPanel({ fe }) {
  if (!fe) return null;

  const CODEC_LABELS = { hevc:"H.265/HEVC", h265:"H.265/HEVC", h264:"H.264/AVC", avc:"H.264/AVC", vp9:"VP9", av1:"AV1" };
  const codecLabel = CODEC_LABELS[fe.actualCodec] || fe.actualCodec || "—";
  const bitrateStr = fe.bitrateKbps
    ? `${fe.bitrateKbps.min.toLocaleString()}–${fe.bitrateKbps.max.toLocaleString()} kbps`
    : "—";

  const VRow = ({ label, value, ok }) => (
    <div style={{ display:"flex", alignItems:"center", gap:8, padding:"5px 0",
      borderBottom:"1px solid var(--c-track)" }}>
      {ok != null
        ? <span style={{ fontSize:10, fontWeight:700, width:12, flexShrink:0, textAlign:"center",
            color: ok ? "var(--c-green)" : "var(--c-amber)" }}>{ok ? "✓" : "⚠"}</span>
        : <span style={{ width:12, flexShrink:0 }} />}
      <span style={{ fontSize:11, color:"var(--c-text-3)", fontWeight:500, width:76, flexShrink:0 }}>{label}</span>
      <span style={{ fontSize:11, color:"var(--c-text-1)" }}>{value}</span>
    </div>
  );

  return (
    <div style={{ borderTop:"1px solid var(--c-border)", padding:"18px 24px 20px" }}>

      {/* panel header */}
      <div style={{ display:"flex", alignItems:"center", gap:14, marginBottom:16 }}>
        <span style={{ fontSize:10, fontWeight:700, color:"var(--c-text-2)",
          letterSpacing:"0.8px", textTransform:"uppercase" }}>Verification</span>
        <span style={{ fontSize:10, color:"var(--c-text-3)" }}>
          {fe.filesChecked} file{fe.filesChecked !== 1 ? "s" : ""} checked via ffprobe
          {fe.evaluatedAt ? ` · ${fe.evaluatedAt.slice(0, 10)}` : ""}
        </span>
      </div>

      {/* three columns */}
      <div style={{ display:"grid", gridTemplateColumns:"repeat(3, 1fr)", gap:"0 32px" }}>

        {/* ── col 1: actual values ─── */}
        <div>
          <div style={{ fontSize:10, fontWeight:600, color:"var(--c-text-3)",
            textTransform:"uppercase", letterSpacing:0.5, marginBottom:8 }}>Actual (ffprobe)</div>
          <VRow label="Frame rate" value={fe.actualFps != null ? `${fe.actualFps} fps` : "—"}
            ok={fe.fpsMatch !== false} />
          <VRow label="Resolution" value={fe.actualResW ? `${fe.actualResW}\u00d7${fe.actualResH}` : "—"}
            ok={fe.resMatch !== false} />
          <VRow label="Codec"      value={codecLabel}  ok={null} />
          <VRow label="Bitrate"    value={bitrateStr}  ok={null} />
          <VRow label="Audio"      value={fe.hasAudio ? "Present" : "None"} ok={null} />
        </div>

        {/* ── col 2: annotation coverage ─── */}
        <div>
          <div style={{ fontSize:10, fontWeight:600, color:"var(--c-text-3)",
            textTransform:"uppercase", letterSpacing:0.5, marginBottom:8 }}>Annotation coverage</div>
          {fe.annCovByType
            ? Object.entries(fe.annCovByType).map(([type, info]) => {
                const pct = info.pct != null ? info.pct : 0;
                const col = pct >= 90 ? "var(--c-green)" : pct >= 50 ? "var(--c-amber)" : "var(--c-red)";
                return (
                  <div key={type} style={{ marginBottom:9 }}>
                    <div style={{ display:"flex", justifyContent:"space-between", marginBottom:3 }}>
                      <span style={{ fontSize:10, color:"var(--c-text-3)" }}>
                        {type.replace(/_/g, " ")}
                      </span>
                      <span style={{ fontSize:10, fontWeight:600, color:col }}>
                        {info.covered}/{info.total}
                      </span>
                    </div>
                    <div style={{ height:4, borderRadius:2, background:"var(--c-track)", overflow:"hidden" }}>
                      <div style={{ width:`${pct}%`, height:"100%", borderRadius:2, background:col }} />
                    </div>
                  </div>
                );
              })
            : <span style={{ fontSize:11, color:"var(--c-text-3)", fontStyle:"italic" }}>No data</span>
          }
        </div>

        {/* ── col 3: discrepancies ─── */}
        <div>
          <div style={{ fontSize:10, fontWeight:600, color:"var(--c-text-3)",
            textTransform:"uppercase", letterSpacing:0.5, marginBottom:8 }}>Discrepancies</div>
          {!fe.fieldDiscrepancies || fe.fieldDiscrepancies.length === 0
            ? <span style={{ fontSize:11, color:"var(--c-green)" }}>None found</span>
            : fe.fieldDiscrepancies.map((disc, i) => {
                const sev = disc.severity || "info";
                const isRed = sev === "critical" || sev === "high";
                const bord = isRed ? "var(--c-red)"   : "var(--c-amber)";
                const bg   = isRed ? "var(--c-red-bg)" : "var(--c-amber-bg)";
                return (
                  <div key={i} style={{ fontSize:10, lineHeight:1.5, padding:"5px 8px",
                    marginBottom:5, borderRadius:6, background:bg,
                    borderLeft:`2px solid ${bord}` }}>
                    <span style={{ fontWeight:600, color:"var(--c-text-1)" }}>
                      {disc.field.replace(/_/g, " ")}
                    </span>
                    <span style={{ color:"var(--c-text-3)" }}>
                      {" · claimed "}{String(disc.claimed)}{", actual "}{String(disc.actual)}
                    </span>
                    {disc.affectedClips != null && (
                      <span style={{ color:"var(--c-text-3)" }}>
                        {" "}({disc.affectedClips}/{disc.totalClips} clips)
                      </span>
                    )}
                  </div>
                );
              })
          }
        </div>

      </div>
    </div>
  );
}

// ─── score card ───────────────────────────────────────────────────
function ScoreCard({ d }) {
  const [open, setOpen] = useState({
    technical:true, reliability:true, access:true,
    scale:true, downstream:true, hardware:true,
  });
  const [modalOpen, setModalOpen] = useState(true);
  const [hovered, setHovered] = useState(false);
  const toggle = key => setOpen(s => ({...s, [key]:!s[key]}));
  const isMobile = useIsMobile();

  const shortEdge = d.resW && d.resH ? Math.min(d.resW, d.resH) : null;
  const fe = d.fileEval;
  const fmtFps = v => v != null ? `${Math.round(v)}fps` : null;
  const verifiedFpsDisplay = fe ? fmtFps(fe.actualFps) : (d.fpsRaw != null ? fmtFps(d.fpsRaw) : null);
  const claimedFpsDisplay  = (fe && fe.fpsMatch === false) ? `${Math.round(fe.claimedFps)}fps` : null;
  const verifiedShortEdge  = fe ? Math.min(fe.actualResW, fe.actualResH) : shortEdge;
  const claimedResDisplay  = (fe && fe.resMatch === false && fe.claimedResW && fe.claimedResH)
    ? `${Math.min(fe.claimedResW, fe.claimedResH)}p` : null;
  const dlLabel = d.dl == null ? null
    : d.dl >= 0.8
        ? (d.dlFramework && d.dlFramework !== "unspecified" ? d.dlFramework : "Yes")
        : d.dl >= 0.5 ? "Community"
        : "No";
  const accVal = [d.accLevel, d.accUrlStatus]
    .filter(v => v && v !== "Not specified").join(" · ") || null;

  return (
    <div
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
      style={{
        background:"var(--c-surface)", border:"1px solid var(--c-border)",
        borderRadius:14, maxWidth:1100, width:"100%", overflow:"hidden",
        boxShadow: hovered
          ? "0 2px 6px rgba(15,17,23,0.08), 0 8px 24px rgba(15,17,23,0.06)"
          : "var(--shadow-card)",
        transition:"box-shadow 0.2s ease",
      }}>

      {/* ── header ─────────────────────────────────────────────── */}
      <div style={{ padding:"24px 24px 12px", borderBottom:"1px solid var(--c-border)" }}>
        <div style={{ display:"flex", justifyContent:"space-between", alignItems:"flex-start" }}>
          <div style={{ flex:1, paddingRight:16 }}>
            <div style={{ fontSize:22, fontWeight:800, color:"var(--c-text-1)",
              lineHeight:1.2, letterSpacing:"-0.4px" }}>
              {d.name}
            </div>
            <div style={{ fontSize:12, fontWeight:600, color:"var(--c-text-2)",
              marginTop:4, letterSpacing:"0.2px" }}>
              {d.cat} · {d.yr}
            </div>
            <div style={{ fontSize:12, color:"var(--c-text-3)", marginTop:3 }}>
              {d.hrs ? `${Number(d.hrs).toLocaleString()} h` : "hours unknown"}
              {d.citations != null
                ? ` · ~${d.citations.toLocaleString()} citations`
                : d.noPaper ? " · No paper" : ""}
            </div>
            <div style={{ marginTop:8 }}>
              <button onClick={() => setModalOpen(o => !o)}
                style={{ fontSize:11, fontWeight:500, color:"var(--c-text-2)", background:"none", border:"none",
                  cursor:"pointer", padding:0, display:"inline-flex", alignItems:"center", gap:4 }}>
                {d.modCount} {d.modCount === 1 ? "modality" : "modalities"}
                <span style={{ fontSize:10 }}>{modalOpen ? "▴" : "▸"}</span>
              </button>
              {modalOpen && (
                <div style={{ display:"flex", gap:4, flexWrap:"wrap", marginTop:6 }}>
                  {(d.modalities || []).map(m => (
                    <span key={m} style={{ fontSize:10, fontWeight:500, padding:"2px 8px", borderRadius:20,
                      background:"var(--c-track)", color:"var(--c-text-2)",
                      border:"1px solid var(--c-border)" }}>
                      {m}
                    </span>
                  ))}
                </div>
              )}
            </div>
          </div>
          <div style={{ display:"flex", flexDirection:"column", alignItems:"center", gap:3, flexShrink:0 }}>
            <CompletionRing value={d.completeness} />
            <span style={{ fontSize:9, color:"var(--c-text-3)" }}>fields documented</span>
            {fe && (
              <span style={{ fontSize:9, fontWeight:600, color:"var(--c-blue)",
                background:"rgba(37,99,235,0.08)", padding:"2px 7px", borderRadius:10,
                marginTop:2, whiteSpace:"nowrap" }}>
                File verified
              </span>
            )}
          </div>
        </div>
      </div>

      {/* ── three-column body ─────────────────────────────────── */}
      <div style={{
        display:"grid",
        gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr 240px",
        paddingLeft:24,
      }}>

        {/* ── LEFT ─────────────────────────────────────────────── */}
        <div style={{ paddingRight: isMobile ? 0 : 22, paddingBottom:20, paddingTop:8 }}>

          <Section title="Technical" color="var(--c-accent-tech)"
            open={open.technical} onToggle={() => toggle("technical")}>
            <div style={{ display:"flex", gap:16, paddingTop:4, flexWrap:"wrap" }}>
              <TechStamp score={d.fps}
                display={verifiedFpsDisplay}
                label={fe ? "Frame rate ✓" : "Frame rate"}
                claimed={claimedFpsDisplay} />
              <TechStamp score={d.res}
                display={verifiedShortEdge != null ? `${verifiedShortEdge}p` : null}
                label={fe ? "Resolution ✓" : "Resolution"}
                claimed={claimedResDisplay} />
              {(() => {
                const hasAudio = fe ? fe.hasAudio : (d.modalities || []).includes("Audio");
                return (
                  <TechStamp score={hasAudio ? 1.0 : null}
                    display={hasAudio ? "Audio" : null}
                    label={fe ? "Audio ✓" : "Audio"} />
                );
              })()}
            </div>
          </Section>

          <Section title="Accessibility & Docs" color="var(--c-accent-access)"
            open={open.access} onToggle={() => toggle("access")}>
            <AccessRow label="License"       value={d.licType}  score={d.lic} />
            <AccessRow label="Accessibility" value={accVal}     score={d.acc} />
            <AccessRow label="Dataloader"    value={dlLabel}    score={d.dl}  />
            <AccessRow label="Documentation"
              value={d.docRating != null ? `${d.docRating}/3` : null} score={d.doc} />
          </Section>

          <Section title="Reliability" color="var(--c-accent-rel)"
            open={open.reliability} onToggle={() => toggle("reliability")}>
            <div style={{ fontSize:11, fontWeight:500, color:"var(--c-text-2)", marginBottom:8 }}>
              Annotation coverage
            </div>
            <AnnotCov d={d} />
            <div style={{ fontSize:11, fontWeight:500, color:"var(--c-text-2)",
              marginTop:14, marginBottom:8 }}>
              Camera calibration
            </div>
            <CalChecklist d={d} />
          </Section>

        </div>

        {/* ── MIDDLE ───────────────────────────────────────────── */}
        <div style={{
          paddingLeft: isMobile ? 0 : 22,
          paddingRight: isMobile ? 0 : 22,
          paddingBottom:20, paddingTop:8,
          borderLeft: isMobile ? "none" : "1px solid var(--c-border)",
        }}>

          <Section title="Scale & Diversity" color="var(--c-accent-scale)"
            open={open.scale} onToggle={() => toggle("scale")}>
            <ScaleHero d={d} />
          </Section>

          <Section title="Downstream Fit" color="var(--c-accent-fit)"
            open={open.downstream} onToggle={() => toggle("downstream")}>
            <DownstreamFitSection d={d} />
          </Section>

          <Section title="Hardware & Format" color="var(--c-accent-hw)"
            open={open.hardware} onToggle={() => toggle("hardware")}>
            <div style={{ display:"flex", flexDirection:"column", gap:6 }}>
              {d.captureDevice && <HardwareRow label="Device"      value={d.captureDevice} />}
              {(d.lensType || d.fovDegrees) && (
                <HardwareRow label="Lens"
                  value={[d.lensType, d.fovDegrees ? `${d.fovDegrees}° FOV` : null].filter(Boolean).join(" · ")} />
              )}
              {d.videoFormat  && <HardwareRow label="Video"       value={d.videoFormat} />}
              {d.annotFormat  && <HardwareRow label="Annotations" value={d.annotFormat} />}
            </div>
          </Section>

        </div>

        {/* ── RIGHT: Score Profile ─────────────────────────────── */}
        <div style={{
          borderLeft: isMobile ? "none" : "1px solid var(--c-border)",
          background: isMobile ? "transparent" : "var(--c-surface-2)",
          borderRadius: isMobile ? 0 : "0 14px 14px 0",
          padding: isMobile ? 0 : "16px",
        }}>
          <div style={{ fontSize:10, fontWeight:700, color:"var(--c-text-2)",
            letterSpacing:"0.8px", textTransform:"uppercase", marginBottom:12,
            paddingBottom:8, borderBottom:"1px solid var(--c-border)" }}>
            Score Profile
          </div>
          <div style={{ display:"flex", justifyContent:"center" }}>
            <RadarChart d={d} size={170} />
          </div>
          <div style={{ borderTop:"1px solid var(--c-border)", paddingTop:12, marginTop:4 }}>
            <div style={{ fontSize:10, fontWeight:700, color:"var(--c-text-2)",
              letterSpacing:"0.8px", textTransform:"uppercase", marginBottom:12 }}>
              Score Breakdown
            </div>
            <ScoreBreakdown d={d} />
          </div>
        </div>

      </div>

      {/* ── verification panel ─────────────────────────────────── */}
      {fe && <VerificationPanel fe={fe} />}

      {/* ── footer ─────────────────────────────────────────────── */}
      <div style={{ display:"flex", justifyContent:"space-between", padding:"10px 24px",
        borderTop:"1px solid var(--c-border)", background:"var(--c-surface-2)" }}>
        <span style={{ fontSize:10, fontWeight:500, color:"var(--c-text-3)", letterSpacing:"0.2px" }}>
          Open Data Eval · Metadata Eval v1.0
        </span>
        <span style={{ fontSize:10, color:"var(--c-text-3)", opacity:0.7 }}>ISO/IEC 5259-2</span>
      </div>
    </div>
  );
}

// ─── compare: 3 use-case constants ───────────────────────────────
const COMPARE_USECASES = [
  { key: "actRecog", label: "Action Recognition", field: "actRecog",
    desc: "Temporal activity understanding — classifying and localizing actions in egocentric video",
    profile: { critical:["RGB video"], important:["Audio","Narrations/captions","Optical flow"], bonus:["Eye gaze","Hand pose annotations","IMU"] } },
  { key: "handObj", label: "Hand-Object Interaction", field: "handPose",
    desc: "Hand pose, grasping, and manipulation analysis from first-person view",
    profile: { critical:["RGB video","Hand pose annotations"], important:["Depth (RGB-D)","Depth (stereo)"], bonus:["Tactile","IMU","Force/torque"] } },
  { key: "nav", label: "Navigation", field: "nav",
    desc: "Navigation, 3D reconstruction, SLAM, and scene-level reasoning",
    profile: { critical:["RGB video","IMU"], important:["Depth (RGB-D)","SLAM/odometry","3D point clouds"], bonus:["LiDAR","Depth (stereo)","Eye gaze"] } },
];

// ─── compare: tooltip ────────────────────────────────────────────
function CmpTooltip({ text, children }) {
  const [show, setShow] = React.useState(false);
  return (
    <span style={{ position:"relative", display:"inline-flex" }}
      onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
      {children}
      {show && (
        <div style={{ position:"absolute", bottom:"calc(100% + 8px)", left:"50%", transform:"translateX(-50%)",
          background:"var(--c-text-1)", color:"#fff", fontSize:11, padding:"6px 10px",
          borderRadius:6, zIndex:100, lineHeight:1.4, boxShadow:"0 4px 12px rgba(0,0,0,0.15)",
          pointerEvents:"none", maxWidth:260, whiteSpace:"normal", textAlign:"center" }}>
          {text}
        </div>
      )}
    </span>
  );
}

// ─── compare: downstream fit (3 use cases) ───────────────────────
function CmpDownstreamFit({ d }) {
  const [active, setActive] = React.useState(null);
  const present = new Set(d.modalities || []);
  const TIERS = [{ key:"critical", label:"Critical" }, { key:"important", label:"Important" }, { key:"bonus", label:"Bonus" }];
  return (
    <div>
      <div style={{ display:"flex", gap:5, flexWrap:"wrap", marginBottom: active ? 10 : 0 }}>
        {COMPARE_USECASES.map(uc => {
          const v = d[uc.field];
          const isActive = active === uc.key;
          return (
            <CmpTooltip key={uc.key} text={uc.desc}>
              <button onClick={() => setActive(isActive ? null : uc.key)}
                style={{ fontSize:11, padding:"4px 10px", borderRadius:20, cursor:"pointer",
                  background: isActive ? scoreBg(v) : "var(--c-track)", color:scoreColor(v),
                  border:`2px solid ${isActive ? scoreColor(v) : "transparent"}`,
                  fontWeight:isActive ? 600 : 400, lineHeight:1.4 }}>
                {uc.label}&thinsp;{fmtPct(v)}
              </button>
            </CmpTooltip>
          );
        })}
      </div>
      {active && (() => {
        const uc = COMPARE_USECASES.find(u => u.key === active);
        if (!uc) return null;
        return (
          <div style={{ display:"flex", flexDirection:"column", gap:7 }}>
            {TIERS.map(tier => {
              const mods = uc.profile[tier.key] || [];
              if (!mods.length) return null;
              return (
                <div key={tier.key} style={{ display:"flex", gap:10, alignItems:"flex-start" }}>
                  <span style={{ fontSize:10, color:"var(--c-text-3)", width:56, flexShrink:0, paddingTop:3 }}>{tier.label}</span>
                  <div style={{ display:"flex", gap:4, flexWrap:"wrap" }}>
                    {mods.map(m => {
                      const has = present.has(m);
                      return (
                        <span key={m} style={{ fontSize:10, padding:"2px 7px", borderRadius:4,
                          background: has ? "var(--c-green-bg)" : "var(--c-red-bg)",
                          color: has ? "var(--c-green)" : "var(--c-red)",
                          textDecoration: has ? "none" : "line-through" }}>{m}</span>
                      );
                    })}
                  </div>
                </div>
              );
            })}
          </div>
        );
      })()}
    </div>
  );
}

// ─── compare: fact sheet helpers ─────────────────────────────────
function FH({ title }) {
  return <div style={{ fontSize:10, fontWeight:700, color:"var(--c-text-2)", textTransform:"uppercase",
    letterSpacing:0.8, padding:"10px 0 5px", borderBottom:"1px solid var(--c-border)" }}>{title}</div>;
}

// Scale bars — no winner highlighting; each side always its dataset color
function FNum({ label, vA, vB }) {
  const nA = vA != null ? Number(vA) : null, nB = vB != null ? Number(vB) : null;
  const ratio = nA && nB && Math.min(nA, nB) > 0 ? (Math.max(nA, nB) / Math.min(nA, nB)).toFixed(1) + "x" : "";
  const maxV = Math.max(nA || 0, nB || 0);
  const pA = maxV > 0 && nA ? (nA / maxV) * 100 : 0;
  const pB = maxV > 0 && nB ? (nB / maxV) * 100 : 0;
  return (
    <div style={{ display:"flex", alignItems:"center", padding:"5px 0", borderBottom:"1px solid var(--c-track)" }}>
      <span style={{ width:80, fontSize:10, color:"var(--c-text-3)", fontWeight:500, flexShrink:0 }}>{label}</span>
      <span style={{ width:52, fontSize:11, fontWeight:400, textAlign:"right",
        color:"var(--c-blue)", fontVariantNumeric:"tabular-nums", flexShrink:0 }}>
        {nA != null ? nA.toLocaleString() : "—"}</span>
      <div style={{ flex:1, display:"flex", gap:1, margin:"0 6px" }}>
        <div style={{ flex:1, height:4, borderRadius:2, background:"var(--c-track)", overflow:"hidden", direction:"rtl" }}>
          <div style={{ width:`${pA}%`, height:"100%", borderRadius:2, background:"var(--c-blue)" }} />
        </div>
        <div style={{ flex:1, height:4, borderRadius:2, background:"var(--c-track)", overflow:"hidden" }}>
          <div style={{ width:`${pB}%`, height:"100%", borderRadius:2, background:"var(--c-amber)" }} />
        </div>
      </div>
      <span style={{ width:52, fontSize:11, fontWeight:400,
        color:"var(--c-amber)", fontVariantNumeric:"tabular-nums", flexShrink:0 }}>
        {nB != null ? nB.toLocaleString() : "—"}</span>
      <span style={{ width:32, fontSize:9, color:"var(--c-text-3)", textAlign:"right", flexShrink:0 }}>{ratio}</span>
    </div>
  );
}

// Technical text — colored by threshold, dimmed when values match
function FTech({ label, vA, vB, greenAt, amberAt }) {
  const nA = vA != null ? parseFloat(vA) : null;
  const nB = vB != null ? parseFloat(vB) : null;
  const same = vA != null && vB != null && String(vA) === String(vB);
  const colorOf = n => {
    if (n == null) return "var(--c-text-3)";
    if (n >= greenAt) return "var(--c-green)";
    if (n >= amberAt) return "var(--c-amber)";
    return "var(--c-red)";
  };
  return (
    <div style={{ display:"flex", alignItems:"center", padding:"5px 0", borderBottom:"1px solid var(--c-track)" }}>
      <span style={{ width:80, fontSize:10, color:"var(--c-text-3)", fontWeight:500, flexShrink:0 }}>{label}</span>
      <div style={{ flex:1, textAlign:"right", paddingRight:8 }}>
        <span style={{ fontSize:11, opacity: same ? 0.6 : 1,
          color: colorOf(nA),
          fontStyle: vA ? "normal" : "italic" }}>{vA || "—"}</span>
      </div>
      <div style={{ width:1, height:16, background:"var(--c-border)", flexShrink:0 }} />
      <div style={{ flex:1, paddingLeft:8 }}>
        <span style={{ fontSize:11, opacity: same ? 0.6 : 1,
          color: colorOf(nB),
          fontStyle: vB ? "normal" : "italic" }}>{vB || "—"}</span>
      </div>
      <span style={{ width:32, flexShrink:0 }}></span>
    </div>
  );
}

function FTxt({ label, vA, vB }) {
  return (
    <div style={{ display:"flex", alignItems:"center", padding:"5px 0", borderBottom:"1px solid var(--c-track)" }}>
      <span style={{ width:80, fontSize:10, color:"var(--c-text-3)", fontWeight:500, flexShrink:0 }}>{label}</span>
      <div style={{ flex:1, textAlign:"right", paddingRight:8 }}>
        <span style={{ fontSize:11, color: vA ? "var(--c-text-1)" : "var(--c-text-3)",
          fontStyle: vA ? "normal" : "italic" }}>{vA || "—"}</span>
      </div>
      <div style={{ width:1, height:16, background:"var(--c-border)", flexShrink:0 }} />
      <div style={{ flex:1, paddingLeft:8 }}>
        <span style={{ fontSize:11, color: vB ? "var(--c-text-1)" : "var(--c-text-3)",
          fontStyle: vB ? "normal" : "italic" }}>{vB || "—"}</span>
      </div>
      <span style={{ width:32, flexShrink:0 }}></span>
    </div>
  );
}
function FChk({ label, vA, vB }) {
  const ic = v => v === true ? "✓" : v === false ? "✗" : "?";
  const cl = v => v === true ? "var(--c-green)" : v === false ? "var(--c-null)" : "var(--c-text-3)";
  return (
    <div style={{ display:"flex", alignItems:"center", padding:"4px 0", borderBottom:"1px solid var(--c-track)" }}>
      <span style={{ width:80, fontSize:10, color:"var(--c-text-3)", fontWeight:500, flexShrink:0 }}>{label}</span>
      <div style={{ flex:1, textAlign:"right", paddingRight:8 }}>
        <span style={{ fontSize:12, fontWeight:600, color:cl(vA) }}>{ic(vA)}</span>
      </div>
      <div style={{ width:1, height:16, background:"var(--c-border)", flexShrink:0 }} />
      <div style={{ flex:1, paddingLeft:8 }}>
        <span style={{ fontSize:12, fontWeight:600, color:cl(vB) }}>{ic(vB)}</span>
      </div>
      <span style={{ width:32, flexShrink:0 }}></span>
    </div>
  );
}

// Access section — collapses identical fields, differing fields shown by default
function FAccessSection({ A, B, dlL }) {
  const [allExpanded, setAllExpanded] = React.useState(false);
  const [diffExpanded, setDiffExpanded] = React.useState(false);
  const fields = [
    { label:"License",      vA: A.licType,      vB: B.licType },
    { label:"Access level", vA: A.accLevel,      vB: B.accLevel },
    { label:"URL status",   vA: A.accUrlStatus,  vB: B.accUrlStatus },
    { label:"Dataloader",   vA: dlL(A),          vB: dlL(B) },
    { label:"Docs",         vA: A.docRating != null ? A.docRating + "/3" : null,
                            vB: B.docRating != null ? B.docRating + "/3" : null },
  ];
  const norm = v => v == null ? "—" : String(v);
  const allSame = fields.every(f => norm(f.vA) === norm(f.vB));
  const diffFields = fields.filter(f => norm(f.vA) !== norm(f.vB));
  const sameFields = fields.filter(f => norm(f.vA) === norm(f.vB));

  // All identical — single collapsed row (whole row clickable)
  if (allSame && !allExpanded) {
    return (
      <div onClick={() => setAllExpanded(true)}
        style={{ display:"flex", alignItems:"center", padding:"6px 0",
          borderBottom:"1px solid var(--c-track)", cursor:"pointer" }}>
        <span style={{ width:80, fontSize:10, color:"var(--c-text-3)", fontWeight:500, flexShrink:0 }}>Access</span>
        <span style={{ fontSize:11, color:"var(--c-green)", fontWeight:500 }}>Identical</span>
      </div>
    );
  }

  // All identical — expanded (whole row clickable to collapse)
  if (allSame && allExpanded) {
    return (
      <div>
        {fields.map(f => <FTxt key={f.label} label={f.label} vA={f.vA} vB={f.vB} />)}
        <div onClick={() => setAllExpanded(false)}
          style={{ fontSize:10, color:"var(--c-text-3)", padding:"4px 0 2px", cursor:"pointer" }}>
          {fields.length} fields identical
        </div>
      </div>
    );
  }

  // Some differ — show diff rows; identical rows collapsed behind clickable label
  return (
    <div>
      {diffFields.map(f => <FTxt key={f.label} label={f.label} vA={f.vA} vB={f.vB} />)}
      {sameFields.length > 0 && !diffExpanded && (
        <div onClick={() => setDiffExpanded(true)}
          style={{ fontSize:10, padding:"4px 0 2px", cursor:"pointer", color:"var(--c-text-3)" }}>
          {sameFields.length} field{sameFields.length > 1 ? "s" : ""} identical, hidden
        </div>
      )}
      {sameFields.length > 0 && diffExpanded && (
        <div>
          {sameFields.map(f => <FTxt key={f.label} label={f.label} vA={f.vA} vB={f.vB} />)}
          <div onClick={() => setDiffExpanded(false)}
            style={{ fontSize:10, color:"var(--c-text-3)", padding:"4px 0 2px", cursor:"pointer" }}>
            {sameFields.length} field{sameFields.length > 1 ? "s" : ""} identical
          </div>
        </div>
      )}
    </div>
  );
}

// Downstream fit — four-tier verdict, columns match FTech/FNum exactly
const FIT_LABELS = { actRecog:"Action Recognition", handObj:"Hand-Object Interaction", nav:"Navigation" };
function CmpDownstreamFitCompare({ A, B }) {
  return (
    <div>
      {COMPARE_USECASES.map(uc => {
        const vA = A[uc.field] != null ? Math.round(A[uc.field] * 100) : null;
        const vB = B[uc.field] != null ? Math.round(B[uc.field] * 100) : null;
        const delta = vA != null && vB != null ? Math.abs(vA - vB) : null;
        const aLeads = vA != null && vB != null && vA >= vB;
        const winner = aLeads ? A : B;
        const winColor = aLeads ? "var(--c-blue)" : "var(--c-amber)";
        const winBg = aLeads ? "rgba(37,99,235,0.08)" : "rgba(217,119,6,0.08)";

        const bothStrong = vA != null && vB != null && vA >= 70 && vB >= 70;
        const oneStrong  = vA != null && vB != null && (vA >= 70) !== (vB >= 70);
        const bothLow    = vA != null && vB != null && vA < 40 && vB < 40;
        const midRange   = vA != null && vB != null && !bothStrong && !oneStrong && !bothLow;

        const verdictNode = (vA == null || vB == null)
          ? <span style={{ fontSize:10, color:"var(--c-text-3)", fontStyle:"italic",
              whiteSpace:"nowrap" }}>no data</span>
          : delta === 0
          ? <span style={{ fontSize:10, color:"var(--c-text-3)",
              whiteSpace:"nowrap" }}>Tied</span>
          : bothStrong
          ? <span style={{ fontSize:10, fontWeight:600, color:"var(--c-green)",
              background:"var(--c-green-bg)", padding:"2px 8px", borderRadius:10,
              whiteSpace:"nowrap" }}>Both strong</span>
          : (oneStrong || midRange)
          ? <span style={{ fontSize:10, fontWeight:600, color: winColor, background: winBg,
              padding:"2px 8px", borderRadius:10, whiteSpace:"nowrap" }}>
              {winner.name} +{delta}
            </span>
          : <span style={{ fontSize:10, color:"var(--c-text-3)", whiteSpace:"nowrap" }}>
              {winner.name} +{delta}
            </span>;

        return (
          <div key={uc.key} style={{ padding:"7px 0", borderBottom:"1px solid var(--c-track)" }}>
            {/* category label on its own line */}
            <div style={{ fontSize:11, color:"var(--c-text-1)", fontWeight:500, marginBottom:5 }}>
              {FIT_LABELS[uc.key] || uc.label}
            </div>
            {/* scores row — no label column so verdict is exactly centered */}
            <div style={{ display:"flex", alignItems:"center" }}>
              <div style={{ flex:1, textAlign:"right", paddingRight:24 }}>
                <span style={{ fontSize:11, color:"var(--c-blue)", fontVariantNumeric:"tabular-nums" }}>
                  {vA != null ? vA + "%" : "—"}
                </span>
              </div>
              <div style={{ width:150, textAlign:"center", flexShrink:0 }}>
                {verdictNode}
              </div>
              <div style={{ flex:1, paddingLeft:24 }}>
                <span style={{ fontSize:11, color:"var(--c-amber)", fontVariantNumeric:"tabular-nums" }}>
                  {vB != null ? vB + "%" : "—"}
                </span>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

// Auto-summary: finds 2–3 largest relative differences, templates into sentences
function genSummary(A, B) {
  const fmtHrs = h => h >= 1000 ? (Math.round(h / 100) / 10) + "K" : String(Math.round(h));
  const aAdvantages = [], bAdvantages = [], shared = [];

  // Hours
  if (A.hrs != null && B.hrs != null && A.hrs !== B.hrs) {
    const ratio = Math.max(A.hrs, B.hrs) / Math.min(A.hrs, B.hrs);
    if (ratio >= 1.5) {
      const winner = A.hrs > B.hrs ? A : B;
      const loser  = A.hrs > B.hrs ? B : A;
      const txt = `${Math.round(ratio)}× more footage (${fmtHrs(winner.hrs)}h vs ${fmtHrs(loser.hrs)}h)`;
      (A.hrs > B.hrs ? aAdvantages : bAdvantages).push({ score: ratio, txt });
    }
  }
  // Participants
  if (A.demoParts != null && B.demoParts != null && A.demoParts !== B.demoParts) {
    const ratio = Math.max(A.demoParts, B.demoParts) / Math.min(A.demoParts, B.demoParts);
    if (ratio >= 2) {
      const txt = `${Math.round(ratio)}× more participants`;
      (A.demoParts > B.demoParts ? aAdvantages : bAdvantages).push({ score: ratio * 0.8, txt });
    }
  }
  // FPS
  if (A.fpsRaw != null && B.fpsRaw != null && A.fpsRaw !== B.fpsRaw) {
    const ratio = Math.max(A.fpsRaw, B.fpsRaw) / Math.min(A.fpsRaw, B.fpsRaw);
    if (ratio >= 1.5) {
      const winner = A.fpsRaw > B.fpsRaw ? A : B;
      const loser  = A.fpsRaw > B.fpsRaw ? B : A;
      const txt = `higher frame rate (${winner.fpsRaw}fps vs ${loser.fpsRaw}fps)`;
      (A.fpsRaw > B.fpsRaw ? aAdvantages : bAdvantages).push({ score: ratio * 0.7, txt });
    }
  }
  // License clarity
  const stdLic = l => ["CC-BY-4.0","CC-BY-NC-4.0","CC-BY-NC-SA-4.0","Apache-2.0","MIT","CC-BY-NC-ND-4.0"].includes(l);
  if (A.licType && B.licType && stdLic(A.licType) !== stdLic(B.licType)) {
    const winner = stdLic(A.licType) ? A : B;
    const loser  = stdLic(A.licType) ? B : A;
    const txt = `a standard license (${winner.licType}) vs ${loser.licType || "unspecified"}`;
    (stdLic(A.licType) ? aAdvantages : bAdvantages).push({ score: 1.4, txt });
  }
  // Modality count
  if (A.modCount != null && B.modCount != null && Math.abs(A.modCount - B.modCount) >= 2) {
    const diff = Math.abs(A.modCount - B.modCount);
    const txt = `${diff} more sensor modalities`;
    (A.modCount > B.modCount ? aAdvantages : bAdvantages).push({ score: diff * 0.4, txt });
  }
  // Shared modalities
  const modsA = new Set((A.modalities || []).filter(m => m !== "RGB video"));
  const modsB = new Set((B.modalities || []).filter(m => m !== "RGB video"));
  const common = [...modsA].filter(m => modsB.has(m));
  if (common.length >= 2) {
    shared.push(`Both include ${common.slice(0,2).join(" and ")}`);
  } else if (A.cat && A.cat === B.cat) {
    shared.push(`Both are ${A.cat.toLowerCase()} datasets`);
  }

  aAdvantages.sort((a, b) => b.score - a.score);
  bAdvantages.sort((a, b) => b.score - a.score);
  const parts = [];
  if (aAdvantages.length) parts.push(`${A.name} has ${aAdvantages[0].txt}`);
  if (bAdvantages.length) parts.push(`${B.name} has ${bAdvantages[0].txt}`);
  if (shared.length && parts.length >= 1) parts.push(shared[0]);
  if (!parts.length) return `${A.name} and ${B.name} are similar across most dimensions.`;
  return parts.map(p => p.endsWith(".") ? p : p + ".").join(" ");
}

// ─── compare: side-by-side view ───────────────────────────────────
function CompareSideBySide() {
  const [aIdx, setAIdx] = React.useState(0);
  const [bIdx, setBIdx] = React.useState(Math.min(1, DATA.length - 1));
  const A = DATA[aIdx], B = DATA[bIdx];

  const sel = { fontSize:13, padding:"5px 10px", borderRadius:6,
    border:"1px solid var(--c-border)", background:"var(--c-surface)",
    color:"var(--c-text-1)", width:"100%" };
  const shortA = A.resW && A.resH ? Math.min(A.resW, A.resH) : null;
  const shortB = B.resW && B.resH ? Math.min(B.resW, B.resH) : null;
  const dlL = d => d.dl == null ? null : d.dl >= 0.8 ? (d.dlFramework && d.dlFramework !== "unspecified" ? d.dlFramework : "Yes") : d.dl >= 0.5 ? "Community" : "No";
  // Use verified ffprobe values when file eval is available
  const fmtFpsVal = (fe, raw) => fe ? `${Math.round(fe.actualFps)} fps` : (raw != null ? raw + " fps" : null);
  const fpsValA = fmtFpsVal(A.fileEval, A.fpsRaw);
  const fpsValB = fmtFpsVal(B.fileEval, B.fpsRaw);
  const resValA = A.fileEval ? `${Math.min(A.fileEval.actualResW, A.fileEval.actualResH)}p` : (shortA ? shortA + "p" : null);
  const resValB = B.fileEval ? `${Math.min(B.fileEval.actualResW, B.fileEval.actualResH)}p` : (shortB ? shortB + "p" : null);

  return (
    <div>
      <div style={{ display:"flex", gap:12, marginBottom:18, flexWrap:"wrap", alignItems:"flex-end" }}>
        <div style={{ flex:1, minWidth:160 }}>
          <label style={{ fontSize:10, fontWeight:700, color:"var(--c-text-3)", textTransform:"uppercase",
            letterSpacing:0.8, display:"block", marginBottom:5 }}>Dataset A</label>
          <select value={aIdx} onChange={e => setAIdx(+e.target.value)} style={sel}>
            {DATA.map((d, i) => <option key={i} value={i}>{d.name}</option>)}
          </select>
        </div>
        <div style={{ fontSize:14, fontWeight:700, color:"var(--c-text-3)", paddingBottom:6 }}>vs</div>
        <div style={{ flex:1, minWidth:160 }}>
          <label style={{ fontSize:10, fontWeight:700, color:"var(--c-text-3)", textTransform:"uppercase",
            letterSpacing:0.8, display:"block", marginBottom:5 }}>Dataset B</label>
          <select value={bIdx} onChange={e => setBIdx(+e.target.value)} style={sel}>
            {DATA.map((d, i) => <option key={i} value={i}>{d.name}</option>)}
          </select>
        </div>
      </div>

      {/* auto-summary */}
      <div style={{ fontSize:12.5, color:"var(--c-text-1)", lineHeight:1.65, marginBottom:14,
        padding:"12px 16px", background:"var(--c-surface-2)", borderRadius:8,
        border:"1px solid var(--c-border)", borderLeft:"3px solid var(--c-accent-scale)" }}>
        {genSummary(A, B)}
      </div>

      <div style={{ background:"var(--c-surface)", border:"1px solid var(--c-border)",
        borderRadius:12, overflow:"hidden" }}>
        <div style={{ padding:"16px 22px" }}>
          {/* column headers */}
          <div style={{ display:"flex", alignItems:"center", marginBottom:2 }}>
            <span style={{ width:80, flexShrink:0 }}></span>
            <div style={{ flex:1, textAlign:"right", paddingRight:8 }}>
              <span style={{ fontSize:12, fontWeight:700, color:"var(--c-blue)" }}>{A.name}</span>
            </div>
            <div style={{ width:1, height:16, background:"var(--c-border)", flexShrink:0 }} />
            <div style={{ flex:1, paddingLeft:8 }}>
              <span style={{ fontSize:12, fontWeight:700, color:"var(--c-amber)" }}>{B.name}</span>
            </div>
            <span style={{ width:32, flexShrink:0 }}></span>
          </div>

          <FH title="Scale" />
          <FNum label="Hours" vA={A.hrs} vB={B.hrs} />
          <FNum label="Environments" vA={A.envDivCount} vB={B.envDivCount} />
          <FNum label="Participants" vA={A.demoParts} vB={B.demoParts} />
          <FNum label="Countries" vA={A.demoGeo} vB={B.demoGeo} />
          <FNum label="Citations" vA={A.citations} vB={B.citations} />

          <FH title="Technical" />
          <FTech label="Frame rate"
            vA={fpsValA} vB={fpsValB}
            greenAt={30} amberAt={15} />
          <FTech label="Resolution"
            vA={resValA} vB={resValB}
            greenAt={1080} amberAt={720} />
          {(A.fileEval || B.fileEval) && (
            <div style={{ display:"flex", alignItems:"center", padding:"3px 0 6px",
              borderBottom:"1px solid var(--c-track)" }}>
              <span style={{ width:80, flexShrink:0 }} />
              <div style={{ flex:1, textAlign:"right", paddingRight:8 }}>
                <span style={{ fontSize:9, color: A.fileEval ? "var(--c-green)" : "var(--c-text-3)",
                  fontWeight:500 }}>{A.fileEval ? "ffprobe verified" : "metadata only"}</span>
              </div>
              <div style={{ width:1, height:12, background:"var(--c-border)", flexShrink:0 }} />
              <div style={{ flex:1, paddingLeft:8 }}>
                <span style={{ fontSize:9, color: B.fileEval ? "var(--c-green)" : "var(--c-text-3)",
                  fontWeight:500 }}>{B.fileEval ? "ffprobe verified" : "metadata only"}</span>
              </div>
              <span style={{ width:32, flexShrink:0 }} />
            </div>
          )}
          <FChk label="Intrinsics" vA={A.calIntrinsics} vB={B.calIntrinsics} />
          <FChk label="Distortion" vA={A.calDistortion} vB={B.calDistortion} />
          <FChk label="Extrinsics" vA={A.calExtrinsics} vB={B.calExtrinsics} />
          <FChk label="Validated" vA={A.calValidated} vB={B.calValidated} />

          <FH title="Annotations" />
          <FNum label="Coverage %" vA={A.annCov != null ? Math.round(A.annCov * 100) : null} vB={B.annCov != null ? Math.round(B.annCov * 100) : null} />
          <FNum label="Modalities" vA={A.modCount} vB={B.modCount} />
          {(() => {
            const modsA = A.modalities || [], modsB = B.modalities || [];
            const setB = new Set(modsB), setA = new Set(modsA);
            const onlyA = modsA.filter(m => !setB.has(m));
            const both  = modsA.filter(m => setB.has(m));
            const onlyB = modsB.filter(m => !setA.has(m));
            return (
              <div style={{ padding:"8px 0 6px", borderBottom:"1px solid var(--c-track)" }}>
                <div style={{ display:"flex", gap:16, marginLeft:80 }}>
                  <div style={{ flex:1, textAlign:"right" }}>
                    {onlyA.length > 0
                      ? <div style={{ display:"flex", gap:3, flexWrap:"wrap", justifyContent:"flex-end" }}>
                          {onlyA.map(m => <span key={m} style={{ fontSize:9, padding:"1px 5px", borderRadius:3,
                            background:"rgba(37,99,235,0.08)", color:"var(--c-blue)", fontWeight:500 }}>{m}</span>)}
                        </div>
                      : <span style={{ fontSize:9, color:"var(--c-text-3)", fontStyle:"italic" }}>none unique</span>}
                    <div style={{ fontSize:8, color:"var(--c-blue)", marginTop:3, fontWeight:600 }}>{A.name} only</div>
                  </div>
                  <div style={{ flex:1, textAlign:"center" }}>
                    {both.length > 0
                      ? <div style={{ display:"flex", gap:3, flexWrap:"wrap", justifyContent:"center" }}>
                          {both.map(m => <span key={m} style={{ fontSize:9, padding:"1px 5px", borderRadius:3,
                            background:"var(--c-green-bg)", color:"var(--c-green)", fontWeight:500 }}>{m}</span>)}
                        </div>
                      : <span style={{ fontSize:9, color:"var(--c-text-3)", fontStyle:"italic" }}>none shared</span>}
                    <div style={{ fontSize:8, color:"var(--c-green)", marginTop:3, fontWeight:600 }}>both</div>
                  </div>
                  <div style={{ flex:1 }}>
                    {onlyB.length > 0
                      ? <div style={{ display:"flex", gap:3, flexWrap:"wrap" }}>
                          {onlyB.map(m => <span key={m} style={{ fontSize:9, padding:"1px 5px", borderRadius:3,
                            background:"rgba(217,119,6,0.08)", color:"var(--c-amber)", fontWeight:500 }}>{m}</span>)}
                        </div>
                      : <span style={{ fontSize:9, color:"var(--c-text-3)", fontStyle:"italic" }}>none unique</span>}
                    <div style={{ fontSize:8, color:"var(--c-amber)", marginTop:3, fontWeight:600 }}>{B.name} only</div>
                  </div>
                </div>
              </div>
            );
          })()}

          <FH title="Access" />
          <FAccessSection A={A} B={B} dlL={dlL} />

          <FH title="Hardware" />
          <FTxt label="Device" vA={A.captureDevice} vB={B.captureDevice} />
          <FTxt label="Video fmt" vA={A.videoFormat} vB={B.videoFormat} />
          <FTxt label="Annot. fmt" vA={A.annotFormat} vB={B.annotFormat} />
        </div>

        <div style={{ padding:"14px 22px", borderTop:"1px solid var(--c-border)" }}>
          <div style={{ marginBottom:8 }}>
            <span style={{ fontSize:10, fontWeight:700, color:"var(--c-text-2)",
              textTransform:"uppercase", letterSpacing:0.8 }}>Downstream Fit</span>
            <div style={{ display:"flex", alignItems:"center", marginTop:6 }}>
              <div style={{ flex:1, textAlign:"right", paddingRight:24 }}>
                <span style={{ fontSize:10, color:"var(--c-blue)", fontWeight:600 }}>{A.name}</span>
              </div>
              <div style={{ width:150, textAlign:"center", flexShrink:0,
                fontSize:9, color:"var(--c-text-3)", fontWeight:500, letterSpacing:0.3 }}>Verdict</div>
              <div style={{ flex:1, paddingLeft:24 }}>
                <span style={{ fontSize:10, color:"var(--c-amber)", fontWeight:600 }}>{B.name}</span>
              </div>
            </div>
          </div>
          <CmpDownstreamFitCompare A={A} B={B} />
        </div>
      </div>
    </div>
  );
}

// ─── compare table ────────────────────────────────────────────────
function CompareTable({ usecase, sortBy, onSortChange, onRowClick }) {
  const uc = USECASES.find(u => u.key === usecase);

  const sorted = useMemo(() => {
    return DATA.map(d => ({
      ...d,
      techScore: avg([d.fps, d.res]),
      relScore:  avg([d.camCal, d.annCov]),
      accScore:  avg([d.lic, d.acc, d.dl, d.doc]),
    })).sort((a, b) => {
      const av = a[sortBy], bv = b[sortBy];
      if (av == null && bv == null) return 0;
      if (av == null) return 1;
      if (bv == null) return -1;
      return bv - av;
    });
  }, [sortBy, usecase]);

  const active = col => sortBy === col;
  const TH = ({ col, label, right = true }) => (
    <th onClick={() => onSortChange(col)}
      style={{ padding:"9px 8px", textAlign: right ? "right" : "left", fontSize:11, fontWeight:600,
        color: active(col) ? "var(--c-blue)" : "var(--c-text-2)",
        background: active(col) ? "rgba(37,99,235,0.04)" : "transparent",
        cursor:"pointer", whiteSpace:"nowrap", letterSpacing:"0.2px",
        borderBottom:"1px solid var(--c-border-strong)",
        userSelect:"none" }}>
      {label}{active(col) ? " ↓" : ""}
    </th>
  );
  const ScoreCell = ({ v }) => (
    <td style={{ textAlign:"right", padding:"7px 8px", fontSize:12, fontWeight:600,
      color:scoreColor(v), borderBottom:"1px solid var(--c-track)",
      fontVariantNumeric:"tabular-nums" }}>
      {fmtPct(v)}
    </td>
  );
  const NumCell = ({ v }) => (
    <td style={{ textAlign:"right", padding:"7px 8px", fontSize:12,
      color: v != null ? "var(--c-text-2)" : "var(--c-text-3)",
      borderBottom:"1px solid var(--c-track)",
      fontVariantNumeric:"tabular-nums" }}>
      {fmtNum(v)}
    </td>
  );
  const subHdr = (label) => (
    <td style={{ padding:"3px 8px", textAlign:"right", fontSize:10,
      color:"var(--c-text-3)", borderBottom:"1px solid var(--c-border)" }}>
      {label}
    </td>
  );

  return (
    <div style={{ background:"var(--c-surface)", border:"1px solid var(--c-border)",
      borderRadius:12, boxShadow:"var(--shadow-card)", overflow:"hidden" }}>
      <div style={{ overflowX:"auto" }}>
        <table style={{ width:"100%", borderCollapse:"collapse", fontSize:12 }}>
          <thead style={{ background:"var(--c-surface-2)" }}>
            <tr>
              <TH col="_name"        label="Dataset"     right={false} />
              <TH col="techScore"    label="Technical"   />
              <TH col={uc.field}     label="Fit"         />
              <TH col="relScore"     label="Reliability" />
              <TH col="accScore"     label="Access"      />
              <TH col="hrs"          label="Hours"       />
              <TH col="envDivCount"  label="Envs"        />
              <TH col="demoParts"    label="Ppl"         />
              <TH col="citations"    label="Cit."        />
              <TH col="completeness" label="Cmp"         />
            </tr>
            <tr>
              <td style={{ padding:"3px 8px", fontSize:10, color:"var(--c-text-3)",
                borderBottom:"1px solid var(--c-border)" }} />
              {subHdr("fps+res")}
              {subHdr(uc.label)}
              {subHdr("cal+ann")}
              {subHdr("lic+acc+dl+doc")}
              {subHdr("raw")}
              {subHdr("raw")}
              {subHdr("raw")}
              {subHdr("raw")}
              {subHdr("score")}
            </tr>
          </thead>
          <tbody>
            {sorted.map((row, idx) => (
              <tr key={row.name} onClick={() => onRowClick(row.name)}
                style={{ cursor:"pointer",
                  background: idx % 2 === 1 ? "var(--c-surface-2)" : "var(--c-surface)" }}
                onMouseOver={e => e.currentTarget.style.background = "#eef3ff"}
                onMouseOut={e  => e.currentTarget.style.background =
                  idx % 2 === 1 ? "var(--c-surface-2)" : "var(--c-surface)"}>
                <td style={{ padding:"7px 8px", paddingLeft:16, fontWeight:600, fontSize:12,
                  color:"var(--c-text-1)", borderBottom:"1px solid var(--c-track)", whiteSpace:"nowrap" }}>
                  {row.name}
                  <span style={{ fontSize:10, color:"var(--c-text-3)", fontWeight:400, marginLeft:6 }}>
                    {row.yr}
                  </span>
                </td>
                <ScoreCell v={row.techScore} />
                <ScoreCell v={row[uc.field]} />
                <ScoreCell v={row.relScore}  />
                <ScoreCell v={row.accScore}  />
                <NumCell   v={row.hrs}          />
                <NumCell   v={row.envDivCount}  />
                <NumCell   v={row.demoParts}    />
                <NumCell   v={row.citations}    />
                <ScoreCell v={row.completeness} />
              </tr>
            ))}
          </tbody>
        </table>
      </div>
      <div style={{ padding:"10px 16px", fontSize:10, color:"var(--c-text-3)",
        borderTop:"1px solid var(--c-border)" }}>
        Click column header to sort · Click row to open card.&ensp;
        Scores: <span style={{ color:"var(--c-green)" }}>■ ≥70</span>&nbsp;
        <span style={{ color:"var(--c-amber)" }}>■ ≥40</span>&nbsp;
        <span style={{ color:"var(--c-red)" }}>■ &lt;40</span>&nbsp;
        <span style={{ color:"var(--c-null)" }}>■ no data</span>.
        Fit column reflects selected use case.
      </div>
    </div>
  );
}

// ─── app ──────────────────────────────────────────────────────────
function App() {
  const [selected, setSelected] = useState(DATA[0]?.name || "");
  const [usecase,  setUsecase]  = useState("actRecog");
  const [view,     setView]     = useState("card");
  const [sortBy,   setSortBy]   = useState("completeness");

  const d = DATA.find(x => x.name === selected);
  function handleRowClick(name) { setSelected(name); setView("card"); }

  const TabBtn = ({ id, label }) => (
    <button onClick={() => setView(id)}
      style={{ padding:"4px 14px", fontSize:13,
        fontWeight: view===id ? 600 : 500,
        color:      view===id ? "var(--c-text-1)" : "var(--c-text-2)",
        background: view===id ? "var(--c-surface)" : "transparent",
        border:     view===id ? "1px solid var(--c-border)" : "1px solid transparent",
        borderRadius:6, cursor:"pointer",
        boxShadow: view===id ? "0 1px 3px rgba(15,17,23,0.08)" : "none" }}>
      {label}
    </button>
  );

  return (
    <div style={{ maxWidth:1200, margin:"0 auto", padding:"20px 20px 24px", fontFamily:"var(--font)" }}>

      {/* controls */}
      <div style={{ display:"flex", gap:8, marginBottom:18, flexWrap:"wrap", alignItems:"center" }}>
        {view === "card" && (
          <select value={selected} onChange={e => setSelected(e.target.value)}
            style={{ fontSize:13, fontWeight:500, padding:"6px 12px", borderRadius:8, height:34,
              border:"1px solid var(--c-border-strong)", background:"var(--c-surface)",
              color:"var(--c-text-1)", minWidth:200,
              boxShadow:"0 1px 2px rgba(15,17,23,0.06)" }}>
            {DATA.map(d => <option key={d.name} value={d.name}>{d.name}</option>)}
          </select>
        )}

        <div style={{ marginLeft:"auto", display:"flex", gap:2,
          background:"var(--c-track)", borderRadius:8, padding:3 }}>
          <TabBtn id="card"     label="Card"     />
          <TabBtn id="datasets" label="Datasets" />
          <TabBtn id="compare"  label="Compare"  />
        </div>
      </div>

      {/* views */}
      {view === "card" && d && (
        <ScoreCard d={d} />
      )}
      {view === "datasets" && (
        <CompareTable usecase={usecase} sortBy={sortBy}
          onSortChange={setSortBy} onRowClick={handleRowClick} />
      )}
      {view === "compare" && (
        <CompareSideBySide />
      )}

      {/* page footer */}
      <div style={{ marginTop:28, paddingTop:14, borderTop:"1px solid var(--c-border)",
        display:"flex", justifyContent:"space-between", alignItems:"center", flexWrap:"wrap", gap:6 }}>
        <span style={{ fontSize:11, color:"var(--c-text-3)" }}>
          <a href="https://github.com/Varun-Nair/open-data-eval"
            style={{ color:"var(--c-text-3)", textDecoration:"none" }}
            onMouseEnter={e => { e.target.style.color = "var(--c-text-2)"; e.target.style.textDecoration = "underline"; e.target.style.textDecorationStyle = "dotted"; }}
            onMouseLeave={e => { e.target.style.color = "var(--c-text-3)"; e.target.style.textDecoration = "none"; }}>
            open-data-eval
          </a>
          &nbsp;· CC-BY-4.0 · Varun Nair 2026
        </span>
        <span style={{ fontSize:11, color:"var(--c-text-3)" }}>
          Open Data Eval · Metadata Eval v1.0 · ISO/IEC 5259-2
        </span>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(React.createElement(App));
