81 lines
5.1 KiB
JavaScript
81 lines
5.1 KiB
JavaScript
// Source of truth for the palette. Run npm run design after changing values.
|
||
// See /Design.md for semantic pairings, ratios and component rules.
|
||
export const THEME = Object.freeze({
|
||
background: '#A8BECF',
|
||
board: '#F2EEE5',
|
||
boardGrid: '#89867D',
|
||
track: '#435F78',
|
||
trackHalo: '#F2EEE5',
|
||
surface: '#F7F6F2',
|
||
textPrimary: '#273B50',
|
||
textSecondary: '#314A5F',
|
||
primary: '#6652B8',
|
||
onPrimary: '#FFFFFF',
|
||
secondary: '#E4EAF0',
|
||
utility: '#52697E',
|
||
onUtility: '#FFFFFF',
|
||
selection: '#DFD9F3',
|
||
onSelection: '#3F327A',
|
||
disabled: '#CBD4DD',
|
||
onDisabled: '#526579',
|
||
input: '#FFFFFF',
|
||
reward: '#B9760C',
|
||
warning: '#A44825',
|
||
warningSurface: '#F9E3D2',
|
||
scrim: '#273B507A',
|
||
focus: '#3F327A',
|
||
// Approximate center pixels of the accepted, unchanged generated token art.
|
||
tileBlue: '#2392FD',
|
||
tilePink: '#FC3E80',
|
||
tileYellow: '#FDBC28',
|
||
tilePurple: '#874FFD',
|
||
arrowLight: '#FFFFFF',
|
||
arrowDark: '#273B50',
|
||
});
|
||
export const SURFACES = Object.freeze({
|
||
surface: 'surface', primary: 'primary', secondary: 'secondary', utility: 'utility',
|
||
warning: 'warningSurface', reward: 'reward', input: 'input', selection: 'selection',
|
||
disabled: 'disabled', tileBlue: 'tileBlue', tilePink: 'tilePink', tileYellow: 'tileYellow', tilePurple: 'tilePurple',
|
||
});
|
||
export const TYPOGRAPHY = Object.freeze({level:28,moves:32,dialogTitle:24,body:14,label:12,caption:11});
|
||
export const MOTION = Object.freeze({pressMs:150,modalMs:420,starStartMs:300,starGapMs:190,collisionMs:650,rotationMs:240,exitMsPerUnit:60,exitMinTravelMs:140,exitMaxTravelMs:800,exitEffectMs:180,exitLongBaseMs:550,exitLongCellMs:85,exitLongMaxMs:1700,exitLongStartCells:2,exitLongFullCells:5});
|
||
export const RADII = Object.freeze({control:14,dialog:24,small:8});
|
||
export function luminance(hex){return hex.slice(1,7).match(/../g).map(v=>parseInt(v,16)/255).map(v=>v<=.04045?v/12.92:((v+.055)/1.055)**2.4).reduce((a,v,i)=>a+v*[.2126,.7152,.0722][i],0);}
|
||
export function contrast(a,b){const x=luminance(a),y=luminance(b);return (Math.max(x,y)+.05)/(Math.min(x,y)+.05);}
|
||
export const CONTRAST_PAIRS = [
|
||
['正文 / 背景','textPrimary','background',4.5], ['辅助文字 / 背景','textSecondary','background',4.5],
|
||
['正文 / 弹窗','textPrimary','surface',4.5], ['辅助文字 / 弹窗','textSecondary','surface',4.5],
|
||
['主按钮文字','onPrimary','primary',4.5], ['工具图标','onUtility','utility',3],
|
||
['次按钮文字','textPrimary','secondary',4.5], ['选中标签','onSelection','selection',4.5],
|
||
['阻挡提示文字','warning','warningSurface',4.5], ['分格 / 棋盘','boardGrid','board',3],
|
||
['轨道 / 棋盘','track','board',3], ['轨道 / 浅色外缘','track','trackHalo',3],
|
||
['方向箭头 / 蓝棋子','arrowLight','tileBlue',3], ['方向箭头 / 粉棋子','arrowLight','tilePink',3],
|
||
['方向箭头 / 黄棋子','arrowDark','tileYellow',3], ['方向箭头 / 紫棋子','arrowLight','tilePurple',3],
|
||
];
|
||
|
||
// V2: global pigment library. A level uses only its curated subset.
|
||
export const PIGMENTS = Object.freeze([
|
||
{name:'晴空蓝', color:THEME.tileBlue}, {name:'莓果粉', color:THEME.tilePink},
|
||
{name:'日光黄', color:THEME.tileYellow}, {name:'葡萄紫', color:THEME.tilePurple},
|
||
{name:'湖水青', color:'#008B83'}, {name:'珊瑚橙', color:'#EF704F'},
|
||
{name:'嫩芽绿', color:'#91C95B'}, {name:'鸢尾蓝', color:'#5267CF'},
|
||
{name:'玫瑰红', color:'#C83F66'}, {name:'蜜桃杏', color:'#F5AA83'},
|
||
{name:'冰川青', color:'#58C9D5'}, {name:'兰花紫', color:'#AE64C7'},
|
||
].map(p=>Object.freeze({...p, arrow:contrast(THEME.arrowLight,p.color)>=3?THEME.arrowLight:THEME.arrowDark})));
|
||
export const LEVEL_THEMES = Object.freeze([
|
||
{id:'lagoon',name:'薄荷浅湾',label:'轻松',range:'0–29',background:'#C5E4DF',ink:'#273B50',palette:[0,4,2],description:'冷润的浅湾,配蓝、青与一点日光黄。'},
|
||
{id:'apricot',name:'杏色午后',label:'进阶',range:'30–49',background:'#F1D7B8',ink:'#273B50',palette:[5,7,4,9],description:'温暖杏底,以珊瑚、鸢尾蓝和湖水青平衡。'},
|
||
{id:'lavender',name:'鸢尾花园',label:'烧脑',range:'50–69',background:'#D8D0EB',ink:'#273B50',palette:[3,6,8,10],description:'柔紫空间,用嫩芽绿与冰川青拉开层次。'},
|
||
{id:'rose',name:'玫瑰暮色',label:'挑战',range:'70–100',background:'#E7C3CE',ink:'#273B50',palette:[8,7,2,11],description:'低饱和玫瑰底,深色棋子与日光黄形成节奏。'},
|
||
{id:'midnight',name:'午夜极光',label:'每周挑战',range:'特别主题',background:'#293956',ink:'#F7F6F2',palette:[10,11,2,1],description:'深靛蓝舞台,冰川青、兰花紫和明亮暖色。'},
|
||
].map(t=>Object.freeze({...t,palette:Object.freeze(t.palette)})));
|
||
export function themeForScore(score){return LEVEL_THEMES[score<30?0:score<50?1:score<70?2:3];}
|
||
export function pigmentIndex(color,theme){return theme?theme.palette[color%theme.palette.length]:color;}
|
||
|
||
// Keep each world's anchor colors, then add distinct hues before close relatives.
|
||
export function themeWithColors(theme,count=7){
|
||
const limit=[4,7,9,12].includes(count)?count:7;
|
||
const order=[...new Set([...theme.palette,0,1,2,3,4,5,6,7,8,9,10,11])];
|
||
return {...theme,palette:order.slice(0,limit)};
|
||
}
|