/** A single color role in the fixed isotype brand palette, or an empty cell. */ export type IsotypeColorKey = 'purple' | 'blue' | 'magenta' | 'yellow' | 'green' | 'red' | 'brown'; export type IsotypeCell = IsotypeColorKey | 'blank'; /** Row-major 4x4 grid (16 cells) — a tuple so every pattern is guaranteed complete at compile time. */ export type IsotypePattern = readonly [ IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, IsotypeCell, ]; /** * Fixed Xertica brand hex values for the `xertica` theme, provided directly * by design. These are intentionally NOT CSS custom properties — an isotype * is a brand identity mark, like a logo, and must not reskin when the * active UI theme changes. */ export const ISOTYPE_COLORS: Record = { purple: '#5A4A96', blue: '#1899AF', magenta: '#C45BAA', yellow: '#FAF338', green: '#2E8B5A', red: '#DE5B48', brown: '#2A2415', }; /** Widen this union as new isotypes join the series. */ export type IsotypePatternId = 'core'; export const ISOTYPE_PATTERNS: Record = { core: [ 'purple', 'blue', 'blue', 'blue', 'magenta', 'blank', 'yellow', 'green', 'magenta', 'yellow', 'yellow', 'green', 'magenta', 'red', 'red', 'brown', ], }; export const ISOTYPE_PATTERN_IDS = Object.keys(ISOTYPE_PATTERNS) as IsotypePatternId[]; /** Deterministic PRNG (mulberry32) — the same seed always produces the same sequence. */ export function mulberry32(seed: number): () => number { let a = seed >>> 0; return () => { a |= 0; a = (a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } /** * Returns a copy of the given pattern with exactly one additional non-blank * cell (chosen deterministically from `seed`) swapped to `'blank'`. Pure — * the same `(patternId, seed)` pair always returns the same result, and a * cell that's already blank in the base pattern is never re-chosen. */ export function getIsotypeVariant(patternId: IsotypePatternId, seed: number): IsotypePattern { const base = ISOTYPE_PATTERNS[patternId]; const candidateIndices = base.reduce( (acc, cell, i) => (cell === 'blank' ? acc : [...acc, i]), [] ); const rng = mulberry32(seed); const chosen = candidateIndices[Math.floor(rng() * candidateIndices.length)]; return base.map((cell, i) => (i === chosen ? 'blank' : cell)) as unknown as IsotypePattern; }