// 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 (
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 */}
{/* Decorative top — small visual specific to service type */}
{p.name}
{p.nameBn && (
{p.nameBn}
)}
{p.description}
{/* Stack badges */}
{p.stack.map(s => (
{s}
))}
{/* Footer: impact + actions */}
{p.impact.primary}
{p.impact.secondary}
{p.gitRepo ? (
) : (
)}
{p.demo && (
Demo
)}
);
};
// Tiny SVG visual per service — gives each card a distinct "product preview"
const ProjectVisual = ({ id, tint, hover }) => {
if (id === "ai-agent-automation") {
return (
{/* nodes */}
{[
[120, 60], [220, 110], [320, 70], [420, 130], [500, 90],
[180, 160], [280, 180], [380, 50], [460, 180],
].map(([x, y], i) => (
))}
{/* connectors */}
);
}
if (id === "scraping-data-pipeline") {
// bar chart
const bars = [40, 64, 52, 78, 92, 70, 110, 95, 130, 118, 142];
return (
{bars.map((h, i) => (
))}
);
}
if (id === "web3-blockchain") {
// chain dots
const chains = ["ETH","ARB","OP","BASE","SOL","ZK","POL","BNB","AVAX","CELO","XLM","TRX"];
return (
{chains.map((c, i) => (
{c}
))}
);
}
if (id === "mt5-ea-trading") {
// line chart
return (
{[60,180,300,420,540].map((x, i) => (
))}
);
}
if (id === "web-app-dev") {
// schema → grid morph
return (
{/* schema box */}
{[20, 40, 60, 80].map(y => )}
{[20, 40, 60, 80].map(y => )}
{/* arrow */}
{/* dashboard grid */}
);
}
if (id === "infra-devops") {
// chat bubbles
return (
How do I rotate my API key?
Settings → API → Rotate. New key live in <5s.
);
}
return null;
};
// Merge Dashboard API data (demo/git links, active state) into local static data
function mergeServices(local, apiData) {
const byId = {};
apiData.forEach(s => { byId[s.serviceId] = s; });
return local
.filter(s => !byId[s.id] || byId[s.id].isActive !== false)
.map(s => {
const api = byId[s.id];
if (!api) return s;
return {
...s,
demo: api.demoUrl || s.demo || null,
gitRepo: api.gitRepo || null,
name: api.name || s.name,
nameBn: api.nameBn || s.nameBn,
impact: {
primary: api.impactPrimary || s.impact.primary,
secondary: api.impactSecondary || s.impact.secondary,
},
};
});
}
const Projects = () => {
const D = PORTFOLIO_DATA;
const [services, setServices] = React.useState(D.services);
React.useEffect(() => {
const CACHE_KEY = 'aura_services_v2';
const CACHE_TTL = 60 * 1000;
const cached = sessionStorage.getItem(CACHE_KEY);
if (cached) {
try {
const { data, ts } = JSON.parse(cached);
if (Date.now() - ts < CACHE_TTL && data.length > 0) {
setServices(mergeServices(D.services, data));
return;
}
} catch {}
}
fetch('https://aura.auraajenticai.cloud/api/public/services', { signal: AbortSignal.timeout(3000) })
.then(r => r.ok ? r.json() : null)
.then(data => {
if (Array.isArray(data) && data.length > 0) {
sessionStorage.setItem(CACHE_KEY, JSON.stringify({ data, ts: Date.now() }));
setServices(mergeServices(D.services, data));
}
})
.catch(() => {});
}, []);
return (
{['Zero','One','Two','Three','Four','Five','Six','Seven','Eight','Nine'][services.length] || services.length} service{services.length !== 1 ? 's' : ''}. One team. All production-grade .>}
sub="From landing pages to AI agent runtimes — we take your idea from spec to live deployment."
/>
);
};
window.Projects = Projects;