// Pages for 株価急騰要因メモ

const { useState: usePState, useMemo: useMPMemo } = React;

// ArchivePage のフィルタ状態を、別ページへ遷移して戻ってきても保持するための簡易ストア。
// （scroll コンテナが key={page} で再マウントされ通常の useState はリセットされるため）
const _archiveUIStore = {};
function useArchiveState(key, initial) {
  const [v, setV] = usePState(() => (key in _archiveUIStore ? _archiveUIStore[key] : initial));
  const set = React.useCallback((next) => {
    setV(prev => {
      const resolved = typeof next === "function" ? next(prev) : next;
      _archiveUIStore[key] = resolved;
      return resolved;
    });
  }, [key]);
  return [v, set];
}

// ── アーカイブ検索 ──
// 正規化: 全角→半角(NFKC)・小文字化・ひらがな→カタカナ。
// 「ＰＣＢ」「pcb」「PCB」を同一視するため。
function _searchNorm(s) {
  return String(s || "")
    .normalize("NFKC")
    .toLowerCase()
    .replace(/[\u3041-\u3096]/g, ch => String.fromCharCode(ch.charCodeAt(0) + 0x60));
}

// 投資家がよく使う略語・言い換え。入力がグループ内のいずれかに一致したら、
// グループ内のどの語で書かれていてもヒットさせる。
const SEARCH_SYNONYMS = [
  ["PCB", "プリント基板", "プリント配線板", "銅張積層板", "基板"],
  ["MLCC", "積層セラミックコンデンサ", "セラミックコンデンサ", "コンデンサ"],
  ["半導体製造装置", "SPE", "前工程", "後工程", "製造装置"],
  ["HBM", "広帯域メモリ", "メモリ"],
  ["AI", "人工知能", "生成AI"],
  ["データセンター", "DC", "サーバー"],
  ["EV", "電気自動車", "車載電池"],
  ["再エネ", "再生可能エネルギー", "太陽光", "風力", "ペロブスカイト"],
  ["原子力", "原発", "再稼働", "SMR"],
  ["防衛", "安全保障", "軍事", "装備品"],
  ["宇宙", "衛星", "ロケット"],
  ["インバウンド", "訪日", "観光"],
  ["TOB", "MBO", "公開買付", "買収"],
  ["上方修正", "上振れ"],
  ["増配", "配当", "株主還元", "自社株買い"],
  ["バイオ", "創薬", "治験", "パイプライン"],
  ["量子", "量子コンピュータ", "quantum"],
  ["サイバーセキュリティ", "セキュリティ"],
  ["円安", "為替"],
];
const _SYN_GROUPS = SEARCH_SYNONYMS.map(g => g.map(_searchNorm));

// エントリの全テキストフィールドを検索対象に束ねる
function _entryHaystack(e) {
  return _searchNorm([
    e.code, e.name, e.reason, e.cat, e.business, e.related_stocks, e.raw_post,
    ...(e.themes || []),
    ...(e.keywords_all || []),
    ...(Array.isArray(e.backgrounds) ? e.backgrounds : []),
  ].filter(Boolean).join(" "));
}

// スペース区切りは AND 検索。各語は本文一致 or 類義語グループ経由で判定
function _entryMatchesQuery(e, rawQuery) {
  const terms = _searchNorm(rawQuery).split(/\s+/).filter(Boolean);
  if (!terms.length) return true;
  const hay = _entryHaystack(e);
  return terms.every(term => {
    if (hay.includes(term)) return true;
    for (const group of _SYN_GROUPS) {
      if (group.includes(term) && group.some(w => hay.includes(w))) return true;
    }
    return false;
  });
}

// JSTの「今日」かどうかを判定。detected_atが無いサンプル銘柄は本日扱い。
function _isTodayJST(stock) {
  if (!stock) return false;
  if (!stock.detected_at) return true; // sample fallback
  try {
    const fmt = new Intl.DateTimeFormat("en-CA", {
      timeZone: "Asia/Tokyo", year: "numeric", month: "2-digit", day: "2-digit",
    });
    const today = fmt.format(new Date());
    const target = fmt.format(new Date(stock.detected_at));
    return today === target;
  } catch (e) {
    return false;
  }
}
function _todayStocks(stocks) {
  return (stocks || []).filter(_isTodayJST);
}

// カテゴリ判定用のキーワード（CATEGORIES の key に対応）
const _CATEGORY_TOKENS = {
  earn:   ["好決算", "決算"],
  upward: ["上方修正", "業績.*上方", "上振れ"],
  div:    ["増配", "配当"],
  tob:    ["TOB", "M&A", "MBO", "公開買付"],
  ai:     ["AI", "半導体", "GPU", "生成AI"],
  bio:    ["バイオ", "創薬", "治験"],
  def:    ["防衛", "装備"],
  fx:     ["円安"],
  pol:    ["政策", "規制", "補助金", "原発", "原子力"],
  low:    ["低位"],
};
function _matchCategory(entry, cat) {
  if (!entry || !cat) return false;
  const reason = String(entry.reason || entry.cat || "");
  const themes = entry.themes || [];
  const tokens = _CATEGORY_TOKENS[cat.key] || [cat.label];
  return tokens.some(t => {
    if (reason.includes(t)) return true;
    return themes.some(th => String(th).includes(t));
  });
}
// アーカイブ全件を CATEGORIES に集計
function useCategoryCounts() {
  const archive = window.useArchive ? window.useArchive({ pageSize: 200 }) : { entries: window.SAMPLE_ARCHIVE || [] };
  return useMPMemo(() => {
    const entries = archive.entries || [];
    if (entries.length === 0) {
      return { cats: CATEGORIES, total: 0, ready: false };
    }
    const cats = CATEGORIES.map(c => {
      const count = entries.reduce((acc, e) => acc + (_matchCategory(e, c) ? 1 : 0), 0);
      return { ...c, count };
    });
    return { cats, total: entries.length, ready: true };
  }, [archive.entries]);
}

