// Timeline + Contact + Footer
const Timeline = () => {
const D = PORTFOLIO_DATA;
return (
{/* spine */}
{D.experience.map((e, i) => (
{/* node */}
{e.role}
{e.company}
{e.detail}
))}
);
};
const Contact = ({ lang = "en" }) => {
const D = PORTFOLIO_DATA;
const [form, setForm] = React.useState({ name: "", email: "", service: "", budget: "", message: "" });
const [errors, setErrors] = React.useState({});
const [status, setStatus] = React.useState("idle"); // idle | sending | sent
const SERVICES = [
"Website & Webapp Development",
"AI Agent & Automation",
"Web3 & Blockchain",
"MT5 EA & Trading Automation",
"Browser Scraping & Data Pipeline",
"Infrastructure & DevOps",
];
const BUDGETS = ["< $500", "$500 – $2,000", "$2,000 – $5,000", "$5,000 – $15,000", "$15,000+"];
const validate = () => {
const e = {};
if (!form.name.trim()) e.name = "Required";
if (!form.email.trim()) e.email = "Required";
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) e.email = "Not a valid email";
if (!form.service) e.service = "Please pick a service";
if (!form.message.trim()) e.message = "Tell us about the project";
else if (form.message.trim().length < 12) e.message = "A few more words please";
return e;
};
const submit = async (ev) => {
ev.preventDefault();
const e = validate();
setErrors(e);
if (Object.keys(e).length) return;
setStatus("sending");
try {
await fetch("https://n8n.auraajenticai.cloud/webhook/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});
setStatus("sent");
} catch {
setStatus("idle");
setErrors({ message: "Network error — please email us directly." });
}
};
const Field = ({ id, label, type = "text", as = "input", placeholder }) => {
const Tag = as;
const hasErr = errors[id];
return (
);
};
const SelectField = ({ id, label, options, required = false }) => {
const hasErr = errors[id];
return (
);
};
return (
);
};
const ChatbotWidget = () => {
const [msgs, setMsgs] = React.useState([
{ role: "bot", text: "Ask me anything about the work — projects, stack choices, timelines." },
]);
const [input, setInput] = React.useState("");
const [thinking, setThinking] = React.useState(false);
const scrollRef = React.useRef(null);
React.useEffect(() => {
if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}, [msgs, thinking]);
const reply = async (q) => {
setThinking(true);
try {
const text = await window.claude.complete(
`You are a concise portfolio assistant for Aura, a senior agentic AI & full-stack engineer with 7+ years experience. Domains: AI agents, Web3, MLM/EA dashboards, full-stack. Answer in 1-3 sentences, professional but friendly. Question: ${q}`
);
setMsgs(m => [...m, { role: "bot", text }]);
} catch (e) {
setMsgs(m => [...m, { role: "bot", text: "Aura's offline — drop a note via the form and I'll reply within 48h." }]);
}
setThinking(false);
};
const send = () => {
const q = input.trim();
if (!q) return;
setMsgs(m => [...m, { role: "user", text: q }]);
setInput("");
reply(q);
};
return (
Ask about my work
powered by claude · live
online
{msgs.map((m, i) => (
{m.text}
))}
{thinking && (
{[0,1,2].map(i => (
))}
)}
);
};
const Footer = () => {
const D = PORTFOLIO_DATA;
return (
);
};
window.Timeline = Timeline;
window.Contact = Contact;
window.Footer = Footer;