/* ProofMark Studio — main app: tile-led, grouped, SmallPDF-ish */ const { useState, useEffect, useMemo, useRef, useCallback } = React; /* ---------- Command Palette ---------- */ const CommandPalette = ({ open, onClose, onRun }) => { const [q, setQ] = useState(''); const [idx, setIdx] = useState(0); const inputRef = useRef(null); useEffect(() => { if (open) { setQ(''); setIdx(0); setTimeout(() => inputRef.current?.focus(), 20); } }, [open]); const results = useMemo(() => { const tools = window.PM_TOOLS; const term = q.trim().toLowerCase(); if (!term) { return [ { group:'Popular', items: tools.filter(t => t.popular).slice(0,6) }, { group:'Pinned', items: tools.filter(t => t.pin).slice(0,4) }, ]; } const hits = tools.filter(t => t.title.toLowerCase().includes(term) || t.desc.toLowerCase().includes(term) || t.cat.includes(term) || t.slug.includes(term) ); return [{ group:`${hits.length} result${hits.length===1?'':'s'}`, items: hits.slice(0, 14) }]; }, [q]); const flat = results.flatMap(g => g.items); useEffect(() => { if (!open) return; const h = (e) => { if (e.key === 'Escape') onClose(); else if (e.key === 'ArrowDown') { e.preventDefault(); setIdx(i => Math.min(i+1, flat.length-1)); } else if (e.key === 'ArrowUp') { e.preventDefault(); setIdx(i => Math.max(i-1, 0)); } else if (e.key === 'Enter') { e.preventDefault(); const t = flat[idx]; if (t) { onRun(t); onClose(); } } }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [open, flat, idx, onClose, onRun]); if (!open) return null; let running = -1; return (
e.stopPropagation()} style={{ width:'min(640px, 92vw)', borderRadius:16, background:'var(--bg-elev)', border:'1px solid var(--border-strong)', boxShadow:'var(--shadow-lg)', overflow:'hidden', }}>
{setQ(e.target.value); setIdx(0);}} placeholder="Search 50+ tools, workflows…" style={{ flex:1, border:0, outline:0, background:'transparent', color:'var(--text)', fontSize:15, fontFamily:'inherit' }}/> Esc
{results.map((g, gi) => (
{g.group}
{g.items.length === 0 &&
No matches.
} {g.items.map(t => { running++; const sel = running === idx; const myIdx = running; const grp = window.PM_GROUPS.find(x=>x.id===t.group); return ( ); })}
))}
navigate open Esc close ProofMark Studio · v0.3.4
); }; /* ---------- Tool drawer ---------- */ const ToolDrawer = ({ tool, onClose }) => { if (!tool) return null; const grp = window.PM_GROUPS.find(g => g.id === tool.group); const tone = grp?.tone || '#7cb0ff'; return (
e.stopPropagation()} style={{ width:'min(520px, 94vw)', height:'100%', background:'var(--bg-elev)', borderLeft:'1px solid var(--border-strong)', boxShadow:'var(--shadow-lg)', display:'flex', flexDirection:'column', }}>
{grp?.label}
{tool.title}
/tools/{tool.slug}

{tool.desc}

{[ { label:'Avg. run time', value:'1.2s' }, { label:'Max file size', value:'512 MB' }, { label:'Docs processed', value:'14.2k' }, { label:'Last updated', value:'Apr 16' }, ].map(m => (
{m.label}
{m.value}
))}
How it works
    {['Drop your files into the workspace','Configure options (preserved between runs)','Download or forward the result'].map((s,i)=>(
  1. {i+1}
    {s}
  2. ))}
); }; /* ---------- Big icon tile (SmallPDF-style) ---------- */ const ToolTile = ({ tool, onOpen }) => { const grp = window.PM_GROUPS.find(g => g.id === tool.group); const tone = grp?.tone || '#7cb0ff'; const [hover, setHover] = useState(false); return ( ); }; /* ---------- Group section header ---------- */ const GroupHeader = ({ group, count, onViewAll }) => (
{String(count).padStart(2,'0')} tools
{group.id}

{group.label}

{group.desc}
); /* ---------- Hero: visual-first ---------- */ const HeroPanel = ({ onOpenPalette }) => { const liveCount = window.PM_TOOLS.filter(t=>t.status==='live').length; const total = window.PM_TOOLS.length; const planned = window.PM_TOOLS.filter(t=>t.status==='planned').length; const beta = window.PM_TOOLS.filter(t=>t.status==='beta').length; // The flying mini-tile cluster behind the hero copy const stack = [ { ic:'merge', tone:'#ff7a45', x: 62, y: 6, r:-8 }, { ic:'docx', tone:'#7cb0ff', x: 72, y: 44, r:5 }, { ic:'sig', tone:'#ff6b8a', x: 86, y: 18, r:10 }, { ic:'aa', tone:'#62e0d9', x: 54, y: 42, r:-4 }, { ic:'ai', tone:'#f0c674', x: 80, y: 64, r:-6 }, ]; return (
{/* Floating tiles cluster */}
{stack.map((s,i) => (
))}
Workspace · online
ProofMark Studio · Working hub

Every PDF tool
you need, in one studio.

Merge, split, convert, sign, compress, and proofread. Fifty plus tools, organized like a real workspace — private, keyboard-first, built for document craft.

{[ { k: liveCount, l:'Live now', dot:'var(--live)' }, { k: beta, l:'In beta', dot:'var(--beta)' }, { k: planned, l:'Planned lanes', dot:'var(--planned)' }, { k: '99.98%', l:'Uptime · 30d', dot:null }, ].map((m,i)=>(
{m.k}
{m.dot && }
{m.l}
))}
); }; /* ---------- Popular strip ---------- */ const PopularStrip = ({ onOpen }) => { const pops = window.PM_TOOLS.filter(t => t.popular); return (
Popular right now
{pops.map(t => )}
); }; /* ---------- Grouped catalog ---------- */ const GroupedCatalog = ({ onOpen, activeGroup, onSetGroup }) => { const groups = activeGroup === 'all' ? window.PM_GROUPS : window.PM_GROUPS.filter(g => g.id === activeGroup); return (
{groups.map(g => { const items = window.PM_TOOLS.filter(t => t.group === g.id); return (
{items.map(t => )}
); })}
); }; /* ---------- Group chips with colored dots ---------- */ const GroupChips = ({ active, onSelect }) => (
{window.PM_GROUPS.map(g => { const isActive = active === g.id; const count = window.PM_TOOLS.filter(t => t.group === g.id).length; return ( ); })}
); /* ---------- Recent activity ---------- */ const RecentStream = () => (
Recent activity
Today · You + 2
{window.PM_RECENT.map((r, i) => { const tool = window.PM_TOOLS.find(t => t.slug === r.kind); const grp = tool && window.PM_GROUPS.find(g => g.id === tool.group); const tone = grp?.tone || '#7cb0ff'; return (
{r.label}
{r.meta}
{r.when}
{r.by}
); })}
); /* ---------- Throughput ---------- */ const Throughput = () => { const data = [8,12,9,14,18,16,22,19,24,28,26,32,30,35,33,40,38,44,47,43,49,52]; const max = Math.max(...data); const w = 100, h = 40; const pts = data.map((v,i) => [(i/(data.length-1))*w, h - (v/max)*h]).map(p=>p.join(',')).join(' '); return (
Throughput
22d
14,283
+38.2%
Docs processed across your workspace.
); }; /* ---------- Platform map ---------- */ const PlatformMap = () => { const spokes = window.PM_SPOKES; return (
Platform map
6 lanes
{spokes.map((s, i) => (
{String(i+1).padStart(2,'0')}
{s.title}
{s.desc}
))}
); }; /* ---------- Tweaks panel ---------- */ const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "theme": "dark", "density": "comfortable", "accent": "#7cb0ff" }/*EDITMODE-END*/; const TweaksPanel = ({ open, values, onChange, onClose }) => { if (!open) return null; const opt = (current, choices, onPick, label) => (
{label}
{choices.map(c => ( ))}
); const accents = ['#7cb0ff','#5ee59b','#ffb366','#ff6b8a','#a57cff','#1e4fd6']; return (
Tweaks
Live
{opt(values.theme, [{ v:'dark', l:'Console (dark)' }, { v:'light', l:'Editorial (light)' }], v => onChange({ theme: v }), 'Theme')} {opt(values.density, [{ v:'comfortable', l:'Comfortable' }, { v:'compact', l:'Compact' }], v => onChange({ density: v }), 'Density')}
Accent
{accents.map(a => (
); }; /* ---------- Main App ---------- */ const App = () => { const [view, setView] = useState('home'); const [paletteOpen, setPaletteOpen] = useState(false); const [tweaksOpen, setTweaksOpen] = useState(false); const [selectedTool, setSelectedTool] = useState(null); const [group, setGroup] = useState('all'); const [tweaks, setTweaks] = useState(() => { try { return { ...TWEAK_DEFAULTS, ...JSON.parse(localStorage.getItem('pm_tweaks')||'{}') }; } catch { return TWEAK_DEFAULTS; } }); useEffect(() => { document.body.dataset.theme = tweaks.theme; document.body.style.setProperty('--accent', tweaks.accent); document.body.style.setProperty('--accent-glow', tweaks.accent + '22'); try { localStorage.setItem('pm_tweaks', JSON.stringify(tweaks)); } catch {} }, [tweaks]); useEffect(() => { const handler = (e) => { if (!e.data || typeof e.data !== 'object') return; if (e.data.type === '__activate_edit_mode') setTweaksOpen(true); if (e.data.type === '__deactivate_edit_mode') setTweaksOpen(false); }; window.addEventListener('message', handler); window.parent.postMessage({ type:'__edit_mode_available' }, '*'); return () => window.removeEventListener('message', handler); }, []); const updateTweak = (patch) => { setTweaks(prev => ({ ...prev, ...patch })); window.parent.postMessage({ type:'__edit_mode_set_keys', edits: patch }, '*'); }; useEffect(() => { const h = (e) => { if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { e.preventDefault(); setPaletteOpen(true); return; } if (document.activeElement?.tagName === 'INPUT') return; if (paletteOpen) return; if (e.key === 'g' || e.key === 'G') setView('tools'); if (e.key === 'h' || e.key === 'H') setView('home'); if (e.key === 'r' || e.key === 'R') setView('recent'); if (e.key === 'p' || e.key === 'P') setView('pinned'); if (e.key === 'w' || e.key === 'W') setView('workflow'); if (e.key === 'm' || e.key === 'M') setView('map'); }; window.addEventListener('keydown', h); return () => window.removeEventListener('keydown', h); }, [paletteOpen]); const onRun = (tool) => setSelectedTool(tool); const breadcrumb = (() => { if (view === 'home') return ['Workspace', 'Home']; if (view === 'tools') return ['Workspace', 'All tools', group === 'all' ? 'All' : (window.PM_GROUPS.find(g=>g.id===group)?.label || 'All')]; if (view === 'recent') return ['Workspace', 'Recent']; if (view === 'pinned') return ['Workspace', 'Pinned']; if (view === 'workflow') return ['Workspace', 'Workflow']; if (view === 'map') return ['Workspace', 'Platform map']; return ['Workspace']; })(); return (
{ if (v.startsWith('tool:')) { const slug = v.slice(5); const tool = window.PM_TOOLS.find(t => t.slug === slug); if (tool) setSelectedTool(tool); } else setView(v); }} onOpenPalette={() => setPaletteOpen(true)} density={tweaks.density}/>
setPaletteOpen(true)} onOpenTweaks={() => setTweaksOpen(true)} breadcrumb={breadcrumb}/>
{view === 'home' && (
setPaletteOpen(true)}/> {}}/>
)} {view === 'tools' && (
Catalog

All tools · {window.PM_TOOLS.length}

Click a category to filter, or scroll through grouped sections below.

)} {view === 'recent' && (

Recent activity

)} {view === 'pinned' && (

Pinned tools

{window.PM_TOOLS.filter(t=>t.pin).map(t => )}
)} {view === 'workflow' && (

Workflow

{window.PM_TOOLS.filter(t=>t.cat==='workflow').map(t => )}
)} {view === 'map' && (

Platform map

)} {view === 'settings' && (

Settings

Use the Tweaks panel (bottom-right) to adjust theme, density, and accent.
)}
setPaletteOpen(false)} onRun={onRun}/> setSelectedTool(null)}/> setTweaksOpen(false)}/>
); }; /* ---------- Backend registry sync ---------- * Hub serves /api/tools as source of truth for { status, url } per slug. * We merge that into the React catalog (window.PM_TOOLS) BEFORE first render * so status pills + drawer targets reflect whatever the backend currently says. * Falls back to hardcoded values after a 600ms timeout so offline dev still works. */ const __pmSyncFromBackend = async () => { try { const res = await fetch('/api/tools', { cache: 'no-store' }); if (!res.ok) return; const payload = await res.json(); const serverTools = payload.tools || {}; window.PM_TOOLS.forEach(t => { const s = serverTools[t.slug]; if (!s) return; if (s.status) t.status = s.status; if (s.url) t.url = s.url; // drawer keeps this for potential deep-link }); } catch (err) { console.warn('[registry] sync failed, using hardcoded catalog', err); } }; const __pmMount = () => { ReactDOM.createRoot(document.getElementById('root')).render(); }; Promise.race([ __pmSyncFromBackend(), new Promise(resolve => setTimeout(resolve, 600)), ]).then(__pmMount);