/* global React, PMT, UsMap, ordinal */
const V = window.VariantDesignSystem_40a86e;
const { Button, Link, Card, Badge, Tag, Tabs, Select } = V;
const { useState, useMemo } = React;

const LABEL = { fontFamily: "var(--font-sans)", fontSize: "13px", fontWeight: "var(--weight-medium)", letterSpacing: "0.01em" };
const SMALL = { ...LABEL, fontSize: "12px" };
const MARK = { fontFamily: "var(--font-sans)", fontSize: "12.5px", fontWeight: "var(--weight-medium)", letterSpacing: "0.01em", lineHeight: 1 };
const CHIP = { ...MARK, padding: "6px 9px", borderRadius: "2px", display: "inline-block", border: "1px solid transparent" };
const CHIP_STYLE = {
  state: { background: "var(--blue-700)", color: "var(--white)" },
  cftc: { background: "var(--grey-500)", color: "var(--white)" },
  unresolved: { background: "var(--blue-200)", color: "var(--black)" },
  none: { background: "var(--grey-100)", color: "var(--grey-600)", borderColor: "var(--grey-300)" },
  neutral: { background: "transparent", color: "var(--grey-600)", borderColor: "var(--grey-300)" }
};

function Eyebrow({ children, style }) {
  return <div style={{ ...LABEL, color: "var(--text-muted)", ...style }}>{children}</div>;
}

function SectionHead({ id, title, sub, rule }) {
  return (
    <div id={id} style={{ display: "grid", gap: "10px", marginBottom: rule ? "0" : "28px" }}>
      <h2 className="pmt-h2" style={{ letterSpacing: "var(--tracking-heading)" }}>{title}</h2>
      {sub ? <p style={{ color: "var(--text-secondary)", maxWidth: "56ch" }}>{sub}</p> : null}
      {rule ? <div style={{ borderTop: "2px solid var(--border-strong)", marginTop: "26px" }}></div> : null}
    </div>
  );
}

function Sources({ src }) {
  if (!src || !src.length) return null;
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: "8px 14px", alignItems: "baseline" }}>
      {src.map(([label, url], i) => (
        <a key={i} href={url} target="_blank" rel="noopener noreferrer" style={{ ...SMALL, color: "var(--link)" }}>{label}</a>
      ))}
    </div>
  );
}

function Legend({ items }) {
  return (
    <div style={{ display: "flex", flexWrap: "wrap", gap: "10px 22px", alignItems: "center" }}>
      {items.map(([c, l]) => (
        <div key={l} style={{ display: "flex", gap: "8px", alignItems: "center" }}>
          <span style={{ width: "13px", height: "13px", background: c, border: "1px solid " + (c === "#DEDEDE" ? "var(--grey-400)" : c), borderRadius: "2px", flex: "none", boxSizing: "border-box" }}></span>
          <span style={{ ...MARK, color: "var(--text-primary)" }}>{l}</span>
        </div>
      ))}
    </div>
  );
}

const VIEWS = [
  { value: "state", label: "By state" },
  { value: "circuit", label: "By circuit" },
  { value: "platform", label: "By platform" },
  { value: "trade", label: "Can you trade?" }
];

const VIEW_NOTE = {
  state: (
    <span>
      <strong>What the labels mean.</strong>
      <ul style={{ margin: "8px 0 0", paddingLeft: "18px", display: "grid", gap: "6px" }}>
        <li><strong>State ahead</strong>: a court has so far let that state enforce its gambling laws.</li>
        <li><strong>CFTC ahead</strong>: a court has blocked the state, or the state legislated in the CFTC’s favour, as North Carolina did.</li>
        <li><strong>Unresolved</strong>: both sides have filed and nothing has been decided. Almost all of these are preliminary rulings, so they can flip on appeal.</li>
        <li><strong>Sued by the CFTC</strong>, in the count below: a separate tally from the map colors above. It counts states the CFTC has filed suit against to block their enforcement — regardless of who's currently winning that case.</li>
      </ul>
    </span>
  ),
  circuit: <span><strong>A circuit is a regional federal appeals court.</strong> There are twelve, each covering a block of states, and a ruling from one binds every state underneath it. That is why a single decision can settle the question for nine states at once, and why two circuits disagreeing sends the issue to the Supreme Court.</span>,
  platform: <span><strong>Where a platform is a defendant.</strong> The CFTC has sued nine states over this: Arizona, Connecticut, Illinois, Kentucky, Minnesota, New Mexico, New York, Rhode Island and Wisconsin. Hyperliquid appears nowhere here.</span>,
  trade: <span><strong>What a retail trader can do today,</strong> which is not the same as who is winning. Platforms geofence ahead of rulings, so a state can be unresolved in court and still have sports contracts switched off.</span>
};

