// Data Mirai — Landing page
const { useState, useEffect, useRef, useMemo, useCallback } = React;
// ─── Scroll Reveal ──────────────────────────────────────────────────────────
function useReveal(deps) {
useEffect(() => {
const els = document.querySelectorAll('.reveal:not(.in)');
if (!els.length) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('in');
observer.unobserve(entry.target);
}
});
}, { threshold: 0.1, rootMargin: '0px 0px -60px 0px' });
els.forEach((el) => observer.observe(el));
return () => observer.disconnect();
}, deps);
}
// ─── Typed Pitch ────────────────────────────────────────────────────────────
function TypedPitch({ pre, strong, post, end }) {
const [typed, setTyped] = useState(0);
useEffect(() => {
if (typed < strong.length) {
const t = setTimeout(() => setTyped(typed + 1), 40 + Math.random() * 30);
return () => clearTimeout(t);
}
}, [typed, strong]);
const done = typed >= strong.length;
return (
{pre}{' '}
{strong.slice(0, typed)}{!done && }
{post}{end}
);
}
// ─── Hero Orbit ─────────────────────────────────────────────────────────────
function OrbitChip({ x, y, kind, children, big, small, agent }) {
const left = (x / 560) * 100;
const top = (y / 560) * 100;
const palette = {
core: { c: '#fff', bg: 'linear-gradient(135deg, rgba(234,88,12,0.25), rgba(147,51,234,0.35))', border: 'rgba(255,255,255,0.18)' },
trigger: { c: 'var(--c-trigger)', bg: 'rgba(22,163,74,0.12)', border: 'rgba(22,163,74,0.35)' },
logic: { c: 'var(--c-logic)', bg: 'rgba(37,99,235,0.12)', border: 'rgba(37,99,235,0.35)' },
ai: { c: 'var(--c-ai)', bg: 'rgba(147,51,234,0.12)', border: 'rgba(147,51,234,0.35)' },
data: { c: 'var(--c-data)', bg: 'rgba(234,88,12,0.12)', border: 'rgba(234,88,12,0.35)' },
agent: { c: 'var(--c-agent)', bg: 'rgba(245,158,11,0.12)', border: 'rgba(245,158,11,0.35)' },
output: { c: 'var(--c-output)', bg: 'rgba(236,72,153,0.12)', border: 'rgba(236,72,153,0.35)' },
};
const p = palette[kind] || palette.core;
const style = {
position: 'absolute',
left: `${left}%`,
top: `${top}%`,
transform: 'translate(-50%, -50%)',
background: p.bg,
color: p.c,
border: `1px solid ${p.border}`,
backdropFilter: 'blur(14px)',
borderRadius: big ? '14px' : '10px',
padding: big ? '14px 18px' : (small ? '5px 9px' : '7px 11px'),
fontFamily: 'var(--font-mono)',
fontSize: big ? '12px' : (small ? '10px' : '11px'),
letterSpacing: '0.04em',
whiteSpace: 'nowrap',
boxShadow: big
? '0 20px 60px -20px rgba(147, 51, 234, 0.6), 0 0 0 4px rgba(167,139,250,0.05)'
: `0 0 20px ${p.c}33, 0 0 0 1px ${p.border}`,
zIndex: big ? 5 : (agent ? 4 : 3),
minWidth: big ? '120px' : 'auto',
textAlign: big ? 'center' : 'left',
lineHeight: big ? '1.2' : '1.3',
};
return (
{kind !== 'core' && (
)}
{children}
);
}
function HeroOrbit({ t }) {
const wrapRef = useRef(null);
const [time, setTime] = useState(0);
useEffect(() => {
let raf, start = performance.now();
const loop = (now) => { setTime((now - start) / 1000); raf = requestAnimationFrame(loop); };
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, []);
const S = 560, cx = S / 2, cy = S / 2;
const agentR = 140, planetR = 215;
// Deterministic star field (like tucania-engine)
const stars = useMemo(() => Array.from({ length: 110 }, (_, i) => ({
x: (i * 137.5 + 23) % S, y: (i * 89.7 + 17) % S,
r: 0.3 + (i % 5) * 0.25, op: 0.08 + (i % 7) * 0.05,
})), []);
const COLORS = { agent: '#f59e0b', output: '#ec4899', logic: '#2563eb', ai: '#9333ea', data: '#ea580c', trigger: '#16a34a' };
const agentDefs = [
{ name: t.orbit.n1, kind: 'agent', color: COLORS.agent },
{ name: t.orbit.n2, kind: 'output', color: COLORS.output },
{ name: t.orbit.n3, kind: 'logic', color: COLORS.logic },
{ name: t.orbit.n4, kind: 'ai', color: COLORS.ai },
];
const planetDefs = [
{ name: t.orbit.p1, kind: 'data', color: COLORS.data },
{ name: t.orbit.p2, kind: 'trigger', color: COLORS.trigger },
{ name: t.orbit.p3, kind: 'data', color: COLORS.data },
{ name: t.orbit.p4, kind: 'output', color: COLORS.output },
];
const agents = agentDefs.map((a, i) => {
const angle = (Math.PI * 2 * i) / 4 + Math.PI * 0.25 + time * 0.06;
return { ...a, x: cx + Math.cos(angle) * agentR, y: cy + Math.sin(angle) * agentR };
});
const planets = planetDefs.map((p, i) => {
const angle = (Math.PI * 2 * i) / 4 + time * 0.035;
return { ...p, x: cx + Math.cos(angle) * planetR, y: cy + Math.sin(angle) * planetR };
});
return (
{/* Nebula glow */}
{/* HTML glass chips */}
{t.orbit.core.split('\n').map((s,i) =>
{s}
)}
{agents.map((a) =>
{a.name})}
{planets.map((p) =>
{p.name})}
);
}
// ─── Graph Demo ─────────────────────────────────────────────────────────────
function kindColor(k) {
return {
trigger: '#16a34a', logic: '#2563eb', ai: '#9333ea',
data: '#ea580c', agent: '#f59e0b', output: '#ec4899',
}[k] || '#a78bfa';
}
function getLogMessage(node) {
const msgs = {
trigger: 'lead received · acme.co · n=1',
data: 'enriched 1 contact · 42 fields · 230ms',
logic: 'icp_score=83 · qualified=true',
ai: 'generated email · 142 tokens · $0.0008',
agent: 'astronaut Sales picked up · tools loaded',
output: 'draft → gmail · scheduled 09:30 PT',
};
return msgs[node.k] || 'tick';
}
function GraphDemo({ t }) {
const [running, setRunning] = useState(true);
const [activeIdx, setActiveIdx] = useState(0);
const [logs, setLogs] = useState([]);
const timerRef = useRef(null);
const nodes = t.demo.nodes;
const positions = [
{ x: 30, y: 60 },
{ x: 200, y: 30 },
{ x: 200, y: 200 },
{ x: 380, y: 60 },
{ x: 380, y: 230 },
{ x: 560, y: 150 },
];
const edges = [
[0, 1], [0, 2], [1, 3], [2, 4], [3, 5], [4, 5],
];
useEffect(() => {
if (!running) return;
timerRef.current = setInterval(() => {
setActiveIdx((i) => {
const next = (i + 1) % nodes.length;
const now = new Date();
const ts = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`;
setLogs((prev) => {
const node = nodes[next];
const newLog = { t: ts, tag: `[${node.k}]`, msg: getLogMessage(node) };
return [newLog, ...prev].slice(0, 18);
});
return next;
});
}, 1200);
return () => clearInterval(timerRef.current);
}, [running, nodes]);
useEffect(() => {
const now = new Date();
const ts = `${String(now.getHours()).padStart(2,'0')}:${String(now.getMinutes()).padStart(2,'0')}:${String(now.getSeconds()).padStart(2,'0')}`;
setLogs([
{ t: ts, tag: '[boot]', msg: 'engine v0.42.0 booted · 6 nodes · MIT', ok: true },
{ t: ts, tag: '[wsk]', msg: 'subscribed to @webhook/leads', ok: true },
]);
}, []);
const step = () => {
setActiveIdx((i) => (i + 1) % nodes.length);
};
return (
datamirai.app — {t.demo.crumb}
{t.demo.panels.nodes}
{['trigger','logic','ai','data','agent','output'].map((k) => (
{k}
))}
{nodes.map((n, i) => {
const p = positions[i];
const active = activeIdx === i;
return (
);
})}
{t.demo.panels.kpis}
{t.demo.kpi.map((k, i) => (
))}
{t.demo.panels.logs}
{logs.map((l, i) => (
{l.t}
{l.tag}
{l.msg}
))}
);
}
// ─── Shift Arrow ────────────────────────────────────────────────────────────
function ShiftArrow({ label }) {
return (
);
}
// ─── Step Illustrations ─────────────────────────────────────────────────────
function StepIllu({ n }) {
if (n === 0) {
return (
);
}
if (n === 1) {
return (
);
}
return (
);
}
// ─── Particles ──────────────────────────────────────────────────────────────
function Particles({ count }) {
const particles = useMemo(() => {
return Array.from({ length: count }, () => ({
left: Math.random() * 100,
top: 60 + Math.random() * 40,
delay: Math.random() * 12,
duration: 8 + Math.random() * 10,
size: 1 + Math.random() * 2,
}));
}, [count]);
return (
{particles.map((p, i) => (
))}
);
}
// ─── NavBar ─────────────────────────────────────────────────────────────────
function NavBar({ t, lang, theme, toggleLang, toggleTheme }) {
const [menuOpen, setMenuOpen] = useState(false);
useEffect(() => {
document.body.style.overflow = menuOpen ? 'hidden' : '';
return () => { document.body.style.overflow = ''; };
}, [menuOpen]);
return (
<>
{/* Desktop nav — floating glass bar */}
{/* Mobile trigger — floating pill top-left */}
{/* Mobile fullscreen overlay */}
未来
Data Mirai
setMenuOpen(false)}>{t.nav.cta}
>
);
}
// ─── Catalog ────────────────────────────────────────────────────────────────
function Catalog({ t }) {
const [active, setActive] = useState('all');
const items = t.catalog.items;
const visible = active === 'all' ? items : items.filter((it) => it.cat === active);
return (
{t.catalog.tag}
{t.catalog.title}
{t.catalog.lede}
{t.catalog.filters.map((f) => (
))}
{visible.map((it, i) => (
{it.dept}
{it.t}
{it.p}
{it.freq}
{t.catalog.saves_label} {it.saves}
))}
{t.catalog.cta_more}
);
}
// ─── Waitlist ───────────────────────────────────────────────────────────────
const WAITLIST_URL = 'https://script.google.com/macros/s/AKfycbx5PcBDn5FW1ABt4HKMl0HTdMjUKsQP1TA4Z2JHnB6aNYRcKg3fm4VKb-wGlYFjdRCk/exec';
function useSessionData() {
const start = useRef(Date.now());
const maxScroll = useRef(0);
useEffect(() => {
const onScroll = () => {
const depth = Math.round((window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100);
if (depth > maxScroll.current) maxScroll.current = depth;
};
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
return useCallback(() => {
const params = new URLSearchParams(window.location.search);
const w = window.innerWidth;
return {
referrer: document.referrer || 'direct',
utm_source: params.get('utm_source') || '',
utm_medium: params.get('utm_medium') || '',
utm_campaign: params.get('utm_campaign') || '',
device: w < 480 ? 'mobile' : w < 1024 ? 'tablet' : 'desktop',
screen: `${window.screen.width}x${window.screen.height}`,
browser_lang: navigator.language || '',
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || '',
scroll_depth: maxScroll.current + '%',
time_on_page: Math.round((Date.now() - start.current) / 1000),
};
}, []);
}
function Waitlist({ t, lang }) {
const [email, setEmail] = useState('');
const [hp, setHp] = useState('');
const [status, setStatus] = useState('idle');
const getSession = useSessionData();
const submit = (e) => {
e.preventDefault();
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return;
setStatus('sending');
const payload = {
email,
lang,
_hp: hp,
...getSession(),
};
fetch(WAITLIST_URL, {
method: 'POST',
body: JSON.stringify(payload),
})
.then(() => setStatus('done'))
.catch(() => setStatus('done'));
};
if (status === 'done') {
return {t.waitlist.success}
;
}
return (
);
}
// ─── App ────────────────────────────────────────────────────────────────────
function App() {
const [lang, setLang] = useState('es');
const [theme, setTheme] = useState('dark');
const [langFade, setLangFade] = useState(false);
const t = window.I18N[lang];
useEffect(() => {
document.documentElement.setAttribute('data-theme', theme);
}, [theme]);
useReveal([lang]);
const toggleLang = () => {
setLangFade(true);
setTimeout(() => {
setLang(lang === 'es' ? 'en' : 'es');
}, 50);
setTimeout(() => setLangFade(false), 360);
};
return (
{/* Background */}
{/* NAV — floating glass, logo centered */}
setTheme(theme === 'dark' ? 'light' : 'dark')} />
{/* HERO */}
{t.hero.eyebrow}
DATA
MIRAI
{/* MIRAI — storytelling */}
未来
/ mi·rai /
{t.mirai.meaning}
{/* PROBLEMA */}
{t.problema.tag}
{t.problema.title}
{t.problema.lede}
{t.problema.stats.map((s, i) => (
))}
{/* SHIFT */}
{t.shift.tag}
{t.shift.title}
{t.shift.before.label}
{t.shift.before.items.map((it, i) => (
- {it}
))}
{t.shift.after.label}
{t.shift.after.items.map((it, i) => (
- {it}
))}
{/* COMMAND CENTER */}
{t.panel.tag}
{t.panel.title}
{t.panel.lede}
{t.panel.columns.you.title.toUpperCase()}
{t.panel.columns.you.big}
{t.panel.columns.you.title}
{t.panel.columns.you.sub}
{t.panel.columns.you.desc}
{t.panel.columns.you.tags.map((tg, i) => - {tg}
)}
{t.panel.columns.agents.title.toUpperCase()}
{t.panel.columns.agents.big}
{t.panel.columns.agents.title}
{t.panel.columns.agents.sub}
{t.panel.columns.agents.desc}
{t.panel.columns.agents.chips.map((c, i) => (
{c.name}
))}
{t.panel.columns.services.title.toUpperCase()}
{t.panel.columns.services.big}
{t.panel.columns.services.title}
{t.panel.columns.services.sub}
{t.panel.columns.services.desc}
{t.panel.columns.services.chips.map((c, i) => (
{c.name}
))}
{/* CATALOG */}
{/* COMO FUNCIONA */}
{t.como.tag}
{t.como.title}
{t.como.lede}
{t.como.steps.map((s, i) => (
))}
{/* LIVE DEMO */}
{t.demo.tag}
{t.demo.title}
{t.demo.lede}
{/* DOS CAMINOS */}
{t.paths.tag}
{t.paths.title}
{t.paths.lede}
{t.paths.os.tag}
{t.paths.os.h}
{t.paths.os.price_pre}{t.paths.os.price_post}
{t.paths.os.lede}
{t.paths.os.features.map((f, i) => - {f}
)}
{t.paths.cloud.tag}
{t.paths.cloud.h}
{t.paths.cloud.price_pre} $49{t.paths.cloud.price_post}
{t.paths.cloud.lede}
{t.paths.cloud.features.map((f, i) => - {f}
)}
{/* WAITLIST */}
未来
{t.waitlist.title}
{t.waitlist.lede}
{/* FOOTER */}
);
}
// ─── Mount ──────────────────────────────────────────────────────────────────
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();