/** * 设计令牌(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 = { 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 = { 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`:这里取值方拿到的就是 `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 = { 优秀: 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;