function StatusBadge({ status }) {
  return <Badge tone="neutral" style={{ ...CHIP, ...CHIP_STYLE[status], fontFamily: "var(--font-sans)", textTransform: "none", letterSpacing: "0.01em", borderRadius: "2px" }}>{PMT.statusLabel[status]}</Badge>;
}
function KindChip({ children }) {
  return <Badge tone="neutral" style={{ ...CHIP, ...CHIP_STYLE.neutral, fontFamily: "var(--font-sans)", textTransform: "none", letterSpacing: "0.01em", borderRadius: "2px" }}>{children}</Badge>;
}

function StatePanel({ abbr, onClear, onPlatform }) {
  const s = PMT.states.find((x) => x.abbr === abbr);
  if (!s) return null;
  const [tLabel, tNote] = PMT.tradeCopy[s.trade];
  const circ = s.circuit === "DC" ? "D.C. Circuit" : s.circuit + ordinal(s.circuit) + " Circuit";
  return (
    <div style={{ border: "1px solid var(--border-strong)", borderRadius: "4px" }}>
      <div style={{ padding: "22px 26px", borderBottom: "1px solid var(--border-default)", display: "grid", gap: "14px" }}>
        <div style={{ display: "flex", justifyContent: "space-between", gap: "16px", alignItems: "flex-start" }}>
          <div>
            <Eyebrow>{circ} · {PMT.statusLabel[PMT.CIRCUITS[s.circuit].status]}</Eyebrow>
            <h3 style={{ fontSize: "var(--size-heading-md)", marginTop: "8px" }}>{s.name}</h3>
          </div>
          <button onClick={onClear} aria-label="Clear selection" style={{ ...SMALL, background: "none", border: "1px solid var(--border-default)", borderRadius: "2px", padding: "6px 8px", cursor: "pointer", color: "var(--text-secondary)" }}>Clear</button>
        </div>
        <div><StatusBadge status={s.status} /></div>
      </div>
      <div style={{ padding: "20px 26px", borderBottom: "1px solid var(--border-default)", display: "grid", gap: "10px" }}>
        <Eyebrow>Can you trade?</Eyebrow>
        <div style={{ fontSize: "var(--size-body-lg)", fontWeight: "var(--weight-medium)" }}>{tLabel}</div>
        <p style={{ color: "var(--text-secondary)", fontSize: "var(--size-body-sm)" }}>{tNote}</p>
      </div>
      <div style={{ padding: "20px 26px", borderBottom: "1px solid var(--border-default)", display: "grid", gap: "12px" }}>
        <Eyebrow>Platforms named</Eyebrow>
        {s.platforms.length ? (
          <React.Fragment>
            <div style={{ display: "flex", flexWrap: "wrap", gap: "8px" }}>
              {s.platforms.map((p) => (
                <span key={p} onClick={() => onPlatform(p)} onKeyDown={(e) => { if (e.key === "Enter") onPlatform(p); }} role="button" tabIndex={0} title={"Show every state where " + p + " is a defendant"} style={{ ...MARK, cursor: "pointer", padding: "7px 10px", border: "1px solid var(--grey-300)", borderRadius: "2px", color: "var(--text-primary)", transition: "var(--transition-hover)" }}>{p} →</span>
              ))}
            </div>
            <p style={{ ...SMALL, color: "var(--text-muted)" }}>Select one to map its cases</p>
          </React.Fragment>
        ) : <p style={{ color: "var(--text-muted)", fontSize: "var(--size-body-sm)" }}>None.</p>}
      </div>
      <div style={{ padding: "20px 26px", display: "grid", gap: "20px" }}>
        <Eyebrow>Case record</Eyebrow>
        {s.cases.length ? s.cases.map((c, i) => (
          <div key={i} style={{ display: "grid", gap: "8px", paddingBottom: i === s.cases.length - 1 ? 0 : "20px", borderBottom: i === s.cases.length - 1 ? "0" : "1px solid var(--border-default)" }}>
            <div style={{ display: "flex", gap: "12px", alignItems: "center", flexWrap: "wrap" }}>
              <span style={{ ...SMALL, color: "var(--text-primary)" }}>{c.d}</span>
              <span style={{ ...SMALL, color: "var(--text-muted)" }}>{c.c}</span>
            </div>
            <div style={{ fontWeight: "var(--weight-medium)", lineHeight: "1.35" }}>{c.t}</div>
            <p style={{ color: "var(--text-secondary)", fontSize: "var(--size-body-sm)" }}>{c.n}</p>
            <Sources src={c.src} />
          </div>
        )) : <p style={{ color: "var(--text-secondary)", fontSize: "var(--size-body-sm)" }}>No action filed. No state order, no suit, no appeal.</p>}
      </div>
    </div>
  );
}

