/* acme-ui.jsx — Reusable UI components for Acme Bank prototype */
const ACME = {
primary: '#0D7C4A', primaryDark: '#065F3A', primaryBg: '#ECFDF5',
bg: '#F3F4F6', card: '#FFFFFF', border: '#E5E7EB',
text: '#111827', muted: '#6B7280', subtle: '#9CA3AF',
success: '#059669', warning: '#D97706', danger: '#DC2626', neutral: '#6B7280',
font: "'Plus Jakarta Sans', sans-serif",
};
/* ─── Top Nav ─── */
const NAV_TABS = ['Dashboard', 'Applications', 'Portfolio', 'Reports', 'Settings'];
const AcmeNav = ({ activeTab, onTabClick }) => (
onTabClick && onTabClick('Dashboard')}>
acmebank
{NAV_TABS.map((t) => {
const active = t === activeTab;
return (
onTabClick && onTabClick(t)} style={{ padding: '0 14px', height: 56, display: 'flex', alignItems: 'center', fontSize: 14, fontWeight: active ? 600 : 500, color: active ? ACME.primary : ACME.muted, borderBottom: active ? '2px solid ' + ACME.primary : '2px solid transparent', cursor: 'pointer', fontFamily: ACME.font }}>{t}
);
})}
Search
DN
David Nakamura
Sr. Credit Analyst
);
/* ─── Breadcrumb ─── */
const AcmeBreadcrumb = ({ appId }) => (
Consumer Lending
Applications
{appId}
);
/* ─── Sidebar Queue ─── */
const AcmeSidebar = ({ applicants, selectedIndex, onSelect }) => {
const statusColors = {
'approved-conditions': { bg: '#FEF3C7', text: '#B45309', dot: '#F59E0B', label: 'Conditions' },
'review': { bg: '#DBEAFE', text: '#1D4ED8', dot: '#3B82F6', label: 'Review' },
'approved': { bg: '#D1FAE5', text: '#065F46', dot: '#10B981', label: 'Approved' },
'declined': { bg: '#FEE2E2', text: '#991B1B', dot: '#EF4444', label: 'Declined' },
};
return (
Application Queue
Filter applications...
{applicants.map((app, i) => {
const sel = i === selectedIndex;
const sc = statusColors[app.decisionType] || statusColors['review'];
return (
onSelect(i)} style={{
padding: '12px 16px', cursor: 'pointer', borderLeft: sel ? '3px solid ' + ACME.primary : '3px solid transparent',
background: sel ? ACME.primaryBg : 'transparent', transition: 'all 0.15s',
}}>
{app.name}
{app.productShort} · {app.amount}
);
})}
Showing 4 of 47 pending
);
};
/* ─── Animated Gauge ─── */
const AcmeGauge = ({ targetScore, min, max, label, isAnimating }) => {
const [displayScore, setDisplayScore] = React.useState(targetScore);
const prevTargetRef = React.useRef(targetScore);
const animRef = React.useRef(null);
React.useEffect(() => {
if (isAnimating) { setDisplayScore(min); prevTargetRef.current = min; return; }
const from = prevTargetRef.current;
prevTargetRef.current = targetScore;
if (from === targetScore) { setDisplayScore(targetScore); return; }
let start = null;
const duration = 1200;
const step = (ts) => {
if (!start) start = ts;
const pct = Math.min((ts - start) / duration, 1);
const eased = 1 - Math.pow(1 - pct, 3);
setDisplayScore(Math.round(from + (targetScore - from) * eased));
if (pct < 1) animRef.current = requestAnimationFrame(step);
};
animRef.current = requestAnimationFrame(step);
return () => cancelAnimationFrame(animRef.current);
}, [targetScore, isAnimating]);
const score = displayScore;
const pct = Math.max(0, (score - min) / (max - min));
const startAngle = 135, sweep = 270;
const endAngle = startAngle + sweep * pct;
const r = 80, cx = 100, cy = 100;
const polarToCart = (deg) => {
const rad = (deg - 90) * Math.PI / 180;
return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
};
const describeArc = (s, e) => {
if (e - s < 0.5) return '';
const sp = polarToCart(s), ep = polarToCart(e);
return `M ${sp.x} ${sp.y} A ${r} ${r} 0 ${(e - s) > 180 ? 1 : 0} 1 ${ep.x} ${ep.y}`;
};
const arcColor = score >= 740 ? '#059669' : score >= 670 ? '#0D7C4A' : score >= 580 ? '#D97706' : '#DC2626';
return (
);
};
/* ─── Decision Badge ─── */
const DecisionBadge = ({ decision, type, visible }) => {
const styles = {
'approved': { bg: '#D1FAE5', color: '#065F46', icon: '✓' },
'approved-conditions': { bg: '#D1FAE5', color: '#065F46', icon: '✓' },
'review': { bg: '#DBEAFE', color: '#1E40AF', icon: '⬭' },
'declined': { bg: '#FEE2E2', color: '#991B1B', icon: '✕' },
};
const s = styles[type] || styles['review'];
return (
);
};
/* ─── Factor Card ─── */
const FactorCard = ({ factor, visible, delay }) => {
const colors = {
positive: { bg: '#F0FDF4', border: '#BBF7D0', text: ACME.success, icon: '✓' },
warning: { bg: '#FFFBEB', border: '#FDE68A', text: '#B45309', icon: '⚠' },
neutral: { bg: '#F9FAFB', border: ACME.border, text: ACME.neutral, icon: 'ⓘ' },
};
const c = colors[factor.type];
return (
{c.icon}
{factor.label}
{factor.detail}
);
};
/* ─── Section Card ─── */
const InfoSection = ({ title, children }) => (
);
/* ─── Field ─── */
const InfoField = ({ label, value }) => (
);
/* ─── Loading Spinner ─── */
const ScoringLoader = () => (
Analyzing application...
Running credit model inference
);
Object.assign(window, {
ACME, AcmeNav, AcmeBreadcrumb, AcmeSidebar, AcmeGauge,
DecisionBadge, FactorCard, InfoSection, InfoField, ScoringLoader,
});