// Modals.jsx — all secondary flows (new key, recovery codes, upgrade, photo upload, confirms, change pw, totp, export, system prompt edit) function ModalShell({ eb, title, desc, children, footer, width, onClose }) { React.useEffect(() => { const onKey = e => { if (e.key === 'Escape') onClose && onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose]); return (
e.stopPropagation()}>
{eb &&
{eb}
}

{title}

{desc &&

{desc}

}
{children}
{footer}
); } /* ---- 1. New API key (multi-step: name+scopes → reveal) ---- */ function NewKeyModal({ onClose }) { const [step, setStep] = React.useState(1); const [name, setName] = React.useState('production-2'); const [scopes, setScopes] = React.useState({read: true, write: true, verify: true, billing: false}); const tog = (k) => setScopes({...scopes, [k]: !scopes[k]}); const key = 'zg-live-a8f3c91d4e7b8s2nCAAB2901F40137'; if (step === 1) return (
}>
Scopes
{[ {k: 'read', t: 'Read', s: 'List threads, models, and certificates'}, {k: 'write', t: 'Write', s: 'Create threads and submit statements'}, {k: 'verify', t: 'Verify', s: 'Run kernel verification, fetch certificates'}, {k: 'billing', t: 'Billing', s: 'Read invoices and update payment methods'}, ].map(o => ( ))}
); return (
}>
{key} Copy
Store this in a secure secrets manager. If lost, you'll need to create a new key. Treat it like a password.
name · {name}
scopes · {Object.keys(scopes).filter(k => scopes[k]).join(', ')}
created · {new Date().toISOString().slice(0, 16).replace('T', ' ')}
); } /* ---- 2. Recovery codes ---- */ function RecoveryCodesModal({ onClose }) { const codes = ['8h2d-fw3k', 'p1qa-ne7r', 'm4xv-2tcb', 'gz9o-rk1y', 'aa3e-d8u2', 'sd0n-rm4p', '4er8-lo2k', 'b7yu-fwn9', 'cc1a-vb6q', '7l9z-hk4n']; return (
10 codes · 0 used
}>
{codes.map(c =>
{c}
)}
Store these somewhere safe and offline. They are equivalent to your password.
); } /* ---- 3. Change password ---- */ function ChangePasswordModal({ onClose }) { const [done, setDone] = React.useState(false); if (done) return ( }>
Your password has been changed. All other sessions have been signed out.
); return (
}>
); } /* ---- 4. Reconfigure TOTP ---- */ function TotpModal({ onClose }) { const secret = 'JBSWY3DPEHPK3PXPSXNJWGGMTQTQM'; return (
}>
Or paste this secret
{secret}Copy
); } /* ---- 5. Upgrade plan ---- */ function UpgradeModal({ onClose }) { const [sel, setSel] = React.useState('atlas'); return (
Billed monthly · prorated today
}>
setSel('atlas')}>
Atlas
$1,499/mo
10M tokens · 25k proofs · SSO · audit log · 99.95% SLA · email + slack support
setSel('enterprise')}>
Enterprise
Talk to us
Dedicated kernel · on-prem option · custom retention · designated SRE · MSA
); } /* ---- 6. Photo upload ---- */ function PhotoUploadModal({ onClose }) { const ref = React.useRef(null); return (
}>
ref.current && ref.current.click()}>
Drag an image here, or click to browse
jpg · png · webp · 4 MB max
); } /* ---- 7. Confirm destructive ---- */ function ConfirmModal({ eb, title, desc, confirmText = 'Confirm', requirePhrase, onClose, onConfirm }) { const [phrase, setPhrase] = React.useState(''); const ok = !requirePhrase || phrase === requirePhrase; return (
}>
This action cannot be undone.
{requirePhrase && ( )}
); } /* ---- 8. Generic info modal ---- */ function ExportModal({ onClose }) { return (
}>
); } /* ---- 9. Generic value-edit (radio-style selector) ---- */ const VALUE_PRESETS = { timezone: { eb: 'Account · Profile', title: 'Change timezone', desc: 'Used for timestamps, scheduled sends, and daily digest delivery.', options: ['Asia/Seoul · UTC+09', 'Asia/Tokyo · UTC+09', 'America/Los_Angeles · UTC-08', 'Europe/London · UTC+00', 'UTC'] }, locale: { eb: 'Account · Profile', title: 'Change locale', desc: 'Affects UI language and number/date formatting.', options: ['한국어 / English', 'English (US)', '日本語', '中文 (简体)', 'Deutsch'] }, 'billing-email': { eb: 'Account · Plan & billing', title: 'Billing email', desc: 'Invoices and dunning notices are sent here.', input: true, current: 'billing@gbr.ai' }, 'pay-method': { eb: 'Account · Plan & billing', title: 'Update payment method', desc: 'We never store your full PAN. Stripe Elements handles the card field.', options: ['Visa ···· 4128 (default)', 'Mastercard ···· 7733', '+ Add new card', '+ Add bank transfer / wire'] }, invoices: { eb: 'Account · Plan & billing', title: 'Download invoice history', desc: 'Choose a range — we\u2019ll email the CSV bundle within 30 minutes.', options: ['Last 30 days', 'Last 90 days', 'Last 12 months', 'All time'] }, 'math-render': { eb: 'Settings · Appearance', title: 'Math rendering engine', desc: 'KaTeX is faster; MathJax has wider symbol support.', options: ['KaTeX · italic serif (default)', 'KaTeX · upright sans', 'MathJax · classical', 'Plain text · monospace'] }, 'code-theme': { eb: 'Settings · Appearance', title: 'Code block theme', options: ['GBR Gold (default)', 'Classic dark', 'Solarized dark', 'Monokai', 'High contrast'] }, 'default-model': { eb: 'Settings · Chat defaults', title: 'Default model', desc: 'Applied to every new conversation. You can swap per-thread.', options: ['proof-α-2.4 (recommended)', 'proof-α-2.4 · thinking', 'proof-β-1.1 · fast', 'kernel-only · no LLM'] }, retention: { eb: 'Settings · Data & privacy', title: 'Conversation retention', desc: 'After this period, threads are permanently destroyed. Certificates are kept indefinitely.', options: ['30 days', '90 days (default)', '180 days', '1 year', 'Forever (until manually deleted)'] }, }; function ValueEditModal({ presetKey, onClose, onSave }) { const p = VALUE_PRESETS[presetKey] || { eb: 'Settings', title: 'Edit value', options: [] }; const [val, setVal] = React.useState(p.input ? (p.current || '') : (p.options ? p.options[0] : '')); return (
}> {p.input ? ( setVal(e.target.value)} style={{width:'100%',background:'var(--bg-canvas)',border:'1px solid var(--border-1)',borderRadius:'var(--r-2)',padding:'10px 12px',color:'var(--fg-1)',fontFamily:'var(--font-mono)',fontSize:13,outline:'none'}}/> ) : (
{p.options.map(o => ( ))}
)}
); } /* ---- 10. Share thread ---- */ function ShareModal({ onClose, threadTitle = 'Riemann zeta — non-trivial zeros' }) { const [scope, setScope] = React.useState('link'); const [expiry, setExpiry] = React.useState('7d'); const [allowVerify, setAllowVerify] = React.useState(true); const [copied, setCopied] = React.useState(false); const link = 'https://gbr.ai/s/' + (Math.random().toString(36).slice(2, 10)); const scopes = [ { id: 'link', t: 'Anyone with the link', s: 'Read-only. No sign-in required.' }, { id: 'workspace', t: 'Workspace members', s: 'ZIGU LABS · 14 members' }, { id: 'specific', t: 'Specific people', s: 'Invite by email; they\u2019ll need a GBR account.' }, { id: 'off', t: 'Off · only me', s: 'Disable any existing share links.' }, ]; const expiries = ['24 hours', '7 days', '30 days', 'No expiry']; return (
cert · 0xCAAB29F4…8E2A
}>
{link}
Who can view
{scopes.map(o => ( ))}
Link expires
{expiries.map(e => )}
Options
); } /* ---- 11. Export thread ---- */ function ExportThreadModal({ onClose, threadTitle = 'thread' }) { const [fmt, setFmt] = React.useState('md'); const formats = [ { id: 'md', t: 'Markdown', s: 'Plain .md · code fences preserved', size: '12.4 KB', ic: 'M↓' }, { id: 'json', t: 'JSON', s: 'Structured messages + metadata', size: '38.1 KB', ic: '{}' }, { id: 'pdf', t: 'PDF', s: 'Printable · GBR Gold theme · 1-column', size: '410 KB', ic: '📄' }, { id: 'cbor', t: 'Proof bundle', s: 'Signed CBOR · re-verifiable offline', size: '4.2 MB', ic: '⛨' }, ]; return (
}>
{formats.map(f => ( ))}
); } /* ---- 12. Search threads (command palette) ---- */ function SearchModal({ onClose, threads = [], onSelect }) { const [q, setQ] = React.useState(''); const ref = React.useRef(null); React.useEffect(() => { ref.current && ref.current.focus(); }, []); const filtered = !q ? threads : threads.filter(t => t.title.toLowerCase().includes(q.toLowerCase()) || t.model.toLowerCase().includes(q.toLowerCase())); return (
e.stopPropagation()}>
setQ(e.target.value)}/> esc
{!q &&
Jump to
} {!q && [ { ic: '+', t: 'New conversation', kbd: '⌘ N' }, { ic: '⚙', t: 'Settings', kbd: '⌘ ,' }, { ic: '⌘', t: 'Switch model', kbd: '⌘ ⇧ M' }, { ic: '✓', t: 'Verify last proof', kbd: '⌘ ⇧ V' }, ].map((c, i) => (
{c.ic}{c.t}{c.kbd}
))}
{q ? 'Threads · ' + filtered.length + ' match' + (filtered.length === 1 ? '' : 'es') : 'Recent threads'}
{filtered.length === 0 &&
No threads match "{q}". Try a model name, a date, or a partial proof statement.
} {filtered.map(t => (
{ onSelect && onSelect(t.id); onClose(); }}> {t.title}{t.model} · {t.time}
))}
↑↓ navigate open⌘ ↵ open in new tabesc close
); } /* ---- 13. Rename thread ---- */ function RenameThreadModal({ onClose, current = '', onSave }) { const [v, setV] = React.useState(current); return (
}> setV(e.target.value)} style={{width:'100%',background:'var(--bg-canvas)',border:'1px solid var(--border-1)',borderRadius:'var(--r-2)',padding:'12px 14px',color:'var(--fg-1)',fontFamily:'var(--font-sans)',fontSize:14,outline:'none'}}/>
); } /* ---- 14. Move to project ---- */ function MoveToProjectModal({ onClose }) { const [sel, setSel] = React.useState('p2'); const projects = [ { id: 'p1', t: 'Riemann hypothesis · 2026', s: '12 threads · last update 2 hr ago' }, { id: 'p2', t: 'Internal · proof-α evals', s: '38 threads · pinned' }, { id: 'p3', t: 'Sandbox', s: '6 threads · draft' }, { id: 'new', t: '+ New project', s: 'Create a project from this thread.' }, ]; return (
}>
{projects.map(p => ( ))}
); } Object.assign(window, { ModalShell, NewKeyModal, RecoveryCodesModal, ChangePasswordModal, TotpModal, UpgradeModal, PhotoUploadModal, ConfirmModal, ExportModal, ValueEditModal, ShareModal, ExportThreadModal, SearchModal, RenameThreadModal, MoveToProjectModal });