// Shared UI primitives + tokens for the Unblock Research ecosystem // Aesthetic: scientific instrument × terminal. Off-white paper, near-black ink, // acidic chartreuse accent. Inter Tight + JetBrains Mono + Instrument Serif. const UB = { paper: '#F2F0E8', paperDeep: '#E9E6DC', ink: '#0E0E10', ink2: '#26252A', ink3: '#5B5A60', ink4: '#8A8990', rule: '#1A1A1C', rule2: 'rgba(14,14,16,0.12)', rule3: 'rgba(14,14,16,0.06)', accent: 'oklch(0.86 0.19 118)', // acidic chartreuse accentDim: 'oklch(0.86 0.19 118 / 0.18)', ok: 'oklch(0.72 0.13 155)', warn: 'oklch(0.78 0.16 70)', bad: 'oklch(0.62 0.20 25)', info: 'oklch(0.66 0.13 240)', serif: '"Instrument Serif", "Times New Roman", serif', sans: '"Inter Tight", "Inter", system-ui, sans-serif', mono: '"JetBrains Mono", "SF Mono", ui-monospace, monospace' }; // ---- Layout: a uniform 1280×800 dapp frame ---------------------------------- // Auto-fits content: if children render taller than 800px, the inner stack is // scaled down (origin top-left) so nothing spills out of the frame. Reads tweaks // from window.__UB_TWEAKS for global overrides (density, font scale, dark, accent). function Dapp({ children, dark: darkProp = false, frameless = false }) { const innerRef = React.useRef(null); const [scale, setScale] = React.useState(1); const [tw, setTw] = React.useState(() => window.__UB_TWEAKS || {}); React.useEffect(() => { const onTw = () => { const t = window.__UB_TWEAKS || {}; if (t.accent && t.accent !== UB.accent) { UB.accent = t.accent; // recompute accentDim UB.accentDim = t.accent + '2E'; // ~18% alpha hex suffix } setTw({ ...t }); }; window.addEventListener('__ub_tweaks', onTw); onTw(); return () => window.removeEventListener('__ub_tweaks', onTw); }, []); React.useLayoutEffect(() => { const el = innerRef.current; if (!el) return; const measure = () => { if (tw.fitOverflow === false) { setScale(1); return; } const sh = el.scrollHeight, sw = el.scrollWidth; const sy = sh > 800 ? 800 / sh : 1; const sx = sw > 1280 ? 1280 / sw : 1; const next = Math.min(sx, sy, 1); setScale(next < 0.999 ? next : 1); }; measure(); const ro = new ResizeObserver(measure); ro.observe(el); return () => ro.disconnect(); }, [children, tw.fontScale, tw.density, tw.fitOverflow, tw.compactNav]); const dark = tw.dark != null ? tw.dark : darkProp; const fontScale = tw.fontScale || 1; return (
{children}
); } // Top chrome: tiny system bar + dapp title bar function ChromeBar({ dapp, code, status = 'ONLINE', user = 'malcolm.harriot.eth', balance = '12,840 UBR' }) { return (
UNBLOCK / {dapp} {code}
UB-CHAIN · BLOCK 8,419,206 {status} GAS 0.00021 UBR
◆ {balance} {user}
); } function Pulse() { return ; } // Two-column layout used by most dapps: thin icon rail + expandable context rail + workspace. // Mendeley-style: clicking an icon swaps which context rail is shown; clicking the active // icon collapses the context rail (icon-only mode). const __DEFAULT_DAPP_SECTIONS = [ { id: 'workspace', icon: 'home', label: 'Workspace' }, { id: 'identity', icon: 'user', label: 'Identity' }, { id: 'lab', icon: 'flask', label: 'Lab & Pipeline' }, { id: 'markets', icon: 'cube', label: 'Markets' }, { id: 'infra', icon: 'cpu', label: 'Infrastructure' }, { id: 'admin', icon: 'shield', label: 'Admin' }, ]; function IconRail({ sections, active, onPick, onAdd, dark = false }) { const railBtn = (children, opts = {}) => ; return (
{sections.map((s) => { const isActive = s.id === active; return railBtn( <> {isActive && } {Icon[s.icon] || Icon.cube} , { key: s.id, onClick: () => onPick(s.id), title: s.label, style: { color: isActive ? UB.accent : 'rgba(255,255,255,.55)', transition: 'color .12s' } } ); })} {/* divider + add */}
{railBtn( {Icon.plus}, { title: 'Add dapp · open marketplace', onClick: () => onAdd && onAdd() } )}
{railBtn(Icon.bell, { title: 'Notifications', style: { color: 'rgba(255,255,255,.4)' } })} {railBtn(Icon.cog, { title: 'Settings', style: { color: 'rgba(255,255,255,.4)' } })}
); } function Layout({ rail, sections, defaultSection, sectionRails, children, dark = false }) { const sx = sections || __DEFAULT_DAPP_SECTIONS; const initial = defaultSection || sx[0]?.id; const [activeSection, setActiveSection] = React.useState(initial); const [expanded, setExpanded] = React.useState(true); const [marketOpen, setMarketOpen] = React.useState(false); const tw = (typeof window !== 'undefined' && window.__UB_TWEAKS) || {}; // Resolve which secondary rail content to show. // - If `sectionRails` provided, use sectionRails[activeSection]; clicking unmapped // icons collapses the rail. // - Else (legacy `rail`), the same content is shown regardless of which icon is // active — the icon highlight just shifts. Clicking the active icon collapses. const secondary = sectionRails ? sectionRails[activeSection] : rail; const pick = (id) => { if (id === activeSection) { setExpanded((e) => !e); } else { setActiveSection(id); setExpanded(true); } }; const showRail = expanded && secondary; const compact = !!tw.compactNav; const railW = compact ? 168 : 200; return (
setMarketOpen(true)} /> {showRail && (
{secondary}
)}
{children}
{marketOpen && setMarketOpen(false)} />}
); } // Sidebar nav item function NavGroup({ label, children }) { return (
{label}
{children}
); } function NavItem({ icon, label, badge, active, dark = false }) { const baseColor = dark ? '#A8A7A0' : UB.ink2; return (
{icon} {label} {badge != null && {badge} }
); } // Page header inside the workspace function PageHead({ eyebrow, title, sub, actions }) { return (
{eyebrow &&
{eyebrow}
}

