/* 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.id}
{sc.label}
{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 ( {pct > 0.01 && } {score} {label} {min} {max} ); }; /* ─── 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 (
{decision}
); }; /* ─── 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 }) => (
{title}
{children}
); /* ─── Field ─── */ const InfoField = ({ label, value }) => (
{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, });