// Sygnal app mockups — recreated to match the latest actual UI at sygnal-main/frontend.
// Nav, copy, components, and color tokens all follow the real frontend.
/* ───────── Icons (lucide-react style: stroke = currentColor) ───────── */
const Icon = ({ d, size = 15, fill = "none", strokeWidth = 1.8, viewBox = "0 0 24 24", children }) => (
);
const IconDatabase = (p) => ;
const IconList = (p) => ;
const IconSend = (p) => ;
const IconTrendingUp = (p) => ;
const IconSparkles = (p) => ;
const IconBan = (p) => ;
const IconSettings = (p) => ;
const IconBuilding = (p) => ;
const IconCheck = (p) => ;
const IconCheckCircle = (p) => ;
const IconXCircle = (p) => ;
const IconAlertTriangle = (p) => ;
const IconClock = (p) => ;
const IconShield = (p) => ;
const IconSearch = (p) => ;
const IconFilter = (p) => ;
const IconChevronRight = (p) => ;
const IconChevronDown = (p) => ;
const IconPlay = (p) => ;
const IconPause = (p) => ;
const IconArrowRight = (p) => ;
const IconLink = (p) => ;
const IconCursor = (p) => ;
const IconCalendar = (p) => ;
const IconUser = (p) => ;
const IconTarget = (p) => ;
const IconHelp = (p) => ;
const IconExternal = (p) => ;
const IconDot = ({ color = "#16A34A" }) => (
);
/* ───────── Sygnal brand mark (3 vertical bars — matches real frontend) ───────── */
const Mark = ({ scale = 1 }) => (
);
/* ───────── Generic window chrome ─────────
Renders at a fixed PC size (1440x900) inside a landscape frame that is
scaled down to fit its container, so the screenshot keeps a real
desktop-browser aspect ratio instead of going portrait. */
const WinChrome = ({ addr = "app.sygnal.jp", children }) => (
);
/* ───────── Shared sidebar (matches layout.tsx menuItems) ───────── */
const Sidebar = ({ active = "campaigns" }) => {
const items = [
{ key: "database", label: "企業データベース", Ic: IconDatabase },
{ key: "lists", label: "リスト管理", Ic: IconList },
{ key: "campaigns", label: "案件管理", Ic: IconSend },
{ key: "sales-management", label: "営業管理", Ic: IconTrendingUp },
{ key: "insights", label: "AI分析", Ic: IconSparkles },
{ key: "ng-companies", label: "NG企業", Ic: IconBan },
{ key: "settings", label: "設定", Ic: IconSettings },
];
return (
);
};
/* ───────── shared styles ───────── */
const panel = { border: "1px solid #E5E5E5", borderRadius: 8, background: "#fff", overflow: "hidden" };
const panelHead = { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "11px 14px", borderBottom: "1px solid #f0f0f0", background: "#fff" };
const th = { padding: "8px 14px", fontWeight: 400, color: "#666", fontSize: 11 };
const td = { padding: "10px 14px", verticalAlign: "middle" };
const btnGhost = { display: "inline-flex", alignItems: "center", gap: 6, padding: "6px 10px", borderRadius: 4, border: "1px solid #E5E5E5", background: "#fff", fontSize: 11.5, color: "#666", cursor: "default" };
const btnPrimary = { ...btnGhost, background: "#111", color: "#fff", borderColor: "#111" };
/* ───────── Status badge (matches campaign-status.ts) ───────── */
const StatusBadge = ({ status }) => {
const map = {
"実行中": { bg: "#F0FDF4", fg: "#16A34A", border: "#D1FAE5" },
"一時停止": { bg: "#FFFBEB", fg: "#D97706", border: "#FDE68A" },
"完了": { bg: "#F5F5F5", fg: "#666", border: "#E5E5E5" },
"待機": { bg: "#EFF6FF", fg: "#2563EB", border: "#DBEAFE" },
"下書き": { bg: "#F5F5F5", fg: "#999", border: "#E5E5E5" },
"予約済み": { bg: "#EFF6FF", fg: "#2563EB", border: "#DBEAFE" },
};
const c = map[status] || map["完了"];
return (
{status === "実行中" && }
{status}
);
};
/* ============================================================
1) HERO — Campaign Detail (案件詳細) in running state
============================================================ */
const HeroCampaignDetail = () => {
// Match the actual campaign-detail UI: title row, tabs (成功/失敗/スキップ/AI生成結果/タイムライン), success rate progress
const total = 1284, success = 942, failed = 218, skipped = 47;
const sent = success + failed + skipped;
const pct = (n) => ((n / total) * 100).toFixed(1);
const sentPct = ((sent / total) * 100).toFixed(0);
// Recent send history
const rows = [
{ n: "株式会社グランドワークス", url: "grandworks.co.jp", at: "16:42", state: "success" },
{ n: "サンライズ工業株式会社", url: "sunrise-ind.jp", at: "16:40", state: "success" },
{ n: "Helix Robotics株式会社", url: "helix-robotics.jp", at: "16:38", state: "clicked" },
{ n: "ミライ食品株式会社", url: "mirai-foods.co.jp", at: "16:36", state: "failed" },
{ n: "株式会社オリオン物流", url: "orion-logistics.jp", at: "16:34", state: "skipped" },
{ n: "アクシス精機株式会社", url: "axis-seiki.co.jp", at: "16:32", state: "success" },
{ n: "Crest Medical株式会社", url: "crestmed.jp", at: "16:30", state: "success" },
];
const stateChip = (s) => {
if (s === "success") return 成功;
if (s === "clicked") return クリック;
if (s === "failed") return 失敗;
if (s === "skipped") return スキップ;
return null;
};
// Bars data
const bars = [12, 22, 31, 38, 42, 36, 44, 51, 48, 43, 38, 32, 24, 14];
const maxBar = Math.max(...bars);
return (
{/* Breadcrumb */}
案件管理2026年Q2 製造業アタック
{/* Header */}
2026年Q2 製造業アタック
商材: 自社サービスA
·
リスト: 製造業_関東_50-300名(1,284社)
·
開始: 4/12 09:00
{/* KPI strip — matches campaign-detail style: 進捗 + 件数 ribbon */}
{/* Progress stacked bar */}
{/* Tabs — matches latest campaign-detail.tsx */}
{["送信文面", "成功の詳細", "失敗の詳細", "スキップの詳細", "クリック企業"].map((t, i) => (
{t}
))}
{/* Two-column area */}
{/* Send volume chart */}
{bars.map((b, i) => = 12 ? .4 : 1 }} />)}
03:0009:0015:0021:00
{/* Failure reason breakdown */}
{/* Send history table */}
| 企業 | ドメイン | 送信時刻 | 結果 |
{rows.map((r, i) => (
|
{r.n}
|
{r.url} |
{r.at} |
{stateChip(r.state)} |
))}
);
};
/* Small atoms */
const chip = { display: "inline-flex", alignItems: "center", gap: 5, fontSize: 11, padding: "3px 8px", borderRadius: 4, background: "#F5F5F5", color: "#666", fontWeight: 400, border: "1px solid #E5E5E5" };
const Kpi = ({ label, value, sub, tone }) => {
const color = tone === "green" ? "#16A34A" : tone === "red" ? "#DC2626" : tone === "amber" ? "#D97706" : "#111";
return (
);
};
const ReasonRow = ({ label, v, max, tone, last }) => (
);
/* ============================================================
2) DATABASE — 企業データベース
============================================================ */
const DatabaseMock = () => {
const companies = [
{ n: "株式会社グランドワークス", ind: "建設業", area: "東京都港区", emp: "120名", rev: "23億", status: "未送信" },
{ n: "サンライズ工業株式会社", ind: "製造業 / 金属加工", area: "神奈川県川崎市", emp: "84名", rev: "12億", status: "送信済" },
{ n: "Helix Robotics株式会社", ind: "製造業 / 産業機械", area: "東京都品川区", emp: "240名", rev: "58億", status: "クリック" },
{ n: "ミライ食品株式会社", ind: "製造業 / 食品", area: "埼玉県さいたま市", emp: "67名", rev: "8億", status: "NG企業", ng: true },
{ n: "Crest Medical株式会社", ind: "製造業 / 医療機器", area: "東京都中央区", emp: "310名", rev: "94億", status: "未送信" },
{ n: "株式会社ノーザンライト", ind: "小売業", area: "北海道札幌市", emp: "52名", rev: "6億", status: "未送信" },
{ n: "アクシス精機株式会社", ind: "製造業 / 精密機械", area: "長野県諏訪市", emp: "180名", rev: "31億", status: "送信済" },
{ n: "株式会社トウカイ電子", ind: "製造業 / 電子部品", area: "愛知県名古屋市", emp: "210名", rev: "44億", status: "クリック" },
{ n: "明和樹脂工業株式会社", ind: "製造業 / 樹脂成形", area: "大阪府東大阪市", emp: "96名", rev: "15億", status: "未送信" },
{ n: "株式会社ブルーポート物流", ind: "運輸業", area: "兵庫県神戸市", emp: "148名", rev: "27億", status: "送信済" },
{ n: "日峰メディカル株式会社", ind: "製造業 / 医療機器", area: "福岡県福岡市", emp: "275名", rev: "71億", status: "未送信" },
{ n: "株式会社セイワ製作所", ind: "製造業 / 金属加工", area: "静岡県浜松市", emp: "63名", rev: "9億", status: "未送信" },
{ n: "クロスフィールド株式会社", ind: "情報通信業", area: "東京都渋谷区", emp: "88名", rev: "18億", status: "送信済" },
];
return (
企業データベース
日々更新される法人データベースから市場を条件抽出
{/* Filter chips */}
+ 条件を追加
{/* Search and results meta */}
{/* Table */}
|
企業名 |
業種 |
所在地 |
従業員 |
売上高 |
送信状況 |
{companies.map((c, i) => (
|
{c.n}
|
{c.ind} |
{c.area} |
{c.emp} |
{c.rev} |
{c.status === "クリック" ? クリック
: c.status === "送信済" ? 送信済
: c.status === "NG企業" ? NG企業
: 未送信}
|
))}
);
};
const FilterChip = ({ label, value }) => (
{label}:
{value}
);
/* ============================================================
3) AI MESSAGE — Phase 2 文面作成(AI文面生成 / 手動入力)
Matches the actual campaign-wizard/phase3-message.tsx UI:
- messageMode radio: AI文面生成 / 手動入力
- 文字数制限に応じて2パターン生成 checkbox
- トラッキングURL選択 (LP / 日程調整 / 資料DL) toggle chips
- generated subject / body preview with 500文字未満用 / 500文字以上用 tabs
============================================================ */
const AIMessageMock = () => {
return (
案件作成Phase 2文面作成
{/* Wizard step indicator — 3 phases (matches latest wizard) */}
{[
{ n: 1, t: "基本情報", done: true },
{ n: 2, t: "文面作成", done: false, active: true },
{ n: 3, t: "送信設定", done: false },
].map((s, i) => (
{s.done ? : s.n}
{s.t}
{i < 2 && }
))}
{/* Two columns so the screen fits a real 1440x900 desktop window */}
{/* LEFT — mode + generation settings */}
文面生成方法
AI文面生成
送信先に合わせた文面をAIが作成します。
手動入力
自分で作成した文面を全企業に送信します。
{/* AI generation settings */}
AI文面生成
AIが文面を作成します
文字数制限に応じて2パターン生成
500文字未満用と500文字以上用の2種類の文面を生成します
{/* RIGHT — generated content */}
生成完了 - 内容を確認・編集してください
💡500文字未満の問い合わせフォームで使用されます
製造業のご担当者様
突然のご連絡失礼いたします。
貴社ホームページを拝見し、ご連絡いたしました。
営業リードを増やしたい中堅製造業様向けに、
AIによるフォーム営業の自動化サービスをご案内しています。
同規模10社で平均3.4倍の問い合わせ獲得を実現しています。
ご興味があれば15分でご案内可能です。
▼日程調整はこちら
https://timerex.net/s/example
418 / 500文字推奨
OK
各企業の問い合わせフォームの文字数制限に応じて、最適な文面が自動選択されます
);
};
/* TrackUrlRow — tracking URL toggle chips inside AI panel */
const TrackUrlRow = ({ icon, label, items }) => (
{icon}
{label}
{items.map((it, i) => (
{it.name}
))}
);
/* ============================================================
4) INSIGHTS — AI分析(攻めるべき市場 × 刺さる訴求 × 推奨アクション)
============================================================ */
const InsightsMock = () => {
// Recommendation badge config — matches insights-dashboard.tsx RECOMMENDATION_CONFIG
const recBadge = (r) => {
const m = {
expand: { l: "伸ばす", bg: "#F0FDF4", fg: "#10B981", border: "#BBF7D0" },
maintain: { l: "維持", bg: "#EFF6FF", fg: "#3B82F6", border: "#BFDBFE" },
reduce: { l: "絞る", bg: "#FEF2F2", fg: "#EF4444", border: "#FECACA" },
test: { l: "テスト", bg: "#FEF3C7", fg: "#F59E0B", border: "#FDE68A" },
}[r];
return {m.l};
};
const priorityBadge = (p) => {
const m = {
high: { l: "高", bg: "#FEF2F2", fg: "#EF4444" },
medium: { l: "中", bg: "#FEF3C7", fg: "#F59E0B" },
low: { l: "低", bg: "#F0FDF4", fg: "#10B981" },
}[p];
return {m.l};
};
const segments = [
{ seg: "医療機器", success: 96, click: 12, score: 87, rec: "expand" },
{ seg: "産業機械", success: 94, click: 9, score: 76, rec: "expand" },
{ seg: "食品製造", success: 89, click: 7, score: 64, rec: "maintain" },
{ seg: "建設業", success: 82, click: 5, score: 52, rec: "maintain" },
{ seg: "小売業", success: 71, click: 3, score: 38, rec: "reduce" },
];
const patterns = [
{
cr: 14, count: 6, top: true,
flow: [
{ k: "objective", v: "日程調整誘導" },
{ k: "problem", v: "営業リード不足" },
{ k: "value", v: "数字で効果実証" },
{ k: "trust", v: "導入120社" },
{ k: "cta", v: "15分カレンダー" },
],
bd: [["医療機器", 18], ["産業機械", 12]],
},
{
cr: 9, count: 8, top: true,
flow: [
{ k: "objective", v: "資料DL誘導" },
{ k: "problem", v: "人手不足" },
{ k: "value", v: "工数1/10" },
{ k: "cta", v: "資料DL" },
],
bd: [["産業機械", 11], ["建設業", 6]],
},
];
const actions = [
{ cat: "market", seg: "業種 / 医療機器", title: "医療機器セグメントを次回優先で抽出", reason: "成功率96%・有望度87と他セグメント比2.1倍。300社規模で集中アタックを推奨。", pri: "high" },
{ cat: "appeal", seg: "訴求 / 数字実証 + 15分CTA", title: "件名に「導入事例の数字」を含める運用に統一", reason: "数字訴求パターンのクリック率が他比 +3.4pt。", pri: "medium" },
{ cat: "combined", seg: "送信時間 / 火・水 10:00-11:30", title: "送信ウィンドウを午前帯に絞る", reason: "クリック発生時刻の62%が10:00〜11:30台に集中。", pri: "medium" },
];
const catIc = (c) => c === "market" ? : c === "appeal" ? : ;
const tagBg = (k) => ({
objective: { bg: "#FEF3C7", fg: "#92400E" },
problem: { bg: "#FEE2E2", fg: "#991B1B" },
value: { bg: "#DBEAFE", fg: "#1D4ED8" },
trust: { bg: "#D1FAE5", fg: "#065F46" },
cta: { bg: "#E9D5FF", fg: "#6D28D9" },
}[k]);
return (
AI分析
送信結果をAIが分析し、効果的な営業戦略を提案します
{/* KPI cards */}
} label="送信成功率" value="89%" color="#16A34A" />
} label="クリック率" value="8%" color="#3B82F6" />
} label="案件数" value="14" color="#111" />
} label="最終更新" value="5/12 14:32" color="#666" small />
{/* Two columns: 攻めるべき市場 / 刺さる訴求 */}
{/* 攻めるべき市場 */}
攻めるべき市場
| セグメント |
成功率 |
クリック率 |
有望度 |
推奨 |
{segments.map((s, i) => (
| {s.seg} |
{s.success}% |
{s.click}% |
{s.score} |
{recBadge(s.rec)} |
))}
AI分析: 医療機器・産業機械セグメントの有望度が突出。次回案件はこの2セグメントへ集中投下を推奨します。
{/* 刺さる訴求 */}
刺さる訴求
{patterns.map((p, i) => (
#{i + 1} クリック率 {p.cr}% / {p.count}キャンペーン
{p.flow.map((f, j) => (
{j > 0 && →}
{f.v}
))}
{p.bd.map(([seg, cr], k) => (
{seg}: {cr}%
))}
))}
{/* 今週の推奨アクション */}
今週の推奨アクション
{actions.map((a, i) => (
{catIc(a.cat)}{a.seg}
{priorityBadge(a.pri)}
{a.title}
{a.reason}
))}
);
};
const KpiInsight = ({ ic, label, value, color, small }) => (
);
/* ============================================================
5) SALES — 営業管理(カンバン)
============================================================ */
const SalesMock = () => {
const cols = [
{ stage: "クリック", count: 14, color: "#3B82F6", cards: [
{ n: "Crest Medical株式会社", ind: "製造業 / 医療機器", at: "8分前", asg: "田中" },
{ n: "サンライズ工業株式会社", ind: "製造業 / 金属加工", at: "32分前", asg: "佐藤" },
{ n: "アクシス精機株式会社", ind: "製造業 / 精密機械", at: "1時間前", asg: "田中" },
{ n: "株式会社トウカイ電子", ind: "製造業 / 電子部品", at: "3時間前", asg: "鈴木" },
{ n: "日峰メディカル株式会社", ind: "製造業 / 医療機器", at: "5時間前", asg: "佐藤" },
{ n: "株式会社ヤマト計測機器", ind: "製造業 / 精密機械", at: "本日 09:12", asg: "未割当" },
]},
{ stage: "アプローチ中", count: 6, color: "#F59E0B", cards: [
{ n: "株式会社ノーザンライト", ind: "小売業", at: "本日 11:30", asg: "佐藤" },
{ n: "ミライ食品株式会社", ind: "製造業 / 食品", at: "昨日", asg: "田中" },
{ n: "クロスフィールド株式会社", ind: "情報通信業", at: "2日前", asg: "鈴木" },
{ n: "三葉化成工業株式会社", ind: "製造業 / 化学", at: "3日前", asg: "田中" },
]},
{ stage: "商談化", count: 3, color: "#8B5CF6", cards: [
{ n: "Helix Robotics株式会社", ind: "製造業 / 産業機械", at: "2日前", asg: "田中", amount: "¥1,800,000" },
{ n: "明和樹脂工業株式会社", ind: "製造業 / 樹脂成形", at: "4日前", asg: "佐藤", amount: "¥960,000" },
{ n: "株式会社ブルーポート物流", ind: "運輸業", at: "1週間前", asg: "鈴木", amount: "¥1,200,000" },
]},
{ stage: "成約", count: 2, color: "#10B981", cards: [
{ n: "株式会社グランドワークス", ind: "建設業", at: "1週間前", asg: "佐藤", amount: "¥2,400,000" },
{ n: "株式会社アペックス空調", ind: "建設業 / 設備", at: "3週間前", asg: "田中", amount: "¥1,650,000" },
]},
];
return (
営業管理
クリックのあった企業の営業進捗を、カンバンで一元管理
{/* Kanban */}
{cols.map((c, i) => (
{c.cards.map((card, j) => (
{card.n}
{card.ind}
{card.at}
{card.asg}
{card.amount && (
{card.amount}
)}
))}
))}
);
};
/* ───────── Render hero into slot ───────── */
const heroSlot = document.getElementById("hero-window-slot");
if (heroSlot) ReactDOM.createRoot(heroSlot).render();
// Expose to sections script (precheck removed — no longer in product)
window.SygnalMocks = { DatabaseMock, AIMessageMock, InsightsMock, SalesMock, HeroCampaignDetail };
/* ───────── Scale each fixed 1440x900 window to fit its container ───────── */
(function () {
const W = 1440;
const fit = () => document.querySelectorAll('.mock-frame').forEach(f => {
const inner = f.querySelector('.mock-scale');
if (inner && f.clientWidth) inner.style.transform = 'scale(' + (f.clientWidth / W) + ')';
});
const mo = new MutationObserver(fit);
mo.observe(document.body, { childList: true, subtree: true });
window.addEventListener('resize', fit);
fit();
[50, 200, 600, 1500].forEach(t => setTimeout(fit, t));
})();