tokens.js 5.39 KB
/**
 * 主题 tokens —— 设计变量的**单一事实来源**(JS 形态)。
 *
 * 颜色 / 间距 / 圆角 / 字号 / 阴影集中于此。`theme.css` 的 `--drama-*` CSS 变量与本对象一一对齐
 * (CSS 形态供组件 `<style>` 引用,JS 形态供调用方在脚本中读取 token 值)。新增品牌主题 = 在
 * `theme.css` 增一组 `[data-theme="..."]` 变量并在 `color.dark` 处补对应 JS 值,组件零改动。
 *
 * 设计语言对齐 `docs/design`:黑白主色、圆角 12–16px、标题 Space Grotesk / 正文 Inter。
 *
 * @typedef {Object} ColorScale
 * @property {string} bg          页面底色
 * @property {string} surface     卡片 / 弹层表面色
 * @property {string} surfaceAlt  次级表面色
 * @property {string} border      描边色
 * @property {string} text        主文本色
 * @property {string} textMuted   次级文本色
 * @property {string} primary     品牌主色
 * @property {string} primaryHover 主色 hover 态
 * @property {string} onPrimary   主色之上的前景色
 * @property {string} danger      危险 / 错误色
 * @property {string} dangerHover 危险色 hover 态
 * @property {string} success     成功色
 * @property {string} warning     警告色
 * @property {string} info        信息色
 * @property {string} overlay     遮罩色(含透明度)
 *
 * @typedef {Object} Tokens
 * @property {{ light: ColorScale, dark: ColorScale, vgStudio: ColorScale, vgStudioLight: ColorScale, vokoCinemaLight: ColorScale }} color  默认明暗与品牌色板
 * @property {Record<'xs'|'sm'|'md'|'lg'|'xl'|'2xl', string>} space   间距刻度
 * @property {Record<'sm'|'md'|'lg', string>} control                表单控件高度刻度(各控件共用一把尺,横排才对得齐)
 * @property {Record<'sm'|'md'|'lg'|'full', string>} radius          圆角刻度
 * @property {{ family:{ heading:string, body:string }, size: Record<'xs'|'sm'|'md'|'lg'|'xl', string>, weight: Record<'normal'|'medium'|'bold', string> }} font 字体
 * @property {Record<'sm'|'md'|'lg', string>} shadow                  阴影刻度
 */

/** @type {ColorScale} */
const lightColor = {
  bg: '#f5f5f8',
  surface: '#ffffff',
  surfaceAlt: '#ececf3',
  border: '#d9d9e3',
  text: '#16161d',
  textMuted: '#5b5b6b',
  primary: '#111111',
  primaryHover: '#333333',
  onPrimary: '#ffffff',
  danger: '#dc2f3c',
  dangerHover: '#c01f2c',
  success: '#1f9d55',
  warning: '#b7791f',
  info: '#2b6cb0',
  overlay: 'rgba(22, 22, 29, 0.45)',
}

/** @type {ColorScale} */
const darkColor = {
  bg: '#0d0d12',
  surface: '#16161f',
  surfaceAlt: '#20202c',
  border: '#2c2c3a',
  text: '#f2f2f7',
  textMuted: '#9a9ab0',
  primary: '#7c5dff',
  primaryHover: '#9277ff',
  onPrimary: '#ffffff',
  danger: '#ff5b66',
  dangerHover: '#ff7681',
  success: '#34d27b',
  warning: '#eab24a',
  info: '#5aa9ff',
  overlay: 'rgba(0, 0, 0, 0.6)',
}

/** @type {ColorScale} */
const vgStudioColor = {
  bg: '#0b0a08',
  surface: '#141210',
  surfaceAlt: '#1b1815',
  border: 'rgba(235, 211, 166, 0.14)',
  text: '#f4ecdd',
  textMuted: '#c2b295',
  primary: '#d3ac63',
  primaryHover: '#e6c583',
  onPrimary: '#1a1409',
  danger: '#b95543',
  dangerHover: '#d06a56',
  success: '#5f9e6a',
  warning: '#c9973f',
  info: '#5b8aa6',
  overlay: 'rgba(6, 5, 4, 0.72)',
}

/**
 * vg-studio 的亮色态:换的是明暗,不是品牌 —— 金色主色压深到能在纸白底上当文字和描边用。
 * @type {ColorScale}
 */
const vgStudioLightColor = {
  bg: '#f6f1e7',
  surface: '#fffdf8',
  surfaceAlt: '#f0e8d9',
  border: 'rgba(90, 66, 24, 0.16)',
  text: '#1f1a12',
  textMuted: '#6b6050',
  primary: '#8a6520',
  primaryHover: '#6f5019',
  onPrimary: '#fff9ec',
  danger: '#a3372a',
  dangerHover: '#8a2c21',
  success: '#2f7a45',
  warning: '#8a6112',
  info: '#2b6382',
  overlay: 'rgba(31, 26, 18, 0.35)',
}

/**
 * VOKO 用户官网的中性影院亮色主题。
 * @type {ColorScale}
 */
const vokoCinemaLightColor = {
  bg: '#ffffff',
  surface: '#ffffff',
  surfaceAlt: '#f5f5f5',
  border: '#e5e5e5',
  text: '#111111',
  textMuted: '#666666',
  primary: '#111111',
  primaryHover: '#333333',
  onPrimary: '#ffffff',
  danger: '#b42318',
  dangerHover: '#8f1c13',
  success: '#257942',
  warning: '#8a6112',
  info: '#245e86',
  overlay: 'rgba(17, 17, 17, 0.42)',
}

/** @type {Tokens} */
export const tokens = Object.freeze({
  color: Object.freeze({
    light: Object.freeze(lightColor),
    dark: Object.freeze(darkColor),
    vgStudio: Object.freeze(vgStudioColor),
    vgStudioLight: Object.freeze(vgStudioLightColor),
    vokoCinemaLight: Object.freeze(vokoCinemaLightColor),
  }),
  space: Object.freeze({
    xs: '4px',
    sm: '8px',
    md: '12px',
    lg: '16px',
    xl: '24px',
    '2xl': '32px',
  }),
  control: Object.freeze({
    sm: '32px',
    md: '40px',
    lg: '48px',
  }),
  radius: Object.freeze({
    sm: '8px',
    md: '12px',
    lg: '16px',
    full: '9999px',
  }),
  font: Object.freeze({
    family: Object.freeze({
      heading: "'Space Grotesk', system-ui, sans-serif",
      body: "'Inter', system-ui, sans-serif",
    }),
    size: Object.freeze({
      xs: '12px',
      sm: '14px',
      md: '16px',
      lg: '20px',
      xl: '28px',
    }),
    weight: Object.freeze({
      normal: '400',
      medium: '500',
      bold: '700',
    }),
  }),
  shadow: Object.freeze({
    sm: '0 1px 2px rgba(0, 0, 0, 0.12)',
    md: '0 4px 16px rgba(0, 0, 0, 0.18)',
    lg: '0 12px 40px rgba(0, 0, 0, 0.28)',
  }),
})