/* global React, TOOLS, TOOL_BY_ID, useI18n, useTheme, useHistory, useHashRoute, navigate, Icon, formatRelativeTime, clearHistory */ /* ============================================================ shell.jsx — App shell (top bar, breadcrumbs) + Hub page ============================================================ */ const { useState: useState_S, useEffect: useEffect_S, useMemo: useMemo_S } = React; // ---------- TopBar ---------- function TopBar({ tweaks, setTweaks }) { const { lang, setLang, t } = useI18n(); const { theme, toggle } = useTheme(); const route = useHashRoute(); let crumbs = null; if (route.name === 'tool') { const tool = TOOL_BY_ID[route.toolId]; crumbs = (
{ e.preventDefault(); navigate(''); }} href="#/">{t('crumb_hub')} {t('crumb_sep')} {tool ? t(tool.nameKey) : ''}
); } else { crumbs = (
{t('crumb_hub')}
); } return (
{ e.preventDefault(); navigate(''); }} href="#/">
C↓
{t('brand')}
{t('brand_sub')}
{crumbs}
); } // ---------- Hub ---------- function Hub() { const { t } = useI18n(); const [query, setQuery] = useState_S(''); const { items: history, reload } = useHistory(); const filtered = useMemo_S(() => { if (!query.trim()) return TOOLS; const q = query.toLowerCase(); return TOOLS.filter(tool => t(tool.nameKey).toLowerCase().includes(q) || t(tool.descKey).toLowerCase().includes(q) || tool.id.includes(q) ); }, [query, t]); const tweaks = window.__cv_tweaks || {}; const cardStyle = tweaks.cardStyle || 'soft'; return (
{t('hub_eyebrow')}

{t('hub_title_pre')} {t('hub_title_em')}

{t('hub_sub')}

setQuery(e.target.value)} placeholder={t('hub_search_placeholder')} />
{t('section_tools')}
{filtered.length.toString().padStart(2, '0')} · {t('section_tools_meta')}
{filtered.map(tool => ( ))}
{t('section_history')}
{history.length.toString().padStart(2, '0')} · {t('section_history_meta')} {history.length > 0 && ( )}
{history.length === 0 ? (
{t('history_empty')}
) : (
{history.slice(0, 12).map(entry => { const tool = TOOL_BY_ID[entry.toolId]; if (!tool) return null; return (
navigate(entry.payload ? `${entry.toolId}?h=${entry.id}` : entry.toolId)} style={{ '--card-hue': `var(--hue-${tool.hue})` }} >
{tool.initials}
{entry.title || t('untitled')} {entry.size != null && {window.fmtBytes(entry.size)}}
{t(tool.nameKey)}
{formatRelativeTime(entry.ts, t)}
); })}
)}
); } function ToolCard({ tool, styleVariant }) { const { t } = useI18n(); const cls = `tool-card style-${styleVariant || 'soft'}`; return (
navigate(tool.id)} style={{ '--card-hue': `var(--hue-${tool.hue})` }} >
{tool.initials}

{t(tool.nameKey)}

{t(tool.descKey)}

{t(tool.badgeKey)}
); } // ---------- Common Tool Page chrome ---------- function ToolHead({ tool, actions }) { const { t } = useI18n(); return (
{tool.initials}
{t(tool.nameKey)}
{t(tool.descKey)}
{actions}
); } function StatusBar({ status, left, right }) { const { t } = useI18n(); const pillClass = status === 'busy' ? 'busy' : status === 'err' ? 'err' : 'ok'; const pillText = status === 'busy' ? t('common_busy') : status === 'err' ? t('common_error') : t('common_ready'); return ( ); } // ---------- Dropzone ---------- function Dropzone({ accept, multiple, files, onFiles, onRemove, styleVariant = 'default', title, sub, busy, busyText }) { const { t } = useI18n(); const { dragover, bind, inputProps } = window.useDropzone({ accept, multiple, onFiles: (newFiles) => { if (multiple) onFiles([...(files || []), ...newFiles]); else onFiles(newFiles); }, }); const cls = `dropzone style-${styleVariant} ${dragover ? 'dragover' : ''}`; const hasFiles = files && files.length > 0; return (
{busy ? ( <>
{busyText || t('common_busy')}
) : ( <>
{dragover ? '↓' : '+'}
{dragover ? t('common_drop_here') : title}
{sub}
{hasFiles ? (
{files.map((f, i) => ( e.stopPropagation()}> {f.name} · {window.fmtBytes(f.size)} { e.stopPropagation(); onRemove?.(i); }} title={t('common_remove')}>× ))}
) : (
{t('common_browse')}
)} )}
); } // ---------- Common option controls ---------- function OptCheckbox({ checked, onChange, label, hint }) { return ( ); } function OptRadio({ value, onChange, label, options }) { return (
{label}
{options.map(o => ( ))}
); } function OptDivider() { return
; } // ---------- Empty state ---------- function EmptyOutput({ title, sub }) { return (
{title}
{sub}
); } // ---------- Export to window ---------- Object.assign(window, { TopBar, Hub, ToolHead, StatusBar, Dropzone, OptCheckbox, OptRadio, OptDivider, EmptyOutput, });