{title}

{sub &&
{sub}
}
{actions &&
{actions}
}
); } // Buttons function Btn({ children, primary, ghost, small, icon, onClick, style, dark = false }) { const h = small ? 26 : 32; return ( ); } // Pill / tag function Tag({ children, tone = 'default', mono = true, style }) { const tones = { default: { bg: 'rgba(0,0,0,.05)', fg: UB.ink2 }, accent: { bg: UB.accentDim, fg: UB.ink }, ok: { bg: 'oklch(0.72 0.13 155 / 0.15)', fg: 'oklch(0.42 0.13 155)' }, warn: { bg: 'oklch(0.78 0.16 70 / 0.18)', fg: 'oklch(0.45 0.16 70)' }, bad: { bg: 'oklch(0.62 0.20 25 / 0.15)', fg: 'oklch(0.45 0.18 25)' }, info: { bg: 'oklch(0.66 0.13 240 / 0.15)', fg: 'oklch(0.40 0.13 240)' }, ink: { bg: UB.ink, fg: UB.paper } }; const t = tones[tone] || tones.default; return ( {children}); } // Card / panel function Card({ title, code, action, children, style, dark = false, pad = true }) { return (
{(title || action) &&
{title} {code && {code}}
{action}
}
{children}
); } // Stat block (large mono number) function Stat({ label, value, delta, mono = true, sub, large }) { return (
{label}
{value}
{(delta || sub) &&
{delta && {delta}} {sub && {sub}}
}
); } // Mono row used for ledger-like data function Row({ cells, head, dark = false }) { return (
c.w || '1fr').join(' '), gap: 12, alignItems: 'center', padding: '8px 14px', borderBottom: `1px solid ${dark ? 'rgba(255,255,255,.05)' : UB.rule3}`, fontFamily: UB.mono, fontSize: 11, color: head ? UB.ink3 : dark ? '#D9D8D2' : UB.ink2, textTransform: head ? 'uppercase' : 'none', letterSpacing: head ? '0.12em' : '0', fontSize: head ? 9 : 11 }}> {cells.map((c, i) =>
{c.v}
)}
); } // Decorative bracket "registry corner" function Corners({ children, style }) { const C = (pos) => ; return (
{C({ top: 0, left: 0, borderTopWidth: 1, borderLeftWidth: 1 })} {C({ top: 0, right: 0, borderTopWidth: 1, borderRightWidth: 1 })} {C({ bottom: 0, left: 0, borderBottomWidth: 1, borderLeftWidth: 1 })} {C({ bottom: 0, right: 0, borderBottomWidth: 1, borderRightWidth: 1 })} {children}
); } // Logo: a simple ⊞ "unblock" mark — three connected nodes inside a square function UBLogo({ size = 18, mono = false, color }) { const c = color || (mono ? 'currentColor' : UB.ink); return ( ); } // Tiny inline SVG icons (16×16). Stroke-based, currentColor. const Icon = { home: , user: , book: , doc: , flask: , cube: , cpu: , storage: , bank: , rocket: , chain: , store: , search: , bell: , plus: , arrow: , check: , branch: , shield: , cog: }; // Small bar chart (sparkline-style) function MiniBars({ values, max, h = 36, color }) { const m = max || Math.max(...values); return (
{values.map((v, i) =>
)}
); } // Spark line function Sparkline({ values, w = 120, h = 32, color }) { const max = Math.max(...values),min = Math.min(...values); const dx = w / (values.length - 1); const d = values.map((v, i) => `${i === 0 ? 'M' : 'L'} ${i * dx} ${h - (v - min) / (max - min || 1) * (h - 4) - 2}`).join(' '); return ( ); } // Striped placeholder (for imagery) function StripePlaceholder({ label = 'PLACEHOLDER', height = 80, style }) { return (
{label}
); } Object.assign(window, { UB, Dapp, ChromeBar, Layout, IconRail, NavGroup, NavItem, PageHead, Btn, Tag, Card, Stat, Row, Corners, UBLogo, Icon, MiniBars, Sparkline, StripePlaceholder, Pulse });