| 1 | import { PROJECT_ICON_COLORS } from "./projectIconColors"; |
| 2 | import type { ProjectIconColor } from "@t3tools/contracts"; |
| 3 | |
| 4 | /** Visual identity tokens for a generated project badge. */ |
| 5 | export interface ProjectIdentity { |
| 6 | readonly monogram: string; |
| 7 | readonly color: ProjectIconColor; |
| 8 | } |
| 9 | |
| 10 | function normalizeProjectName(projectName: string): string { |
| 11 | return projectName.normalize("NFKC").trim(); |
| 12 | } |
| 13 | |
| 14 | function projectMonogram(projectName: string): string { |
| 15 | const words = normalizeProjectName(projectName).match(/[\p{L}\p{N}]+/gu) ?? []; |
| 16 | const firstWord = words[0]; |
| 17 | if (!firstWord) return "PR"; |
| 18 | |
| 19 | const glyphs = Array.from(firstWord); |
| 20 | const first = glyphs[0] ?? "P"; |
| 21 | const second = |
| 22 | glyphs.slice(1).find((glyph) => /\p{N}/u.test(glyph)) ?? |
| 23 | (words.length > 1 ? Array.from(words.at(-1) ?? "")[0] : glyphs.at(-1)) ?? |
| 24 | first; |
| 25 | return Array.from(`${first}${second}`.toUpperCase()).slice(0, 2).join(""); |
| 26 | } |
| 27 | |
| 28 | function projectColor(projectName: string): ProjectIconColor { |
| 29 | const seed = normalizeProjectName(projectName).toLocaleLowerCase("en-US") || "project"; |
| 30 | let index = 0; |
| 31 | for (const glyph of seed) { |
| 32 | index = (index * 31 + (glyph.codePointAt(0) ?? 0)) % PROJECT_ICON_COLORS.length; |
| 33 | } |
| 34 | return PROJECT_ICON_COLORS[index]?.value ?? "blue"; |
| 35 | } |
| 36 | |
| 37 | /** Derives the stable monogram and generated colors used when a project has no icon. */ |
| 38 | export function deriveProjectIdentity(projectName: string): ProjectIdentity { |
| 39 | return { |
| 40 | monogram: projectMonogram(projectName), |
| 41 | color: projectColor(projectName), |
| 42 | }; |
| 43 | } |