fix(theme): 品牌色令牌语义拆分与暗黑模式全站治理

- 拆分品牌色「文字/底色」双通道:新增 --color-brand-ink(-rgb)(深色 #F87171)
  与 tailwind brand.ink;text-brand → text-brand-ink 迁移 247 处;
  bg-brand/border-brand 保持 --color-brand-rgb 不翻转(红底白字 5.58:1 不能动)
- dark 块补齐 --color-brand-bg: #2A1418(修复深色白字压浅粉底 1.04:1)
- hero 徽章新增 --badge-accent-text 桥接:accentColor 直用作深底文字不可读
  (#1e3a5f 深底 1.65:1),浅色取原色 / 深色 color-mix 提亮
- 硬编码颜色 token 化:bg-white/text-gray-*/border-gray-* → bg-bg-*/text-text-*/border-border-*
- 可访问性:触控目标 <24px 归零(footer 链接列 [&_a]:min-h-6 收口 377 处等)、
  news 搜索框补 aria-label、标题跳级 h2→h4 修正为 h3、装饰 blur 光斑容器补
  overflow-hidden 消除伪文本裁剪
- 验证:tsc 0 / eslint 0 error / jest 0 失败用例 / dogfood 33 路由 × 双主题 P1-P3 全零
This commit is contained in:
2026-09-20 11:50:58 +08:00
parent d959fa9a81
commit 5ecab7b15d
54 changed files with 339 additions and 280 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ const buttonVariants = cva(
ghost:
'text-text-secondary hover:bg-bg-secondary hover:text-text-primary active:scale-[0.97] transition-all duration-fast ease-standard',
link:
'text-text-primary underline-offset-4 hover:text-brand relative after:absolute after:bottom-0 after:left-0 after:h-[2px] after:w-0 hover:after:w-full after:bg-brand after:transition-all after:duration-normal after:ease-standard',
'text-text-primary underline-offset-4 hover:text-brand-ink relative after:absolute after:bottom-0 after:left-0 after:h-[2px] after:w-0 hover:after:w-full after:bg-brand after:transition-all after:duration-normal after:ease-standard',
destructive:
'bg-error text-white shadow-sm hover:bg-error-hover hover:-translate-y-px active:translate-y-0 active:scale-[0.97] transition-all duration-fast ease-standard',
},
+2 -2
View File
@@ -111,7 +111,7 @@ const ContextMenuCheckboxItem = React.forwardRef<
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Check className="size-4 text-brand" />
<Check className="size-4 text-brand-ink" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -136,7 +136,7 @@ const ContextMenuRadioItem = React.forwardRef<
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="size-2 fill-brand text-brand" />
<Circle className="size-2 fill-brand text-brand-ink" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
+1 -1
View File
@@ -45,7 +45,7 @@ const VARIANT_STYLES: Record<CTAButtonVariant, string> = {
dark:
'text-dark-text-primary border border-dark-border hover:border-dark-text-muted hover:bg-white/5 px-10 py-4 font-semibold text-base',
inline:
'text-text-primary hover:text-brand px-0 py-0 font-medium text-sm bg-transparent border-0',
'text-text-primary hover:text-brand-ink px-0 py-0 font-medium text-sm bg-transparent border-0',
};
const ARROW_STYLES: Record<CTAButtonVariant, string> = {
+2 -2
View File
@@ -113,7 +113,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="size-4 text-brand" />
<Check className="size-4 text-brand-ink" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
@@ -138,7 +138,7 @@ const DropdownMenuRadioItem = React.forwardRef<
>
<span className="absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="size-2 fill-brand text-brand" />
<Circle className="size-2 fill-brand text-brand-ink" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
+2 -2
View File
@@ -31,7 +31,7 @@ const LabeledInput = React.forwardRef<HTMLInputElement, LabeledInputProps>(
>
{label}
{required && (
<span className="text-brand ml-1.5" aria-hidden="true">*</span>
<span className="text-brand-ink ml-1.5" aria-hidden="true">*</span>
)}
</Label>
)}
@@ -56,7 +56,7 @@ const LabeledInput = React.forwardRef<HTMLInputElement, LabeledInputProps>(
{error && (
<p
id={errorId}
className="mt-1.5 text-sm text-brand"
className="mt-1.5 text-sm text-brand-ink"
role="alert"
data-testid="error-message"
>
+2 -2
View File
@@ -31,7 +31,7 @@ const LabeledTextarea = React.forwardRef<HTMLTextAreaElement, LabeledTextareaPro
>
{label}
{required && (
<span className="text-brand ml-1.5" aria-hidden="true">*</span>
<span className="text-brand-ink ml-1.5" aria-hidden="true">*</span>
)}
</Label>
)}
@@ -56,7 +56,7 @@ const LabeledTextarea = React.forwardRef<HTMLTextAreaElement, LabeledTextareaPro
{error && (
<p
id={errorId}
className="mt-1.5 text-sm text-brand"
className="mt-1.5 text-sm text-brand-ink"
role="alert"
data-testid="error-message"
>
+1 -1
View File
@@ -26,7 +26,7 @@ interface MetricCardProps {
}
const accentStyles: Record<string, string> = {
brand: 'text-brand bg-brand-bg',
brand: 'text-brand-ink bg-brand-bg',
blue: 'text-accent-blue bg-accent-blue/10',
teal: 'text-accent-teal bg-accent-teal/10',
amber: 'text-accent-amber bg-accent-amber/10',
+3 -3
View File
@@ -68,7 +68,7 @@ export function MilestoneTimeline({
)}>
<div className="w-2 h-2 rounded-full bg-brand" />
</div>
<div className="text-sm font-bold text-brand mb-1 tracking-wider">
<div className="text-sm font-bold text-brand-ink mb-1 tracking-wider">
{item.year}
</div>
<h4 className={cn('text-lg font-semibold mb-2', isDark ? 'text-white' : 'text-text-primary')}>
@@ -80,7 +80,7 @@ export function MilestoneTimeline({
{item.highlight && (
<div className={cn(
'mt-3 inline-flex items-center px-3 py-1 rounded-full text-xs font-medium',
isDark ? 'bg-brand/10 text-brand' : 'bg-brand-bg text-brand'
isDark ? 'bg-brand/10 text-brand-ink' : 'bg-brand-bg text-brand-ink'
)}>
{item.highlight}
</div>
@@ -112,7 +112,7 @@ export function MilestoneTimeline({
)}>
<div className="w-2 h-2 rounded-full bg-brand" />
</div>
<div className="text-sm font-bold text-brand mb-2 tracking-wider">
<div className="text-sm font-bold text-brand-ink mb-2 tracking-wider">
{item.year}
</div>
<h4 className={cn('text-base font-semibold mb-2 leading-snug', isDark ? 'text-white' : 'text-text-primary')}>
+1 -1
View File
@@ -36,7 +36,7 @@ export function SectionLabel({
)}
>
<div className="w-10 h-px bg-brand" />
<span className="text-[13px] tracking-[0.15em] font-medium text-brand">
<span className="text-[13px] tracking-[0.15em] font-medium text-brand-ink">
{children}
</span>
</div>
+2 -2
View File
@@ -30,7 +30,7 @@ const RadioGroupItem = React.forwardRef<
ref={ref}
data-slot="radio-group-item"
className={cn(
'aspect-square size-4 rounded-full border border-border-primary bg-bg-primary text-brand shadow-sm',
'aspect-square size-4 rounded-full border border-border-primary bg-bg-primary text-brand-ink shadow-sm',
'focus:outline-none focus-visible:ring-2 focus-visible:ring-brand focus-visible:ring-offset-2',
'disabled:cursor-not-allowed disabled:opacity-50',
'data-[state=checked]:border-brand',
@@ -43,7 +43,7 @@ const RadioGroupItem = React.forwardRef<
data-slot="radio-group-indicator"
className="flex items-center justify-center"
>
<Circle className="size-2.5 fill-brand text-brand" />
<Circle className="size-2.5 fill-brand text-brand-ink" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
));
+1 -1
View File
@@ -133,7 +133,7 @@ const SelectItem = React.forwardRef<
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="size-4 text-brand" />
<Check className="size-4 text-brand-ink" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>