style(webui): improve typography with Apple-inspired font stack and CJK support

- Add explicit CJK fonts (PingFang SC, Noto Sans SC, Microsoft YaHei) and
  programmer fonts (JetBrains Mono, Fira Code, Cascadia Code) to Tailwind config
- Bump prose base size from prose-sm (14px) to prose-lg (18px) for sharper CJK rendering
- Unify user/assistant message font size at 18px with CJK-aware line-height (1.8)
- Replace pure black/white foreground with Apple-style warm grays (#1d1d1f / #f5f5f7)
- Override Tailwind Typography colors to use design tokens for consistency
- Add negative letter-spacing on headings for tighter, more polished look
This commit is contained in:
chengyongru 2026-04-20 00:03:38 +08:00
parent 7527961b19
commit a7caee1186
5 changed files with 72 additions and 21 deletions

View File

@ -26,8 +26,10 @@
background: #ffffff; background: #ffffff;
color: #0a0a0a; color: #0a0a0a;
font-family: font-family:
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; "Helvetica Neue", Arial, "Noto Sans", "Noto Sans SC",
"PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
sans-serif;
} }
html.dark body { html.dark body {

View File

@ -24,10 +24,10 @@ export default function MarkdownTextRenderer({
return ( return (
<div <div
className={cn( className={cn(
"markdown-content prose prose-sm max-w-none dark:prose-invert", "markdown-content prose prose-lg max-w-none dark:prose-invert",
"prose-headings:mt-4 prose-headings:mb-2 prose-headings:font-semibold", "prose-headings:mt-4 prose-headings:mb-2 prose-headings:font-semibold prose-headings:tracking-tight",
"prose-h1:text-lg prose-h2:text-base prose-h3:text-[0.95rem] prose-h4:text-sm", "prose-h1:text-xl prose-h2:text-lg prose-h3:text-base prose-h4:text-sm",
"prose-p:my-2 prose-p:leading-relaxed", "prose-p:my-2",
"prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5", "prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5",
"prose-blockquote:my-3 prose-blockquote:border-l-2 prose-blockquote:font-normal", "prose-blockquote:my-3 prose-blockquote:border-l-2 prose-blockquote:font-normal",
"prose-blockquote:not-italic prose-blockquote:text-foreground/80", "prose-blockquote:not-italic prose-blockquote:text-foreground/80",
@ -38,6 +38,7 @@ export default function MarkdownTextRenderer({
"prose-table:my-3 prose-th:text-left prose-th:font-medium", "prose-table:my-3 prose-th:text-left prose-th:font-medium",
className, className,
)} )}
style={{ lineHeight: "var(--cjk-line-height)" }}
> >
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]} remarkPlugins={[remarkGfm, remarkMath]}

View File

@ -37,7 +37,7 @@ export function MessageBubble({ message }: MessageBubbleProps) {
<p <p
className={cn( className={cn(
"ml-auto w-fit rounded-[18px] border border-border/60 bg-secondary/70 px-4 py-2", "ml-auto w-fit rounded-[18px] border border-border/60 bg-secondary/70 px-4 py-2",
"text-right text-sm whitespace-pre-wrap break-words", "text-right text-[18px]/[1.8] whitespace-pre-wrap break-words",
"shadow-[0_10px_24px_-18px_rgba(0,0,0,0.55)]", "shadow-[0_10px_24px_-18px_rgba(0,0,0,0.55)]",
)} )}
> >
@ -49,7 +49,7 @@ export function MessageBubble({ message }: MessageBubbleProps) {
const empty = message.content.trim().length === 0; const empty = message.content.trim().length === 0;
return ( return (
<div className={cn("w-full text-sm leading-relaxed", baseAnim)}> <div className={cn("w-full text-sm", baseAnim)} style={{ lineHeight: "var(--cjk-line-height)" }}>
{empty && message.isStreaming ? ( {empty && message.isStreaming ? (
<TypingDots /> <TypingDots />
) : ( ) : (

View File

@ -6,12 +6,12 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 0 0% 3.9%; --foreground: 240 3% 12%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 0 0% 3.9%; --card-foreground: 240 3% 12%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%; --popover-foreground: 240 3% 12%;
--primary: 0 0% 9%; --primary: 240 4% 16%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%; --secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%; --secondary-foreground: 0 0% 9%;
@ -34,12 +34,12 @@
.dark { .dark {
--background: 0 0% 10%; --background: 0 0% 10%;
--foreground: 0 0% 98%; --foreground: 240 4% 96%;
--card: 0 0% 12%; --card: 0 0% 12%;
--card-foreground: 0 0% 98%; --card-foreground: 240 4% 96%;
--popover: 0 0% 12%; --popover: 0 0% 12%;
--popover-foreground: 0 0% 98%; --popover-foreground: 240 4% 96%;
--primary: 0 0% 98%; --primary: 240 5% 98%;
--primary-foreground: 0 0% 9%; --primary-foreground: 0 0% 9%;
--secondary: 0 0% 12%; --secondary: 0 0% 12%;
--secondary-foreground: 0 0% 98%; --secondary-foreground: 0 0% 98%;
@ -72,11 +72,7 @@
} }
body { body {
@apply bg-background text-foreground antialiased; @apply bg-background text-foreground font-sans antialiased;
font-family:
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji";
} }
::selection { ::selection {
@ -97,6 +93,30 @@
@apply mb-0; @apply mb-0;
} }
/* Override Tailwind Typography's built-in colors with our design tokens
so assistant messages use the same --foreground as user messages. */
.markdown-content {
--tw-prose-body: hsl(var(--foreground));
--tw-prose-headings: hsl(var(--foreground));
--tw-prose-bold: hsl(var(--foreground));
--tw-prose-lead: hsl(var(--foreground));
}
/* CJK-friendly line-height: prose paragraphs default to 1.625 which is
tight for Chinese/Japanese/Korean characters. Bump to 1.8 for better
readability when the browser detects a CJK primary font. */
:lang(zh),
:lang(zh-CN),
:lang(zh-TW),
:lang(zh-HK),
:lang(ja),
:lang(ko) {
--cjk-line-height: 1.8;
}
:root {
--cjk-line-height: 1.625;
}
/* Subtle scrollbar that doesn't fight the dark background. */ /* Subtle scrollbar that doesn't fight the dark background. */
.scrollbar-thin { .scrollbar-thin {
scrollbar-width: thin; scrollbar-width: thin;

View File

@ -14,6 +14,34 @@ export default {
}, },
}, },
extend: { extend: {
fontFamily: {
sans: [
"system-ui",
"-apple-system",
"BlinkMacSystemFont",
'"Segoe UI"',
"Roboto",
'"Helvetica Neue"',
"Arial",
'"Noto Sans"',
'"Noto Sans SC"',
'"PingFang SC"',
'"Hiragino Sans GB"',
'"Microsoft YaHei"',
"sans-serif",
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
],
mono: [
'"JetBrains Mono"',
'"Fira Code"',
'"Cascadia Code"',
'"Source Code Pro"',
"Menlo",
"Consolas",
"monospace",
],
},
borderRadius: { borderRadius: {
lg: "var(--radius)", lg: "var(--radius)",
md: "calc(var(--radius) - 2px)", md: "calc(var(--radius) - 2px)",