function downloadCsv() {
  const head = ["State", "Abbr", "Circuit", "Status", "Circuit status", "Can you trade", "Platforms named", "Latest filing", "Date"];
  const rows = PMT.states.map((s) => {
    const c0 = s.cases[0] || {};
    return [s.name, s.abbr, s.circuit, PMT.statusLabel[s.status], PMT.statusLabel[PMT.CIRCUITS[s.circuit].status], PMT.tradeCopy[s.trade][0], s.platforms.join(" / "), c0.t || "", c0.d || ""];
  });
  const csv = [head, ...rows].map((r) => r.map((v) => '"' + String(v).replace(/"/g, '""') + '"').join(",")).join("\n");
  const a = document.createElement("a");
  a.href = URL.createObjectURL(new Blob([csv], { type: "text/csv" }));
  a.download = "prediction-markets-tracker-" + PMT.snapshot.replace(/ /g, "-").toLowerCase() + ".csv";
  document.body.appendChild(a); a.click(); a.remove();
}

function StateSearch({ onSelect }) {
  const [q, setQ] = useState("");
  const [open, setOpen] = useState(false);
  const [hi, setHi] = useState(0);
  const wrapRef = React.useRef(null);

  const matches = useMemo(() => {
    const query = q.trim().toLowerCase();
    if (!query) return [];
    return PMT.states
      .filter((s) => s.name.toLowerCase().includes(query) || s.abbr.toLowerCase() === query)
      .slice(0, 8);
  }, [q]);

  React.useEffect(() => {
    function onDocClick(e) { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); }
    document.addEventListener("mousedown", onDocClick);
    return () => document.removeEventListener("mousedown", onDocClick);
  }, []);

  React.useEffect(() => { setHi(0); }, [q]);

  function pick(s) {
    onSelect(s.abbr);
    setQ("");
    setOpen(false);
  }

  function onKeyDown(e) {
    if (!open || !matches.length) return;
    if (e.key === "ArrowDown") { e.preventDefault(); setHi((i) => Math.min(i + 1, matches.length - 1)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setHi((i) => Math.max(i - 1, 0)); }
    else if (e.key === "Enter") { e.preventDefault(); pick(matches[hi]); }
    else if (e.key === "Escape") { setOpen(false); }
  }

  return (
    <div ref={wrapRef} style={{ position: "relative", minWidth: "220px", maxWidth: "360px", flex: "1 1 220px" }}>
      <label style={{ ...SMALL, color: "var(--text-muted)", display: "block", marginBottom: "6px" }}>Jump to a state</label>
      <input
        type="text"
        value={q}
        onChange={(e) => { setQ(e.target.value); setOpen(true); }}
        onFocus={() => setOpen(true)}
        onKeyDown={onKeyDown}
        placeholder="Type a state…"
        aria-label="Jump to a state"
        style={{ width: "100%", boxSizing: "border-box", padding: "9px 12px", border: "1px solid var(--border-default)", borderRadius: "2px", fontSize: "14px", fontFamily: "var(--font-sans)", color: "var(--text-primary)", background: "var(--white)" }}
      />
      {open && matches.length ? (
        <div style={{ position: "absolute", top: "100%", left: 0, right: 0, marginTop: "4px", background: "var(--white)", border: "1px solid var(--border-default)", borderRadius: "2px", boxShadow: "0 6px 18px rgba(0,0,0,0.10)", zIndex: 10, maxHeight: "240px", overflowY: "auto" }}>
          {matches.map((s, i) => (
            <div
              key={s.abbr}
              onMouseDown={(e) => e.preventDefault()}
              onClick={() => pick(s)}
              onMouseEnter={() => setHi(i)}
              style={{ padding: "9px 12px", cursor: "pointer", fontSize: "14px", background: i === hi ? "var(--grey-100)" : "transparent" }}
            >{s.name}</div>
          ))}
        </div>
      ) : null}
    </div>
  );
}

function MapSection() {
  const [view, setView] = useState("state");
  const [platform, setPlatform] = useState("Kalshi");
  const [selected, setSelected] = useState(null);
  const panelRef = React.useRef(null);
  React.useEffect(() => {
    if (selected && panelRef.current && window.innerWidth <= 1180) {
      panelRef.current.scrollIntoView({ behavior: "smooth", block: "start" });
    }
  }, [selected]);
  const legend = view === "trade"
    ? [["#2B57B5", "Sports and events"], ["#9EBDFF", "Events only"], ["#767676", "Blocked"]]
    : view === "platform"
      ? [["#2B57B5", platform + " named"], ["#DEDEDE", "Not named"]]
      : [["#2B57B5", "State ahead"], ["#767676", "CFTC ahead"], ["#9EBDFF", "Unresolved"], ["#DEDEDE", view === "circuit" ? "No ruling" : "No action filed"]];
  const counts = useMemo(() => {
    const c = { state: 0, cftc: 0, unresolved: 0, none: 0 };
    PMT.states.forEach((s) => c[s.status]++);
    return c;
  }, []);
  return (
    <section id="map" className="pmt-wide" style={{ paddingBottom: "88px" }}>
      <div className="pmt-maphead" style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-end", gap: "20px", flexWrap: "wrap", borderBottom: "1px solid var(--border-strong)" }}>
        <div className="pmt-viewtabs" style={{ display: "flex", minWidth: 0, maxWidth: "100%", flex: "1 1 auto", overflowX: "auto", WebkitOverflowScrolling: "touch" }}>
          <Tabs items={VIEWS} value={view} onChange={setView} variant="underline" style={{ border: "0" }} />
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: "10px", paddingBottom: "14px" }}>
          <span className="pmt-live-dot" aria-hidden="true"></span>
          <div style={{ ...SMALL, color: "var(--grey-400)", whiteSpace: "nowrap" }}>Snapshot {PMT.snapshot} · Auto-updating</div>
        </div>
      </div>
      <div className="pmt-legendrow">
        <Legend items={legend} />
        {view === "platform" ? (
          <div style={{ display: "flex", gap: "8px", alignItems: "center", flexWrap: "wrap" }}>
            {PMT.platforms.map((p) => (
              <Tag key={p} selected={platform === p} onClick={() => setPlatform(p)} style={{ cursor: "pointer", fontFamily: "var(--font-sans)", fontWeight: "var(--weight-medium)" }}>{p}</Tag>
            ))}
          </div>
        ) : null}
      </div>
      <div className={"pmt-map-grid" + (selected ? " sel" : "")}>
        <div>
          <div className="pmt-mapscroll">
            <UsMap view={view} platform={platform} selected={selected} onSelect={(a) => setSelected((p) => (p === a ? null : a))} />
          </div>
          <div className="pmt-mapactions" style={{ display: "flex", gap: "14px", alignItems: "flex-end", flexWrap: "wrap", marginTop: "16px" }}>
            <StateSearch onSelect={setSelected} />
          </div>
        </div>
        {selected ? (
          <div className="pmt-panel" ref={panelRef}>
            <StatePanel abbr={selected} onClear={() => setSelected(null)} onPlatform={(p) => {
              setPlatform(p); setView("platform"); setSelected(null);
              setTimeout(() => {
                document.getElementById("map")?.scrollIntoView({ behavior: "smooth", block: "start" });
              }, 50);
            }} />
          </div>
        ) : null}
      </div>
      <div className="pmt-below" style={{ borderTop: "1px solid var(--border-default)", marginTop: "32px", paddingTop: "24px" }}>
        <div style={{ display: "grid", gap: "20px" }}>
          <div className="pmt-statrow" style={{ display: "flex", gap: "36px", flexWrap: "wrap" }}>
            {[[counts.state, "states enforcing"], [counts.cftc, "CFTC ahead"], [counts.unresolved, "unresolved"], [9, "sued by the CFTC"]].map(([n, l]) => (
              <div key={l} className="pmt-statitem" style={{ display: "grid", gap: "6px", justifyItems: "center", textAlign: "center" }}>
                <div className="pmt-statnum" style={{ fontSize: "var(--size-heading-md)", fontWeight: "var(--weight-bold)", letterSpacing: "-0.02em", lineHeight: 1 }}>{n}</div>
                <div style={{ ...SMALL, color: "var(--text-secondary)" }}>{l}</div>
              </div>
            ))}
          </div>
          <p style={{ color: "var(--text-secondary)" }}>{selected ? "Select another state, or clear the selection to see the whole map." : "Select a state for its cases, dates, sources and what you can trade there."}</p>
        </div>
        <div style={{ fontSize: "var(--size-body-sm)", lineHeight: "var(--leading-relaxed)", color: "var(--text-secondary)" }}>{VIEW_NOTE[view]}</div>
      </div>
    </section>
  );
}

function Latest() {
  return (
    <section className="pmt-wrap pmt-sec">
      <SectionHead id="latest" title="The latest from the courts." rule />
      <div className="pmt-meta-row" style={{ paddingTop: "26px" }}>
        <Eyebrow style={{ color: "var(--text-primary)" }}>{PMT.latest.date}</Eyebrow>
        <div style={{ display: "grid", gap: "20px" }}>
          <p className="pmt-prose" style={{ fontSize: "var(--size-body-lg)", lineHeight: "var(--leading-relaxed)", maxWidth: "var(--measure-prose)" }} dangerouslySetInnerHTML={{ __html: PMT.latest.body }}></p>
          <Sources src={PMT.latest.src} />
        </div>
      </div>
    </section>
  );
}

function Watch() {
  return (
    <section className="pmt-wrap pmt-sec">
      <SectionHead id="watch" title="What to watch." rule />
      <div>
        {PMT.watch.map((w, i) => (
          <div key={i} className="pmt-meta-row" style={{ padding: "26px 0", borderBottom: "1px solid var(--border-default)" }}>
            <Eyebrow style={{ color: "var(--text-primary)" }}>{w.when}</Eyebrow>
            <div style={{ display: "grid", gap: "12px" }}>
              <h3 style={{ fontSize: "var(--size-heading-sm)" }}>{w.t}</h3>
              <p className="pmt-prose" style={{ color: "var(--text-secondary)", lineHeight: "var(--leading-relaxed)", maxWidth: "var(--measure-prose)" }}>{w.n}</p>
              <Sources src={w.src} />
            </div>
          </div>
        ))}
      </div>
    </section>
  );
}

function CaseTimeline({ steps }) {
  const doneCount = steps.filter((s) => s.status === "done").length;
  const pct = steps.length > 1 ? (doneCount / (steps.length - 1)) * 100 : 0;
  return (
    <div className="pmt-timeline-wrap">
      <div className="pmt-timeline">
        <div className="pmt-timeline-track"></div>
        <div className="pmt-timeline-track-done" style={{ width: pct + "%" }}></div>
        <div className="pmt-timeline-steps">
          {steps.map((s, i) => (
            <div key={i} className={"pmt-timeline-step pmt-timeline-" + s.status}>
              <div className="pmt-timeline-dot"></div>
              <div className="pmt-timeline-date">{s.d}</div>
              <div className="pmt-timeline-label">{s.label}</div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function parseSortDate(s) {
  if (!s) return new Date(1900, 0, 1);
  const trimmed = s.trim();
  if (trimmed.toLowerCase() === "ongoing" || trimmed.toLowerCase() === "still offshore") {
    const snap = new Date(PMT.snapshot);
    return isNaN(snap.getTime()) ? new Date(2026, 8, 1) : snap;
  }
  let m = trimmed.match(/^([A-Za-z]+) (\d{1,2}), (\d{4})$/);
  if (m) {
    const d = new Date(trimmed);
    if (!isNaN(d.getTime())) return d;
  }
  m = trimmed.match(/^([A-Za-z]+) (\d{1,2})-(\d{1,2}), (\d{4})$/);
  if (m) {
    const d = new Date(`${m[1]} ${m[3]}, ${m[4]}`);
    if (!isNaN(d.getTime())) return d;
  }
  m = trimmed.match(/^([A-Za-z]+) (\d{4})$/);
  if (m) {
    const d = new Date(`${m[1]} 1, ${m[2]}`);
    if (!isNaN(d.getTime())) return d;
  }
  const MONTHS = { jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5, jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11 };
  m = trimmed.match(/(early|mid|late)?\s*([A-Za-z]{3,9})\.?\s+(\d{4})/i);
  if (m) {
    const monKey = m[2].toLowerCase().slice(0, 3);
    if (MONTHS[monKey] !== undefined) {
      const day = { early: 5, mid: 15, late: 25 }[(m[1] || "").toLowerCase()] || 15;
      return new Date(parseInt(m[3], 10), MONTHS[monKey], day);
    }
  }
  const SEASONS = { spring: 3, summer: 6, fall: 9, autumn: 9, winter: 0 };
  m = trimmed.match(/(spring|summer|fall|autumn|winter)\s+(\d{4})/i);
  if (m) return new Date(parseInt(m[2], 10), SEASONS[m[1].toLowerCase()], 15);
  return new Date(1900, 0, 1);
}

function SortToggle({ dir, onChange }) {
  return (
    <button
      onClick={() => onChange(dir === "desc" ? "asc" : "desc")}
      style={{ ...SMALL, display: "inline-flex", alignItems: "center", gap: "6px", cursor: "pointer", background: "none", border: "1px solid var(--border-default)", borderRadius: "2px", padding: "6px 10px", color: "var(--text-secondary)" }}
    >
      {dir === "desc" ? "Newest first" : "Oldest first"} <span aria-hidden="true">{dir === "desc" ? "↓" : "↑"}</span>
    </button>
  );
}

function Perps() {
  const [open, setOpen] = useState(true);
  const [sortDir, setSortDir] = useState("desc");
  const rows = useMemo(() => {
    return [...PMT.perps].sort((a, b) => {
      const diff = parseSortDate(b.d) - parseSortDate(a.d);
      return sortDir === "desc" ? diff : -diff;
    });
  }, [sortDir]);
  return (
    <section data-theme="accent" style={{ background: "var(--blue-700)", color: "var(--text-primary)" }}>
      <div className="pmt-wrap" style={{ paddingTop: "72px", paddingBottom: open ? "72px" : "56px" }}>
        <div id="perps" style={{ display: "grid", gap: "10px", marginBottom: open ? "32px" : "0" }}>
          <button onClick={() => setOpen(!open)} aria-expanded={open} style={{ background: "none", border: "0", padding: "0", cursor: "pointer", color: "inherit", textAlign: "left", display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: "24px", width: "100%" }}>
            <span className="pmt-h2" style={{ fontWeight: "var(--weight-bold)", letterSpacing: "var(--tracking-heading)", lineHeight: "var(--leading-heading)" }}>Perpetual futures</span>
            <span style={{ ...MARK, whiteSpace: "nowrap", opacity: 0.8 }}>{open ? "Hide —" : "Show +"}</span>
          </button>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: "16px", flexWrap: "wrap" }}>
            <p style={{ color: "var(--text-secondary)" }}>Derivative contracts with no expiry date.</p>
            {open ? (
              <button
                onClick={() => setSortDir(sortDir === "desc" ? "asc" : "desc")}
                style={{ ...SMALL, display: "inline-flex", alignItems: "center", gap: "6px", cursor: "pointer", background: "none", border: "1px solid rgba(255,255,255,0.4)", borderRadius: "2px", padding: "6px 10px", color: "var(--text-secondary)", whiteSpace: "nowrap" }}
              >
                {sortDir === "desc" ? "Newest first" : "Oldest first"} <span aria-hidden="true">{sortDir === "desc" ? "↓" : "↑"}</span>
              </button>
            ) : null}
          </div>
        </div>
        {open ? (
          <div style={{ borderTop: "1px solid var(--border-default)" }}>
            {rows.map((p, i) => (
              <div key={i} className="pmt-meta-row" style={{ padding: "30px 0", borderBottom: "1px solid var(--border-default)" }}>
                <Eyebrow style={{ color: "var(--text-primary)", display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }}>
                  {p.d}
                  {isRecentFiling(p.d, PMT.snapshot) ? <Badge tone="neutral" style={{ fontSize: "9px", padding: "2px 5px", background: "var(--white)", color: "var(--blue-700)" }}>New</Badge> : null}
                </Eyebrow>
                <div style={{ display: "grid", gap: "16px" }}>
                  <h3 style={{ fontSize: "var(--size-heading-md)" }} dangerouslySetInnerHTML={{ __html: p.t }}></h3>
                  <p className="pmt-prose" style={{ color: "var(--text-secondary)", lineHeight: "var(--leading-relaxed)", maxWidth: "var(--measure-prose)" }} dangerouslySetInnerHTML={{ __html: p.n }}></p>
                  {p.timeline ? <CaseTimeline steps={p.timeline} /> : null}
                  <Sources src={p.src} />
                </div>
              </div>
            ))}
          </div>
        ) : null}
      </div>
    </section>
  );
}

const FILTERS = [
  { value: "all", label: "All" }, { value: "state", label: "State" }, { value: "cftc", label: "CFTC" },
  { value: "unresolved", label: "Unresolved" }
];

function isRecentFiling(dateStr, snapshotStr) {
  const d = new Date(dateStr);
  const snap = new Date(snapshotStr);
  if (isNaN(d.getTime()) || isNaN(snap.getTime())) return false;
  const diffDays = (snap - d) / (1000 * 60 * 60 * 24);
  return diffDays >= 0 && diffDays <= 2;
}

function Filings() {
  const [f, setF] = useState("all");
  const [sortDir, setSortDir] = useState("desc");
  const rows = useMemo(() => {
    const list = f === "all" ? PMT.filings : PMT.filings.filter((r) => r.kind === f);
    return [...list].sort((a, b) => {
      const diff = parseSortDate(b.d) - parseSortDate(a.d);
      return sortDir === "desc" ? diff : -diff;
    });
  }, [f, sortDir]);
  return (
    <section className="pmt-wrap pmt-sec-top" style={{ paddingBottom: "72px" }}>
      <SectionHead id="filings" title="Rulings, filings, and policy" />
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: "20px", flexWrap: "wrap", marginBottom: "26px" }}>
        <div style={{ display: "flex", minWidth: 0, maxWidth: "100%", overflowX: "auto", WebkitOverflowScrolling: "touch" }}>
          <Tabs items={FILTERS} value={f} onChange={setF} variant="pill" />
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: "16px" }}>
          <Eyebrow>{rows.length} entries</Eyebrow>
          <SortToggle dir={sortDir} onChange={setSortDir} />
        </div>
      </div>
      <div style={{ borderTop: "2px solid var(--border-strong)" }}>
        <div className="pmt-frow pmt-fhead" style={{ padding: "12px 0", borderBottom: "1px solid var(--border-default)" }}>
          {["Date", "Where", "What happened", "Outcome"].map((h) => <Eyebrow key={h} style={{ textAlign: h === "Outcome" ? "center" : "left" }}>{h}</Eyebrow>)}
        </div>
        {rows.map((r, i) => (
          <div key={i} className="pmt-frow" style={{ padding: "20px 0", borderBottom: "1px solid var(--border-default)", alignItems: "start" }}>
            <span className="pmt-frow-date" style={{ ...SMALL, color: "var(--text-primary)", display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" }}>
              {r.d}
              {isRecentFiling(r.d, PMT.snapshot) ? <Badge tone="accent" style={{ fontSize: "9px", padding: "2px 5px" }}>New</Badge> : null}
            </span>
            <div className="pmt-frow-where">
              <div style={{ fontSize: "var(--size-body-sm)", fontWeight: "var(--weight-medium)" }}>{r.where}</div>
              <div style={{ ...SMALL, color: "var(--text-muted)", marginTop: "4px" }}>{r.court}</div>
            </div>
            <div className="pmt-frow-content" style={{ display: "grid", gap: "8px" }}>
              <div style={{ fontWeight: "var(--weight-medium)", lineHeight: "1.35" }}>{r.t}</div>
              <p className="pmt-prose" style={{ color: "var(--text-secondary)", fontSize: "var(--size-body-sm)" }}>{r.n}</p>
              <Sources src={r.src} />
            </div>
            <div className="pmt-frow-badge" style={{ display: "flex", alignItems: "center", justifyContent: "center" }}>{r.kind === "biz" ? null : <StatusBadge status={r.kind} />}</div>
          </div>
        ))}
      </div>
    </section>
  );
}

function Header() {
  const [open, setOpen] = useState(false);
  const nav = [["Map", "#map"], ["Latest", "#latest"], ["Watchlist", "#watch"], ["Filings", "#filings"], ["Perps", "#perps"]];
  return (
    <header style={{ position: "sticky", top: 0, zIndex: 20, background: "var(--white)" }}>
      <div className="pmt-wide" style={{ height: "52px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: "26px" }}>
        <div style={{ display: "flex", alignItems: "center", gap: "10px", minWidth: 0 }}>
          <a href="#map" onClick={() => setOpen(false)} style={{ color: "var(--text-primary)", textDecoration: "none", display: "flex", alignItems: "center", gap: "18px", minWidth: 0 }}>
            <span style={{ fontWeight: "var(--weight-bold)", letterSpacing: "-0.02em", fontSize: "var(--size-body-sm)", whiteSpace: "nowrap" }}>Prediction Markets &amp; Perps</span>
            <span className="pmt-nav" style={{ ...SMALL, color: "var(--text-muted)", paddingLeft: "18px", borderLeft: "1px solid var(--border-default)" }}>U.S. Regulatory Tracker</span>
          </a>
          <a href="https://variant.fund" target="_blank" rel="noopener" className="pmt-mobile-icon" style={{ display: "none", flex: "none" }}>
            <img src="assets/variant-mark.svg" alt="Variant" style={{ width: "16px", height: "16px", display: "block" }} />
          </a>
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: "26px" }}>
          <nav className="pmt-navlinks" style={{ display: "flex", gap: "26px", alignItems: "center" }}>
            {nav.map(([l, h]) => <Link key={h} href={h} variant="nav">{l}</Link>)}
          </nav>
          <div className="pmt-navlinks" style={{ width: "1px", height: "20px", background: "var(--border-default)" }}></div>
          <a href="https://variant.fund" target="_blank" rel="noopener" title="Built with Variant" className="pmt-navlinks" style={{ display: "flex" }}>
            <img src="assets/variant-full-logo.png" alt="Variant" style={{ height: "17px", width: "auto", display: "block" }} />
          </a>
          <button
            onClick={() => setOpen((v) => !v)}
            aria-label={open ? "Close menu" : "Open menu"}
            aria-expanded={open}
            className="pmt-hamburger"
            style={{ display: "none", alignItems: "center", justifyContent: "center", cursor: "pointer", background: "none", border: "1px solid var(--border-default)", borderRadius: "2px", width: "34px", height: "34px", color: "var(--text-primary)", flex: "none" }}
          >
            <span style={{ fontSize: "16px", lineHeight: 1 }}>{open ? "✕" : "☰"}</span>
          </button>
        </div>
      </div>
      {open ? (
        <div style={{ borderTop: "1px solid var(--border-default)", background: "var(--white)", position: "absolute", left: 0, right: 0 }}>
          <div className="pmt-wrap" style={{ display: "grid", paddingTop: "6px", paddingBottom: "10px" }}>
            {nav.map(([l, h]) => (
              <a key={h} href={h} onClick={() => setOpen(false)} style={{ ...LABEL, fontSize: "14px", padding: "13px 0", color: "var(--text-primary)", textDecoration: "none", borderBottom: "1px solid var(--border-default)" }}>{l}</a>
            ))}
            <button onClick={() => { setOpen(false); downloadCsv(); }} style={{ ...LABEL, fontSize: "14px", textAlign: "left", padding: "13px 0", background: "none", border: "0", cursor: "pointer", color: "var(--link)" }}>Download CSV →</button>
          </div>
        </div>
      ) : null}
    </header>
  );
}

function Hero() {
  React.useEffect(() => {
    const sync = () => {
      const a = document.querySelector("header nav a");
      const wrap = document.querySelector(".pmt-hero");
      if (!a || !wrap) return;
      const w = wrap.getBoundingClientRect();
      const n = a.getBoundingClientRect();
      const col = Math.max(200, Math.round(w.right - parseFloat(getComputedStyle(wrap).paddingRight) - n.left));
      document.documentElement.style.setProperty("--dek-col", col + "px");
    };
    sync();
    requestAnimationFrame(sync);
    if (document.fonts && document.fonts.ready) document.fonts.ready.then(sync);
    window.addEventListener("resize", sync);
    return () => window.removeEventListener("resize", sync);
  }, []);
  return (
    <div className="pmt-wide pmt-hero">
      <h1 className="pmt-h1" style={{ position: "relative", zIndex: 1, lineHeight: "var(--leading-display)", letterSpacing: "var(--tracking-display)", fontStyle: "italic", maxWidth: "18ch" }}>Who regulates prediction markets?</h1>
      <div className="pmt-dek" style={{ position: "relative", zIndex: 1 }}>
        <p style={{ color: "var(--text-secondary)", lineHeight: "var(--leading-body)", fontSize: "var(--size-body-sm)" }}>A state-by-state tracker for prediction markets — plus the federal fight over perpetual futures.</p>
        <a href="#perps" className="pmt-jumppill"><span className="pmt-jumppill-plus">+</span> Perps <span aria-hidden="true">→</span></a>
      </div>
    </div>
  );
}

function Footer() {
  return (
    <footer style={{ borderTop: "1px solid var(--border-default)" }}>
      <div className="pmt-wrap" style={{ padding: "36px 32px 56px", display: "flex", justifyContent: "space-between", gap: "20px", flexWrap: "wrap", alignItems: "center" }}>
        <Eyebrow>Not legal advice.</Eyebrow>
        <div style={{ display: "flex", gap: "24px", alignItems: "center", flexWrap: "wrap" }}>
          <a href="https://variant.fund" target="_blank" rel="noopener" title="Built with Variant" style={{ display: "flex" }}>
            <img src="assets/variant-full-logo.png" alt="Variant" style={{ height: "14px", width: "auto", display: "block" }} />
          </a>
          <Eyebrow>Snapshot {PMT.snapshot}</Eyebrow>
          <button onClick={downloadCsv} style={{ ...SMALL, cursor: "pointer", background: "none", border: "0", padding: "0", color: "var(--link)" }}>Download CSV →</button>
        </div>
      </div>
    </footer>
  );
}

function App() {
  return (
    <div>
      <Header />
      <Hero />
      <MapSection />
      <Latest />
      <Watch />
      <Filings />
      <Perps />
      <Footer />
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
