Commit 811086b9 authored by Andy-bubu's avatar Andy-bubu

feat: refine xinyuan brand and chart colors

parent a878b481
# XinYuan Design System # XinYuan Design System
Updated: 2026-08-26 Updated: 2026-09-02
## Authority ## Authority
The project follows the approved Xinyuan HeroUI design system. The immutable The project follows the approved Xinyuan HeroUI design system. The theme
theme authority is `src/design-system/theme.css`; it must remain byte-identical authority is `src/design-system/theme.css`; its current approved brand profile
to the skill baseline. Project extensions belong in `tokens.css` and may add uses `#DA241A` for HeroUI primary/focus and the shared restrained chart palette.
`--rc-*` aliases without redefining HeroUI core variables. Project extensions belong in `tokens.css` and may add `--rc-*` aliases without
redefining unrelated HeroUI core variables.
Global CSS order: Global CSS order:
...@@ -26,7 +27,16 @@ paths are not valid delivery mechanisms. ...@@ -26,7 +27,16 @@ paths are not valid delivery mechanisms.
## Identity And Theme ## Identity And Theme
- Primary brand: `#9E1B1B` through the authoritative `--accent` token. - Primary brand: `#DA241A` through HeroUI's `--accent` token. HeroUI v3 uses
`--accent` as the primary Button/active/focus source; legacy numbered
`primary-50` through `primary-950` tokens are intentionally not introduced.
- Hover and pressed primary states remain HeroUI-derived shades of `#DA241A`;
they never switch to a framework blue or purple.
- Charts use the shared 16-token Xinyuan palette (`--xinyuan-chart-1` through
`--xinyuan-chart-16`) and the `--rc-chart-*` aliases. The sequence is a
restrained brand-red, warm-neutral and slate-blue family with no green or
fluorescent colors. A single series always resolves to `#DA241A` and small
series sets use the first four brand-red steps.
- Light identity: `xy-logo-red.png`. - Light identity: `xy-logo-red.png`.
- Dark identity: `xy-logo-white.png`. - Dark identity: `xy-logo-white.png`.
- Root theme: the application shell is explicitly `.light`; the content canvas stays light while the Sidebar consumes the fixed dark split-shell tokens. - Root theme: the application shell is explicitly `.light`; the content canvas stays light while the Sidebar consumes the fixed dark split-shell tokens.
......
# XinYuan UI Audit # XinYuan UI Audit
Updated: 2026-08-26 Updated: 2026-09-02
## Repository Baseline ## Repository Baseline
...@@ -90,8 +90,17 @@ switcher grouping gap without changing its application source or navigation. ...@@ -90,8 +90,17 @@ switcher grouping gap without changing its application source or navigation.
- `src/design-system/theme.css` retains the Xinyuan HeroUI tokens and now includes - `src/design-system/theme.css` retains the Xinyuan HeroUI tokens and now includes
the fixed dark Sidebar and ECharts chart tokens used by the approved baseline. the fixed dark Sidebar and ECharts chart tokens used by the approved baseline.
- `tokens.css`, `shell.css` and the login contract in `tailwind.css` are exact - HeroUI `--accent` and `--focus` are fixed to the Xinyuan Fund brand color
copies of the approved `problem_rectification` shared baseline. `#DA241A` in both theme blocks, so primary Buttons and focus states do not
inherit the default HeroUI blue. HeroUI v3 has no numbered primary scale;
`--accent` is the supported primary source.
- ECharts uses 16 `--xinyuan-chart-*` tokens in a restrained red, warm-neutral
and slate-blue sequence. Green, fluorescent and rainbow series colors were
removed; `tokens.css` only aliases `--rc-chart-*` to the authoritative chart
tokens. KPI icon tones use the same non-green family.
- `tokens.css`, `shell.css` and the login contract in `tailwind.css` retain the
approved shared baseline structure; the local brand profile overrides only
the primary/focus and chart/KPI color Tokens described above.
- Inter weights and the stable red/white Xinyuan assets are loaded globally. - Inter weights and the stable red/white Xinyuan assets are loaded globally.
- Official `@heroui-pro/react/css` loads before the Xinyuan theme and - Official `@heroui-pro/react/css` loads before the Xinyuan theme and
extension Tokens. The temporary Pro CSS compatibility snapshot was removed. extension Tokens. The temporary Pro CSS compatibility snapshot was removed.
......
...@@ -7,8 +7,18 @@ export const XINYUAN_CHART_PALETTE = Object.freeze([ ...@@ -7,8 +7,18 @@ export const XINYUAN_CHART_PALETTE = Object.freeze([
"var(--rc-chart-6)", "var(--rc-chart-6)",
"var(--rc-chart-7)", "var(--rc-chart-7)",
"var(--rc-chart-8)", "var(--rc-chart-8)",
"var(--rc-chart-9)",
"var(--rc-chart-10)",
"var(--rc-chart-11)",
"var(--rc-chart-12)",
"var(--rc-chart-13)",
"var(--rc-chart-14)",
"var(--rc-chart-15)",
"var(--rc-chart-16)",
]); ]);
const SMALL_SERIES_PALETTE_INDEXES = Object.freeze([0, 1, 2, 3]);
export const XINYUAN_CHART_SEMANTIC = Object.freeze({ export const XINYUAN_CHART_SEMANTIC = Object.freeze({
primary: "var(--rc-chart-primary)", primary: "var(--rc-chart-primary)",
risk: "var(--rc-chart-risk)", risk: "var(--rc-chart-risk)",
...@@ -41,14 +51,19 @@ export function createXinyuanChartColorMap(keys = []) { ...@@ -41,14 +51,19 @@ export function createXinyuanChartColorMap(keys = []) {
const stableKeys = [...new Set(keys.map(normalizeChartKey).filter(Boolean))].sort(); const stableKeys = [...new Set(keys.map(normalizeChartKey).filter(Boolean))].sort();
const colorMap = new Map(); const colorMap = new Map();
const usedIndexes = new Set(); const usedIndexes = new Set();
const paletteIndexes = stableKeys.length === 1
? [0]
: stableKeys.length <= SMALL_SERIES_PALETTE_INDEXES.length
? SMALL_SERIES_PALETTE_INDEXES
: XINYUAN_CHART_PALETTE.map((_, index) => index);
stableKeys.forEach((key, keyIndex) => { stableKeys.forEach((key, keyIndex) => {
const preferredIndex = hashChartKey(key) % XINYUAN_CHART_PALETTE.length; const preferredIndex = hashChartKey(key) % paletteIndexes.length;
let paletteIndex = preferredIndex; let paletteIndex = paletteIndexes[preferredIndex];
if (keyIndex < XINYUAN_CHART_PALETTE.length) { if (keyIndex < paletteIndexes.length) {
for (let offset = 0; offset < XINYUAN_CHART_PALETTE.length; offset += 1) { for (let offset = 0; offset < paletteIndexes.length; offset += 1) {
const candidateIndex = (preferredIndex + offset) % XINYUAN_CHART_PALETTE.length; const candidateIndex = paletteIndexes[(preferredIndex + offset) % paletteIndexes.length];
if (!usedIndexes.has(candidateIndex)) { if (!usedIndexes.has(candidateIndex)) {
paletteIndex = candidateIndex; paletteIndex = candidateIndex;
usedIndexes.add(candidateIndex); usedIndexes.add(candidateIndex);
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
[data-theme="light"], [data-theme="light"],
[data-theme="default"] { [data-theme="default"] {
/* Theme Colors (Light Mode) */ /* Theme Colors (Light Mode) */
--accent: oklch(57.14% 0.2156 29.20); --accent: #DA241A;
--accent-foreground: oklch(99.11% 0 0); --accent-foreground: oklch(99.11% 0 0);
--background: oklch(97.02% 0.0000 29.20); --background: oklch(97.02% 0.0000 29.20);
--border: oklch(90.00% 0.0000 29.20); --border: oklch(90.00% 0.0000 29.20);
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
--field-border: transparent; --field-border: transparent;
--field-foreground: oklch(21.03% 0.0000 29.20); --field-foreground: oklch(21.03% 0.0000 29.20);
--field-placeholder: oklch(55.17% 0.0000 29.20); --field-placeholder: oklch(55.17% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20); --focus: #DA241A;
--foreground: oklch(21.03% 0.0000 29.20); --foreground: oklch(21.03% 0.0000 29.20);
--muted: oklch(55.17% 0.0000 29.20); --muted: oklch(55.17% 0.0000 29.20);
--overlay: oklch(100.00% 0.0000 29.20); --overlay: oklch(100.00% 0.0000 29.20);
...@@ -70,22 +70,22 @@ ...@@ -70,22 +70,22 @@
* Ant Design remains the icon source. Resolve these CSS variables to * Ant Design remains the icon source. Resolve these CSS variables to
* concrete colors before assigning them to an ECharts option object. * concrete colors before assigning them to an ECharts option object.
*/ */
--xinyuan-chart-1: #d92d20; --xinyuan-chart-1: #DA241A;
--xinyuan-chart-2: #ef4444; --xinyuan-chart-2: #B83A32;
--xinyuan-chart-3: #f97316; --xinyuan-chart-3: #C95A52;
--xinyuan-chart-4: #f59e0b; --xinyuan-chart-4: #D47A70;
--xinyuan-chart-5: #22c55e; --xinyuan-chart-5: #A65A45;
--xinyuan-chart-6: #14b87a; --xinyuan-chart-6: #B87958;
--xinyuan-chart-7: #06b6d4; --xinyuan-chart-7: #C99772;
--xinyuan-chart-8: #0ea5e9; --xinyuan-chart-8: #6F6674;
--xinyuan-chart-9: #3b82f6; --xinyuan-chart-9: #807783;
--xinyuan-chart-10: #6366f1; --xinyuan-chart-10: #938B96;
--xinyuan-chart-11: #8b5cf6; --xinyuan-chart-11: #4F6380;
--xinyuan-chart-12: #7c3aed; --xinyuan-chart-12: #61758F;
--xinyuan-chart-13: #b42318; --xinyuan-chart-13: #74879A;
--xinyuan-chart-14: #84cc16; --xinyuan-chart-14: #8A6F63;
--xinyuan-chart-15: #a855f7; --xinyuan-chart-15: #9A8174;
--xinyuan-chart-16: #64748b; --xinyuan-chart-16: #B5A6A1;
--xinyuan-chart-grid: #e8e8e8; --xinyuan-chart-grid: #e8e8e8;
--xinyuan-chart-axis: #737373; --xinyuan-chart-axis: #737373;
--xinyuan-chart-label: #737373; --xinyuan-chart-label: #737373;
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
[data-theme="dark"] { [data-theme="dark"] {
color-scheme: dark; color-scheme: dark;
/* Theme Colors (Dark Mode) */ /* Theme Colors (Dark Mode) */
--accent: oklch(57.14% 0.2156 29.20); --accent: #DA241A;
--accent-foreground: oklch(99.11% 0 0); --accent-foreground: oklch(99.11% 0 0);
--background: oklch(12.00% 0.0000 29.20); --background: oklch(12.00% 0.0000 29.20);
--border: oklch(28.00% 0.0000 29.20); --border: oklch(28.00% 0.0000 29.20);
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
--field-border: transparent; --field-border: transparent;
--field-foreground: oklch(99.11% 0.0000 29.20); --field-foreground: oklch(99.11% 0.0000 29.20);
--field-placeholder: oklch(70.50% 0.0000 29.20); --field-placeholder: oklch(70.50% 0.0000 29.20);
--focus: oklch(57.14% 0.2156 29.20); --focus: #DA241A;
--foreground: oklch(99.11% 0.0000 29.20); --foreground: oklch(99.11% 0.0000 29.20);
--muted: oklch(70.50% 0.0000 29.20); --muted: oklch(70.50% 0.0000 29.20);
--overlay: oklch(21.03% 0.0000 29.20); --overlay: oklch(21.03% 0.0000 29.20);
......
...@@ -91,14 +91,15 @@ ...@@ -91,14 +91,15 @@
0 4px 12px rgb(15 23 42 / 2%); 0 4px 12px rgb(15 23 42 / 2%);
/* KPI icon surfaces. Card bodies remain neutral; only the icon well carries tone. */ /* KPI icon surfaces. Card bodies remain neutral; only the icon well carries tone. */
--rc-metric-tone-info: oklch(55% 0.14 255); /* KPI icon tones use the restrained chart palette; no green is used for data emphasis. */
--rc-metric-tone-success: oklch(54% 0.13 145); --rc-metric-tone-info: var(--xinyuan-chart-11);
--rc-metric-tone-teal: oklch(55% 0.1 205); --rc-metric-tone-success: var(--xinyuan-chart-2);
--rc-metric-tone-danger: oklch(52% 0.17 25); --rc-metric-tone-teal: var(--xinyuan-chart-13);
--rc-metric-tone-warning: oklch(62% 0.12 75); --rc-metric-tone-danger: var(--xinyuan-chart-1);
--rc-metric-tone-violet: oklch(56% 0.12 300); --rc-metric-tone-warning: var(--xinyuan-chart-6);
--rc-metric-tone-indigo: oklch(53% 0.13 275); --rc-metric-tone-violet: var(--xinyuan-chart-8);
--rc-metric-tone-coral: oklch(60% 0.12 48); --rc-metric-tone-indigo: var(--xinyuan-chart-12);
--rc-metric-tone-coral: var(--xinyuan-chart-3);
--rc-metric-tone-neutral: var(--muted); --rc-metric-tone-neutral: var(--muted);
--rc-metric-tone-info-soft: color-mix( --rc-metric-tone-info-soft: color-mix(
in oklab, in oklab,
...@@ -147,14 +148,15 @@ ...@@ -147,14 +148,15 @@
); );
/* Data visualization only. Do not reuse UI status colors as a series palette. */ /* Data visualization only. Do not reuse UI status colors as a series palette. */
--rc-chart-1: oklch(45.32% 0.1661 27.03); /* Chart aliases point to the authoritative 16-color Xinyuan palette. */
--rc-chart-2: oklch(56% 0.135 255); --rc-chart-1: var(--xinyuan-chart-1);
--rc-chart-3: oklch(64% 0.11 220); --rc-chart-2: var(--xinyuan-chart-2);
--rc-chart-4: oklch(56% 0.115 280); --rc-chart-3: var(--xinyuan-chart-3);
--rc-chart-5: oklch(60% 0.095 200); --rc-chart-4: var(--xinyuan-chart-4);
--rc-chart-6: oklch(70% 0.12 80); --rc-chart-5: var(--xinyuan-chart-5);
--rc-chart-7: oklch(60% 0.1 295); --rc-chart-6: var(--xinyuan-chart-6);
--rc-chart-8: oklch(57% 0.035 250); --rc-chart-7: var(--xinyuan-chart-7);
--rc-chart-8: var(--xinyuan-chart-8);
--rc-chart-9: var(--xinyuan-chart-9); --rc-chart-9: var(--xinyuan-chart-9);
--rc-chart-10: var(--xinyuan-chart-10); --rc-chart-10: var(--xinyuan-chart-10);
--rc-chart-11: var(--xinyuan-chart-11); --rc-chart-11: var(--xinyuan-chart-11);
...@@ -164,7 +166,7 @@ ...@@ -164,7 +166,7 @@
--rc-chart-15: var(--xinyuan-chart-15); --rc-chart-15: var(--xinyuan-chart-15);
--rc-chart-16: var(--xinyuan-chart-16); --rc-chart-16: var(--xinyuan-chart-16);
--rc-chart-primary: var(--rc-chart-1); --rc-chart-primary: var(--rc-chart-1);
--rc-chart-risk: oklch(47% 0.145 27.03); --rc-chart-risk: var(--xinyuan-chart-2);
--rc-chart-warning: var(--rc-chart-6); --rc-chart-warning: var(--rc-chart-6);
--rc-chart-positive: var(--rc-chart-2); --rc-chart-positive: var(--rc-chart-2);
--rc-chart-comparison: var(--rc-chart-4); --rc-chart-comparison: var(--rc-chart-4);
......
:root { :root {
--pm-brand: var(--rc-brand-primary, #9e1b1b); --pm-brand: var(--rc-brand-primary, #DA241A);
--pm-brand-strong: var(--rc-brand-dark, #7e1515); --pm-brand-strong: var(--rc-brand-dark, #B51B14);
} }
html, html,
......
...@@ -50,8 +50,8 @@ body .login-page { ...@@ -50,8 +50,8 @@ body .login-page {
--rc-login-field-filled-background: #fbf1f1; --rc-login-field-filled-background: #fbf1f1;
--rc-login-field-filled-background-hover: #faeaea; --rc-login-field-filled-background-hover: #faeaea;
--rc-login-field-autofill-background: #fbf1f1; --rc-login-field-autofill-background: #fbf1f1;
--rc-login-field-border-hover: rgb(158 27 27 / 24%); --rc-login-field-border-hover: rgb(218 36 26 / 24%);
--rc-login-field-focus-ring: rgb(158 27 27 / 16%); --rc-login-field-focus-ring: rgb(218 36 26 / 16%);
} }
body .login-brand img { body .login-brand img {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment