105 lines
5.1 KiB
TypeScript
105 lines
5.1 KiB
TypeScript
import type { ThemeColors } from './theme.js'
|
||
|
||
type Line = [string, string]
|
||
|
||
// ── Rich markup parser ──────────────────────────────────────────────
|
||
// Parses Python Rich markup like "[bold #A3261F]text[/]" into Line[].
|
||
const RICH_RE = /\[(?:bold\s+)?(?:dim\s+)?(#(?:[0-9a-fA-F]{3,8}))\]([\s\S]*?)(\[\/\])/g
|
||
|
||
export function parseRichMarkup(markup: string): Line[] {
|
||
const lines: Line[] = []
|
||
|
||
for (const raw of markup.split('\n')) {
|
||
const trimmed = raw.trimEnd()
|
||
|
||
if (!trimmed) {
|
||
lines.push(['', ' '])
|
||
|
||
continue
|
||
}
|
||
|
||
let lastIndex = 0
|
||
let matched = false
|
||
let m: RegExpExecArray | null
|
||
|
||
RICH_RE.lastIndex = 0
|
||
|
||
while ((m = RICH_RE.exec(trimmed)) !== null) {
|
||
matched = true
|
||
const before = trimmed.slice(lastIndex, m.index)
|
||
|
||
if (before) {
|
||
lines.push(['', before])
|
||
}
|
||
|
||
lines.push([m[1]!, m[2]!])
|
||
lastIndex = m.index + m[0].length
|
||
}
|
||
|
||
if (!matched) {
|
||
lines.push(['', trimmed])
|
||
} else if (lastIndex < trimmed.length) {
|
||
lines.push(['', trimmed.slice(lastIndex)])
|
||
}
|
||
}
|
||
|
||
return lines
|
||
}
|
||
|
||
const LOGO_ART = [
|
||
'██╗ ██╗███████╗██████╗ ███╗ ███╗███████╗███████╗ █████╗ ██████╗ ███████╗███╗ ██╗████████╗',
|
||
'██║ ██║██╔════╝██╔══██╗████╗ ████║██╔════╝██╔════╝ ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝',
|
||
'███████║█████╗ ██████╔╝██╔████╔██║█████╗ ███████╗█████╗███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ',
|
||
'██╔══██║██╔══╝ ██╔══██╗██║╚██╔╝██║██╔══╝ ╚════██║╚════╝██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ',
|
||
'██║ ██║███████╗██║ ██║██║ ╚═╝ ██║███████╗███████║ ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ',
|
||
'╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ '
|
||
]
|
||
|
||
const CADUCEUS_ART = [
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⣀⣀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣇⠸⣿⣿⠇⣸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⢀⣠⣴⣶⠿⠋⣩⡿⣿⡿⠻⣿⡇⢠⡄⢸⣿⠟⢿⣿⢿⣍⠙⠿⣶⣦⣄⡀⠀',
|
||
'⠀⠀⠉⠉⠁⠶⠟⠋⠀⠉⠀⢀⣈⣁⡈⢁⣈⣁⡀⠀⠉⠀⠙⠻⠶⠈⠉⠉⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⡿⠛⢁⡈⠛⢿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠿⣿⣦⣤⣈⠁⢠⣴⣿⠿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠻⢿⣿⣦⡉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢷⣦⣈⠛⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣴⠦⠈⠙⠿⣦⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣤⡈⠁⢤⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⠑⢶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠁⢰⡆⠈⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⠈⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀',
|
||
'⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀'
|
||
]
|
||
|
||
const LOGO_GRADIENT = [0, 0, 1, 1, 2, 2] as const
|
||
const CADUC_GRADIENT = [2, 2, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 3] as const
|
||
|
||
function colorize(art: string[], gradient: readonly number[], c: ThemeColors): Line[] {
|
||
const palette = [c.gold, c.amber, c.bronze, c.dim]
|
||
|
||
return art.map((text, i) => [palette[gradient[i]] ?? c.dim, text])
|
||
}
|
||
|
||
export const LOGO_WIDTH = 98
|
||
export const CADUCEUS_WIDTH = 30
|
||
|
||
export const logo = (c: ThemeColors, customLogo?: string): Line[] =>
|
||
customLogo ? parseRichMarkup(customLogo) : colorize(LOGO_ART, LOGO_GRADIENT, c)
|
||
|
||
export const caduceus = (c: ThemeColors, customHero?: string): Line[] =>
|
||
customHero ? parseRichMarkup(customHero) : colorize(CADUCEUS_ART, CADUC_GRADIENT, c)
|
||
|
||
export function artWidth(lines: Line[]): number {
|
||
let max = 0
|
||
|
||
for (const [, text] of lines) {
|
||
if (text.length > max) {
|
||
max = text.length
|
||
}
|
||
}
|
||
|
||
return max
|
||
}
|