Eight-card explainer covering Business-as-Code philosophy, the five element types (Flow/Data/View/Rule/Version), the DEFINE/VERSION/DEPLOY/ EXECUTE lifecycle, hubs, work items, EA2 graph storage, and the Pi agent. Sourced from FlowMaster Element Architecture v2.1 + the EA2 collection/edge contracts recalled from Hindsight today. New 'What is FlowMaster?' chip on Landing routes here.
165 lines
7.2 KiB
TypeScript
165 lines
7.2 KiB
TypeScript
// Cinematic landing scene: brand, scenarios pick, start tour CTA.
|
|
import { motion } from "framer-motion";
|
|
import { useApp } from "../state/store";
|
|
import { liveMeta } from "../data/scenarios";
|
|
import { Sparkles, Arrow, Cmd, Bot, Pulse, Sun, Moon } from "../components/icons";
|
|
|
|
export default function Landing() {
|
|
const setScene = useApp((s) => s.setScene);
|
|
const setScenarioId = useApp((s) => s.setScenarioId);
|
|
const setCmdOpen = useApp((s) => s.setCmdOpen);
|
|
const setConsoleOpen = useApp((s) => s.setConsoleOpen);
|
|
const scenarios = useApp((s) => s.scenarios);
|
|
const mode = useApp((s) => s.mode);
|
|
const setMode = useApp((s) => s.setMode);
|
|
const refreshLive = useApp((s) => s.refreshLive);
|
|
const liveLoading = useApp((s) => s.liveLoading);
|
|
const liveTotals = useApp((s) => s.liveTotals);
|
|
|
|
return (
|
|
<div className="landing">
|
|
<div className="landing-bg" aria-hidden />
|
|
<div className="landing-grid" aria-hidden />
|
|
|
|
<header className="landing-top">
|
|
<div className="brand-lock">
|
|
<span className="brand-mark" />
|
|
<span className="brand-name">FlowMaster</span>
|
|
<span className="brand-divider" />
|
|
<span className="brand-sub">Mission Control</span>
|
|
</div>
|
|
<div className="landing-top-actions">
|
|
<ThemeToggle />
|
|
<button className="link-btn" onClick={() => setCmdOpen(true)}>
|
|
<Cmd size={13} /> Command <kbd>⌘K</kbd>
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<main className="landing-main">
|
|
<motion.div
|
|
className="landing-hero"
|
|
initial={{ opacity: 0, y: 16 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.45 }}
|
|
>
|
|
<span className="hero-eyebrow"><Sparkles size={13} /> Business-as-code · Mission Control</span>
|
|
<h1 className="hero-title">
|
|
Every process. <span className="hl">One control surface.</span>
|
|
</h1>
|
|
<p className="hero-sub">
|
|
FlowMaster turns the operational map of a company into living,
|
|
typed processes — backed by humans, agents, and rules — and gives you
|
|
one control surface to drive them. Every procurement, finance, people,
|
|
and service workflow runs end-to-end through EA2 on{" "}
|
|
<span className="mono">{liveMeta.fetchedFrom?.replace("https://", "") ?? "the FlowMaster backend"}</span>.
|
|
</p>
|
|
<div className="hero-actions">
|
|
<button className="btn btn-primary btn-lg" onClick={() => setScene("mission")}>
|
|
<Arrow size={14} /> Enter Mission Control
|
|
</button>
|
|
<button
|
|
className={`btn btn-ghost btn-lg${mode === "live" ? " is-on" : ""}`}
|
|
onClick={() => mode === "live" ? refreshLive() : setMode("live")}
|
|
disabled={liveLoading}
|
|
title={mode === "live" ? "Re-fetch live scenarios from demo.flow-master.ai" : "Toggle between bundled snapshot and a live in-browser fetch"}
|
|
>
|
|
{liveLoading ? <span className="spin" /> : <Pulse size={13} />}
|
|
{mode === "live" ? "Live · refresh" : "Go live"}
|
|
</button>
|
|
<button className="btn btn-ghost btn-lg" onClick={() => { setScene("studio"); }}>
|
|
<Sparkles size={13} /> Design a process
|
|
</button>
|
|
<button className="btn btn-ghost btn-lg" onClick={() => setConsoleOpen(true)}>
|
|
<Bot size={13} /> Open live console
|
|
</button>
|
|
</div>
|
|
|
|
<div className="hero-hub-row">
|
|
<button className="hub-chip" onClick={() => setScene("hub-procurement")}>Procurement Hub</button>
|
|
<button className="hub-chip" onClick={() => setScene("hub-hr")}>People Hub</button>
|
|
<button className="hub-chip" onClick={() => setScene("hub-it")}>IT Hub</button>
|
|
<button className="hub-chip" onClick={() => setScene("geo-attendance")}>Attendance Map</button>
|
|
<button className="hub-chip" onClick={() => setScene("agent")}>Talk to Pi</button>
|
|
<button className="hub-chip" onClick={() => setScene("chat")}>Team Chat</button>
|
|
<button className="hub-chip" onClick={() => setScene("explainer")}>What is FlowMaster?</button>
|
|
</div>
|
|
|
|
<div className="hero-stats">
|
|
<div className="stat">
|
|
<Pulse size={12} />
|
|
<span className="stat-v mono">{liveTotals?.workItems ?? liveMeta.workItems}</span>
|
|
<span className="stat-l">{mode === "live" ? "live work items (now)" : "snapshot work items"}</span>
|
|
</div>
|
|
<div className="stat">
|
|
<span className="stat-v mono">{liveTotals?.distinctDefs ?? liveMeta.distinctDefs}</span>
|
|
<span className="stat-l">process definitions</span>
|
|
</div>
|
|
<div className="stat">
|
|
<Bot size={12} />
|
|
<span className="stat-v mono">{scenarios.length}</span>
|
|
<span className="stat-l">scenarios in catalog</span>
|
|
</div>
|
|
<div className="stat">
|
|
<span className={`mode-pill mode-${mode}`}>{mode === "live" ? "LIVE" : "SNAPSHOT"}</span>
|
|
<span className="stat-l">data mode</span>
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
<motion.div
|
|
className="landing-cards"
|
|
initial={{ opacity: 0, y: 18 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.12, duration: 0.45 }}
|
|
>
|
|
{scenarios.map((s) => (
|
|
<button
|
|
key={s.id}
|
|
className="sc-card"
|
|
style={{ ["--sc-accent" as string]: s.family.accent }}
|
|
onClick={() => { setScenarioId(s.id); setScene("mission"); }}
|
|
>
|
|
<div className="sc-card-top">
|
|
<span className="sc-card-mark" />
|
|
<span className={`tag ${s.live ? "tag-live" : "tag-syn"}`}>{s.live ? "live" : "blueprint"}</span>
|
|
</div>
|
|
<h3 className="sc-card-title">{s.family.label}</h3>
|
|
<p className="sc-card-sub">{s.family.subtitle}</p>
|
|
<div className="sc-card-meta">
|
|
<span>{s.steps.length} steps</span>
|
|
<span>·</span>
|
|
<span>{s.queue.length} cases</span>
|
|
<span>·</span>
|
|
<span className="mono">{s.version}</span>
|
|
</div>
|
|
<div className="sc-card-cta">Open <Arrow size={12} /></div>
|
|
</button>
|
|
))}
|
|
</motion.div>
|
|
</main>
|
|
|
|
<footer className="landing-foot">
|
|
<span className="foot-eyebrow">FlowMaster · Mission Control · live operator surface for FlowMaster processes</span>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function ThemeToggle() {
|
|
const theme = useApp((s) => s.theme);
|
|
const setTheme = useApp((s) => s.setTheme);
|
|
const isDark = theme === "dark";
|
|
return (
|
|
<button
|
|
className="link-btn theme-toggle"
|
|
onClick={() => setTheme(isDark ? "light" : "dark")}
|
|
title={isDark ? "Switch to light theme" : "Switch to dark theme"}
|
|
aria-label="Toggle theme"
|
|
>
|
|
{isDark ? <Sun size={13} /> : <Moon size={13} />}
|
|
<span className="theme-toggle-label">{isDark ? "LIGHT" : "DARK"}</span>
|
|
</button>
|
|
);
|
|
}
|