diff --git a/src/frontend/client/src/pages/_gallery/sections/ColorSection.tsx b/src/frontend/client/src/pages/_gallery/sections/ColorSection.tsx
new file mode 100644
index 000000000..b2a1d81cc
--- /dev/null
+++ b/src/frontend/client/src/pages/_gallery/sections/ColorSection.tsx
@@ -0,0 +1,288 @@
+/**
+ * Color section — visualizes the color spec from docs-ui-refactor/基础-色彩规范.md
+ * (brand dual-theme ramp / Arco neutral grays / functional colors / tag pairs)
+ * so the designer can verify the tokens landed in src/style.css + tailwind.config.cjs.
+ *
+ * DEV-ONLY internal tooling, never shipped (route gated by import.meta.env.DEV).
+ * NOTE on literal hex values in this file: they are DISPLAY-ONLY spec swatches —
+ * the blue and green brand columns must render side by side regardless of the
+ * active theme, and some tag colors (skill purple, the fixed-blue exception) are
+ * intentionally not tokenized. Business code must never hardcode these values.
+ */
+import { CSSProperties, ReactNode } from 'react';
+import { cn } from '~/utils';
+import { ComponentPage, ExampleGroup, ExampleGrid, ExampleCard, CompareTable } from '../components/kit';
+
+/* ------------------------------------------------------------------ *
+ * Small presentational helpers
+ * ------------------------------------------------------------------ */
+
+/** Inline swatch + optional code label, for table cells. */
+function ColorCell({
+ hex,
+ className,
+ style,
+ label,
+}: {
+ /** Literal display color (spec documentation value). */
+ hex?: string;
+ /** Live token class (e.g. bg-blue-500) — proves the wiring works. */
+ className?: string;
+ style?: CSSProperties;
+ label?: string;
+}) {
+ return (
+
+
+ {(label ?? hex) && (
+ {label ?? hex}
+ )}
+
+ );
+}
+
+/** Labeled swatch for the functional-color state rows. */
+function StateSwatch({
+ className,
+ style,
+ label,
+ sub,
+}: {
+ className?: string;
+ style?: CSSProperties;
+ label: string;
+ sub?: string;
+}) {
+ return (
+
+
+ {label}
+ {sub && {sub}}
+
+ );
+}
+
+/** A light-bg + strong-text tag chip (§4 tag pairs). */
+function TagChip({
+ className,
+ style,
+ children,
+}: {
+ className?: string;
+ style?: CSSProperties;
+ children: ReactNode;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+
+/* ------------------------------------------------------------------ *
+ * Spec data
+ * ------------------------------------------------------------------ */
+
+/** §1.1 brand ramp — blue/green documentation values. */
+const BRAND_RAMP = [
+ { step: '50', blue: '#E8F3FF', green: '#E4F1EC', usage: '浅底一档:选中背景;按钮 filled 底 / outlined·text hover' },
+ { step: '100', blue: '#BEDAFF', green: '#CCE4DA', usage: '浅底二档:filled hover / outlined·text 触屏 active' },
+ { step: '200', blue: '#94BFFF', green: '#A3D2C0', usage: '浅底三档:filled 触屏 active' },
+ { step: '300', blue: '#6AA1FF', green: '#6FBAA0', usage: '' },
+ { step: '400', blue: '#4080FF', green: '#3D9B78', usage: 'hover(比主色亮一档)' },
+ { step: '500', blue: '#165DFF', green: '#169C47', usage: '主色(= --brand-main)' },
+ { step: '600', blue: '#024DE3', green: '#136345', usage: 'active / 按下(深一档)' },
+ { step: '700', blue: '#0239AB', green: '#0F4D36', usage: '' },
+ { step: '800', blue: '#042B80', green: '#0A3826', usage: '' },
+ { step: '900', blue: '#051D52', green: '#062619', usage: '' },
+];
+
+/** §2.1 primitive Arco gray ramp — rendered live from --arco-gray-N. */
+const ARCO_GRAYS = [
+ { n: 1, hex: '#F7F8FA', note: 'hover 底 → fill-1' },
+ { n: 2, hex: '#F2F3F5', note: 'filled 控件底 → fill-2' },
+ { n: 3, hex: '#E5E6EB', note: '常规边框 → border-base / fill-3' },
+ { n: 4, hex: '#C9CDD4', note: '禁用/占位 → text-4 / fill-4 / border-deep' },
+ { n: 5, hex: '#A9AEB8', note: '' },
+ { n: 6, hex: '#86909C', note: '辅助文字 → text-3(全站最高频 hex ×263)' },
+ { n: 7, hex: '#6B7785', note: '' },
+ { n: 8, hex: '#4E5969', note: '次要文字 → text-2' },
+ { n: 9, hex: '#272E3B', note: '' },
+ { n: 10, hex: '#1D2129', note: '主文字 → text-1' },
+];
+
+/** §2.2 semantic neutral layer — token / class / value / live demo. */
+const NEUTRAL_SEMANTIC: { token: string; cls: string; value: string; usage: string; demo: ReactNode }[] = [
+ { token: '--text-1', cls: 'text-text-1', value: 'gray-10 #1D2129', usage: '主文字:标题、正文主体', demo: 标题、正文主体 },
+ { token: '--text-2', cls: 'text-text-2', value: 'gray-8 #4E5969', usage: '次文字:次要说明、默认按钮文字', demo: 次要说明文字 },
+ { token: '--text-3', cls: 'text-text-3', value: 'gray-6 #86909C', usage: '辅助文字:弱提示、时间戳、占位符', demo: 弱提示 · 10 分钟前 },
+ { token: '--text-4', cls: 'text-text-4', value: 'gray-4 #C9CDD4', usage: '禁用文字', demo: 禁用状态文字 },
+ { token: '--fill-1', cls: 'bg-fill-1', value: 'gray-1 #F7F8FA', usage: '浅填充:hover 底、页面浅灰背景', demo: },
+ { token: '--fill-2', cls: 'bg-fill-2', value: 'gray-2 #F2F3F5', usage: '填充:active 底、filled 控件底', demo: },
+ { token: '--fill-3', cls: 'bg-fill-3', value: 'gray-3 #E5E6EB', usage: '深填充:filled hover', demo: },
+ { token: '--fill-4', cls: 'bg-fill-4', value: 'gray-4 #C9CDD4', usage: '重填充:filled active', demo: },
+ { token: '--border-base', cls: 'border-border-base', value: 'gray-3 #E5E6EB', usage: '常规边框:输入框、卡片、分割线(规范里的 border)', demo: },
+ { token: '--border-deep', cls: 'border-border-deep', value: 'gray-4 #C9CDD4', usage: '深边框:强调分割、hover 边框', demo: },
+];
+
+/* ------------------------------------------------------------------ */
+
+export function ColorSection() {
+ return (
+
+ Arco 色板为基准的两层 token:primitive(--arco-gray-1~10)→ semantic(
+ --text-1~4 / --fill-1~4 / --border-base|-deep /{' '}
+ --success|warning|danger-*)。命名注:规范里的语义名 border 与
+ shadcn 的 --border(HSL)及 Tailwind 边框类冲突,落地为{' '}
+ border-base(类 border-border-base,与现有{' '}
+ border-border-light 同构)。
+ >
+ }
+ whenToUse={[
+ <>
+ 组件与业务代码只用 semantic 层(text-text-1 / bg-fill-1 /{' '}
+ border-border-base / bg-success…),不用{' '}
+ --arco-gray-* primitive(Tailwind 故意未接线),禁止裸 hex。
+ >,
+ <>
+ 品牌色永远走 blue-* 类(已重指向 --brand-*,自动蓝⇄绿换肤)或{' '}
+ rgb(var(--brand-NNN));禁止写品牌 hex。列表/菜单选中浅底统一{' '}
+ bg-blue-500/[0.07];按钮浅底走 50/100/200 实档。
+ >,
+ <>
+ 语义色(成功/警告/危险)不参与换肤。危险浅底分两种:按钮用主色透明阶{' '}
+ bg-danger/10~20;tag 用实色 bg-danger-tint(#FFECE8)。
+ >,
+ <>同一语义只允许一个值(如次要文字只能 text-text-2);新颜色先在色板找替代,确需新增走 token 评审进 primitive 层。>,
+ ]}
+ >
+
+ [
+ {r.step},
+ ,
+ ,
+ r.usage,
+ ]),
+ [
+ muted,
+ ,
+ ,
+ '低饱和品牌点缀(如置顶 pin)· --brand-muted',
+ ],
+ ]}
+ />
+
+ 固定色例外(永远不换肤,别误改):审批中 tag 永远蓝 #E8F3FF/#165DFF
+ (ApprovalCenterDialog);应用中心置顶 pin 用 --brand-muted;第三方 logo
+ 原色保留。选中浅底示例:
+
+ bg-blue-500/[0.07]
+
+
+
+
+
+ [
+ --arco-gray-{g.n},
+ ,
+ g.note,
+ ])}
+ />
+
+
+
+ [
+ {s.token},
+ {s.cls},
+ {s.value},
+ s.usage,
+ {s.demo}
,
+ ])}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 已驳回(tag 实色)
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 技能(紫 · 未 token 化)
+ 助手(橙 = warning 同值)
+ 已完成
+ 已驳回
+ 审批中(例外:永远蓝)
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/client/src/style.css b/src/frontend/client/src/style.css
index dc0d9892f..05560dbc0 100644
--- a/src/frontend/client/src/style.css
+++ b/src/frontend/client/src/style.css
@@ -77,6 +77,60 @@
--text-h1: var(--font-size-7); --leading-h1: var(--line-height-7);
--text-display: var(--font-size-8); --leading-display: var(--line-height-8);
--text-metric: var(--font-size-9); --leading-metric: var(--line-height-9);
+
+ /* ============================================================
+ * Color tokens (docs-ui-refactor/基础-色彩规范.md §2/§3/§7)
+ * Two layers: primitive --arco-gray-1..10 (Arco gray ramp, the
+ * numeric source) and semantic --text-1..4 / --fill-1..4 /
+ * --border-base|-deep / --success|warning|danger-* — components use
+ * the semantic layer only, via Tailwind (text-text-1 / bg-fill-1 /
+ * border-border-base / bg-success ...). RGB channel triplets so
+ * Tailwind `/` modifiers keep working.
+ * Naming constraints: --gray-N / --red-N / --green-N belong to the
+ * legacy LibreChat ramps above and must NOT be overwritten; --border
+ * belongs to shadcn (HSL) — hence --arco-gray-N and --border-base.
+ * success/warning/danger are theme-INDEPENDENT: they never follow
+ * the blue⇄green brand switch. Light-mode values only for now
+ * (dark mode inherits them — known debt, same as --btn-*).
+ * ============================================================ */
+ /* Primitive — Arco gray 1-10 */
+ --arco-gray-1: 247 248 250; /* #F7F8FA */
+ --arco-gray-2: 242 243 245; /* #F2F3F5 */
+ --arco-gray-3: 229 230 235; /* #E5E6EB */
+ --arco-gray-4: 201 205 212; /* #C9CDD4 */
+ --arco-gray-5: 169 174 184; /* #A9AEB8 */
+ --arco-gray-6: 134 144 156; /* #86909C */
+ --arco-gray-7: 107 119 133; /* #6B7785 */
+ --arco-gray-8: 78 89 105; /* #4E5969 */
+ --arco-gray-9: 39 46 59; /* #272E3B */
+ --arco-gray-10: 29 33 41; /* #1D2129 */
+
+ /* Semantic — neutral text / fill / border */
+ --text-1: var(--arco-gray-10); /* primary text: titles, body */
+ --text-2: var(--arco-gray-8); /* secondary text */
+ --text-3: var(--arco-gray-6); /* hint / timestamp / placeholder */
+ --text-4: var(--arco-gray-4); /* disabled text */
+ --fill-1: var(--arco-gray-1); /* hover bg / light page bg */
+ --fill-2: var(--arco-gray-2); /* active bg / filled control base */
+ --fill-3: var(--arco-gray-3); /* filled hover */
+ --fill-4: var(--arco-gray-4); /* filled active */
+ --border-base: var(--arco-gray-3); /* regular border ("border" in the spec) */
+ --border-deep: var(--arco-gray-4); /* emphasized divider / hover border */
+
+ /* Semantic — functional (never themed) */
+ --success: 0 180 42; /* #00B42A (Arco green-6) */
+ --success-hover: 35 195 67; /* #23C343 */
+ --success-active: 0 154 41; /* #009A29 */
+ --success-tint: 232 255 234; /* #E8FFEA (Arco green-1) — tag / light bg */
+ --warning: 255 125 0; /* #FF7D00 (Arco orange-6) */
+ --warning-hover: 255 154 46; /* #FF9A2E */
+ --warning-active: 210 95 0; /* #D25F00 */
+ --warning-tint: 255 247 232; /* #FFF7E8 (Arco orange-1) — tag / light bg */
+ --danger: 245 63 63; /* #F53F3F (= --btn-danger) */
+ --danger-hover: 214 55 58; /* #D6373A (= --btn-danger-hover) */
+ --danger-active: 208 47 51; /* #D02F33 (= --btn-danger-active) */
+ --danger-tint: 255 236 232; /* #FFECE8 (Arco red-1) — TAG bg only; buttons
+ use alpha over the main red (bg-danger/10 …) */
}
/* Mobile (narrow viewport ≤768px) remap of the SEMANTIC layer only:
diff --git a/src/frontend/client/tailwind.config.cjs b/src/frontend/client/tailwind.config.cjs
index 71c9833b8..abb20f475 100644
--- a/src/frontend/client/tailwind.config.cjs
+++ b/src/frontend/client/tailwind.config.cjs
@@ -180,6 +180,42 @@ module.exports = {
'btn-danger-hover': 'rgb(var(--btn-danger-hover) / )',
'btn-danger-active': 'rgb(var(--btn-danger-active) / )',
'btn-disabled-border': 'rgb(var(--btn-disabled-border) / )',
+ // Arco color tokens (docs-ui-refactor/基础-色彩规范.md §2/§3/§7) — semantic
+ // layer only; the --arco-gray-* primitives are intentionally NOT wired so
+ // components can't bypass the semantic names. RGB-channel vars live in
+ // src/style.css :root; channel form keeps `/` modifiers working.
+ // Classes: text-text-1…4 / bg-fill-1…4 / border-border-base|-deep /
+ // bg-success|warning|danger(+-hover/-active/-tint). "border-base" because
+ // the plain `border` color key is taken by shadcn below. success/warning/
+ // danger never follow the blue⇄green brand theme.
+ 'text-1': 'rgb(var(--text-1) / )',
+ 'text-2': 'rgb(var(--text-2) / )',
+ 'text-3': 'rgb(var(--text-3) / )',
+ 'text-4': 'rgb(var(--text-4) / )',
+ 'fill-1': 'rgb(var(--fill-1) / )',
+ 'fill-2': 'rgb(var(--fill-2) / )',
+ 'fill-3': 'rgb(var(--fill-3) / )',
+ 'fill-4': 'rgb(var(--fill-4) / )',
+ 'border-base': 'rgb(var(--border-base) / )',
+ 'border-deep': 'rgb(var(--border-deep) / )',
+ success: {
+ DEFAULT: 'rgb(var(--success) / )',
+ hover: 'rgb(var(--success-hover) / )',
+ active: 'rgb(var(--success-active) / )',
+ tint: 'rgb(var(--success-tint) / )',
+ },
+ warning: {
+ DEFAULT: 'rgb(var(--warning) / )',
+ hover: 'rgb(var(--warning-hover) / )',
+ active: 'rgb(var(--warning-active) / )',
+ tint: 'rgb(var(--warning-tint) / )',
+ },
+ danger: {
+ DEFAULT: 'rgb(var(--danger) / )',
+ hover: 'rgb(var(--danger-hover) / )',
+ active: 'rgb(var(--danger-active) / )',
+ tint: 'rgb(var(--danger-tint) / )',
+ },
'presentation': 'var(--presentation)',
'text-primary': 'var(--text-primary)',
'text-secondary': 'var(--text-secondary)',