use ratatui::{
layout::Rect,
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, Borders, Clear, Paragraph},
Frame,
};
use super::super::helpers::{
compute_overlay_size_standard, display_width, truncate_to_width,
truncate_to_width_with_ellipsis, DETAIL_OVERLAY_HEIGHT, OVERLAY_BG_COLOR, OVERLAY_MARGIN,
OVERLAY_MIN_WIDTH,
};
use super::super::theme;
use crate::app::App;
use crate::event::GitEventKind;
use crate::git::FileChangeStatus;
pub(crate) fn render_detail_overlay(frame: &mut Frame, app: &App) {
let Some(event) = app.selected_event() else {
return;
};
let area = frame.area();
let (overlay_x, _, overlay_width, _) = compute_overlay_size_standard(area);
let file_count = app
.detail_diff_cache
.as_ref()
.map(|d| d.files.len())
.unwrap_or(0);
let content_height = if app.detail_diff_cache.is_some() {
(11 + file_count) as u16
} else {
9
};
let overlay_height = content_height
.max(DETAIL_OVERLAY_HEIGHT)
.min(area.height.saturating_sub(OVERLAY_MARGIN));
let overlay_y = (area.height.saturating_sub(overlay_height)) / 2;
let overlay_area = Rect::new(overlay_x, overlay_y, overlay_width, overlay_height);
frame.render_widget(Clear, overlay_area);
let lang = app.language;
let kind_str = match event.kind {
GitEventKind::Commit => lang.event_commit(),
GitEventKind::Merge => lang.event_merge(),
GitEventKind::BranchSwitch => lang.event_branch_switch(),
};
const LABEL_WIDTH: usize = 9;
let label_style = Style::default().fg(theme::SUBTEXT0);
let dim_style = Style::default().fg(theme::SUBTEXT0);
let mut content = vec![
Line::from(vec![
Span::styled(format!("{:>LABEL_WIDTH$}", lang.detail_type()), label_style),
Span::raw(kind_str),
Span::raw(" "),
Span::styled(format!("{:>LABEL_WIDTH$}", lang.detail_hash()), label_style),
Span::styled(event.short_hash.clone(), Style::default().fg(theme::YELLOW)),
]),
Line::from(vec![
Span::styled(
format!("{:>LABEL_WIDTH$}", lang.detail_message()),
label_style,
),
Span::raw(event.message.clone()),
]),
Line::from(vec![
Span::styled(
format!("{:>LABEL_WIDTH$}", lang.detail_author()),
label_style,
),
Span::raw(event.author.clone()),
Span::raw(" "),
Span::styled(format!("{:>LABEL_WIDTH$}", lang.detail_time()), label_style),
Span::raw(event.relative_time()),
]),
];
if let Some(ref diff) = app.detail_diff_cache {
content.push(Line::from(vec![Span::styled(
"─".repeat(overlay_width as usize - 4),
dim_style,
)]));
content.push(Line::from(vec![Span::styled(
format!(
" Changes: {} files, +{} -{}",
diff.stats.files_changed, diff.stats.insertions, diff.stats.deletions
),
Style::default().fg(theme::SAPPHIRE),
)]));
content.push(Line::from(vec![Span::styled(
"─".repeat(overlay_width as usize - 4),
dim_style,
)]));
let visible_file_lines = overlay_height.saturating_sub(10) as usize;
let scroll_offset = app.detail_nav.scroll_offset;
let end_idx = (scroll_offset + visible_file_lines).min(diff.files.len());
for (idx, file) in diff
.files
.iter()
.enumerate()
.skip(scroll_offset)
.take(end_idx - scroll_offset)
{
let is_selected = idx == app.detail_nav.selected_index;
let (status_char, status_color) = match file.status {
FileChangeStatus::Added => ('A', theme::FILE_ADDED),
FileChangeStatus::Modified => ('M', theme::FILE_MODIFIED),
FileChangeStatus::Deleted => ('D', theme::FILE_DELETED),
FileChangeStatus::Renamed => ('R', theme::FILE_RENAMED),
};
let marker = if is_selected { "▶" } else { " " };
let stats_width = 14;
let path_max_width = (overlay_width as usize).saturating_sub(stats_width + 6);
let path_display = truncate_to_width_with_ellipsis(&file.path, path_max_width);
let path_display_width = display_width(&path_display);
let padding = path_max_width.saturating_sub(path_display_width);
let line_style = if is_selected {
Style::default()
.fg(theme::YELLOW)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
content.push(Line::from(vec![
Span::styled(marker, line_style),
Span::styled(
format!(" {} ", status_char),
Style::default().fg(status_color),
),
Span::styled(path_display, line_style),
Span::raw(" ".repeat(padding)),
Span::styled(
format!(" +{:<3} -{:<3}", file.insertions, file.deletions),
dim_style,
),
]));
}
if diff.files.len() > visible_file_lines {
let indicator = format!(
" [{}/{}]",
app.detail_nav.selected_index + 1,
diff.files.len()
);
content.push(Line::from(vec![Span::styled(indicator, dim_style)]));
}
} else {
content.push(Line::from(""));
content.push(Line::from(vec![Span::styled(
lang.loading_file_changes(),
dim_style,
)]));
}
content.push(Line::from(""));
content.push(Line::from(vec![Span::styled(
lang.detail_footer(),
dim_style,
)]));
let paragraph = Paragraph::new(content).block(
Block::default()
.title(app.language.event_detail())
.borders(Borders::ALL)
.border_style(Style::default().fg(theme::LAVENDER))
.style(Style::default().bg(OVERLAY_BG_COLOR)),
);
frame.render_widget(paragraph, overlay_area);
}
pub(crate) fn render_file_history_view_overlay(frame: &mut Frame, app: &App) {
let area = frame.area();
let overlay_width = ((area.width as f32 * 0.8) as u16)
.max(OVERLAY_MIN_WIDTH)
.min(area.width.saturating_sub(OVERLAY_MARGIN * 2));
let overlay_height = (area.height as f32 * 0.8) as u16;
let x = (area.width.saturating_sub(overlay_width)) / 2;
let y = (area.height.saturating_sub(overlay_height)) / 2;
let overlay_area = Rect::new(x, y, overlay_width, overlay_height);
let inner_width = overlay_width.saturating_sub(2) as usize;
frame.render_widget(Clear, overlay_area);
let Some(ref history) = app.file_history_view.cache else {
let paragraph = Paragraph::new(app.language.loading_history()).block(
Block::default()
.title(app.language.file_history())
.borders(Borders::ALL)
.border_style(Style::default().fg(theme::LAVENDER))
.style(Style::default().bg(OVERLAY_BG_COLOR)),
);
frame.render_widget(paragraph, overlay_area);
return;
};
let dim_style = Style::default().fg(theme::SUBTEXT0);
let show_author = inner_width >= 50;
let hash_len = if inner_width >= 45 { 8 } else { 6 };
let date_len = if inner_width >= 50 {
10
} else if inner_width >= 35 {
7
} else {
6
};
let visible_lines = overlay_height.saturating_sub(8) as usize;
let mut content = Vec::new();
let file_path = app.file_history_view.path.as_deref().unwrap_or("unknown");
let title_width = inner_width.saturating_sub(16); let file_path_display = truncate_to_width_with_ellipsis(file_path, title_width);
let summary = if inner_width >= 25 {
format!(" Total: {} commits", history.len())
} else {
format!(" {} commits", history.len())
};
content.push(Line::from(vec![Span::styled(
summary,
Style::default().fg(theme::SAPPHIRE),
)]));
content.push(Line::from(""));
let lang = app.language;
if show_author {
content.push(Line::from(vec![
Span::styled(" ", dim_style),
Span::styled(
format!("{:<width$} ", lang.header_hash(), width = hash_len),
dim_style,
),
Span::styled(format!("{:<15}", lang.header_author()), dim_style),
Span::styled(
format!("{:<width$}", lang.header_date(), width = date_len),
dim_style,
),
Span::styled(lang.header_message(), dim_style),
]));
} else {
content.push(Line::from(vec![
Span::styled(" ", dim_style),
Span::styled(
format!("{:<width$} ", lang.header_hash(), width = hash_len),
dim_style,
),
Span::styled(
format!("{:<width$} ", lang.header_date(), width = date_len),
dim_style,
),
Span::styled(lang.header_msg_short(), dim_style),
]));
}
content.push(Line::from(vec![Span::styled(
"─".repeat(inner_width.saturating_sub(2)),
dim_style,
)]));
let scroll_offset = app.file_history_view.nav.scroll_offset;
let end_idx = (scroll_offset + visible_lines).min(history.len());
for (idx, entry) in history
.iter()
.enumerate()
.skip(scroll_offset)
.take(end_idx - scroll_offset)
{
let is_selected = idx == app.file_history_view.nav.selected_index;
let marker = if is_selected { "▶" } else { " " };
let line_style = if is_selected {
Style::default()
.fg(theme::YELLOW)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let relative_time = {
let now = chrono::Local::now();
let diff = now.signed_duration_since(entry.date);
if date_len <= 6 {
if diff.num_hours() < 1 {
format!("{}m", diff.num_minutes().max(1))
} else if diff.num_hours() < 24 {
format!("{}h", diff.num_hours())
} else if diff.num_days() < 100 {
format!("{}d", diff.num_days())
} else {
entry.date.format("%m/%d").to_string()
}
} else if date_len <= 7 {
if diff.num_hours() < 1 {
format!("{}m ago", diff.num_minutes().max(1))
} else if diff.num_hours() < 24 {
format!("{}h ago", diff.num_hours())
} else if diff.num_days() < 30 {
format!("{}d ago", diff.num_days())
} else {
entry.date.format("%m/%d").to_string()
}
} else if diff.num_hours() < 1 {
format!("{}m ago", diff.num_minutes().max(1))
} else if diff.num_hours() < 24 {
format!("{}h ago", diff.num_hours())
} else if diff.num_days() < 30 {
format!("{}d ago", diff.num_days())
} else {
entry.date.format("%Y-%m-%d").to_string()
}
};
let msg_base_offset = if show_author { 45 } else { 20 };
let message_max_width = inner_width.saturating_sub(msg_base_offset);
let message_display = truncate_to_width_with_ellipsis(&entry.message, message_max_width);
let hash_display = truncate_to_width(&entry.hash, hash_len);
if show_author {
let author_display = truncate_to_width_with_ellipsis(&entry.author, 13);
content.push(Line::from(vec![
Span::styled(format!("{} ", marker), line_style),
Span::styled(
format!("{:<width$} ", hash_display, width = hash_len),
line_style,
),
Span::styled(format!("{:<15}", author_display), line_style),
Span::styled(
format!("{:<width$} ", relative_time, width = date_len),
line_style,
),
Span::styled(message_display, line_style),
]));
} else {
content.push(Line::from(vec![
Span::styled(format!("{} ", marker), line_style),
Span::styled(
format!("{:<width$} ", hash_display, width = hash_len),
line_style,
),
Span::styled(
format!("{:<width$} ", relative_time, width = date_len),
line_style,
),
Span::styled(message_display, line_style),
]));
}
}
content.push(Line::from(""));
let scroll_indicator = if history.len() > visible_lines {
format!(
" [{}/{}]",
app.file_history_view.nav.selected_index + 1,
history.len()
)
} else {
String::new()
};
if inner_width >= 40 {
content.push(Line::from(vec![
Span::styled("j/k", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_move()),
Span::styled("Enter", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_show_diff()),
Span::styled("F/Esc", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_close()),
Span::styled(scroll_indicator, Style::default().fg(theme::SUBTEXT0)),
]));
} else {
content.push(Line::from(vec![
Span::styled("j/k Enter F/Esc", Style::default().fg(theme::SUBTEXT0)),
Span::styled(scroll_indicator, Style::default().fg(theme::SUBTEXT0)),
]));
}
let paragraph = Paragraph::new(content).block(
Block::default()
.title(format!(" History: {} ", file_path_display))
.borders(Borders::ALL)
.border_style(Style::default().fg(theme::LAVENDER))
.style(Style::default().bg(OVERLAY_BG_COLOR)),
);
frame.render_widget(paragraph, overlay_area);
}
pub(crate) fn render_blame_view_overlay(frame: &mut Frame, app: &App) {
let area = frame.area();
let overlay_width = ((area.width as f32 * 0.9) as u16)
.max(OVERLAY_MIN_WIDTH)
.min(area.width.saturating_sub(OVERLAY_MARGIN * 2));
let overlay_height = (area.height as f32 * 0.85) as u16;
let x = (area.width.saturating_sub(overlay_width)) / 2;
let y = (area.height.saturating_sub(overlay_height)) / 2;
let overlay_area = Rect::new(x, y, overlay_width, overlay_height);
let inner_width = overlay_width.saturating_sub(2) as usize;
frame.render_widget(Clear, overlay_area);
let Some(ref blame) = app.blame_view.cache else {
let paragraph = Paragraph::new(app.language.loading_blame()).block(
Block::default()
.title(app.language.blame())
.borders(Borders::ALL)
.border_style(Style::default().fg(theme::MAUVE))
.style(Style::default().bg(OVERLAY_BG_COLOR)),
);
frame.render_widget(paragraph, overlay_area);
return;
};
let dim_style = Style::default().fg(theme::SUBTEXT0);
let show_author = inner_width >= 55;
let show_date = inner_width >= 45;
let line_no_width = if inner_width >= 40 { 5 } else { 4 };
let hash_len = if inner_width >= 50 {
8
} else if inner_width >= 35 {
6
} else {
5
};
let visible_lines = overlay_height.saturating_sub(4) as usize;
let mut content = Vec::new();
let file_path = app.blame_view.path.as_deref().unwrap_or("unknown");
let title_width = inner_width.saturating_sub(10);
let file_path_display = truncate_to_width_with_ellipsis(file_path, title_width);
let total_lines = blame.len();
for (i, line) in blame
.iter()
.enumerate()
.skip(app.blame_view.nav.scroll_offset)
.take(visible_lines.saturating_sub(2))
{
let is_selected = i == app.blame_view.nav.selected_index;
let line_style = if is_selected {
Style::default().bg(theme::SURFACE2)
} else {
Style::default()
};
let metadata_width = if show_author && show_date {
line_no_width + 1 + hash_len + 1 + 13 + 10 } else if show_date {
line_no_width + 1 + hash_len + 1 + 8 } else {
line_no_width + 1 + hash_len + 1 };
let content_max_width = inner_width.saturating_sub(metadata_width + 2);
let content_display = truncate_to_width_with_ellipsis(&line.content, content_max_width);
let hash_display = truncate_to_width(&line.hash, hash_len);
if show_author && show_date {
let relative_time = {
let now = chrono::Local::now();
let diff = now.signed_duration_since(line.date);
if diff.num_hours() < 1 {
format!("{}m ago", diff.num_minutes().max(1))
} else if diff.num_hours() < 24 {
format!("{}h ago", diff.num_hours())
} else if diff.num_days() < 30 {
format!("{}d ago", diff.num_days())
} else {
line.date.format("%Y-%m-%d").to_string()
}
};
let author_display = truncate_to_width_with_ellipsis(&line.author, 12);
content.push(Line::from(vec![
Span::styled(
format!("{:>width$} ", line.line_number, width = line_no_width),
dim_style,
),
Span::styled(
format!("{:<width$} ", hash_display, width = hash_len),
Style::default().fg(theme::YELLOW),
),
Span::styled(
format!("{:<13}", author_display),
Style::default().fg(theme::SAPPHIRE),
),
Span::styled(format!("{:<10}", relative_time), dim_style),
Span::styled(content_display, line_style),
]));
} else if show_date {
let relative_time = {
let now = chrono::Local::now();
let diff = now.signed_duration_since(line.date);
if diff.num_hours() < 1 {
format!("{}m", diff.num_minutes().max(1))
} else if diff.num_hours() < 24 {
format!("{}h", diff.num_hours())
} else if diff.num_days() < 100 {
format!("{}d", diff.num_days())
} else {
line.date.format("%m/%d").to_string()
}
};
content.push(Line::from(vec![
Span::styled(
format!("{:>width$} ", line.line_number, width = line_no_width),
dim_style,
),
Span::styled(
format!("{:<width$} ", hash_display, width = hash_len),
Style::default().fg(theme::YELLOW),
),
Span::styled(format!("{:<8}", relative_time), dim_style),
Span::styled(content_display, line_style),
]));
} else {
content.push(Line::from(vec![
Span::styled(
format!("{:>width$} ", line.line_number, width = line_no_width),
dim_style,
),
Span::styled(
format!("{:<width$} ", hash_display, width = hash_len),
Style::default().fg(theme::YELLOW),
),
Span::styled(content_display, line_style),
]));
}
}
content.push(Line::from(""));
let scroll_indicator = if total_lines > visible_lines.saturating_sub(2) {
format!(
" [{}/{}]",
app.blame_view.nav.selected_index + 1,
total_lines
)
} else {
String::new()
};
let lang = app.language;
if inner_width >= 40 {
content.push(Line::from(vec![
Span::styled("j/k", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_scroll()),
Span::styled("Enter", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_show_commit()),
Span::styled("B/Esc", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_close()),
Span::styled(scroll_indicator, Style::default().fg(theme::SUBTEXT0)),
]));
} else {
content.push(Line::from(vec![
Span::styled("j/k Enter B/Esc", Style::default().fg(theme::SUBTEXT0)),
Span::styled(scroll_indicator, Style::default().fg(theme::SUBTEXT0)),
]));
}
let paragraph = Paragraph::new(content).block(
Block::default()
.title(format!(" Blame: {} ", file_path_display))
.borders(Borders::ALL)
.border_style(Style::default().fg(theme::MAUVE))
.style(Style::default().bg(OVERLAY_BG_COLOR)),
);
frame.render_widget(paragraph, overlay_area);
}
pub(crate) fn render_patch_view_overlay(frame: &mut Frame, app: &App) {
let Some(ref patch) = app.patch_view.cache else {
return;
};
let area = frame.area();
let overlay_width = ((area.width as f32 * 0.9) as u16)
.max(OVERLAY_MIN_WIDTH)
.min(area.width.saturating_sub(OVERLAY_MARGIN * 2));
let overlay_height = (area.height as f32 * 0.85) as u16;
let x = (area.width.saturating_sub(overlay_width)) / 2;
let y = (area.height.saturating_sub(overlay_height)) / 2;
let overlay_area = Rect::new(x, y, overlay_width, overlay_height);
let inner_width = overlay_width.saturating_sub(2) as usize;
frame.render_widget(Clear, overlay_area);
let dim_style = Style::default().fg(theme::SUBTEXT0);
let show_old_lineno = inner_width >= 45;
let lineno_width = if show_old_lineno { 4 } else { 3 };
let visible_lines = overlay_height.saturating_sub(4) as usize;
let mut content: Vec<Line> = Vec::new();
let total_lines = patch.lines.len();
let scroll_offset = app.patch_view.scroll_offset;
let end_idx = (scroll_offset + visible_lines.saturating_sub(2)).min(total_lines);
for line in patch
.lines
.iter()
.skip(scroll_offset)
.take(end_idx - scroll_offset)
{
let (style, prefix) = match line.origin {
'+' => (Style::default().fg(theme::GREEN), "+"),
'-' => (Style::default().fg(theme::RED), "-"),
_ => (Style::default().fg(theme::TEXT), " "),
};
let lineno_str = if show_old_lineno {
match (line.old_lineno, line.new_lineno) {
(Some(old), Some(new)) => format!("{:>4} {:>4}", old, new),
(Some(old), None) => format!("{:>4} ", old),
(None, Some(new)) => format!(" {:>4}", new),
(None, None) => " ".to_string(),
}
} else {
match line.new_lineno {
Some(new) => format!("{:>width$}", new, width = lineno_width),
None => match line.old_lineno {
Some(old) => format!("{:>width$}", old, width = lineno_width),
None => " ".repeat(lineno_width),
},
}
};
let metadata_width = if show_old_lineno {
12
} else {
lineno_width + 3
};
let content_max_width = inner_width.saturating_sub(metadata_width);
let content_display = truncate_to_width(&line.content, content_max_width);
content.push(Line::from(vec![
Span::styled(format!("{} ", lineno_str), dim_style),
Span::styled(prefix.to_string(), style),
Span::styled(content_display, style),
]));
}
content.push(Line::from(""));
let scroll_indicator = if total_lines > visible_lines.saturating_sub(2) {
format!(" [{}/{}]", scroll_offset + 1, total_lines)
} else {
String::new()
};
let lang = app.language;
if inner_width >= 35 {
content.push(Line::from(vec![
Span::styled("j/k", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_scroll()),
Span::styled("P/Esc", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_close()),
Span::styled(scroll_indicator, Style::default().fg(theme::SUBTEXT0)),
]));
} else {
content.push(Line::from(vec![
Span::styled("j/k P/Esc", Style::default().fg(theme::SUBTEXT0)),
Span::styled(scroll_indicator, Style::default().fg(theme::SUBTEXT0)),
]));
}
let title_width = inner_width.saturating_sub(10);
let title = format!(
" Patch: {} ",
truncate_to_width_with_ellipsis(&patch.path, title_width)
);
let paragraph = Paragraph::new(content).block(
Block::default()
.title(title)
.borders(Borders::ALL)
.border_style(Style::default().fg(theme::GREEN))
.style(Style::default().bg(OVERLAY_BG_COLOR)),
);
frame.render_widget(paragraph, overlay_area);
}
pub(crate) fn render_stash_view_overlay(frame: &mut Frame, app: &App) {
let Some(ref stashes) = app.stash_view.cache else {
return;
};
let area = frame.area();
let (overlay_x, overlay_y, overlay_width, overlay_height) = compute_overlay_size_standard(area);
let overlay_area = Rect::new(overlay_x, overlay_y, overlay_width, overlay_height);
frame.render_widget(Clear, overlay_area);
let mut content: Vec<Line> = Vec::new();
let lang = app.language;
content.push(Line::from(vec![
Span::styled(lang.stash_list_label(), Style::default().fg(theme::YELLOW)),
Span::styled(
format!("({} stashes)", stashes.len()),
Style::default().fg(theme::SUBTEXT0),
),
]));
content.push(Line::from(""));
if stashes.is_empty() {
content.push(Line::from(Span::styled(
lang.no_stashes(),
Style::default().fg(theme::SUBTEXT0),
)));
} else {
let visible_lines = (overlay_height as usize).saturating_sub(6);
for (i, entry) in stashes
.iter()
.enumerate()
.skip(app.stash_view.nav.scroll_offset)
.take(visible_lines)
{
let is_selected = i == app.stash_view.nav.selected_index;
let line_style = if is_selected {
Style::default()
.bg(theme::SURFACE2)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let marker = if is_selected { "▶" } else { " " };
let stash_label = format!("stash@{{{}}}", entry.index);
let max_message_width = (overlay_width as usize).saturating_sub(stash_label.len() + 6);
let message = truncate_to_width_with_ellipsis(&entry.message, max_message_width);
content.push(Line::from(vec![
Span::styled(format!("{} ", marker), line_style),
Span::styled(
format!("{}: ", stash_label),
Style::default().fg(theme::SAPPHIRE),
),
Span::styled(message, line_style),
]));
}
}
content.push(Line::from(""));
let scroll_indicator = if !stashes.is_empty() {
format!(
" [{}/{}]",
app.stash_view.nav.selected_index + 1,
stashes.len()
)
} else {
String::new()
};
content.push(Line::from(vec![
Span::styled("Space", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_apply()),
Span::styled("p", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_pop()),
Span::styled("d", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_drop()),
Span::styled("z/Esc", Style::default().fg(theme::YELLOW)),
Span::raw(lang.hint_close()),
Span::styled(scroll_indicator, Style::default().fg(theme::SUBTEXT0)),
]));
let paragraph = Paragraph::new(content).block(
Block::default()
.title(app.language.stash_overlay())
.borders(Borders::ALL)
.border_style(Style::default().fg(theme::MAUVE))
.style(Style::default().bg(OVERLAY_BG_COLOR)),
);
frame.render_widget(paragraph, overlay_area);
}