style(web): 全站 font-mono → font-body (DM Mono 設計系統套用)
Some checks failed
E2E Health Check / e2e-health (push) Successful in 17s
CD Pipeline / build-and-deploy (push) Has been cancelled

45 個 component + 6 個 page 統一從舊 font-mono 遷移到
font-body (DM Mono),確保設計系統一致性。

font-body = DM Mono (等寬),視覺效果相同但走新設計 token。
保留: font-heading (Syne)、font-dot-matrix (VT323/DSEG7)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-02 09:37:03 +08:00
parent db1aed81d9
commit 4d46e6b9a7
52 changed files with 338 additions and 338 deletions

View File

@@ -139,12 +139,12 @@ function StatCard({
/>
</div>
<div>
<p className="text-xs text-nothing-gray-500 font-mono uppercase">
<p className="text-xs text-nothing-gray-500 font-body uppercase">
{label}
</p>
<p className="text-xl font-bold text-nothing-black">{value}</p>
{subValue && (
<p className="text-[10px] text-nothing-gray-400 font-mono">
<p className="text-[10px] text-nothing-gray-400 font-body">
{subValue}
</p>
)}
@@ -349,7 +349,7 @@ export default function ActionLogPage({
<DataPincerPanel title={t('actionLog.title')} status="healthy">
{/* Toolbar */}
<div className="flex items-center justify-between mb-4 px-1">
<div className="text-sm text-nothing-gray-500 font-mono">
<div className="text-sm text-nothing-gray-500 font-body">
{totalCount > 0
? `${totalCount} ${t('actionLog.columns.operation').toLowerCase()}s`
: ''}
@@ -361,7 +361,7 @@ export default function ActionLogPage({
}}
disabled={isLoading}
className={cn(
'flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-mono',
'flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-body',
'bg-nothing-gray-100 text-nothing-gray-600',
'hover:bg-nothing-gray-200 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -378,7 +378,7 @@ export default function ActionLogPage({
{error && (
<div className="flex items-center gap-2 p-4 mb-4 rounded-lg bg-status-critical/10 border border-status-critical/20">
<AlertCircle className="w-4 h-4 text-status-critical" />
<span className="text-sm text-status-critical font-mono">
<span className="text-sm text-status-critical font-body">
{t('actionLog.fetchError')}: {error}
</span>
</div>
@@ -388,7 +388,7 @@ export default function ActionLogPage({
{isLoading && logs.length === 0 && (
<div className="flex items-center justify-center py-12">
<RefreshCw className="w-6 h-6 text-nothing-gray-400 animate-spin" />
<span className="ml-2 text-nothing-gray-400 font-mono">
<span className="ml-2 text-nothing-gray-400 font-body">
{t('actionLog.loading')}
</span>
</div>
@@ -398,7 +398,7 @@ export default function ActionLogPage({
{!isLoading && logs.length === 0 && !error && (
<div className="text-center py-12">
<FileText className="w-12 h-12 mx-auto mb-3 text-nothing-gray-300" />
<p className="text-nothing-gray-400 font-mono">
<p className="text-nothing-gray-400 font-body">
{t('actionLog.noLogs')}
</p>
</div>
@@ -410,25 +410,25 @@ export default function ActionLogPage({
<table className="w-full text-sm">
<thead>
<tr className="border-b border-nothing-gray-200">
<th className="px-3 py-2 text-left font-mono text-[10px] uppercase text-nothing-gray-500">
<th className="px-3 py-2 text-left font-body text-[10px] uppercase text-nothing-gray-500">
{t('actionLog.columns.time')}
</th>
<th className="px-3 py-2 text-left font-mono text-[10px] uppercase text-nothing-gray-500">
<th className="px-3 py-2 text-left font-body text-[10px] uppercase text-nothing-gray-500">
{t('actionLog.columns.operation')}
</th>
<th className="px-3 py-2 text-left font-mono text-[10px] uppercase text-nothing-gray-500">
<th className="px-3 py-2 text-left font-body text-[10px] uppercase text-nothing-gray-500">
{t('actionLog.columns.target')}
</th>
<th className="px-3 py-2 text-left font-mono text-[10px] uppercase text-nothing-gray-500">
<th className="px-3 py-2 text-left font-body text-[10px] uppercase text-nothing-gray-500">
{t('actionLog.columns.namespace')}
</th>
<th className="px-3 py-2 text-center font-mono text-[10px] uppercase text-nothing-gray-500">
<th className="px-3 py-2 text-center font-body text-[10px] uppercase text-nothing-gray-500">
{t('actionLog.columns.status')}
</th>
<th className="px-3 py-2 text-right font-mono text-[10px] uppercase text-nothing-gray-500">
<th className="px-3 py-2 text-right font-body text-[10px] uppercase text-nothing-gray-500">
{t('actionLog.columns.duration')}
</th>
<th className="px-3 py-2 text-left font-mono text-[10px] uppercase text-nothing-gray-500">
<th className="px-3 py-2 text-left font-body text-[10px] uppercase text-nothing-gray-500">
{t('actionLog.columns.executor')}
</th>
</tr>
@@ -439,13 +439,13 @@ export default function ActionLogPage({
key={log.id}
className="border-b border-nothing-gray-100 hover:bg-nothing-gray-50/50 transition-colors"
>
<td className="px-3 py-3 font-mono text-xs text-nothing-gray-600">
<td className="px-3 py-3 font-body text-xs text-nothing-gray-600">
{formatDate(log.created_at)}
</td>
<td className="px-3 py-3">
<span
className={cn(
'inline-flex items-center px-2 py-0.5 rounded text-[10px] font-mono font-bold uppercase',
'inline-flex items-center px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase',
log.operation_type === 'DELETE_POD' &&
'bg-status-critical/10 text-status-critical',
log.operation_type === 'RESTART_DEPLOYMENT' &&
@@ -458,33 +458,33 @@ export default function ActionLogPage({
log.operation_type}
</span>
</td>
<td className="px-3 py-3 font-mono text-xs text-nothing-black max-w-[200px] truncate">
<td className="px-3 py-3 font-body text-xs text-nothing-black max-w-[200px] truncate">
{log.target_resource}
</td>
<td className="px-3 py-3 font-mono text-xs text-nothing-gray-600">
<td className="px-3 py-3 font-body text-xs text-nothing-gray-600">
{log.namespace}
</td>
<td className="px-3 py-3 text-center">
{log.success ? (
<span className="inline-flex items-center gap-1 text-status-healthy">
<CheckCircle2 className="w-4 h-4" />
<span className="text-[10px] font-mono uppercase">
<span className="text-[10px] font-body uppercase">
{t('actionLog.status.success')}
</span>
</span>
) : (
<span className="inline-flex items-center gap-1 text-status-critical">
<XCircle className="w-4 h-4" />
<span className="text-[10px] font-mono uppercase">
<span className="text-[10px] font-body uppercase">
{t('actionLog.status.failure')}
</span>
</span>
)}
</td>
<td className="px-3 py-3 text-right font-mono text-xs text-nothing-gray-600">
<td className="px-3 py-3 text-right font-body text-xs text-nothing-gray-600">
{formatDuration(log.execution_duration_ms)}
</td>
<td className="px-3 py-3 font-mono text-xs text-nothing-gray-600">
<td className="px-3 py-3 font-body text-xs text-nothing-gray-600">
{log.executed_by}
</td>
</tr>
@@ -497,7 +497,7 @@ export default function ActionLogPage({
{/* Pagination */}
{totalPages > 1 && (
<div className="flex items-center justify-between mt-4 px-1">
<span className="text-xs text-nothing-gray-500 font-mono">
<span className="text-xs text-nothing-gray-500 font-body">
{t('actionLog.pagination.page', {
current: page,
total: totalPages,
@@ -508,7 +508,7 @@ export default function ActionLogPage({
onClick={handlePrevPage}
disabled={page <= 1 || isLoading}
className={cn(
'flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-mono',
'flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-body',
'border border-nothing-gray-200',
'hover:bg-nothing-gray-50 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -521,7 +521,7 @@ export default function ActionLogPage({
onClick={handleNextPage}
disabled={page >= totalPages || isLoading}
className={cn(
'flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-mono',
'flex items-center gap-1 px-3 py-1.5 rounded-lg text-xs font-body',
'border border-nothing-gray-200',
'hover:bg-nothing-gray-50 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -541,7 +541,7 @@ export default function ActionLogPage({
<p className="text-sm text-nothing-gray-500">
{t('footer.copyright')}
</p>
<p className="text-sm text-nothing-gray-400 font-mono">
<p className="text-sm text-nothing-gray-400 font-body">
{t('footer.poweredBy')} v1.0.0
</p>
</div>

View File

@@ -33,7 +33,7 @@ function SeverityBadge({ severity }: { severity: string }) {
const style = SEVERITY_STYLE[severity] ?? SEVERITY_STYLE['P3']
return (
<span className={cn(
'inline-flex items-center gap-1 px-2 py-0.5 rounded font-mono text-[11px] font-bold uppercase',
'inline-flex items-center gap-1 px-2 py-0.5 rounded font-body text-[11px] font-bold uppercase',
style.bg, style.text
)}>
{severity === 'P0' && <AlertTriangle className="w-3 h-3" />}
@@ -57,12 +57,12 @@ function StatPill({ label, value, highlight }: { label: string; value: number; h
: 'bg-white border-nothing-gray-200'
)}>
<span className={cn(
'text-2xl font-bold font-mono tabular-nums',
'text-2xl font-bold font-body tabular-nums',
highlight && value > 0 ? 'text-status-critical' : 'text-nothing-black'
)}>
{value}
</span>
<span className="text-[11px] font-mono text-nothing-gray-500 uppercase tracking-wider mt-0.5">
<span className="text-[11px] font-body text-nothing-gray-500 uppercase tracking-wider mt-0.5">
{label}
</span>
</div>
@@ -100,7 +100,7 @@ export default function AlertsPage({ params }: { params: { locale: string } }) {
<Bell className="w-6 h-6" />
{t('nav.alerts')}
</h2>
<p className="mt-1 text-sm text-nothing-gray-500 font-mono">
<p className="mt-1 text-sm text-nothing-gray-500 font-body">
{t('incident.activeIncidents')} · {t('alerts.autoRefresh', { seconds: 15 })}
</p>
</div>
@@ -109,7 +109,7 @@ export default function AlertsPage({ params }: { params: { locale: string } }) {
disabled={isLoading}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg',
'text-xs font-mono bg-nothing-gray-100 text-nothing-gray-600',
'text-xs font-body bg-nothing-gray-100 text-nothing-gray-600',
'hover:bg-nothing-gray-200 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
@@ -131,7 +131,7 @@ export default function AlertsPage({ params }: { params: { locale: string } }) {
{error && (
<div className="mb-4 p-4 rounded-lg bg-status-critical/10 border border-status-critical/20 flex items-center gap-2">
<AlertCircle className="w-4 h-4 text-status-critical" />
<span className="text-sm font-mono text-status-critical">{error}</span>
<span className="text-sm font-body text-status-critical">{error}</span>
</div>
)}
@@ -139,7 +139,7 @@ export default function AlertsPage({ params }: { params: { locale: string } }) {
{isLoading && sorted.length === 0 && (
<div className="flex items-center justify-center py-16">
<RefreshCw className="w-6 h-6 animate-spin text-nothing-gray-400" />
<span className="ml-2 text-nothing-gray-400 font-mono text-sm">{t('common.loading')}</span>
<span className="ml-2 text-nothing-gray-400 font-body text-sm">{t('common.loading')}</span>
</div>
)}
@@ -147,8 +147,8 @@ export default function AlertsPage({ params }: { params: { locale: string } }) {
{!isLoading && sorted.length === 0 && !error && (
<div className="flex flex-col items-center justify-center py-20 text-center">
<BellOff className="w-12 h-12 text-nothing-gray-300 mb-4" />
<p className="font-mono text-nothing-gray-500 text-sm">{t('incident.noActiveIncidents')}</p>
<p className="font-mono text-nothing-gray-400 text-xs mt-1">{t('incident.systemStable')}</p>
<p className="font-body text-nothing-gray-500 text-sm">{t('incident.noActiveIncidents')}</p>
<p className="font-body text-nothing-gray-400 text-xs mt-1">{t('incident.systemStable')}</p>
</div>
)}
@@ -163,7 +163,7 @@ export default function AlertsPage({ params }: { params: { locale: string } }) {
{/* Section header */}
<div className="flex items-center gap-3 mb-3">
<SeverityBadge severity={sev} />
<span className="text-[11px] font-mono text-nothing-gray-400">
<span className="text-[11px] font-body text-nothing-gray-400">
{tAlerts('incidentCount', { count: group.length })}
</span>
<div className="flex-1 h-px bg-nothing-gray-200" />

View File

@@ -86,11 +86,11 @@ function StatCard({
'rounded-lg border p-4',
highlight ? 'bg-claw-blue/5 border-claw-blue/20' : 'bg-white border-nothing-gray-200'
)}>
<p className="text-[11px] font-mono text-nothing-gray-500 uppercase tracking-wider mb-1">{label}</p>
<p className={cn('text-2xl font-bold font-mono tabular-nums', highlight ? 'text-claw-blue' : 'text-nothing-black')}>
<p className="text-[11px] font-body text-nothing-gray-500 uppercase tracking-wider mb-1">{label}</p>
<p className={cn('text-2xl font-bold font-body tabular-nums', highlight ? 'text-claw-blue' : 'text-nothing-black')}>
{value}
</p>
{sub && <p className="text-[11px] font-mono text-nothing-gray-400 mt-0.5">{sub}</p>}
{sub && <p className="text-[11px] font-body text-nothing-gray-400 mt-0.5">{sub}</p>}
</div>
)
}
@@ -147,18 +147,18 @@ function IncidentEvalRow({
onClick={() => setExpanded(v => !v)}
>
<span className={cn(
'px-2 py-0.5 rounded font-mono text-[11px] font-bold',
'px-2 py-0.5 rounded font-body text-[11px] font-bold',
severity === 'P0' ? 'bg-status-critical/10 text-status-critical' :
severity === 'P1' ? 'bg-status-warning/10 text-status-warning' :
'bg-nothing-gray-100 text-nothing-gray-600'
)}>{severity}</span>
<span className="font-mono text-sm text-nothing-black flex-1 truncate">{incidentId}</span>
<span className="font-body text-sm text-nothing-black flex-1 truncate">{incidentId}</span>
{loading && <RefreshCw className="w-4 h-4 animate-spin text-nothing-gray-400" />}
{!loading && eval_ && (
eval_.can_auto_repair
? <span className="flex items-center gap-1 text-[11px] font-mono text-status-healthy"><CheckCircle2 className="w-3.5 h-3.5" />{t('canAutoRepair')}</span>
: <span className="flex items-center gap-1 text-[11px] font-mono text-nothing-gray-400"><XCircle className="w-3.5 h-3.5" />{t('notEligibleShort')}</span>
? <span className="flex items-center gap-1 text-[11px] font-body text-status-healthy"><CheckCircle2 className="w-3.5 h-3.5" />{t('canAutoRepair')}</span>
: <span className="flex items-center gap-1 text-[11px] font-body text-nothing-gray-400"><XCircle className="w-3.5 h-3.5" />{t('notEligibleShort')}</span>
)}
{expanded ? <ChevronUp className="w-4 h-4 text-nothing-gray-400" /> : <ChevronDown className="w-4 h-4 text-nothing-gray-400" />}
</div>
@@ -168,34 +168,34 @@ function IncidentEvalRow({
<div className="px-4 py-4 bg-white space-y-3">
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<span className="text-[10px] font-mono text-nothing-gray-500 uppercase">Playbook</span>
<p className="font-mono text-nothing-black mt-0.5">{eval_.playbook_name ?? '—'}</p>
<span className="text-[10px] font-body text-nothing-gray-500 uppercase">Playbook</span>
<p className="font-body text-nothing-black mt-0.5">{eval_.playbook_name ?? '—'}</p>
</div>
<div>
<span className="text-[10px] font-mono text-nothing-gray-500 uppercase">{t('riskLevel')}</span>
<span className="text-[10px] font-body text-nothing-gray-500 uppercase">{t('riskLevel')}</span>
<p className="mt-0.5">
<span className={cn('px-2 py-0.5 rounded border text-[11px] font-mono font-bold', RISK_STYLE[eval_.risk_level] ?? RISK_STYLE.MEDIUM)}>
<span className={cn('px-2 py-0.5 rounded border text-[11px] font-body font-bold', RISK_STYLE[eval_.risk_level] ?? RISK_STYLE.MEDIUM)}>
{eval_.risk_level}
</span>
</p>
</div>
{eval_.success_rate != null && (
<div>
<span className="text-[10px] font-mono text-nothing-gray-500 uppercase">{t('successRate')}</span>
<p className="font-mono text-status-healthy font-bold mt-0.5">{(eval_.success_rate * 100).toFixed(1)}%</p>
<span className="text-[10px] font-body text-nothing-gray-500 uppercase">{t('successRate')}</span>
<p className="font-body text-status-healthy font-bold mt-0.5">{(eval_.success_rate * 100).toFixed(1)}%</p>
</div>
)}
{eval_.total_executions != null && (
<div>
<span className="text-[10px] font-mono text-nothing-gray-500 uppercase">{t('execCount')}</span>
<p className="font-mono text-nothing-black mt-0.5">{eval_.total_executions}</p>
<span className="text-[10px] font-body text-nothing-gray-500 uppercase">{t('execCount')}</span>
<p className="font-body text-nothing-black mt-0.5">{eval_.total_executions}</p>
</div>
)}
</div>
<div className="p-3 bg-nothing-gray-50 rounded-lg">
<span className="text-[10px] font-mono text-nothing-gray-500 uppercase">{t('decisionReason')}</span>
<p className="text-sm font-mono text-nothing-gray-700 mt-1">{eval_.reason}</p>
<span className="text-[10px] font-body text-nothing-gray-500 uppercase">{t('decisionReason')}</span>
<p className="text-sm font-body text-nothing-gray-700 mt-1">{eval_.reason}</p>
</div>
{/* Execute result */}
@@ -208,14 +208,14 @@ function IncidentEvalRow({
{result.success
? <CheckCircle2 className="w-4 h-4 text-status-healthy" />
: <XCircle className="w-4 h-4 text-status-critical" />}
<span className={cn('text-sm font-mono font-bold', result.success ? 'text-status-healthy' : 'text-status-critical')}>
<span className={cn('text-sm font-body font-bold', result.success ? 'text-status-healthy' : 'text-status-critical')}>
{result.success ? t('execSuccess', { ms: result.execution_time_ms }) : t('execFailed', { error: result.error })}
</span>
</div>
{result.executed_steps.length > 0 && (
<ul className="space-y-1">
{result.executed_steps.map((step, i) => (
<li key={i} className="text-xs font-mono text-nothing-gray-600 flex items-start gap-1.5">
<li key={i} className="text-xs font-body text-nothing-gray-600 flex items-start gap-1.5">
<Zap className="w-3 h-3 mt-0.5 text-claw-blue flex-shrink-0" />
{step}
</li>
@@ -232,7 +232,7 @@ function IncidentEvalRow({
disabled={executing}
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-lg',
'bg-claw-blue text-white font-mono text-sm font-semibold',
'bg-claw-blue text-white font-body text-sm font-semibold',
'hover:bg-claw-blue/90 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
@@ -305,7 +305,7 @@ export default function AutoRepairPage({ params }: { params: { locale: string }
<Wrench className="w-6 h-6" />
{tNav('autoRepair')}
</h2>
<p className="mt-1 text-sm text-nothing-gray-500 font-mono">
<p className="mt-1 text-sm text-nothing-gray-500 font-body">
{t('subtitle')}
</p>
</div>
@@ -314,7 +314,7 @@ export default function AutoRepairPage({ params }: { params: { locale: string }
disabled={statsLoading}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg',
'text-xs font-mono bg-nothing-gray-100 text-nothing-gray-600',
'text-xs font-body bg-nothing-gray-100 text-nothing-gray-600',
'hover:bg-nothing-gray-200 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
@@ -328,7 +328,7 @@ export default function AutoRepairPage({ params }: { params: { locale: string }
{statsError && (
<div className="mb-4 p-4 rounded-lg bg-status-critical/10 border border-status-critical/20 flex items-center gap-2">
<AlertCircle className="w-4 h-4 text-status-critical" />
<span className="text-sm font-mono text-status-critical">{statsError}</span>
<span className="text-sm font-body text-status-critical">{statsError}</span>
</div>
)}
@@ -369,10 +369,10 @@ export default function AutoRepairPage({ params }: { params: { locale: string }
? <CheckCircle2 className="w-5 h-5 text-status-healthy" />
: <ShieldAlert className="w-5 h-5 text-nothing-gray-400" />}
<div>
<p className={cn('text-sm font-mono font-semibold', stats.auto_repair_eligible ? 'text-status-healthy' : 'text-nothing-gray-600')}>
<p className={cn('text-sm font-body font-semibold', stats.auto_repair_eligible ? 'text-status-healthy' : 'text-nothing-gray-600')}>
{stats.auto_repair_eligible ? t('ready') : t('notReady')}
</p>
<p className="text-xs font-mono text-nothing-gray-400">
<p className="text-xs font-body text-nothing-gray-400">
{stats.auto_repair_eligible
? t('readyDesc', { count: stats.high_quality_playbooks })
: t('notReadyDesc')}
@@ -383,7 +383,7 @@ export default function AutoRepairPage({ params }: { params: { locale: string }
{/* Incident evaluation list */}
<div>
<h3 className="text-[11px] font-mono text-nothing-gray-500 uppercase tracking-widest mb-3">
<h3 className="text-[11px] font-body text-nothing-gray-500 uppercase tracking-widest mb-3">
{t('incidentEval')}
</h3>
@@ -396,7 +396,7 @@ export default function AutoRepairPage({ params }: { params: { locale: string }
{!incidentsLoading && eligibleIncidents.length === 0 && (
<div className="text-center py-10 border border-dashed border-nothing-gray-200 rounded-lg">
<CheckCircle2 className="w-8 h-8 text-status-healthy mx-auto mb-2" />
<p className="font-mono text-sm text-nothing-gray-500">{t('noEligible')}</p>
<p className="font-body text-sm text-nothing-gray-500">{t('noEligible')}</p>
</div>
)}

View File

@@ -171,7 +171,7 @@ export default function DemoPage({ params }: { params: { locale: string } }) {
{/* Error Display */}
{createError && (
<div className="p-3 bg-status-critical/10 border border-status-critical/30 rounded-lg">
<p className="text-sm text-status-critical font-mono">{createError}</p>
<p className="text-sm text-status-critical font-body">{createError}</p>
</div>
)}
</div>

View File

@@ -84,10 +84,10 @@ function HealthSummary({ data }: { data: DashboardResponse }) {
{ label: '異常', count: data.critical_count, color: 'text-status-critical', bg: 'bg-status-critical/10' },
].map(item => (
<div key={item.label} className={cn('rounded-lg border p-4 text-center', item.bg, 'border-transparent')}>
<div className={cn('text-3xl font-bold font-mono tabular-nums', item.color)}>
<div className={cn('text-3xl font-bold font-body tabular-nums', item.color)}>
{item.count}
</div>
<div className="text-[11px] font-mono text-nothing-gray-500 uppercase tracking-wider mt-1">
<div className="text-[11px] font-body text-nothing-gray-500 uppercase tracking-wider mt-1">
{item.label} · {Math.round(item.count / total * 100)}%
</div>
</div>
@@ -155,7 +155,7 @@ export default function MonitoringPage({ params }: { params: { locale: string }
<Monitor className="w-6 h-6" />
{t('nav.monitoring')}
</h2>
<p className="mt-1 text-sm text-nothing-gray-500 font-mono">
<p className="mt-1 text-sm text-nothing-gray-500 font-body">
{t('alerts.autoRefresh', { seconds: 30 })}
</p>
</div>
@@ -164,7 +164,7 @@ export default function MonitoringPage({ params }: { params: { locale: string }
disabled={isLoading}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg',
'text-xs font-mono bg-nothing-gray-100 text-nothing-gray-600',
'text-xs font-body bg-nothing-gray-100 text-nothing-gray-600',
'hover:bg-nothing-gray-200 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
)}
@@ -178,7 +178,7 @@ export default function MonitoringPage({ params }: { params: { locale: string }
{dashError && (
<div className="mb-4 p-4 rounded-lg bg-status-critical/10 border border-status-critical/20 flex items-center gap-2">
<AlertCircle className="w-4 h-4 text-status-critical" />
<span className="text-sm font-mono text-status-critical">{dashError}</span>
<span className="text-sm font-body text-status-critical">{dashError}</span>
</div>
)}
@@ -187,7 +187,7 @@ export default function MonitoringPage({ params }: { params: { locale: string }
{/* Gold Metrics */}
<div className="mb-6">
<h3 className="text-[11px] font-mono text-nothing-gray-500 uppercase tracking-widest mb-3">
<h3 className="text-[11px] font-body text-nothing-gray-500 uppercase tracking-widest mb-3">
(Gold Metrics)
</h3>
{metricsLoading && metrics.length === 0 ? (
@@ -201,7 +201,7 @@ export default function MonitoringPage({ params }: { params: { locale: string }
{/* Host Grid */}
<div className="mb-6">
<h3 className="text-[11px] font-mono text-nothing-gray-500 uppercase tracking-widest mb-3">
<h3 className="text-[11px] font-body text-nothing-gray-500 uppercase tracking-widest mb-3">
(Four-Host Architecture)
</h3>
<HostGrid hosts={[]} />
@@ -210,7 +210,7 @@ export default function MonitoringPage({ params }: { params: { locale: string }
{/* Service Table */}
{dashboard && dashboard.services && dashboard.services.length > 0 && (
<div>
<h3 className="text-[11px] font-mono text-nothing-gray-500 uppercase tracking-widest mb-3">
<h3 className="text-[11px] font-body text-nothing-gray-500 uppercase tracking-widest mb-3">
</h3>
<div className="rounded-lg border border-nothing-gray-200 overflow-hidden">
@@ -218,7 +218,7 @@ export default function MonitoringPage({ params }: { params: { locale: string }
<thead>
<tr className="bg-nothing-gray-50 border-b border-nothing-gray-200">
{['服務名稱', '狀態', '延遲', '可用率', '最後檢查'].map(h => (
<th key={h} className="px-4 py-2.5 text-left font-mono text-[10px] uppercase text-nothing-gray-500 tracking-wider">
<th key={h} className="px-4 py-2.5 text-left font-body text-[10px] uppercase text-nothing-gray-500 tracking-wider">
{h}
</th>
))}
@@ -230,25 +230,25 @@ export default function MonitoringPage({ params }: { params: { locale: string }
'border-b border-nothing-gray-100 last:border-0',
i % 2 === 0 ? 'bg-white' : 'bg-nothing-gray-50/50'
)}>
<td className="px-4 py-3 font-mono text-sm text-nothing-black font-medium">
<td className="px-4 py-3 font-body text-sm text-nothing-black font-medium">
{svc.name}
</td>
<td className="px-4 py-3">
<span className={cn(
'inline-flex items-center gap-1.5 px-2 py-0.5 rounded border text-[11px] font-mono font-bold uppercase',
'inline-flex items-center gap-1.5 px-2 py-0.5 rounded border text-[11px] font-body font-bold uppercase',
STATUS_BADGE[svc.status]
)}>
{STATUS_ICON[svc.status]}
{svc.status}
</span>
</td>
<td className="px-4 py-3 font-mono text-sm text-nothing-gray-700">
<td className="px-4 py-3 font-body text-sm text-nothing-gray-700">
{svc.latency_ms != null ? `${svc.latency_ms}ms` : '--'}
</td>
<td className="px-4 py-3 font-mono text-sm text-nothing-gray-700">
<td className="px-4 py-3 font-body text-sm text-nothing-gray-700">
{svc.uptime_pct != null ? `${svc.uptime_pct.toFixed(2)}%` : '--'}
</td>
<td className="px-4 py-3 font-mono text-xs text-nothing-gray-500">
<td className="px-4 py-3 font-body text-xs text-nothing-gray-500">
{svc.last_checked
? new Date(svc.last_checked).toLocaleTimeString()
: '--'}

View File

@@ -42,7 +42,7 @@ function SettingRow({
<div className="flex items-center justify-between py-4 border-b border-nothing-gray-100 last:border-0">
<div>
<p className="text-sm font-medium text-nothing-black">{label}</p>
{description && <p className="text-xs font-mono text-nothing-gray-400 mt-0.5">{description}</p>}
{description && <p className="text-xs font-body text-nothing-gray-400 mt-0.5">{description}</p>}
</div>
<div className="flex-shrink-0 ml-6">{children}</div>
</div>
@@ -131,7 +131,7 @@ export default function SettingsPage({ params }: { params: { locale: string } })
)}
>
<section.icon className="w-4 h-4 flex-shrink-0" />
<span className="text-sm font-mono">{t(section.titleKey)}</span>
<span className="text-sm font-body">{t(section.titleKey)}</span>
{activeSection !== section.id && <ChevronRight className="w-3 h-3 ml-auto text-nothing-gray-400" />}
</button>
))}
@@ -144,12 +144,12 @@ export default function SettingsPage({ params }: { params: { locale: string } })
{/* Appearance */}
{activeSection === 'appearance' && (
<div>
<h3 className="font-mono text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('appearanceSettings')}</h3>
<h3 className="font-body text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('appearanceSettings')}</h3>
<SettingRow label={t('compactMode')} description={t('compactModeDesc')}>
<Toggle value={compactMode} onChange={setCompactMode} />
</SettingRow>
<SettingRow label={t('designSystem')} description={t('designSystemValue')}>
<span className="text-xs font-mono text-nothing-gray-400 bg-nothing-gray-100 px-2 py-1 rounded">v7.0</span>
<span className="text-xs font-body text-nothing-gray-400 bg-nothing-gray-100 px-2 py-1 rounded">v7.0</span>
</SettingRow>
<SettingRow label={t('themeColor')} description={t('themeColorValue')}>
<div className="flex gap-1.5">
@@ -163,7 +163,7 @@ export default function SettingsPage({ params }: { params: { locale: string } })
{/* Language */}
{activeSection === 'language' && (
<div>
<h3 className="font-mono text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('languageSettings')}</h3>
<h3 className="font-body text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('languageSettings')}</h3>
{[
{ code: 'zh-TW', labelKey: 'zhTW', subKey: 'zhTWSub' },
{ code: 'en', labelKey: 'en', subKey: 'enSub' },
@@ -180,7 +180,7 @@ export default function SettingsPage({ params }: { params: { locale: string } })
>
<div>
<p className="text-sm font-medium text-nothing-black">{t(lang.labelKey)}</p>
<p className="text-xs font-mono text-nothing-gray-400">{t(lang.subKey)}</p>
<p className="text-xs font-body text-nothing-gray-400">{t(lang.subKey)}</p>
</div>
{params.locale === lang.code && <CheckCircle2 className="w-5 h-5 text-claw-blue" />}
</div>
@@ -191,7 +191,7 @@ export default function SettingsPage({ params }: { params: { locale: string } })
{/* Notifications */}
{activeSection === 'notify' && (
<div>
<h3 className="font-mono text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('notifySettings')}</h3>
<h3 className="font-body text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('notifySettings')}</h3>
<SettingRow label={t('browserNotify')} description={t('browserNotifyDesc')}>
<Toggle value={notifyBrowser} onChange={setNotifyBrowser} />
</SettingRow>
@@ -199,7 +199,7 @@ export default function SettingsPage({ params }: { params: { locale: string } })
<Toggle value={notifyP0Only} onChange={setNotifyP0Only} />
</SettingRow>
<SettingRow label={t('telegramNotify')} description={t('telegramNotifyDesc')}>
<span className="text-xs font-mono text-nothing-gray-400 bg-nothing-gray-100 px-2 py-1 rounded">{t('backendConfig')}</span>
<span className="text-xs font-body text-nothing-gray-400 bg-nothing-gray-100 px-2 py-1 rounded">{t('backendConfig')}</span>
</SettingRow>
</div>
)}
@@ -207,7 +207,7 @@ export default function SettingsPage({ params }: { params: { locale: string } })
{/* System */}
{activeSection === 'system' && (
<div>
<h3 className="font-mono text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('systemSettings')}</h3>
<h3 className="font-body text-sm font-bold text-nothing-black mb-4 uppercase tracking-wider">{t('systemSettings')}</h3>
{[
{ labelKey: 'frontendVersion', value: 'v1.0.0' },
{ labelKey: 'apiEndpoint', value: process.env.NEXT_PUBLIC_API_URL ?? t('notConfigured') },
@@ -215,7 +215,7 @@ export default function SettingsPage({ params }: { params: { locale: string } })
{ labelKey: 'phase', value: 'Phase 23 (2026-04-01)' },
].map(item => (
<SettingRow key={item.labelKey} label={t(item.labelKey)}>
<span className="text-sm font-mono text-nothing-gray-600">{item.value}</span>
<span className="text-sm font-body text-nothing-gray-600">{item.value}</span>
</SettingRow>
))}
</div>
@@ -226,12 +226,12 @@ export default function SettingsPage({ params }: { params: { locale: string } })
<div className="mt-6 flex items-center gap-3">
<button
onClick={saveSettings}
className="px-5 py-2.5 rounded-lg bg-nothing-black text-white font-mono text-sm font-semibold hover:bg-nothing-black/90 transition-colors"
className="px-5 py-2.5 rounded-lg bg-nothing-black text-white font-body text-sm font-semibold hover:bg-nothing-black/90 transition-colors"
>
{t('save')}
</button>
{saved && (
<span className="flex items-center gap-1.5 text-sm font-mono text-status-healthy">
<span className="flex items-center gap-1.5 text-sm font-body text-status-healthy">
<CheckCircle2 className="w-4 h-4" />
{t('saved')}
</span>

View File

@@ -145,7 +145,7 @@ export function ApprovalCard({
{/* Risk Badge */}
<div
className={cn(
'inline-flex items-center gap-2 px-3 py-1 rounded-full border text-xs font-mono tracking-wider mb-3',
'inline-flex items-center gap-2 px-3 py-1 rounded-full border text-xs font-body tracking-wider mb-3',
riskStyle.bgColor,
riskStyle.borderColor,
riskStyle.color
@@ -174,12 +174,12 @@ export function ApprovalCard({
{/* Signature Counter */}
<div className="text-right shrink-0">
<div className="font-mono text-xs text-nothing-gray-500 mb-1">
<div className="font-body text-xs text-nothing-gray-500 mb-1">
{t('signatures')}
</div>
<div
className={cn(
'font-mono text-lg',
'font-body text-lg',
needsMoreSignatures ? 'text-status-warning' : 'text-status-healthy'
)}
>
@@ -193,40 +193,40 @@ export function ApprovalCard({
{/* Blast Radius Section */}
<div className="space-y-3">
<h4 className="font-mono text-xs text-nothing-gray-500 tracking-wider">
<h4 className="font-body text-xs text-nothing-gray-500 tracking-wider">
{tBlast('title')}
</h4>
<div className="grid grid-cols-2 gap-3">
{/* Affected Pods */}
<div className="bg-nothing-gray-900 rounded-card p-3">
<div className="font-mono text-xs text-nothing-gray-500 mb-1">
<div className="font-body text-xs text-nothing-gray-500 mb-1">
{tBlast('affectedPods')}
</div>
<div className="font-mono text-lg text-nothing-white">
<div className="font-body text-lg text-nothing-white">
{request.blastRadius?.affectedPods ?? 0}
</div>
</div>
{/* Estimated Downtime */}
<div className="bg-nothing-gray-900 rounded-card p-3">
<div className="font-mono text-xs text-nothing-gray-500 mb-1">
<div className="font-body text-xs text-nothing-gray-500 mb-1">
{tBlast('estimatedDowntime')}
</div>
<div className="font-mono text-lg text-nothing-white">
<div className="font-body text-lg text-nothing-white">
{request.blastRadius?.estimatedDowntime ?? '0'}
</div>
</div>
{/* Related Services */}
<div className="bg-nothing-gray-900 rounded-card p-3">
<div className="font-mono text-xs text-nothing-gray-500 mb-1">
<div className="font-body text-xs text-nothing-gray-500 mb-1">
{tBlast('relatedServices')}
</div>
<div className="flex flex-wrap gap-1 mt-1">
{(request.blastRadius?.relatedServices ?? []).map((service) => (
<span
key={service}
className="px-2 py-0.5 bg-nothing-gray-800 rounded text-xs font-mono text-nothing-gray-300"
className="px-2 py-0.5 bg-nothing-gray-800 rounded text-xs font-body text-nothing-gray-300"
>
{service}
</span>
@@ -236,10 +236,10 @@ export function ApprovalCard({
{/* Data Impact */}
<div className="bg-nothing-gray-900 rounded-card p-3">
<div className="font-mono text-xs text-nothing-gray-500 mb-1">
<div className="font-body text-xs text-nothing-gray-500 mb-1">
{tBlast('dataImpact')}
</div>
<div className={cn('font-mono text-lg', dataImpactStyle.color)}>
<div className={cn('font-body text-lg', dataImpactStyle.color)}>
{dataImpactLabel}
</div>
</div>
@@ -251,7 +251,7 @@ export function ApprovalCard({
{/* Dry-Run Checks */}
<div className="space-y-3">
<h4 className="font-mono text-xs text-nothing-gray-500 tracking-wider">
<h4 className="font-body text-xs text-nothing-gray-500 tracking-wider">
{tDryRun('validation')}
</h4>
<div className="space-y-2">
@@ -271,12 +271,12 @@ export function ApprovalCard({
>
{check.passed ? '✓' : '✗'}
</div>
<span className="font-mono text-sm text-nothing-white">
<span className="font-body text-sm text-nothing-white">
{check.name}
</span>
</div>
{check.message && (
<span className="font-mono text-xs text-nothing-gray-500">
<span className="font-body text-xs text-nothing-gray-500">
{check.message}
</span>
)}
@@ -291,7 +291,7 @@ export function ApprovalCard({
{/* Footer: Meta + Actions */}
<div className="flex items-center justify-between">
{/* Meta */}
<div className="font-mono text-xs text-nothing-gray-600">
<div className="font-body text-xs text-nothing-gray-600">
<span>{t('requestedBy')} </span>
<span className="text-nothing-gray-400">{request.requestedBy}</span>
<span className="mx-2">|</span>
@@ -304,7 +304,7 @@ export function ApprovalCard({
<button
onClick={handleReject}
className={cn(
'px-5 py-2 rounded-button font-mono text-sm transition-all',
'px-5 py-2 rounded-button font-body text-sm transition-all',
'border border-nothing-gray-700 text-nothing-gray-400',
'hover:border-nothing-red hover:text-nothing-red hover:bg-nothing-red/10'
)}
@@ -318,7 +318,7 @@ export function ApprovalCard({
onClick={handleApprove}
disabled={!allChecksPassed}
className={cn(
'px-5 py-2 rounded-button font-mono text-sm transition-all animate-pulse',
'px-5 py-2 rounded-button font-body text-sm transition-all animate-pulse',
'bg-nothing-red text-nothing-white border-2 border-nothing-red',
'hover:bg-nothing-red/80'
)}
@@ -330,7 +330,7 @@ export function ApprovalCard({
onClick={handleApprove}
disabled={!allChecksPassed}
className={cn(
'px-5 py-2 rounded-button font-mono text-sm transition-all',
'px-5 py-2 rounded-button font-body text-sm transition-all',
allChecksPassed
? isDestructive
? 'bg-nothing-red/20 text-nothing-red border border-nothing-red/50 hover:bg-nothing-red/30'

View File

@@ -190,7 +190,7 @@ export function DataPincer({
{showLabel && (
<div
className={cn(
'mt-6 font-mono tracking-wider uppercase',
'mt-6 font-body tracking-wider uppercase',
sizeClass.label,
hasError ? 'text-status-critical' : 'text-nothing-gray-400',
config.animate && 'animate-pulse'

View File

@@ -266,7 +266,7 @@ export function ThinkingTerminalOptimized({
</div>
<h2 className="font-display text-display-sm">{t('title')}</h2>
</div>
<span className="font-mono text-xs text-nothing-gray-500">
<span className="font-body text-xs text-nothing-gray-500">
v1.0 | DOM Bypass | {lineCount} lines
</span>
</div>
@@ -277,7 +277,7 @@ export function ThinkingTerminalOptimized({
onClick={startStream}
disabled={isStreaming}
className={cn(
'flex-1 py-3 px-4 rounded-button font-mono text-sm transition-all border',
'flex-1 py-3 px-4 rounded-button font-body text-sm transition-all border',
isStreaming
? 'bg-nothing-gray-800 text-nothing-gray-500 border-nothing-gray-700 cursor-not-allowed'
: 'bg-nothing-white text-nothing-black border-transparent hover:bg-nothing-gray-200'
@@ -289,7 +289,7 @@ export function ThinkingTerminalOptimized({
{isStreaming && (
<button
onClick={stopStream}
className="py-3 px-4 rounded-button font-mono text-sm bg-status-critical/20 text-status-critical border border-status-critical/50 hover:bg-status-critical/30"
className="py-3 px-4 rounded-button font-body text-sm bg-status-critical/20 text-status-critical border border-status-critical/50 hover:bg-status-critical/30"
>
{t('stop')}
</button>
@@ -299,7 +299,7 @@ export function ThinkingTerminalOptimized({
onClick={clearTerminal}
disabled={isStreaming || lineCount === 0}
className={cn(
'py-3 px-4 rounded-button font-mono text-sm border',
'py-3 px-4 rounded-button font-body text-sm border',
isStreaming || lineCount === 0
? 'bg-nothing-gray-800 text-nothing-gray-500 border-nothing-gray-700 cursor-not-allowed'
: 'bg-nothing-gray-800 text-nothing-gray-300 border-nothing-gray-700 hover:bg-nothing-gray-700'
@@ -312,7 +312,7 @@ export function ThinkingTerminalOptimized({
{/* Terminal Output - DOM Bypass Container */}
<div
ref={terminalRef}
className="bg-nothing-gray-900 rounded-card p-4 overflow-y-auto font-mono text-sm"
className="bg-nothing-gray-900 rounded-card p-4 overflow-y-auto font-body text-sm"
style={{ maxHeight, minHeight: '150px' }}
>
{/* Content appended directly via DOM manipulation */}
@@ -340,10 +340,10 @@ export function ThinkingTerminalOptimized({
{/* Footer */}
<div className="flex items-center justify-between pt-2 border-t border-nothing-gray-800">
<p className="font-mono text-xs text-nothing-gray-600">
<p className="font-body text-xs text-nothing-gray-600">
{t('stream')}
</p>
<p className="font-mono text-xs text-nothing-gray-600">
<p className="font-body text-xs text-nothing-gray-600">
{status === 'streaming' ? (
<span className="text-status-thinking animate-pulse"> LIVE</span>
) : (

View File

@@ -59,7 +59,7 @@ function DependencyPathVisualizer({
<div className="text-xs text-nothing-gray-500 mb-2">
{direction === 'upstream' ? t('blastRadius') : t('rootCauseChain')}
</div>
<div className="font-mono text-sm space-y-1">
<div className="font-body text-sm space-y-1">
{paths.map((path, i) => (
<div key={i} className="flex items-center gap-2">
<span className="text-nothing-gray-600">{i === 0 ? '>' : '|'}</span>
@@ -97,7 +97,7 @@ function ServiceChainVisualizer({
</div>
{/* ASCII Art 風格圖形 */}
<div className="font-mono text-xs leading-relaxed">
<div className="font-body text-xs leading-relaxed">
{isBlastRadius ? (
// Blast Radius: 向上展示誰會受影響
<>
@@ -200,7 +200,7 @@ function FinOpsVisualizer({ data }: { data: NonNullable<ThinkingStep['finopsData
</span>
<span
className={cn(
'font-mono',
'font-body',
action.risk === 'low' && 'text-status-healthy',
action.risk === 'medium' && 'text-status-warning',
action.risk === 'high' && 'text-status-critical'
@@ -338,7 +338,7 @@ export function ThinkingTerminal({
</div>
<h2 className="font-display text-display-sm">{t('title')}</h2>
</div>
<span className="font-mono text-xs text-nothing-gray-500">
<span className="font-body text-xs text-nothing-gray-500">
v0.1.0 | SSE
</span>
</div>
@@ -348,7 +348,7 @@ export function ThinkingTerminal({
onClick={() => startThinkingStream()}
disabled={isStreaming}
className={cn(
'w-full py-3 px-4 rounded-button font-mono text-sm transition-all border',
'w-full py-3 px-4 rounded-button font-body text-sm transition-all border',
isStreaming
? 'bg-nothing-gray-800 text-nothing-gray-500 border-nothing-gray-700 cursor-not-allowed'
: 'bg-nothing-white text-nothing-black border-transparent hover:bg-nothing-gray-200'
@@ -359,7 +359,7 @@ export function ThinkingTerminal({
{/* Terminal Output */}
<div
className="bg-nothing-gray-900 rounded-card p-4 overflow-y-auto font-mono text-sm"
className="bg-nothing-gray-900 rounded-card p-4 overflow-y-auto font-body text-sm"
style={{ maxHeight, minHeight: '150px' }}
>
{thinkingStream.length === 0 && !isStreaming && !error && (
@@ -383,10 +383,10 @@ export function ThinkingTerminal({
{/* Footer */}
<div className="flex items-center justify-between pt-2 border-t border-nothing-gray-800">
<p className="font-mono text-xs text-nothing-gray-600">
<p className="font-body text-xs text-nothing-gray-600">
{t('stream')}
</p>
<p className="font-mono text-xs text-nothing-gray-600">
<p className="font-body text-xs text-nothing-gray-600">
{t('events', { count: thinkingStream.length })}
</p>
</div>

View File

@@ -73,13 +73,13 @@ export function AICommandPanel({ className }: AICommandPanelProps) {
<div className="flex items-center justify-between px-1">
<div className="flex items-center gap-2">
<ShieldCheck className="w-4 h-4 text-claw-blue" />
<span className="font-mono text-xs text-nothing-gray-600">
<span className="font-body text-xs text-nothing-gray-600">
{tApproval('pendingApprovals')}
</span>
</div>
<div className="flex items-center gap-1.5">
<Bell className="w-3 h-3 text-status-warning animate-pulse" />
<span className="font-mono text-xs font-bold text-status-warning">
<span className="font-body text-xs font-bold text-status-warning">
{pendingApprovals.length}
</span>
</div>
@@ -107,7 +107,7 @@ export function AICommandPanel({ className }: AICommandPanelProps) {
{pendingApprovals.length === 0 && (
<div className="flex flex-col items-center justify-center py-6 text-center">
<ShieldCheck className="w-8 h-8 text-nothing-gray-300 mb-2" />
<p className="font-mono text-xs text-nothing-gray-400">
<p className="font-body text-xs text-nothing-gray-400">
{tApproval('noApprovals')}
</p>
</div>

View File

@@ -179,18 +179,18 @@ export function AIThinkingPanel({
<div className="flex-1 min-w-0">
{/* Header */}
<div className="flex items-center gap-2 mb-3">
<span className="text-xs font-mono text-status-thinking uppercase tracking-widest">
<span className="text-xs font-body text-status-thinking uppercase tracking-widest">
OpenClaw AI
</span>
{alertType && (
<span className="px-2 py-0.5 rounded text-[10px] font-mono bg-status-thinking/10 text-status-thinking border border-status-thinking/20">
<span className="px-2 py-0.5 rounded text-[10px] font-body bg-status-thinking/10 text-status-thinking border border-status-thinking/20">
{alertType}
</span>
)}
</div>
{/* Typewriter text - Dot Matrix Style (Phase 8.0 #16: CSS cursor blink) */}
<div className="font-mono text-lg text-nothing-gray-800 tracking-wide">
<div className="font-body text-lg text-nothing-gray-800 tracking-wide">
<span className="whitespace-pre-wrap">{displayText}</span>
<span className="inline-block w-2 h-5 bg-status-thinking ml-1 -mb-0.5 animate-pulse" />
</div>
@@ -216,7 +216,7 @@ export function AIThinkingPanel({
{/* Bottom status bar */}
<div className="mt-4 pt-4 border-t border-status-thinking/10">
<div className="flex items-center justify-between text-xs font-mono text-nothing-gray-500">
<div className="flex items-center justify-between text-xs font-body text-nothing-gray-500">
<span className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-status-thinking animate-pulse" />
{currentPhase === 'complete' ? t('analysisComplete') : t('processingAlert')}

View File

@@ -280,7 +280,7 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
disabled={isSimulating}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg',
'font-mono text-xs font-medium',
'font-body text-xs font-medium',
'bg-status-critical/10 text-status-critical border border-status-critical/20',
'hover:bg-status-critical/20 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -298,7 +298,7 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
disabled={isSimulating}
className={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-lg',
'font-mono text-xs font-medium',
'font-body text-xs font-medium',
'bg-status-warning/10 text-status-warning border border-status-warning/20',
'hover:bg-status-warning/20 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -332,7 +332,7 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
<GlassCardTitle className="text-nothing-gray-500">
{tApproval('noApprovals')}
</GlassCardTitle>
<p className="text-sm text-nothing-gray-400 mt-2 font-mono">
<p className="text-sm text-nothing-gray-400 mt-2 font-body">
[SYS] All clear. Awaiting alerts...
</p>
</GlassCard>
@@ -356,7 +356,7 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
{/* Phase 3: Permission Warning Badge */}
{!hasPermission && (
<div className="absolute top-3 right-3 flex items-center gap-1 px-2 py-1 bg-status-critical/10 border border-status-critical/30 rounded text-[10px] font-mono text-status-critical z-10">
<div className="absolute top-3 right-3 flex items-center gap-1 px-2 py-1 bg-status-critical/10 border border-status-critical/30 rounded text-[10px] font-body text-status-critical z-10">
<Lock className="w-3 h-3" />
{getRequiredRolesDisplay(frontendApproval.riskLevel)}
</div>
@@ -382,11 +382,11 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
{/* Current User */}
<div className="flex items-center gap-2 px-3 py-1.5 bg-nothing-gray-100 rounded-lg">
<Lock className="w-3.5 h-3.5 text-nothing-gray-500" />
<span className="font-mono text-xs text-nothing-gray-600">
<span className="font-body text-xs text-nothing-gray-600">
: <span className="font-bold text-nothing-gray-800">{currentUserName}</span>
</span>
<span className={cn(
'px-2 py-0.5 rounded text-[10px] font-mono font-bold uppercase',
'px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase',
currentUserRole === 'cto' || currentUserRole === 'ciso' || currentUserRole === 'ceo'
? 'bg-status-healthy/10 text-status-healthy border border-status-healthy/30'
: currentUserRole === 'admin'
@@ -399,11 +399,11 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
{/* Role Switcher (Demo Only) */}
<div className="flex items-center gap-1.5">
<span className="font-mono text-[10px] text-nothing-gray-400">:</span>
<span className="font-body text-[10px] text-nothing-gray-400">:</span>
<button
onClick={() => setCurrentUserRole('devops')}
className={cn(
'px-2 py-1 rounded text-[10px] font-mono font-medium transition-colors',
'px-2 py-1 rounded text-[10px] font-body font-medium transition-colors',
currentUserRole === 'devops'
? 'bg-status-warning/20 text-status-warning'
: 'bg-nothing-gray-100 text-nothing-gray-500 hover:bg-nothing-gray-200'
@@ -414,7 +414,7 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
<button
onClick={() => setCurrentUserRole('cto')}
className={cn(
'px-2 py-1 rounded text-[10px] font-mono font-medium transition-colors',
'px-2 py-1 rounded text-[10px] font-body font-medium transition-colors',
currentUserRole === 'cto'
? 'bg-status-healthy/20 text-status-healthy'
: 'bg-nothing-gray-100 text-nothing-gray-500 hover:bg-nothing-gray-200'
@@ -426,7 +426,7 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
</div>
{/* Debug Info */}
<div className="text-[10px] font-mono text-nothing-gray-400">
<div className="text-[10px] font-body text-nothing-gray-400">
Pending: {pendingApprovals.length} | Status: {openclawStatus}
</div>
</div>
@@ -452,29 +452,29 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
{/* Risk Level Badge */}
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-status-critical/10 border border-status-critical/30 mb-4">
<AlertTriangle className="w-4 h-4 text-status-critical" />
<span className="font-mono text-sm text-status-critical font-semibold uppercase">
<span className="font-body text-sm text-status-critical font-semibold uppercase">
{accessDeniedModal.riskLevel} RISK
</span>
</div>
{/* Message */}
<p className="text-nothing-gray-600 font-mono text-sm mb-2">
<p className="text-nothing-gray-600 font-body text-sm mb-2">
</p>
<p className="text-nothing-gray-500 font-mono text-xs mb-6">
<p className="text-nothing-gray-500 font-body text-xs mb-6">
: <span className="text-status-warning font-bold">{currentUserRole.toUpperCase()}</span>
</p>
{/* Required Roles */}
<div className="bg-nothing-gray-50 rounded-lg p-4 mb-6">
<p className="text-[10px] text-nothing-gray-500 font-mono uppercase tracking-wider mb-2">
<p className="text-[10px] text-nothing-gray-500 font-body uppercase tracking-wider mb-2">
</p>
<div className="flex flex-wrap justify-center gap-2">
{accessDeniedModal.requiredRoles.split(' / ').map((role) => (
<span
key={role}
className="px-2 py-1 rounded bg-status-healthy/10 text-status-healthy font-mono text-xs font-bold border border-status-healthy/30"
className="px-2 py-1 rounded bg-status-healthy/10 text-status-healthy font-body text-xs font-bold border border-status-healthy/30"
>
{role}
</span>
@@ -485,7 +485,7 @@ export function HITLSection({ locale: _locale, className }: HITLSectionProps) {
{/* Action Button */}
<button
onClick={() => setAccessDeniedModal(null)}
className="w-full px-6 py-3 rounded-xl font-mono text-sm font-semibold bg-nothing-gray-100 text-nothing-gray-700 hover:bg-nothing-gray-200 transition-colors"
className="w-full px-6 py-3 rounded-xl font-body text-sm font-semibold bg-nothing-gray-100 text-nothing-gray-700 hover:bg-nothing-gray-200 transition-colors"
>
</button>

View File

@@ -279,7 +279,7 @@ export function OpenClawPanel({
{/* Complete indicator */}
{status === 'complete' && (
<div className="mt-3 flex justify-center">
<span className="px-2 py-0.5 rounded text-[9px] font-mono bg-status-healthy/10 text-status-healthy border border-status-healthy/20">
<span className="px-2 py-0.5 rounded text-[9px] font-body bg-status-healthy/10 text-status-healthy border border-status-healthy/20">
READY
</span>
</div>

View File

@@ -292,12 +292,12 @@ export function OpenClawStateMachine({
{/* Status Bar */}
<div className="flex items-center justify-between px-1">
<div className="flex items-center gap-2">
<span className="font-mono text-xs text-nothing-gray-500">
<span className="font-body text-xs text-nothing-gray-500">
STATE:
</span>
<span
className={cn(
'px-2 py-0.5 rounded text-[10px] font-mono font-bold uppercase',
'px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase',
machineState === 'idle' && 'bg-nothing-gray-100 text-nothing-gray-600',
machineState === 'thinking' && 'bg-status-thinking/10 text-status-thinking',
machineState === 'awaiting_approval' && 'bg-status-warning/10 text-status-warning'
@@ -317,7 +317,7 @@ export function OpenClawStateMachine({
{error && (
<div className="flex items-center gap-1 text-status-critical">
<AlertCircle className="w-3 h-3" />
<span className="text-[10px] font-mono">{error}</span>
<span className="text-[10px] font-body">{error}</span>
</div>
)}
@@ -326,7 +326,7 @@ export function OpenClawStateMachine({
onClick={fetchPendingApprovals}
disabled={isLoading}
className={cn(
'flex items-center gap-1 px-2 py-1 rounded text-[10px] font-mono',
'flex items-center gap-1 px-2 py-1 rounded text-[10px] font-body',
'bg-nothing-gray-100 text-nothing-gray-600',
'hover:bg-nothing-gray-200 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed'
@@ -349,10 +349,10 @@ export function OpenClawStateMachine({
<div className="space-y-2">
{/* 批次操作提示 */}
<div className="flex items-center justify-between px-1 mb-2">
<span className="text-[10px] font-mono text-nothing-gray-400">
<span className="text-[10px] font-body text-nothing-gray-400">
{pendingApprovals.length}
</span>
<span className="text-[10px] font-mono text-nothing-gray-400">
<span className="text-[10px] font-body text-nothing-gray-400">
</span>
</div>
@@ -400,13 +400,13 @@ export function OpenClawStateMachine({
{/* 標題 */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-mono text-sm font-medium text-nothing-gray-900 truncate">
<span className="font-body text-sm font-medium text-nothing-gray-900 truncate">
{title}
</span>
</div>
<div className="flex items-center gap-2 mt-0.5">
<span className={cn(
'text-[10px] font-mono font-bold uppercase px-1.5 py-0.5 rounded',
'text-[10px] font-body font-bold uppercase px-1.5 py-0.5 rounded',
isCritical && 'bg-status-critical/10 text-status-critical',
isHigh && 'bg-status-warning/10 text-status-warning',
approval.riskLevel === 'medium' && 'bg-status-warning/10 text-status-warning',
@@ -414,7 +414,7 @@ export function OpenClawStateMachine({
)}>
{approval.riskLevel}
</span>
<span className="text-[10px] font-mono text-nothing-gray-400">
<span className="text-[10px] font-body text-nothing-gray-400">
{approval.currentSignatures}/{approval.requiredSignatures}
</span>
</div>
@@ -530,10 +530,10 @@ export function OpenClawStateMachine({
{machineState === 'idle' && pendingApprovals.length === 0 && !isLoading && (
<div className="text-center py-6 border border-dashed border-nothing-gray-200 rounded-lg">
<CheckCircle2 className="w-8 h-8 mx-auto mb-2 text-status-healthy/50" />
<p className="font-mono text-xs text-nothing-gray-400">
<p className="font-body text-xs text-nothing-gray-400">
{t('ai.standby')}
</p>
<p className="font-mono text-[10px] text-nothing-gray-300 mt-1">
<p className="font-body text-[10px] text-nothing-gray-300 mt-1">
{lastFetch
? `Last check: ${lastFetch.toLocaleTimeString()}`
: 'Waiting for first fetch...'}
@@ -544,7 +544,7 @@ export function OpenClawStateMachine({
{/* Demo mode warning */}
{demoMode && (
<div className="bg-status-warning/10 border border-status-warning/20 rounded-lg p-3">
<p className="font-mono text-xs text-status-warning">
<p className="font-body text-xs text-status-warning">
Demo mode is enabled. Real API polling is disabled.
</p>
</div>

View File

@@ -134,7 +134,7 @@ function ThinkingLine({
// 減少高頻 setState 造成的 DOM 重繪
return (
<div className="flex items-start gap-2 font-mono text-sm leading-relaxed">
<div className="flex items-start gap-2 font-body text-sm leading-relaxed">
{/* Prefix */}
<span
className={cn(
@@ -232,7 +232,7 @@ export function ThinkingStream({
{messages.slice(0, currentIndex).map((msg) => (
<div
key={msg.id}
className="flex items-start gap-2 font-mono text-sm leading-relaxed opacity-60"
className="flex items-start gap-2 font-body text-sm leading-relaxed opacity-60"
>
<span
className={cn(

View File

@@ -193,7 +193,7 @@ function LongPressButton({
onPointerCancel={handlePointerUp}
disabled={disabled}
className={cn(
'relative overflow-hidden px-6 py-3.5 rounded-xl font-mono text-sm font-semibold',
'relative overflow-hidden px-6 py-3.5 rounded-xl font-body text-sm font-semibold',
'transition-all duration-300 ease-out select-none touch-none',
'active:scale-[0.97]',
disabled
@@ -360,7 +360,7 @@ export function ApprovalCard({
{readOnly && finalStatus && (
<div className={cn(
'absolute top-3 right-3 flex items-center gap-1.5 px-2.5 py-1 rounded-lg',
'font-mono text-xs font-bold uppercase',
'font-body text-xs font-bold uppercase',
finalStatus === 'executed' && 'bg-status-healthy/10 text-status-healthy border border-status-healthy/20',
finalStatus === 'approved' && 'bg-claw-blue/10 text-claw-blue border border-claw-blue/20',
finalStatus === 'rejected' && 'bg-nothing-gray-100 text-nothing-gray-500 border border-nothing-gray-200',
@@ -389,7 +389,7 @@ export function ApprovalCard({
<div
className={cn(
'inline-flex items-center gap-2 px-3 py-1.5 rounded-lg',
'font-mono text-xs font-semibold tracking-wide uppercase',
'font-body text-xs font-semibold tracking-wide uppercase',
isCritical
? 'bg-status-critical/10 text-status-critical border border-status-critical/30'
: request.riskLevel === 'high'
@@ -430,12 +430,12 @@ export function ApprovalCard({
{/* Multi-Sig Counter + Progress - Enhanced */}
<div className="text-right">
<div className="text-[10px] text-nothing-gray-500 font-mono uppercase tracking-wider mb-1">
<div className="text-[10px] text-nothing-gray-500 font-body uppercase tracking-wider mb-1">
{t('signatures')}
</div>
<div
className={cn(
'text-2xl font-mono font-bold tabular-nums',
'text-2xl font-body font-bold tabular-nums',
'transition-colors duration-200',
needsMoreSignatures ? 'text-status-warning' : 'text-status-healthy'
)}
@@ -460,7 +460,7 @@ export function ApprovalCard({
{/* 已簽核人員列表 (有簽名時顯示) */}
{signatures && signatures.length > 0 && (
<div className="px-6 -mt-2 mb-4">
<div className="flex items-center gap-2 text-[10px] text-nothing-gray-500 font-mono">
<div className="flex items-center gap-2 text-[10px] text-nothing-gray-500 font-body">
<Shield className="w-3 h-3" />
<span>:</span>
{signatures.map((sig, idx) => (
@@ -486,7 +486,7 @@ export function ApprovalCard({
{/* kubectl 命令: 獨立顯示,可複製 */}
{request.action.includes('|') && (
<code className="block mb-3 px-3 py-2 bg-nothing-gray-100 rounded-lg text-xs font-mono text-nothing-gray-700 break-all border border-nothing-gray-200">
<code className="block mb-3 px-3 py-2 bg-nothing-gray-100 rounded-lg text-xs font-body text-nothing-gray-700 break-all border border-nothing-gray-200">
{request.action.split('|')[1].trim()}
</code>
)}
@@ -504,7 +504,7 @@ export function ApprovalCard({
{/* Blast Radius - 可折疊面板 */}
<details className="mb-4 group" open={isCritical || isDestructive}>
<summary className="flex items-center justify-between cursor-pointer select-none py-2 px-3 -mx-3 rounded-lg hover:bg-nothing-gray-50 transition-colors">
<h4 className="text-[10px] font-mono text-nothing-gray-500 uppercase tracking-widest flex items-center gap-2">
<h4 className="text-[10px] font-body text-nothing-gray-500 uppercase tracking-widest flex items-center gap-2">
<Zap className="w-3 h-3" />
{tBlast('title')}
{/* 摘要預覽 */}
@@ -528,14 +528,14 @@ export function ApprovalCard({
/>
<div className="col-span-2">
<div className="bg-nothing-gray-50 rounded-lg p-3 border border-nothing-gray-200">
<div className="text-[10px] text-nothing-gray-500 font-mono uppercase tracking-wider mb-2">
<div className="text-[10px] text-nothing-gray-500 font-body uppercase tracking-wider mb-2">
{tBlast('relatedServices')}
</div>
<div className="flex flex-wrap gap-1.5">
{(request.blastRadius.relatedServices ?? []).map((svc) => (
<span
key={svc}
className="px-2 py-0.5 rounded text-xs font-mono bg-white border border-nothing-gray-200 text-nothing-gray-700"
className="px-2 py-0.5 rounded text-xs font-body bg-white border border-nothing-gray-200 text-nothing-gray-700"
>
{svc}
</span>
@@ -552,7 +552,7 @@ export function ApprovalCard({
<div className="mb-5 p-4 bg-status-critical/5 border border-status-critical/20 rounded-lg">
<div className="flex items-center gap-2">
<StatusOrb status="critical" size="sm" pulse />
<span className="text-sm font-mono text-status-critical font-semibold">
<span className="text-sm font-body text-status-critical font-semibold">
{tBlast('dataImpact')}: {dataImpactLabel}
</span>
</div>
@@ -562,12 +562,12 @@ export function ApprovalCard({
{/* Dry-Run Checks - 可折疊面板 (預設收合) */}
<details className="mb-4 group">
<summary className="flex items-center justify-between cursor-pointer select-none py-2 px-3 -mx-3 rounded-lg hover:bg-nothing-gray-50 transition-colors">
<h4 className="text-[10px] font-mono text-nothing-gray-500 uppercase tracking-widest flex items-center gap-2">
<h4 className="text-[10px] font-body text-nothing-gray-500 uppercase tracking-widest flex items-center gap-2">
<Clock className="w-3 h-3" />
{tDryRun('validation')}
{/* 通過/失敗摘要 */}
<span className={cn(
'text-[10px] font-mono px-1.5 py-0.5 rounded',
'text-[10px] font-body px-1.5 py-0.5 rounded',
allChecksPassed
? 'bg-status-healthy/10 text-status-healthy'
: 'bg-status-critical/10 text-status-critical'
@@ -595,13 +595,13 @@ export function ApprovalCard({
) : (
<XCircle className="w-4 h-4 text-status-critical" />
)}
<span className="text-sm text-nothing-gray-800 font-mono font-medium">
<span className="text-sm text-nothing-gray-800 font-body font-medium">
{check.name}
</span>
</div>
{check.message && (
<span className={cn(
'text-xs font-mono px-2 py-0.5 rounded',
'text-xs font-body px-2 py-0.5 rounded',
check.passed
? 'bg-status-healthy/10 text-status-healthy'
: 'bg-status-critical/10 text-status-critical'
@@ -621,7 +621,7 @@ export function ApprovalCard({
<GlassCardFooter className="sticky bottom-0 bg-white border-t border-nothing-gray-100 p-4 mt-auto">
{/* Meta */}
<div className="flex items-center justify-between mb-4">
<div className="text-xs text-nothing-gray-500 font-mono">
<div className="text-xs text-nothing-gray-500 font-body">
<span>{t('requestedBy')} </span>
<span className="text-nothing-gray-700">{request.requestedBy}</span>
<span className="mx-2">|</span>
@@ -636,7 +636,7 @@ export function ApprovalCard({
<svg className="w-5 h-5 text-claw-blue animate-spin" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeDasharray="60" strokeDashoffset="20" />
</svg>
<span className="text-sm font-mono text-claw-blue font-semibold">
<span className="text-sm font-body text-claw-blue font-semibold">
...
</span>
</div>
@@ -646,7 +646,7 @@ export function ApprovalCard({
{/* 唯讀模式 - 不顯示按鈕 */}
{readOnly ? (
<div className="text-center py-2">
<p className="text-xs text-nothing-gray-400 font-mono">
<p className="text-xs text-nothing-gray-400 font-body">
</p>
</div>
@@ -657,7 +657,7 @@ export function ApprovalCard({
<div className="mb-4 p-3 bg-status-warning/10 border border-status-warning/30 rounded-lg">
<div className="flex items-center gap-2">
<StatusOrb status="warning" size="sm" />
<span className="text-sm font-mono text-status-warning">
<span className="text-sm font-body text-status-warning">
{t('waitingSecondSig')}
</span>
</div>
@@ -673,7 +673,7 @@ export function ApprovalCard({
disabled={isLoading || isProcessing}
className={cn(
'flex-1 flex items-center justify-center gap-2',
'px-6 py-3.5 rounded-xl font-mono text-sm font-semibold',
'px-6 py-3.5 rounded-xl font-body text-sm font-semibold',
'bg-nothing-gray-50 border-2 border-nothing-gray-200 text-nothing-gray-600',
'hover:border-nothing-gray-300 hover:bg-nothing-gray-100 hover:text-nothing-gray-800',
'active:scale-[0.97]',
@@ -707,7 +707,7 @@ export function ApprovalCard({
</div>
{/* Long Press Hint - Subtle */}
<p className="mt-4 text-[11px] text-nothing-gray-400 text-center font-mono tracking-wide">
<p className="mt-4 text-[11px] text-nothing-gray-400 text-center font-body tracking-wide">
{t('holdHint', {
seconds: holdSeconds,
action: hasPartialSignatures ? t('actionSign') : isDestructive ? t('actionConfirm') : t('actionApprove')
@@ -740,9 +740,9 @@ function MetricBox({ label, value, className, highlight = false }: MetricBoxProp
highlight ? 'border-status-warning/30' : 'border-nothing-gray-200',
className
)}>
<div className="text-[10px] text-nothing-gray-500 font-mono uppercase tracking-wider mb-1">{label}</div>
<div className="text-[10px] text-nothing-gray-500 font-body uppercase tracking-wider mb-1">{label}</div>
<div className={cn(
'text-lg font-mono font-bold tabular-nums',
'text-lg font-body font-bold tabular-nums',
highlight ? 'text-status-warning' : 'text-nothing-gray-900'
)}>
{value}

View File

@@ -143,14 +143,14 @@ export function ApprovalThreadItem({
{/* 頂部: 風險指示器 + 時間 */}
<div className="flex items-center justify-between mb-2">
<div className={cn(
'flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-mono',
'flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-body',
riskConfig.bgColor,
riskConfig.color
)}>
<RiskIcon className="w-3 h-3" />
<span className="uppercase">{approval.riskLevel}</span>
</div>
<span className="text-xs text-nothing-gray-400 font-mono">
<span className="text-xs text-nothing-gray-400 font-body">
{relativeTime}
</span>
</div>
@@ -167,7 +167,7 @@ export function ApprovalThreadItem({
{/* 底部: 動作類型 + 狀態指示 */}
<div className="flex items-center justify-between">
<span className="text-xs text-nothing-gray-500 font-mono truncate max-w-[70%]">
<span className="text-xs text-nothing-gray-500 font-body truncate max-w-[70%]">
{actionSummary}
</span>
<StatusOrb

View File

@@ -183,7 +183,7 @@ export function BatchModeSelector({
<h3 className="font-heading text-sm font-bold text-nothing-gray-900">
{tBatch('title')}
</h3>
<div className="flex items-center gap-2 text-xs font-mono">
<div className="flex items-center gap-2 text-xs font-body">
<span className="text-nothing-gray-400">{stats.total} {t('pendingApprovals')}</span>
</div>
</div>
@@ -192,25 +192,25 @@ export function BatchModeSelector({
<div className="grid grid-cols-4 gap-2 mb-4">
<div className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-nothing-gray-50">
<StatusOrb status="healthy" size="sm" />
<span className="text-xs font-mono text-nothing-gray-600">
<span className="text-xs font-body text-nothing-gray-600">
LOW: {stats.low}
</span>
</div>
<div className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-claw-blue/5">
<StatusOrb status="healthy" size="sm" />
<span className="text-xs font-mono text-claw-blue">
<span className="text-xs font-body text-claw-blue">
MED: {stats.medium}
</span>
</div>
<div className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-status-warning/5">
<StatusOrb status="warning" size="sm" />
<span className="text-xs font-mono text-status-warning">
<span className="text-xs font-body text-status-warning">
HIGH: {stats.high}
</span>
</div>
<div className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-status-critical/5">
<StatusOrb status="critical" size="sm" pulse />
<span className="text-xs font-mono text-status-critical">
<span className="text-xs font-body text-status-critical">
CRIT: {stats.critical}
</span>
</div>
@@ -241,7 +241,7 @@ export function BatchModeSelector({
{tBatch('bulkApprove')}
</span>
</div>
<span className="text-xs font-mono text-nothing-gray-400">
<span className="text-xs font-body text-nothing-gray-400">
{stats.canBulkApprove} {tBatch('eligible')}
</span>
</button>
@@ -263,7 +263,7 @@ export function BatchModeSelector({
{tBatch('sequential')}
</span>
</div>
<span className="text-xs font-mono text-nothing-gray-400">
<span className="text-xs font-body text-nothing-gray-400">
{stats.total} {tBatch('items')}
</span>
</button>
@@ -287,7 +287,7 @@ export function BatchModeSelector({
{tBatch('criticalOnly')}
</span>
</div>
<span className="text-xs font-mono text-status-critical">
<span className="text-xs font-body text-status-critical">
{stats.critical} {tBatch('items')}
</span>
</button>

View File

@@ -109,14 +109,14 @@ export function CompactApprovalItem({
{/* 標題與摘要 */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
<span className="font-mono text-sm font-medium text-nothing-gray-900 truncate">
<span className="font-body text-sm font-medium text-nothing-gray-900 truncate">
{title}
</span>
<ChevronRight className="w-4 h-4 text-nothing-gray-400 flex-shrink-0 group-hover:translate-x-0.5 transition-transform" />
</div>
<div className="flex items-center gap-2 mt-0.5">
<span className={cn(
'text-[10px] font-mono font-bold uppercase px-1.5 py-0.5 rounded',
'text-[10px] font-body font-bold uppercase px-1.5 py-0.5 rounded',
isCritical && 'bg-status-critical/10 text-status-critical',
isHigh && 'bg-status-warning/10 text-status-warning',
request.riskLevel === 'medium' && 'bg-status-warning/10 text-status-warning',
@@ -124,7 +124,7 @@ export function CompactApprovalItem({
)}>
{tRisk(request.riskLevel)}
</span>
<span className="text-[10px] font-mono text-nothing-gray-400">
<span className="text-[10px] font-body text-nothing-gray-400">
{request.currentSignatures}/{request.requiredSignatures}
</span>
</div>

View File

@@ -236,7 +236,7 @@ export function ConversationalView({
{/* SSE 連線狀態 */}
<div
className={cn(
'flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-mono',
'flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-body',
isConnected
? 'bg-status-healthy/10 text-status-healthy'
: 'bg-status-warning/10 text-status-warning'
@@ -264,7 +264,7 @@ export function ConversationalView({
{/* 導航提示 */}
{approvals.length > 1 && (
<div className="mt-2 flex items-center justify-between text-xs text-nothing-gray-400">
<span className="font-mono">
<span className="font-body">
{selectedIndex + 1} / {approvals.length}
</span>
<div className="flex items-center gap-1">
@@ -302,7 +302,7 @@ export function ConversationalView({
{approvals.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-nothing-gray-400">
<StatusOrb status="healthy" size="lg" />
<p className="mt-4 text-sm font-mono">{t('noPendingApprovals')}</p>
<p className="mt-4 text-sm font-body">{t('noPendingApprovals')}</p>
</div>
) : (
approvals.map((approval) => (
@@ -353,7 +353,7 @@ export function ConversationalView({
</button>
<div className="flex items-center gap-2">
{/* 分頁指示器 */}
<div className="flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-mono bg-nothing-gray-100">
<div className="flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-body bg-nothing-gray-100">
<span>{selectedIndex + 1} / {approvals.length}</span>
</div>
{/* 快速導航按鈕 */}
@@ -411,7 +411,7 @@ export function ConversationalView({
<div className="flex-1 flex items-center justify-center">
<div className="text-center text-nothing-gray-400">
<MessageSquare className="w-12 h-12 mx-auto mb-4 opacity-50" aria-hidden="true" />
<p className="text-sm font-mono">{t('selectApproval')}</p>
<p className="text-sm font-body">{t('selectApproval')}</p>
</div>
</div>
)}
@@ -439,7 +439,7 @@ export function ConversationalView({
<ShortcutRow keys={['?']} label={tCommon('showShortcuts')} />
<ShortcutRow keys={['Esc']} label={tCommon('close')} />
</div>
<p className="mt-4 text-xs text-nothing-gray-400 font-mono">
<p className="mt-4 text-xs text-nothing-gray-400 font-body">
{t('holdToApproveHint')}
</p>
</div>
@@ -469,7 +469,7 @@ function ShortcutRow({
<kbd
key={key}
className={cn(
'px-2 py-1 rounded text-xs font-mono border',
'px-2 py-1 rounded text-xs font-body border',
highlight
? 'bg-status-healthy/10 text-status-healthy border-status-healthy/30'
: 'bg-nothing-gray-100 text-nothing-gray-700 border-nothing-gray-200'

View File

@@ -245,7 +245,7 @@ export function LiveApprovalPanel({
<h2 className="font-heading text-2xl font-bold text-nothing-gray-900">
{t('pendingApprovals')}
</h2>
<span className="px-3 py-1 rounded-full bg-nothing-gray-100 text-nothing-gray-600 font-mono text-sm">
<span className="px-3 py-1 rounded-full bg-nothing-gray-100 text-nothing-gray-600 font-body text-sm">
{approvals.length}
</span>
</div>
@@ -254,14 +254,14 @@ export function LiveApprovalPanel({
{/* Error State */}
{error && (
<div className="p-4 bg-status-critical/10 border border-status-critical/30 rounded-lg">
<p className="text-sm text-status-critical font-mono">{t('fetchError')}: {error}</p>
<p className="text-sm text-status-critical font-body">{t('fetchError')}: {error}</p>
</div>
)}
{/* Phase 20: CSRF Error State */}
{csrfError && (
<div className="p-4 bg-status-warning/10 border border-status-warning/30 rounded-lg">
<p className="text-sm text-status-warning font-mono">
<p className="text-sm text-status-warning font-body">
CSRF Token 使
</p>
</div>
@@ -270,7 +270,7 @@ export function LiveApprovalPanel({
{/* Phase 20: CSRF Loading State */}
{csrfLoading && (
<div className="p-4 bg-claw-blue/10 border border-claw-blue/30 rounded-lg">
<p className="text-sm text-claw-blue font-mono">
<p className="text-sm text-claw-blue font-body">
🔐 ...
</p>
</div>
@@ -282,7 +282,7 @@ export function LiveApprovalPanel({
<GlassCardContent>
<div className="py-12 text-center">
<StatusOrb status="healthy" size="lg" glow className="mx-auto mb-4" />
<p className="text-nothing-gray-500 font-mono">{t('noApprovals')}</p>
<p className="text-nothing-gray-500 font-body">{t('noApprovals')}</p>
</div>
</GlassCardContent>
</GlassCard>
@@ -291,11 +291,11 @@ export function LiveApprovalPanel({
{/* Current User Role Display (Phase 3 Demo) */}
<div className="flex items-center gap-2 px-4 py-2 bg-nothing-gray-100 rounded-lg w-fit">
<Lock className="w-4 h-4 text-nothing-gray-500" />
<span className="font-mono text-xs text-nothing-gray-600">
<span className="font-body text-xs text-nothing-gray-600">
: <span className="font-bold text-nothing-gray-800">{currentUser.name}</span>
</span>
<span className={cn(
'px-2 py-0.5 rounded text-[10px] font-mono font-bold uppercase',
'px-2 py-0.5 rounded text-[10px] font-body font-bold uppercase',
currentUser.role === 'cto' || currentUser.role === 'ciso' || currentUser.role === 'ceo'
? 'bg-status-healthy/10 text-status-healthy border border-status-healthy/30'
: currentUser.role === 'admin'
@@ -326,7 +326,7 @@ export function LiveApprovalPanel({
{/* 🔴 已解決狀態橫幅 */}
{isResolved && (
<div className={cn(
'absolute -top-3 left-1/2 -translate-x-1/2 z-10 px-4 py-1 rounded-full font-mono text-xs font-bold uppercase tracking-wider shadow-lg',
'absolute -top-3 left-1/2 -translate-x-1/2 z-10 px-4 py-1 rounded-full font-body text-xs font-bold uppercase tracking-wider shadow-lg',
resolvedStatus === 'approved'
? 'bg-status-healthy text-white'
: 'bg-status-critical text-white'
@@ -345,7 +345,7 @@ export function LiveApprovalPanel({
{/* Permission Warning Badge (Phase 3) */}
{!canSignApproval(currentUser.role, approval.riskLevel) && (
<div className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 bg-status-critical/10 border border-status-critical/30 rounded text-[10px] font-mono text-status-critical">
<div className="absolute top-2 right-2 flex items-center gap-1 px-2 py-1 bg-status-critical/10 border border-status-critical/30 rounded text-[10px] font-body text-status-critical">
<Lock className="w-3 h-3" />
{getRequiredRolesDisplay(approval.riskLevel)}
</div>
@@ -356,7 +356,7 @@ export function LiveApprovalPanel({
<div className="absolute inset-0 flex items-center justify-center bg-status-healthy/20 rounded-xl">
<div className="text-center">
<StatusOrb status="healthy" size="lg" glow />
<p className="mt-2 text-status-healthy font-mono font-semibold">
<p className="mt-2 text-status-healthy font-body font-semibold">
{t('signSuccess')}
</p>
</div>
@@ -368,7 +368,7 @@ export function LiveApprovalPanel({
<div className="absolute inset-0 flex items-center justify-center bg-status-critical/20 rounded-xl">
<div className="text-center">
<StatusOrb status="critical" size="lg" glow />
<p className="mt-2 text-status-critical font-mono font-semibold">
<p className="mt-2 text-status-critical font-body font-semibold">
{tCommon('error')}
</p>
</div>
@@ -394,20 +394,20 @@ export function LiveApprovalPanel({
value={rejectReason}
onChange={(e) => setRejectReason(e.target.value)}
placeholder={t('enterReason')}
className="w-full h-32 px-4 py-3 rounded-lg border border-nothing-gray-200 bg-white/50 resize-none font-mono text-sm focus:outline-none focus:ring-2 focus:ring-status-critical/50"
className="w-full h-32 px-4 py-3 rounded-lg border border-nothing-gray-200 bg-white/50 resize-none font-body text-sm focus:outline-none focus:ring-2 focus:ring-status-critical/50"
autoFocus
/>
<div className="flex gap-3 mt-4">
<button
onClick={() => setRejectModalId(null)}
className="flex-1 px-6 py-3 rounded-lg border border-nothing-gray-300 text-nothing-gray-600 font-mono text-sm hover:bg-nothing-gray-100 transition-colors"
className="flex-1 px-6 py-3 rounded-lg border border-nothing-gray-300 text-nothing-gray-600 font-body text-sm hover:bg-nothing-gray-100 transition-colors"
>
{tCommon('cancel')}
</button>
<button
onClick={confirmReject}
disabled={!rejectReason.trim()}
className="flex-1 px-6 py-3 rounded-lg bg-status-critical text-white font-mono text-sm hover:bg-status-critical/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
className="flex-1 px-6 py-3 rounded-lg bg-status-critical text-white font-body text-sm hover:bg-status-critical/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{t('reject')}
</button>
@@ -438,29 +438,29 @@ export function LiveApprovalPanel({
{/* Risk Level Badge */}
<div className="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg bg-status-critical/10 border border-status-critical/30 mb-4">
<AlertTriangle className="w-4 h-4 text-status-critical" />
<span className="font-mono text-sm text-status-critical font-semibold uppercase">
<span className="font-body text-sm text-status-critical font-semibold uppercase">
{accessDeniedModal.riskLevel} RISK
</span>
</div>
{/* Message */}
<p className="text-nothing-gray-600 font-mono text-sm mb-2">
<p className="text-nothing-gray-600 font-body text-sm mb-2">
</p>
<p className="text-nothing-gray-500 font-mono text-xs mb-6">
<p className="text-nothing-gray-500 font-body text-xs mb-6">
: <span className="text-status-warning font-bold">{currentUser.role.toUpperCase()}</span>
</p>
{/* Required Roles */}
<div className="bg-nothing-gray-50 rounded-lg p-4 mb-6">
<p className="text-[10px] text-nothing-gray-500 font-mono uppercase tracking-wider mb-2">
<p className="text-[10px] text-nothing-gray-500 font-body uppercase tracking-wider mb-2">
</p>
<div className="flex flex-wrap justify-center gap-2">
{accessDeniedModal.requiredRoles.split(' / ').map((role) => (
<span
key={role}
className="px-2 py-1 rounded bg-status-healthy/10 text-status-healthy font-mono text-xs font-bold border border-status-healthy/30"
className="px-2 py-1 rounded bg-status-healthy/10 text-status-healthy font-body text-xs font-bold border border-status-healthy/30"
>
{role}
</span>
@@ -471,7 +471,7 @@ export function LiveApprovalPanel({
{/* Action Button */}
<button
onClick={() => setAccessDeniedModal(null)}
className="w-full px-6 py-3 rounded-xl font-mono text-sm font-semibold bg-nothing-gray-100 text-nothing-gray-700 hover:bg-nothing-gray-200 transition-colors"
className="w-full px-6 py-3 rounded-xl font-body text-sm font-semibold bg-nothing-gray-100 text-nothing-gray-700 hover:bg-nothing-gray-200 transition-colors"
>
</button>

View File

@@ -155,7 +155,7 @@ function Step({ step, index, isLast }: StepProps) {
<div className="flex items-center gap-2">
<span
className={cn(
'text-xs font-mono uppercase tracking-wider',
'text-xs font-body uppercase tracking-wider',
config.text
)}
>

View File

@@ -139,7 +139,7 @@ function MetricCard({ metric }: MetricCardProps) {
<p className="dot-matrix-display">
{metric.value}
{metric.unit && (
<span className="text-base font-mono font-normal text-ink-secondary ml-2">
<span className="text-base font-body font-normal text-ink-secondary ml-2">
{metric.unit}
</span>
)}

View File

@@ -98,7 +98,7 @@ function CustomTooltip({ active, payload, unit }: CustomTooltipProps) {
return (
<div className="bg-nothing-gray-900/95 backdrop-blur-sm px-3 py-2 rounded-lg border border-nothing-gray-700">
<p className="text-nothing-gray-400 text-xs mb-1">{time}</p>
<p className="text-white font-mono text-sm font-semibold">
<p className="text-white font-body text-sm font-semibold">
{data.value.toFixed(2)}{unit && ` ${unit}`}
</p>
</div>

View File

@@ -93,7 +93,7 @@ export function DataPincerCard({
{/* 標題 (如有) */}
{title && (
<div className="mb-4 pb-3 border-b border-nothing-gray-200/40">
<h3 className="font-mono text-xs uppercase tracking-wider text-ink-secondary">
<h3 className="font-body text-xs uppercase tracking-wider text-ink-secondary">
{title}
</h3>
</div>

View File

@@ -90,11 +90,11 @@ export function ConnectionStatus({
{showLabel && (
<div className="flex flex-col">
<span className="text-xs font-mono text-nothing-gray-600">
<span className="text-xs font-body text-nothing-gray-600">
{t(labelKey)}
</span>
{showLastUpdate && lastUpdate && (
<span className="text-[10px] font-mono text-nothing-gray-400">
<span className="text-[10px] font-body text-nothing-gray-400">
{lastUpdateStr}
</span>
)}
@@ -102,7 +102,7 @@ export function ConnectionStatus({
)}
{mockMode && (
<span className="px-1.5 py-0.5 text-[10px] font-mono bg-status-warning/10 text-status-warning rounded">
<span className="px-1.5 py-0.5 text-[10px] font-body bg-status-warning/10 text-status-warning rounded">
{t('mockMode')}
</span>
)}
@@ -127,7 +127,7 @@ export function ConnectionStatusCompact({ className }: { className?: string }) {
)}
>
<StatusOrb status={status} size="sm" pulse={connectionStatus !== 'connected'} />
<span className="font-mono text-xs text-nothing-gray-600 uppercase">
<span className="font-body text-xs text-nothing-gray-600 uppercase">
{t(connectionLabelKeys[connectionStatus])}
</span>
</div>

View File

@@ -113,7 +113,7 @@ export function HostCard({
<div className="flex items-center gap-3">
<StatusOrb status={status} size="md" glow />
<div>
<span className="inline-flex items-center px-2 py-0.5 rounded-md bg-nothing-gray-100 text-nothing-gray-600 text-xs font-mono">
<span className="inline-flex items-center px-2 py-0.5 rounded-md bg-nothing-gray-100 text-nothing-gray-600 text-xs font-body">
{ip}
</span>
</div>
@@ -140,18 +140,18 @@ export function HostCard({
>
<div className="flex items-center gap-2">
<StatusOrb status={service.status} size="sm" />
<span className="font-mono text-sm text-nothing-gray-800">
<span className="font-body text-sm text-nothing-gray-800">
{service.name}
</span>
</div>
<div className="flex items-center gap-2">
{service.port && (
<span className="text-xs text-nothing-gray-400 font-mono">
<span className="text-xs text-nothing-gray-400 font-body">
:{service.port}
</span>
)}
{service.latencyMs !== null && service.latencyMs !== undefined && (
<span className="text-xs text-nothing-gray-500 font-mono">
<span className="text-xs text-nothing-gray-500 font-body">
{service.latencyMs.toFixed(1)}ms
</span>
)}
@@ -168,7 +168,7 @@ export function HostCard({
<div>
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-nothing-gray-500">{tDashboard('cpu')}</span>
<span className="text-xs font-mono text-nothing-gray-700">
<span className="text-xs font-body text-nothing-gray-700">
{formatPercent(metrics.cpuPercent)}
</span>
</div>
@@ -191,7 +191,7 @@ export function HostCard({
<div>
<div className="flex items-center justify-between mb-1">
<span className="text-xs text-nothing-gray-500">{tDashboard('memory')}</span>
<span className="text-xs font-mono text-nothing-gray-700">
<span className="text-xs font-body text-nothing-gray-700">
{formatPercent(metrics.memoryPercent)}
</span>
</div>
@@ -218,7 +218,7 @@ export function HostCard({
<GlassCardFooter>
<div className="flex items-center justify-between">
<StatusBadge status={status} label={statusLabel} size="sm" />
<span className="text-xs text-nothing-gray-400 font-mono">
<span className="text-xs text-nothing-gray-400 font-body">
{new Date().toLocaleTimeString('zh-TW', {
hour: '2-digit',
minute: '2-digit',

View File

@@ -225,12 +225,12 @@ function OpenClawPanel() {
<h3 className="font-heading font-bold text-nothing-gray-900">
{t('name')}
</h3>
<span className="text-[10px] text-status-thinking font-mono uppercase tracking-wider">
<span className="text-[10px] text-status-thinking font-body uppercase tracking-wider">
{t('monitoring')}
</span>
</div>
</div>
<span className="text-[10px] text-nothing-gray-400 font-mono bg-nothing-gray-100 px-2 py-1 rounded">v5.0.0</span>
<span className="text-[10px] text-nothing-gray-400 font-body bg-nothing-gray-100 px-2 py-1 rounded">v5.0.0</span>
</GlassCardHeader>
<GlassCardContent>
@@ -243,7 +243,7 @@ function OpenClawPanel() {
<div className="flex items-start gap-2 mb-2">
<span
className={cn(
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-mono font-semibold',
'inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-body font-semibold',
warningHost
? 'bg-status-warning/10 text-status-warning'
: 'bg-status-healthy/10 text-status-healthy'
@@ -365,7 +365,7 @@ function StatsPanel({
</div>
<span
className={cn(
'font-mono text-lg font-bold tabular-nums',
'font-body text-lg font-bold tabular-nums',
stat.color === 'healthy'
? 'text-status-healthy'
: stat.color === 'warning'

View File

@@ -112,7 +112,7 @@ export function LiveHostCard({
return (
<GlassCard className={cn('h-full animate-pulse', className)}>
<div className="h-40 flex items-center justify-center">
<span className="text-nothing-gray-400 font-mono text-sm">
<span className="text-nothing-gray-400 font-body text-sm">
{tCommon('loading')} {ip}
</span>
</div>
@@ -126,7 +126,7 @@ export function LiveHostCard({
<GlassCardHeader>
<div className="flex items-center gap-3">
<StatusOrb status="idle" size="md" />
<span className="inline-flex items-center px-2 py-0.5 rounded-md bg-nothing-gray-100 text-nothing-gray-600 text-xs font-mono">
<span className="inline-flex items-center px-2 py-0.5 rounded-md bg-nothing-gray-100 text-nothing-gray-600 text-xs font-body">
{ip}
</span>
</div>
@@ -137,7 +137,7 @@ export function LiveHostCard({
{fallbackData.services.map((service) => (
<div key={service.name} className="flex items-center gap-2 py-1.5 px-2">
<StatusOrb status="idle" size="sm" />
<span className="font-mono text-sm text-nothing-gray-500">
<span className="font-body text-sm text-nothing-gray-500">
{service.name}
</span>
</div>
@@ -197,7 +197,7 @@ export function LiveHostCard({
{getRoleIcon(host.role || '')}
</div>
<div className="flex flex-col gap-1">
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-nothing-gray-50 border border-nothing-gray-100 text-nothing-gray-700 text-xs font-mono">
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-nothing-gray-50 border border-nothing-gray-100 text-nothing-gray-700 text-xs font-body">
<Wifi className="w-3 h-3" />
{host.ip}
</span>
@@ -210,7 +210,7 @@ export function LiveHostCard({
<div className="mb-4">
<GlassCardTitle className="text-lg">{host.name}</GlassCardTitle>
{host.role && (
<span className="text-[10px] text-nothing-gray-400 font-mono uppercase tracking-widest">
<span className="text-[10px] text-nothing-gray-400 font-body uppercase tracking-widest">
{host.role.replace('_', ' ')}
</span>
)}
@@ -226,20 +226,20 @@ export function LiveHostCard({
>
<div className="flex items-center gap-2">
<StatusOrb status={mapServiceStatus(service.status)} size="sm" />
<span className="font-mono text-sm text-nothing-gray-800">
<span className="font-body text-sm text-nothing-gray-800">
{service.name}
</span>
</div>
<div className="flex items-center gap-2">
{service.port && (
<span className="text-xs text-nothing-gray-400 font-mono">
<span className="text-xs text-nothing-gray-400 font-body">
:{service.port}
</span>
)}
{service.latency_ms !== null && service.latency_ms !== undefined && (
<span
className={cn(
'text-xs font-mono',
'text-xs font-body',
service.latency_ms > 100
? 'text-status-warning'
: 'text-nothing-gray-500'
@@ -287,7 +287,7 @@ export function LiveHostCard({
<GlassCardFooter>
<div className="flex items-center justify-between">
<StatusBadge status={overallStatus} label={statusLabel} size="sm" />
<span className="text-xs text-nothing-gray-400 font-mono">
<span className="text-xs text-nothing-gray-400 font-body">
{new Date(host.last_check).toLocaleTimeString('zh-TW', {
hour: '2-digit',
minute: '2-digit',
@@ -330,8 +330,8 @@ function MetricBar({
return (
<div className="p-3 rounded-xl bg-nothing-gray-50/50 border border-nothing-gray-100">
<div className="flex items-center justify-between mb-2">
<span className="text-[10px] text-nothing-gray-500 uppercase tracking-wider font-mono">{label}</span>
<span className="text-xs font-mono text-nothing-gray-400 animate-pulse">
<span className="text-[10px] text-nothing-gray-500 uppercase tracking-wider font-body">{label}</span>
<span className="text-xs font-body text-nothing-gray-400 animate-pulse">
{collectingLabel}
</span>
</div>
@@ -372,12 +372,12 @@ function MetricBar({
) : (
<Gauge className="w-3 h-3 text-nothing-gray-400" />
)}
<span className="text-[10px] text-nothing-gray-500 uppercase tracking-wider font-mono">{label}</span>
<span className="text-[10px] text-nothing-gray-500 uppercase tracking-wider font-body">{label}</span>
</div>
<div className="flex items-center gap-1.5">
<span
className={cn(
'text-sm font-mono font-bold tabular-nums',
'text-sm font-body font-bold tabular-nums',
value >= criticalThreshold
? 'text-status-critical'
: value >= warningThreshold
@@ -389,7 +389,7 @@ function MetricBar({
</span>
{/* Dynamic Baseline Display */}
{baselineValue !== null && baselineValue !== undefined && (
<span className="text-[9px] font-mono text-nothing-gray-300">
<span className="text-[9px] font-body text-nothing-gray-300">
({formatPercent(baselineValue)})
</span>
)}

View File

@@ -84,7 +84,7 @@ export const ApprovalCard: React.FC<ApprovalCardProps> = ({ data }) => {
<span className="hidden sm:inline">Approval Required // </span>{data.approvalId}
</span>
</div>
<div className={`px-2 py-0.5 sm:py-1 text-xs font-mono font-bold border-2 self-start sm:self-auto shrink-0 ${
<div className={`px-2 py-0.5 sm:py-1 text-xs font-body font-bold border-2 self-start sm:self-auto shrink-0 ${
isCritical ? 'border-red-500 text-red-500' : 'border-blue-500 text-blue-500'
}`}>
{data.riskLevel}
@@ -94,7 +94,7 @@ export const ApprovalCard: React.FC<ApprovalCardProps> = ({ data }) => {
<div className="p-3 sm:p-4 space-y-3 sm:space-y-4">
{/* Metadata */}
{(data.requestedBy || data.requestedAt) && (
<div className="flex flex-wrap gap-2 sm:gap-4 text-xs font-mono text-gray-500">
<div className="flex flex-wrap gap-2 sm:gap-4 text-xs font-body text-gray-500">
{data.requestedBy && (
<div className="flex items-center gap-1">
<User size={12} />
@@ -132,13 +132,13 @@ export const ApprovalCard: React.FC<ApprovalCardProps> = ({ data }) => {
{/* Nuclear Key Authorization */}
<div className="pt-1 sm:pt-2">
{executionResult === 'success' ? (
<div className="w-full py-3 sm:py-4 bg-green-50 text-green-700 border-2 border-green-500 font-mono font-bold text-xs sm:text-sm uppercase flex justify-center items-center gap-2 rounded-sm">
<div className="w-full py-3 sm:py-4 bg-green-50 text-green-700 border-2 border-green-500 font-body font-bold text-xs sm:text-sm uppercase flex justify-center items-center gap-2 rounded-sm">
<span className="text-lg sm:text-xl"></span>
<span className="hidden sm:inline">{t('executionAuthorized')}</span>
<span className="sm:hidden">{t('authorized')}</span>
</div>
) : executionResult === 'error' ? (
<div className="w-full py-3 sm:py-4 bg-red-50 text-red-700 border-2 border-red-500 font-mono font-bold text-xs sm:text-sm uppercase flex justify-center items-center gap-2 rounded-sm">
<div className="w-full py-3 sm:py-4 bg-red-50 text-red-700 border-2 border-red-500 font-body font-bold text-xs sm:text-sm uppercase flex justify-center items-center gap-2 rounded-sm">
<span className="text-lg sm:text-xl"></span>
{t('executionFailed')}
</div>
@@ -158,7 +158,7 @@ export const ApprovalCard: React.FC<ApprovalCardProps> = ({ data }) => {
}
const Label = ({ children }: { children: React.ReactNode }) => (
<span className="text-xs uppercase font-mono font-bold text-gray-500 tracking-wider">
<span className="text-xs uppercase font-body font-bold text-gray-500 tracking-wider">
{children}
</span>
)

View File

@@ -127,7 +127,7 @@ const LoadingCard: React.FC<{ componentName: string }> = ({ componentName }) =>
<div className="w-full max-w-2xl bg-white border-2 border-gray-200 shadow-lg rounded-sm overflow-hidden mt-4 mb-2 animate-pulse">
<div className="p-4 flex items-center gap-2">
<Loader2 className="animate-spin text-gray-400" size={20} />
<span className="font-mono text-sm text-gray-500">
<span className="font-body text-sm text-gray-500">
Loading {componentName}...
</span>
</div>
@@ -139,8 +139,8 @@ const ErrorCard: React.FC<{ title: string; message: string }> = ({ title, messag
<div className="p-4 flex items-start gap-3">
<AlertTriangle className="text-red-500 shrink-0" size={20} />
<div>
<div className="font-mono font-bold text-red-700">{title}</div>
<div className="font-mono text-sm text-red-600 mt-1">{message}</div>
<div className="font-body font-bold text-red-700">{title}</div>
<div className="font-body text-sm text-red-600 mt-1">{message}</div>
</div>
</div>
</div>

View File

@@ -53,11 +53,11 @@ export const IncidentTimelineCard: React.FC<IncidentTimelineCardProps> = ({ data
</div>
<div className="flex items-center gap-2">
{data.severity && (
<span className="px-2 py-1 text-xs font-mono font-bold border-2 border-nothing-black">
<span className="px-2 py-1 text-xs font-body font-bold border-2 border-nothing-black">
{data.severity}
</span>
)}
<span className={`px-2 py-1 text-xs font-mono font-bold border-2 ${statusColors[data.status]} uppercase`}>
<span className={`px-2 py-1 text-xs font-body font-bold border-2 ${statusColors[data.status]} uppercase`}>
{data.status}
</span>
</div>
@@ -67,18 +67,18 @@ export const IncidentTimelineCard: React.FC<IncidentTimelineCardProps> = ({ data
<div className="space-y-3">
{data.events.map((event, i) => (
<div key={i} className="flex gap-3 text-sm">
<div className="text-xs font-mono text-gray-400 w-20 shrink-0">
<div className="text-xs font-body text-gray-400 w-20 shrink-0">
{event.timestamp}
</div>
<div className="flex-1">
<span className={`text-xs font-mono uppercase px-1 ${
<span className={`text-xs font-body uppercase px-1 ${
event.type === 'resolved' ? 'bg-green-100 text-green-700' :
event.type === 'escalated' ? 'bg-red-100 text-red-700' :
'bg-gray-100 text-gray-700'
}`}>
{event.type}
</span>
<span className="ml-2 font-mono text-nothing-black">{event.message}</span>
<span className="ml-2 font-body text-nothing-black">{event.message}</span>
</div>
</div>
))}

View File

@@ -77,7 +77,7 @@ export const K8sPodStatusCard: React.FC<K8sPodStatusCardProps> = ({ data }) => {
K8s Pods // {data.namespace}
</span>
</div>
<div className={`px-2 py-1 text-xs font-mono font-bold border-2 ${isHealthy ? 'border-green-500 text-green-600' : 'border-yellow-500 text-yellow-600'}`}>
<div className={`px-2 py-1 text-xs font-body font-bold border-2 ${isHealthy ? 'border-green-500 text-green-600' : 'border-yellow-500 text-yellow-600'}`}>
{summary.running}/{summary.total} RUNNING
</div>
</div>
@@ -87,7 +87,7 @@ export const K8sPodStatusCard: React.FC<K8sPodStatusCardProps> = ({ data }) => {
{data.pods.map((pod, i) => (
<div
key={i}
className="flex items-center gap-3 p-2 bg-gray-50 rounded-sm text-sm font-mono"
className="flex items-center gap-3 p-2 bg-gray-50 rounded-sm text-sm font-body"
>
{statusIcon(pod.status)}
<span className="flex-1 truncate text-nothing-black">{pod.name}</span>

View File

@@ -55,7 +55,7 @@ export const MetricsSummaryCard: React.FC<MetricsSummaryCardProps> = ({ data })
SignOz Metrics // Real-time
</span>
</div>
<div className={`px-2 py-1 text-xs font-mono font-bold border-2 ${statusTextColors[status]} border-current uppercase`}>
<div className={`px-2 py-1 text-xs font-body font-bold border-2 ${statusTextColors[status]} border-current uppercase`}>
{status}
</div>
</div>
@@ -93,26 +93,26 @@ export const MetricsSummaryCard: React.FC<MetricsSummaryCardProps> = ({ data })
<div className="p-4 pt-0 grid grid-cols-2 gap-4 bg-white/80">
{data.cpu !== undefined && (
<div className="flex items-center gap-2">
<span className="text-xs font-mono text-gray-500">CPU:</span>
<span className="text-xs font-body text-gray-500">CPU:</span>
<div className="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full ${data.cpu > 80 ? 'bg-red-500' : data.cpu > 60 ? 'bg-yellow-500' : 'bg-green-500'}`}
style={{ width: `${data.cpu}%` }}
/>
</div>
<span className="text-xs font-mono font-bold">{data.cpu}%</span>
<span className="text-xs font-body font-bold">{data.cpu}%</span>
</div>
)}
{data.memory !== undefined && (
<div className="flex items-center gap-2">
<span className="text-xs font-mono text-gray-500">MEM:</span>
<span className="text-xs font-body text-gray-500">MEM:</span>
<div className="flex-1 h-2 bg-gray-200 rounded-full overflow-hidden">
<div
className={`h-full ${data.memory > 80 ? 'bg-red-500' : data.memory > 60 ? 'bg-yellow-500' : 'bg-green-500'}`}
style={{ width: `${data.memory}%` }}
/>
</div>
<span className="text-xs font-mono font-bold">{data.memory}%</span>
<span className="text-xs font-body font-bold">{data.memory}%</span>
</div>
)}
</div>
@@ -121,7 +121,7 @@ export const MetricsSummaryCard: React.FC<MetricsSummaryCardProps> = ({ data })
{/* Pods status */}
{data.pods && (
<div className="px-4 pb-4 bg-white/80">
<div className="text-xs font-mono text-gray-500">
<div className="text-xs font-body text-gray-500">
Pods: <span className="font-bold text-nothing-black">{data.pods.running}/{data.pods.total}</span> running
</div>
</div>
@@ -141,7 +141,7 @@ const MetricBox: React.FC<{
<div className={`text-center p-3 rounded-sm border ${highlight ? 'border-red-300 bg-red-50' : 'border-gray-200 bg-gray-50'}`}>
<div className="flex items-center justify-center gap-1 text-gray-500 mb-1">
{icon}
<span className="text-xs font-mono uppercase">{label}</span>
<span className="text-xs font-body uppercase">{label}</span>
</div>
<div className={`font-['VT323'] text-2xl ${highlight ? 'text-red-600' : 'text-nothing-black'}`}>
{value}

View File

@@ -167,7 +167,7 @@ export const NuclearKeyButton: React.FC<NuclearKeyButtonProps> = ({
w-full py-4 px-6 rounded-sm border-2
bg-green-50 border-green-500
flex items-center justify-center gap-2
font-mono font-bold text-green-700
font-body font-bold text-green-700
transition-all duration-300
`}>
<CheckCircle size={20} />
@@ -218,7 +218,7 @@ export const NuclearKeyButton: React.FC<NuclearKeyButtonProps> = ({
className={`${config.textColor} ${isHolding ? 'animate-pulse' : ''} sm:w-5 sm:h-5`}
/>
<span className={`font-mono font-bold uppercase tracking-wider text-sm sm:text-base ${config.textColor}`}>
<span className={`font-body font-bold uppercase tracking-wider text-sm sm:text-base ${config.textColor}`}>
{isHolding ? (
<span className="tabular-nums">
{label} ({Math.ceil((1 - progress) * (duration ?? 1500) / 1000)}s)
@@ -230,7 +230,7 @@ export const NuclearKeyButton: React.FC<NuclearKeyButtonProps> = ({
{/* Risk Badge */}
<span className={`
px-1.5 sm:px-2 py-0.5 text-[10px] sm:text-xs font-mono font-bold
px-1.5 sm:px-2 py-0.5 text-[10px] sm:text-xs font-body font-bold
border ${config.borderColor} ${config.textColor}
rounded-sm
`}>
@@ -241,7 +241,7 @@ export const NuclearKeyButton: React.FC<NuclearKeyButtonProps> = ({
{/* Shortcut Hint - Hidden on mobile touch devices */}
{showShortcut && !disabled && (
<div className="text-center text-xs font-mono text-gray-400">
<div className="text-center text-xs font-body text-gray-400">
{isHolding ? (
<span className="text-gray-600">
{t('keepHolding')}
@@ -271,7 +271,7 @@ export const NuclearKeyButton: React.FC<NuclearKeyButtonProps> = ({
{/* Critical Warning */}
{isCritical && !disabled && !isHolding && (
<div className={`
text-center text-xs font-mono ${config.textColor}
text-center text-xs font-body ${config.textColor}
animate-pulse
`}>
<AlertTriangle size={12} className="inline mr-1" />

View File

@@ -41,14 +41,14 @@ export const SentryErrorCard: React.FC<SentryErrorCardProps> = ({ data }) => {
Sentry // {data.errorId}
</span>
</div>
<div className={`px-2 py-1 text-xs font-mono font-bold border-2 ${levelColors[level]} uppercase`}>
<div className={`px-2 py-1 text-xs font-body font-bold border-2 ${levelColors[level]} uppercase`}>
{level}
</div>
</div>
<div className="p-4 space-y-3">
<div>
<span className="text-xs uppercase font-mono font-bold text-gray-500 tracking-wider">
<span className="text-xs uppercase font-body font-bold text-gray-500 tracking-wider">
Error
</span>
<div className="mt-1 font-['VT323'] text-xl text-nothing-black">
@@ -56,7 +56,7 @@ export const SentryErrorCard: React.FC<SentryErrorCardProps> = ({ data }) => {
</div>
</div>
<div className="flex gap-4 text-sm font-mono">
<div className="flex gap-4 text-sm font-body">
{data.count !== undefined && (
<div>
<span className="text-gray-500">Count:</span>{' '}
@@ -76,7 +76,7 @@ export const SentryErrorCard: React.FC<SentryErrorCardProps> = ({ data }) => {
href={data.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-sm font-mono text-[#4A90D9] hover:underline"
className="inline-flex items-center gap-1 text-sm font-body text-[#4A90D9] hover:underline"
>
View in Sentry <ExternalLink size={14} />
</a>

View File

@@ -49,7 +49,7 @@ export const TraceWaterfallCard: React.FC<TraceWaterfallCardProps> = ({ data })
Trace // {data.traceId.slice(0, 16)}...
</span>
</div>
<div className="px-2 py-1 text-xs font-mono font-bold border-2 border-[#4A90D9] text-[#4A90D9]">
<div className="px-2 py-1 text-xs font-body font-bold border-2 border-[#4A90D9] text-[#4A90D9]">
{data.duration}ms
</div>
</div>
@@ -58,7 +58,7 @@ export const TraceWaterfallCard: React.FC<TraceWaterfallCardProps> = ({ data })
{data.spans.map((span, i) => (
<div key={i} className="flex items-center gap-2 text-sm">
{/* Service name */}
<div className="w-24 shrink-0 truncate text-xs font-mono text-gray-500">
<div className="w-24 shrink-0 truncate text-xs font-body text-gray-500">
{span.service}
</div>
@@ -74,7 +74,7 @@ export const TraceWaterfallCard: React.FC<TraceWaterfallCardProps> = ({ data })
}}
/>
<span
className="absolute text-xs font-mono text-white px-1 truncate"
className="absolute text-xs font-body text-white px-1 truncate"
style={{
left: `${getBarOffset(span.startOffset)}%`,
maxWidth: `${getBarWidth(span.duration)}%`,
@@ -86,7 +86,7 @@ export const TraceWaterfallCard: React.FC<TraceWaterfallCardProps> = ({ data })
</div>
{/* Duration */}
<div className="w-16 shrink-0 text-right text-xs font-mono text-gray-600">
<div className="w-16 shrink-0 text-right text-xs font-body text-gray-600">
{span.duration}ms
</div>
</div>
@@ -99,7 +99,7 @@ export const TraceWaterfallCard: React.FC<TraceWaterfallCardProps> = ({ data })
href={data.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-sm font-mono text-[#4A90D9] hover:underline"
className="inline-flex items-center gap-1 text-sm font-body text-[#4A90D9] hover:underline"
>
View in SignOz <ExternalLink size={14} />
</a>

View File

@@ -8,7 +8,7 @@
*
* Nothing.tech 視覺規範:
* - 深色背景 (bg-black text-white) - 全站唯一深色區域
* - 強制等寬字體 (font-mono)
* - 強制等寬字體 (font-body)
* - 打字機效果展示推論步驟
*
* 統帥鐵律: 禁止假數據!
@@ -168,7 +168,7 @@ export function ThinkingTerminal({
{/* Title */}
<div className="flex items-center gap-2">
<Terminal className="w-4 h-4 text-gray-400" />
<span className="font-mono text-xs text-gray-400">
<span className="font-body text-xs text-gray-400">
OpenClaw Terminal
</span>
</div>
@@ -177,7 +177,7 @@ export function ThinkingTerminal({
{/* Controls */}
<div className="flex items-center gap-2">
{incidentId && (
<span className="font-mono text-xs text-gray-600">
<span className="font-body text-xs text-gray-600">
{incidentId}
</span>
)}
@@ -214,7 +214,7 @@ export function ThinkingTerminal({
{isExpanded && (
<div
ref={containerRef}
className="p-4 overflow-y-auto font-mono text-sm"
className="p-4 overflow-y-auto font-body text-sm"
style={{ maxHeight, minHeight: '150px' }}
>
{/* No Data State */}
@@ -291,10 +291,10 @@ export function ThinkingTerminal({
{/* Terminal Footer */}
<div className="flex items-center justify-between px-4 py-2 border-t border-gray-800 bg-gray-900/50">
<span className="font-mono text-xs text-gray-600">
<span className="font-body text-xs text-gray-600">
{t('steps')}: {displayedSteps}/{totalSteps}
</span>
<span className="font-mono text-xs text-gray-600">
<span className="font-body text-xs text-gray-600">
{isPlaying ? t('streaming') : t('paused')}
</span>
</div>

View File

@@ -30,11 +30,11 @@ export function ComingSoon({ icon: Icon, title, description, className }: Coming
<Icon className="w-8 h-8 text-nothing-gray-400" />
</div>
)}
<h2 className="font-mono text-lg font-bold text-nothing-gray-700 tracking-tight mb-2">
<h2 className="font-body text-lg font-bold text-nothing-gray-700 tracking-tight mb-2">
{title}
</h2>
{description && (
<p className="font-mono text-sm text-nothing-gray-400 max-w-xs">
<p className="font-body text-sm text-nothing-gray-400 max-w-xs">
{description}
</p>
)}

View File

@@ -95,7 +95,7 @@ export function Header({
{pendingApprovals > 0 && (
<div className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-status-critical/10 border border-status-critical/30">
<StatusOrb status="critical" size="sm" pulse />
<span className="text-sm font-mono text-status-critical">
<span className="text-sm font-body text-status-critical">
{pendingApprovals} {tDashboard('pendingApprovals')}
</span>
</div>
@@ -105,13 +105,13 @@ export function Header({
<div className="flex items-center gap-2">
<StatusOrb status={connectionOrbStatus} size="md" glow />
<span className={cn(
'font-mono text-sm',
'font-body text-sm',
connectionStatus === 'connected' ? 'text-status-healthy' : 'text-nothing-gray-500'
)}>
{connectionLabel}
</span>
{mockMode && (
<span className="px-2 py-0.5 text-xs font-mono rounded bg-status-warning/10 text-status-warning border border-status-warning/30">
<span className="px-2 py-0.5 text-xs font-body rounded bg-status-warning/10 text-status-warning border border-status-warning/30">
MOCK
</span>
)}
@@ -122,7 +122,7 @@ export function Header({
<button
onClick={() => switchLocale('zh-TW')}
className={cn(
'px-3 py-1.5 rounded-md font-mono text-sm transition-all',
'px-3 py-1.5 rounded-md font-body text-sm transition-all',
locale === 'zh-TW'
? 'bg-nothing-black text-white'
: 'text-nothing-gray-600 hover:text-nothing-black'
@@ -133,7 +133,7 @@ export function Header({
<button
onClick={() => switchLocale('en')}
className={cn(
'px-3 py-1.5 rounded-md font-mono text-sm transition-all',
'px-3 py-1.5 rounded-md font-body text-sm transition-all',
locale === 'en'
? 'bg-nothing-black text-white'
: 'text-nothing-gray-600 hover:text-nothing-black'

View File

@@ -323,7 +323,7 @@ export function Sidebar({
collapsed ? 'px-2 text-center' : 'px-4'
)}>
{!collapsed && (
<span className="text-[10px] font-mono text-neutral-300 uppercase tracking-widest">
<span className="text-[10px] font-body text-muted uppercase tracking-widest">
v1.0.0
</span>
)}

View File

@@ -90,12 +90,12 @@ class AutoHealingErrorBoundaryInner extends Component<InnerProps, State> {
if (this.state.retryCount >= 3) {
return (
<div className="flex flex-col items-center justify-center p-8 bg-[#111111] text-white border border-red-500/30 rounded-lg max-w-lg mx-auto mt-12">
<h2 className="text-xl font-mono text-red-500 mb-4">{translations.systemFailure}</h2>
<p className="font-mono text-sm text-gray-400 mb-4">{this.props.fallbackMessage || translations.criticalError}</p>
<p className="font-mono text-xs text-purple-400 mb-6">{translations.escalating}</p>
<h2 className="text-xl font-body text-red-500 mb-4">{translations.systemFailure}</h2>
<p className="font-body text-sm text-gray-400 mb-4">{this.props.fallbackMessage || translations.criticalError}</p>
<p className="font-body text-xs text-purple-400 mb-6">{translations.escalating}</p>
<button
onClick={() => window.location.reload()}
className="px-6 py-2 bg-white text-black font-mono text-sm rounded-full hover:bg-gray-200 transition-colors"
className="px-6 py-2 bg-white text-black font-body text-sm rounded-full hover:bg-gray-200 transition-colors"
>
{translations.forceRestart}
</button>
@@ -105,8 +105,8 @@ class AutoHealingErrorBoundaryInner extends Component<InnerProps, State> {
return (
<div className="flex flex-col items-center justify-center p-12 space-y-4 animate-pulse">
<div className="text-red-500 font-mono text-sm font-bold tracking-widest">{translations.detectingAnomaly}</div>
<div className="text-gray-400 font-mono text-xs">{translations.autoHealingAttempt(this.state.retryCount + 1)}</div>
<div className="text-red-500 font-body text-sm font-bold tracking-widest">{translations.detectingAnomaly}</div>
<div className="text-gray-400 font-body text-xs">{translations.autoHealingAttempt(this.state.retryCount + 1)}</div>
<div className="w-48 h-1 bg-gray-800 rounded overflow-hidden mt-4">
<div className="h-full bg-white opacity-50 animate-ping"></div>
</div>

View File

@@ -41,7 +41,7 @@ export function LanguageSwitcher({ className }: LanguageSwitcherProps) {
key={loc}
onClick={() => handleSwitch(loc)}
className={cn(
'px-3 py-1.5 rounded-sm font-mono text-xs transition-all duration-200',
'px-3 py-1.5 rounded-sm font-body text-xs transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-nothing-gray-400',
isActive
? 'bg-nothing-gray-900 text-nothing-white'
@@ -81,7 +81,7 @@ export function LanguageSwitcherCompact({ className }: LanguageSwitcherProps) {
className={cn(
'px-3 py-1.5 rounded-button',
'bg-nothing-gray-100 border border-nothing-gray-200',
'font-mono text-xs text-nothing-gray-600',
'font-body text-xs text-nothing-gray-600',
'hover:text-nothing-gray-900 hover:bg-nothing-gray-200',
'transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-nothing-gray-400',

View File

@@ -83,7 +83,7 @@ export function StatusOrb({ status, size = 'md', className }: StatusOrbProps) {
{/* Label */}
<div className="absolute -bottom-6 left-1/2 -translate-x-1/2 whitespace-nowrap">
<span className="font-mono text-xs text-nothing-gray-500">
<span className="font-body text-xs text-nothing-gray-500">
{label}
</span>
</div>

View File

@@ -141,7 +141,7 @@ export const OmniTerminal = () => {
const result = msg.payload.result as Record<string, unknown> | undefined
return (
<div className={`font-mono text-sm ${status === 'completed' ? 'text-green-600' : status === 'failed' ? 'text-red-600' : 'text-yellow-600'}`}>
<div className={`font-body text-sm ${status === 'completed' ? 'text-green-600' : status === 'failed' ? 'text-red-600' : 'text-yellow-600'}`}>
<span className="opacity-70">[{tool ?? 'unknown'}]</span>{' '}
<span className="font-bold">{status ?? 'executing'}</span>
{result && (
@@ -205,12 +205,12 @@ export const OmniTerminal = () => {
</span>
<div className="flex items-center gap-1 opacity-60">
<span className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-gray-400'}`}></span>
<span className="text-xs uppercase font-mono hidden sm:inline">{isConnected ? t('sseLive') : t('offline')}</span>
<span className="text-xs uppercase font-body hidden sm:inline">{isConnected ? t('sseLive') : t('offline')}</span>
</div>
</div>
<button
onClick={toggleTerminal}
className="text-gray-500 hover:text-black font-mono px-2 py-1"
className="text-gray-500 hover:text-black font-body px-2 py-1"
aria-label="Close terminal"
>
[x]

View File

@@ -128,7 +128,7 @@ export function ThinkingStreamTest() {
{/* Header - Nothing.tech 風格 */}
<div className="flex items-center justify-between border-b border-nothing-gray-800 pb-4">
<h2 className="font-display text-display-sm">AWOOOI Terminal</h2>
<span className="font-mono text-xs text-nothing-gray-500">
<span className="font-body text-xs text-nothing-gray-500">
v0.1.0 | SSE
</span>
</div>
@@ -138,7 +138,7 @@ export function ThinkingStreamTest() {
onClick={startStream}
disabled={isStreaming}
className={cn(
'w-full py-3 px-4 rounded-button font-mono text-sm transition-all border',
'w-full py-3 px-4 rounded-button font-body text-sm transition-all border',
isStreaming
? 'bg-nothing-gray-800 text-nothing-gray-500 border-nothing-gray-700 cursor-not-allowed'
: 'bg-nothing-white text-nothing-black border-transparent hover:bg-nothing-gray-200'
@@ -150,7 +150,7 @@ export function ThinkingStreamTest() {
{/* Terminal Output */}
<div className="bg-nothing-gray-900 rounded-card p-4 min-h-[200px] max-h-[300px] overflow-y-auto">
{thinkingStream.length === 0 && !isStreaming && (
<div className="font-mono text-sm text-nothing-gray-600">
<div className="font-body text-sm text-nothing-gray-600">
{'>'} Waiting for command...
</div>
)}
@@ -159,7 +159,7 @@ export function ThinkingStreamTest() {
<div
key={index}
className={cn(
'font-mono text-sm py-0.5 animate-fade-in',
'font-body text-sm py-0.5 animate-fade-in',
step.type === 'result' && 'text-status-healthy',
step.type === 'thinking' && 'text-nothing-gray-500',
step.type === 'error' && 'text-status-critical'
@@ -172,7 +172,7 @@ export function ThinkingStreamTest() {
{/* Cursor 動畫 */}
{isStreaming && (
<div className="flex items-center gap-1 mt-1">
<span className="font-mono text-sm text-nothing-gray-500">{'>'}</span>
<span className="font-body text-sm text-nothing-gray-500">{'>'}</span>
<span className="w-2 h-4 bg-nothing-white animate-pulse" />
</div>
)}
@@ -180,7 +180,7 @@ export function ThinkingStreamTest() {
{/* Connection Info */}
<div className="pt-2 border-t border-nothing-gray-800">
<p className="font-mono text-xs text-nothing-gray-600">
<p className="font-body text-xs text-nothing-gray-600">
ENDPOINT: {API_BASE_URL}/agent/thinking
</p>
</div>

View File

@@ -135,7 +135,7 @@ function TimelineItem({ event, isLast, isNewest = false, isPulsingExec = false }
</div>
{/* Title */}
<p className="font-mono text-[11px] text-nothing-gray-700 leading-tight">
<p className="font-body text-[11px] text-nothing-gray-700 leading-tight">
{event.title}
</p>
@@ -143,7 +143,7 @@ function TimelineItem({ event, isLast, isNewest = false, isPulsingExec = false }
{event.status === 'error' && event.description && (
<div className="mt-1.5 flex items-start gap-1.5 p-2 rounded bg-nothing-red/5 border border-nothing-red/20">
<span className="font-dot-matrix text-nothing-red text-[14px] leading-none animate-pulse">!</span>
<pre className="font-mono text-[9px] text-nothing-red/90 leading-relaxed whitespace-pre-wrap break-all">
<pre className="font-body text-[9px] text-nothing-red/90 leading-relaxed whitespace-pre-wrap break-all">
{event.description}
</pre>
</div>
@@ -153,12 +153,12 @@ function TimelineItem({ event, isLast, isNewest = false, isPulsingExec = false }
{event.actor && (
<div className="flex items-center gap-1.5 mt-0.5">
<Icon className={cn('w-2.5 h-2.5', config.labelColor)} />
<span className="font-mono text-[9px] text-nothing-gray-500">
<span className="font-body text-[9px] text-nothing-gray-500">
{event.actor}
</span>
{event.actorRole && (
<span className={cn(
'px-1 py-0.5 rounded text-[7px] font-mono font-bold uppercase',
'px-1 py-0.5 rounded text-[7px] font-body font-bold uppercase',
event.actorRole === 'cto' || event.actorRole === 'ciso'
? 'bg-status-healthy/10 text-status-healthy'
: event.actorRole === 'ai'
@@ -173,7 +173,7 @@ function TimelineItem({ event, isLast, isNewest = false, isPulsingExec = false }
{/* Risk Badge */}
{event.riskLevel === 'critical' && (
<span className="inline-block mt-1 px-1 py-0.5 rounded text-[7px] font-mono font-bold uppercase bg-status-critical/10 text-status-critical">
<span className="inline-block mt-1 px-1 py-0.5 rounded text-[7px] font-body font-bold uppercase bg-status-critical/10 text-status-critical">
CRITICAL
</span>
)}

View File

@@ -104,7 +104,7 @@ export function ApprovalModal({
<h2 className="font-heading text-lg font-bold text-nothing-gray-900">
{displayTitle}
</h2>
<p className="text-xs text-nothing-gray-500 font-mono">
<p className="text-xs text-nothing-gray-500 font-body">
Multi-Sig Authorization Required
</p>
</div>
@@ -127,7 +127,7 @@ export function ApprovalModal({
>
<ChevronLeft className="w-4 h-4" />
</button>
<span className="text-sm font-mono text-nothing-gray-600 tabular-nums min-w-[3rem] text-center">
<span className="text-sm font-body text-nothing-gray-600 tabular-nums min-w-[3rem] text-center">
{current} / {total}
</span>
<button

View File

@@ -112,7 +112,7 @@ export function SlidePanel({
{/* 導航控制 */}
{(onPrev || onNext) && current && total && (
<div className="flex items-center gap-2">
<span className="text-xs font-mono text-nothing-gray-400">
<span className="text-xs font-body text-nothing-gray-400">
{current} / {total}
</span>
<div className="flex items-center gap-1">

View File

@@ -242,7 +242,7 @@ export const StatusBadge = forwardRef<HTMLSpanElement, StatusBadgeProps>(
)}
>
<StatusOrb status={status} size={size} {...props} />
<span className="text-xs font-mono text-nothing-gray-600">
<span className="text-xs font-body text-nothing-gray-600">
{displayLabel}
</span>
</span>

View File

@@ -155,7 +155,7 @@ function ToastItem({ toast, onClose }: { toast: ToastItem; onClose: () => void }
toast.type === 'loading' && 'animate-spin'
)}
/>
<p className={cn('font-mono text-sm', config.text)}>{toast.message}</p>
<p className={cn('font-body text-sm', config.text)}>{toast.message}</p>
</div>
)
}