/* ============ Interactive Demo — working click-through app ============ */ /* ---- MachiLog sample app screens (rendered HTML mockups) ---- */ function ApStatusbar({ dark }) { return (
9:41 ●●● ▮
); } function Sc_Home() { return (
KAMIYAMA — 神領エリア
おはようございます
対応中 3
今月の記録 28
センサー異常 1
近くの記録MAP ↗
{[["道路の陥没","神領 大埜地","対応中"],["街灯の故障","下分 中央","受付"],["見守り要請","阿野 西","完了"]].map((r,i)=>(
{r[0]}{r[1]}
{r[2]}
))}
+ 記録する
); } function Sc_Map() { return (
{[[28,34,0],[58,22,1],[44,60,2],[72,52,0],[36,76,1]].map((p,i)=>( ))}
マップ 5件
道路の陥没神領 大埜地 · 120m
対応中
); } function Sc_Report() { const cats = [["🛣","道路・舗装"],["💡","街灯・照明"],["🌿","公園・緑地"],["🚸","見守り"],["📡","センサー"],["⋯","その他"]]; return (
記録する
どんな気づきですか?SELECT CATEGORY
{cats.map((c,i)=>(
{c[0]}{c[1]}
))}
+ 写真を追加
次へ進む →
); } function Sc_Confirm() { return (
確認
PHOTO
{[["カテゴリー","道路・舗装"],["場所","神領 大埜地 299-1 付近"],["内容","歩道が陥没している。通行注意。"],["記録者","あなた"]].map((r,i)=>(
{r[0]}{r[1]}
))}
この内容で送信する
); } function Sc_Done() { return (

記録を送信しました

あなたの気づきが、まちの記録になりました。
担当者が確認し、対応状況をお知らせします。

REPORT #2025-0418
ホームに戻る
); } function ApTabbar({ active }) { const tabs = [["⌂","ホーム"],["◎","マップ"],["+",""],["🔔","通知"],["☰","メニュー"]]; return (
{tabs.map((t,i)=>( {t[0]}{t[1]&&{t[1]}} ))}
); } const DEMO_SCREENS = { home: { name: "ホーム", Comp: Sc_Home, hotspots: [ { id:"h1", label:"記録する", x:6, y:84.5, w:88, h:7.2, target:"report", t:"slide-left", tip:"記録フローを開始" }, { id:"h2", label:"マップを開く", x:60, y:53.5, w:34, h:5, target:"map", t:"slide-left", tip:"地図で見る" }, ]}, map: { name: "マップ", Comp: Sc_Map, hotspots: [ { id:"m1", label:"戻る", x:4, y:5.5, w:16, h:5.5, target:"home", t:"slide-right", tip:"ホームへ" }, { id:"m2", label:"記録を開く", x:5, y:80, w:90, h:9, target:"confirm", t:"slide-left", tip:"この記録の詳細" }, ]}, report: { name: "記録 / カテゴリー", Comp: Sc_Report, hotspots: [ { id:"r0", label:"戻る", x:4, y:5.5, w:16, h:5.5, target:"home", t:"slide-right" }, { id:"r1", label:"次へ", x:6, y:88, w:88, h:7, target:"confirm", t:"slide-left", tip:"確認画面へ" }, ]}, confirm: { name: "確認", Comp: Sc_Confirm, hotspots: [ { id:"c0", label:"戻る", x:4, y:5.5, w:16, h:5.5, target:"report", t:"slide-right" }, { id:"c1", label:"送信する", x:6, y:88, w:88, h:7, target:"done", t:"scale", tip:"送信を確定" }, ]}, done: { name: "完了", Comp: Sc_Done, hotspots: [ { id:"d1", label:"ホームに戻る", x:18, y:78, w:64, h:7, target:"home", t:"fade", tip:"最初の画面へ" }, ]}, }; function GenericHotspotDemo({ config = {}, title = "Interactive Demo" }) { const screens = config.screens || []; const firstScreen = config.initial || (screens[0] && screens[0].id); const [screen, setScreen] = useState(firstScreen); const [hist, setHist] = useState([]); const [showSpots, setShowSpots] = useState(false); const [hint, setHint] = useState(true); const [full, setFull] = useState(false); useEffect(() => { const t = setTimeout(() => setHint(false), 3200); return () => clearTimeout(t); }, [screen]); useEffect(() => { if (!full) return; const onKey = (e) => { if (e.key === "Escape") setFull(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [full]); if (!screens.length) { return (
); } const cur = screens.find(s => s.id === screen) || screens[0]; const navigate = (target) => { if (!target || !screens.some(s => s.id === target)) return; setHist(h => [...h, cur.id]); setScreen(target); setHint(false); }; const back = () => { if (!hist.length) return; const prev = hist[hist.length - 1]; setHist(h => h.slice(0, -1)); setScreen(prev); }; const reset = () => { setHist([]); setScreen(firstScreen); setHint(true); }; return (
{full &&
setFull(false)}>
}
INTERACTIVE DEMO {cur.name || title}
{cur.image ? {cur.name : }
{(cur.hotspots || []).map(h => ( ))}
{showSpots ? "ハイライト中 — タップで画面遷移します" : "画面をタップして操作できます。◎ で操作箇所を表示。"} {screens.length} SCREENS
); } function InteractiveDemo({ initial = "home", config, title }) { if (config && (config.type === "imageHotspots" || config.type === "flow")) { return ; } const [screen, setScreen] = useState(initial); const [hist, setHist] = useState([]); const [anim, setAnim] = useState(""); const [showSpots, setShowSpots] = useState(false); const [hint, setHint] = useState(true); const [device, setDevice] = useState("phone"); const [full, setFull] = useState(false); const stageRef = useRef(null); useEffect(() => { const t = setTimeout(() => setHint(false), 3200); return () => clearTimeout(t); }, [screen]); useEffect(() => { if (!full) return; const onKey = (e) => { if (e.key === "Escape") setFull(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [full]); const navigate = (target, t) => { setHist(h => [...h, screen]); setAnim(t || "fade"); setScreen(target); setHint(false); }; const back = () => { if (!hist.length) return; const prev = hist[hist.length - 1]; setHist(h => h.slice(0, -1)); setAnim("slide-right"); setScreen(prev); }; const reset = () => { setHist([]); setAnim("fade"); setScreen(initial); setHint(true); }; const cur = DEMO_SCREENS[screen]; const Comp = cur.Comp; const frame = (
{device === "desktop" &&
machilog.app
}
{cur.hotspots.map(h => ( ))}
); return (
{full &&
setFull(false)}>
}
INTERACTIVE DEMO {cur.name}
{frame}
{showSpots ? "ハイライト中 — タップで画面遷移します" : "画面をタップして操作できます。◎ で操作箇所を表示。"} {Object.keys(DEMO_SCREENS).length} SCREENS
); } Object.assign(window, { InteractiveDemo, GenericHotspotDemo, DEMO_SCREENS });