style(web): unify design system across default frontend

🔤 Typography
- Fix --font-sans stack: 'Public Sans Variable' was never applied (wrong family name), add CJK fallbacks (PingFang SC / Microsoft YaHei UI / Noto Sans SC)
- Sync <html lang> with i18n language for correct CJK glyph selection
- Remove dead fake font system (font-provider.tsx, config/fonts.ts, --font-inter/--font-manrope)
- Typography contract: 3-tier headings, arbitrary text-[10px]/[11px] → text-xs (46 files), no uppercase table headers, font-bold → font-semibold
- Numbers use tabular-nums only; font-mono reserved for keys/IDs/code (29 files)

🎨 Color discipline
- Retire StatusBadge string-hash rainbow (autoColor → neutral); all variants resolve to 5 semantic voices
- Add tintedBadgeClassMap as the single tinted-pill recipe
- Replace hardcoded emerald/amber/rose/sky classes with semantic tokens across ~30 files (timing badges, progress bars, system panels, pricing, rankings, log row tints)
- Drop all !important text overrides and [font-family:var(--font-body)] hacks

🧘 Motion restraint
- Remove admin entrance animations: table row stagger, card hover lift/shadow, button press scale, dashboard shimmer line
- Neuter page-transition stagger components into plain wrappers; page transitions reduced to pure opacity fade
- Decorative motion stays landing-only

📐 Shape
- Single radius source: --radius 1rem → 0.625rem
- Sparklines use solid chart tokens instead of decorative gradients
- PanelWrapper: border-only (no shadow), align radius with Card

📏 Guardrails
- Add "Design System Discipline" section (3.10.1) to web/default/AGENTS.md

 Verified: tsgo typecheck clean, rsbuild production build passes
This commit is contained in:
t0ng7u
2026-07-11 02:02:11 +08:00
parent 0cb741d8da
commit 262ab93123
97 changed files with 561 additions and 863 deletions
@@ -77,9 +77,9 @@ const CHANNEL_FIELD_LABELS: Record<string, string> = {
function timingTextColorClass(
variant: 'success' | 'warning' | 'danger'
): string {
if (variant === 'success') return 'text-emerald-600'
if (variant === 'warning') return 'text-amber-600'
return 'text-rose-600'
if (variant === 'success') return 'text-success'
if (variant === 'warning') return 'text-warning'
return 'text-destructive'
}
function DetailRow(props: {
@@ -628,7 +628,10 @@ export function DetailsDialog(props: DetailsDialogProps) {
label={t('IP Address')}
value={
<span className='flex items-center gap-1'>
<Globe className='size-3 text-amber-500' aria-hidden='true' />
<Globe
className='text-muted-foreground size-3'
aria-hidden='true'
/>
{props.log.ip}
</span>
}
@@ -814,7 +817,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
/>
))}
{showLegacyTopupWarning && (
<div className='flex items-start gap-1.5 text-xs text-amber-600 dark:text-amber-400'>
<div className='text-warning flex items-start gap-1.5 text-xs'>
<Info className='mt-0.5 size-3.5 shrink-0' aria-hidden='true' />
<span>
{t(
@@ -1030,9 +1033,9 @@ export function DetailsDialog(props: DetailsDialogProps) {
value={
<span className='flex items-center gap-1'>
{other.admin_info.local_count_tokens ? (
<Monitor className='size-3 text-blue-500' />
<Monitor className='text-muted-foreground size-3' />
) : (
<Cloud className='size-3 text-emerald-500' />
<Cloud className='text-muted-foreground size-3' />
)}
<span className='text-xs'>
{other.admin_info.local_count_tokens
@@ -1080,7 +1083,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
)}
{Array.isArray(other.stream_status.errors) &&
other.stream_status.errors.length > 0 && (
<pre className='bg-background/60 mt-1 max-h-32 overflow-y-auto rounded border p-2 font-mono text-[11px] leading-relaxed wrap-break-word whitespace-pre-wrap'>
<pre className='bg-background/60 mt-1 max-h-32 overflow-y-auto rounded border p-2 font-mono text-xs leading-relaxed wrap-break-word whitespace-pre-wrap'>
{other.stream_status.errors.join('\n')}
</pre>
)}
@@ -1155,7 +1158,7 @@ export function DetailsDialog(props: DetailsDialogProps) {
className='shrink-0 font-medium'
copyable={false}
/>
<span className='min-w-0 font-mono text-[11px] leading-relaxed break-all sm:wrap-break-word'>
<span className='min-w-0 font-mono text-xs leading-relaxed break-all sm:wrap-break-word'>
{parsed.content}
</span>
</div>