Files
aiturk-hermes-ide/ui-tui/src/lib/starmapPalette.ts
T

148 lines
3.8 KiB
TypeScript

// Star Map palette — ported from apps/desktop/src/app/starmap/color.ts so the
// TUI overlay derives the same memory ink (complement of the theme primary) and
// the same age fade (rgba(ink, alpha) over the background) as the desktop panel.
interface Rgb {
b: number
g: number
r: number
}
function hexToRgb(hex: string): Rgb {
let s = hex.trim().replace(/^#/, '')
if (s.length === 3) {
s = s
.split('')
.map(c => c + c)
.join('')
}
const n = parseInt(s, 16)
if (Number.isNaN(n) || s.length < 6) {
return { b: 0, g: 215, r: 255 }
}
return { b: n & 255, g: (n >> 8) & 255, r: (n >> 16) & 255 }
}
function rgbToHex(c: Rgb): string {
const h = (v: number) =>
Math.max(0, Math.min(255, Math.round(v)))
.toString(16)
.padStart(2, '0')
return `#${h(c.r)}${h(c.g)}${h(c.b)}`
}
function mix(a: Rgb, b: Rgb, t: number): Rgb {
const p = Math.max(0, Math.min(1, t))
return { b: a.b + (b.b - a.b) * p, g: a.g + (b.g - a.g) * p, r: a.r + (b.r - a.r) * p }
}
function luminance(c: Rgb): number {
return (0.2126 * c.r + 0.7152 * c.g + 0.114 * c.b) / 255
}
function rgbToHsl(c: Rgb): [number, number, number] {
const r = c.r / 255
const g = c.g / 255
const b = c.b / 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
const l = (max + min) / 2
const d = max - min
if (!d) {
return [0, 0, l]
}
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
const h = (max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4) * 60
return [h, s, l]
}
function hslToRgb(h: number, s: number, l: number): Rgb {
const hue = ((h % 360) + 360) % 360
const c = (1 - Math.abs(2 * l - 1)) * s
const x = c * (1 - Math.abs(((hue / 60) % 2) - 1))
const m = l - c / 2
const [r, g, b] =
hue < 60
? [c, x, 0]
: hue < 120
? [x, c, 0]
: hue < 180
? [0, c, x]
: hue < 240
? [0, x, c]
: hue < 300
? [x, 0, c]
: [c, 0, x]
return { b: (b + m) * 255, g: (g + m) * 255, r: (r + m) * 255 }
}
function complementaryInk(c: Rgb): Rgb {
const [h, s, l] = rgbToHsl(c)
return hslToRgb(h + 165, Math.max(s, 0.5), Math.max(0.5, Math.min(0.7, l)))
}
export interface StarmapPalette {
bg: Rgb
dim: Rgb
label: Rgb
memory: Rgb
skill: Rgb
}
/** Derive the Star Map inks from the theme primary + foreground color. */
export function deriveStarmapPalette(primaryHex: string, fgHex: string): StarmapPalette {
const primary = hexToRgb(primaryHex)
const dark = luminance(hexToRgb(fgHex)) > 0.55
const base: Rgb = dark ? { b: 255, g: 255, r: 255 } : { b: 0, g: 0, r: 0 }
const bg: Rgb = dark ? { b: 12, g: 8, r: 8 } : { b: 250, g: 250, r: 250 }
return {
bg,
dim: mix(base, bg, 0.7),
label: mix(base, bg, 0.35),
// Memories are drillable, so they wear the primary "clickable" ink; skills
// are dead-ends and get the muted complement.
memory: mix(primary, base, dark ? 0.12 : 0.18),
skill: mix(complementaryInk(primary), bg, 0.45)
}
}
/** Fade an explicit hex ink toward the background by alpha (for category bars). */
export function fadeHex(palette: StarmapPalette, hex: string, alpha: number): string {
const base = hexToRgb(hex)
return rgbToHex(alpha >= 0.999 ? base : mix(palette.bg, base, alpha))
}
/** Fade a base ink toward the background by alpha (rgba-over-bg), as a hex. */
export function fadeInk(palette: StarmapPalette, style: string, alpha: number): string | undefined {
const base =
style === 'skill'
? palette.skill
: style === 'memory'
? palette.memory
: style === 'label'
? palette.label
: style === 'dim'
? palette.dim
: null
if (!base) {
return undefined
}
return rgbToHex(alpha >= 0.999 ? base : mix(palette.bg, base, alpha))
}