Asnip / lib /visual.mjs
teddyoas's picture
Duplicate from teddyoas/PHANTOM-v2
20f108c
Raw History Blame Contribute Delete
4.82 kB
// Phantom β€” shared visual components
// ANSI colors, ASCII art, banners, prompts, separators
// Neon violet/cyan on dark β€” hacker-in-hoodie aesthetic
// ── ANSI helpers (local β€” no dependency on phantom.mjs) ──
function fg(name) {
const colors = { black:30, red:31, green:32, yellow:33, blue:34, magenta:35, cyan:36, white:37, dim:2 };
const n = colors[name] ?? 37;
return `\x1b[${n}m`;
}
const _R = "\x1b[0m";
const _B = "\x1b[1m";
const _D = "\x1b[2m";
// ── Hooded figure logo (cyberpunk neon) ─────────────────
export function renderLogo(opts = {}) {
const {
mode = "", // "non-interactive", "v0.2.0"
tools = 0,
wide = false,
} = opts;
const M = fg("magenta");
const C = fg("cyan");
const G = fg("green");
const Y = fg("yellow");
const dim = fg("dim");
const _r = _R;
const b = _B;
const d = _D;
// Shadow figlet logo β€” no box-drawing borders, clean
const lines = [
` ${M}${b} _ \\ | | \\ \\ |__ __| _ \\ \\ |${_r}`,
` ${M}${b} | | | | _ \\ \\ | | | | |\\/ |${_r}`,
` ${M}${b} ___/ ___ | ___ \\ |\\ | | | | | |${_r}`,
` ${M}${b} _| _| _|_/ _\\_| \\_| _| \\___/ _| _|${_r}`,
` ${C}${b}P${_r} ${M}${b}H${_r} ${Y}${b}A${_r} ${C}${b}N${_r} ${M}${b}T${_r} ${Y}${b}O${_r} ${C}${b}M${_r}`,
];
if (tools) lines.push(` ${dim}${tools} tools${_r}`);
return lines.join("\n");
}
// ── Simple banner (small terminals / DesktopUI) ──
export function renderBanner(text = "PHANTOM", subtitle = "") {
const C = fg("cyan");
const dim = fg("dim");
const _r = _R;
const b = _B;
return `${C}${b}${text}${_r}${subtitle ? ` ${dim}${subtitle}${_r}` : ""}`;
}
// ── Prompt symbols ────────────────────────────────
export const prompt = {
ghost: `${fg("green")}πŸ‘»${_R} `,
bolt: `${fg("cyan")}⚑${_R} `,
hash: `${fg("magenta")}#${_R} `,
arrow: `${fg("green")}β†’${_R} `,
dim: `${fg("dim")}β§©${_R} `,
};
// ── Separator line ──────────────────────────────────
export function separator(char = "─", len = 50) {
return `${fg("dim")}${char.repeat(len)}${_R}`;
}
// ── Status icons ────────────────────────────────────
// ── Animated spinner ──────────────────────────────────
// Minimal frame-based spinner; no external deps.
export function createSpinner(startTime, opts = {}) {
const frames = ['β ‹', 'β ™', 'β Ή', 'β Έ', 'β Ό', 'β ΄', 'β ¦', 'β §', 'β ‡', '⠏'];
let i = 0, timer = null;
const write = opts.write || ((s) => process.stdout.write(s));
const fmt = (ts) => {
const s = Math.floor((Date.now() - ts) / 1000);
return s > 3600 ? `${Math.floor(s/3600)}h ${Math.floor((s%3600)/60)}m`
: s > 60 ? `${Math.floor(s/60)}m ${s%60}s`
: `${s}s`;
};
return {
start(msg) {
if (timer) return;
timer = setInterval(() => {
const f = frames[i = (i + 1) % frames.length];
write(`\r${fg("cyan")}${_B}${f}${_R} [${fmt(startTime)}] ${msg}`);
}, 80);
},
stop() {
if (!timer) return;
clearInterval(timer); timer = null;
write('\r\x1b[K');
},
update(msg) {
const f = timer ? frames[i] : frames[0];
write(`\r${fg("cyan")}${_B}${f}${_R} [${fmt(startTime)}] ${msg}`);
}
};
}
export const icons = {
ok: `${fg("green")}βœ“${_R}`,
err: `${fg("red")}βœ•${_R}`,
warn: `${fg("yellow")}⚠${_R}`,
info: `${fg("cyan")}β—ˆ${_R}`,
bullet: `${fg("magenta")}β—†${_R}`,
arrow: `${fg("green")}β†’${_R}`,
};
// ── Chat border β€” wraps multi-line text in a bordered box ──
// width = total width (defaults to terminal cols - 2)
// title = optional top-left label
// color = border color function (default dim)
export function chatBorder(text, opts = {}) {
const { width, title, color = fg("dim") } = opts;
const cols = width || (process.stdout.columns || 80) - 4;
const c = color;
const _r = _R;
const lines = text.split("\n");
const out = [];
const top = title
? `${c}╔═ ${_B}${title}${_R}${c} ${"═".repeat(Math.max(0, cols - title.length - 4))}β•—${_r}`
: `${c}β•”${"═".repeat(cols + 2)}β•—${_r}`;
out.push(top);
for (const line of lines) {
const wrapped = line.length > cols ? line.substring(0, cols - 1) + "…" : line;
const pad = " ".repeat(Math.max(0, cols - wrapped.length + 2));
out.push(`${c}β•‘${_r} ${wrapped}${pad}${c}β•‘${_r}`);
}
out.push(`${c}β•š${"═".repeat(cols + 2)}╝${_r}`);
return out.join("\n");
}