// 株価急騰要因メモ — app shell // Wires iOS frame + tweaks + page navigation const { useState, useEffect } = React; // 公開ビルド判定:公開版HTMLで window.PUBLIC_BUILD = true をセット。 // true のとき iPhoneベゼル・Tweaksパネルを外し、実端末フルスクリーンで表示する。 const PUBLIC_BUILD = !!window.PUBLIC_BUILD; // 画面幅で PC / スマホを判定。PC(>=1000px)では全幅レイアウト、 // それ未満では従来の iPhone フレーム表示にする。 function useIsDesktop(bp = 1000) { const q = `(min-width: ${bp}px)`; const [is, setIs] = useState(() => typeof window !== "undefined" && window.matchMedia(q).matches); useEffect(() => { const m = window.matchMedia(q); const h = (e) => setIs(e.matches); m.addEventListener ? m.addEventListener("change", h) : m.addListener(h); setIs(m.matches); return () => { m.removeEventListener ? m.removeEventListener("change", h) : m.removeListener(h); }; }, [q]); return is; } const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "showLiveTicker": true, "accent": "navy", "marketPalette": "jp" }/*EDITMODE-END*/; // タブのルートページ — これらに遷移したら履歴をリセットする const TAB_ROOTS = ["home", "cats", "archive", "watch", "report", "plan"]; // URLハッシュで直接開けるページ(例: /#legal → 特商法表記)。Stripe審査等でURL提出に使う。 const HASH_PAGES = ["home", "cats", "archive", "watch", "report", "plan", "legal", "scouter"]; function App() { // ページ遷移履歴のスタック。最後の要素が現在のページ。 const [history, setHistory] = useState(() => { const h = (window.location.hash || "").replace(/^#\/?/, ""); return [{ page: HASH_PAGES.includes(h) ? h : "home", data: null }]; }); const current = history[history.length - 1]; const page = current.page; const pageData = current.data; const scrollRef = React.useRef(null); const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS); const live = window.useLiveData ? window.useLiveData() : { status: "fallback" }; // Apply tweak: accent color useEffect(() => { const root = document.documentElement; if (t.accent === "ink") { root.style.setProperty("--navy", "#0B1220"); root.style.setProperty("--navy-deep", "#000000"); } else if (t.accent === "indigo") { root.style.setProperty("--navy", "#2A3B6E"); root.style.setProperty("--navy-deep", "#1A2548"); } else { root.style.setProperty("--navy", "#14223D"); root.style.setProperty("--navy-deep", "#0A1226"); } // Market color convention if (t.marketPalette === "us") { root.style.setProperty("--up", "#1F7A3A"); root.style.setProperty("--up-soft", "#E9F3EC"); root.style.setProperty("--down", "#C8333A"); root.style.setProperty("--down-soft", "#FBECEC"); } else { root.style.setProperty("--up", "#C8333A"); root.style.setProperty("--up-soft", "#FBECEC"); root.style.setProperty("--down", "#1F7A3A"); root.style.setProperty("--down-soft", "#E9F3EC"); } }, [t.accent, t.marketPalette]); const _scrollTop = () => { setTimeout(() => { if (scrollRef.current) scrollRef.current.scrollTop = 0; }, 10); }; const navigate = (p, data) => { setHistory(h => { // タブのルートへ移動するときは履歴を畳んで1件に if (TAB_ROOTS.includes(p)) return [{ page: p, data: data || null }]; // 同じページの重複pushは避ける const top = h[h.length - 1]; if (top && top.page === p && top.data === (data || null)) return h; return [...h, { page: p, data: data || null }]; }); // 直リンク可能なページはURLハッシュを同期(履歴は汚さない) try { window.history.replaceState(null, "", HASH_PAGES.includes(p) && p !== "home" ? "#" + p : window.location.pathname + window.location.search); } catch (e) {} _scrollTop(); }; // 一つ前のページへ戻る。履歴がなければホームへ。 const goBack = () => { setHistory(h => (h.length > 1 ? h.slice(0, -1) : [{ page: "home", data: null }])); _scrollTop(); }; const tabKey = ["home"].includes(page) ? "home" : ["cats", "category"].includes(page) ? "cats" : ["archive"].includes(page) ? "archive" : ["watch"].includes(page) ? "watch" : ["report"].includes(page) ? "report" : ["plan"].includes(page) ? "plan" : null; let body = null; switch (page) { case "home": body = ; break; case "surges": body = ; break; case "stock": body = ; break; case "cats": body = ; break; case "category": body = ; break; case "theme": body = ; break; case "archive": body = ; break; case "watch": body = ; break; case "report": body = ; break; case "plan": body = ; break; case "article": body = ; break; case "legal": body = ; break; case "scouter": body = window.ScouterPage ? : ; break; default: body = ; } const isReportPage = page === "report"; const isDesktop = useIsDesktop(); // ===== PC(全幅)レイアウト:左サイドバー + 中央コンテンツ列 ===== if (isDesktop) { const navItems = [ { k: "home", label: "ホーム", Icon: window.IconHome }, { k: "cats", label: "カテゴリ", Icon: window.IconGrid }, { k: "archive", label: "アーカイブ", Icon: window.IconArchive }, { k: "watch", label: "ウォッチ", Icon: window.IconStar }, { k: "report", label: "レポート", Icon: window.IconChart }, { k: "plan", label: "プラン", Icon: window.IconCrown }, ]; return ( <>
{t.showLiveTicker && }
{body}
{/* Tweaks panel */} {renderTweaks(t, setTweak)} ); } return ( <>
{!isReportPage && navigate("plan")} onHome={() => navigate("home")} status={live.status}/>} {!isReportPage && t.showLiveTicker && }
{body}
{tabKey !== null && { if (k === "home") navigate("home"); else if (k === "cats") navigate("cats"); else if (k === "archive") navigate("archive"); else if (k === "watch") navigate("watch"); else if (k === "report") navigate("report"); else if (k === "plan") navigate("plan"); }}/>}
{/* Tweaks panel */} {renderTweaks(t, setTweak)} ); } // Tweaks panel(PC/スマホ両レイアウトで共有) function renderTweaks(t, setTweak) { if (PUBLIC_BUILD) return null; return ( setTweak("showLiveTicker", v)} /> setTweak("accent", v)} /> setTweak("marketPalette", v)} /> ); } // Render: PC は全幅、スマホは iPhone フレーム中央表示 function Root() { const isDesktop = useIsDesktop(); const content = ( ); if (isDesktop || PUBLIC_BUILD) return content; return (
{content}
); } ReactDOM.createRoot(document.getElementById("root")).render();