// 検知実績ランキング — 期間切替（1ヶ月/3ヶ月/半年/1年）。上げ幅（メイン）と上昇ペースをトグルで切替
function SlopeRankingSection({ navigate }) {
  // 集計期間（日数）
  const [rankDays, setRankDays] = React.useState(() => {
    try {
      const v = Number(localStorage.getItem("shodo_rank_days"));
      return [31, 92, 183, 365].includes(v) ? v : 31;
    } catch (e) { return 31; }
  });
  const switchDays = d => { setRankDays(d); try { localStorage.setItem("shodo_rank_days", String(d)); } catch (e) {} };
  const rec = window.useRecentEntries ? window.useRecentEntries(rankDays) : { entries: [] };
  // 表示モード: gain=上げ幅（メイン） / slope=上昇ペース
  const [rankMode, setRankMode] = React.useState(() => {
    try { return localStorage.getItem("shodo_rank_mode") === "slope" ? "slope" : "gain"; } catch (e) { return "gain"; }
  });
  const switchMode = m => { setRankMode(m); try { localStorage.setItem("shodo_rank_mode", m); } catch (e) {} };
  const ranked = useMPMemo(() => {
    const now = Date.now();
    const best = new Map(); // code → 最良エントリ
    (rec.entries || []).forEach(e => {
      const sp = Number(e.surge_price ?? e.price);
      const cp = e.current_price != null ? Number(e.current_price) : NaN;
      if (!sp || sp <= 0 || isNaN(cp)) return;
      const t = e.detected_at ? new Date(e.detected_at).getTime() : NaN;
      if (isNaN(t) || now - t > rankDays * 86400000 || t > now + 60000) return;
      const totalPct = ((cp - sp) / sp) * 100;
      if (totalPct <= 0) return;
      const days = Math.max(1, (now - t) / 86400000);
      const slope = totalPct / days;
      const score = rankMode === "slope" ? slope : totalPct;
      const cur = best.get(String(e.code));
      if (!cur || score > cur.score) best.set(String(e.code), { e, slope, totalPct, days, score });
    });
    return [...best.values()].sort((a, b) => b.score - a.score).slice(0, 5);
  }, [rec.entries, rankMode, rankDays]);

  const periodLbl = ({ 31: "1ヶ月", 92: "3ヶ月", 183: "半年", 365: "1年" })[rankDays] || "1ヶ月";

  return (
    <>
      <SectionHeader kicker={`直近${periodLbl}の検知実績`} title={rankMode === "slope" ? "初動後、伸びが速い銘柄" : "初動後、大きく伸びた銘柄"}/>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, flexWrap: "wrap", margin: "0 14px 8px" }}>
        <div style={{ display: "inline-flex", border: "1px solid var(--hairline-strong)" }}>
          {[[31, "1ヶ月"], [92, "3ヶ月"], [183, "半年"], [365, "1年"]].map(([d, lbl]) => {
            const on = rankDays === d;
            return (
              <button key={d} className="tappable" onClick={() => switchDays(d)} style={{
                fontSize: 10.5, padding: "4px 8px", cursor: "pointer",
                fontFamily: "inherit", border: "none",
                background: on ? "var(--navy)" : "transparent",
                color: on ? "#fff" : "var(--muted)",
                fontWeight: on ? 600 : 400,
                letterSpacing: "0.02em",
              }}>{lbl}</button>
            );
          })}
        </div>
        <div style={{ display: "inline-flex", border: "1px solid var(--hairline-strong)" }}>
          {[["gain", "上げ幅"], ["slope", "上昇ペース"]].map(([m, lbl]) => {
            const on = rankMode === m;
            return (
              <button key={m} className="tappable" onClick={() => switchMode(m)} style={{
                fontSize: 10.5, padding: "4px 10px", cursor: "pointer",
                fontFamily: "inherit", border: "none",
                background: on ? "var(--navy)" : "transparent",
                color: on ? "#fff" : "var(--muted)",
                fontWeight: on ? 600 : 400,
                letterSpacing: "0.04em",
              }}>{lbl}</button>
            );
          })}
        </div>
      </div>
      <div style={{ background: "var(--surface)", borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
        {ranked.length === 0 && (
          <div style={{ padding: "22px 16px", fontSize: 11.5, color: "var(--muted)", textAlign: "center", lineHeight: 1.8 }}>
            {rec.loading ? "読み込み中…" : "この期間に対象となる検知はありません。"}
          </div>
        )}
        {ranked.map((r, i) => {
          let dateLbl = "";
          try {
            const d = new Date(r.e.detected_at);
            dateLbl = `${d.getMonth() + 1}/${d.getDate()}`;
          } catch (err) {}
          const dispDays = Math.max(1, Math.round(r.days));
          return (
            <button key={r.e.id || r.e.code} className="tappable" onClick={() => navigate("stock", r.e)} style={{
              width: "100%", display: "grid", gridTemplateColumns: "30px 1fr auto",
              gap: 10, alignItems: "center",
              padding: "11px 14px", cursor: "pointer", textAlign: "left",
              background: "transparent", border: "none",
              borderBottom: i < ranked.length - 1 ? "1px solid var(--hairline)" : "none",
              fontFamily: "inherit",
            }}>
              <div className="mincho" style={{
                fontSize: 17, fontWeight: 700, textAlign: "center",
                color: i === 0 ? "var(--navy)" : "var(--muted-2)",
              }}>{i + 1}</div>
              <div style={{ minWidth: 0 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                  <span className="mono" style={{ fontSize: 10.5, color: "var(--muted)", letterSpacing: "0.04em" }}>{r.e.code}</span>
                  <span style={{
                    fontSize: 13.5, fontWeight: 600, color: "var(--ink)",
                    overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0,
                  }}>{r.e.name}</span>
                </div>
                <div className="mono" style={{ fontSize: 10.5, color: "var(--muted)", marginTop: 2 }}>
                  {rankMode === "slope"
                    ? `${dateLbl} 検知 · ${dispDays}日で +${r.totalPct.toFixed(1)}%`
                    : `${dateLbl} 検知 · ${dispDays}日間 · +${r.slope.toFixed(1)}%/日`}
                </div>
              </div>
              {rankMode === "slope" ? (
                <div className="mono" style={{ fontSize: 15, fontWeight: 700, color: "var(--up)", whiteSpace: "nowrap" }}>
                  +{r.slope.toFixed(1)}<span style={{ fontSize: 10, fontWeight: 500, color: "var(--muted)" }}>%/日</span>
                </div>
              ) : (
                <div className="mono" style={{ fontSize: 16, fontWeight: 700, color: "var(--up)", whiteSpace: "nowrap" }}>
                  +{r.totalPct.toFixed(1)}<span style={{ fontSize: 10.5, fontWeight: 500, color: "var(--muted)" }}>%</span>
                </div>
              )}
            </button>
          );
        })}
      </div>
      <div style={{ padding: "6px 14px 0", fontSize: 10, color: "var(--muted-2)", lineHeight: 1.6 }}>
        {rankMode === "slope"
          ? "※ 上昇ペース =（現在値 − 急騰時値）÷ 急騰時値 ÷ 検知からの経過日数。同一銘柄は最良の回を採用。"
          : `※ 上げ幅 =（現在値 − 急騰時値）÷ 急騰時値。検知から${periodLbl}以内の上昇分。同一銘柄は最良の回を採用。`}
      </div>
    </>
  );
}

// 今週のテーマ勢力図 — 直近7日の検知をテーマ別に集計
function ThemeHeatSection({ navigate }) {
  const rec = window.useRecentEntries ? window.useRecentEntries(7) : { entries: [] };
  const tally = useMPMemo(() => {
    const now = Date.now();
    const m = new Map();
    (rec.entries || []).forEach(e => {
      const t = e.detected_at ? new Date(e.detected_at).getTime() : now;
      if (isNaN(t) || now - t > 7 * 86400000) return;
      (e.themes || []).forEach(th => {
        const k = String(th).trim();
        if (k) m.set(k, (m.get(k) || 0) + 1);
      });
    });
    return [...m.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
  }, [rec.entries]);

  if (tally.length === 0) return null;
  const max = tally[0][1];

  return (
    <>
      <SectionHeader kicker="直近7日の物色" title="今週のテーマ勢力図"/>
      <div style={{ background: "var(--surface)", borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
        {tally.map(([t, n], i) => (
          <button key={t} className="tappable" onClick={() => navigate("theme", t)} style={{
            width: "100%", textAlign: "left", cursor: "pointer",
            padding: "9px 14px 10px", border: "none", background: "transparent", fontFamily: "inherit",
            borderBottom: i < tally.length - 1 ? "1px solid var(--hairline)" : "none",
          }}>
            <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
              <span style={{ fontSize: 12.5, fontWeight: 600, color: "var(--ink)" }}>{t}</span>
              <span className="mono" style={{ marginLeft: "auto", fontSize: 10.5, color: "var(--muted)" }}>
                <span style={{ fontSize: 13, fontWeight: 700, color: "var(--ink)" }}>{n}</span> 件
              </span>
            </div>
            <div style={{ marginTop: 5, height: 4, background: "var(--surface-sub)" }}>
              <div style={{ width: `${(n / max) * 100}%`, height: "100%", background: "var(--navy)", opacity: 0.35 + 0.65 * (n / max) }}></div>
            </div>
          </button>
        ))}
      </div>
    </>
  );
}

// 常連銘柄 — 直近1ヶ月で検知回数の多い銘柄
function RegularsSection({ navigate }) {
  const rec = window.useRecentEntries ? window.useRecentEntries(31) : { entries: [] };
  const regs = useMPMemo(() => {
    const now = Date.now();
    const m = new Map();
    (rec.entries || []).forEach(e => {
      const t = e.detected_at ? new Date(e.detected_at).getTime() : now;
      if (isNaN(t) || now - t > 31 * 86400000) return;
      const c = String(e.code);
      const cur = m.get(c);
      if (!cur) m.set(c, { count: 1, latest: e, t });
      else {
        cur.count++;
        if (t > cur.t) { cur.latest = e; cur.t = t; }
      }
    });
    return [...m.values()].filter(r => r.count >= 2)
      .sort((a, b) => b.count - a.count || b.t - a.t).slice(0, 5);
  }, [rec.entries]);

  if (regs.length === 0) return null;

  return (
    <>
      <SectionHeader kicker="直近1ヶ月で繰り返し検知" title="常連銘柄"/>
      <div style={{ background: "var(--surface)", borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
        {regs.map((r, i) => {
          let dateLbl = "";
          try {
            const d = new Date(r.t);
            if (!isNaN(d)) dateLbl = `${d.getMonth() + 1}/${d.getDate()}`;
          } catch (e) {}
          return (
            <button key={r.latest.code} className="tappable" onClick={() => navigate("stock", r.latest)} style={{
              width: "100%", display: "grid", gridTemplateColumns: "44px 1fr auto", gap: 10, alignItems: "center",
              padding: "11px 14px", textAlign: "left", cursor: "pointer",
              border: "none", background: "transparent", fontFamily: "inherit",
              borderBottom: i < regs.length - 1 ? "1px solid var(--hairline)" : "none",
            }}>
              <div style={{ textAlign: "center", whiteSpace: "nowrap" }}>
                <span className="mono" style={{ fontSize: 17, fontWeight: 700, color: "var(--navy)" }}>{r.count}</span>
                <span style={{ fontSize: 10, color: "var(--muted)" }}>回</span>
              </div>
              <div style={{ minWidth: 0 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                  <span className="mono" style={{ fontSize: 10.5, color: "var(--muted)", letterSpacing: "0.04em" }}>{r.latest.code}</span>
                  <span style={{ fontSize: 13.5, fontWeight: 600, color: "var(--ink)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", minWidth: 0 }}>{r.latest.name}</span>
                </div>
                <div className="mono" style={{ fontSize: 10.5, color: "var(--muted)", marginTop: 2 }}>直近 {dateLbl} 検知</div>
              </div>
              <span aria-hidden="true" style={{ fontSize: 13, color: "var(--muted-2)" }}>›</span>
            </button>
          );
        })}
      </div>
    </>
  );
}

// ===== HOME =====
function HomePage({ navigate }) {
  const live = window.useLiveData ? window.useLiveData() : { stocks: SURGE_STOCKS, newIds: new Set(), status: "fallback", lastUpdate: null };
  const stocks = useMPMemo(() => _todayStocks(live.stocks), [live.stocks]);
  const catData = useCategoryCounts();

  return (
    <div className="page-enter">
      <Hero
        onPrimary={() => navigate("surges")}
        onSecondary={() => navigate("report")}
        stockCount={stocks.length}
        status={live.status}
        lastUpdate={live.lastUpdate}
      />

      {/* Today's surges */}
      <SectionHeader
        kicker="本日の急騰銘柄"
        title="今日動いた日本株"
        more={stocks.length > 4 ? "すべて見る" : null}
        onMore={() => navigate("surges")}
      />
      <div style={{ background: "var(--surface)", borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
        {stocks.length === 0 ? (
          <EmptySurges status={live.status}/>
        ) : stocks.slice(0, 4).map((s, i) => (
          <StockCard
            key={s.id || s.code + (s.time || i)}
            stock={s}
            idx={i}
            isNew={live.newIds && live.newIds.has(s.id)}
            onOpen={() => navigate("stock", s)}
          />
        ))}
      </div>

      {/* ── ゾーン: 蓄積データ ── */}
      <ZoneBreak label="蓄積から読む" sub="検知データの記録と傾向"/>

      {/* 初動後の伸びランキング */}
      <SlopeRankingSection navigate={navigate}/>

      {/* 今週のテーマ勢力図 */}
      <ThemeHeatSection navigate={navigate}/>

      {/* 常連銘柄 */}
      <RegularsSection navigate={navigate}/>

      {/* Categories */}
      <SectionHeader
        kicker="急騰理由カテゴリ"
        title="材料・テーマで読み解く"
      />
      <div style={{
        padding: "0 12px 4px",
        display: "grid", gridTemplateColumns: "1fr 1fr",
        gap: 8,
      }}>
        {catData.cats.filter(c => c.count > 0).map(c => (
          <button key={c.key} className="tappable" onClick={() => navigate("category", c)} style={{
            background: "var(--surface)",
            border: "1px solid var(--hairline)",
            padding: "12px 12px",
            display: "flex", alignItems: "center", justifyContent: "space-between",
            cursor: "pointer", textAlign: "left",
          }}>
            <div>
              <div style={{ fontSize: 13.5, fontWeight: 500, color: "var(--ink)" }}>{c.label}</div>
              <div className="mono" style={{ fontSize: 10.5, color: "var(--muted)", marginTop: 2 }}>
                {c.count} 件
              </div>
            </div>
            <div className="mincho" style={{
              fontSize: 13, color: "var(--navy)",
              width: 28, height: 28,
              border: "1px solid var(--hairline-strong)",
              display: "flex", alignItems: "center", justifyContent: "center",
              letterSpacing: 0,
            }}>{c.icon}</div>
          </button>
        ))}
      </div>

      {/* Free articles */}
      <SectionHeader
        kicker="無料記事"
        title="無料で読める急騰理由メモ"
      />
      <div style={{ padding: "0 12px" }}>
        {FREE_ARTICLES.map((a, i) => (
          <article key={i} className="tappable" onClick={() => navigate("article", a)} style={{
            background: "var(--surface)",
            border: "1px solid var(--hairline)",
            padding: "14px 14px",
            marginBottom: 8, cursor: "pointer",
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }}>
              <span className="mono" style={{ fontSize: 10.5, color: "var(--muted)" }}>{a.date}</span>
              <span style={{
                fontSize: 9.5, color: "var(--muted)",
                border: "1px solid var(--hairline-strong)",
                padding: "0 4px", letterSpacing: "0.08em",
              }}>無料</span>
              <span className="mono" style={{ fontSize: 10, color: "var(--muted-2)", marginLeft: "auto" }}>
                {a.read}で読める
              </span>
            </div>
            <div style={{ fontSize: 14.5, fontWeight: 600, marginBottom: 4 }}>
              <span className="mono" style={{ fontSize: 11.5, color: "var(--muted)", marginRight: 8 }}>{a.code}</span>
              {a.name} ・ {a.reason}
            </div>
            <p style={{
              margin: 0, fontSize: 12.5, color: "var(--ink-soft)",
              lineHeight: 1.7,
            }}>{a.summary}</p>
            <div style={{
              marginTop: 10, paddingTop: 10,
              borderTop: "1px dashed var(--hairline)",
              fontSize: 11, color: "var(--navy)", fontWeight: 500,
            }}>
              続きを読む →
            </div>
          </article>
        ))}
      </div>

      {/* 広告枠（PR）— ads-config.js でON/OFF */}
      {window.HomeScouterAd && <window.HomeScouterAd navigate={navigate}/>}

      {/* ── ゾーン: ご案内 ── */}
      <ZoneBreak label="ご案内" sub="有料会員・料金プラン"/>

      {/* Paid promo */}
      <PaidPromo navigate={navigate} />

      {/* Pricing */}
      <PricingSection navigate={navigate} />

      {/* 広告帯（PR）— ads-config.js でON/OFF */}
      {window.FooterAdStrip && <window.FooterAdStrip navigate={navigate}/>}

      {/* Disclaimer + Footer */}
      <Disclaimer />
      <Footer navigate={navigate} />
    </div>
  );
}

// Paid member promo block — leads into report dashboard
function PaidPromo({ navigate }) {
  return (
    <div style={{ padding: "30px 14px 4px" }}>
      <div style={{
        background: "var(--navy-deep)",
        color: "#fff",
        padding: "22px 18px 18px",
        position: "relative", overflow: "hidden",
      }}>
        <div aria-hidden="true" style={{
          position: "absolute", inset: 0,
          backgroundImage: "linear-gradient(90deg, rgba(255,255,255,.04) 1px, transparent 1px), linear-gradient(rgba(255,255,255,.04) 1px, transparent 1px)",
          backgroundSize: "20px 20px",
        }}/>
        <div style={{ position: "relative" }}>
          <div className="eyebrow" style={{ color: "var(--gold)", letterSpacing: "0.22em", marginBottom: 10 }}>
            有料会員限定
          </div>
          <h2 className="mincho" style={{
            margin: 0, fontSize: 22, fontWeight: 700, letterSpacing: "0.02em",
          }}>
            資金流入レポート
          </h2>
          <p style={{
            margin: "10px 0 16px",
            fontSize: 12.5, lineHeight: 1.75, color: "#c0c7d6",
          }}>
            日々の急騰背景を分類し、どのテーマに資金が流れているのか、
            過去の急騰パターンと比較しながら分析します。
          </p>

          <ul style={{
            listStyle: "none", padding: 0, margin: "0 0 18px",
            display: "grid", gap: 8,
            fontSize: 12, color: "#dfe3ec",
          }}>
            {[
              "今週の資金流入テーマ",
              "急騰理由ランキング",
              "過去データに基づくバックテスト分析",
              "注目されやすい材料テーマ",
              "急騰初動の特徴",
              "週次レポート",
              "過熱感のあるテーマの注意点",
            ].map(t => (
              <li key={t} style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{
                  width: 14, height: 14, flexShrink: 0,
                  border: "1px solid var(--gold)", color: "var(--gold)",
                  display: "inline-flex", alignItems: "center", justifyContent: "center",
                  fontSize: 9,
                }}>✓</span>
                {t}
              </li>
            ))}
          </ul>

          <div style={{ display: "flex", gap: 8 }}>
            <button className="btn btn-gold tappable" onClick={() => navigate("report")} style={{ flex: 1 }}>
              有料レポートを見る
            </button>
            <button className="btn tappable" onClick={() => navigate("plan")} style={{
              flex: 1, background: "transparent", color: "#fff",
              border: "1px solid rgba(255,255,255,0.4)",
            }}>
              会員登録する
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// Pricing
function PricingSection({ navigate }) {
  return (
    <>
      <SectionHeader kicker="料金プラン" title="シンプルな料金プラン" />
      <div style={{ padding: "12px 14px 0", display: "grid", gap: 10 }}>
        <PlanCard
          name="無料プラン"
          price="¥0"
          per="ずっと無料"
          features={["今日の急騰理由メモ", "急騰理由カテゴリ", "アーカイブ（直近1週間）"]}
          cta="このまま利用する"
        />
        <PlanCard
          highlight
          name="プレミアム"
          price="¥500"
          per="月額 / 税込 ・ いつでも解約可"
          features={[
            "資金流入レポート（週次）",
            "過去事例・継続率の分析",
            "急騰理由ランキング",
            "バックテスト分析",
            "過去データベース（全期間）",
          ]}
          cta="会員登録する"
          onCta={() => navigate("plan")}
        />
      </div>
    </>
  );
}

function PlanCard({ name, price, per, features, cta, onCta, highlight }) {
  return (
    <div style={{
      background: highlight ? "var(--surface)" : "transparent",
      border: highlight ? "1px solid var(--ink)" : "1px solid var(--hairline)",
      padding: "16px 16px 14px",
      position: "relative",
    }}>
      {highlight && (
        <div style={{
          position: "absolute", top: -1, right: -1,
          background: "var(--ink)", color: "var(--bg)",
          fontSize: 9.5, letterSpacing: "0.14em", padding: "3px 8px",
        }}>RECOMMENDED</div>
      )}
      <div style={{ fontSize: 12, color: "var(--muted)", letterSpacing: "0.08em" }}>{name}</div>
      <div style={{ display: "flex", alignItems: "baseline", gap: 6, marginTop: 6 }}>
        <span className="mono" style={{ fontSize: 30, fontWeight: 600, letterSpacing: "-0.01em" }}>{price}</span>
        <span style={{ fontSize: 11, color: "var(--muted)" }}>{per}</span>
      </div>
      <ul style={{
        listStyle: "none", padding: 0, margin: "12px 0 14px",
        display: "grid", gap: 6, fontSize: 12.5,
      }}>
        {features.map(f => (
          <li key={f} style={{ display: "flex", alignItems: "center", gap: 8, color: "var(--ink-soft)" }}>
            <span style={{ color: "var(--navy)", fontSize: 12 }}>—</span>
            {f}
          </li>
        ))}
      </ul>
      <button onClick={onCta} className={`btn tappable ${highlight ? "btn-primary" : "btn-secondary"}`} style={{ width: "100%" }}>
        {cta}
      </button>
    </div>
  );
}

function Disclaimer() {
  return (
    <div style={{
      margin: "30px 14px 16px",
      padding: "12px 14px",
      background: "var(--surface-sub)",
      borderLeft: "2px solid var(--muted-2)",
      fontSize: 11, lineHeight: 1.75, color: "var(--muted)",
    }}>
      <div style={{
        fontSize: 9.5, letterSpacing: "0.16em",
        color: "var(--ink-soft)", marginBottom: 4, fontWeight: 600,
      }}>免責事項</div>
      本サイトは、公開情報および過去データをもとにした情報整理・分析を目的としており、特定の金融商品の売買を推奨するものではありません。投資判断はご自身の責任で行ってください。
    </div>
  );
}

function Footer({ navigate }) {
  const links = [
    { label: "運営者情報" },
    { label: "特定商取引法に基づく表記", page: "legal" },
    { label: "プライバシーポリシー" },
    { label: "免責事項" },
    { label: "お問い合わせ" },
  ];
  return (
    <footer style={{
      padding: "20px 18px 28px",
      background: "var(--navy-deep)", color: "#9aa3b8",
      fontSize: 11, lineHeight: 1.9,
    }}>
      <div className="mincho" style={{
        color: "#fff", fontSize: 14, fontWeight: 700, letterSpacing: "0.04em",
        marginBottom: 12,
      }}>株価急騰要因メモ</div>
      <ul style={{ listStyle: "none", padding: 0, margin: 0, display: "grid", gap: 4 }}>
        {links.map(l => (
          <li key={l.label}>
            <a
              href="#"
              onClick={e => { e.preventDefault(); if (l.page && navigate) navigate(l.page); }}
              style={{ color: "inherit", textDecoration: "none", cursor: l.page ? "pointer" : "default" }}
            >{l.label}</a>
          </li>
        ))}
        <li style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 4 }}>
          <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2H21l-6.55 7.485L22 22h-6.804l-5.323-6.965L3.74 22H1l7.01-8.013L1.5 2h6.93l4.814 6.367L18.244 2Zm-1.193 18.4h1.55L7.044 3.5H5.39L17.05 20.4Z"/></svg>
          @kyuto_memo
        </li>
      </ul>
      <div style={{
        marginTop: 18, paddingTop: 12,
        borderTop: "1px solid rgba(255,255,255,0.08)",
        fontSize: 10, color: "#6b738a",
      }}>
        © 2026 株価急騰要因メモ. All rights reserved.
      </div>
    </footer>
  );
}


// ===== SURGES (all today) =====
function SurgesPage({ navigate }) {
  const [sortBy, setSortBy] = usePState("time");
  const live = window.useLiveData ? window.useLiveData() : { stocks: SURGE_STOCKS, newIds: new Set(), status: "fallback" };
  const sorted = useMPMemo(() => {
    const arr = _todayStocks(live.stocks);
    if (sortBy === "pct")  arr.sort((a, b) => (b.pct ?? -Infinity) - (a.pct ?? -Infinity));
    if (sortBy === "time") arr.sort((a, b) => (b.detected_at || b.time || "").localeCompare(a.detected_at || a.time || ""));
    if (sortBy === "vol")  arr.sort((a, b) => {
      const f = v => v ? parseInt(String(v).replace(/[,万]/g, "")) : 0;
      return f(b.vol) - f(a.vol);
    });
    return arr;
  }, [sortBy, live.stocks]);

  const dateLabel = (() => {
    const d = new Date();
    return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`;
  })();

  return (
    <div className="page-enter">
      <PageHeader
        title="今日の急騰銘柄"
        subtitle={`${dateLabel} · 全 ${sorted.length} 件${live.status === "live" ? " · ライブ取得" : live.status === "fallback" ? " · サンプル表示" : ""}`}
        onBack={() => navigate("home")}
      />

      {/* Sort */}
      <div style={{
        display: "flex", gap: 6, padding: "10px 14px",
        background: "var(--bg)", borderBottom: "1px solid var(--hairline)",
        position: "sticky", top: 0, zIndex: 5,
      }}>
        {[
          { k: "time", l: "時刻順" },
          { k: "pct", l: "上昇率順" },
          { k: "vol", l: "出来高順" },
        ].map(o => (
          <button key={o.k} onClick={() => setSortBy(o.k)} className="tappable" style={{
            padding: "5px 11px", fontSize: 11.5, letterSpacing: "0.04em",
            border: "1px solid " + (sortBy === o.k ? "var(--ink)" : "var(--hairline-strong)"),
            background: sortBy === o.k ? "var(--ink)" : "transparent",
            color: sortBy === o.k ? "var(--bg)" : "var(--ink)",
            cursor: "pointer",
          }}>{o.l}</button>
        ))}
      </div>

      <div style={{ background: "var(--surface)" }}>
        {sorted.length === 0 ? (
          <EmptySurges status={live.status}/>
        ) : sorted.map((s, i) => (
          <StockCard
            key={s.id || s.code + (s.time || i)}
            stock={s}
            idx={i}
            isNew={live.newIds && live.newIds.has(s.id)}
            onOpen={() => navigate("stock", s)}
          />
        ))}
      </div>
      <Disclaimer/>
    </div>
  );
}

// Empty state for when no live data has arrived yet
function EmptySurges({ status }) {
  // JSTで土日なら休場日表示に
  let isWeekend = false;
  try {
    const wd = new Intl.DateTimeFormat("en-US", { timeZone: "Asia/Tokyo", weekday: "short" }).format(new Date());
    isWeekend = wd === "Sat" || wd === "Sun";
  } catch (e) {}
  return (
    <div style={{
      padding: "40px 24px", textAlign: "center",
      background: "var(--surface)",
    }}>
      <div style={{
        width: 48, height: 48, margin: "0 auto 14px",
        border: "1px solid var(--hairline-strong)",
        display: "flex", alignItems: "center", justifyContent: "center",
        color: "var(--muted-2)",
      }}>
        {status === "live"
          ? <span className="live-dot" style={{ width: 8, height: 8 }}/>
          : <span className="mono" style={{ fontSize: 18 }}>·</span>}
      </div>
      <div className="mincho" style={{ fontSize: 14, fontWeight: 600, marginBottom: 6, color: "var(--ink)" }}>
        {status === "live" ? (isWeekend ? "本日は休場日" : "新しい急騰検知を待機中") : "データ未到着"}
      </div>
      <div style={{ fontSize: 11.5, color: "var(--muted)", lineHeight: 1.7 }}>
        {status === "live"
          ? (isWeekend
            ? "東証の取引は平日のみ。次の営業日の検知をお待ちください。"
            : "検知された瞬間、ここにスライドインで追加されます。")
          : "site-config.js に Supabase の接続情報を設定してください。"}
      </div>
    </div>
  );
}


// related_stocks テキストをパース
// 例: "(3641: パピレス — 説明文)" の行が改行区切りで並ぶ
 function _parseRelatedStocks(text) {
  if (!text) return [];
  return String(text)
    .split(/\n+/)
    .map(line => {
      let s = line.trim();
      if (!s) return null;
      s = s.replace(/^\(/, "").replace(/\)$/, "");
      const m = s.match(/^([0-9]{3,4}[A-Za-z]?)\s*[:：]\s*(.+)$/);
      if (!m) return null;
      const parts = m[2].split(/\s*[—–―]\s*/);
      return {
        code: m[1],
        name: (parts[0] || "").trim(),
        desc: parts.slice(1).join(" — ").trim(),
      };
    })
    .filter(Boolean);
}

// ===== STOCK DETAIL =====

function StockDetailPage({ stock, navigate, goBack }) {
  const live = window.useLiveData ? window.useLiveData() : { stocks: SURGE_STOCKS };
  const wl = window.useWatchlist ? window.useWatchlist() : { has: () => false, toggle: () => {} };
  if (!stock) stock = live.stocks[0] || SURGE_STOCKS[0];
  const watched = wl.has(stock.code);
  const hasPrice  = stock.pct !== null && stock.pct !== undefined;
  const hasChart  = !!stock.chart_url;
  const hasSpark  = Array.isArray(stock.spark) && stock.spark.length > 0;
  const businessText    = stock.business || `${stock.reason}を受けた買い。出来高は前日比で大きく増加し、寄り付き直後から売り買い拮抗の場面なく一方向の値動きに。関連テーマ（${(stock.themes || []).join(" / ")}）への連想買いも観測。`;
  const backgrounds = Array.isArray(stock.backgrounds) ? stock.backgrounds : [];
  const relatedStocks = _parseRelatedStocks(stock.related_stocks);
  // 同一銘柄の過去検知履歴（古い順）
  const hist = window.useStockHistory ? window.useStockHistory(stock.code) : { entries: [] };
  const histEntries = hist.entries || [];
  // 今回のエントリが何番目か（id → detected_at の順で照合、なければ最新扱い）
  let histCurIdx = stock.id != null ? histEntries.findIndex(e => e.id === stock.id) : -1;
  if (histCurIdx < 0 && stock.detected_at) histCurIdx = histEntries.findIndex(e => e.detected_at === stock.detected_at);
  if (histCurIdx < 0) histCurIdx = histEntries.length - 1;
  const histMultiYear = new Set(histEntries.map(e => String(e.detected_at || "").slice(0, 4))).size > 1;

  // 同じテーマを持つ他銘柄（実際にテーマが重なるものだけ、重なりの多い順）
  const _normTheme = t => String(t).trim();
  const myThemes = new Set((stock.themes || []).map(_normTheme));
  const _themeSeen = new Set([String(stock.code)]);
  const _themePool = [];
  [
    ...(live.stocks || []),
    ...(live.status === "live" ? [] : (window.SAMPLE_ARCHIVE || [])),
  ].forEach(s => {
    const c = String(s.code);
    if (_themeSeen.has(c)) return;
    _themeSeen.add(c);
    _themePool.push(s);
  });
  const themeMatches = _themePool
    .map(s => ({ s, common: (s.themes || []).map(_normTheme).filter(t => myThemes.has(t)) }))
    .filter(m => m.common.length > 0)
    .sort((a, b) => b.common.length - a.common.length)
    .slice(0, 3);

  const _sdSurge = stock.surge_price ?? stock.price ?? null;
  const _sdCur   = stock.current_price ?? null;
  const _sdPct = (_sdSurge != null && _sdCur != null && Number(_sdSurge) > 0)
    ? ((Number(_sdCur) - Number(_sdSurge)) / Number(_sdSurge)) * 100 : null;
  const _sdPctColor = _sdPct == null ? "var(--muted-2)"
    : _sdPct > 0 ? "var(--up)" : _sdPct < 0 ? "var(--down)" : "var(--muted)";

  return (
    <div className="page-enter">
      <PageHeader title="銘柄詳細" onBack={goBack || (() => navigate("home"))}/>
      <div style={{ background: "var(--surface)", padding: "18px 18px 14px", borderBottom: "1px solid var(--hairline)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
          <span className="mono" style={{ fontSize: 12, color: "var(--muted)" }}>{stock.code}</span>
          <span style={{
            fontSize: 9.5, color: "var(--muted)", border: "1px solid var(--hairline-strong)",
            padding: "0 4px",
          }}>{stock.market || "東"}</span>
          {stock.time && (
            <span className="mono" style={{ fontSize: 10.5, color: "var(--muted-2)", marginLeft: "auto" }}>
              {(() => {
                try {
                  if (!stock.detected_at) return stock.time + " JST";
                  const p = new Intl.DateTimeFormat("ja-JP", { timeZone: "Asia/Tokyo", month: "numeric", day: "numeric" }).format(new Date(stock.detected_at));
                  return p + " " + stock.time + " JST";
                } catch (e) { return stock.time + " JST"; }
              })()}
            </span>
          )}
        </div>
        <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
          <h1 className="mincho" style={{ margin: 0, fontSize: 22, fontWeight: 700, letterSpacing: "0.02em", flex: 1, minWidth: 0 }}>
            {stock.name}
          </h1>
          <button
            onClick={() => wl.toggle(stock)}
            className="tappable"
            aria-label={watched ? "ウォッチリストから外す" : "ウォッチリストに追加"}
            style={{
              flexShrink: 0,
              display: "inline-flex", alignItems: "center", gap: 5,
              padding: "6px 10px", cursor: "pointer",
              background: watched ? "var(--navy)" : "var(--surface)",
              color: watched ? "#fff" : "var(--ink-soft)",
              border: "1px solid " + (watched ? "var(--navy)" : "var(--hairline-strong)"),
              fontSize: 11, fontFamily: "inherit", letterSpacing: "0.04em",
            }}
          >
            <svg width="13" height="13" viewBox="0 0 24 24"
              fill={watched ? "var(--gold)" : "none"}
              stroke={watched ? "var(--gold)" : "currentColor"}
              strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2Z"/>
            </svg>
            {watched ? "ウォッチ中" : "ウォッチ"}
          </button>
        </div>
        {hasPrice ? (
          <div style={{ display: "flex", alignItems: "baseline", gap: 10, marginTop: 10 }}>
            <span className="mono" style={{ fontSize: 28, fontWeight: 600 }}>¥{stock.price.toLocaleString()}</span>
            <PctChip pct={stock.pct} size="lg"/>
          </div>
        ) : (
          <div style={{ marginTop: 10, display: "flex", alignItems: "center", gap: 8 }}>
            <span style={{
              color: "var(--up)", background: "var(--up-soft)",
              fontSize: 13, fontWeight: 600, padding: "4px 10px",
              letterSpacing: "0.06em", display: "inline-flex", alignItems: "center", gap: 6,
            }}>
              <span className="live-dot"/>
              急騰検知
            </span>
            <span className="eyebrow" style={{ color: "var(--navy)" }}>{stock.cat || stock.reason}</span>
          </div>
        )}

        {/* 急騰時 → 現在の値動き */}
        {_sdSurge != null && _sdCur != null && (
          <div style={{
            display: "grid", gridTemplateColumns: "1fr 1fr auto",
            marginTop: 10,
            border: "1px solid var(--hairline)", background: "var(--surface-sub)",
          }}>
            <div style={{ padding: "6px 10px", borderRight: "1px solid var(--hairline)" }}>
              <div style={{ fontSize: 9, letterSpacing: "0.1em", color: "var(--muted-2)", marginBottom: 1 }}>急騰時</div>
              <div className="mono" style={{ fontSize: 13, fontWeight: 600 }}>
                <span style={{ fontSize: 10, color: "var(--muted)", marginRight: 1 }}>¥</span>{_fmtPrice(_sdSurge)}
              </div>
            </div>
            <div style={{ padding: "6px 10px", borderRight: _sdPct != null ? "1px solid var(--hairline)" : "none" }}>
              <div style={{ fontSize: 9, letterSpacing: "0.1em", color: "var(--muted-2)", marginBottom: 1 }}>現在</div>
              <div className="mono" style={{ fontSize: 13, fontWeight: 600 }}>
                <span style={{ fontSize: 10, color: "var(--muted)", marginRight: 1 }}>¥</span>{_fmtPrice(_sdCur)}
              </div>
            </div>
            {_sdPct != null && (
              <div style={{
                padding: "0 12px", display: "flex", flexDirection: "column",
                alignItems: "flex-end", justifyContent: "center", minWidth: 72,
              }}>
                <div style={{ fontSize: 9, letterSpacing: "0.1em", color: "var(--muted-2)", marginBottom: 1 }}>急騰時から</div>
                <div className="mono" style={{ fontSize: 13, fontWeight: 700, color: _sdPctColor }}>
                  {_sdPct > 0 ? "+" : ""}{_sdPct.toFixed(1)}%
                </div>
              </div>
            )}
          </div>
        )}

        {/* Chart — ライブ画像、もしくはサンプルのスパークライン */}
        <div style={{ marginTop: 14 }}>
          {hasChart ? (
            <div style={{
              border: "1px solid var(--hairline)",
              background: "#fff", overflow: "hidden",
              position: "relative",
            }}>
              <img src={stock.chart_url} alt="急騰チャート" loading="lazy" style={{
                width: "100%", display: "block", height: "auto",
              }}/>
              <div style={{
                position: "absolute", top: 6, right: 6,
                fontSize: 9, letterSpacing: "0.12em", color: "var(--muted)",
                background: "rgba(255,255,255,0.85)", padding: "2px 6px",
              }}>AUTO-GENERATED</div>
            </div>
          ) : hasSpark ? (
            <div style={{ background: "var(--surface-sub)", padding: "8px 6px" }}>
              <Sparkline points={stock.spark} color={stock.pct >= 0 ? "var(--up)" : "var(--down)"} w={325} h={70}/>
            </div>
          ) : (
            <div style={{
              padding: "20px 12px", background: "var(--surface-sub)",
              fontSize: 11, color: "var(--muted)", textAlign: "center",
            }}>
              チャート画像を読み込み中…
            </div>
          )}
        </div>

        <div style={{ display: "flex", gap: 14, marginTop: 12, fontSize: 11, color: "var(--muted)", flexWrap: "wrap" }}>
          {stock.vol && <span>出来高 <span className="mono" style={{ color: "var(--ink)" }}>{stock.vol}</span></span>}
          {stock.time && <span>更新 <span className="mono" style={{ color: "var(--ink)" }}>{stock.time}</span></span>}
          {(stock.cat || stock.reason) && <span>カテゴリ <span style={{ color: "var(--navy)" }}>{stock.cat || stock.reason}</span></span>}
        </div>
      </div>

      {stock.business && (
        <>
          <SectionHeader kicker="会社概要" title="事業内容"/>
          <div style={{
            background: "var(--surface)", padding: "13px 16px",
            margin: "0 14px",
            border: "1px solid var(--hairline)",
          }}>
            <p style={{ margin: 0, fontSize: 13, lineHeight: 1.85, color: "var(--ink-soft)" }}>
              {stock.business}
            </p>
          </div>
        </>
      )}

      <SectionHeader kicker="本日の整理" title="なぜ上がったのか"/>
      <div style={{
        background: "var(--surface)", padding: "14px 16px",
        margin: "0 14px", borderLeft: "2px solid var(--navy)",
      }}>
        {!stock.business && (
          <p style={{ margin: 0, fontSize: 13, lineHeight: 1.85, color: "var(--ink-soft)" }}>
            {businessText}
          </p>
        )}
        {backgrounds.length > 0 && (
          <ul style={{ margin: !stock.business ? "10px 0 0" : 0, padding: "0 0 0 16px", fontSize: 12.5, lineHeight: 1.85, color: "var(--ink-soft)" }}>
            {backgrounds.map((b, i) => <li key={i} style={{ marginBottom: 2 }}>{b}</li>)}
          </ul>
        )}
        {stock.themes && stock.themes.length > 0 && (
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginTop: 12 }}>
            {stock.themes.map(t => (
              <button key={t} className="tappable" onClick={() => navigate("theme", String(t).trim())} style={{
                fontSize: 11, color: "var(--ink-soft)",
                border: "1px solid var(--hairline-strong)",
                padding: "2px 7px", background: "transparent",
                fontFamily: "inherit", cursor: "pointer",
                display: "inline-flex", alignItems: "center", gap: 4,
              }}>
                {t}<span aria-hidden="true" style={{ color: "var(--muted-2)", fontSize: 10 }}>›</span>
              </button>
            ))}
          </div>
        )}
      </div>

      {histEntries.length >= 2 && (
        <>
          <SectionHeader kicker="同一銘柄の検知履歴" title={`${histCurIdx + 1}回目の登場`}/>
          <div style={{ margin: "0 14px", border: "1px solid var(--hairline)", background: "var(--surface)" }}>
            {histEntries.map((e, i) => {
              const isCur = i === histCurIdx;
              let dateStr = "";
              try {
                dateStr = new Intl.DateTimeFormat("ja-JP", {
                  timeZone: "Asia/Tokyo",
                  ...(histMultiYear ? { year: "numeric" } : {}),
                  month: "long", day: "numeric",
                }).format(new Date(e.detected_at));
              } catch (err) {}
              return (
                <div key={e.id || i}
                  className={isCur ? undefined : "tappable"}
                  role={isCur ? undefined : "button"}
                  tabIndex={isCur ? undefined : 0}
                  onClick={isCur ? undefined : () => navigate("stock", e)}
                  onKeyDown={isCur ? undefined : (ev => { if (ev.key === "Enter" || ev.key === " ") { ev.preventDefault(); navigate("stock", e); } })}
                  style={{
                  padding: "10px 14px 11px 12px",
                  borderTop: i > 0 ? "1px solid var(--hairline)" : "none",
                  background: isCur ? "var(--surface-sub)" : "transparent",
                  borderLeft: "2px solid " + (isCur ? "var(--navy)" : "transparent"),
                  cursor: isCur ? "default" : "pointer",
                }}>
                  <div style={{ display: "flex", alignItems: "baseline", gap: 8 }}>
                    <span className="mono" style={{ fontSize: 11, fontWeight: 600, color: isCur ? "var(--navy)" : "var(--muted)" }}>
                      {i + 1}回目
                    </span>
                    <span className="mono" style={{ fontSize: 11, color: "var(--muted)" }}>{dateStr}</span>
                    {isCur && (
                      <span style={{
                        fontSize: 9.5, color: "#fff", background: "var(--navy)",
                        padding: "1px 6px", letterSpacing: "0.1em",
                      }}>今回</span>
                    )}
                    <span style={{ marginLeft: "auto", display: "inline-flex", alignItems: "baseline", gap: 5 }}>
                      <span style={{ fontSize: 9.5, color: "var(--muted-2)", letterSpacing: "0.06em" }}>急騰時</span>
                      <span className="mono" style={{ fontSize: 12.5, fontWeight: 600 }}>
                        {e.surge_price != null ? "¥" + Math.round(e.surge_price).toLocaleString() : "—"}
                      </span>
                      {!isCur && <span aria-hidden="true" style={{ fontSize: 12, color: "var(--muted-2)", alignSelf: "center" }}>›</span>}
                    </span>
                  </div>
                  <div style={{ fontSize: 12.5, lineHeight: 1.7, color: "var(--ink-soft)", marginTop: 3 }}>
                    {e.reason}
                  </div>
                </div>
              );
            })}
          </div>
        </>
      )}

      <SectionHeader kicker="リアルチャート" title="現在の値動き"/>
      {stock.code && window.RealChart && (
        <window.RealChart
          code={stock.code}
          surgeAt={stock.detected_at || null}
          surgePrice={stock.surge_price ?? stock.price ?? null}
        />
      )}

      {/* 広告枠（PR）— ads-config.js でON/OFF */}
      {window.ScouterPromoCard && <window.ScouterPromoCard navigate={navigate}/>}

      {/* 関連銘柄（分析フローが抽出した連想銘柄） */}
      {relatedStocks.length > 0 && (
        <>
          <SectionHeader kicker="関連銘柄" title="連想で動きやすい銘柄"/>
          <div style={{
            background: "var(--surface)",
            borderTop: "1px solid var(--hairline)",
            borderBottom: "1px solid var(--hairline)",
          }}>
            {relatedStocks.map((r, i) => (
              <div key={r.code + i} style={{
                padding: "11px 14px",
                borderBottom: i < relatedStocks.length - 1 ? "1px solid var(--hairline)" : "none",
              }}>
                <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: r.desc ? 4 : 0 }}>
                  <span className="mono" style={{
                    fontSize: 10.5, color: "var(--muted)",
                    border: "1px solid var(--hairline-strong)",
                    padding: "0 5px", letterSpacing: "0.04em", flexShrink: 0,
                  }}>{r.code}</span>
                  <span style={{ fontSize: 13, fontWeight: 600, color: "var(--ink)" }}>{r.name}</span>
                </div>
                {r.desc && (
                  <div style={{ fontSize: 11.5, color: "var(--ink-soft)", lineHeight: 1.65 }}>
                    {r.desc}
                  </div>
                )}
              </div>
            ))}
          </div>
        </>
      )}

      {themeMatches.length > 0 && (
        <>
          <SectionHeader kicker="関連テーマ" title="同じテーマで動いた銘柄"/>
          <div style={{ padding: "0 14px 8px", fontSize: 11, color: "var(--muted)" }}>
            紺の塗りの札が、この銘柄と共通するテーマです
          </div>
          <div style={{ background: "var(--surface)", borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
            {themeMatches.map((m, i) => (
              <StockCard
                key={m.s.id || m.s.code}
                stock={m.s}
                idx={i}
                highlightThemes={new Set(m.common)}
                onOpen={() => navigate("stock", m.s)}
                onTheme={t => navigate("theme", t)}
              />
            ))}
          </div>
        </>
      )}

      {/* Paid teaser */}
      <div style={{ padding: "20px 14px 0" }}>
        <div style={{
          background: "var(--navy-deep)", color: "#fff",
          padding: "16px 16px",
        }}>
          <div className="eyebrow" style={{ color: "var(--gold)", marginBottom: 8 }}>有料会員限定</div>
          <div style={{ fontSize: 13, lineHeight: 1.7, color: "#c0c7d6" }}>
            この急騰パターンの過去事例（n=214）と、テーマ別の継続率を見る
          </div>
          <button className="btn btn-gold tappable" style={{ width: "100%", marginTop: 12 }} onClick={() => navigate("report")}>
            資金流入レポートで見る
          </button>
        </div>
      </div>

      <Disclaimer/>
    </div>
  );
}


// ===== CATEGORIES =====
function CategoriesPage({ navigate }) {
  const catData = useCategoryCounts();
  const subtitle = catData.ready
    ? `アーカイブ全 ${catData.total} 件から材料・テーマを分類`
    : "材料・テーマで読み解く";
  return (
    <div className="page-enter">
      <PageHeader title="急騰理由カテゴリ" subtitle={subtitle}/>
      <div className="cats-grid" style={{ padding: "8px 12px 0" }}>
        {catData.cats.map(c => (
          <button key={c.key} className="tappable" onClick={() => navigate("category", c)} style={{
            width: "100%",
            background: "var(--surface)",
            border: "1px solid var(--hairline)",
            padding: "14px 14px",
            display: "flex", alignItems: "center", justifyContent: "space-between",
            marginBottom: 8, cursor: "pointer", textAlign: "left",
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
              <div className="mincho" style={{
                width: 36, height: 36, border: "1px solid var(--hairline-strong)",
                display: "flex", alignItems: "center", justifyContent: "center",
                color: "var(--navy)", fontSize: 14,
              }}>{c.icon}</div>
              <div>
                <div style={{ fontSize: 14.5, fontWeight: 600 }}>{c.label}</div>
                <div style={{ fontSize: 11, color: "var(--muted)", marginTop: 2 }}>
                  累計 <span className="mono">{c.count}</span> 件の動きを観測
                </div>
              </div>
            </div>
            <span style={{ color: "var(--muted-2)" }}>›</span>
          </button>
        ))}
      </div>
      <Disclaimer/>
    </div>
  );
}


// ===== REPORT helpers =====
// 急騰理由テキストを定型カテゴリに分類
function _classifyReason(reason) {
  const r = String(reason || "");
  if (!r) return "その他";
  if (/TOB|M&A|MBO|公開買付/i.test(r)) return "TOB・M&A";
  if (/上方修正|業績予想.*上方|上振れ/.test(r)) return "上方修正";
  if (/増配|配当.*増|連続増配/.test(r)) return "増配";
  if (/好決算|決算/.test(r)) return "好決算";
  if (/受注|出荷|納入/.test(r)) return "受注・出荷好調";
  if (/防衛|原発|原子力|政策|補助金|規制/.test(r)) return "政策テーマ";
  if (/月次|売上.*好調|過去最高|入園|販売.*過去|回復/.test(r)) return "月次・販売好調";
  if (/発表|契約|公開|報道|ライセンス|提携|資本/.test(r)) return "材料発表";
  if (/価格.*上昇|値上げ|市況/.test(r)) return "市況・価格上昇";
  return "その他";
}

// ── JST 日付ユーティリティ ────────────────────────────────
// 重要: 以前は `d.getTime() + (9*60 - d.getTimezoneOffset())*60000` で
// JST 換算していたが、getTimezoneOffset() は「ブラウザの」オフセットを返すため、
// 日本(JST)端末では +18時間ずれて 15時以降が翌日扱いになっていた。
// timeZone を明示できる Intl で求め直す（端末のタイムゾーンに依存しない）。
const _JST_DTF = new Intl.DateTimeFormat("en-CA", {
  timeZone: "Asia/Tokyo", year: "numeric", month: "2-digit", day: "2-digit",
});
// iso(またはDate) → { y, m, d }（すべて数値, m は 1-12）
function _jstParts(iso) {
  const parts = _JST_DTF.formatToParts(iso instanceof Date ? iso : new Date(iso));
  const o = {};
  for (const p of parts) if (p.type !== "literal") o[p.type] = Number(p.value);
  return { y: o.year, m: o.month, d: o.day };
}

// JST の YYYY-MM-DD を返す
function _toJstKey(iso) {
  try {
    const { y, m, d } = _jstParts(iso);
    return `${y}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
  } catch (e) { return null; }
}

// MM.DD で表示
function _fmtMD(iso) {
  try {
    const { m, d } = _jstParts(iso);
    return `${m}.${d}`;
  } catch (e) { return ""; }
}

// ISO 週番号
function _isoWeek(iso) {
  try {
    const { y, m, d } = _jstParts(iso);
    // ISO week: Thursday-based
    const t = new Date(Date.UTC(y, m - 1, d));
    const dayNum = (t.getUTCDay() + 6) % 7;
    t.setUTCDate(t.getUTCDate() - dayNum + 3);
    const firstThu = new Date(Date.UTC(t.getUTCFullYear(), 0, 4));
    const week = 1 + Math.round(((t - firstThu) / 86400000 - 3 + ((firstThu.getUTCDay() + 6) % 7)) / 7);
    return week;
  } catch (e) { return null; }
}

// アーカイブから集計：
//   - テーマ : 今週分（最新エントリから過去7日分）
//   - 急騰理由: アーカイブ全件
function _useThisWeekStats(entries) {
  return useMPMemo(() => {
    const list = (entries || []).filter(e => e && e.detected_at);
    if (list.length === 0) return null;
    // 最新日 = anchor
    const sorted = [...list].sort((a, b) => new Date(b.detected_at) - new Date(a.detected_at));
    const anchorIso = sorted[0].detected_at;
    const anchor = new Date(anchorIso);
    const start = new Date(anchor.getTime() - 7 * 86400000);

    const week = sorted.filter(e => {
      const d = new Date(e.detected_at);
      return d > start && d <= anchor;
    });
    // テーマ用プール（今週分、足りなければ直近20件で代用）
    const themePool = week.length >= 3 ? week : sorted.slice(0, Math.min(20, sorted.length));
    const total = themePool.length;

    // テーマ集計
    const themeMap = {};
    themePool.forEach(e => {
      (e.themes || []).forEach(t => {
        const k = String(t).trim();
        if (!k) return;
        themeMap[k] = (themeMap[k] || 0) + 1;
      });
    });
    const themes = Object.entries(themeMap)
      .map(([name, count]) => ({ name, count, pct: total > 0 ? (count / total) * 100 : 0 }))
      .sort((a, b) => b.count - a.count || b.pct - a.pct)
      .slice(0, 7)
      .map((t, i) => ({ ...t, rank: i + 1, hot: i === 0 }));

    // 急騰理由集計（今週分）
    const reasonMap = {};
    themePool.forEach(e => {
      const k = _classifyReason(e.reason || e.cat);
      reasonMap[k] = (reasonMap[k] || 0) + 1;
    });
    const reasonTotal = Object.values(reasonMap).reduce((a, b) => a + b, 0) || 1;
    const reasons = Object.entries(reasonMap)
      .map(([name, count]) => ({ name, count, pct: (count / reasonTotal) * 100 }))
      .sort((a, b) => b.count - a.count)
      .slice(0, 8);

    return {
      total,
      weekTotal: week.length,
      reasonTotal: sorted.length,
      anchorIso,
      startIso: start.toISOString(),
      themes,
      reasons,
      isFallback: week.length < 3,
    };
  }, [entries]);
}

// ===== REPORT (paid dashboard) =====
function ReportPage(props) {
  const Gate = window.PayGate;
  if (!Gate) return <ReportPageInner {...props}/>;
  return (
    <Gate dark label="資金流入レポート" navigate={props.navigate}>
      <ReportPageInner {...props}/>
    </Gate>
  );
}

function ReportPageInner({ navigate }) {
  const archive = window.useArchive ? window.useArchive({ pageSize: 80 }) : { entries: window.SAMPLE_ARCHIVE || [] };
  const stats = _useThisWeekStats(archive.entries);

  // 表示用ヘッダー値
  const weekNo  = stats ? _isoWeek(stats.anchorIso) : null;
  const dateRng = stats ? `${_fmtMD(stats.startIso)} — ${_fmtMD(stats.anchorIso)}` : "";

  const flowThemes = stats ? stats.themes : (window.FLOW_THEMES || []).map((t, i) => ({
    ...t, pct: t.change, rank: t.rank, hot: t.hot,
  }));
  const reasonRank = stats ? stats.reasons : (window.REASON_RANK || []).map(r => ({
    ...r, pct: 0,
  }));

  return (
    <div className="page-enter" style={{ background: "#0E1628", color: "#E8EBF2", minHeight: "100%" }}>
      <div style={{
        padding: "14px 16px 6px",
        background: "var(--navy-deep)",
        borderBottom: "1px solid rgba(255,255,255,0.06)",
        position: "sticky", top: 0, zIndex: 5,
      }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          <button onClick={() => navigate("home")} className="tappable" style={{
            background: "transparent", border: "none", color: "#9aa3b8",
            cursor: "pointer", padding: 4,
          }}>‹ ホーム</button>
          <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 6 }}>
            <span className="live-dot"/>
            <span className="mono" style={{ fontSize: 10, color: "#9aa3b8", letterSpacing: "0.1em" }}>JST 14:42 更新</span>
          </div>
        </div>
        <div style={{ marginTop: 8 }}>
          <div className="eyebrow" style={{ color: "var(--gold)", letterSpacing: "0.22em" }}>
            有料会員限定 · WEEKLY
          </div>
          <h1 className="mincho" style={{
            margin: "4px 0 0", fontSize: 24, fontWeight: 700, color: "#fff",
            letterSpacing: "0.02em",
          }}>資金流入レポート</h1>
          <div style={{ fontSize: 11.5, color: "#9aa3b8", marginTop: 4 }}>
            {stats
              ? <>第 {weekNo} 週 · {dateRng} · 集計対象 <span className="mono">{stats.total}</span> 件{stats.isFallback && <span style={{ color: "#6b738a" }}> · 直近データで代用</span>}</>
              : "集計データを読み込み中..."}
          </div>
        </div>
      </div>

      {/* TOP: Theme flow rank */}
      <DashSection title="今週の資金流入テーマ" kicker="WEEKLY · THEME SHARE">
        <div style={{ fontSize: 10.5, color: "#6b738a", marginBottom: 8, letterSpacing: "0.04em" }}>
          今週検知された急騰銘柄のテーマタグ出現率 ／ 件数
        </div>
        <div style={{ display: "grid", gap: 6 }}>
          {flowThemes.length === 0 && (
            <div style={{ fontSize: 11.5, color: "#6b738a", padding: "12px 0" }}>
              今週は集計対象がありません。
            </div>
          )}
          {flowThemes.map(t => {
            const max = Math.max(...flowThemes.map(x => x.pct), 1);
            const w = (t.pct / max) * 100;
            return (
              <div key={t.rank + "-" + t.name} style={{
                display: "grid", gridTemplateColumns: "26px 1fr 92px",
                alignItems: "center", gap: 10,
                padding: "8px 0",
                borderBottom: "1px solid rgba(255,255,255,0.06)",
              }}>
                <span className="mono" style={{
                  fontSize: 11, color: t.rank <= 3 ? "var(--gold)" : "#6b738a",
                  fontWeight: 600, letterSpacing: "0.06em",
                }}>{String(t.rank).padStart(2,"0")}</span>
                <div>
                  <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                    <span style={{ fontSize: 13.5, fontWeight: 500 }}>{t.name}</span>
                    {t.hot && <span style={{
                      fontSize: 9, padding: "1px 4px",
                      background: "var(--up)", color: "#fff", letterSpacing: "0.08em",
                    }}>HOT</span>}
                  </div>
                  <div style={{
                    marginTop: 6, height: 3, background: "rgba(255,255,255,0.06)",
                    position: "relative",
                  }}>
                    <div style={{
                      position: "absolute", left: 0, top: 0, bottom: 0,
                      width: w + "%",
                      background: t.rank <= 3 ? "var(--gold)" : "#4a5878",
                    }}/>
                  </div>
                </div>
                <div style={{ textAlign: "right", lineHeight: 1.25 }}>
                  <div className="mono" style={{ fontSize: 13, color: "#ff8a8f", fontWeight: 600 }}>
                    {t.pct.toFixed(1)}%
                  </div>
                  <div className="mono" style={{ fontSize: 10, color: "#6b738a" }}>
                    {t.count}件
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </DashSection>

      {/* Reason ranking + heatmap-ish bar */}
      <DashSection title="急騰理由ランキング" kicker="REASON · DISTRIBUTION">
        <div style={{ fontSize: 10.5, color: "#6b738a", marginBottom: 8, letterSpacing: "0.04em" }}>
          検知された急騰理由をカテゴリ分類した分布 ／ 件数
        </div>
        <div style={{ display: "grid", gap: 6 }}>
          {reasonRank.length === 0 && (
            <div style={{ fontSize: 11.5, color: "#6b738a", padding: "12px 0" }}>
              今週は集計対象がありません。
            </div>
          )}
          {reasonRank.map(r => {
            const max = Math.max(...reasonRank.map(x => x.count), 1);
            const w = (r.count / max) * 100;
            return (
              <div key={r.name} style={{ display: "grid", gridTemplateColumns: "1fr 76px", gap: 8, alignItems: "center" }}>
                <div>
                  <div style={{ fontSize: 12.5, marginBottom: 4 }}>{r.name}</div>
                  <div style={{ height: 6, background: "rgba(255,255,255,0.06)", position: "relative" }}>
                    <div style={{
                      position: "absolute", inset: 0, width: w + "%",
                      background: "linear-gradient(90deg, var(--gold), #d9b96a)",
                    }}/>
                  </div>
                </div>
                <div style={{ textAlign: "right", lineHeight: 1.25 }}>
                  <div className="mono" style={{ fontSize: 12.5, color: "#fff", fontWeight: 600 }}>
                    {r.pct.toFixed(1)}%
                  </div>
                  <div className="mono" style={{ fontSize: 10, color: "#6b738a" }}>
                    {r.count}件
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </DashSection>

      {/* Backtest */}
      <DashSection title="バックテスト分析" kicker="BACKTEST · n>40">
        <div style={{ display: "grid", gap: 10 }}>
          {BACKTEST_CARDS.map((b, i) => (
            <div key={i} style={{
              background: "rgba(255,255,255,0.03)",
              border: "1px solid rgba(255,255,255,0.06)",
              padding: "12px 14px",
            }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
                <span className="mincho" style={{ fontSize: 14, fontWeight: 600, color: "#fff" }}>{b.title}</span>
                <span className="mono" style={{
                  fontSize: 9.5, color: "var(--gold)", marginLeft: "auto",
                  border: "1px solid rgba(165,127,42,0.4)", padding: "1px 5px",
                  letterSpacing: "0.08em",
                }}>{b.period}</span>
              </div>
              <p style={{ margin: 0, fontSize: 12, lineHeight: 1.7, color: "#c0c7d6" }}>
                {b.note}
              </p>
              <div className="mono" style={{ fontSize: 10, color: "#6b738a", marginTop: 8 }}>
                {b.detail}
              </div>
            </div>
          ))}
        </div>
      </DashSection>

      {/* Watch themes */}
      <DashSection title="来週注目されやすいテーマ候補" kicker="WATCHLIST">
        <div style={{ fontSize: 10.5, color: "#9aa3b8", marginBottom: 10, lineHeight: 1.7 }}>
          ※ 売買推奨ではなく、ウォッチ対象として整理しています。
        </div>
        <div style={{ display: "grid", gap: 6 }}>
          {WATCH_THEMES.map(t => (
            <div key={t.name} style={{
              padding: "10px 12px",
              background: "rgba(255,255,255,0.03)",
              border: "1px solid rgba(255,255,255,0.06)",
              borderLeft: `2px solid ${t.level === "注意" ? "var(--up)" : "var(--gold)"}`,
              display: "flex", alignItems: "center", gap: 10,
            }}>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 600, marginBottom: 2 }}>{t.name}</div>
                <div style={{ fontSize: 11, color: "#9aa3b8" }}>{t.note}</div>
              </div>
              <span className="mono" style={{
                fontSize: 9.5, padding: "2px 6px", letterSpacing: "0.1em",
                color: t.level === "注意" ? "var(--up)" : "var(--gold)",
                border: `1px solid ${t.level === "注意" ? "rgba(200,51,58,0.5)" : "rgba(165,127,42,0.5)"}`,
              }}>{t.level}</span>
            </div>
          ))}
        </div>
      </DashSection>

      {/* Overheat */}
      <DashSection title="過熱感のあるテーマ" kicker="OVERHEAT NOTICE">
        <div style={{
          padding: "12px 14px",
          background: "rgba(200,51,58,0.08)",
          border: "1px solid rgba(200,51,58,0.25)",
        }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 6 }}>
            <span style={{
              fontSize: 9.5, padding: "1px 5px", letterSpacing: "0.1em",
              background: "var(--up)", color: "#fff",
            }}>NOTE</span>
            <span style={{ fontSize: 12, fontWeight: 600 }}>AI関連の一部に短期過熱</span>
          </div>
          <p style={{ margin: 0, fontSize: 11.5, lineHeight: 1.7, color: "#c0c7d6" }}>
            出来高が直近20日平均の3倍を超え、信用買い残比率も上昇。短期反落の事例が過去比率で増加傾向。情報整理のための注意点であり、判断は読み手に委ねます。
          </p>
        </div>
      </DashSection>

      {/* Footer disclaimer in dark variant */}
      <div style={{
        margin: "20px 16px 24px",
        padding: "12px 14px",
        background: "rgba(255,255,255,0.03)",
        borderLeft: "2px solid #4a5878",
        fontSize: 10.5, lineHeight: 1.8, color: "#9aa3b8",
      }}>
        <div style={{ fontSize: 9.5, color: "#c0c7d6", letterSpacing: "0.16em", fontWeight: 600, marginBottom: 4 }}>
          DISCLAIMER
        </div>
        本レポートは、公開情報および過去データをもとにした情報整理・分析を目的としており、特定の金融商品の売買を推奨するものではありません。投資判断はご自身の責任で行ってください。
      </div>
    </div>
  );
}

function DashSection({ title, kicker, children }) {
  return (
    <section style={{ padding: "18px 16px 6px" }}>
      <div className="eyebrow" style={{ color: "#6b738a", marginBottom: 4 }}>{kicker}</div>
      <h2 className="mincho" style={{
        margin: "0 0 12px", fontSize: 17, fontWeight: 600,
        color: "#fff", letterSpacing: "0.02em",
      }}>{title}</h2>
      {children}
    </section>
  );
}


// ===== LEGAL: 特定商取引法に基づく表記（枠のみ・記載内容は公開時に記入） =====
// 決済導入の審査で求められる標準項目を網羅した骨組み。
// 値は TOKUSHOHO_ITEMS の value を書き換えるだけで反映される。
const _TBD = null; // 未記入マーカー
const TOKUSHOHO_ITEMS = [
  { label: "販売事業者",       value: "森谷心" },
  { label: "運営統括責任者",   value: "森谷心" },
  { label: "所在地",           value: "請求があった場合、遅滞なく開示します。" },
  { label: "電話番号",         value: "請求があった場合、遅滞なく開示します。お問い合わせはメールにて承ります。" },
  { label: "メールアドレス",   value: "senguxin@gmail.com" },
  { label: "販売URL",          value: "https://kyuutoumemo.com" },
  { label: "販売価格",         value: "プレミアム会員 月額500円（税込）" },
  { label: "商品代金以外の必要料金", value: "インターネット接続にかかる通信費はお客様のご負担となります。" },
  { label: "お支払い方法",     value: "クレジットカード決済（Stripe）" },
  { label: "お支払い時期",     value: "初回申込時に課金され、1ヶ月ごとに自動更新されます。" },
  { label: "サービス提供時期", value: "決済完了後、直ちにご利用いただけます。" },
  { label: "解約について",     value: "会員プランページの「お支払い・解約の管理」からいつでも解約できます。解約後も次回更新日の前日までご利用いただけます。" },
  { label: "返品・返金について", value: "デジタルコンテンツの性質上、提供開始後の返金には対応しておりません。" },
  { label: "動作環境",         value: "最新版の主要ブラウザ（Chrome / Safari / Edge 等）" },
];

function LegalPage({ navigate, goBack }) {
  return (
    <div className="page-enter">
      <PageHeader title="特定商取引法に基づく表記" onBack={goBack || (() => navigate("home"))}/>
      <div style={{ padding: "16px 14px 6px" }}>
        <p style={{ margin: 0, fontSize: 11.5, color: "var(--muted)", lineHeight: 1.8 }}>
          特定商取引法第11条（通信販売についての広告）に基づき、以下のとおり表示します。
        </p>
      </div>
      <div style={{
        margin: "12px 14px 0",
        background: "var(--surface)",
        border: "1px solid var(--hairline)",
      }}>
        {TOKUSHOHO_ITEMS.map((item, i) => (
          <div key={item.label} style={{
            display: "grid", gridTemplateColumns: "108px 1fr",
            borderTop: i > 0 ? "1px solid var(--hairline)" : "none",
          }}>
            <div style={{
              padding: "11px 12px",
              background: "var(--surface-sub)",
              borderRight: "1px solid var(--hairline)",
              fontSize: 11, fontWeight: 600, color: "var(--ink-soft)",
              lineHeight: 1.6, letterSpacing: "0.02em",
            }}>{item.label}</div>
            <div style={{ padding: "11px 12px", minWidth: 0 }}>
              {item.value ? (
                <div style={{ fontSize: 12.5, color: "var(--ink)", lineHeight: 1.7 }}>{item.value}</div>
              ) : (
                <div style={{
                  fontSize: 11.5, color: "var(--muted-2)",
                  border: "1px dashed var(--hairline-strong)",
                  padding: "5px 8px", lineHeight: 1.6,
                }}>—</div>
              )}
              {item.note && (
                <div style={{ fontSize: 10, color: "var(--muted-2)", lineHeight: 1.6, marginTop: 5 }}>
                  {item.note}
                </div>
              )}
            </div>
          </div>
        ))}
      </div>
      <div style={{ padding: "10px 14px 0", fontSize: 10, color: "var(--muted-2)", lineHeight: 1.7 }}>
        ※ 当サイトは情報の記録・整理を目的としたサービスであり、投資助言・売買推奨を行うものではありません。
      </div>
      <Disclaimer/>
      <Footer navigate={navigate}/>
    </div>
  );
}


// ===== PLAN PAGE =====
function PlanPage({ navigate, goBack }) {
  return (
    <div className="page-enter">
      <PageHeader title="会員プラン" onBack={goBack || (() => navigate("home"))}/>
      {window.PaywallAccount && <window.PaywallAccount navigate={navigate}/>}
      <div style={{ padding: "16px 14px 6px" }}>
        <p style={{ margin: "0 0 16px", fontSize: 12.5, color: "var(--muted)", lineHeight: 1.7 }}>
          毎日の急騰理由メモは無料でご覧いただけます。<br/>
          資金の流れを継続的に追いかけたい方向けに、有料プランをご用意しています。
        </p>
      </div>
      <PricingSection navigate={navigate}/>
      <Disclaimer/>
      <Footer navigate={navigate}/>
    </div>
  );
}


// ===== ARTICLE =====
function ArticlePage({ article, navigate, goBack }) {
  if (!article) article = FREE_ARTICLES[0];
  return (
    <div className="page-enter">
      <PageHeader title="無料記事" onBack={goBack || (() => navigate("home"))}/>
      <div style={{ padding: "18px 18px 8px", background: "var(--surface)", borderBottom: "1px solid var(--hairline)" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 10, color: "var(--muted)", fontSize: 11 }}>
          <span className="mono">{article.date}</span>
          <span style={{ border: "1px solid var(--hairline-strong)", padding: "0 4px", fontSize: 9.5, letterSpacing: "0.08em" }}>無料</span>
          <span className="mono" style={{ marginLeft: "auto" }}>{article.read}で読める</span>
        </div>
        <h1 className="mincho" style={{ margin: 0, fontSize: 22, lineHeight: 1.5, fontWeight: 700, letterSpacing: "0.02em" }}>
          {article.name} ・ {article.reason}
        </h1>
        <div className="mono" style={{ marginTop: 6, fontSize: 11, color: "var(--muted)" }}>
          銘柄コード {article.code}
        </div>
      </div>
      <div style={{ padding: "18px 18px 4px" }}>
        <p style={{ margin: 0, fontSize: 13.5, lineHeight: 1.95, color: "var(--ink-soft)" }}>
          {article.summary}
        </p>
        <p style={{ margin: "14px 0 0", fontSize: 13.5, lineHeight: 1.95, color: "var(--ink-soft)" }}>
          過去の類似事例では、初動から3営業日以内に出来高がピークアウトする傾向。継続性は個別の材料に依存します。詳細な分類とパターン比較は資金流入レポートで整理しています。
        </p>
      </div>
      <div style={{ padding: "20px 14px 0" }}>
        <button className="btn btn-primary tappable" onClick={() => navigate("report")} style={{ width: "100%" }}>
          資金流入レポートで深掘りする
        </button>
      </div>
      <Disclaimer/>
    </div>
  );
}


// ===== CATEGORY DETAIL =====
function CategoryPage({ cat, navigate, goBack }) {
  if (!cat) cat = CATEGORIES[0];
  const live = window.useLiveData ? window.useLiveData() : { stocks: SURGE_STOCKS };
  // Filter live stocks loosely by category label match
  const filtered = (live.stocks || []).filter(s =>
    !cat.label ||
    (s.reason && s.reason.includes(cat.label)) ||
    (s.themes && s.themes.some(t => t.includes(cat.label) || cat.label.includes(t)))
  );
  const showStocks = filtered.length > 0 ? filtered : (live.stocks || []).slice(0, 6);

  return (
    <div className="page-enter">
      <PageHeader title={cat.label} subtitle={`${showStocks.length} 件 表示中`} onBack={goBack || (() => navigate("cats"))}/>
      <div style={{ padding: "12px 14px 4px" }}>
        <p style={{ margin: 0, fontSize: 12, color: "var(--muted)", lineHeight: 1.7 }}>
          「{cat.label}」を背景に動いた銘柄を整理しています。
          過去事例との比較・継続率の分析は <span style={{ color: "var(--navy)", fontWeight: 600 }}>資金流入レポート</span>（有料）でご覧いただけます。
        </p>
      </div>
      <div style={{ background: "var(--surface)", marginTop: 8, borderTop: "1px solid var(--hairline)", borderBottom: "1px solid var(--hairline)" }}>
        {showStocks.map((s, i) => (
          <StockCard key={s.id || s.code} stock={s} idx={i} onOpen={() => navigate("stock", s)}/>
        ))}
      </div>
      <Disclaimer/>
    </div>
  );
}


// PageHeader
function PageHeader({ title, subtitle, onBack }) {
  return (
    <div style={{
      padding: "12px 14px 12px",
      background: "var(--bg)",
      borderBottom: "1px solid var(--hairline)",
      position: "sticky", top: 0, zIndex: 10,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        {onBack && (
          <button onClick={onBack} className="tappable" style={{
            background: "transparent", border: "none",
            color: "var(--muted)", cursor: "pointer", padding: 4,
            fontSize: 13,
          }}>‹ 戻る</button>
        )}
      </div>
      <h1 className="mincho" style={{
        margin: onBack ? "2px 0 0" : 0,
        fontSize: 18, fontWeight: 700, letterSpacing: "0.02em",
      }}>{title}</h1>
      {subtitle && (
        <div style={{ fontSize: 11, color: "var(--muted)", marginTop: 4 }}>{subtitle}</div>
      )}
    </div>
  );
}

Object.assign(window, {
  HomePage, SurgesPage, StockDetailPage, CategoriesPage,
  ReportPage, PlanPage, ArticlePage, CategoryPage,
  ArchivePage, WatchlistPage, LegalPage,
  Disclaimer, Footer, PageHeader, EmptySurges,
});


// ============================================================
// ARCHIVE — Notion / Supabase に蓄積された全履歴を日付別に整理
// ============================================================
const _WEEKDAY = ["日", "月", "火", "水", "木", "金", "土"];

function _formatDateKey(iso) {
  if (!iso) return "—";
  // JST 換算してから YYYY-MM-DD を返す（端末TZに依存しない）
  return _toJstKey(iso) || ((iso || "").slice(0, 10) || "—");
}

function _parseDateKey(key) {
  // key = "YYYY-MM-DD" を Date オブジェクトに（JST正午で固定）
  if (!key || key === "—") return null;
  const [y, m, d] = key.split("-").map(Number);
  return new Date(Date.UTC(y, (m || 1) - 1, d || 1, 3, 0, 0)); // JST正午相当
}

function _todayKey() {
  const d = new Date();
  const fmt = new Intl.DateTimeFormat("en-CA", {
    timeZone: "Asia/Tokyo", year: "numeric", month: "2-digit", day: "2-digit",
  });
  return fmt.format(d); // YYYY-MM-DD
}

function _formatDateLabel(key) {
  // "2026-05-20" → "5月20日（水）"
  const d = _parseDateKey(key);
  if (!d) return key || "—";
  const w = ["日", "月", "火", "水", "木", "金", "土"][d.getUTCDay()];
  return `${d.getUTCMonth() + 1}月${d.getUTCDate()}日（${w}）`;
}

// "YYYY-MM-DD" を days 日ずらした "YYYY-MM-DD" を返す
function _shiftDate(key, days) {
  const [y, m, d] = key.split("-").map(Number);
  const dt = new Date(Date.UTC(y, m - 1, d + days));
  return `${dt.getUTCFullYear()}-${String(dt.getUTCMonth() + 1).padStart(2, "0")}-${String(dt.getUTCDate()).padStart(2, "0")}`;
}

// 急騰時→現在の変化率（%）。データ不足は null
 function _entryPctChange(e) {
  const sp = e.surge_price ?? e.price ?? null;
  const cp = e.current_price ?? null;
  if (sp == null || cp == null || Number(sp) <= 0) return null;
  return ((Number(cp) - Number(sp)) / Number(sp)) * 100;
}

// 選択日が今日（JST）以降なら true（「翌日」ボタンの無効化用）
function _isTodayOrLater(key) {
  const now = new Date(Date.now() + 9 * 3600000);
  const todayKey = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}-${String(now.getUTCDate()).padStart(2, "0")}`;
  return key >= todayKey;
}

// ── ミニカレンダー（アーカイブの日付選択用） ──
function MiniCalendar({ value, onChange, onClose }) {
  const initial = (() => {
    const d = value ? _parseDateKey(value) : new Date();
    return new Date(d.getUTCFullYear ? Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1, 3) : Date.UTC(d.getFullYear(), d.getMonth(), 1, 3));
  })();
  const [view, setView] = usePState(initial);
  const year  = view.getUTCFullYear();
  const month = view.getUTCMonth();

  const firstDow    = new Date(Date.UTC(year, month, 1, 3)).getUTCDay();
  const daysInMonth = new Date(Date.UTC(year, month + 1, 0, 3)).getUTCDate();
  const todayKey    = _todayKey();
  const pad = n => String(n).padStart(2, "0");
  const mkKey = d => `${year}-${pad(month + 1)}-${pad(d)}`;

  const cells = [];
  for (let i = 0; i < firstDow; i++) cells.push(null);
  for (let d = 1; d <= daysInMonth; d++) cells.push(d);

  const goPrev = () => setView(new Date(Date.UTC(year, month - 1, 1, 3)));
  const goNext = () => setView(new Date(Date.UTC(year, month + 1, 1, 3)));
  const ymLabel = `${year}年${month + 1}月`;

  // 当月の日付ごとの急騰件数
  const dateCounts = window.useDateCounts ? window.useDateCounts(year, month) : {};

  // PC では絞り込みページが縦に長く、position:absolute inset:0 だと
  // ページ全体の中央＝画面外に出てしまう。PCは fixed（ビューポート基準）、
  // スマホ(iPhone枠内)は従来どおり absolute にする。
  const _desktop = typeof window !== "undefined"
    && window.matchMedia && window.matchMedia("(min-width: 1000px)").matches;

  const modal = (
    <div
      onClick={onClose}
      style={{
        position: _desktop ? "fixed" : "absolute", inset: 0, zIndex: 9999,
        background: "rgba(8, 12, 22, 0.55)",
        display: "flex", alignItems: "center", justifyContent: "center",
        padding: 16, animation: "fadeUp .18s ease-out both",
      }}
    >
      <div
        onClick={e => e.stopPropagation()}
        style={{
          width: "100%", maxWidth: 340,
          background: "var(--bg)",
          border: "1px solid var(--hairline-strong)",
          padding: "14px 12px 12px",
          boxShadow: "0 12px 40px rgba(0,0,0,0.35)",
        }}
      >
        {/* Header */}
        <div style={{
          display: "flex", alignItems: "center", justifyContent: "space-between",
          marginBottom: 10,
        }}>
          <button onClick={goPrev} className="tappable" aria-label="前の月" style={{
            border: "1px solid var(--hairline-strong)", background: "var(--surface)",
            cursor: "pointer", padding: "4px 12px", fontSize: 13, lineHeight: 1,
            color: "var(--ink)",
          }}>‹</button>
          <div className="mincho" style={{
            fontSize: 14, fontWeight: 600, color: "var(--ink)", letterSpacing: "0.04em",
          }}>{ymLabel}</div>
          <button onClick={goNext} className="tappable" aria-label="次の月" style={{
            border: "1px solid var(--hairline-strong)", background: "var(--surface)",
            cursor: "pointer", padding: "4px 12px", fontSize: 13, lineHeight: 1,
            color: "var(--ink)",
          }}>›</button>
        </div>

        {/* Weekday row */}
        <div style={{
          display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 2,
          marginBottom: 4,
        }}>
          {["日", "月", "火", "水", "木", "金", "土"].map((w, i) => (
            <div key={w} style={{
              textAlign: "center", fontSize: 9.5, letterSpacing: "0.08em",
              padding: "4px 0",
              color: i === 0 ? "var(--up)" : i === 6 ? "var(--navy)" : "var(--muted-2)",
            }}>{w}</div>
          ))}
        </div>

        {/* Day grid */}
        <div style={{
          display: "grid", gridTemplateColumns: "repeat(7, 1fr)", gap: 2,
        }}>
          {cells.map((d, i) => {
            if (d === null) return <div key={i} style={{ aspectRatio: "1" }}/>;
            const k          = mkKey(d);
            const isToday    = k === todayKey;
            const isSelected = k === value;
            const isFuture   = k > todayKey;
            const dow        = (firstDow + d - 1) % 7;
            const dowColor   = dow === 0 ? "var(--up)" : dow === 6 ? "var(--navy)" : "var(--ink)";
            const cnt        = dateCounts[k] || 0;
            return (
              <button
                key={i}
                disabled={isFuture}
                onClick={() => { onChange(k); onClose(); }}
                className="tappable"
                style={{
                  position: "relative",
                  aspectRatio: "1",
                  border: "1px solid " + (isSelected ? "var(--navy)" : isToday ? "var(--ink)" : "var(--hairline)"),
                  background: isSelected ? "var(--navy)" : "var(--surface)",
                  color: isSelected ? "#fff" : isFuture ? "var(--muted-2)" : dowColor,
                  fontSize: 12.5,
                  fontWeight: isToday || isSelected ? 700 : 400,
                  cursor: isFuture ? "default" : "pointer",
                  opacity: isFuture ? 0.35 : 1,
                  fontVariantNumeric: "tabular-nums",
                  padding: 0,
                  display: "flex", flexDirection: "column",
                  alignItems: "center", justifyContent: "center",
                  gap: 1,
                }}
              >
                <span style={{ lineHeight: 1 }}>{d}</span>
                {cnt > 0 && !isFuture && (
                  <span
                    className="mono"
                    style={{
                      fontSize: 8.5, lineHeight: 1,
                      letterSpacing: "0.02em",
                      color: isSelected ? "rgba(255,255,255,0.85)" : "var(--up)",
                      fontWeight: 600,
                    }}
                  >({cnt})</span>
                )}
              </button>
            );
          })}
        </div>

        {/* Footer */}
        <div style={{
          display: "flex", alignItems: "center",
          fontSize: 9.5, color: "var(--muted)",
          letterSpacing: "0.04em",
          marginTop: 10, marginBottom: 8,
          gap: 4,
        }}>
          <span className="mono" style={{ color: "var(--up)", fontWeight: 600 }}>(N)</span>
          <span>= 急騰銘柄数</span>
        </div>
        <div style={{ display: "flex", gap: 6 }}>
          <button onClick={onClose} className="btn btn-secondary tappable" style={{ flex: 1 }}>
            キャンセル
          </button>
          <button
            onClick={() => { onChange(todayKey); onClose(); }}
            className="btn btn-primary tappable"
            style={{ flex: 1 }}
          >今日</button>
        </div>
      </div>
    </div>
  );

  // マウント先:
  //  - PC: document.body 直下に portal し、position:fixed をビューポート基準にする
  //    （.page-enter の transform や列の高さに引きずられて画面外に出るのを防ぐ）
  //  - スマホ: iOSフレーム内の .app に portal して absolute センタリング
  let mountNode = null;
  if (typeof document !== "undefined") {
    mountNode = _desktop ? document.body : document.querySelector(".app");
  }
  if (mountNode && ReactDOM && ReactDOM.createPortal) {
    return ReactDOM.createPortal(modal, mountNode);
  }
  return modal;
}

function ArchivePage({ navigate }) {
  const archive = window.useArchive ? window.useArchive({ pageSize: 60 }) : {
    entries: window.SAMPLE_ARCHIVE || [], total: (window.SAMPLE_ARCHIVE || []).length,
    status: "fallback", hasMore: false, loading: false, loadMore: () => {},
  };
  const [q, setQ] = useArchiveState("q", "");
  const [themeFilter, setThemeFilter] = useArchiveState("themeFilter", null);
  const [selectedDate, setSelectedDate] = useArchiveState("selectedDate", null); // "YYYY-MM-DD"（初期化後は常に設定）
  const [calOpen, setCalOpen] = usePState(false);

  // 条件検索（急騰時からの変化率）
  const [condOpen, setCondOpen] = useArchiveState("condOpen", false);
  const [pctVal, setPctVal]     = useArchiveState("pctVal", "");    // 閾値（%）文字列
  const [pctDir, setPctDir]     = useArchiveState("pctDir", "gte"); // gte=以上 / lte=以下
  const [condScope, setCondScope] = useArchiveState("condScope", "all"); // all=全期間 / day=表示中の日
  const condActive = pctVal !== "" && !Number.isNaN(Number(pctVal));
  const searchActive = q.trim() !== "";
  // 検索欄に入力があるとき、または全期間条件検索のときは、
  // 当日だけでなく読み込み済みの全日付を横断して検索する
  const globalMode = searchActive || (condActive && condScope === "all");

  // 全期間検索・条件検索のあいだは、未読込の過去分を自動で全件読み込む
  // （「さらに過去分を読み込む」を押さなくても過去のヒットがそのまま出るように）
  React.useEffect(() => {
    if (globalMode && archive.hasMore && !archive.loading) {
      archive.loadMore();
    }
  }, [globalMode, archive.hasMore, archive.loading]);

  // データがあると分かっている日付キー（新しい順）— 前日/翌日のスキップに使用
  const knownKeys = useMPMemo(() => {
    const s = new Set();
    (archive.entries || []).forEach(e => {
      const k = _formatDateKey(e.detected_at);
      if (k) s.add(k);
    });
    return [...s].sort((a, b) => b.localeCompare(a));
  }, [archive.entries]);

  // 初期表示: データのある最新日
  React.useEffect(() => {
    if (!selectedDate && knownKeys.length > 0) setSelectedDate(knownKeys[0]);
  }, [knownKeys, selectedDate]);

  // 選択日のデータ取得
  const dateView = window.useArchiveByDate
    ? window.useArchiveByDate(selectedDate)
    : { entries: [], loading: false, status: "idle" };

  // 前日・翌日ナビ（既知のデータあり日へスキップ、なければ ±1 日）
  const goPrevDay = () => {
    if (!selectedDate) return;
    const older = knownKeys.find(k => k < selectedDate);
    setSelectedDate(older || _shiftDate(selectedDate, -1));
  };
  const goNextDay = () => {
    if (!selectedDate) return;
    const newer = [...knownKeys].reverse().find(k => k > selectedDate);
    if (newer) setSelectedDate(newer);
    else if (!_isTodayOrLater(selectedDate)) setSelectedDate(_shiftDate(selectedDate, 1));
  };
  const nextDisabled = !selectedDate || _isTodayOrLater(selectedDate);

  // テーマ chip 候補: 出現頻度トップ8
  const topThemes = useMPMemo(() => {
    const source = globalMode ? (archive.entries || []) : (dateView.entries || []);
    const counts = {};
    source.forEach(e => {
      (e.themes || []).forEach(t => {
        if (!t) return;
        counts[t] = (counts[t] || 0) + 1;
      });
    });
    return Object.entries(counts)
      .sort((a, b) => b[1] - a[1])
      .slice(0, 8)
      .map(([name, count]) => ({ name, count }));
  }, [archive.entries, dateView.entries, selectedDate, globalMode]);

  // フィルタ後の配列
  const filtered = useMPMemo(() => {
    const needle = q.trim();
    const source = globalMode ? (archive.entries || []) : (dateView.entries || []);
    const threshold = condActive ? Number(pctVal) : null;
    return source.filter(e => {
      if (themeFilter && !(e.themes || []).some(t => t === themeFilter)) return false;
      if (needle && !_entryMatchesQuery(e, needle)) return false;
      if (threshold != null) {
        const pct = _entryPctChange(e);
        if (pct == null) return false;
        if (pctDir === "gte" ? pct < threshold : pct > threshold) return false;
      }
      return true;
    });
  }, [archive.entries, dateView.entries, globalMode, q, themeFilter, condActive, pctVal, pctDir]);

  // 表示中の日の中では時刻降順
  const dayItems = useMPMemo(() => {
    return [...filtered].sort((a, b) => (b.detected_at || "").localeCompare(a.detected_at || ""));
  }, [filtered]);

  // 全期間モード用: 日付別グループ（新しい順）
  const grouped = useMPMemo(() => {
    if (!globalMode) return [];
    const map = new Map();
    filtered.forEach(e => {
      const key = _formatDateKey(e.detected_at);
      if (!map.has(key)) map.set(key, []);
      map.get(key).push(e);
    });
    for (const items of map.values()) {
      items.sort((a, b) => (b.detected_at || "").localeCompare(a.detected_at || ""));
    }
    return [...map.entries()]
      .sort((a, b) => b[0].localeCompare(a[0]))
      .map(([date, items]) => ({ date, items }));
  }, [filtered, globalMode]);

  const condLabel = condActive
    ? `急騰時から ${Number(pctVal) > 0 && pctDir === "gte" ? "+" : ""}${Number(pctVal)}% ${pctDir === "gte" ? "以上" : "以下"}`
    : null;

  const totalLabel = dayItems.length;
  const statusNote = dateView.status === "live" ? "Supabase連携中"
    : dateView.status === "connecting" ? "読み込み中…"
    : dateView.status === "error" ? "接続エラー"
    : dateView.status === "fallback" ? "サンプル表示"
    : (archive.status === "connecting" ? "読み込み中…" : "サンプル表示");

  return (
    <div className="page-enter" style={{ position: "relative" }}>
      <PageHeader
        title="急騰履歴アーカイブ"
        subtitle={
          globalMode
            ? `${condActive ? "条件検索" : "全期間検索"}（全期間）· ${filtered.length} 件ヒット · 読み込み済み ${(archive.entries || []).length} 件が対象`
            : selectedDate
              ? `${_formatDateLabel(selectedDate)} · ${totalLabel} 件 · ${statusNote}`
              : "読み込み中…"
        }
        onBack={() => navigate("home")}
      />

      {window.ArchivePayNotice && <window.ArchivePayNotice navigate={navigate}/>}
      <div style={{
        position: "sticky", top: 0, zIndex: 5,
        background: "var(--bg)", borderBottom: "1px solid var(--hairline)",
        padding: "10px 14px 8px",
      }}>
        <div style={{ display: "flex", gap: 6, alignItems: "stretch" }}>
          <div style={{
            flex: 1, minWidth: 0, display: "flex", alignItems: "center", gap: 6,
            background: "var(--surface)", border: "1px solid var(--hairline-strong)",
            padding: "6px 10px",
          }}>
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="var(--muted-2)" strokeWidth="1.8" style={{ flexShrink: 0 }}>
              <circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/>
            </svg>
            <input
              value={q}
              onChange={e => setQ(e.target.value)}
              placeholder="コード・銘柄名・キーワードで検索"
              style={{
                flex: 1, border: "none", outline: "none", background: "transparent",
                fontSize: 12.5, color: "var(--ink)", fontFamily: "inherit", minWidth: 0,
              }}
            />
            {q && (
              <button onClick={() => setQ("")} style={{
                background: "transparent", border: "none", color: "var(--muted-2)",
                cursor: "pointer", fontSize: 14, padding: 0, lineHeight: 1,
              }}>×</button>
            )}
          </div>
          <button
            onClick={() => setCalOpen(true)}
            className="tappable"
            aria-label="カレンダーで日付を選択"
            style={{
              flexShrink: 0,
              display: "inline-flex", alignItems: "center", gap: 5,
              padding: "6px 10px",
              border: "1px solid " + (selectedDate ? "var(--navy)" : "var(--hairline-strong)"),
              background: selectedDate ? "var(--navy)" : "var(--surface)",
              color: selectedDate ? "#fff" : "var(--ink)",
              cursor: "pointer",
              fontSize: 11.5, letterSpacing: "0.04em",
              fontFamily: "inherit",
            }}
          >
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <rect x="3" y="4" width="18" height="17" rx="1"/>
              <path d="M3 9h18M8 2v4M16 2v4"/>
            </svg>
            <span>日付</span>
          </button>
          <button
            onClick={() => setCondOpen(o => !o)}
            className="tappable"
            aria-label="条件で絞り込み"
            style={{
              flexShrink: 0,
              display: "inline-flex", alignItems: "center", gap: 5,
              padding: "6px 10px",
              border: "1px solid " + (condActive || condOpen ? "var(--navy)" : "var(--hairline-strong)"),
              background: condActive ? "var(--navy)" : "var(--surface)",
              color: condActive ? "#fff" : (condOpen ? "var(--navy)" : "var(--ink)"),
              cursor: "pointer",
              fontSize: 11.5, letterSpacing: "0.04em",
              fontFamily: "inherit",
            }}
          >
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
              <path d="M3 5h18l-7 8v6l-4-2v-4L3 5Z"/>
            </svg>
            <span>条件</span>
          </button>
        </div>

        {/* 条件検索パネル */}
        {condOpen && (
          <div style={{
            marginTop: 8,
            border: "1px solid var(--hairline-strong)",
            background: "var(--surface)",
            padding: "10px 12px",
          }}>
            <div className="eyebrow" style={{ color: "var(--navy)", marginBottom: 8 }}>条件検索 · 急騰時からの変化率</div>
            <div style={{ display: "flex", gap: 6, alignItems: "stretch", marginBottom: 8, flexWrap: "wrap" }}>
              <div style={{
                display: "flex", alignItems: "center", gap: 4,
                border: "1px solid var(--hairline-strong)", background: "var(--bg)",
                padding: "5px 8px", flex: "1 1 120px", minWidth: 0,
              }}>
                <input
                  type="number"
                  inputMode="decimal"
                  value={pctVal}
                  onChange={e => setPctVal(e.target.value)}
                  placeholder="例: 10"
                  style={{
                    flex: 1, minWidth: 0, border: "none", outline: "none",
                    background: "transparent", fontSize: 13, color: "var(--ink)",
                    fontFamily: "inherit",
                  }}
                />
                <span className="mono" style={{ fontSize: 11, color: "var(--muted)" }}>%</span>
              </div>
              <div style={{ display: "flex", flexShrink: 0 }}>
                {[["gte", "以上"], ["lte", "以下"]].map(([v, label]) => (
                  <button key={v} onClick={() => setPctDir(v)} className="tappable" style={{
                    padding: "5px 12px", cursor: "pointer",
                    fontSize: 11.5, fontFamily: "inherit", letterSpacing: "0.04em",
                    background: pctDir === v ? "var(--ink)" : "var(--surface)",
                    color: pctDir === v ? "var(--bg)" : "var(--ink-soft)",
                    border: "1px solid " + (pctDir === v ? "var(--ink)" : "var(--hairline-strong)"),
                    marginLeft: -1,
                  }}>{label}</button>
                ))}
              </div>
            </div>
            <div style={{ display: "flex", gap: 5, flexWrap: "wrap", marginBottom: 10 }}>
              {[5, 10, 20, 50].map(v => (
                <button key={v} onClick={() => { setPctVal(String(v)); setPctDir("gte"); }} className="tappable" style={{
                  fontSize: 10.5, padding: "3px 9px", cursor: "pointer",
                  fontFamily: "inherit", letterSpacing: "0.04em",
                  background: condActive && pctDir === "gte" && Number(pctVal) === v ? "var(--navy)" : "transparent",
                  color: condActive && pctDir === "gte" && Number(pctVal) === v ? "#fff" : "var(--ink-soft)",
                  border: "1px solid var(--hairline-strong)",
                }}>+{v}%以上</button>
              ))}
              <button onClick={() => { setPctVal("0"); setPctDir("lte"); }} className="tappable" style={{
                fontSize: 10.5, padding: "3px 9px", cursor: "pointer",
                fontFamily: "inherit", letterSpacing: "0.04em",
                background: condActive && pctDir === "lte" && Number(pctVal) === 0 ? "var(--navy)" : "transparent",
                color: condActive && pctDir === "lte" && Number(pctVal) === 0 ? "#fff" : "var(--ink-soft)",
                border: "1px solid var(--hairline-strong)",
              }}>下落中</button>
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <span style={{ fontSize: 10.5, color: "var(--muted)", letterSpacing: "0.04em", flexShrink: 0 }}>範囲</span>
              <div style={{ display: "flex" }}>
                {[["all", "全期間"], ["day", "表示中の日"]].map(([v, label]) => (
                  <button key={v} onClick={() => setCondScope(v)} className="tappable" style={{
                    padding: "4px 10px", cursor: "pointer",
                    fontSize: 10.5, fontFamily: "inherit", letterSpacing: "0.04em",
                    background: condScope === v ? "var(--ink)" : "var(--surface)",
                    color: condScope === v ? "var(--bg)" : "var(--ink-soft)",
                    border: "1px solid " + (condScope === v ? "var(--ink)" : "var(--hairline-strong)"),
                    marginLeft: -1,
                  }}>{label}</button>
                ))}
              </div>
              {condActive && (
                <button onClick={() => { setPctVal(""); }} className="tappable" style={{
                  marginLeft: "auto", flexShrink: 0,
                  fontSize: 10.5, padding: "4px 10px", cursor: "pointer",
                  fontFamily: "inherit", letterSpacing: "0.04em",
                  background: "transparent", color: "var(--down)",
                  border: "1px solid var(--hairline-strong)",
                }}>クリア</button>
              )}
            </div>
            <div style={{ fontSize: 10, color: "var(--muted-2)", marginTop: 8, lineHeight: 1.6 }}>
              ※ 現在株価・急騰時株価が揃っている銘柄のみが対象です。
            </div>
          </div>
        )}

        {/* 適用中の条件 chip */}
        {condActive && !condOpen && (
          <div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 8 }}>
            <span style={{
              display: "inline-flex", alignItems: "center", gap: 8,
              padding: "4px 10px",
              background: "var(--navy)", color: "#fff",
              fontSize: 11.5, fontWeight: 600, letterSpacing: "0.02em",
            }}>
              {condLabel} · {condScope === "all" ? "全期間" : "表示中の日"}
              <button
                onClick={() => setPctVal("")}
                aria-label="条件を解除"
                style={{
                  background: "transparent", border: "none", color: "#fff",
                  cursor: "pointer", padding: 0, fontSize: 14, lineHeight: 1,
                  opacity: 0.85,
                }}
              >×</button>
            </span>
          </div>
        )}

        {/* Selected date chip + prev/next day nav（全期間条件検索中は非表示） */}
        {selectedDate && !globalMode && (
          <div style={{
            display: "flex", alignItems: "center", gap: 6, marginTop: 8,
          }}>
            <button
              onClick={goPrevDay}
              className="tappable"
              aria-label="前の日"
              style={{
                flexShrink: 0, display: "inline-flex", alignItems: "center", gap: 3,
                padding: "5px 10px", cursor: "pointer",
                background: "var(--surface)", color: "var(--ink)",
                border: "1px solid var(--hairline-strong)",
                fontSize: 11.5, fontFamily: "inherit", letterSpacing: "0.02em",
              }}
            >
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"/></svg>
              前日
            </button>
            <span style={{
              flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
              padding: "5px 10px",
              background: "var(--navy)", color: "#fff",
              fontSize: 12, fontWeight: 600,
              letterSpacing: "0.02em", whiteSpace: "nowrap",
            }}>
              {_formatDateLabel(selectedDate)}
              {knownKeys.length > 0 && selectedDate !== knownKeys[0] && (
                <button
                  onClick={() => setSelectedDate(knownKeys[0])}
                  aria-label="最新の日へ戻る"
                  style={{
                    background: "rgba(255,255,255,0.16)", border: "none", color: "#fff",
                    cursor: "pointer", padding: "1px 7px", fontSize: 10, lineHeight: 1.5,
                    letterSpacing: "0.05em", fontFamily: "inherit",
                  }}
                >最新へ</button>
              )}
            </span>
            <button
              onClick={goNextDay}
              disabled={nextDisabled}
              className="tappable"
              aria-label="次の日"
              style={{
                flexShrink: 0, display: "inline-flex", alignItems: "center", gap: 3,
                padding: "5px 10px",
                cursor: nextDisabled ? "default" : "pointer",
                background: "var(--surface)",
                color: nextDisabled ? "var(--muted-2)" : "var(--ink)",
                border: "1px solid var(--hairline-strong)",
                opacity: nextDisabled ? 0.5 : 1,
                fontSize: 11.5, fontFamily: "inherit", letterSpacing: "0.02em",
              }}
            >
              翌日
              <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6"/></svg>
            </button>
          </div>
        )}

        {/* Theme chips */}
        {topThemes.length > 0 && (
          <div style={{
            display: "flex", gap: 5, marginTop: 8, overflowX: "auto",
            scrollbarWidth: "none",
          }}>
            <button onClick={() => setThemeFilter(null)} className="tappable" style={{
              flexShrink: 0,
              fontSize: 10.5, padding: "3px 9px",
              letterSpacing: "0.04em", cursor: "pointer",
              background: themeFilter === null ? "var(--ink)" : "transparent",
              color: themeFilter === null ? "var(--bg)" : "var(--ink)",
              border: "1px solid " + (themeFilter === null ? "var(--ink)" : "var(--hairline-strong)"),
            }}>すべて</button>
            {topThemes.map(t => (
              <button key={t.name} onClick={() => setThemeFilter(themeFilter === t.name ? null : t.name)} className="tappable" style={{
                flexShrink: 0,
                fontSize: 10.5, padding: "3px 9px",
                letterSpacing: "0.04em", cursor: "pointer",
                background: themeFilter === t.name ? "var(--navy)" : "transparent",
                color: themeFilter === t.name ? "#fff" : "var(--ink-soft)",
                border: "1px solid " + (themeFilter === t.name ? "var(--navy)" : "var(--hairline-strong)"),
                display: "inline-flex", alignItems: "center", gap: 4,
                whiteSpace: "nowrap",
              }}>
                {t.name}
                <span className="mono" style={{
                  fontSize: 9.5,
                  color: themeFilter === t.name ? "rgba(255,255,255,0.7)" : "var(--muted-2)",
                }}>{t.count}</span>
              </button>
            ))}
          </div>
        )}
      </div>

      {/* Empty */}
      {(globalMode ? filtered.length === 0 : dayItems.length === 0) && (
        <div style={{
          padding: "40px 24px", textAlign: "center", color: "var(--muted)",
          fontSize: 12, lineHeight: 1.7,
        }}>
          {globalMode
            ? (archive.loading ? "読み込み中…" : "該当する銘柄がありません。「さらに過去分を読み込む」で対象を広げられます。")
            : !selectedDate || dateView.loading || archive.status === "connecting"
              ? "読み込み中…"
              : (q.trim() || themeFilter || condActive)
                ? "条件に該当するデータがありません。"
                : `${_formatDateLabel(selectedDate)} に急騰検知データはありません。`}
        </div>
      )}

      {/* 全期間条件検索: 日付別グループ表示 */}
      {globalMode && grouped.map(g => (
        <DateGroup key={g.date} dateKey={g.date} items={g.items} navigate={navigate}/>
      ))}

      {/* 全期間条件検索: 過去分を自動読み込み中の表示 */}
      {globalMode && archive.hasMore && (
        <div style={{ padding: "14px 14px 6px", textAlign: "center" }}>
          <span style={{ fontSize: 11.5, color: "var(--muted)", letterSpacing: "0.04em" }}>
            過去ログを読み込み中… · 読み込み済み {(archive.entries || []).length} 件
          </span>
        </div>
      )}

      {/* 表示中の日の一覧 */}
      {!globalMode && selectedDate && dayItems.length > 0 && (
        <DateGroup dateKey={selectedDate} items={dayItems} navigate={navigate}/>
      )}

      {/* 下部の前日/翌日ナビ（リストを見終わった後の移動用） */}
      {!globalMode && selectedDate && (
        <div style={{
          display: "flex", alignItems: "center", gap: 6,
          padding: "14px 14px 6px",
        }}>
          <button
            onClick={() => {
              goPrevDay();
              const sc = document.querySelector(".scroll");
              if (sc) sc.scrollTo({ top: 0 });
            }}
            className="tappable"
            aria-label="前の日"
            style={{
              flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5,
              padding: "9px 10px", cursor: "pointer",
              background: "var(--surface)", color: "var(--ink)",
              border: "1px solid var(--hairline-strong)",
              fontSize: 12, fontFamily: "inherit", letterSpacing: "0.02em",
            }}
          >
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="m15 18-6-6 6-6"/></svg>
            前日
          </button>
          <button
            onClick={() => {
              goNextDay();
              const sc = document.querySelector(".scroll");
              if (sc) sc.scrollTo({ top: 0 });
            }}
            disabled={nextDisabled}
            className="tappable"
            aria-label="次の日"
            style={{
              flex: 1, display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 5,
              padding: "9px 10px",
              cursor: nextDisabled ? "default" : "pointer",
              background: "var(--surface)",
              color: nextDisabled ? "var(--muted-2)" : "var(--ink)",
              border: "1px solid var(--hairline-strong)",
              opacity: nextDisabled ? 0.5 : 1,
              fontSize: 12, fontFamily: "inherit", letterSpacing: "0.02em",
            }}
          >
            翌日
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round"><path d="m9 18 6-6-6-6"/></svg>
          </button>
        </div>
      )}

      <Disclaimer/>

      {/* Calendar modal */}
      {calOpen && (
        <MiniCalendar
          value={selectedDate}
          onChange={k => setSelectedDate(k)}
          onClose={() => setCalOpen(false)}
        />
      )}
    </div>
  );
}

function DateGroup({ dateKey, items, navigate }) {
  const dt = _parseDateKey(dateKey);
  const dateLabel = dt
    ? `${dt.getUTCFullYear()}.${String(dt.getUTCMonth() + 1).padStart(2, "0")}.${String(dt.getUTCDate()).padStart(2, "0")}`
    : "—";
  const weekday = dt ? _WEEKDAY[dt.getUTCDay()] : "";
  const isWeekend = weekday === "土" || weekday === "日";

  // 「今日」「昨日」表示
  const today = new Date();
  const todayKey = _formatDateKey(today.toISOString());
  const yest = new Date(today.getTime() - 86400000);
  const yestKey = _formatDateKey(yest.toISOString());
  const relLabel = dateKey === todayKey ? "今日"
                  : dateKey === yestKey ? "昨日" : null;

  return (
    <section>
      {/* Date header */}
      <div style={{
        padding: "16px 14px 8px",
        background: "var(--bg)",
        display: "flex", alignItems: "baseline", gap: 10,
        borderBottom: "1px solid var(--hairline)",
      }}>
        <div className="mono" style={{
          fontSize: 13, fontWeight: 600,
          letterSpacing: "0.04em", color: "var(--ink)",
        }}>{dateLabel}</div>
        <div style={{
          fontSize: 10.5, color: isWeekend ? "var(--up)" : "var(--muted)",
          letterSpacing: "0.08em", fontWeight: 500,
        }}>
          {weekday}曜日
          {relLabel && <span style={{
            marginLeft: 6, padding: "0 5px",
            background: "var(--ink)", color: "var(--bg)",
            fontSize: 9, letterSpacing: "0.1em",
          }}>{relLabel}</span>}
        </div>
        <div className="mono" style={{
          marginLeft: "auto", fontSize: 10.5, color: "var(--muted-2)",
        }}>
          {items.length}件
        </div>
      </div>

      {/* Entries */}
      <div style={{ background: "var(--surface)" }}>
        {items.map((item, i) => (
          <ArchiveRow
            key={item.id || (item.code + (item.detected_at || i))}
            item={item}
            onOpen={() => navigate("stock", item)}
            onTheme={t => navigate("theme", t)}
          />
        ))}
      </div>
    </section>
  );
}

function _fmtPrice(v) {
  if (v == null || v === "" || Number.isNaN(Number(v))) return null;
  const n = Number(v);
  // 1未満は小数2桁、それ以外は整数+カンマ
  return n < 10 ? n.toFixed(2) : Math.round(n).toLocaleString("ja-JP");
}

function ArchiveRow({ item, onOpen, onTheme, showDate = false }) {
  const time = item.time || (item.detected_at || "").slice(11, 16) || "—:—";
  const themes = (item.themes || []).slice(0, 3);
  let dateLbl = null;
  if (showDate && item.detected_at) {
    try {
      const d = new Date(item.detected_at);
      if (!isNaN(d)) dateLbl = `${d.getMonth() + 1}/${d.getDate()}`;
    } catch (e) {}
  }

  // 急騰時 / 現在の株価（データ未取得なら null）
  const surgePrice   = item.surge_price ?? item.price ?? null;
  const currentPrice = item.current_price ?? null;
  const surgeFmt   = _fmtPrice(surgePrice);
  const currentFmt = _fmtPrice(currentPrice);
  const hasBoth = surgePrice != null && currentPrice != null && Number(surgePrice) > 0;
  const pctChange = hasBoth
    ? ((Number(currentPrice) - Number(surgePrice)) / Number(surgePrice)) * 100
    : null;
  const pctColor = pctChange == null ? "var(--muted-2)"
                 : pctChange > 0 ? "var(--up)"
                 : pctChange < 0 ? "var(--down)" : "var(--muted)";
  const pctLabel = pctChange == null ? null
                 : `${pctChange > 0 ? "+" : ""}${pctChange.toFixed(1)}%`;
  return (
    <div role="button" tabIndex={0} onClick={onOpen}
      onKeyDown={e => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onOpen && onOpen(); } }}
      className="tappable" style={{
      width: "100%", textAlign: "left", cursor: "pointer",
      background: "var(--surface)", border: "none",
      borderBottom: "1px solid var(--hairline)",
      padding: "12px 14px",
      display: "grid",
      gridTemplateColumns: "48px 1fr",
      gap: 10, alignItems: "flex-start",
      fontFamily: "inherit",
    }}>
      {/* Time column */}
      <div style={{ paddingTop: 2 }}>
        {dateLbl && (
          <div className="mono" style={{
            fontSize: 11, fontWeight: 700, color: "var(--navy)",
            letterSpacing: "0.02em", marginBottom: 1,
          }}>{dateLbl}</div>
        )}
        <div className="mono" style={{
          fontSize: 12, fontWeight: 600,
          color: "var(--ink-soft)", letterSpacing: "0.02em",
        }}>{time}</div>
        <div className="mono" style={{
          fontSize: 9.5, color: "var(--muted-2)", marginTop: 2,
          letterSpacing: "0.08em",
        }}>JST</div>
      </div>

      {/* Body */}
      <div style={{ minWidth: 0 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 4 }}>
          <span className="mono" style={{
            fontSize: 10.5, color: "var(--muted)",
            border: "1px solid var(--hairline-strong)",
            padding: "0 5px", letterSpacing: "0.04em",
          }}>{item.code}</span>
          <span style={{
            fontSize: 13.5, fontWeight: 600, color: "var(--ink)",
            overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
            minWidth: 0,
          }}>{item.name}</span>
          <span style={{
            marginLeft: "auto", color: "var(--muted-2)",
            fontSize: 14, lineHeight: 1, flexShrink: 0,
          }}>›</span>
        </div>
        <div style={{
          fontSize: 12, color: "var(--ink-soft)", lineHeight: 1.55,
          borderLeft: "2px solid var(--navy)", paddingLeft: 8,
          marginBottom: 6,
        }}>
          {item.reason || "—"}
        </div>

        {/* 株価セル: 急騰時 / 現在 / 変化率 */}
        <div style={{
          display: "grid",
          gridTemplateColumns: "1fr 1fr auto",
          gap: 6, alignItems: "stretch",
          marginBottom: themes.length > 0 ? 6 : 0,
          border: "1px solid var(--hairline)",
          background: "var(--surface-sub)",
        }}>
          <div style={{ padding: "5px 8px", borderRight: "1px solid var(--hairline)" }}>
            <div style={{
              fontSize: 9, letterSpacing: "0.1em", color: "var(--muted-2)",
              marginBottom: 1,
            }}>急騰時</div>
            <div className="mono" style={{
              fontSize: 12, fontWeight: 600,
              color: surgeFmt ? "var(--ink)" : "var(--muted-2)",
            }}>
              {surgeFmt ? <><span style={{ fontSize: 9.5, color: "var(--muted)", marginRight: 1 }}>¥</span>{surgeFmt}</> : "—"}
            </div>
          </div>
          <div style={{ padding: "5px 8px", borderRight: pctLabel ? "1px solid var(--hairline)" : "none" }}>
            <div style={{
              fontSize: 9, letterSpacing: "0.1em", color: "var(--muted-2)",
              marginBottom: 1,
            }}>現在</div>
            <div className="mono" style={{
              fontSize: 12, fontWeight: 600,
              color: currentFmt ? "var(--ink)" : "var(--muted-2)",
            }}>
              {currentFmt ? <><span style={{ fontSize: 9.5, color: "var(--muted)", marginRight: 1 }}>¥</span>{currentFmt}</> : "—"}
            </div>
          </div>
          {pctLabel && (
            <div className="mono" style={{
              padding: "0 10px",
              display: "flex", alignItems: "center", justifyContent: "flex-end",
              fontSize: 12, fontWeight: 700,
              color: pctColor,
              letterSpacing: "0.02em",
              minWidth: 64,
            }}>{pctLabel}</div>
          )}
        </div>
        {themes.length > 0 && (
          <div style={{ display: "flex", gap: 5, flexWrap: "wrap" }}>
            {themes.map(t => (
              <span key={t}
                onClick={onTheme ? (e => { e.stopPropagation(); onTheme(String(t).trim()); }) : undefined}
                role={onTheme ? "button" : undefined}
                style={{
                fontSize: 10, color: "var(--muted)",
                border: "1px solid var(--hairline)",
                padding: "1px 6px", letterSpacing: "0.02em",
                cursor: onTheme ? "pointer" : undefined,
              }}>{t}</span>
            ))}
            {(item.themes || []).length > 3 && (
              <span className="mono" style={{
                fontSize: 10, color: "var(--muted-2)", padding: "1px 4px",
              }}>+{item.themes.length - 3}</span>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// ============================================================
// THEME — テーマ別の検知履歴（テーマ札から遷移）
// ============================================================
function ThemePage({ theme, navigate, goBack }) {
  const th = window.useThemeEntries ? window.useThemeEntries(theme) : { entries: [], loading: false };
  const entries = th.entries || [];
  const codes = new Set(entries.map(e => String(e.code)));
  const dates = entries.map(e => e.detected_at).filter(Boolean).sort();
  const fmtDate = d => {
    try {
      return new Intl.DateTimeFormat("ja-JP", { timeZone: "Asia/Tokyo", year: "numeric", month: "long", day: "numeric" }).format(new Date(d));
    } catch (e) { return ""; }
  };

  return (
    <div className="page-enter">
      <PageHeader title="テーマ" onBack={goBack}/>

      <div style={{ padding: "18px 16px 4px" }}>
        <div className="eyebrow" style={{ color: "var(--navy)" }}>テーマ別の検知履歴</div>
        <h1 className="mincho" style={{ margin: "6px 0 0", fontSize: 24, fontWeight: 700, letterSpacing: "0.02em" }}>
          {theme}
        </h1>
        <div style={{ display: "flex", gap: 16, marginTop: 12, fontSize: 11, color: "var(--muted)", flexWrap: "wrap" }}>
          <span>検知 <span className="mono" style={{ color: "var(--ink)", fontWeight: 600 }}>{entries.length}</span> 件</span>
          <span>銘柄 <span className="mono" style={{ color: "var(--ink)", fontWeight: 600 }}>{codes.size}</span> 種</span>
          {dates.length > 0 && <span>初出 <span style={{ color: "var(--ink)" }}>{fmtDate(dates[0])}</span></span>}
        </div>
      </div>

      <SectionHeader kicker="新しい順" title="このテーマで動いた銘柄"/>
      <div style={{ borderTop: "1px solid var(--hairline)" }}>
        {th.loading && (
          <div style={{ padding: "28px 16px", fontSize: 11, color: "var(--muted)", textAlign: "center" }}>読み込み中…</div>
        )}
        {!th.loading && entries.length === 0 && (
          <div style={{ padding: "28px 16px", fontSize: 11.5, color: "var(--muted)", textAlign: "center", lineHeight: 1.9 }}>
            このテーマの検知はまだありません。
          </div>
        )}
        {entries.map((e, i) => (
          <ArchiveRow
            key={e.id || i}
            item={e}
            showDate={true}
            onOpen={() => navigate("stock", e)}
            onTheme={t => { if (t !== theme) navigate("theme", t); }}
          />
        ))}
      </div>
    </div>
  );
}

// ============================================================
// WATCHLIST — 自分のウォッチリスト（localStorage 保存）
// ============================================================
function WatchlistPage({ navigate }) {
  const wl = window.useWatchlist ? window.useWatchlist() : { items: [], remove: () => {} };
  const prices = window.useWatchPrices
    ? window.useWatchPrices(wl.items.map(x => x.code))
    : {};

  return (
    <div className="page-enter">
      <PageHeader
        title="ウォッチリスト"
        subtitle={wl.items.length > 0 ? `${wl.items.length} 銘柄を登録中 · この端末に保存` : "気になる銘柄を保存"}
        onBack={() => navigate("home")}
      />

      {wl.items.length === 0 && (
        <div style={{
          padding: "48px 28px", textAlign: "center",
          color: "var(--muted)", fontSize: 12.5, lineHeight: 2,
        }}>
          <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="var(--muted-2)" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" style={{ marginBottom: 10 }}>
            <path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2Z"/>
          </svg>
          <div style={{ fontWeight: 600, color: "var(--ink-soft)", marginBottom: 4 }}>まだ登録がありません</div>
          銘柄詳細ページの「ウォッチ」ボタンから<br/>気になる銘柄を追加できます。
        </div>
      )}

      {wl.items.length > 0 && (
        <div style={{ background: "var(--surface)", borderTop: "1px solid var(--hairline)" }}>
          {wl.items.map(item => (
            <WatchRow
              key={item.code}
              item={item}
              livePrice={prices[item.code] || null}
              onOpen={() => navigate("stock", { ...item, current_price: prices[item.code]?.price ?? item.current_price })}
              onRemove={() => wl.remove(item.code)}
            />
          ))}
        </div>
      )}

      <Disclaimer/>
    </div>
  );
}

function WatchRow({ item, livePrice, onOpen, onRemove }) {
  const surgePrice   = item.surge_price ?? null;
  const currentPrice = (livePrice && livePrice.price != null) ? livePrice.price : (item.current_price ?? null);
  const surgeFmt   = _fmtPrice(surgePrice);
  const currentFmt = _fmtPrice(currentPrice);
  const hasBoth = surgePrice != null && currentPrice != null && Number(surgePrice) > 0;
  const pctChange = hasBoth
    ? ((Number(currentPrice) - Number(surgePrice)) / Number(surgePrice)) * 100
    : null;
  const pctColor = pctChange == null ? "var(--muted-2)"
                 : pctChange > 0 ? "var(--up)"
                 : pctChange < 0 ? "var(--down)" : "var(--muted)";
  const pctLabel = pctChange == null ? null
                 : `${pctChange > 0 ? "+" : ""}${pctChange.toFixed(1)}%`;
  const detectedLabel = item.detected_at
    ? `${item.detected_at.slice(0, 10).replace(/-/g, ".")} 検知`
    : null;

  return (
    <div
      onClick={onOpen}
      className="tappable"
      role="button"
      tabIndex={0}
      onKeyDown={e => { if (e.key === "Enter" || e.key === " ") onOpen(); }}
      style={{
        cursor: "pointer",
        borderBottom: "1px solid var(--hairline)",
        padding: "12px 14px",
      }}
    >
      <div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }}>
        <span className="mono" style={{
          fontSize: 10.5, color: "var(--muted)",
          border: "1px solid var(--hairline-strong)",
          padding: "0 5px", letterSpacing: "0.04em",
        }}>{item.code}</span>
        <span style={{
          fontSize: 13.5, fontWeight: 600, color: "var(--ink)",
          overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap",
          minWidth: 0,
        }}>{item.name}</span>
        {detectedLabel && (
          <span className="mono" style={{ fontSize: 9.5, color: "var(--muted-2)", flexShrink: 0 }}>
            {detectedLabel}
          </span>
        )}
        <button
          onClick={e => { e.stopPropagation(); onRemove(); }}
          aria-label="ウォッチリストから外す"
          className="tappable"
          style={{
            marginLeft: "auto", flexShrink: 0,
            background: "transparent", border: "none", cursor: "pointer",
            padding: "4px 2px 4px 8px", lineHeight: 1,
          }}
        >
          <svg width="15" height="15" viewBox="0 0 24 24" fill="var(--gold)" stroke="var(--gold)" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round">
            <path d="m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2Z"/>
          </svg>
        </button>
      </div>

      {item.reason && (
        <div style={{
          fontSize: 11.5, color: "var(--ink-soft)", lineHeight: 1.55,
          borderLeft: "2px solid var(--navy)", paddingLeft: 8,
          marginBottom: 8,
          overflow: "hidden", display: "-webkit-box",
          WebkitLineClamp: 2, WebkitBoxOrient: "vertical",
        }}>
          {item.reason}
        </div>
      )}

      <div style={{
        display: "grid",
        gridTemplateColumns: "1fr 1fr auto",
        gap: 6, alignItems: "stretch",
        border: "1px solid var(--hairline)",
        background: "var(--surface-sub)",
      }}>
        <div style={{ padding: "5px 8px", borderRight: "1px solid var(--hairline)" }}>
          <div style={{ fontSize: 9, letterSpacing: "0.1em", color: "var(--muted-2)", marginBottom: 1 }}>急騰時</div>
          <div className="mono" style={{ fontSize: 12, fontWeight: 600, color: surgeFmt ? "var(--ink)" : "var(--muted-2)" }}>
            {surgeFmt ? <><span style={{ fontSize: 9.5, color: "var(--muted)", marginRight: 1 }}>¥</span>{surgeFmt}</> : "—"}
          </div>
        </div>
        <div style={{ padding: "5px 8px", borderRight: pctLabel ? "1px solid var(--hairline)" : "none" }}>
          <div style={{ fontSize: 9, letterSpacing: "0.1em", color: "var(--muted-2)", marginBottom: 1 }}>現在</div>
          <div className="mono" style={{ fontSize: 12, fontWeight: 600, color: currentFmt ? "var(--ink)" : "var(--muted-2)" }}>
            {currentFmt ? <><span style={{ fontSize: 9.5, color: "var(--muted)", marginRight: 1 }}>¥</span>{currentFmt}</> : "—"}
          </div>
        </div>
        {pctLabel && (
          <div className="mono" style={{
            padding: "0 10px",
            display: "flex", alignItems: "center", justifyContent: "flex-end",
            fontSize: 12.5, fontWeight: 700, color: pctColor,
          }}>{pctLabel}</div>
        )}
      </div>
    </div>
  );
}
