import { isSettingsInitialized, settings } from "../../config/settings";
import type { Theme, ThemeColor } from "./theme";
const CLASSIC_PADDING = 10;
const CLASSIC_SWEEP_MS = 1400;
const CLASSIC_BAND_HALF_WIDTH = 6;
const KITT_CYCLE_MS = 1500;
const KITT_HEAD_HALF = 0.6;
const KITT_TRAIL_LEN = 7;
const TIER_HIGH = 0.65;
const TIER_MID = 0.22;
const FG_RESET = "\x1b[39m";
const BOLD_OPEN = "\x1b[1m";
const BOLD_CLOSE = "\x1b[22m";
type ShimmerTheme = Pick<Theme, "bold" | "fg" | "getFgAnsi">;
type ShimmerMode = "classic" | "kitt" | "disabled";
type ShimmerPaletteTier = ThemeColor | { ansi: string };
function resolveTierAnsi(theme: ShimmerTheme, tier: ShimmerPaletteTier): string {
return typeof tier === "string" ? theme.getFgAnsi(tier) : tier.ansi;
}
export interface ShimmerPalette {
low: ShimmerPaletteTier;
mid: ShimmerPaletteTier;
high: ShimmerPaletteTier;
bold?: boolean;
}
export interface ShimmerSegment {
text: string;
palette?: ShimmerPalette;
}
export const DEFAULT_SHIMMER_PALETTE: ShimmerPalette = {
low: "dim",
mid: "muted",
high: "accent",
bold: true,
};
interface TierSeq {
open: string;
close: string;
}
interface CompiledPalette {
low: TierSeq;
mid: TierSeq;
high: TierSeq;
}
const kCompiledFor = Symbol("shimmer.compiledFor");
const kCompiled = Symbol("shimmer.compiled");
interface PaletteCache {
[kCompiledFor]?: ShimmerTheme;
[kCompiled]?: CompiledPalette;
}
function compile(theme: ShimmerTheme, palette: ShimmerPalette): CompiledPalette {
const p = palette as ShimmerPalette & PaletteCache;
const cached = p[kCompiled];
if (cached && p[kCompiledFor] === theme) return cached;
const lowOpen = resolveTierAnsi(theme, palette.low);
const midOpen = resolveTierAnsi(theme, palette.mid);
const highColorOpen = resolveTierAnsi(theme, palette.high);
const highOpen = palette.bold ? `${BOLD_OPEN}${highColorOpen}` : highColorOpen;
const highClose = palette.bold ? `${BOLD_CLOSE}${FG_RESET}` : FG_RESET;
const out: CompiledPalette = {
low: { open: lowOpen, close: FG_RESET },
mid: { open: midOpen, close: FG_RESET },
high: { open: highOpen, close: highClose },
};
p[kCompiledFor] = theme;
p[kCompiled] = out;
return out;
}
function classicIntensity(time: number, index: number, length: number): number {
const period = length + CLASSIC_PADDING * 2;
const pos = ((time % CLASSIC_SWEEP_MS) / CLASSIC_SWEEP_MS) * period;
const dist = Math.abs(index + CLASSIC_PADDING - pos);
if (dist >= CLASSIC_BAND_HALF_WIDTH) return 0;
return 0.5 * (1 + Math.cos((Math.PI * dist) / CLASSIC_BAND_HALF_WIDTH));
}
* Knight Rider K.I.T.T. scanner: a single bright head ping-pongs across the
* bar with a quadratic-decay trail behind it. No leading glow — LEDs don't
* predict the future.
*/
function kittIntensity(time: number, index: number, length: number): number {
const range = length - 1;
if (range <= 0) return 1;
const phase = (time % KITT_CYCLE_MS) / KITT_CYCLE_MS;
const goingRight = phase < 0.5;
const head = goingRight ? phase * 2 * range : (1 - phase) * 2 * range;
const delta = index - head;
const abs = delta < 0 ? -delta : delta;
if (abs <= KITT_HEAD_HALF) return 1;
const behind = goingRight ? -delta : delta;
if (behind <= KITT_HEAD_HALF) return 0;
const t = (behind - KITT_HEAD_HALF) / KITT_TRAIL_LEN;
if (t >= 1) return 0;
const f = 1 - t;
return f * f;
}
type Tier = "low" | "mid" | "high";
function tierFor(intensity: number): Tier {
if (intensity >= TIER_HIGH) return "high";
if (intensity >= TIER_MID) return "mid";
return "low";
}
function resolveMode(): ShimmerMode {
if (!isSettingsInitialized()) return "classic";
return settings.get("display.shimmer");
}
export function shimmerEnabled(): boolean {
return resolveMode() !== "disabled";
}
* Apply a shimmer sweep across one or more segments, treating them as a
* single continuous string for band positioning. Each segment can supply
* its own palette so the gradient stays in lockstep while the colors
* differ.
*
* Performance shape (per call, dominant cost):
* - One `Date.now()` read.
* - One `compile()` lookup per segment (Symbol-keyed cache slot, hot path
* skipped after first frame).
* - One ANSI open/close pair per **run of same-tier chars**, not per char.
* - No per-char allocations beyond the run buffer.
*/
export function shimmerSegments(segments: readonly ShimmerSegment[], theme: ShimmerTheme): string {
const mode = resolveMode();
let total = 0;
const perSeg: { chars: string[]; palette: ShimmerPalette }[] = [];
for (const seg of segments) {
const chars = Array.from(seg.text);
total += chars.length;
perSeg.push({ chars, palette: seg.palette ?? DEFAULT_SHIMMER_PALETTE });
}
if (total === 0) return "";
if (mode === "disabled") {
let out = "";
for (const { chars, palette } of perSeg) {
const seq = compile(theme, palette).mid;
out += `${seq.open}${chars.join("")}${seq.close}`;
}
return out;
}
const time = Date.now();
const intensityFn = mode === "kitt" ? kittIntensity : classicIntensity;
let out = "";
let index = 0;
for (const { chars, palette } of perSeg) {
const compiled = compile(theme, palette);
let runTier: Tier | null = null;
let runBuf = "";
for (let i = 0; i < chars.length; i++) {
const tier = tierFor(intensityFn(time, index, total));
if (tier !== runTier) {
if (runTier !== null) {
const seq = compiled[runTier];
out += `${seq.open}${runBuf}${seq.close}`;
runBuf = "";
}
runTier = tier;
}
runBuf += chars[i];
index++;
}
if (runTier !== null && runBuf.length > 0) {
const seq = compiled[runTier];
out += `${seq.open}${runBuf}${seq.close}`;
}
}
return out;
}
export function shimmerText(text: string, theme: ShimmerTheme, palette?: ShimmerPalette): string {
return shimmerSegments([{ text, palette }], theme);
}