feat: expand campaign and refine puzzle visuals and controls

This commit is contained in:
cabinw
2026-09-09 11:02:45 +08:00
parent 9f8990b579
commit 70b021d4cc
66 changed files with 41108 additions and 10553 deletions
+30 -4
View File
@@ -3,9 +3,9 @@
export const THEME = Object.freeze({
background: '#A8BECF',
board: '#F2EEE5',
boardGrid: '#7C8B94',
track: '#6D808B',
trackHalo: '#DFE3E2',
boardGrid: '#89867D',
track: '#435F78',
trackHalo: '#F2EEE5',
surface: '#F7F6F2',
textPrimary: '#273B50',
textSecondary: '#314A5F',
@@ -38,7 +38,7 @@ export const SURFACES = Object.freeze({
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:1250});
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);}
@@ -52,3 +52,29 @@ export const CONTRAST_PAIRS = [
['方向箭头 / 蓝棋子','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)};
}