use std::io;
use std::time::Duration;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Terminal;
const BG: Color = Color::Rgb(6, 6, 12);
const AQUA: Color = Color::Rgb(80, 220, 210); const AQUA_BRIGHT: Color = Color::Rgb(140, 255, 245); const AQUA_DIM: Color = Color::Rgb(25, 70, 68);
const VIOLET: Color = Color::Rgb(160, 100, 240); const VIOLET_BRIGHT: Color = Color::Rgb(200, 150, 255); const VIOLET_DIM: Color = Color::Rgb(50, 30, 80);
const BAR_BG: Color = Color::Rgb(12, 14, 22); const TAG: Color = Color::Rgb(100, 110, 130); const VER: Color = Color::Rgb(50, 55, 70);
const DOT: &str = "\u{25CF}";
fn letter_p() -> [&'static str; 5] {
["####", "# #", "####", "# ", "# "]
}
fn letter_h() -> [&'static str; 5] {
["# #", "# #", "####", "# #", "# #"]
}
fn letter_o() -> [&'static str; 5] {
[" ## ", "# #", "# #", "# #", " ## "]
}
fn letter_s() -> [&'static str; 5] {
[" ###", "# ", " ## ", " #", "### "]
}
fn letter_r() -> [&'static str; 5] {
["### ", "# #", "### ", "# #", "# #"]
}
fn render_word() -> Vec<String> {
let letters = [
letter_p(), letter_h(), letter_o(), letter_s(),
letter_p(), letter_h(), letter_o(), letter_r(),
];
let mut lines = vec![String::new(); 5];
for (li, letter) in letters.iter().enumerate() {
for row in 0..5 {
if li > 0 { lines[row].push_str(" "); } for ch in letter[row].chars() {
if ch == '#' {
lines[row].push_str(DOT);
} else {
lines[row].push(' ');
}
}
}
}
lines
}
fn lerp_color(a: Color, b: Color, t: f64) -> Color {
let (ar, ag, ab) = color_rgb(a);
let (br, bg, bb) = color_rgb(b);
Color::Rgb(
(ar as f64 + (br as f64 - ar as f64) * t) as u8,
(ag as f64 + (bg as f64 - ag as f64) * t) as u8,
(ab as f64 + (bb as f64 - ab as f64) * t) as u8,
)
}
fn color_rgb(c: Color) -> (u8, u8, u8) {
match c {
Color::Rgb(r, g, b) => (r, g, b),
_ => (0, 0, 0),
}
}
const TAGLINE: &str = "where the terminal makes music";
const STAGES: &[&str] = &[
"initializing audio engine",
"scanning midi ports",
"loading synthesizers",
"warming up oscillators",
"calibrating filters",
"ready",
];
pub fn show_splash(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> anyhow::Result<()> {
let word_lines = render_word();
let total_steps = 50;
for step in 0..=total_steps {
let progress = step as f64 / total_steps as f64;
let stage_idx = ((progress * (STAGES.len() - 1) as f64) as usize).min(STAGES.len() - 1);
let stage = STAGES[stage_idx];
let shimmer = (step as f64 * 0.3).sin() * 0.5 + 0.5;
terminal.draw(|frame| {
let area = frame.area();
let cx = area.width / 2;
let cy = area.height / 2;
let bg_block = ratatui::widgets::Block::default()
.style(Style::default().bg(BG));
frame.render_widget(bg_block, area);
let art_start_y = cy.saturating_sub(6);
for (i, line) in word_lines.iter().enumerate() {
let y = art_start_y + i as u16;
if y >= area.height { break; }
let chars: Vec<char> = line.chars().collect();
let total_chars = chars.len();
let lit_up = (progress * total_chars as f64) as usize;
let mut spans = Vec::new();
for (ci, ch) in chars.iter().enumerate() {
let color = if *ch == '\u{25CF}' {
if ci < lit_up {
let dot_phase = ((ci as f64 * 0.15) + (step as f64 * 0.25)).sin() * 0.5 + 0.5;
if ci + 3 > lit_up {
lerp_color(AQUA_BRIGHT, VIOLET_BRIGHT, dot_phase)
} else {
lerp_color(AQUA, VIOLET, dot_phase)
}
} else {
lerp_color(AQUA_DIM, VIOLET_DIM, shimmer)
}
} else {
BG
};
spans.push(Span::styled(
ch.to_string(),
Style::default().fg(color).bg(BG),
));
}
let line_w = total_chars as u16;
let x = cx.saturating_sub(line_w / 2);
let r = Rect::new(x, y, line_w.min(area.width.saturating_sub(x)), 1);
frame.render_widget(Paragraph::new(Line::from(spans)), r);
}
let tag_y = art_start_y + 6;
if tag_y < area.height {
let tag_color = lerp_color(TAG, Color::Rgb(120, 130, 160), shimmer * 0.4);
let tag_style = Style::default().fg(tag_color).bg(BG).add_modifier(Modifier::ITALIC);
let tag_w = TAGLINE.len() as u16;
let tag_x = cx.saturating_sub(tag_w / 2);
let r = Rect::new(tag_x, tag_y, tag_w.min(area.width.saturating_sub(tag_x)), 1);
frame.render_widget(
Paragraph::new(Span::styled(TAGLINE, tag_style)),
r,
);
}
let bar_y = art_start_y + 8;
if bar_y < area.height {
let bar_w = 32u16;
let bar_x = cx.saturating_sub(bar_w / 2 + 1);
let filled = (progress * bar_w as f64) as u16;
let mut bar_spans = Vec::new();
let bracket_color = lerp_color(AQUA_DIM, VIOLET_DIM, shimmer);
bar_spans.push(Span::styled("[", Style::default().fg(bracket_color).bg(BG)));
for i in 0..bar_w {
if i < filled {
let bar_phase = ((i as f64 * 0.2) + (step as f64 * 0.3)).sin() * 0.5 + 0.5;
let bar_color = lerp_color(AQUA, VIOLET, bar_phase);
bar_spans.push(Span::styled(
"\u{2588}",
Style::default().fg(bar_color).bg(BAR_BG),
));
} else {
bar_spans.push(Span::styled(
"\u{2500}",
Style::default().fg(Color::Rgb(20, 22, 35)).bg(BAR_BG),
));
}
}
bar_spans.push(Span::styled("]", Style::default().fg(bracket_color).bg(BG)));
let r = Rect::new(bar_x, bar_y, bar_w + 2, 1);
frame.render_widget(Paragraph::new(Line::from(bar_spans)), r);
}
let stage_y = art_start_y + 10;
if stage_y < area.height {
let dots = ".".repeat((step % 4) as usize);
let text = format!("{}{}", stage, dots);
let text_w = text.len() as u16;
let stage_x = cx.saturating_sub(text_w / 2);
let stage_color = lerp_color(AQUA_DIM, VIOLET_DIM, shimmer);
let r = Rect::new(stage_x, stage_y, text_w.min(area.width.saturating_sub(stage_x)), 1);
frame.render_widget(
Paragraph::new(Span::styled(text, Style::default().fg(stage_color).bg(BG))),
r,
);
}
let ver = format!("v{}", env!("CARGO_PKG_VERSION"));
let ver_x = area.width.saturating_sub(ver.len() as u16 + 1);
let ver_y = area.height.saturating_sub(1);
frame.render_widget(
Paragraph::new(Span::styled(&ver, Style::default().fg(VER).bg(BG))),
Rect::new(ver_x, ver_y, ver.len() as u16, 1),
);
})?;
std::thread::sleep(Duration::from_millis(65));
}
for step in 0..15 {
let shimmer = (step as f64 * 0.3 + 15.0).sin() * 0.5 + 0.5;
terminal.draw(|frame| {
let area = frame.area();
let cx = area.width / 2;
let cy = area.height / 2;
let bg_block = ratatui::widgets::Block::default()
.style(Style::default().bg(BG));
frame.render_widget(bg_block, area);
let art_start_y = cy.saturating_sub(6);
for (i, line) in word_lines.iter().enumerate() {
let y = art_start_y + i as u16;
if y >= area.height { break; }
let chars: Vec<char> = line.chars().collect();
let total_chars = chars.len();
let mut spans = Vec::new();
for (ci, ch) in chars.iter().enumerate() {
let color = if *ch == '\u{25CF}' {
let dot_phase = ((ci as f64 * 0.15) + ((50 + step) as f64 * 0.25)).sin() * 0.5 + 0.5;
lerp_color(AQUA, VIOLET, dot_phase)
} else {
BG
};
spans.push(Span::styled(ch.to_string(), Style::default().fg(color).bg(BG)));
}
let line_w = total_chars as u16;
let x = cx.saturating_sub(line_w / 2);
let r = Rect::new(x, y, line_w.min(area.width.saturating_sub(x)), 1);
frame.render_widget(Paragraph::new(Line::from(spans)), r);
}
let tag_y = art_start_y + 6;
if tag_y < area.height {
let tag_color = lerp_color(TAG, Color::Rgb(120, 130, 160), shimmer * 0.4);
let tag_w = TAGLINE.len() as u16;
let tag_x = cx.saturating_sub(tag_w / 2);
let r = Rect::new(tag_x, tag_y, tag_w.min(area.width.saturating_sub(tag_x)), 1);
frame.render_widget(
Paragraph::new(Span::styled(TAGLINE, Style::default().fg(tag_color).bg(BG).add_modifier(Modifier::ITALIC))),
r,
);
}
let bar_y = art_start_y + 8;
if bar_y < area.height {
let bar_w = 32u16;
let bar_x = cx.saturating_sub(bar_w / 2 + 1);
let bracket_color = lerp_color(AQUA_DIM, VIOLET_DIM, shimmer);
let mut bar_spans = Vec::new();
bar_spans.push(Span::styled("[", Style::default().fg(bracket_color).bg(BG)));
for i in 0..bar_w {
let bar_phase = ((i as f64 * 0.2) + ((50 + step) as f64 * 0.3)).sin() * 0.5 + 0.5;
let bar_color = lerp_color(AQUA, VIOLET, bar_phase);
bar_spans.push(Span::styled("\u{2588}", Style::default().fg(bar_color).bg(BAR_BG)));
}
bar_spans.push(Span::styled("]", Style::default().fg(bracket_color).bg(BG)));
let r = Rect::new(bar_x, bar_y, bar_w + 2, 1);
frame.render_widget(Paragraph::new(Line::from(bar_spans)), r);
}
let stage_y = art_start_y + 10;
if stage_y < area.height {
let ready_color = lerp_color(AQUA, VIOLET, shimmer);
let text_w = 5u16; let stage_x = cx.saturating_sub(text_w / 2);
let r = Rect::new(stage_x, stage_y, text_w, 1);
frame.render_widget(
Paragraph::new(Span::styled("ready", Style::default().fg(ready_color).bg(BG))),
r,
);
}
let ver = format!("v{}", env!("CARGO_PKG_VERSION"));
let ver_x = area.width.saturating_sub(ver.len() as u16 + 1);
let ver_y = area.height.saturating_sub(1);
frame.render_widget(
Paragraph::new(Span::styled(&ver, Style::default().fg(VER).bg(BG))),
Rect::new(ver_x, ver_y, ver.len() as u16, 1),
);
})?;
std::thread::sleep(Duration::from_millis(80));
}
Ok(())
}