use std::sync::OnceLock; use std::time::Duration; use std::time::Instant; use ratatui::style::Color; use ratatui::style::Modifier; use ratatui::style::Style; use ratatui::text::Span; use crate::color::blend; use crate::terminal_palette::default_fg; use crate::terminal_palette::terminal_palette; const FALLBACK_DARK_GRAY: (u8, u8, u8) = (103, 103, 103); static PROCESS_START: OnceLock = OnceLock::new(); fn elapsed_since_start() -> Duration { let start = PROCESS_START.get_or_init(Instant::now); start.elapsed() } pub(crate) fn shimmer_spans(text: &str) -> Vec> { let chars: Vec = text.chars().collect(); if chars.is_empty() { return Vec::new(); } // Use time-based sweep synchronized to process start. let padding = 10usize; let period = chars.len() + padding * 2; let sweep_seconds = 2.0f32; let pos_f = (elapsed_since_start().as_secs_f32() % sweep_seconds) / sweep_seconds * (period as f32); let pos = pos_f as usize; let has_true_color = supports_color::on_cached(supports_color::Stream::Stdout) .map(|level| level.has_16m) .unwrap_or(false); let band_half_width = 3.0; let mut spans: Vec> = Vec::with_capacity(chars.len()); let default_fg = default_fg(); let palette_dark_gray = terminal_palette().map(|palette| palette[8]); for (i, ch) in chars.iter().enumerate() { let i_pos = i as isize + padding as isize; let pos = pos as isize; let dist = (i_pos - pos).abs() as f32; let t = if dist <= band_half_width { let x = std::f32::consts::PI * (dist / band_half_width); 0.5 * (1.0 + x.cos()) } else { 0.0 }; let style = if has_true_color { let base = palette_dark_gray .or(default_fg) .unwrap_or(FALLBACK_DARK_GRAY); let highlight = t.clamp(0.0, 1.0); let (r, g, b) = blend((255, 255, 255), base, highlight); // Allow custom RGB colors, as the implementation is thoughtfully // adjusting the level of the default foreground color. #[allow(clippy::disallowed_methods)] { Style::default() .fg(Color::Rgb(r, g, b)) .add_modifier(Modifier::BOLD) } } else { color_for_level(t) }; spans.push(Span::styled(ch.to_string(), style)); } spans } fn color_for_level(intensity: f32) -> Style { // Tune fallback styling so the shimmer band reads even without RGB support. if intensity < 0.2 { Style::default().add_modifier(Modifier::DIM) } else if intensity < 0.6 { Style::default() } else { Style::default().add_modifier(Modifier::BOLD) } }