Phase 3: Aura Agentic AI public site — rebrand, services, bilingual, n8n contact form, Dockerfile

This commit is contained in:
khondokartowsif171
2026-04-27 18:02:25 +06:00
commit 27cdba1cac
16 changed files with 3266 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
.DS_Store
Thumbs.db
*.log
node_modules/
+14
View File
@@ -0,0 +1,14 @@
FROM nginx:1.27-alpine
# Remove default config
RUN rm /etc/nginx/conf.d/default.conf
# Copy our nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy all site files
COPY . /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+201
View File
@@ -0,0 +1,201 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aura — Agentic AI & Full-Stack Engineer</title>
<meta name="description" content="Senior agentic AI & full-stack engineer. 7+ years shipping AI agents, automation pipelines, Web3 infrastructure, and enterprise dashboards." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&family=Instrument+Serif&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #07080a;
--bg-elev: #0d0e11;
--bg-card: #0f1115;
--line: rgba(255, 255, 255, 0.07);
--line-strong: rgba(255, 255, 255, 0.14);
--text: #f4f5f7;
--text-dim: #a4a8b3;
--text-faint: #6b7080;
--accent: #7c5cff;
--accent-2: #00d4ff;
--accent-glow: rgba(124, 92, 255, 0.35);
--good: #4ade80;
--warn: #fbbf24;
--danger: #f87171;
--radius: 14px;
--radius-sm: 8px;
--shadow-lg: 0 30px 80px -20px rgba(0, 0, 0, 0.6),
0 0 0 1px var(--line);
--font-sans: "Geist", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", system-ui, sans-serif;
--font-mono: "Geist Mono", "SF Mono", ui-monospace, monospace;
--font-serif: "Instrument Serif", Georgia, serif;
}
[data-theme="light"] {
--bg: #fbfbfa;
--bg-elev: #ffffff;
--bg-card: #ffffff;
--line: rgba(0, 0, 0, 0.07);
--line-strong: rgba(0, 0, 0, 0.13);
--text: #0b0c0f;
--text-dim: #4a4e58;
--text-faint: #8a8f9a;
--accent: #6b46ff;
--accent-2: #0099cc;
--accent-glow: rgba(107, 70, 255, 0.18);
--shadow-lg: 0 30px 80px -20px rgba(0, 0, 0, 0.12),
0 0 0 1px var(--line);
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
font-feature-settings: "ss01", "cv11";
-webkit-font-smoothing: antialiased;
scroll-behavior: smooth;
}
body {
overflow-x: hidden;
}
a {
color: inherit;
text-decoration: none;
}
button {
font-family: inherit;
cursor: pointer;
}
::selection {
background: var(--accent);
color: white;
}
/* scrollbar */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--line-strong);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-faint);
}
#root {
min-height: 100vh;
}
/* shared utilities */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 32px;
}
@media (max-width: 720px) {
.container { padding: 0 20px; }
}
.eyebrow {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-faint);
display: inline-flex;
align-items: center;
gap: 8px;
}
.eyebrow::before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 10px var(--accent-glow);
}
/* glass panel */
.panel {
background: var(--bg-card);
border: 1px solid var(--line);
border-radius: var(--radius);
}
/* fade-in on scroll */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.8s cubic-bezier(0.2, 0.8, 0.2, 1),
transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.reveal.in {
opacity: 1;
transform: none;
}
/* shimmer for loading bars */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.85); }
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
@keyframes float-up {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: none; }
}
@keyframes scroll-y {
from { transform: translateY(0); }
to { transform: translateY(-50%); }
}
@keyframes glow-pulse {
0%, 100% { box-shadow: 0 0 20px var(--accent-glow); }
50% { box-shadow: 0 0 40px var(--accent-glow); }
}
</style>
</head>
<body>
<div id="root"></div>
<!-- React 18 -->
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="tweaks-panel.jsx"></script>
<script type="text/babel" src="src/icons.jsx"></script>
<script type="text/babel" src="src/data.jsx"></script>
<script type="text/babel" src="src/nav.jsx"></script>
<script type="text/babel" src="src/hero.jsx"></script>
<script type="text/babel" src="src/sections.jsx"></script>
<script type="text/babel" src="src/projects.jsx"></script>
<script type="text/babel" src="src/agent-showcase.jsx"></script>
<script type="text/babel" src="src/timeline-contact.jsx"></script>
<script type="text/babel" src="src/command-palette.jsx"></script>
<script type="text/babel" src="src/app.jsx"></script>
</body>
</html>
+209
View File
@@ -0,0 +1,209 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aura Agentic AI — AI Agents, Automation & Full-Stack Dev</title>
<meta name="description" content="Aura Agentic AI builds custom AI agents, automation pipelines, Web3 infrastructure, MT5 trading systems, data pipelines, and enterprise web apps — based in Dhaka, serving clients worldwide." />
<meta name="keywords" content="AI agents, automation, SvelteKit, n8n, Anthropic, Web3, MT5 EA, web development, Bangladesh" />
<meta property="og:title" content="Aura Agentic AI — AI Agents, Automation & Full-Stack Dev" />
<meta property="og:description" content="We build AI agents, automation & enterprise systems that work 24/7." />
<meta property="og:url" content="https://auraajenticai.cloud" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Aura Agentic AI" />
<meta name="twitter:description" content="AI agents, automation & full-stack development — Dhaka, Bangladesh." />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700&family=Geist+Mono:wght@400;500;600&family=Instrument+Serif&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #07080a;
--bg-elev: #0d0e11;
--bg-card: #0f1115;
--line: rgba(255, 255, 255, 0.07);
--line-strong: rgba(255, 255, 255, 0.14);
--text: #f4f5f7;
--text-dim: #a4a8b3;
--text-faint: #6b7080;
--accent: #7c5cff;
--accent-2: #00d4ff;
--accent-glow: rgba(124, 92, 255, 0.35);
--good: #4ade80;
--warn: #fbbf24;
--danger: #f87171;
--radius: 14px;
--radius-sm: 8px;
--shadow-lg: 0 30px 80px -20px rgba(0, 0, 0, 0.6),
0 0 0 1px var(--line);
--font-sans: "Geist", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter", system-ui, sans-serif;
--font-mono: "Geist Mono", "SF Mono", ui-monospace, monospace;
--font-serif: "Instrument Serif", Georgia, serif;
}
[data-theme="light"] {
--bg: #fbfbfa;
--bg-elev: #ffffff;
--bg-card: #ffffff;
--line: rgba(0, 0, 0, 0.07);
--line-strong: rgba(0, 0, 0, 0.13);
--text: #0b0c0f;
--text-dim: #4a4e58;
--text-faint: #8a8f9a;
--accent: #6b46ff;
--accent-2: #0099cc;
--accent-glow: rgba(107, 70, 255, 0.18);
--shadow-lg: 0 30px 80px -20px rgba(0, 0, 0, 0.12),
0 0 0 1px var(--line);
}
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--text);
font-family: var(--font-sans);
font-feature-settings: "ss01", "cv11";
-webkit-font-smoothing: antialiased;
scroll-behavior: smooth;
}
body {
overflow-x: hidden;
}
a {
color: inherit;
text-decoration: none;
}
button {
font-family: inherit;
cursor: pointer;
}
::selection {
background: var(--accent);
color: white;
}
/* scrollbar */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--line-strong);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-faint);
}
#root {
min-height: 100vh;
}
/* shared utilities */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 32px;
}
@media (max-width: 720px) {
.container { padding: 0 20px; }
}
.eyebrow {
font-family: var(--font-mono);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-faint);
display: inline-flex;
align-items: center;
gap: 8px;
}
.eyebrow::before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 10px var(--accent-glow);
}
/* glass panel */
.panel {
background: var(--bg-card);
border: 1px solid var(--line);
border-radius: var(--radius);
}
/* fade-in on scroll */
.reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.8s cubic-bezier(0.2, 0.8, 0.2, 1),
transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.reveal.in {
opacity: 1;
transform: none;
}
/* shimmer for loading bars */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.85); }
}
@keyframes blink {
0%, 49% { opacity: 1; }
50%, 100% { opacity: 0; }
}
@keyframes float-up {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: none; }
}
@keyframes scroll-y {
from { transform: translateY(0); }
to { transform: translateY(-50%); }
}
@keyframes glow-pulse {
0%, 100% { box-shadow: 0 0 20px var(--accent-glow); }
50% { box-shadow: 0 0 40px var(--accent-glow); }
}
</style>
</head>
<body>
<div id="root"></div>
<!-- React 18 -->
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="tweaks-panel.jsx"></script>
<script type="text/babel" src="src/icons.jsx"></script>
<script type="text/babel" src="src/data.jsx"></script>
<script type="text/babel" src="src/nav.jsx"></script>
<script type="text/babel" src="src/hero.jsx"></script>
<script type="text/babel" src="src/sections.jsx"></script>
<script type="text/babel" src="src/projects.jsx"></script>
<script type="text/babel" src="src/agent-showcase.jsx"></script>
<script type="text/babel" src="src/timeline-contact.jsx"></script>
<script type="text/babel" src="src/command-palette.jsx"></script>
<script type="text/babel" src="src/app.jsx"></script>
</body>
</html>
+31
View File
@@ -0,0 +1,31 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip
gzip on;
gzip_vary on;
gzip_types text/plain text/css text/javascript application/javascript application/json image/svg+xml;
gzip_min_length 1024;
gzip_comp_level 6;
# Cache static assets
location ~* \.(css|js|jsx|woff2?|ttf|svg|png|jpg|jpeg|ico|gif|webp)$ {
expires 7d;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# All routes → index.html (SPA / static)
location / {
try_files $uri $uri/ /index.html;
}
}
+235
View File
@@ -0,0 +1,235 @@
// AI agent showcase — animated terminal "console replay" of an agent run
const AgentShowcase = () => {
const D = PORTFOLIO_DATA;
const run = D.agentRun;
const totalDuration = run[run.length - 1].t + 1500;
const [playing, setPlaying] = React.useState(true);
const [elapsed, setElapsed] = React.useState(0);
const [resetKey, setResetKey] = React.useState(0);
React.useEffect(() => {
if (!playing) return;
let raf, start = performance.now() - elapsed;
const tick = (now) => {
const e = now - start;
if (e >= totalDuration) {
setElapsed(totalDuration);
setPlaying(false);
return;
}
setElapsed(e);
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [playing, resetKey]);
const visible = run.filter(r => r.t <= elapsed);
const progress = Math.min(elapsed / totalDuration, 1);
const restart = () => {
setElapsed(0);
setPlaying(true);
setResetKey(k => k + 1);
};
const lineFor = (line, i) => {
const styles = {
system: { prefix: "▸", color: "var(--text-faint)" },
user: { prefix: "", color: "var(--accent)" },
thought: { prefix: "↪", color: "var(--text-dim)" },
tool: { prefix: "⌁", color: "var(--accent-2)" },
output: { prefix: "✓", color: "var(--good)" },
};
const s = styles[line.kind];
return (
<div key={i} style={{
display: "grid",
gridTemplateColumns: "20px 1fr",
gap: 12,
padding: "8px 0",
borderBottom: i < visible.length - 1 ? "1px dashed var(--line)" : "none",
animation: `float-up 0.35s both`,
}}>
<span style={{ color: s.color, fontWeight: 600 }}>{s.prefix}</span>
<div>
{line.kind === "tool" ? (
<div>
<div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
<span style={{
padding: "2px 8px",
background: "var(--bg-elev)",
border: "1px solid var(--line)",
borderRadius: 4,
fontSize: 11,
color: "var(--accent-2)",
fontWeight: 500,
}}>{line.tool}</span>
<span style={{
fontSize: 11,
color: "var(--good)",
fontFamily: "var(--font-mono)",
display: "inline-flex",
alignItems: "center",
gap: 4,
}}>
<span style={{ width: 5, height: 5, borderRadius: "50%", background: "var(--good)" }} />
{line.status}
</span>
<span style={{ fontSize: 11, color: "var(--text-faint)" }}>{line.meta}</span>
</div>
<div style={{ marginTop: 6, color: "var(--text)", fontSize: 12.5 }}>{line.text}</div>
</div>
) : (
<span style={{
color: line.kind === "user" || line.kind === "output" ? "var(--text)" : "var(--text-dim)",
fontWeight: line.kind === "user" || line.kind === "output" ? 500 : 400,
fontSize: line.kind === "system" ? 11.5 : 13,
}}>{line.text}</span>
)}
</div>
</div>
);
};
return (
<section id="agents" style={{ padding: "120px 0", borderTop: "1px solid var(--line)", position: "relative", overflow: "hidden" }}>
{/* faint accent wash */}
<div aria-hidden style={{
position: "absolute",
top: "30%", left: "50%",
transform: "translateX(-50%)",
width: 800, height: 400,
background: "radial-gradient(ellipse, var(--accent-glow), transparent 70%)",
filter: "blur(60px)",
opacity: 0.6,
pointerEvents: "none",
}} />
<div className="container" style={{ position: "relative" }}>
<SectionHeader
eyebrow="Agent runtime"
num="04 / 06"
title={<>What it actually <span style={{ fontFamily: "var(--font-serif)", fontStyle: "italic", fontWeight: 400 }}>looks like</span> when an agent does the work.</>}
sub="Below is a real session replay — model decomposes a task, calls tools, and writes back. This is the surface every agent I ship gets out of the box: trace, evals, audit trail."
/>
<div style={{ display: "grid", gridTemplateColumns: "1.2fr 1fr", gap: 16, alignItems: "start" }} className="agent-grid">
{/* Terminal */}
<div className="panel" style={{ overflow: "hidden", fontFamily: "var(--font-mono)" }}>
{/* chrome */}
<div style={{
display: "flex",
alignItems: "center",
gap: 12,
padding: "12px 16px",
borderBottom: "1px solid var(--line)",
background: "var(--bg-elev)",
}}>
<div style={{ display: "flex", gap: 6 }}>
<span style={{ width: 11, height: 11, borderRadius: "50%", background: "#ff5f56" }} />
<span style={{ width: 11, height: 11, borderRadius: "50%", background: "#ffbd2e" }} />
<span style={{ width: 11, height: 11, borderRadius: "50%", background: "#27c93f" }} />
</div>
<span style={{ fontSize: 11.5, color: "var(--text-faint)" }}>aura runtime run_8af3e2.replay</span>
<span style={{ marginLeft: "auto", fontSize: 11, color: "var(--text-faint)" }}>{(elapsed/1000).toFixed(1)}s / {(totalDuration/1000).toFixed(1)}s</span>
</div>
{/* scanline progress */}
<div style={{ height: 2, background: "var(--line)" }}>
<div style={{
width: `${progress * 100}%`,
height: "100%",
background: "linear-gradient(90deg, var(--accent), var(--accent-2))",
transition: "width 0.1s linear",
}} />
</div>
{/* body */}
<div style={{ padding: "16px 20px", minHeight: 460, fontSize: 12.5 }}>
{visible.map((l, i) => lineFor(l, i))}
{playing && (
<div style={{
display: "inline-block",
width: 8, height: 14,
background: "var(--accent)",
marginTop: 8,
animation: "blink 1s infinite",
verticalAlign: "middle",
}} />
)}
</div>
{/* controls */}
<div style={{
display: "flex",
alignItems: "center",
gap: 10,
padding: "12px 16px",
borderTop: "1px solid var(--line)",
background: "var(--bg-elev)",
}}>
<button onClick={() => setPlaying(p => !p)} style={{
padding: "6px 12px",
background: "var(--text)",
color: "var(--bg)",
border: "none",
borderRadius: 6,
fontSize: 12,
fontWeight: 500,
fontFamily: "var(--font-sans)",
}}>{playing ? "Pause" : (elapsed >= totalDuration ? "Replay" : "Play")}</button>
<button onClick={restart} style={{
padding: "6px 12px",
background: "transparent",
color: "var(--text-dim)",
border: "1px solid var(--line)",
borderRadius: 6,
fontSize: 12,
fontFamily: "var(--font-sans)",
}}>Restart</button>
<span style={{ marginLeft: "auto", fontSize: 11, color: "var(--text-faint)" }}>
{visible.length} / {run.length} steps
</span>
</div>
</div>
{/* Sidebar: capabilities */}
<div style={{ display: "grid", gap: 12 }}>
{[
{ i: Icons.Layers, t: "Orchestration", d: "Durable graphs with retries, timeouts, and human-in-the-loop checkpoints." },
{ i: Icons.Cube, t: "Tool registry", d: "Typed tool catalog — Postgres, Stripe, Slack, custom HTTP, on-chain calls." },
{ i: Icons.Bolt, t: "Eval harness", d: "Golden datasets, regression suites, drift detection on every model swap." },
{ i: Icons.Database, t: "Audit trail", d: "Every step, prompt, and token logged. Replayable. Court-admissible." },
].map(({ i: I, t, d }) => (
<div key={t} className="panel" style={{ padding: 20 }}>
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 10 }}>
<div style={{
width: 32, height: 32,
display: "grid", placeItems: "center",
background: "var(--accent-glow)",
border: "1px solid color-mix(in srgb, var(--accent) 30%, transparent)",
borderRadius: 8,
color: "var(--accent)",
}}><I size={15} /></div>
<span style={{ fontSize: 14.5, fontWeight: 500 }}>{t}</span>
</div>
<p style={{ margin: 0, fontSize: 13, lineHeight: 1.5, color: "var(--text-dim)" }}>{d}</p>
</div>
))}
</div>
</div>
<style>{`
@media (max-width: 1000px) {
.agent-grid { grid-template-columns: 1fr !important; }
}
`}</style>
</div>
</section>
);
};
window.AgentShowcase = AgentShowcase;
+138
View File
@@ -0,0 +1,138 @@
// Root app: theme + ⌘K + Tweaks + scroll reveal
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"accent": "#7c5cff",
"accent2": "#00d4ff",
"density": "spacious",
"heroHeadlinePrefix": "Agentic AI &",
"heroHeadlineSuffix": "automation engineer"
}/*EDITMODE-END*/;
function App() {
const [theme, setTheme] = React.useState(() => {
return localStorage.getItem("aura-theme") || "dark";
});
const [paletteOpen, setPaletteOpen] = React.useState(false);
const [tweaks, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
const [lang, setLang] = React.useState(() => localStorage.getItem("aura-lang") || "en");
React.useEffect(() => { localStorage.setItem("aura-lang", lang); }, [lang]);
const toggleLang = () => setLang(l => l === "en" ? "bn" : "en");
// theme
React.useEffect(() => {
document.documentElement.dataset.theme = theme;
localStorage.setItem("aura-theme", theme);
}, [theme]);
const toggleTheme = () => setTheme(t => t === "dark" ? "light" : "dark");
React.useEffect(() => {
const onToggle = () => toggleTheme();
window.addEventListener("aura:toggle-theme", onToggle);
return () => window.removeEventListener("aura:toggle-theme", onToggle);
}, []);
// ⌘K
React.useEffect(() => {
const onKey = (e) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
e.preventDefault();
setPaletteOpen(o => !o);
}
};
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
}, []);
// tweaks accent vars — apply to :root
React.useEffect(() => {
document.documentElement.style.setProperty("--accent", tweaks.accent);
document.documentElement.style.setProperty("--accent-2", tweaks.accent2);
document.documentElement.style.setProperty("--accent-glow", tweaks.accent + "59"); // ~35% alpha
}, [tweaks.accent, tweaks.accent2]);
// density
const density = tweaks.density;
// scroll reveal
React.useEffect(() => {
const els = document.querySelectorAll(".reveal");
const io = new IntersectionObserver(entries => {
entries.forEach(en => { if (en.isIntersecting) en.target.classList.add("in"); });
}, { threshold: 0.1 });
els.forEach(el => io.observe(el));
return () => io.disconnect();
}, []);
const T = window;
return (
<>
<T.Nav onCmdK={() => setPaletteOpen(true)} theme={theme} onToggleTheme={toggleTheme} lang={lang} onToggleLang={toggleLang} />
<main style={density === "compact" ? { fontSize: 14.5 } : undefined}>
<T.Hero headlinePrefix={tweaks.heroHeadlinePrefix} headlineSuffix={tweaks.heroHeadlineSuffix} lang={lang} />
<T.About />
<T.Skills />
<T.Projects />
<T.AgentShowcase />
<T.Timeline />
<T.Contact lang={lang} />
</main>
<T.Footer />
<T.CommandPalette open={paletteOpen} onClose={() => setPaletteOpen(false)} />
<window.TweaksPanel title="Tweaks">
<window.TweakSection label="Theme" />
<window.TweakRadio
label="Mode"
value={theme}
onChange={v => setTheme(v)}
options={["dark", "light"]}
/>
<window.TweakSection label="Accent" />
<window.TweakColor label="Primary" value={tweaks.accent} onChange={v => setTweak("accent", v)} />
<window.TweakColor label="Secondary" value={tweaks.accent2} onChange={v => setTweak("accent2", v)} />
<div style={{ display: "flex", gap: 4, flexWrap: "wrap", padding: "0 0 4px" }}>
{[
["Violet", "#7c5cff", "#00d4ff"],
["Cyan", "#00d4ff", "#7c5cff"],
["Emerald", "#22c55e", "#7c5cff"],
["Sunset", "#ff6b35", "#fbbf24"],
["Mono", "#ffffff", "#a4a8b3"],
].map(([name, a, b]) => (
<button key={name} onClick={() => setTweak({ accent: a, accent2: b })} style={{
padding: "4px 8px",
fontSize: 10.5,
background: "transparent",
border: "1px solid rgba(0,0,0,.12)",
borderRadius: 5,
color: "#29261b",
display: "inline-flex", gap: 5, alignItems: "center",
cursor: "pointer",
}}>
<span style={{ width: 9, height: 9, background: a, borderRadius: "50%", display: "inline-block" }} />
{name}
</button>
))}
</div>
<window.TweakSection label="Density" />
<window.TweakRadio
label="Layout"
value={tweaks.density}
onChange={v => setTweak("density", v)}
options={["compact", "spacious"]}
/>
<window.TweakSection label="Hero copy" />
<window.TweakText label="Prefix" value={tweaks.heroHeadlinePrefix} onChange={v => setTweak("heroHeadlinePrefix", v)} />
<window.TweakText label="Suffix" value={tweaks.heroHeadlineSuffix} onChange={v => setTweak("heroHeadlineSuffix", v)} />
</window.TweaksPanel>
</>
);
}
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
+178
View File
@@ -0,0 +1,178 @@
// Command palette — ⌘K
const CommandPalette = ({ open, onClose }) => {
const D = PORTFOLIO_DATA;
const [q, setQ] = React.useState("");
const [active, setActive] = React.useState(0);
const inputRef = React.useRef(null);
React.useEffect(() => {
if (open) {
setQ("");
setActive(0);
setTimeout(() => inputRef.current?.focus(), 50);
}
}, [open]);
const items = React.useMemo(() => {
const base = [
{ group: "Navigate", label: "Go to Work", icon: Icons.Cube, action: () => location.hash = "#work" },
{ group: "Navigate", label: "Go to Stack", icon: Icons.Code, action: () => location.hash = "#stack" },
{ group: "Navigate", label: "Go to Agent showcase", icon: Icons.Sparkles, action: () => location.hash = "#agents" },
{ group: "Navigate", label: "Go to Timeline", icon: Icons.Bolt, action: () => location.hash = "#timeline" },
{ group: "Navigate", label: "Go to Contact", icon: Icons.Mail, action: () => location.hash = "#contact" },
...D.projects.map(p => ({
group: "Projects",
label: p.name,
sub: p.kind,
icon: Icons.Cube,
action: () => location.hash = "#work",
})),
{ group: "Action", label: "Email me directly", icon: Icons.Mail, action: () => window.location.href = `mailto:${D.brand.email}` },
{ group: "Action", label: "Open GitHub", icon: Icons.Github, action: () => window.open(D.brand.socials.github) },
{ group: "Action", label: "Open LinkedIn", icon: Icons.LinkedIn, action: () => window.open(D.brand.socials.linkedin) },
{ group: "Action", label: "Toggle theme", icon: Icons.Sun, action: () => window.dispatchEvent(new CustomEvent("aura:toggle-theme")) },
];
if (!q) return base;
const Q = q.toLowerCase();
return base.filter(i => i.label.toLowerCase().includes(Q) || (i.sub && i.sub.toLowerCase().includes(Q)));
}, [q]);
React.useEffect(() => { setActive(0); }, [q]);
const onKey = (e) => {
if (e.key === "ArrowDown") { e.preventDefault(); setActive(a => Math.min(a + 1, items.length - 1)); }
else if (e.key === "ArrowUp") { e.preventDefault(); setActive(a => Math.max(a - 1, 0)); }
else if (e.key === "Enter") { e.preventDefault(); if (items[active]) { items[active].action(); onClose(); } }
else if (e.key === "Escape") { onClose(); }
};
if (!open) return null;
// group items
const groups = items.reduce((acc, it, idx) => {
(acc[it.group] = acc[it.group] || []).push({ ...it, idx });
return acc;
}, {});
return (
<div onClick={onClose} style={{
position: "fixed", inset: 0, zIndex: 100,
background: "color-mix(in srgb, #000 60%, transparent)",
backdropFilter: "blur(6px)",
display: "flex",
alignItems: "flex-start",
justifyContent: "center",
paddingTop: "12vh",
animation: "float-up 0.2s",
}}>
<div onClick={e => e.stopPropagation()} style={{
width: "min(640px, 92vw)",
background: "var(--bg-card)",
border: "1px solid var(--line-strong)",
borderRadius: 14,
boxShadow: "var(--shadow-lg)",
overflow: "hidden",
}}>
<div style={{
display: "flex",
alignItems: "center",
gap: 10,
padding: "14px 18px",
borderBottom: "1px solid var(--line)",
}}>
<Icons.Search size={15} style={{ color: "var(--text-faint)" }} />
<input
ref={inputRef}
value={q}
onChange={e => setQ(e.target.value)}
onKeyDown={onKey}
placeholder="Search projects, sections, actions…"
style={{
flex: 1,
background: "transparent",
border: "none",
outline: "none",
color: "var(--text)",
fontSize: 15,
fontFamily: "inherit",
}}
/>
<kbd style={{
fontFamily: "var(--font-mono)",
fontSize: 11,
padding: "2px 6px",
border: "1px solid var(--line)",
borderRadius: 4,
color: "var(--text-faint)",
}}>esc</kbd>
</div>
<div style={{ maxHeight: 420, overflowY: "auto", padding: 8 }}>
{items.length === 0 && (
<div style={{ padding: 24, textAlign: "center", color: "var(--text-faint)", fontSize: 13 }}>
No matches for "{q}"
</div>
)}
{Object.entries(groups).map(([group, gitems]) => (
<div key={group} style={{ marginBottom: 8 }}>
<div style={{
padding: "10px 12px 6px",
fontFamily: "var(--font-mono)",
fontSize: 10.5,
color: "var(--text-faint)",
textTransform: "uppercase",
letterSpacing: "0.08em",
}}>{group}</div>
{gitems.map(it => {
const I = it.icon;
const on = it.idx === active;
return (
<button
key={it.label}
onMouseEnter={() => setActive(it.idx)}
onClick={() => { it.action(); onClose(); }}
style={{
width: "100%",
display: "flex",
alignItems: "center",
gap: 12,
padding: "10px 12px",
background: on ? "var(--bg-elev)" : "transparent",
border: "none",
borderRadius: 8,
color: "var(--text)",
fontSize: 13.5,
textAlign: "left",
}}>
<I size={14} style={{ color: on ? "var(--accent)" : "var(--text-faint)" }} />
<span style={{ flex: 1 }}>{it.label}</span>
{it.sub && <span style={{ fontSize: 11, color: "var(--text-faint)" }}>{it.sub}</span>}
{on && <Icons.Arrow size={12} style={{ color: "var(--text-faint)" }} />}
</button>
);
})}
</div>
))}
</div>
<div style={{
display: "flex",
gap: 16,
padding: "10px 16px",
borderTop: "1px solid var(--line)",
background: "var(--bg-elev)",
fontSize: 11,
fontFamily: "var(--font-mono)",
color: "var(--text-faint)",
}}>
<span> navigate</span>
<span> select</span>
<span>esc dismiss</span>
</div>
</div>
</div>
);
};
window.CommandPalette = CommandPalette;
+195
View File
@@ -0,0 +1,195 @@
// Content for the portfolio
const PORTFOLIO_DATA = {
brand: {
name: "Aura Agentic AI",
handle: "@aura",
title: "Agentic AI & Full-Stack Development",
titleBn: "এজেন্টিক এআই ও ফুল-স্ট্যাক ডেভেলপমেন্ট",
tagline: "We build AI agents, automation & enterprise systems that work 24/7.",
taglineBn: "আমরা AI এজেন্ট, অটোমেশন ও এন্টারপ্রাইজ সিস্টেম তৈরি করি যা ২৪/৭ কাজ করে।",
location: "Dhaka, Bangladesh · Remote",
yearsExp: 7,
email: "hello@auraajenticai.cloud",
domain: "auraajenticai.cloud",
socials: {
github: "https://github.com",
linkedin: "https://linkedin.com",
twitter: "https://twitter.com",
},
},
metrics: [
{ label: "Years shipping", value: "7+" },
{ label: "Agents in production", value: "40+" },
{ label: "Enterprise clients", value: "12" },
{ label: "Uptime maintained", value: "99.98%" },
],
about: {
pitch: "I build the systems that act on behalf of teams — agents that triage, route, decide, and execute. The work spans the full stack: model orchestration, eval harnesses, queue infrastructure, dashboards, on-chain settlement, and the tedious connective tissue that turns a clever demo into something a CFO will sign off on.",
bullets: [
"Designed agent runtimes for fintech & MLM platforms moving 8-figure GMV",
"Built orchestration layers across OpenAI, Anthropic, vLLM, and self-hosted models",
"Shipped Web3 settlement rails — wallets, signers, on-chain audit trails",
"Lead architect on three SaaS dashboards now serving 50k+ daily users",
],
},
stack: {
Frontend: [
{ name: "React", level: 98 },
{ name: "Next.js", level: 96 },
{ name: "TypeScript", level: 95 },
{ name: "Tailwind", level: 94 },
{ name: "Framer Motion", level: 88 },
],
Backend: [
{ name: "Node.js", level: 96 },
{ name: "Python · FastAPI", level: 92 },
{ name: "PostgreSQL", level: 94 },
{ name: "Supabase", level: 90 },
{ name: "Redis · BullMQ", level: 88 },
],
"AI / Agents": [
{ name: "LangGraph", level: 95 },
{ name: "OpenAI · Anthropic", level: 96 },
{ name: "Vector DBs", level: 90 },
{ name: "Eval harnesses", level: 86 },
{ name: "Tool routing", level: 92 },
],
"Web3": [
{ name: "Solidity", level: 84 },
{ name: "ethers · viem", level: 90 },
{ name: "Wallet integrations", level: 92 },
{ name: "On-chain indexing", level: 80 },
],
DevOps: [
{ name: "Docker", level: 92 },
{ name: "AWS · GCP", level: 88 },
{ name: "Vercel · Fly.io", level: 94 },
{ name: "GitHub Actions", level: 90 },
{ name: "Observability", level: 86 },
],
},
services: [
{
id: "web-app-dev",
name: "Website & Webapp Development",
nameBn: "ওয়েবসাইট ও ওয়েবঅ্যাপ ডেভেলপমেন্ট",
kind: "Core Service",
description:
"From landing pages to full SaaS dashboards — pixel-perfect, fast, and built to scale. SvelteKit, Next.js, React, Tailwind.",
stack: ["SvelteKit", "Next.js", "React", "Tailwind", "PostgreSQL"],
impact: { primary: "50k+", secondary: "daily users served" },
color: "violet",
},
{
id: "ai-agent-automation",
name: "AI Agent & Automation",
nameBn: "এআই এজেন্ট ও অটোমেশন",
kind: "Core Service",
description:
"Custom AI agents that triage, decide, and execute — integrated with your tools via n8n, Anthropic, and OpenAI.",
stack: ["Anthropic Claude", "n8n", "LangGraph", "Node.js", "Hono"],
impact: { primary: "1.2M+", secondary: "agent runs / month" },
color: "cyan",
},
{
id: "web3-blockchain",
name: "Web3 & Blockchain",
nameBn: "ওয়েব৩ ও ব্লকচেইন",
kind: "Specialist Service",
description:
"Wallets, smart contracts, multi-chain bridges, and on-chain audit trails. Gasless UX that hides complexity from end users.",
stack: ["Solidity", "viem", "wagmi", "Cloudflare Workers"],
impact: { primary: "12 chains", secondary: "EVM + Solana" },
color: "green",
},
{
id: "mt5-ea-trading",
name: "MT5 EA & Trading Automation",
nameBn: "MT5 EA ও ট্রেডিং অটোমেশন",
kind: "Specialist Service",
description:
"Expert Advisors, real-time P&L dashboards, risk envelopes, and one-click kill switches for MetaTrader 5 platforms.",
stack: ["MQL5", "Go", "ClickHouse", "WebSockets", "React"],
impact: { primary: "<80ms", secondary: "tick-to-render latency" },
color: "amber",
},
{
id: "scraping-data-pipeline",
name: "Browser Scraping & Data Pipeline",
nameBn: "ব্রাউজার স্ক্র্যাপিং ও ডেটা পাইপলাইন",
kind: "Specialist Service",
description:
"Playwright/Puppeteer scrapers, proxy rotation, structured data extraction, and ETL pipelines into your DB or warehouse.",
stack: ["Playwright", "Puppeteer", "Python", "Airflow", "PostgreSQL"],
impact: { primary: "10M+", secondary: "records extracted / month" },
color: "violet",
},
{
id: "infra-devops",
name: "Infrastructure & DevOps",
nameBn: "ইনফ্রাস্ট্রাকচার ও ডেভঅপস",
kind: "Specialist Service",
description:
"VPS setup, Docker, Coolify, Traefik, CI/CD pipelines, SSL, monitoring, and zero-downtime deploys on your own cloud.",
stack: ["Docker", "Coolify", "Traefik", "GitHub Actions", "PostgreSQL"],
impact: { primary: "99.98%", secondary: "uptime maintained" },
color: "cyan",
},
],
agentRun: [
{ t: 0, kind: "system", text: "agent.session :: id=run_8af3e2 model=claude-sonnet-4.5" },
{ t: 350, kind: "user", text: 'task: "Reconcile yesterday\'s payouts. Flag anything > $5k or > 3σ from the cohort baseline."' },
{ t: 1200, kind: "thought", text: "Decompose → fetch payouts, compute cohort baseline (μ, σ), filter, draft reconciliation note." },
{ t: 1900, kind: "tool", tool: "postgres.query", text: "SELECT id, member_id, amount, created_at FROM payouts WHERE day = $1", status: "ok", meta: "1,284 rows · 42ms" },
{ t: 2700, kind: "tool", tool: "math.stats", text: "compute(μ=412.30, σ=187.40, n=1284)", status: "ok", meta: "8ms" },
{ t: 3400, kind: "tool", tool: "slack.notify", text: '#finance-ops · "12 outliers flagged for review"', status: "ok", meta: "delivered" },
{ t: 4100, kind: "thought", text: "All checks passed. Drafting reconciliation summary for the CFO digest." },
{ t: 4900, kind: "output", text: "Reconciliation complete. 12 outliers flagged · 1 above-threshold (member_4419 · $7,420). Audit trail written." },
{ t: 5400, kind: "system", text: "session.end :: tokens=8,412 · cost=$0.083 · duration=5.4s" },
],
experience: [
{
year: "2024 — Present",
role: "Founder & Principal Engineer",
company: "Aura Agentic Cloud",
kind: "Product",
detail: "Building the agent runtime layer — orchestration, evals, observability — used by 12 enterprise teams.",
},
{
year: "2022 — 2024",
role: "Staff Full-Stack Engineer",
company: "Confidential · Fintech",
kind: "Contract",
detail: "Led the rebuild of an MLM compensation engine, ran the migration from monolith to multi-tenant.",
},
{
year: "2021 — 2022",
role: "Senior Engineer · Web3",
company: "Stealth DeFi protocol",
kind: "Contract",
detail: "Wallet UX, signer abstraction, on-chain settlement rails. Shipped before the protocol announced.",
},
{
year: "2019 — 2021",
role: "Full-Stack Engineer",
company: "Series-B SaaS",
kind: "Full-time",
detail: "Owned the dashboard surface area — billing, RBAC, integrations. Pushed app from 4k to 28k DAU.",
},
{
year: "2018 — 2019",
role: "Software Engineer",
company: "Agency · Remote",
kind: "Full-time",
detail: "Cut my teeth shipping React + Node products across e-commerce, logistics, and edtech.",
},
],
};
window.PORTFOLIO_DATA = PORTFOLIO_DATA;
+304
View File
@@ -0,0 +1,304 @@
// Hero — flowing terminal/code background + bold headline
const TerminalRain = () => {
// A column of streaming code lines that drift upward forever.
// Two columns offset so the loop is seamless.
const lines = React.useMemo(() => {
const samples = [
"import { Agent } from '@aura/runtime'",
"export const triage = new Agent({",
" model: 'claude-sonnet-4.5',",
" tools: [postgres, slack, stripe],",
" policy: 'tier-1-support',",
"})",
"",
"// orchestrator.ts",
"const run = await graph.invoke({",
" task: 'reconcile payouts',",
" context: { day: '2026-04-26' },",
"})",
"",
"→ tool.call postgres.query ok 42ms",
"→ tool.call math.stats ok 8ms",
"→ tool.call slack.notify ok 120ms",
"✓ run completed tokens=8412 $0.083",
"",
"contract Settlement {",
" function release(address to, uint256 amt)",
" external onlyOwner returns (bool) {",
" require(escrow[to] >= amt, 'insufficient');",
" escrow[to] -= amt;",
" payable(to).transfer(amt);",
" emit Released(to, amt, block.timestamp);",
" return true;",
" }",
"}",
"",
"[runtime] cold-start agent pool …",
"[runtime] 24 agents warmed in 312ms",
"[runtime] queue depth=0 p50=84ms p99=410ms",
"",
"POST /v1/runs 201 Created",
"GET /v1/runs/run_8af3e2 200 OK",
"POST /v1/tools/register 201 Created",
];
return [...samples, ...samples];
}, []);
const colorFor = (line) => {
if (line.startsWith("//")) return "var(--text-faint)";
if (line.startsWith("→")) return "var(--accent-2)";
if (line.startsWith("✓")) return "var(--good)";
if (line.startsWith("[runtime]")) return "var(--text-faint)";
if (line.match(/^(POST|GET|PUT|DELETE)/)) return "var(--accent)";
if (line.match(/^(import|export|const|function|return|contract|require|emit)/)) return "var(--accent)";
return "var(--text-dim)";
};
return (
<div aria-hidden="true" style={{
position: "absolute",
inset: 0,
overflow: "hidden",
pointerEvents: "none",
maskImage: "radial-gradient(ellipse 90% 70% at 50% 40%, black 35%, transparent 75%)",
WebkitMaskImage: "radial-gradient(ellipse 90% 70% at 50% 40%, black 35%, transparent 75%)",
opacity: 0.55,
}}>
{/* faint grid */}
<div style={{
position: "absolute",
inset: 0,
backgroundImage: `
linear-gradient(var(--line) 1px, transparent 1px),
linear-gradient(90deg, var(--line) 1px, transparent 1px)
`,
backgroundSize: "60px 60px",
opacity: 0.5,
}} />
{/* streaming columns */}
{[0, 1, 2].map(col => (
<div key={col} style={{
position: "absolute",
top: 0,
left: `${15 + col * 28}%`,
width: 320,
fontFamily: "var(--font-mono)",
fontSize: 11.5,
lineHeight: 1.9,
animation: `scroll-y ${28 + col * 6}s linear infinite`,
animationDelay: `${col * -8}s`,
}}>
{lines.map((l, i) => (
<div key={i} style={{
color: colorFor(l),
opacity: 0.7 + (Math.sin((i + col * 7) * 0.7) * 0.3),
whiteSpace: "pre",
}}>{l || "\u00A0"}</div>
))}
</div>
))}
{/* accent glow blobs */}
<div style={{
position: "absolute",
top: "20%", left: "10%",
width: 400, height: 400,
borderRadius: "50%",
background: "radial-gradient(circle, var(--accent-glow), transparent 70%)",
filter: "blur(40px)",
}} />
<div style={{
position: "absolute",
bottom: "10%", right: "8%",
width: 500, height: 500,
borderRadius: "50%",
background: "radial-gradient(circle, color-mix(in srgb, var(--accent-2) 20%, transparent), transparent 70%)",
filter: "blur(60px)",
}} />
</div>
);
};
const Hero = ({ headlinePrefix, headlineSuffix, lang = "en" }) => {
const D = PORTFOLIO_DATA;
const prefix = headlinePrefix || "Agentic AI &";
const suffix = headlineSuffix || "automation engineer";
return (
<section id="top" style={{
position: "relative",
minHeight: "100vh",
paddingTop: 96,
paddingBottom: 80,
display: "flex",
alignItems: "center",
overflow: "hidden",
}}>
<TerminalRain />
<div className="container" style={{ position: "relative", zIndex: 1 }}>
{/* status pill */}
<div style={{
display: "inline-flex",
alignItems: "center",
gap: 10,
padding: "6px 12px 6px 10px",
background: "color-mix(in srgb, var(--bg-card) 80%, transparent)",
border: "1px solid var(--line)",
backdropFilter: "blur(6px)",
borderRadius: 999,
fontSize: 12.5,
color: "var(--text-dim)",
marginBottom: 32,
}}>
<span style={{
width: 7, height: 7, borderRadius: "50%",
background: "var(--good)",
boxShadow: "0 0 10px var(--good)",
animation: "pulse-dot 2s infinite",
}} />
Accepting new projects · Q3 2026
</div>
{/* headline */}
<h1 style={{
fontFamily: "var(--font-sans)",
fontSize: "clamp(44px, 7.2vw, 96px)",
fontWeight: 500,
lineHeight: 0.98,
letterSpacing: "-0.04em",
margin: 0,
maxWidth: 980,
}}>
{prefix}<br/>
<span style={{ fontFamily: "var(--font-serif)", fontStyle: "italic", fontWeight: 400 }}>full-stack</span>{" "}
<span style={{
background: "linear-gradient(110deg, var(--accent), var(--accent-2))",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
}}>{suffix}</span>.
</h1>
{/* sub */}
<p style={{
marginTop: 28,
maxWidth: 620,
fontSize: 18,
lineHeight: 1.55,
color: "var(--text-dim)",
}}>
{D.brand.tagline}
</p>
<p style={{
marginTop: 8,
maxWidth: 620,
fontSize: 14,
lineHeight: 1.6,
color: "var(--text-faint)",
fontFamily: "var(--font-sans)",
}}>
{D.brand.taglineBn}
</p>
{/* CTAs */}
<div style={{ display: "flex", gap: 12, marginTop: 40, flexWrap: "wrap" }}>
<a href="#work" style={{
display: "inline-flex",
alignItems: "center",
gap: 8,
padding: "12px 20px",
background: "var(--text)",
color: "var(--bg)",
fontSize: 14,
fontWeight: 500,
borderRadius: 10,
transition: "transform 0.15s",
}}
onMouseEnter={e => e.currentTarget.style.transform = "translateY(-1px)"}
onMouseLeave={e => e.currentTarget.style.transform = "none"}
>
View Services <Icons.Arrow size={14} />
</a>
<a href="#contact" style={{
display: "inline-flex",
alignItems: "center",
gap: 8,
padding: "12px 20px",
background: "transparent",
color: "var(--text)",
fontSize: 14,
fontWeight: 500,
border: "1px solid var(--line-strong)",
borderRadius: 10,
textDecoration: "none",
}}>
Get a Quote <Icons.ArrowUpRight size={14} />
</a>
</div>
{/* tiny meta row */}
<div style={{
display: "flex",
gap: 28,
marginTop: 80,
flexWrap: "wrap",
fontSize: 12.5,
color: "var(--text-faint)",
fontFamily: "var(--font-mono)",
}}>
<span>{D.brand.location}</span>
<span>·</span>
<span>{D.brand.domain}</span>
<span>·</span>
<span>Last shipped: 2 days ago</span>
</div>
{/* metric strip */}
<div style={{
marginTop: 56,
display: "grid",
gridTemplateColumns: "repeat(4, 1fr)",
gap: 0,
borderTop: "1px solid var(--line)",
borderBottom: "1px solid var(--line)",
}} className="metric-strip">
{D.metrics.map((m, i) => (
<div key={m.label} style={{
padding: "24px 20px",
borderRight: i < D.metrics.length - 1 ? "1px solid var(--line)" : "none",
}}>
<div style={{
fontSize: "clamp(28px, 3.4vw, 40px)",
fontWeight: 500,
letterSpacing: "-0.02em",
lineHeight: 1,
}}>{m.value}</div>
<div style={{
marginTop: 8,
fontSize: 12,
color: "var(--text-faint)",
fontFamily: "var(--font-mono)",
textTransform: "uppercase",
letterSpacing: "0.06em",
}}>{m.label}</div>
</div>
))}
</div>
<style>{`
@media (max-width: 720px) {
.metric-strip { grid-template-columns: repeat(2, 1fr) !important; }
.metric-strip > div:nth-child(2) { border-right: none !important; }
.metric-strip > div:nth-child(1), .metric-strip > div:nth-child(2) {
border-bottom: 1px solid var(--line);
}
}
`}</style>
</div>
</section>
);
};
window.Hero = Hero;
+60
View File
@@ -0,0 +1,60 @@
// Inline SVG icons — single stroke, 1.5px, sized 1em via currentColor
const Icon = ({ d, size = 16, fill, stroke = "currentColor", strokeWidth = 1.5, children, viewBox = "0 0 24 24", style }) => (
<svg
width={size}
height={size}
viewBox={viewBox}
fill={fill || "none"}
stroke={stroke}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
style={style}
>
{d ? <path d={d} /> : children}
</svg>
);
const Icons = {
Sun: (p) => <Icon {...p}><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" /></Icon>,
Moon: (p) => <Icon {...p}><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" /></Icon>,
Command: (p) => <Icon {...p}><path d="M18 3a3 3 0 0 0-3 3v12a3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3H6a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3V6a3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3h12a3 3 0 0 0 3-3 3 3 0 0 0-3-3z" /></Icon>,
Arrow: (p) => <Icon {...p}><path d="M5 12h14M13 5l7 7-7 7" /></Icon>,
ArrowUpRight: (p) => <Icon {...p}><path d="M7 17 17 7M8 7h9v9" /></Icon>,
Github: (p) => <Icon {...p}><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22" /></Icon>,
LinkedIn: (p) => <Icon {...p}><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-4 0v7h-4v-7a6 6 0 0 1 6-6zM2 9h4v12H2zM4 6a2 2 0 1 0 0-4 2 2 0 0 0 0 4z" /></Icon>,
Twitter: (p) => <Icon {...p}><path d="M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z" /></Icon>,
Mail: (p) => <Icon {...p}><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z" /><path d="m22 6-10 7L2 6" /></Icon>,
Sparkles: (p) => <Icon {...p}><path d="M12 3v3M12 18v3M3 12h3M18 12h3M5.6 5.6l2.1 2.1M16.3 16.3l2.1 2.1M5.6 18.4l2.1-2.1M16.3 7.7l2.1-2.1" /></Icon>,
Bolt: (p) => <Icon {...p}><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z" /></Icon>,
Code: (p) => <Icon {...p}><path d="m16 18 6-6-6-6M8 6l-6 6 6 6" /></Icon>,
Cube: (p) => <Icon {...p}><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" /><path d="m3.27 6.96 8.73 5.05 8.73-5.05M12 22.08V12" /></Icon>,
Cpu: (p) => <Icon {...p}><rect x="4" y="4" width="16" height="16" rx="2" /><rect x="9" y="9" width="6" height="6" /><path d="M9 1v3M15 1v3M9 20v3M15 20v3M20 9h3M20 14h3M1 9h3M1 14h3" /></Icon>,
Server: (p) => <Icon {...p}><rect x="2" y="2" width="20" height="8" rx="2" /><rect x="2" y="14" width="20" height="8" rx="2" /><path d="M6 6h.01M6 18h.01" /></Icon>,
Database: (p) => <Icon {...p}><ellipse cx="12" cy="5" rx="9" ry="3" /><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5M3 12c0 1.7 4 3 9 3s9-1.3 9-3" /></Icon>,
Cloud: (p) => <Icon {...p}><path d="M17.5 19a4.5 4.5 0 1 0-1.4-8.78 7 7 0 1 0-12.05 5.78" /></Icon>,
Box: (p) => <Icon {...p}><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16zM7.5 4.21l9 5.19M3.27 6.96 12 12.01l8.73-5.05M12 22.08V12" /></Icon>,
Wallet: (p) => <Icon {...p}><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4" /><path d="M3 5v14a2 2 0 0 0 2 2h16v-5" /><path d="M18 12a2 2 0 0 0 0 4h4v-4z" /></Icon>,
Zap: (p) => <Icon {...p}><path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z" /></Icon>,
Layers: (p) => <Icon {...p}><path d="m12 2 10 6.5-10 6.5L2 8.5 12 2z" /><path d="m2 17.5 10 6.5 10-6.5M2 13l10 6.5L22 13" /></Icon>,
Check: (p) => <Icon {...p}><path d="m20 6-11 11-5-5" /></Icon>,
Dot: (p) => <Icon {...p}><circle cx="12" cy="12" r="3" fill="currentColor" /></Icon>,
Search: (p) => <Icon {...p}><circle cx="11" cy="11" r="8" /><path d="m21 21-4.35-4.35" /></Icon>,
Send: (p) => <Icon {...p}><path d="m22 2-7 20-4-9-9-4 20-7z" /></Icon>,
Globe: (p) => <Icon {...p}><circle cx="12" cy="12" r="10" /><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" /></Icon>,
Terminal: (p) => <Icon {...p}><path d="m4 17 6-6-6-6M12 19h8" /></Icon>,
Logo: ({ size = 28 }) => (
<svg width={size} height={size} viewBox="0 0 32 32" fill="none">
<defs>
<linearGradient id="logoG" x1="0" y1="0" x2="32" y2="32" gradientUnits="userSpaceOnUse">
<stop stopColor="var(--accent)" />
<stop offset="1" stopColor="var(--accent-2)" />
</linearGradient>
</defs>
<rect x="2" y="2" width="28" height="28" rx="8" fill="none" stroke="url(#logoG)" strokeWidth="1.5" />
<path d="M10 22 L16 8 L22 22 M12.5 17 H19.5" stroke="url(#logoG)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" fill="none" />
</svg>
),
};
window.Icons = Icons;
+163
View File
@@ -0,0 +1,163 @@
// Top nav — minimal, sticky, blurred
const Nav = ({ onCmdK, theme, onToggleTheme, accent, lang, onToggleLang }) => {
const [scrolled, setScrolled] = React.useState(false);
React.useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 12);
window.addEventListener("scroll", onScroll, { passive: true });
return () => window.removeEventListener("scroll", onScroll);
}, []);
const links = [
{ id: "work", label: "Services" },
{ id: "stack", label: "Stack" },
{ id: "agents", label: "Agents" },
{ id: "timeline", label: "Timeline" },
{ id: "contact", label: "Contact" },
];
return (
<nav style={{
position: "fixed",
top: 0,
left: 0,
right: 0,
zIndex: 50,
borderBottom: scrolled ? "1px solid var(--line)" : "1px solid transparent",
background: scrolled ? "color-mix(in srgb, var(--bg) 75%, transparent)" : "transparent",
backdropFilter: scrolled ? "blur(14px)" : "none",
WebkitBackdropFilter: scrolled ? "blur(14px)" : "none",
transition: "all 0.25s ease",
}}>
<div className="container" style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: 64,
}}>
{/* Brand */}
<a href="#top" style={{ display: "flex", alignItems: "center", gap: 10 }}>
<Icons.Logo size={26} />
<span style={{ fontWeight: 600, fontSize: 15, letterSpacing: "-0.01em" }}>{PORTFOLIO_DATA.brand.name}</span>
<span style={{
fontFamily: "var(--font-mono)",
fontSize: 11,
color: "var(--text-faint)",
padding: "2px 7px",
border: "1px solid var(--line)",
borderRadius: 999,
marginLeft: 4,
}}>v.2026</span>
</a>
{/* Center links */}
<div style={{ display: "flex", gap: 4, alignItems: "center" }} className="nav-links">
{links.map(l => (
<a key={l.id} href={`#${l.id}`} style={{
fontSize: 13.5,
color: "var(--text-dim)",
padding: "6px 12px",
borderRadius: 8,
transition: "all 0.15s",
}}
onMouseEnter={e => { e.currentTarget.style.color = "var(--text)"; e.currentTarget.style.background = "var(--line)"; }}
onMouseLeave={e => { e.currentTarget.style.color = "var(--text-dim)"; e.currentTarget.style.background = "transparent"; }}
>{l.label}</a>
))}
</div>
{/* Right: ⌘K, theme, hire */}
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
<button onClick={onCmdK} style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "6px 8px 6px 12px",
background: "transparent",
border: "1px solid var(--line)",
borderRadius: 8,
color: "var(--text-dim)",
fontSize: 12.5,
}}>
<Icons.Search size={13} />
<span>Search</span>
<kbd style={{
fontFamily: "var(--font-mono)",
fontSize: 10.5,
padding: "2px 5px",
border: "1px solid var(--line)",
borderRadius: 4,
color: "var(--text-faint)",
}}>K</kbd>
</button>
<button onClick={onToggleTheme} aria-label="Toggle theme" style={{
width: 34, height: 34,
display: "grid", placeItems: "center",
background: "transparent",
border: "1px solid var(--line)",
borderRadius: 8,
color: "var(--text-dim)",
}}>
{theme === "dark" ? <Icons.Sun size={15} /> : <Icons.Moon size={15} />}
</button>
{/* EN/BN toggle */}
<button onClick={onToggleLang} style={{
padding: "6px 10px",
background: "transparent",
border: "1px solid var(--line)",
borderRadius: 8,
color: "var(--text-dim)",
fontSize: 12,
fontWeight: 600,
cursor: "pointer",
letterSpacing: "0.04em",
}}>
{lang === "en" ? "বাং" : "EN"}
</button>
{/* Get a Quote */}
<a href="mailto:hello@auraajenticai.cloud?subject=Project Quote" style={{
padding: "7px 13px",
background: "transparent",
border: "1px solid var(--line)",
borderRadius: 8,
color: "var(--text-dim)",
fontSize: 13,
fontWeight: 500,
textDecoration: "none",
whiteSpace: "nowrap",
}}>
Get a Quote
</a>
{/* Client Login */}
<a href="https://auth.auraajenticai.cloud/login" style={{
display: "flex",
alignItems: "center",
gap: 6,
padding: "8px 14px",
background: "var(--text)",
color: "var(--bg)",
fontSize: 13,
fontWeight: 500,
borderRadius: 8,
textDecoration: "none",
whiteSpace: "nowrap",
}}>
Client Login <Icons.Arrow size={13} />
</a>
</div>
</div>
<style>{`
@media (max-width: 880px) {
.nav-links { display: none !important; }
}
`}</style>
</nav>
);
};
window.Nav = Nav;
+332
View File
@@ -0,0 +1,332 @@
// Projects — SaaS-card grid with featured first
const colorMap = {
violet: { bg: "rgba(124, 92, 255, 0.08)", border: "rgba(124, 92, 255, 0.25)", text: "#a18bff" },
cyan: { bg: "rgba(0, 212, 255, 0.08)", border: "rgba(0, 212, 255, 0.25)", text: "#5ee0ff" },
green: { bg: "rgba(74, 222, 128, 0.08)", border: "rgba(74, 222, 128, 0.25)", text: "#7ee8a3" },
amber: { bg: "rgba(251, 191, 36, 0.08)", border: "rgba(251, 191, 36, 0.25)", text: "#fcd34d" },
};
const ProjectCard = ({ p }) => {
const tint = colorMap[p.color] || colorMap.violet;
const [hover, setHover] = React.useState(false);
return (
<article
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
style={{
position: "relative",
gridColumn: "span 1",
background: "var(--bg-card)",
border: "1px solid var(--line)",
borderRadius: 16,
overflow: "hidden",
cursor: "pointer",
transition: "all 0.25s ease",
transform: hover ? "translateY(-2px)" : "none",
boxShadow: hover ? `0 24px 60px -20px ${tint.border}` : "none",
}}>
{/* hover sheen */}
<div style={{
position: "absolute",
inset: 0,
background: `radial-gradient(circle at 30% 0%, ${tint.bg}, transparent 60%)`,
opacity: hover ? 1 : 0.5,
transition: "opacity 0.25s",
pointerEvents: "none",
}} />
{/* Decorative top — small visual specific to service type */}
<div style={{
position: "relative",
height: 160,
borderBottom: "1px solid var(--line)",
overflow: "hidden",
background: `linear-gradient(180deg, color-mix(in srgb, ${tint.text} 8%, var(--bg-card)), var(--bg-card))`,
}}>
<ProjectVisual id={p.id} tint={tint} hover={hover} />
<div style={{
position: "absolute",
top: 16, left: 16,
display: "flex",
alignItems: "center",
gap: 8,
padding: "5px 10px",
background: "color-mix(in srgb, var(--bg) 60%, transparent)",
backdropFilter: "blur(6px)",
border: `1px solid ${tint.border}`,
borderRadius: 999,
fontSize: 11,
fontFamily: "var(--font-mono)",
color: tint.text,
textTransform: "uppercase",
letterSpacing: "0.06em",
}}>
<span style={{ width: 6, height: 6, borderRadius: "50%", background: tint.text }} />
{p.kind}
</div>
<div style={{
position: "absolute",
top: 16, right: 16,
fontFamily: "var(--font-mono)",
fontSize: 11,
color: tint.text,
padding: "2px 8px",
background: tint.bg,
border: `1px solid ${tint.border}`,
borderRadius: 4,
}}>{p.kind}</div>
</div>
<div style={{ padding: 24, position: "relative" }}>
<h3 style={{
margin: 0,
fontSize: 20,
fontWeight: 500,
letterSpacing: "-0.02em",
}}>{p.name}</h3>
{p.nameBn && (
<div style={{
marginTop: 4,
fontSize: 12,
color: "var(--text-faint)",
fontFamily: "var(--font-sans)",
}}>{p.nameBn}</div>
)}
<p style={{
marginTop: 12,
marginBottom: 20,
fontSize: 14,
lineHeight: 1.55,
color: "var(--text-dim)",
}}>{p.description}</p>
{/* Stack badges */}
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 20 }}>
{p.stack.map(s => (
<span key={s} style={{
padding: "4px 10px",
background: "var(--bg-elev)",
border: "1px solid var(--line)",
borderRadius: 6,
fontFamily: "var(--font-mono)",
fontSize: 11,
color: "var(--text-dim)",
}}>{s}</span>
))}
</div>
{/* Footer: impact + actions */}
<div style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
paddingTop: 18,
borderTop: "1px solid var(--line)",
gap: 12,
}}>
<div>
<div style={{
fontSize: featured ? 22 : 18,
fontWeight: 500,
letterSpacing: "-0.01em",
color: tint.text,
}}>{p.impact.primary}</div>
<div style={{
marginTop: 2,
fontSize: 11,
fontFamily: "var(--font-mono)",
color: "var(--text-faint)",
textTransform: "uppercase",
letterSpacing: "0.06em",
}}>{p.impact.secondary}</div>
</div>
<div style={{ display: "flex", gap: 6 }}>
<button style={{
width: 34, height: 34,
display: "grid", placeItems: "center",
background: "transparent",
border: "1px solid var(--line)",
borderRadius: 8,
color: "var(--text-dim)",
}} title="GitHub"><Icons.Github size={14} /></button>
<button style={{
padding: "0 14px", height: 34,
display: "inline-flex", alignItems: "center", gap: 6,
background: "var(--text)",
color: "var(--bg)",
border: "none",
borderRadius: 8,
fontSize: 13,
fontWeight: 500,
}}>Demo <Icons.ArrowUpRight size={12} /></button>
</div>
</div>
</div>
</article>
);
};
// Tiny SVG visual per service — gives each card a distinct "product preview"
const ProjectVisual = ({ id, tint, hover }) => {
if (id === "ai-agent-automation") {
return (
<svg viewBox="0 0 600 220" style={{ width: "100%", height: "100%", position: "absolute", inset: 0 }}>
<defs>
<linearGradient id="aura-g" x1="0" x2="1">
<stop stopColor={tint.text} stopOpacity="0.6"/>
<stop offset="1" stopColor={tint.text} stopOpacity="0.05"/>
</linearGradient>
</defs>
{/* nodes */}
{[
[120, 60], [220, 110], [320, 70], [420, 130], [500, 90],
[180, 160], [280, 180], [380, 50], [460, 180],
].map(([x, y], i) => (
<g key={i}>
<circle cx={x} cy={y} r="4" fill={tint.text} opacity="0.9" />
<circle cx={x} cy={y} r="14" fill={tint.text} opacity={hover ? 0.18 : 0.08}>
<animate attributeName="r" from="4" to="22" dur={`${2 + i*0.3}s`} repeatCount="indefinite" />
<animate attributeName="opacity" from="0.25" to="0" dur={`${2 + i*0.3}s`} repeatCount="indefinite" />
</circle>
</g>
))}
{/* connectors */}
<g stroke={tint.text} strokeOpacity="0.35" strokeWidth="1" fill="none">
<path d="M120,60 L220,110 L320,70 L420,130 L500,90"/>
<path d="M180,160 L280,180 L380,50 L460,180"/>
<path d="M120,60 L180,160 M220,110 L280,180 M320,70 L380,50 M420,130 L460,180"/>
</g>
</svg>
);
}
if (id === "scraping-data-pipeline") {
// bar chart
const bars = [40, 64, 52, 78, 92, 70, 110, 95, 130, 118, 142];
return (
<svg viewBox="0 0 600 160" style={{ width: "100%", height: "100%", position: "absolute", inset: 0 }} preserveAspectRatio="xMidYMid meet">
{bars.map((h, i) => (
<rect key={i} x={50 + i*45} y={150 - h} width="22" height={h} rx="3" fill={tint.text} opacity={0.4 + i*0.05}>
<animate attributeName="height" from="0" to={h} dur="0.8s" begin={`${i*0.05}s`} fill="freeze" />
<animate attributeName="y" from="150" to={150-h} dur="0.8s" begin={`${i*0.05}s`} fill="freeze" />
</rect>
))}
<line x1="40" y1="150" x2="580" y2="150" stroke={tint.border} />
</svg>
);
}
if (id === "web3-blockchain") {
// chain dots
const chains = ["ETH","ARB","OP","BASE","SOL","ZK","POL","BNB","AVAX","CELO","XLM","TRX"];
return (
<div style={{ position: "absolute", inset: 0, display: "grid", gridTemplateColumns: "repeat(6, 1fr)", padding: 24, gap: 8, alignContent: "center" }}>
{chains.map((c, i) => (
<div key={c} style={{
padding: "8px 0",
textAlign: "center",
border: `1px solid ${tint.border}`,
borderRadius: 6,
fontFamily: "var(--font-mono)",
fontSize: 11,
color: tint.text,
background: tint.bg,
animation: `float-up 0.5s ${i*0.04}s both`,
}}>{c}</div>
))}
</div>
);
}
if (id === "mt5-ea-trading") {
// line chart
return (
<svg viewBox="0 0 600 160" style={{ width: "100%", height: "100%", position: "absolute", inset: 0 }}>
<defs>
<linearGradient id="ea-g" x1="0" y1="0" x2="0" y2="1">
<stop stopColor={tint.text} stopOpacity="0.4"/>
<stop offset="1" stopColor={tint.text} stopOpacity="0"/>
</linearGradient>
</defs>
<path d="M0,110 L60,90 L120,100 L180,70 L240,80 L300,55 L360,70 L420,40 L480,55 L540,30 L600,45 L600,160 L0,160 Z" fill="url(#ea-g)" />
<path d="M0,110 L60,90 L120,100 L180,70 L240,80 L300,55 L360,70 L420,40 L480,55 L540,30 L600,45" stroke={tint.text} strokeWidth="1.5" fill="none" />
{[60,180,300,420,540].map((x, i) => (
<circle key={i} cx={x} cy={[90,70,55,40,30][i]} r="3" fill={tint.text} />
))}
</svg>
);
}
if (id === "web-app-dev") {
// schema → grid morph
return (
<svg viewBox="0 0 600 200" style={{ width: "100%", height: "100%", position: "absolute", inset: 0 }}>
{/* schema box */}
<g transform="translate(60, 50)">
<rect width="160" height="100" rx="8" fill="none" stroke={tint.border} />
{[20, 40, 60, 80].map(y => <line key={y} x1="12" y1={y} x2="148" y2={y} stroke={tint.border} strokeOpacity="0.5" strokeDasharray="2 2" />)}
{[20, 40, 60, 80].map(y => <rect key={y} x="14" y={y-4} width="60" height="6" fill={tint.text} opacity="0.4" rx="1" />)}
</g>
{/* arrow */}
<g transform="translate(240, 90)" stroke={tint.text} fill="none" strokeWidth="1.5">
<line x1="0" y1="10" x2="80" y2="10" />
<polyline points="70,4 80,10 70,16" />
</g>
{/* dashboard grid */}
<g transform="translate(360, 30)">
<rect width="200" height="140" rx="8" fill={tint.bg} stroke={tint.border} />
<rect x="12" y="12" width="80" height="36" rx="4" fill={tint.text} opacity="0.5" />
<rect x="100" y="12" width="88" height="36" rx="4" fill={tint.text} opacity="0.3" />
<rect x="12" y="56" width="176" height="72" rx="4" fill={tint.text} opacity="0.18" />
</g>
</svg>
);
}
if (id === "infra-devops") {
// chat bubbles
return (
<div style={{ position: "absolute", inset: 0, padding: "20px 28px", display: "flex", flexDirection: "column", gap: 8, justifyContent: "center", fontFamily: "var(--font-mono)", fontSize: 11 }}>
<div style={{ alignSelf: "flex-start", padding: "8px 12px", background: "var(--bg-elev)", border: "1px solid var(--line)", borderRadius: 12, color: "var(--text-dim)", maxWidth: "70%" }}>How do I rotate my API key?</div>
<div style={{ alignSelf: "flex-end", padding: "8px 12px", background: tint.bg, border: `1px solid ${tint.border}`, borderRadius: 12, color: tint.text, maxWidth: "70%" }}>Settings API Rotate. New key live in &lt;5s.</div>
<div style={{ alignSelf: "flex-start", padding: "8px 12px", background: "var(--bg-elev)", border: "1px solid var(--line)", borderRadius: 12, color: "var(--text-dim)", maxWidth: "70%", display: "flex", gap: 4, alignItems: "center" }}>
<span style={{ width: 4, height: 4, borderRadius: "50%", background: "var(--text-dim)", animation: "pulse-dot 1.2s infinite" }} />
<span style={{ width: 4, height: 4, borderRadius: "50%", background: "var(--text-dim)", animation: "pulse-dot 1.2s 0.2s infinite" }} />
<span style={{ width: 4, height: 4, borderRadius: "50%", background: "var(--text-dim)", animation: "pulse-dot 1.2s 0.4s infinite" }} />
</div>
</div>
);
}
return null;
};
const Projects = () => {
const D = PORTFOLIO_DATA;
return (
<section id="work" style={{ padding: "120px 0", borderTop: "1px solid var(--line)" }}>
<div className="container">
<SectionHeader
eyebrow="What we build"
num="03 / 06"
title={<>Six services. One team. All <span style={{ fontFamily: "var(--font-serif)", fontStyle: "italic", fontWeight: 400 }}>production-grade</span>.</>}
sub="From landing pages to AI agent runtimes — we take your idea from spec to live deployment."
/>
<div style={{
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
gap: 16,
}} className="proj-grid">
{D.services.map(p => <ProjectCard key={p.id} p={p} />)}
</div>
<style>{`
@media (max-width: 880px) {
.proj-grid { grid-template-columns: 1fr !important; }
.proj-grid > article { grid-column: span 1 !important; }
}
`}</style>
</div>
</section>
);
};
window.Projects = Projects;
+242
View File
@@ -0,0 +1,242 @@
// SectionHeader + About + Skills (interactive tabs)
const SectionHeader = ({ eyebrow, title, sub, num }) => (
<div style={{ marginBottom: 56 }}>
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 18 }}>
<span className="eyebrow">{eyebrow}</span>
{num && <span style={{
fontFamily: "var(--font-mono)",
fontSize: 11,
color: "var(--text-faint)",
}}>{num}</span>}
</div>
<h2 style={{
fontSize: "clamp(32px, 4.6vw, 54px)",
fontWeight: 500,
letterSpacing: "-0.03em",
lineHeight: 1.05,
margin: 0,
maxWidth: 820,
}}>{title}</h2>
{sub && <p style={{
marginTop: 18,
fontSize: 17,
lineHeight: 1.55,
color: "var(--text-dim)",
maxWidth: 640,
}}>{sub}</p>}
</div>
);
const About = () => {
const D = PORTFOLIO_DATA;
return (
<section id="about" style={{ padding: "120px 0", borderTop: "1px solid var(--line)" }}>
<div className="container">
<SectionHeader
eyebrow="About"
num="01 / 06"
title={<>I architect the systems that act not the demos that <span style={{ fontFamily: "var(--font-serif)", fontStyle: "italic", fontWeight: 400 }}>impress</span>.</>}
/>
<div style={{
display: "grid",
gridTemplateColumns: "1.4fr 1fr",
gap: 48,
alignItems: "start",
}} className="about-grid">
<div>
<p style={{
fontSize: 18,
lineHeight: 1.65,
color: "var(--text-dim)",
margin: 0,
}}>{D.about.pitch}</p>
<ul style={{
marginTop: 32,
padding: 0,
listStyle: "none",
display: "grid",
gap: 14,
}}>
{D.about.bullets.map((b, i) => (
<li key={i} style={{
display: "flex",
gap: 14,
fontSize: 15,
lineHeight: 1.55,
color: "var(--text)",
}}>
<span style={{
flex: "0 0 auto",
marginTop: 8,
width: 18, height: 1,
background: "var(--accent)",
}} />
<span>{b}</span>
</li>
))}
</ul>
</div>
{/* Side card: at a glance */}
<div className="panel" style={{ padding: 28 }}>
<div className="eyebrow" style={{ marginBottom: 20 }}>At a glance</div>
<div style={{ display: "grid", gap: 16 }}>
{[
["Discipline", "Agentic systems · Full-stack"],
["Years", `${D.brand.yearsExp}+ shipping`],
["Stack range", "TS · Python · Solidity"],
["Working with", "Founders · CTOs · Platform leads"],
["Engagement", "Embedded · Lead architect"],
["Location", D.brand.location],
].map(([k, v]) => (
<div key={k} style={{
display: "grid",
gridTemplateColumns: "110px 1fr",
gap: 16,
fontSize: 13.5,
paddingBottom: 14,
borderBottom: "1px solid var(--line)",
}}>
<span style={{ color: "var(--text-faint)", fontFamily: "var(--font-mono)", fontSize: 11.5, textTransform: "uppercase", letterSpacing: "0.06em", paddingTop: 2 }}>{k}</span>
<span style={{ color: "var(--text)" }}>{v}</span>
</div>
))}
</div>
<a href="#contact" style={{
display: "inline-flex",
alignItems: "center",
gap: 8,
marginTop: 8,
fontSize: 13,
color: "var(--accent)",
}}>
Start a conversation <Icons.ArrowUpRight size={13} />
</a>
</div>
</div>
<style>{`
@media (max-width: 880px) {
.about-grid { grid-template-columns: 1fr !important; gap: 32px !important; }
}
`}</style>
</div>
</section>
);
};
const Skills = () => {
const D = PORTFOLIO_DATA;
const cats = Object.keys(D.stack);
const [active, setActive] = React.useState(cats[0]);
const items = D.stack[active];
const catIcon = {
"Frontend": Icons.Code,
"Backend": Icons.Server,
"AI / Agents": Icons.Sparkles,
"Web3": Icons.Wallet,
"DevOps": Icons.Cloud,
};
return (
<section id="stack" style={{ padding: "120px 0", borderTop: "1px solid var(--line)" }}>
<div className="container">
<SectionHeader
eyebrow="Stack"
num="02 / 06"
title="Tools I reach for, weighted by reps."
sub="Numbers are honest signal — not a vanity bar. They reflect production hours, not tutorials watched."
/>
{/* Category tabs */}
<div style={{
display: "flex",
gap: 6,
padding: 6,
background: "var(--bg-card)",
border: "1px solid var(--line)",
borderRadius: 12,
width: "fit-content",
flexWrap: "wrap",
marginBottom: 32,
}}>
{cats.map(c => {
const I = catIcon[c];
const on = active === c;
return (
<button key={c} onClick={() => setActive(c)} style={{
display: "flex",
alignItems: "center",
gap: 8,
padding: "9px 14px",
background: on ? "var(--bg-elev)" : "transparent",
color: on ? "var(--text)" : "var(--text-dim)",
border: on ? "1px solid var(--line-strong)" : "1px solid transparent",
borderRadius: 8,
fontSize: 13,
fontWeight: 500,
transition: "all 0.15s",
}}>
<I size={13} /> {c}
</button>
);
})}
</div>
{/* Items */}
<div style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))",
gap: 12,
}}>
{items.map((s, i) => (
<div key={s.name} className="panel" style={{
padding: "20px 22px",
animation: `float-up 0.5s ${i * 0.05}s both`,
}}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline", marginBottom: 14 }}>
<span style={{ fontSize: 14.5, fontWeight: 500 }}>{s.name}</span>
<span style={{
fontFamily: "var(--font-mono)",
fontSize: 11,
color: "var(--text-faint)",
}}>{s.level}</span>
</div>
<div style={{
height: 3,
background: "var(--line)",
borderRadius: 2,
overflow: "hidden",
position: "relative",
}}>
<div style={{
width: `${s.level}%`,
height: "100%",
background: "linear-gradient(90deg, var(--accent), var(--accent-2))",
borderRadius: 2,
animation: `grow-w 0.9s ${0.1 + i * 0.04}s both cubic-bezier(0.2,0.8,0.2,1)`,
transformOrigin: "left",
}} />
</div>
</div>
))}
</div>
<style>{`
@keyframes grow-w {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
`}</style>
</div>
</section>
);
};
window.About = About;
window.Skills = Skills;
window.SectionHeader = SectionHeader;
+535
View File
@@ -0,0 +1,535 @@
// Timeline + Contact + Footer
const Timeline = () => {
const D = PORTFOLIO_DATA;
return (
<section id="timeline" style={{ padding: "120px 0", borderTop: "1px solid var(--line)" }}>
<div className="container">
<SectionHeader
eyebrow="Trajectory"
num="05 / 06"
title="Seven years, one through-line: shipping systems that work in production."
/>
<div style={{ position: "relative", paddingLeft: 32 }}>
{/* spine */}
<div style={{
position: "absolute",
left: 7, top: 6, bottom: 6,
width: 1,
background: "linear-gradient(180deg, var(--accent), var(--line) 80%)",
}} />
{D.experience.map((e, i) => (
<div key={i} style={{
position: "relative",
paddingBottom: i < D.experience.length - 1 ? 40 : 0,
animation: `float-up 0.5s ${i * 0.08}s both`,
}}>
{/* node */}
<div style={{
position: "absolute",
left: -32,
top: 4,
width: 15, height: 15,
borderRadius: "50%",
background: "var(--bg)",
border: `2px solid ${i === 0 ? "var(--accent)" : "var(--line-strong)"}`,
boxShadow: i === 0 ? "0 0 16px var(--accent-glow)" : "none",
}}>
{i === 0 && <div style={{
position: "absolute",
inset: 2,
borderRadius: "50%",
background: "var(--accent)",
animation: "pulse-dot 2s infinite",
}} />}
</div>
<div style={{
display: "grid",
gridTemplateColumns: "180px 1fr",
gap: 32,
alignItems: "start",
}} className="tl-row">
<div>
<div style={{
fontFamily: "var(--font-mono)",
fontSize: 11.5,
color: "var(--text-faint)",
textTransform: "uppercase",
letterSpacing: "0.06em",
}}>{e.year}</div>
<span style={{
display: "inline-block",
marginTop: 8,
padding: "2px 8px",
border: "1px solid var(--line)",
borderRadius: 4,
fontSize: 10.5,
color: "var(--text-dim)",
fontFamily: "var(--font-mono)",
}}>{e.kind}</span>
</div>
<div>
<h3 style={{
margin: 0,
fontSize: 19,
fontWeight: 500,
letterSpacing: "-0.01em",
}}>{e.role}</h3>
<div style={{
marginTop: 4,
fontSize: 14,
color: "var(--accent-2)",
}}>{e.company}</div>
<p style={{
marginTop: 10,
marginBottom: 0,
fontSize: 14,
lineHeight: 1.6,
color: "var(--text-dim)",
maxWidth: 600,
}}>{e.detail}</p>
</div>
</div>
</div>
))}
</div>
<style>{`
@media (max-width: 720px) {
.tl-row { grid-template-columns: 1fr !important; gap: 8px !important; }
}
`}</style>
</div>
</section>
);
};
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 (
<label style={{ display: "block" }}>
<div style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 8,
fontSize: 12,
fontFamily: "var(--font-mono)",
color: hasErr ? "var(--danger)" : "var(--text-faint)",
textTransform: "uppercase",
letterSpacing: "0.06em",
}}>
<span>{label}</span>
{hasErr && <span>{hasErr}</span>}
</div>
<Tag
type={type}
value={form[id]}
onChange={(ev) => setForm(f => ({ ...f, [id]: ev.target.value }))}
placeholder={placeholder}
rows={as === "textarea" ? 5 : undefined}
style={{
width: "100%",
background: "var(--bg-elev)",
color: "var(--text)",
border: `1px solid ${hasErr ? "var(--danger)" : "var(--line)"}`,
borderRadius: 10,
padding: "12px 14px",
fontSize: 14,
fontFamily: "var(--font-sans)",
resize: as === "textarea" ? "vertical" : "none",
outline: "none",
transition: "border 0.15s",
}}
onFocus={(ev) => ev.target.style.borderColor = hasErr ? "var(--danger)" : "var(--accent)"}
onBlur={(ev) => ev.target.style.borderColor = hasErr ? "var(--danger)" : "var(--line)"}
/>
</label>
);
};
const SelectField = ({ id, label, options, required = false }) => {
const hasErr = errors[id];
return (
<label style={{ display: "block" }}>
<div style={{
display: "flex",
justifyContent: "space-between",
marginBottom: 8,
fontSize: 12,
fontFamily: "var(--font-mono)",
color: hasErr ? "var(--danger)" : "var(--text-faint)",
textTransform: "uppercase",
letterSpacing: "0.06em",
}}>
<span>{label}</span>
{hasErr && <span>{hasErr}</span>}
</div>
<select
value={form[id]}
onChange={(ev) => setForm(f => ({ ...f, [id]: ev.target.value }))}
style={{
width: "100%",
background: "var(--bg-elev)",
color: form[id] ? "var(--text)" : "var(--text-faint)",
border: `1px solid ${hasErr ? "var(--danger)" : "var(--line)"}`,
borderRadius: 10,
padding: "12px 14px",
fontSize: 14,
fontFamily: "var(--font-sans)",
outline: "none",
transition: "border 0.15s",
cursor: "pointer",
}}
onFocus={(ev) => ev.target.style.borderColor = hasErr ? "var(--danger)" : "var(--accent)"}
onBlur={(ev) => ev.target.style.borderColor = hasErr ? "var(--danger)" : "var(--line)"}
>
<option value="">Select</option>
{options.map(o => <option key={o} value={o}>{o}</option>)}
</select>
</label>
);
};
return (
<section id="contact" style={{ padding: "120px 0", borderTop: "1px solid var(--line)" }}>
<div className="container">
<SectionHeader
eyebrow="Contact"
num="06 / 06"
title={<>Have a project that needs to <span style={{ fontFamily: "var(--font-serif)", fontStyle: "italic", fontWeight: 400 }}>actually ship</span>?</>}
sub="Tell us what you're building — we'll respond within 24 hours with a plan and a quote."
/>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 48, alignItems: "start" }} className="contact-grid">
{/* form */}
<form onSubmit={submit} className="panel" style={{ padding: 32, display: "grid", gap: 18 }}>
{status === "sent" ? (
<div style={{ textAlign: "center", padding: "40px 20px" }}>
<div style={{
width: 56, height: 56,
margin: "0 auto 20px",
borderRadius: "50%",
background: "color-mix(in srgb, var(--good) 15%, transparent)",
border: "1px solid var(--good)",
display: "grid", placeItems: "center",
color: "var(--good)",
}}><Icons.Check size={26} /></div>
<h3 style={{ margin: 0, fontSize: 22, fontWeight: 500, letterSpacing: "-0.02em" }}>
{lang === "en" ? "We'll be in touch!" : "বার্তা পাঠানো হয়েছে!"}
</h3>
<p style={{ marginTop: 10, color: "var(--text-dim)", fontSize: 14 }}>
{lang === "en"
? "We'll get back to you within 24 hours!"
: "আমরা ২৪ ঘণ্টার মধ্যে যোগাযোগ করবো!"}
</p>
<button type="button" onClick={() => { setForm({ name: "", email: "", service: "", budget: "", message: "" }); setStatus("idle"); }} style={{
marginTop: 20,
padding: "8px 16px",
background: "transparent",
color: "var(--text-dim)",
border: "1px solid var(--line)",
borderRadius: 8,
fontSize: 13,
cursor: "pointer",
}}>{lang === "en" ? "Send another" : "আবার পাঠান"}</button>
</div>
) : (
<>
<Field id="name" label="Name" placeholder="Your name" />
<Field id="email" label="Email" type="email" placeholder="you@company.com" />
<SelectField id="service" label="Service" options={SERVICES} required />
<SelectField id="budget" label="Budget (optional)" options={BUDGETS} />
<Field id="message" label="Project Brief" as="textarea" placeholder="What are you trying to build, and where are you stuck?" />
<button type="submit" disabled={status === "sending"} style={{
marginTop: 4,
padding: "12px 18px",
background: "var(--text)",
color: "var(--bg)",
border: "none",
borderRadius: 10,
fontSize: 14,
fontWeight: 500,
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
gap: 8,
opacity: status === "sending" ? 0.6 : 1,
cursor: status === "sending" ? "wait" : "pointer",
}}>
{status === "sending" ? "Sending…" : <>Send message <Icons.Send size={13} /></>}
</button>
</>
)}
</form>
{/* meta + chatbot teaser */}
<div style={{ display: "grid", gap: 16 }}>
<div className="panel" style={{ padding: 28 }}>
<div className="eyebrow" style={{ marginBottom: 18 }}>Direct</div>
<a href={`mailto:${D.brand.email}`} style={{
fontSize: 22,
fontWeight: 500,
letterSpacing: "-0.02em",
display: "inline-flex",
alignItems: "center",
gap: 10,
}}>
<Icons.Mail size={18} /> {D.brand.email}
</a>
<div style={{ marginTop: 24, display: "flex", gap: 8 }}>
{[
{ i: Icons.Github, l: "GitHub", h: D.brand.socials.github },
{ i: Icons.LinkedIn, l: "LinkedIn", h: D.brand.socials.linkedin },
{ i: Icons.Twitter, l: "Twitter", h: D.brand.socials.twitter },
].map(({ i: I, l, h }) => (
<a key={l} href={h} title={l} style={{
width: 40, height: 40,
display: "grid", placeItems: "center",
border: "1px solid var(--line)",
borderRadius: 8,
color: "var(--text-dim)",
transition: "all 0.15s",
}}
onMouseEnter={ev => { ev.currentTarget.style.color = "var(--text)"; ev.currentTarget.style.borderColor = "var(--line-strong)"; }}
onMouseLeave={ev => { ev.currentTarget.style.color = "var(--text-dim)"; ev.currentTarget.style.borderColor = "var(--line)"; }}
><I size={15} /></a>
))}
</div>
</div>
<ChatbotWidget />
</div>
</div>
<style>{`
@media (max-width: 880px) {
.contact-grid { grid-template-columns: 1fr !important; gap: 24px !important; }
}
`}</style>
</div>
</section>
);
};
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 (
<div className="panel" style={{ padding: 0, overflow: "hidden" }}>
<div style={{
padding: "16px 20px",
borderBottom: "1px solid var(--line)",
display: "flex",
alignItems: "center",
gap: 10,
}}>
<div style={{
width: 28, height: 28,
display: "grid", placeItems: "center",
borderRadius: 7,
background: "var(--accent-glow)",
color: "var(--accent)",
}}><Icons.Sparkles size={14} /></div>
<div>
<div style={{ fontSize: 13.5, fontWeight: 500 }}>Ask about my work</div>
<div style={{ fontSize: 11, color: "var(--text-faint)", fontFamily: "var(--font-mono)" }}>powered by claude · live</div>
</div>
<span style={{
marginLeft: "auto",
fontSize: 10.5,
padding: "2px 8px",
background: "color-mix(in srgb, var(--good) 15%, transparent)",
color: "var(--good)",
border: "1px solid color-mix(in srgb, var(--good) 30%, transparent)",
borderRadius: 999,
fontFamily: "var(--font-mono)",
textTransform: "uppercase",
letterSpacing: "0.06em",
}}>online</span>
</div>
<div ref={scrollRef} style={{
padding: 16,
height: 200,
overflowY: "auto",
display: "flex",
flexDirection: "column",
gap: 10,
fontSize: 13,
}}>
{msgs.map((m, i) => (
<div key={i} style={{
alignSelf: m.role === "user" ? "flex-end" : "flex-start",
maxWidth: "85%",
padding: "8px 12px",
background: m.role === "user" ? "var(--accent)" : "var(--bg-elev)",
color: m.role === "user" ? "white" : "var(--text)",
border: m.role === "user" ? "none" : "1px solid var(--line)",
borderRadius: 12,
lineHeight: 1.5,
}}>{m.text}</div>
))}
{thinking && (
<div style={{
alignSelf: "flex-start",
padding: "8px 12px",
background: "var(--bg-elev)",
border: "1px solid var(--line)",
borderRadius: 12,
display: "flex", gap: 4, alignItems: "center",
}}>
{[0,1,2].map(i => (
<span key={i} style={{
width: 5, height: 5, borderRadius: "50%",
background: "var(--text-faint)",
animation: `pulse-dot 1.2s ${i * 0.15}s infinite`,
}} />
))}
</div>
)}
</div>
<form onSubmit={(e) => { e.preventDefault(); send(); }} style={{
display: "flex",
gap: 8,
padding: 12,
borderTop: "1px solid var(--line)",
background: "var(--bg-elev)",
}}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Ask about a project, stack, or timeline…"
style={{
flex: 1,
background: "var(--bg-card)",
color: "var(--text)",
border: "1px solid var(--line)",
borderRadius: 8,
padding: "9px 12px",
fontSize: 13,
fontFamily: "inherit",
outline: "none",
}}
/>
<button type="submit" style={{
padding: "0 12px",
background: "var(--text)",
color: "var(--bg)",
border: "none",
borderRadius: 8,
fontSize: 12,
fontWeight: 500,
display: "flex", alignItems: "center", gap: 6,
}}><Icons.Send size={12} /></button>
</form>
</div>
);
};
const Footer = () => {
const D = PORTFOLIO_DATA;
return (
<footer style={{
borderTop: "1px solid var(--line)",
padding: "48px 0 32px",
}}>
<div className="container" style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
flexWrap: "wrap",
gap: 16,
}}>
<div style={{ display: "flex", alignItems: "center", gap: 12 }}>
<Icons.Logo size={22} />
<span style={{ fontSize: 13, color: "var(--text-dim)" }}>{D.brand.name} · {D.brand.domain}</span>
</div>
<div style={{ fontSize: 12, color: "var(--text-faint)", fontFamily: "var(--font-mono)" }}>
© 2026 Built with intent.
</div>
</div>
</footer>
);
};
window.Timeline = Timeline;
window.Contact = Contact;
window.Footer = Footer;
+425
View File
@@ -0,0 +1,425 @@
// tweaks-panel.jsx
// Reusable Tweaks shell + form-control helpers.
//
// Owns the host protocol (listens for __activate_edit_mode / __deactivate_edit_mode,
// posts __edit_mode_available / __edit_mode_set_keys / __edit_mode_dismissed) so
// individual prototypes don't re-roll it. Ships a consistent set of controls so you
// don't hand-draw <input type="range">, segmented radios, steppers, etc.
//
// Usage (in an HTML file that loads React + Babel):
//
// const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
// "primaryColor": "#D97757",
// "fontSize": 16,
// "density": "regular",
// "dark": false
// }/*EDITMODE-END*/;
//
// function App() {
// const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
// return (
// <div style={{ fontSize: t.fontSize, color: t.primaryColor }}>
// Hello
// <TweaksPanel>
// <TweakSection label="Typography" />
// <TweakSlider label="Font size" value={t.fontSize} min={10} max={32} unit="px"
// onChange={(v) => setTweak('fontSize', v)} />
// <TweakRadio label="Density" value={t.density}
// options={['compact', 'regular', 'comfy']}
// onChange={(v) => setTweak('density', v)} />
// <TweakSection label="Theme" />
// <TweakColor label="Primary" value={t.primaryColor}
// onChange={(v) => setTweak('primaryColor', v)} />
// <TweakToggle label="Dark mode" value={t.dark}
// onChange={(v) => setTweak('dark', v)} />
// </TweaksPanel>
// </div>
// );
// }
//
// ─────────────────────────────────────────────────────────────────────────────
const __TWEAKS_STYLE = `
.twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;
max-height:calc(100vh - 32px);display:flex;flex-direction:column;
background:rgba(250,249,247,.78);color:#29261b;
-webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);
border:.5px solid rgba(255,255,255,.6);border-radius:14px;
box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);
font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}
.twk-hd{display:flex;align-items:center;justify-content:space-between;
padding:10px 8px 10px 14px;cursor:move;user-select:none}
.twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}
.twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);
width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}
.twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}
.twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;
overflow-y:auto;overflow-x:hidden;min-height:0;
scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}
.twk-body::-webkit-scrollbar{width:8px}
.twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}
.twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;
border:2px solid transparent;background-clip:content-box}
.twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25);
border:2px solid transparent;background-clip:content-box}
.twk-row{display:flex;flex-direction:column;gap:5px}
.twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}
.twk-lbl{display:flex;justify-content:space-between;align-items:baseline;
color:rgba(41,38,27,.72)}
.twk-lbl>span:first-child{font-weight:500}
.twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}
.twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
color:rgba(41,38,27,.45);padding:10px 0 0}
.twk-sect:first-child{padding-top:0}
.twk-field{appearance:none;width:100%;height:26px;padding:0 8px;
border:.5px solid rgba(0,0,0,.1);border-radius:7px;
background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}
.twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}
select.twk-field{padding-right:22px;
background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='rgba(0,0,0,.5)' d='M0 0h10L5 6z'/></svg>");
background-repeat:no-repeat;background-position:right 8px center}
.twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;
border-radius:999px;background:rgba(0,0,0,.12);outline:none}
.twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
width:14px;height:14px;border-radius:50%;background:#fff;
border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
.twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;
background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
.twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;
background:rgba(0,0,0,.06);user-select:none}
.twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;
background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);
transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}
.twk-seg.dragging .twk-seg-thumb{transition:none}
.twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;
background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;
border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2;
overflow-wrap:anywhere}
.twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;
background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}
.twk-toggle[data-on="1"]{background:#34c759}
.twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
.twk-toggle[data-on="1"] i{transform:translateX(14px)}
.twk-num{display:flex;align-items:center;height:26px;padding:0 0 0 8px;
border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)}
.twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize;
user-select:none;padding-right:8px}
.twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent;
font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0;
outline:none;color:inherit;-moz-appearance:textfield}
.twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{
-webkit-appearance:none;margin:0}
.twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)}
.twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px;
background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default}
.twk-btn:hover{background:rgba(0,0,0,.88)}
.twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit}
.twk-btn.secondary:hover{background:rgba(0,0,0,.1)}
.twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;
border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;
background:transparent;flex-shrink:0}
.twk-swatch::-webkit-color-swatch-wrapper{padding:0}
.twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}
.twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px}
`;
// ── useTweaks ───────────────────────────────────────────────────────────────
// Single source of truth for tweak values. setTweak persists via the host
// (__edit_mode_set_keys → host rewrites the EDITMODE block on disk).
function useTweaks(defaults) {
const [values, setValues] = React.useState(defaults);
// Accepts either setTweak('key', value) or setTweak({ key: value, ... }) so a
// useState-style call doesn't write a "[object Object]" key into the persisted
// JSON block.
const setTweak = React.useCallback((keyOrEdits, val) => {
const edits = typeof keyOrEdits === 'object' && keyOrEdits !== null
? keyOrEdits : { [keyOrEdits]: val };
setValues((prev) => ({ ...prev, ...edits }));
window.parent.postMessage({ type: '__edit_mode_set_keys', edits }, '*');
}, []);
return [values, setTweak];
}
// ── TweaksPanel ─────────────────────────────────────────────────────────────
// Floating shell. Registers the protocol listener BEFORE announcing
// availability — if the announce ran first, the host's activate could land
// before our handler exists and the toolbar toggle would silently no-op.
// The close button posts __edit_mode_dismissed so the host's toolbar toggle
// flips off in lockstep; the host echoes __deactivate_edit_mode back which
// is what actually hides the panel.
function TweaksPanel({ title = 'Tweaks', children }) {
const [open, setOpen] = React.useState(false);
const dragRef = React.useRef(null);
const offsetRef = React.useRef({ x: 16, y: 16 });
const PAD = 16;
const clampToViewport = React.useCallback(() => {
const panel = dragRef.current;
if (!panel) return;
const w = panel.offsetWidth, h = panel.offsetHeight;
const maxRight = Math.max(PAD, window.innerWidth - w - PAD);
const maxBottom = Math.max(PAD, window.innerHeight - h - PAD);
offsetRef.current = {
x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)),
y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y)),
};
panel.style.right = offsetRef.current.x + 'px';
panel.style.bottom = offsetRef.current.y + 'px';
}, []);
React.useEffect(() => {
if (!open) return;
clampToViewport();
if (typeof ResizeObserver === 'undefined') {
window.addEventListener('resize', clampToViewport);
return () => window.removeEventListener('resize', clampToViewport);
}
const ro = new ResizeObserver(clampToViewport);
ro.observe(document.documentElement);
return () => ro.disconnect();
}, [open, clampToViewport]);
React.useEffect(() => {
const onMsg = (e) => {
const t = e?.data?.type;
if (t === '__activate_edit_mode') setOpen(true);
else if (t === '__deactivate_edit_mode') setOpen(false);
};
window.addEventListener('message', onMsg);
window.parent.postMessage({ type: '__edit_mode_available' }, '*');
return () => window.removeEventListener('message', onMsg);
}, []);
const dismiss = () => {
setOpen(false);
window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*');
};
const onDragStart = (e) => {
const panel = dragRef.current;
if (!panel) return;
const r = panel.getBoundingClientRect();
const sx = e.clientX, sy = e.clientY;
const startRight = window.innerWidth - r.right;
const startBottom = window.innerHeight - r.bottom;
const move = (ev) => {
offsetRef.current = {
x: startRight - (ev.clientX - sx),
y: startBottom - (ev.clientY - sy),
};
clampToViewport();
};
const up = () => {
window.removeEventListener('mousemove', move);
window.removeEventListener('mouseup', up);
};
window.addEventListener('mousemove', move);
window.addEventListener('mouseup', up);
};
if (!open) return null;
return (
<>
<style>{__TWEAKS_STYLE}</style>
<div ref={dragRef} className="twk-panel"
style={{ right: offsetRef.current.x, bottom: offsetRef.current.y }}>
<div className="twk-hd" onMouseDown={onDragStart}>
<b>{title}</b>
<button className="twk-x" aria-label="Close tweaks"
onMouseDown={(e) => e.stopPropagation()}
onClick={dismiss}></button>
</div>
<div className="twk-body">{children}</div>
</div>
</>
);
}
// ── Layout helpers ──────────────────────────────────────────────────────────
function TweakSection({ label, children }) {
return (
<>
<div className="twk-sect">{label}</div>
{children}
</>
);
}
function TweakRow({ label, value, children, inline = false }) {
return (
<div className={inline ? 'twk-row twk-row-h' : 'twk-row'}>
<div className="twk-lbl">
<span>{label}</span>
{value != null && <span className="twk-val">{value}</span>}
</div>
{children}
</div>
);
}
// ── Controls ────────────────────────────────────────────────────────────────
function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = '', onChange }) {
return (
<TweakRow label={label} value={`${value}${unit}`}>
<input type="range" className="twk-slider" min={min} max={max} step={step}
value={value} onChange={(e) => onChange(Number(e.target.value))} />
</TweakRow>
);
}
function TweakToggle({ label, value, onChange }) {
return (
<div className="twk-row twk-row-h">
<div className="twk-lbl"><span>{label}</span></div>
<button type="button" className="twk-toggle" data-on={value ? '1' : '0'}
role="switch" aria-checked={!!value}
onClick={() => onChange(!value)}><i /></button>
</div>
);
}
function TweakRadio({ label, value, options, onChange }) {
const trackRef = React.useRef(null);
const [dragging, setDragging] = React.useState(false);
const opts = options.map((o) => (typeof o === 'object' ? o : { value: o, label: o }));
const idx = Math.max(0, opts.findIndex((o) => o.value === value));
const n = opts.length;
// The active value is read by pointer-move handlers attached for the lifetime
// of a drag — ref it so a stale closure doesn't fire onChange for every move.
const valueRef = React.useRef(value);
valueRef.current = value;
const segAt = (clientX) => {
const r = trackRef.current.getBoundingClientRect();
const inner = r.width - 4;
const i = Math.floor(((clientX - r.left - 2) / inner) * n);
return opts[Math.max(0, Math.min(n - 1, i))].value;
};
const onPointerDown = (e) => {
setDragging(true);
const v0 = segAt(e.clientX);
if (v0 !== valueRef.current) onChange(v0);
const move = (ev) => {
if (!trackRef.current) return;
const v = segAt(ev.clientX);
if (v !== valueRef.current) onChange(v);
};
const up = () => {
setDragging(false);
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', up);
};
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
};
return (
<TweakRow label={label}>
<div ref={trackRef} role="radiogroup" onPointerDown={onPointerDown}
className={dragging ? 'twk-seg dragging' : 'twk-seg'}>
<div className="twk-seg-thumb"
style={{ left: `calc(2px + ${idx} * (100% - 4px) / ${n})`,
width: `calc((100% - 4px) / ${n})` }} />
{opts.map((o) => (
<button key={o.value} type="button" role="radio" aria-checked={o.value === value}>
{o.label}
</button>
))}
</div>
</TweakRow>
);
}
function TweakSelect({ label, value, options, onChange }) {
return (
<TweakRow label={label}>
<select className="twk-field" value={value} onChange={(e) => onChange(e.target.value)}>
{options.map((o) => {
const v = typeof o === 'object' ? o.value : o;
const l = typeof o === 'object' ? o.label : o;
return <option key={v} value={v}>{l}</option>;
})}
</select>
</TweakRow>
);
}
function TweakText({ label, value, placeholder, onChange }) {
return (
<TweakRow label={label}>
<input className="twk-field" type="text" value={value} placeholder={placeholder}
onChange={(e) => onChange(e.target.value)} />
</TweakRow>
);
}
function TweakNumber({ label, value, min, max, step = 1, unit = '', onChange }) {
const clamp = (n) => {
if (min != null && n < min) return min;
if (max != null && n > max) return max;
return n;
};
const startRef = React.useRef({ x: 0, val: 0 });
const onScrubStart = (e) => {
e.preventDefault();
startRef.current = { x: e.clientX, val: value };
const decimals = (String(step).split('.')[1] || '').length;
const move = (ev) => {
const dx = ev.clientX - startRef.current.x;
const raw = startRef.current.val + dx * step;
const snapped = Math.round(raw / step) * step;
onChange(clamp(Number(snapped.toFixed(decimals))));
};
const up = () => {
window.removeEventListener('pointermove', move);
window.removeEventListener('pointerup', up);
};
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
};
return (
<div className="twk-num">
<span className="twk-num-lbl" onPointerDown={onScrubStart}>{label}</span>
<input type="number" value={value} min={min} max={max} step={step}
onChange={(e) => onChange(clamp(Number(e.target.value)))} />
{unit && <span className="twk-num-unit">{unit}</span>}
</div>
);
}
function TweakColor({ label, value, onChange }) {
return (
<div className="twk-row twk-row-h">
<div className="twk-lbl"><span>{label}</span></div>
<input type="color" className="twk-swatch" value={value}
onChange={(e) => onChange(e.target.value)} />
</div>
);
}
function TweakButton({ label, onClick, secondary = false }) {
return (
<button type="button" className={secondary ? 'twk-btn secondary' : 'twk-btn'}
onClick={onClick}>{label}</button>
);
}
Object.assign(window, {
useTweaks, TweaksPanel, TweakSection, TweakRow,
TweakSlider, TweakToggle, TweakRadio, TweakSelect,
TweakText, TweakNumber, TweakColor, TweakButton,
});