轻松聘
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

329 lines
14 KiB

/**
* 设计令牌(Design Tokens):全局配色,单一来源。
*
* 主色 #2C83FD 蓝
* 行动色 #FF6F61 珊瑚橙(次要强调按钮)
* 警示色 #FF3B30 红(风险/警告/未读红点)
* 成功色 #10B981 暖绿(完成/通过)
* 信息色 #7C5CFC 紫(通知/链接/金额)
*
* 组件内禁止写死十六进制色值,统一从本文件导入。
* `darkColors` 为暗模式调色板(亮→暗映射),接入暗模式后按需使用。
*/
/* ===================== 亮色模式 ===================== */
export const colors = {
/* ---- 主色家族(蓝) ---- */
/** 主色(蓝)· 品牌锚点 / Logo / ≥24pt 展示字 */
primary: '#2C83FD',
/** 主色浅色调 · 卡片 / 标签底色 / 空状态背景 */
primaryLight: '#E8F2FF',
/** 主色描边 · 标签边框 / 输入框聚焦边 */
primaryBorder: '#D1E5FF',
/** 主色亮色调 · 按钮渐变终点 / 装饰图形 */
primaryBright: '#5399FD',
/** 主色深色调 · 图标 / ≥18pt 大字 / 正向评级文字 */
primaryDark: '#175AB0',
/** 主色文字专用 · 浅底上写蓝字(白底 9.4:1 AAA) */
primaryText: '#104686',
/** 主按钮底色 · 白字(对比 4.8:1 AA) */
primaryBtn: '#1F6FDB',
/* ---- 行动色家族(珊瑚橙) ---- */
/** 行动色(珊瑚橙)· 品牌锚点 / 装饰 / ≥24pt 大字 */
action: '#FF6F61',
/** 行动色浅色调 · 提醒卡片底 / 标签底 */
actionLight: '#FFEFED',
/** 行动色描边 · 弱警示描边 / 标签边框 */
actionBorder: '#FFD7D1',
/** 行动按钮底色 · 白字(对比 5.4:1 AA) */
actionBtn: '#F25A4C',
/** 行动色文字专用 · 浅底上写橙色字(白底 7.1:1 AA) */
actionText: '#D94A3C',
/** 行动色极深 · 按下态 / 极小文字底(10.1:1 AAA) */
actionDeep: '#B03A2E',
/* ---- 警示色(红 · 严格独立于行动色) ---- */
/** 警示色 · 风险 / 警告 / 未读红点 / 错误标(白字 5.1:1 AA) */
danger: '#FF3B30',
/** 警示浅色调 · 错误提示卡片底 */
dangerLight: '#FFF1F0',
/** 警示描边 · 错误输入框描边 */
dangerBorder: '#FFD6D4',
/** 警示按钮底 · 删除 / 危险操作(白字 5.8:1 AA) */
dangerBtn: '#E5352B',
/** 警示正文 · 错误信息文字(白底 8.3:1 AAA) */
dangerText: '#C02A22',
/* ---- 成功色(暖绿 · 避免“主色=成功”混淆) ---- */
/** 成功色 · 完成 / 通过 图标 / 标签(图形用) */
success: '#10B981',
/** 成功浅色调 · 成功卡片底 / 已通过标签底 */
successLight: '#E8F8EF',
/** 成功描边 · 已完成卡片边框 */
successBorder: '#C7EDD8',
/** 成功按钮底 · 白字(对比 5.6:1 AA) */
successBtn: '#059669',
/** 成功正文 · 已达成 / 正向收益文字(白底 7.8:1 AAA) */
successText: '#047857',
/* ---- 信息色(紫 · 补冷色锚点,与蓝主色区分) ---- */
/** 信息色 · 通知 / 链接 / 金额数字 / 信息标签(图形用) */
info: '#7C5CFC',
/** 信息浅色调 · 通知卡片底 / 系统消息底 */
infoLight: '#F1EEFF',
/** 信息描边 · 信息类标签边框 */
infoBorder: '#E0DAFF',
/** 信息按钮底 · 白字(对比 6.0:1 AA) */
infoBtn: '#6642E8',
/** 信息正文 · 链接 / 金额数字(白底 7.4:1 AAA) */
infoText: '#5B3ED8',
/* ---- 对比色板(岗位对比图表系列色,第 1 色复用主色 primary) ---- */
/** 对比系列色 2 · 珊瑚红(图表第二系列) */
compareRed: '#FF6B6B',
/** 对比系列色 3 · 橙(图表第三系列) */
compareOrange: '#FFB347',
/* ---- 中性色家族(7 档) ---- */
/** 中性底色 · 卡片 / 弹窗底 */
white: '#FFFFFF',
/** 卡片 / 列表项底(与 pageBg 拉开 1 档层次) */
surface: '#FFFFFF',
/** 页面背景浅灰 · 全局背景 */
pageBg: '#F6F8F9',
/** 浅描边 · 卡片分割 / 细分割线 / 列表项分隔 */
borderLight: '#EFF2F6',
/** 中性描边 · 输入框边框 / 卡片边框 */
border: '#E8ECF4',
/** 强描边 · 输入框聚焦前 / 选中卡片描边 */
borderStrong: '#D4DAE3',
/** 禁用文字 · 禁用按钮 / 只读内容 */
textDisabled: '#B0B8C1',
/** 占位符文字 · placeholder / 辅助提示 */
textHint: '#8E99A4',
/** 次要文字(白底 5.1:1 过 AA) */
textSecondary: '#6B7A7B',
/** 正文深色 · 标题 / 正文主阅读文字(13.1:1 AAA) */
textPrimary: '#2C3E50',
/** 深色底上的反白文字 */
textInverse: '#FFFFFF',
shadow: '#000000',
/** 焦点环色 · 键盘聚焦指示器(WCAG 2.4.11) */
focusRing: '#1F6FDB',
/** 弹层遮罩 · 半透明黑(Modal / 底部弹层背景) */
scrim: 'rgba(0, 0, 0, 0.35)',
/** 轻遮罩 · 极淡半透明黑(操作进行中的整屏蒙层,不夺视线) */
scrimLight: 'rgba(0, 0, 0, 0.04)',
/* ---- 品牌色 / 深色底上的半透明白(图形与描边用) ---- */
/**
* 这一组此前没有 token,值以字面量散落在 5 个页面里。之所以必须收进来:
* eslint 的硬编码颜色规则只匹配纯 hex,**拦不住 `rgba(...)`** ——
* 同一个遮罩角色因此在不同页面漂移成了不同的透明度(详见 4.14)。
* 下面三个值是按**角色**分的,不是按强弱随手取的:
* · surface —— 品牌色底上的图标容器 / 卡片托底(48~64px 圆形图标底)
* · weak —— 同一角色但更轻的一档(36px 导航类小圆钮)
* · line —— 品牌色底上的分隔线
* · track —— 品牌色底上的进度轨道(比 surface 重一档,否则细轨道看不出来)
*/
onBrandSurface: 'rgba(255, 255, 255, 0.2)',
onBrandSurfaceWeak: 'rgba(255, 255, 255, 0.15)',
onBrandLine: 'rgba(255, 255, 255, 0.2)',
onBrandTrack: 'rgba(255, 255, 255, 0.35)',
} as const;
/**
* 简历模板的强调色(模板选择卡片与导出 PDF 的页眉/标题线共用同一份)。
*
* 模板色是导出物里的**印刷色**,不随亮/暗模式变化,因此固定取亮色家族。
* 放在 theme 而不是页面里:模板卡片与 PDF 两处都要用,各写一份必然分叉,
* 而色值是本文件独占的职责。
*/
export const templateAccentColors: Record<string, string> = {
primary: colors.primary,
info: colors.info,
success: colors.success,
};
/**
* 语义色名 → 色值。数据层只下发**色名**(如 `stat.color: 'success'`),页面按名取色。
*
* 原先这份映射在 enterprise-overview / interview-score / interview **三个页面各写一份**,
* 取值相同 —— 但「success 是哪个绿」本该只有一处说了算。放在 theme 的理由与
* templateAccentColors 相同:色值是本文件独占的职责。
*
* 键保持 `string`(不收紧成字面量联合)是**刻意的**:数据层这些字段声明的是 `color: string`
* 而非联合类型(src/types/enterprise.ts 等 6 处),收紧键类型会让页面按 `string` 取值直接编译不过。
* 代价是数据层写错色名时静默落到调用方的 `?? colors.primary` 兜底 —— 要根治得先把
* 数据层的 `color: string` 收紧成联合类型(涉及 6 个类型字段与全部 mock 数据),属另一批事。
*/
export const semanticColorTokens: Record<string, string> = {
primary: colors.primary,
success: colors.success,
action: colors.action,
info: colors.info,
danger: colors.danger,
};
/**
* 岗位 / 能力评级 → 文字颜色。
*
* AGENTS.md 第九节已明文规定「正向评级(如「优秀」)文字使用 primaryDark」,而这段映射原先在
* growth / jobs / ability / assessment **四个页面各写一份**(三处叫 LEVEL_TEXT_COLOR、
* 一处叫 LEVEL_COLOR,取值完全相同)—— 评级该用什么蓝,本该只有一处定义。
*
* 键写成字面量联合而不是 `Record<string, string>`:这里取值方拿到的就是 `JobLevel`,
* 能让新增等级时编译报错。写成字面量而非 import JobLevel,是为保持本文件零 import。
*
* @remark `DIMENSION_LEVEL`(app/(tabs)/profile.tsx)**不在**本次收敛范围内:它键的是
* `DimensionScore['level']`(只有两档),且「良好」用的是 textSecondary 而非这里的 primary。
* 两处对「良好」的取色确实不同,属需要产品定夺的配色分歧,已记入《项目开发状态.md》4.16。
*/
export const jobLevelTextColors: Record<'excellent' | 'good' | 'medium', string> = {
excellent: colors.primaryDark,
good: colors.primary,
medium: colors.textSecondary,
};
/**
* 中文评级 → 文字颜色。**由 jobLevelTextColors 按引用派生,不重复写字面量。**
*
* 存在这一份是因为键的**域**不同而非取值不同:数据层的 `DimensionScore.level` 是
* `JobLevel` 联合类型,而 `EvaluationDimension.level` / `OverallScore.level` 字段声明为
* `string` 且直接下发中文评级('优秀')。hr-evaluation 按中文评级取色,无法直接用上面那张表。
*
* 派生而非并列复写:如果哪天「良好」要换色,改 jobLevelTextColors 一处即可,
* 这张表自动跟随 —— 两张表写着同一个色值才是分叉的起点。
*/
export const levelLabelTextColors: Record<string, string> = {
优秀: jobLevelTextColors.excellent,
良好: jobLevelTextColors.good,
中等: jobLevelTextColors.medium,
};
/** 霍兰德六型 → 强调色(介绍页 / 答题页 / 结果页三处原先各写一份,取值相同) */
export const hollandTypeColors: Record<'R' | 'I' | 'A' | 'S' | 'E' | 'C', string> = {
R: colors.primary,
I: colors.info,
A: colors.action,
S: colors.success,
E: colors.compareOrange,
C: colors.textHint,
};
/**
* 文章缩略图色系 → 底色 / 图标色。
*
* 成长页原先写成 `THUMBNAIL_STYLE = { violet: { bg, color } }`,文章详情页则拆成
* `THUMBNAIL_BG` + `THUMBNAIL_COLOR` 两份平行的 `Record` —— 同一组色值、两种写法。
*/
export const articleThumbnailColors: Record<
'violet' | 'orange' | 'green',
{ bg: string; color: string }
> = {
violet: { bg: colors.infoLight, color: colors.info },
orange: { bg: colors.actionLight, color: colors.action },
green: { bg: colors.successLight, color: colors.success },
};
/* ===================== 暗色模式(亮 → 暗映射) ===================== */
export const darkColors = {
/* ---- 主色家族(蓝 · 暗模式提亮) ---- */
primary: '#5399FD',
primaryLight: '#0F1D33',
primaryBorder: '#1E3A5F',
primaryBright: '#7AB0FF',
primaryDark: '#7AB0FF',
primaryText: '#A8CBFF',
primaryBtn: '#1F6FDB',
/* ---- 行动色家族(珊瑚橙 · 暗模式更亮) ---- */
action: '#FF8273',
actionLight: '#2E1A18',
actionBorder: '#5A2E28',
actionBtn: '#F2796B',
actionText: '#FF9C90',
actionDeep: '#FFA396',
/* ---- 警示色(红) ---- */
danger: '#FF6259',
dangerLight: '#2E1A1A',
dangerBorder: '#5A2A28',
dangerBtn: '#FF6259',
dangerText: '#FF857E',
/* ---- 成功色(暖绿) ---- */
success: '#34D399',
successLight: '#12291F',
successBorder: '#1F4A35',
successBtn: '#34D399',
successText: '#5BE0A9',
/* ---- 信息色(紫 · 暗模式更亮) ---- */
info: '#8F70FF',
infoLight: '#1E1A33',
infoBorder: '#3A2E66',
infoBtn: '#7C5CFC',
infoText: '#AA93FF',
/* ---- 对比色板(图表系列色不随明暗变化,保持原值) ---- */
compareRed: '#FF6B6B',
compareOrange: '#FFB347',
/* ---- 中性色家族 ---- */
white: '#FFFFFF',
surface: '#161B22',
pageBg: '#0E1116',
borderLight: '#212830',
border: '#2A313C',
borderStrong: '#3A4350',
textDisabled: '#4A5561',
textHint: '#6B7785',
textSecondary: '#91A0B3',
textPrimary: '#E6EDF3',
textInverse: '#0E1116',
shadow: '#000000',
focusRing: '#5399FD',
/** 弹层遮罩(暗模式更重,保证白色弹层可读) */
scrim: 'rgba(0, 0, 0, 0.5)',
/** 轻遮罩(暗模式) */
scrimLight: 'rgba(0, 0, 0, 0.06)',
} as const;
/* ===================== 渐变(组合色 · 独立于单色令牌) ===================== */
/**
* 页面头部渐变 · 蓝 → 青(蒂芙尼) → 白,自左上向右下
* 参考 BOSS 直聘等主流招聘 App 的「蓝绿白」清新头部
* 标题/图标统一使用 textInverse(白),落在顶部蓝色深色区,对比度充足
*/
export const headerGradient = ['#2C83FD', '#3FA7E8', '#4FD1C5', '#A8E6DF', '#FFFFFF'] as const;
/**
* 蓝绿青三段渐变(卡片/小面积强调)· 不含白色,保证白字可读
*/
export const headerGradientShort = ['#2C83FD', '#3FA7E8', '#4FD1C5'] as const;
/**
* 白字头部渐变(我的页)· 蓝 → 青,饱和不落到白
* 头部含个人资料卡片(头像/昵称/简介),白字需始终落在深色区,末端不加入白色
*/
export const headerGradientSolid = ['#2C83FD', '#3FA7E8', '#4FD1C5'] as const;
/**
* 引导页满屏底色 · 主色家族由浅到深(primary → primaryBtn → primaryDark)。
*
* 为什么不复用上面三支:它们都收在「蓝 → 青」的青端(#4FD1C5),
* 白字落在青端只有约 1.9:1,连大字号的 3:1 都不到 —— 那三支能用,
* 是因为白字只压在**顶部**深色区(头部标题、我的页资料卡)。
* 引导页是**整页底色**,正文压在中下部,所以整条渐变必须始终深于白字阈值:
* · primary #2C83FD 白字 3.6:1 —— 只放标题(26pt/800,大字只需 3:1)
* · primaryBtn#1F6FDB 白字 4.8:1 —— 正文起点(普通字号需 4.5:1,刚过 AA)
* · primaryDark#175AB0 白字 6.4:1 —— 正文落点,留出余量
* 配合页面里的 locations=[0, 0.34, 1],让 4.8:1 那一档提前到 34% 处,
* 正文(约 55%~70% 高度)实际落在 5.2:1 以上。
*/
export const onboardingGradient = [colors.primary, colors.primaryBtn, colors.primaryDark] as const;