Files
HexTrails/scripts/build-design.mjs
T

21 lines
2.7 KiB
JavaScript

import { writeFileSync, mkdirSync } from 'node:fs';
import { deflateSync } from 'node:zlib';
import { THEME, SURFACES, TYPOGRAPHY, MOTION, RADII, CONTRAST_PAIRS, contrast, PIGMENTS, LEVEL_THEMES } from '../src/design-tokens.js';
// Render a newly authored flat color texture from tokens, with no editing of image artwork.
const names=Object.keys(SURFACES),width=names.length*32,height=32,raw=Buffer.alloc((width*4+1)*height);
for(let y=0;y<height;y++)for(let x=0;x<width;x++){
const c=THEME[SURFACES[names[Math.floor(x/32)]]],v=c.slice(1).match(/../g).map(v=>parseInt(v,16)),o=y*(width*4+1)+1+x*4;
raw[o]=v[0];raw[o+1]=v[1];raw[o+2]=v[2];raw[o+3]=255;
}
function crc32(b){let c=0xffffffff;for(const v of b){c^=v;for(let i=0;i<8;i++)c=(c>>>1)^((c&1)?0xedb88320:0);}return(c^0xffffffff)>>>0;}
function chunk(type,b){const t=Buffer.from(type),n=Buffer.alloc(4),crc=Buffer.alloc(4);n.writeUInt32BE(b.length);crc.writeUInt32BE(crc32(Buffer.concat([t,b])));return Buffer.concat([n,t,b,crc]);}
const ihdr=Buffer.alloc(13);ihdr.writeUInt32BE(width);ihdr.writeUInt32BE(height,4);ihdr[8]=8;ihdr[9]=6;
writeFileSync(new URL('../assets/ui-surfaces.png',import.meta.url),Buffer.concat([Buffer.from([137,80,78,71,13,10,26,10]),chunk('IHDR',ihdr),chunk('IDAT',deflateSync(raw)),chunk('IEND',Buffer.alloc(0))]));
const kebab=s=>s.replace(/[A-Z]/g,c=>'-'+c.toLowerCase());
writeFileSync(new URL('../design/tokens.css',import.meta.url),'/* Generated by npm run design. Edit src/design-tokens.js. */\n:root{\n'+Object.entries(THEME).map(([k,v])=>` --color-${kebab(k)}:${v};`).join('\n')+'\n}\n');
writeFileSync(new URL('../design/tokens.json',import.meta.url),JSON.stringify({theme:THEME,pigments:PIGMENTS,levelThemes:LEVEL_THEMES,surfaces:SURFACES,typography:TYPOGRAPHY,motion:MOTION,radii:RADII},null,2)+'\n');
const report=CONTRAST_PAIRS.map(([label,fg,bg,min])=>({label,foreground:THEME[fg],background:THEME[bg],ratio:Number(contrast(THEME[fg],THEME[bg]).toFixed(3)),minimum:min,pass:contrast(THEME[fg],THEME[bg])>=min}));
report.push(...LEVEL_THEMES.map(t=>({label:`${t.name} / 场景文字`,foreground:t.ink,background:t.background,ratio:Number(contrast(t.ink,t.background).toFixed(3)),minimum:4.5,pass:contrast(t.ink,t.background)>=4.5})),...PIGMENTS.map(p=>({label:`${p.name} / 箭头`,foreground:p.arrow,background:p.color,ratio:Number(contrast(p.arrow,p.color).toFixed(3)),minimum:3,pass:contrast(p.arrow,p.color)>=3})));
writeFileSync(new URL('../design/contrast-report.json',import.meta.url),JSON.stringify(report,null,2)+'\n');
if(report.some(r=>!r.pass)){console.error('Contrast checks failed:',report.filter(r=>!r.pass));process.exitCode=1;}else console.log(`Design tokens built. ${report.length} contrast pairs pass.`);