Skip to main content

max / audiofiles

theme: audiofiles accessors purely intent-specified Renamed the theme accessors (and ThemeColors fields) from the color/position vocabulary to intents across all crates: bg_* -> surface_*, text_* -> content*, accent_red/yellow/green/blue -> danger/warning/success/action (AF uses these semantically — errors, stale warnings, success/contrast states, primary emphasis), accent_purple/cyan -> category_five/six (decorative dots), border_default -> border. parse_theme now reads the semantic intents for the status/action accents (was reading the category ramp). Validates the intent set on the egui medium: it's sufficient with zero additions. Waveform peaks use content on surface-page; classification/piano/ spacing stay correctly app-local. Findings in _private docs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-13 02:17 UTC
Commit: 8bce2dbb222c233c9cef89d91dadbcacaf06d6c9
Parent: 1ec02c9
24 files changed, +428 insertions, -428 deletions
@@ -76,7 +76,7 @@ impl AudioFilesApp {
76 76 ui.label(
77 77 egui::RichText::new("Activate a license below to continue.")
78 78 .small()
79 - .color(theme::text_secondary()),
79 + .color(theme::content_secondary()),
80 80 );
81 81 }
82 82
@@ -87,7 +87,7 @@ impl AudioFilesApp {
87 87 // License key entry
88 88 ui.label(
89 89 egui::RichText::new("Already have a license?")
90 - .color(theme::text_secondary()),
90 + .color(theme::content_secondary()),
91 91 );
92 92 ui.add_space(theme::space::SM);
93 93
@@ -127,7 +127,7 @@ impl AudioFilesApp {
127 127
128 128 if let Some(err) = self.activation_error.clone() {
129 129 ui.add_space(theme::space::MD);
130 - ui.colored_label(theme::accent_red(), err.to_string());
130 + ui.colored_label(theme::danger(), err.to_string());
131 131 ui.add_space(theme::space::SM);
132 132 // Per-class recovery affordance.
133 133 match err {
@@ -739,7 +739,7 @@ impl AudioFilesApp {
739 739 painter.rect_stroke(
740 740 rect,
741 741 4.0,
742 - egui::Stroke::new(2.0, theme::accent_blue()),
742 + egui::Stroke::new(2.0, theme::action()),
743 743 egui::StrokeKind::Inside,
744 744 );
745 745 let label = if hovered_count == 1 {
@@ -750,13 +750,13 @@ impl AudioFilesApp {
750 750 let label_pos = egui::pos2(rect.center().x, rect.top() + 32.0);
751 751 // Background pill keeps the label readable on any theme.
752 752 let bg_rect = egui::Rect::from_center_size(label_pos, egui::vec2(280.0, 36.0));
753 - painter.rect_filled(bg_rect, 8.0, theme::bg_tertiary());
753 + painter.rect_filled(bg_rect, 8.0, theme::surface_sunken());
754 754 painter.text(
755 755 label_pos,
756 756 egui::Align2::CENTER_CENTER,
757 757 &label,
758 758 egui::FontId::proportional(18.0),
759 - theme::accent_blue(),
759 + theme::action(),
760 760 );
761 761 }
762 762 } else {
@@ -781,14 +781,14 @@ impl AudioFilesApp {
781 781 ui.label(
782 782 egui::RichText::new(err)
783 783 .small()
784 - .color(audiofiles_browser::ui::theme::text_muted()),
784 + .color(audiofiles_browser::ui::theme::content_muted()),
785 785 );
786 786 }
787 787 ui.add_space(16.0);
788 788 ui.label(
789 789 egui::RichText::new(format!("Vault location: {}", self.data_dir.display()))
790 790 .small()
791 - .color(audiofiles_browser::ui::theme::text_muted()),
791 + .color(audiofiles_browser::ui::theme::content_muted()),
792 792 );
793 793 ui.add_space(16.0);
794 794
@@ -867,7 +867,7 @@ impl AudioFilesApp {
867 867 architecture. License activation is always user-initiated.",
868 868 )
869 869 .small()
870 - .color(audiofiles_browser::ui::theme::text_muted()),
870 + .color(audiofiles_browser::ui::theme::content_muted()),
871 871 );
872 872 ui.add_space(8.0);
873 873 ui.label(
@@ -876,7 +876,7 @@ impl AudioFilesApp {
876 876 self.config_dir.join("preferences.json").display()
877 877 ))
878 878 .small()
879 - .color(audiofiles_browser::ui::theme::text_muted()),
879 + .color(audiofiles_browser::ui::theme::content_muted()),
880 880 );
881 881 ui.add_space(12.0);
882 882 ui.vertical_centered(|ui| {
@@ -38,7 +38,7 @@ impl AudioFilesApp {
38 38 "Your existing library at {} will be opened.",
39 39 default_path.display()
40 40 ))
41 - .color(theme::text_secondary()),
41 + .color(theme::content_secondary()),
42 42 );
43 43 ui.add_space(theme::space::SECTION);
44 44 } else {
@@ -68,7 +68,7 @@ impl AudioFilesApp {
68 68 widgets::accent_strong(chosen_path.display().to_string())
69 69 } else {
70 70 egui::RichText::new(chosen_path.display().to_string())
71 - .color(theme::text_secondary())
71 + .color(theme::content_secondary())
72 72 };
73 73 ui.label(path_text);
74 74 });
@@ -53,7 +53,7 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
53 53 };
54 54
55 55 let resp = waveform::draw_waveform(ui, waveform_data, playback_pos, 120.0);
56 - // Hover indicator: paint a vertical accent_blue line at the cursor X
56 + // Hover indicator: paint a vertical action line at the cursor X
57 57 // and a time label above it so the user can see where a click-to-seek
58 58 // would land before committing.
59 59 if resp.hovered()
@@ -67,7 +67,7 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
67 67 egui::pos2(pos.x, rect.top()),
68 68 egui::pos2(pos.x, rect.bottom()),
69 69 ],
70 - egui::Stroke::new(1.0, theme::accent_blue()),
70 + egui::Stroke::new(1.0, theme::action()),
71 71 );
72 72 let label = format!(
73 73 "{:.0}:{:02.0}",
@@ -79,7 +79,7 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
79 79 egui::Align2::CENTER_BOTTOM,
80 80 label,
81 81 egui::FontId::proportional(10.0),
82 - theme::text_secondary(),
82 + theme::content_secondary(),
83 83 );
84 84 }
85 85 // Click-to-seek: map the click's X position to a 0.0–1.0 fraction
@@ -120,56 +120,56 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
120 120 .num_columns(2)
121 121 .spacing([8.0, theme::grid_row_spacing()])
122 122 .show(ui, |ui| {
123 - ui.label(egui::RichText::new("Duration").color(theme::text_secondary()));
123 + ui.label(egui::RichText::new("Duration").color(theme::content_secondary()));
124 124 ui.label(widgets::format_duration(analysis.duration));
125 125 ui.end_row();
126 126
127 127 if let Some(bpm) = analysis.bpm {
128 - ui.label(egui::RichText::new("BPM").color(theme::text_secondary()));
128 + ui.label(egui::RichText::new("BPM").color(theme::content_secondary()));
129 129 ui.label(widgets::format_bpm(bpm));
130 130 ui.end_row();
131 131 }
132 132
133 133 if let Some(ref key) = analysis.musical_key {
134 - ui.label(egui::RichText::new("Key").color(theme::text_secondary()));
134 + ui.label(egui::RichText::new("Key").color(theme::content_secondary()));
135 135 ui.label(key);
136 136 ui.end_row();
137 137 }
138 138
139 139 if let Some(ref class) = analysis.classification {
140 - ui.label(egui::RichText::new("Class").color(theme::text_secondary()));
140 + ui.label(egui::RichText::new("Class").color(theme::content_secondary()));
141 141 widgets::classification_badge(ui, class.as_str());
142 142 ui.end_row();
143 143 }
144 144
145 - ui.label(egui::RichText::new("Sample Rate").color(theme::text_secondary()));
145 + ui.label(egui::RichText::new("Sample Rate").color(theme::content_secondary()));
146 146 ui.label(format!("{} Hz", analysis.sample_rate));
147 147 ui.end_row();
148 148
149 - ui.label(egui::RichText::new("Channels").color(theme::text_secondary()));
149 + ui.label(egui::RichText::new("Channels").color(theme::content_secondary()));
150 150 ui.label(format!("{}", analysis.channels));
151 151 ui.end_row();
152 152
153 153 if let Some(peak) = analysis.peak_db {
154 - ui.label(egui::RichText::new("Peak").color(theme::text_secondary()));
154 + ui.label(egui::RichText::new("Peak").color(theme::content_secondary()));
155 155 ui.label(format!("{:.1} dB", peak));
156 156 ui.end_row();
157 157 }
158 158
159 159 if let Some(rms) = analysis.rms_db {
160 - ui.label(egui::RichText::new("RMS").color(theme::text_secondary()));
160 + ui.label(egui::RichText::new("RMS").color(theme::content_secondary()));
161 161 ui.label(format!("{:.1} dB", rms));
162 162 ui.end_row();
163 163 }
164 164
165 165 if let Some(lufs) = analysis.lufs {
166 - ui.label(egui::RichText::new("LUFS").color(theme::text_secondary()));
166 + ui.label(egui::RichText::new("LUFS").color(theme::content_secondary()));
167 167 ui.label(format!("{:.1}", lufs));
168 168 ui.end_row();
169 169 }
170 170
171 171 if let Some(is_loop) = analysis.is_loop {
172 - ui.label(egui::RichText::new("Loop").color(theme::text_secondary()));
172 + ui.label(egui::RichText::new("Loop").color(theme::content_secondary()));
173 173 ui.label(if is_loop { "Yes" } else { "No" });
174 174 ui.end_row();
175 175 }
@@ -184,7 +184,7 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
184 184 .default_open(true)
185 185 .show(ui, |ui| {
186 186 if state.selected_tags.is_empty() {
187 - ui.label(egui::RichText::new("No tags").color(theme::text_muted()));
187 + ui.label(egui::RichText::new("No tags").color(theme::content_muted()));
188 188 } else {
189 189 ui.horizontal_wrapped(|ui| {
190 190 let tags = state.selected_tags.clone();
@@ -258,14 +258,14 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
258 258 ui.label(
259 259 egui::RichText::new(format!("Suggest (from {class_str}):"))
260 260 .small()
261 - .color(theme::text_muted()),
261 + .color(theme::content_muted()),
262 262 );
263 263 for sug in &suggestions {
264 264 if ui
265 265 .small_button(
266 266 egui::RichText::new(format!("+{sug}"))
267 267 .small()
268 - .color(theme::accent_blue()),
268 + .color(theme::action()),
269 269 )
270 270 .on_hover_text(format!("Add tag: {sug}"))
271 271 .clicked()
@@ -289,9 +289,9 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
289 289 let p3 = egui::pos2(icon_rect.min.x + pad, icon_rect.max.y - pad);
290 290 let p4 = egui::pos2(icon_rect.max.x - pad, icon_rect.min.y + pad);
291 291 let stroke_color = if icon_resp.hovered() {
292 - theme::text_secondary()
292 + theme::content_secondary()
293 293 } else {
294 - theme::text_muted()
294 + theme::content_muted()
295 295 };
296 296 let stroke = egui::Stroke::new(1.2, stroke_color);
297 297 let painter = ui.painter();
@@ -325,13 +325,13 @@ pub fn draw_detail(ui: &mut egui::Ui, state: &mut BrowserState) {
325 325 ui.label(
326 326 egui::RichText::new(format!("Muted \"{tag}\" for {class_str}."))
327 327 .small()
328 - .color(theme::text_muted()),
328 + .color(theme::content_muted()),
329 329 );
330 330 if ui
331 331 .link(
332 332 egui::RichText::new("Undo")
333 333 .small()
334 - .color(theme::accent_blue()),
334 + .color(theme::action()),
335 335 )
336 336 .clicked()
337 337 {
@@ -478,7 +478,7 @@ fn draw_multi_summary(ui: &mut egui::Ui, state: &mut BrowserState) {
478 478 .spacing([8.0, theme::grid_row_spacing()])
479 479 .show(ui, |ui| {
480 480 let bpm = summarize(&samples, |n| n.bpm);
481 - ui.label(egui::RichText::new("BPM").color(theme::text_secondary()));
481 + ui.label(egui::RichText::new("BPM").color(theme::content_secondary()));
482 482 ui.label(match bpm {
483 483 Some(Ok(v)) => widgets::format_bpm(v),
484 484 Some(Err(())) => "varies".to_string(),
@@ -487,7 +487,7 @@ fn draw_multi_summary(ui: &mut egui::Ui, state: &mut BrowserState) {
487 487 ui.end_row();
488 488
489 489 let key = summarize(&samples, |n| n.musical_key.clone());
490 - ui.label(egui::RichText::new("Key").color(theme::text_secondary()));
490 + ui.label(egui::RichText::new("Key").color(theme::content_secondary()));
491 491 ui.label(match key {
492 492 Some(Ok(v)) => v,
493 493 Some(Err(())) => "varies".to_string(),
@@ -496,7 +496,7 @@ fn draw_multi_summary(ui: &mut egui::Ui, state: &mut BrowserState) {
496 496 ui.end_row();
497 497
498 498 let class = summarize(&samples, |n| n.classification.clone());
499 - ui.label(egui::RichText::new("Class").color(theme::text_secondary()));
499 + ui.label(egui::RichText::new("Class").color(theme::content_secondary()));
500 500 match class {
501 501 Some(Ok(v)) => widgets::classification_badge(ui, &v),
502 502 Some(Err(())) => {
@@ -509,7 +509,7 @@ fn draw_multi_summary(ui: &mut egui::Ui, state: &mut BrowserState) {
509 509 ui.end_row();
510 510
511 511 let dur = summarize(&samples, |n| n.duration);
512 - ui.label(egui::RichText::new("Duration").color(theme::text_secondary()));
512 + ui.label(egui::RichText::new("Duration").color(theme::content_secondary()));
513 513 ui.label(match dur {
514 514 Some(Ok(v)) => widgets::format_duration(v),
515 515 Some(Err(())) => "varies".to_string(),
@@ -532,7 +532,7 @@ fn draw_multi_summary(ui: &mut egui::Ui, state: &mut BrowserState) {
532 532 }
533 533 }
534 534 if tag_counts.is_empty() {
535 - ui.label(egui::RichText::new("No tags").color(theme::text_muted()));
535 + ui.label(egui::RichText::new("No tags").color(theme::content_muted()));
536 536 } else {
537 537 let mut entries: Vec<_> = tag_counts.into_iter().collect();
538 538 entries.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
@@ -569,7 +569,7 @@ fn draw_multi_summary(ui: &mut egui::Ui, state: &mut BrowserState) {
569 569 .label(
570 570 egui::RichText::new(label)
571 571 .small()
572 - .color(theme::accent_blue()),
572 + .color(theme::action()),
573 573 )
574 574 .on_hover_text(hover);
575 575 resp.context_menu(|ui| {
@@ -215,7 +215,7 @@ fn trim_handle(ui: &mut egui::Ui, rect: egui::Rect, frac: f32, id_salt: &str) ->
215 215 let width = if active { 2.5 } else { 1.0 };
216 216 ui.painter_at(rect).line_segment(
217 217 [egui::pos2(x, rect.top()), egui::pos2(x, rect.bottom())],
218 - egui::Stroke::new(width, theme::accent_yellow()),
218 + egui::Stroke::new(width, theme::warning()),
219 219 );
220 220 (new_frac, resp.dragged())
221 221 }
@@ -238,7 +238,7 @@ fn draw_info_line(ui: &mut egui::Ui, state: &BrowserState) {
238 238 ui.label(egui::RichText::new(&name).strong().size(12.0));
239 239 ui.label(
240 240 egui::RichText::new(format!("{} Hz \u{00B7} {:.3}s{}", sr, duration, peak_suffix))
241 - .color(theme::text_muted())
241 + .color(theme::content_muted())
242 242 .size(11.0),
243 243 );
244 244 });
@@ -262,13 +262,13 @@ fn draw_trim_section(ui: &mut egui::Ui, state: &mut BrowserState) {
262 262 ui.horizontal(|ui| {
263 263 ui.label("Start:");
264 264 ui.add_enabled(!disabled, egui::Slider::new(&mut state.edit.trim_start, 0.0..=1.0).show_value(false));
265 - ui.label(egui::RichText::new(format!("{:.3}s", start_time)).color(theme::text_muted()));
265 + ui.label(egui::RichText::new(format!("{:.3}s", start_time)).color(theme::content_muted()));
266 266 });
267 267
268 268 ui.horizontal(|ui| {
269 269 ui.label("End:");
270 270 ui.add_enabled(!disabled, egui::Slider::new(&mut state.edit.trim_end, 0.0..=1.0).show_value(false));
271 - ui.label(egui::RichText::new(format!("{:.3}s", end_time)).color(theme::text_muted()));
271 + ui.label(egui::RichText::new(format!("{:.3}s", end_time)).color(theme::content_muted()));
272 272 });
273 273
274 274 // Clamp start < end
@@ -295,7 +295,7 @@ fn draw_levels_section(ui: &mut egui::Ui, state: &mut BrowserState) {
295 295 let predicted = peak + state.edit.gain_db;
296 296 if predicted > 0.0 {
297 297 ui.colored_label(
298 - theme::accent_red(),
298 + theme::danger(),
299 299 format!("Peak: {:.1} dB \u{2192} {:.1} dB (clips!)", peak, predicted),
300 300 );
301 301 }
@@ -467,13 +467,13 @@ fn draw_batch_section(ui: &mut egui::Ui, state: &mut BrowserState) {
467 467 ui.label(
468 468 egui::RichText::new(format!("Batch \u{00B7} {} samples", selected_count))
469 469 .small()
470 - .color(theme::text_muted()),
470 + .color(theme::content_muted()),
471 471 );
472 472 });
473 473 ui.label(
474 474 egui::RichText::new("Apply to all selected samples at once")
475 475 .small()
476 - .color(theme::text_muted()),
476 + .color(theme::content_muted()),
477 477 );
478 478 ui.add_space(theme::space::SM);
479 479
@@ -580,7 +580,7 @@ fn draw_result_section(ui: &mut egui::Ui, state: &mut BrowserState) {
580 580 ui.label(
581 581 egui::RichText::new(format!("Last edit: {op_label}"))
582 582 .small()
583 - .color(theme::text_muted()),
583 + .color(theme::content_muted()),
584 584 );
585 585 if ui.small_button("Undo").clicked() {
586 586 state.undo_last_edit();
@@ -109,7 +109,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
109 109 safe_secs / 60.0,
110 110 ))
111 111 .small()
112 - .color(theme::accent_yellow()),
112 + .color(theme::warning()),
113 113 );
114 114 }
115 115 }
@@ -144,7 +144,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
144 144 )
145 145 };
146 146 ui.label(
147 - egui::RichText::new(msg).small().color(theme::accent_red()),
147 + egui::RichText::new(msg).small().color(theme::danger()),
148 148 );
149 149 }
150 150 }
@@ -169,7 +169,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
169 169 estimated_bytes as f64 / 1_073_741_824.0,
170 170 ))
171 171 .small()
172 - .color(theme::accent_yellow()),
172 + .color(theme::warning()),
173 173 );
174 174 }
175 175 }
@@ -201,7 +201,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
201 201 ui.label(
202 202 egui::RichText::new(format!("\u{00B7} {} device profiles available", profile_count))
203 203 .small()
204 - .color(theme::text_muted()),
204 + .color(theme::content_muted()),
205 205 );
206 206 }
207 207 });
@@ -262,27 +262,27 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
262 262 ui.label(
263 263 egui::RichText::new(format!("by {}", profile.manufacturer))
264 264 .small()
265 - .color(theme::text_muted()),
265 + .color(theme::content_muted()),
266 266 );
267 267 if let Some(ref summary) = profile.format_summary {
268 268 ui.label(
269 269 egui::RichText::new(summary)
270 270 .small()
271 - .color(theme::text_muted()),
271 + .color(theme::content_muted()),
272 272 );
273 273 }
274 274 if let Some(ref category) = profile.category {
275 275 ui.label(
276 276 egui::RichText::new(category)
277 277 .small()
278 - .color(theme::text_muted()),
278 + .color(theme::content_muted()),
279 279 );
280 280 }
281 281 if let Some(ref notes) = profile.notes {
282 282 ui.label(
283 283 egui::RichText::new(notes)
284 284 .small()
285 - .color(theme::text_muted()),
285 + .color(theme::content_muted()),
286 286 );
287 287 }
288 288 }
@@ -329,7 +329,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
329 329 Choose Original to preserve them.",
330 330 )
331 331 .small()
332 - .color(theme::accent_yellow()),
332 + .color(theme::warning()),
333 333 );
334 334 }
335 335 }
@@ -438,7 +438,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
438 438 ui.label(
439 439 egui::RichText::new("Tokens:")
440 440 .small()
441 - .color(theme::text_muted()),
441 + .color(theme::content_muted()),
442 442 );
443 443 const TOKENS: &[&str] = &[
444 444 "{name}", "{bpm}", "{key}", "{class}", "{duration}",
@@ -489,7 +489,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
489 489 ui.label(
490 490 egui::RichText::new(format!("Preview: {preview}"))
491 491 .small()
492 - .color(theme::text_muted()),
492 + .color(theme::content_muted()),
493 493 );
494 494 }
495 495 }
@@ -497,7 +497,7 @@ pub fn draw_configure_export(ui: &mut egui::Ui, state: &mut BrowserState) {
497 497 ui.label(
498 498 egui::RichText::new(format!("Pattern: {e}"))
499 499 .small()
500 - .color(theme::accent_yellow()),
500 + .color(theme::warning()),
501 501 );
502 502 }
503 503 }
@@ -559,7 +559,7 @@ pub fn draw_export_progress(ui: &mut egui::Ui, state: &mut BrowserState) {
559 559 ui.label(
560 560 egui::RichText::new(format!("Exporting: {current_name}"))
561 561 .small()
562 - .color(theme::text_muted()),
562 + .color(theme::content_muted()),
563 563 );
564 564 }
565 565
@@ -594,19 +594,19 @@ pub fn draw_export_complete(ui: &mut egui::Ui, state: &mut BrowserState) {
594 594 .max_height(200.0)
595 595 .show(ui, |ui| {
596 596 for (name, err) in errors {
597 - // m-8: name in accent_red + body in text_secondary
597 + // m-8: name in danger + body in content_secondary
598 598 // so errors don't blend with hint text. Mirrors the
599 599 // two-label layout in progress.rs / summary.rs.
600 600 ui.horizontal(|ui| {
601 601 ui.label(
602 602 egui::RichText::new(name)
603 603 .small()
604 - .color(theme::accent_red()),
604 + .color(theme::danger()),
605 605 );
606 606 ui.label(
607 607 egui::RichText::new(err)
608 608 .small()
609 - .color(theme::text_secondary()),
609 + .color(theme::content_secondary()),
610 610 );
611 611 });
612 612 }
@@ -47,12 +47,12 @@ pub fn draw_file_list(
47 47 ui.label(
48 48 egui::RichText::new("Welcome to audiofiles")
49 49 .size(22.0)
50 - .color(theme::text_primary()),
50 + .color(theme::content()),
51 51 );
52 52 ui.add_space(theme::space::SECTION);
53 53 ui.label(
54 54 egui::RichText::new("Three steps to your first sample:")
55 - .color(theme::text_secondary()),
55 + .color(theme::content_secondary()),
56 56 );
57 57 ui.add_space(theme::space::LG);
58 58
@@ -84,17 +84,17 @@ pub fn draw_file_list(
84 84 ui.label(
85 85 egui::RichText::new("Your files stay where they are \u{2014} audiofiles only indexes them.")
86 86 .small()
87 - .color(theme::text_muted()),
87 + .color(theme::content_muted()),
88 88 );
89 89 ui.add_space(theme::space::MD);
90 90 ui.label(
91 91 egui::RichText::new("Press F1 for shortcuts \u{00B7} Right-click samples for options")
92 92 .small()
93 - .color(theme::text_muted()),
93 + .color(theme::content_muted()),
94 94 );
95 95 ui.add_space(theme::space::SM);
96 96 if ui
97 - .link(egui::RichText::new("Dismiss").small().color(theme::text_muted()))
97 + .link(egui::RichText::new("Dismiss").small().color(theme::content_muted()))
98 98 .on_hover_text("Hide this welcome. Re-enable from Settings.")
99 99 .clicked()
100 100 {
@@ -121,7 +121,7 @@ pub fn draw_file_list(
121 121 // Quiet link to bring the welcome screen back if the user dismissed it.
122 122 ui.vertical_centered(|ui| {
123 123 ui.add_space(theme::space::LG);
124 - if ui.link(egui::RichText::new("Show welcome").small().color(theme::text_muted())).clicked() {
124 + if ui.link(egui::RichText::new("Show welcome").small().color(theme::content_muted())).clicked() {
125 125 state.show_welcome();
126 126 }
127 127 });
@@ -271,16 +271,16 @@ pub fn draw_file_list(
271 271 }
272 272 if show_peak_db {
273 273 header.col(|ui| {
274 - ui.label(egui::RichText::new("Peak").color(theme::text_secondary()));
274 + ui.label(egui::RichText::new("Peak").color(theme::content_secondary()));
275 275 });
276 276 }
277 277 if show_tags {
278 278 header.col(|ui| {
279 - ui.label(egui::RichText::new("Tags").color(theme::text_secondary()));
279 + ui.label(egui::RichText::new("Tags").color(theme::content_secondary()));
280 280 });
281 281 }
282 282 header.col(|ui| {
283 - ui.label(egui::RichText::new("Play").color(theme::text_muted()));
283 + ui.label(egui::RichText::new("Play").color(theme::content_muted()));
284 284 });
285 285 })
286 286 .body(|mut body| {
@@ -296,7 +296,7 @@ pub fn draw_file_list(
296 296 let resp = ui.selectable_label(
297 297 selected,
298 298 egui::RichText::new(" Up")
299 - .color(theme::text_secondary()),
299 + .color(theme::content_secondary()),
300 300 );
301 301 if resp.clicked() {
302 302 handle_click(state, 0, ui);
@@ -444,7 +444,7 @@ fn draw_name_column(
444 444 ) {
445 445 // Directories get a trailing "/" (Unix convention). Samples get no prefix
446 446 // — the name is the data, no decorative noise. Cloud-only samples already
447 - // render in `theme::text_muted()` below, which carries the signal without
447 + // render in `theme::content_muted()` below, which carries the signal without
448 448 // emoji glyphs (brand rule).
449 449 let label = match node.node.node_type {
450 450 NodeType::Directory => format!("{}/", node.node.name),
@@ -453,7 +453,7 @@ fn draw_name_column(
453 453 let resp = if node.cloud_only {
454 454 ui.selectable_label(
455 455 selected,
456 - egui::RichText::new(&label).color(theme::text_muted()),
456 + egui::RichText::new(&label).color(theme::content_muted()),
457 457 )
458 458 } else {
459 459 ui.selectable_label(selected, &label)
@@ -576,7 +576,7 @@ fn draw_analysis_columns(
576 576 if let Some(dur) = node.duration {
577 577 ui.label(
578 578 egui::RichText::new(widgets::format_duration(dur))
579 - .color(theme::text_secondary()),
579 + .color(theme::content_secondary()),
580 580 );
581 581 }
582 582 });
@@ -597,7 +597,7 @@ fn draw_analysis_columns(
597 597 if let Some(bpm) = node.bpm {
598 598 ui.label(
599 599 egui::RichText::new(widgets::format_bpm(bpm))
600 - .color(theme::text_secondary()),
600 + .color(theme::content_secondary()),
601 601 );
602 602 }
603 603 });
@@ -609,7 +609,7 @@ fn draw_analysis_columns(
609 609 if let Some(ref key) = node.musical_key {
610 610 ui.label(
611 611 egui::RichText::new(key.as_str())
612 - .color(theme::text_secondary()),
612 + .color(theme::content_secondary()),
613 613 );
614 614 }
615 615 });
@@ -621,7 +621,7 @@ fn draw_analysis_columns(
621 621 if let Some(peak) = node.peak_db {
622 622 ui.label(
623 623 egui::RichText::new(format!("{:.1}", peak))
624 - .color(theme::text_secondary()),
624 + .color(theme::content_secondary()),
625 625 );
626 626 }
627 627 });
@@ -635,7 +635,7 @@ fn draw_analysis_columns(
635 635 ui.label(
636 636 egui::RichText::new(tag_str)
637 637 .small()
638 - .color(theme::text_secondary()),
638 + .color(theme::content_secondary()),
639 639 );
640 640 }
641 641 });
@@ -673,7 +673,7 @@ fn draw_sort_header(
673 673 // surfaces — otherwise the user clicks an inert header and gets silence.
674 674 ui.add_enabled(
675 675 false,
676 - egui::Label::new(egui::RichText::new(text).color(theme::text_muted()))
676 + egui::Label::new(egui::RichText::new(text).color(theme::content_muted()))
677 677 .sense(egui::Sense::click()),
678 678 )
679 679 .on_disabled_hover_text(
@@ -26,7 +26,7 @@ pub fn draw_context_menu(
26 26 if node.cloud_only {
27 27 ui.label(
28 28 egui::RichText::new("Cloud-only sample")
29 - .color(theme::text_muted())
29 + .color(theme::content_muted())
30 30 .italics(),
31 31 );
32 32 // Targeted download for the row under the cursor. Falls back
@@ -22,7 +22,7 @@ fn draw_section_clear(ui: &mut egui::Ui) -> bool {
22 22 let mut clicked = false;
23 23 ui.horizontal(|ui| {
24 24 if ui
25 - .small_button(egui::RichText::new("clear").color(theme::text_muted()))
25 + .small_button(egui::RichText::new("clear").color(theme::content_muted()))
26 26 .on_hover_text("Clear this filter category")
27 27 .clicked()
28 28 {
@@ -147,7 +147,7 @@ pub fn draw_filter_panel(ui: &mut egui::Ui, state: &mut BrowserState) {
147 147 ("Other", &["misc", "noise"]),
148 148 ];
149 149 for (label, classes) in groups {
150 - ui.label(egui::RichText::new(*label).small().color(theme::text_muted()));
150 + ui.label(egui::RichText::new(*label).small().color(theme::content_muted()));
151 151 ui.horizontal_wrapped(|ui| {
152 152 for class in *classes {
153 153 let active = state.search_filter.classifications.contains(&class.to_string());
@@ -201,7 +201,7 @@ pub fn draw_filter_panel(ui: &mut egui::Ui, state: &mut BrowserState) {
201 201 ]),
202 202 ];
203 203 for (label, keys) in key_groups {
204 - ui.label(egui::RichText::new(*label).small().color(theme::text_muted()));
204 + ui.label(egui::RichText::new(*label).small().color(theme::content_muted()));
205 205 ui.horizontal_wrapped(|ui| {
206 206 for key in *keys {
207 207 let note = key.split(' ').next().unwrap_or(key);
@@ -256,7 +256,7 @@ pub fn draw_filter_panel(ui: &mut egui::Ui, state: &mut BrowserState) {
256 256 let tags = state.search_filter.required_tags.clone();
257 257 for tag in &tags {
258 258 ui.horizontal(|ui| {
259 - ui.label(egui::RichText::new(tag).color(theme::accent_blue()));
259 + ui.label(egui::RichText::new(tag).color(theme::action()));
260 260 if ui.small_button("x").on_hover_text("Remove tag filter").clicked() {
261 261 state.search_filter.required_tags.retain(|t| t != tag);
262 262 changed = true;
@@ -292,14 +292,14 @@ pub fn draw_filter_panel(ui: &mut egui::Ui, state: &mut BrowserState) {
292 292 if file_count == 1 { "" } else { "s" }
293 293 ))
294 294 .small()
295 - .color(theme::text_muted()),
295 + .color(theme::content_muted()),
296 296 );
297 297
298 298 if state.search_filter.is_active() {
299 299 ui.add_space(theme::space::MD);
300 300 ui.separator();
301 301 widgets::subsection_label(ui, "Save as Collection");
302 - ui.label(egui::RichText::new("Save current filters as a dynamic collection").small().color(theme::text_muted()));
302 + ui.label(egui::RichText::new("Save current filters as a dynamic collection").small().color(theme::content_muted()));
303 303 ui.add_space(theme::space::SM);
304 304 ui.horizontal(|ui| {
305 305 let auto_name = state.search_filter.describe();
@@ -8,13 +8,13 @@ use crate::state::BrowserState;
8 8 use super::theme;
9 9 use super::widgets;
10 10
11 - /// Status message fade threshold — after this, status renders in `text_muted`.
11 + /// Status message fade threshold — after this, status renders in `content_muted`.
12 12 const STATUS_FADE_AFTER: Duration = Duration::from_secs(5);
13 13 /// Status message hide threshold — after this, the status disappears entirely.
14 14 const STATUS_HIDE_AFTER: Duration = Duration::from_secs(30);
15 15
16 16 /// Heuristic: does this status line report a failure? Error statuses are kept
17 - /// visible (never auto-hidden) and rendered in `accent_red`, since a silently
17 + /// visible (never auto-hidden) and rendered in `danger`, since a silently
18 18 /// expiring error is the one message the user most needs to catch.
19 19 fn is_error_status(s: &str) -> bool {
20 20 let l = s.to_ascii_lowercase();
@@ -33,7 +33,7 @@ fn dot(ui: &mut egui::Ui) {
33 33 ui.label(
34 34 egui::RichText::new("\u{00B7}")
35 35 .small()
36 - .color(theme::text_muted()),
36 + .color(theme::content_muted()),
37 37 );
38 38 }
39 39
@@ -89,7 +89,7 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
89 89 }
90 90
91 91 ui.label(
92 - egui::RichText::new(format!("Playing: {name}")).color(theme::text_primary()),
92 + egui::RichText::new(format!("Playing: {name}")).color(theme::content()),
93 93 );
94 94
95 95 // Visual progress bar
@@ -100,12 +100,12 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
100 100 egui::Sense::click(),
101 101 );
102 102 if ui.is_rect_visible(rect) {
103 - ui.painter().rect_filled(rect, 3.0, theme::bg_primary());
103 + ui.painter().rect_filled(rect, 3.0, theme::surface_page());
104 104 let fill_rect = egui::Rect::from_min_size(
105 105 rect.min,
106 106 egui::vec2(rect.width() * progress, rect.height()),
107 107 );
108 - ui.painter().rect_filled(fill_rect, 3.0, theme::accent_blue());
108 + ui.painter().rect_filled(fill_rect, 3.0, theme::action());
109 109 }
110 110
111 111 // Click-to-seek on progress bar
@@ -128,7 +128,7 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
128 128 total_secs / 60.0,
129 129 total_secs % 60.0,
130 130 ))
131 - .color(theme::text_secondary())
131 + .color(theme::content_secondary())
132 132 .small(),
133 133 );
134 134 }
@@ -146,7 +146,7 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
146 146 dot(ui);
147 147 ui.label(
148 148 egui::RichText::new(format!("{sel_count} selected"))
149 - .color(theme::text_secondary()),
149 + .color(theme::content_secondary()),
150 150 );
151 151 }
152 152
@@ -186,11 +186,11 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
186 186 if is_error || elapsed < STATUS_HIDE_AFTER {
187 187 dot(ui);
188 188 let color = if is_error {
189 - theme::accent_red()
189 + theme::danger()
190 190 } else if elapsed >= STATUS_FADE_AFTER {
191 - theme::text_muted()
191 + theme::content_muted()
192 192 } else {
193 - theme::text_secondary()
193 + theme::content_secondary()
194 194 };
195 195 ui.label(egui::RichText::new(&state.status).color(color));
196 196
@@ -214,7 +214,7 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
214 214 ui.label(
215 215 egui::RichText::new("Right-click for options \u{00B7} F1 for shortcuts")
216 216 .small()
217 - .color(theme::text_muted()),
217 + .color(theme::content_muted()),
218 218 );
219 219 if ui.small_button("Dismiss").on_hover_text("Dismiss").clicked() {
220 220 state.dismiss_first_launch_hint();
@@ -243,7 +243,7 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
243 243 ui.label(
244 244 egui::RichText::new(format!("{sel_count} selected"))
245 245 .small()
246 - .color(theme::text_secondary()),
246 + .color(theme::content_secondary()),
247 247 );
248 248 dot(ui);
249 249 }
@@ -265,13 +265,13 @@ pub fn draw_footer(ui: &mut egui::Ui, ctx: &egui::Context, state: &mut BrowserSt
265 265 ui.label(
266 266 egui::RichText::new("\u{00B7}")
267 267 .small()
268 - .color(theme::text_muted()),
268 + .color(theme::content_muted()),
269 269 );
270 270 }
271 271 ui.label(
272 272 egui::RichText::new(tag)
273 273 .small()
274 - .color(theme::text_muted()),
274 + .color(theme::content_muted()),
275 275 );
276 276 }
277 277 });
@@ -305,13 +305,13 @@ fn draw_analysis_coverage(ui: &mut egui::Ui, state: &BrowserState) {
305 305 ui.label(
306 306 egui::RichText::new(format!("{analyzed}/{total_samples} analyzed"))
307 307 .small()
308 - .color(theme::text_muted()),
308 + .color(theme::content_muted()),
309 309 );
310 310 } else {
311 311 ui.label(
312 312 egui::RichText::new(format!("{total_samples} analyzed"))
313 313 .small()
314 - .color(theme::accent_green()),
314 + .color(theme::success()),
315 315 );
316 316 }
317 317 // m-12: suppress untagged count until analysis has produced output.
@@ -319,7 +319,7 @@ fn draw_analysis_coverage(ui: &mut egui::Ui, state: &BrowserState) {
319 319 ui.label(
320 320 egui::RichText::new(format!("\u{00B7} {untagged} untagged"))
321 321 .small()
322 - .color(theme::text_muted()),
322 + .color(theme::content_muted()),
323 323 );
324 324 }
325 325 }
@@ -337,7 +337,7 @@ fn draw_preview_device(ui: &mut egui::Ui, state: &BrowserState) {
337 337 ui.label(
338 338 egui::RichText::new(device_label)
339 339 .small()
340 - .color(theme::text_muted()),
340 + .color(theme::content_muted()),
341 341 )
342 342 .on_hover_text("Audio output device used for sample preview");
343 343 }
@@ -56,7 +56,7 @@ fn draw_waveform_with_marks(ui: &mut egui::Ui, state: &BrowserState) {
56 56 let x = rect.left() + rect.width() * frac.clamp(0.0, 1.0);
57 57 painter.line_segment(
58 58 [egui::pos2(x, rect.top()), egui::pos2(x, rect.bottom())],
59 - egui::Stroke::new(1.0, theme::accent_yellow()),
59 + egui::Stroke::new(1.0, theme::warning()),
60 60 );
61 61 }
62 62 }
@@ -69,7 +69,7 @@ fn draw_info_line(ui: &mut egui::Ui, state: &BrowserState) {
69 69 ui.label(egui::RichText::new(name).strong().size(12.0));
70 70 ui.label(
71 71 egui::RichText::new(format!("{rate} Hz"))
72 - .color(theme::text_muted())
72 + .color(theme::content_muted())
73 73 .size(11.0),
74 74 );
75 75 });
@@ -187,7 +187,7 @@ fn draw_chop_section(ui: &mut egui::Ui, state: &mut BrowserState) {
187 187 ui.label(
188 188 egui::RichText::new("Slices are written into a new folder beside this sample.")
189 189 .small()
190 - .color(theme::text_muted()),
190 + .color(theme::content_muted()),
191 191 );
192 192 }
193 193
@@ -200,7 +200,7 @@ fn draw_conform_section(ui: &mut egui::Ui, state: &mut BrowserState) {
200 200 ui.label(
201 201 egui::RichText::new("No device profiles available.")
202 202 .small()
203 - .color(theme::text_muted()),
203 + .color(theme::content_muted()),
204 204 );
205 205 return;
206 206 }
@@ -239,7 +239,7 @@ fn draw_conform_section(ui: &mut egui::Ui, state: &mut BrowserState) {
239 239 ui.label(
240 240 egui::RichText::new("Resamples and converts bit depth to match the device, as a new sample.")
241 241 .small()
242 - .color(theme::text_muted()),
242 + .color(theme::content_muted()),
243 243 );
244 244 }
245 245
@@ -252,7 +252,7 @@ fn draw_batch_section(ui: &mut egui::Ui, state: &mut BrowserState) {
252 252 ui.label(
253 253 egui::RichText::new("Select 2+ samples to batch trim silence.")
254 254 .small()
255 - .color(theme::text_muted()),
255 + .color(theme::content_muted()),
256 256 );
257 257 return;
258 258 }
@@ -282,6 +282,6 @@ fn draw_foreshadow_section(ui: &mut egui::Ui) {
282 282 egui::RichText::new("Plugin processing (CLAP/VST) — coming soon")
283 283 .small()
284 284 .italics()
285 - .color(theme::text_muted()),
285 + .color(theme::content_muted()),
286 286 );
287 287 }
@@ -175,7 +175,7 @@ pub fn draw_configure_import(ui: &mut egui::Ui, state: &mut BrowserState) {
175 175 "Once started, you can cancel mid-import but copies already made will stay in the library."
176 176 )
177 177 .small()
178 - .color(theme::text_muted()),
178 + .color(theme::content_muted()),
179 179 );
180 180 ui.add_space(theme::space::SM);
181 181
@@ -24,7 +24,7 @@ fn draw_error_log(
24 24 "{err_count} error{}",
25 25 if err_count == 1 { "" } else { "s" },
26 26 ))
27 - .color(theme::accent_red()),
27 + .color(theme::danger()),
28 28 );
29 29 ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
30 30 let toggle_label = if *expanded { "Hide" } else { "Show" };
@@ -46,14 +46,14 @@ fn draw_error_log(
46 46 ui.label(
47 47 egui::RichText::new(format!("{}: {}", err.path, err.error))
48 48 .small()
49 - .color(theme::accent_red()),
49 + .color(theme::danger()),
50 50 );
51 51 }
52 52 for err in analysis_errors {
53 53 ui.label(
54 54 egui::RichText::new(format!("{}: {}", err.name, err.error))
55 55 .small()
56 - .color(theme::accent_red()),
56 + .color(theme::danger()),
57 57 );
58 58 }
59 59 });
@@ -83,7 +83,7 @@ fn draw_rate_and_eta(
83 83 ui.label(
84 84 egui::RichText::new(parts.join(" \u{00B7} "))
85 85 .small()
86 - .color(theme::text_muted()),
86 + .color(theme::content_muted()),
87 87 );
88 88 }
89 89 }
@@ -144,7 +144,7 @@ pub fn draw_import_progress(ui: &mut egui::Ui, state: &mut BrowserState) {
144 144 ui.label(
145 145 egui::RichText::new(storage_text)
146 146 .small()
147 - .color(if loose_files { theme::accent_yellow() } else { theme::text_secondary() }),
147 + .color(if loose_files { theme::warning() } else { theme::content_secondary() }),
148 148 );
149 149 ui.add_space(theme::space::SM);
150 150 }
@@ -353,14 +353,14 @@ pub fn draw_operation_cancelled(ui: &mut egui::Ui, state: &mut BrowserState) {
353 353 ui.add_space(theme::space::SM);
354 354 ui.label(
355 355 egui::RichText::new(follow_up)
356 - .color(theme::text_secondary()),
356 + .color(theme::content_secondary()),
357 357 );
358 358 if let Some(dest) = destination.as_ref() {
359 359 ui.add_space(theme::space::SM);
360 360 ui.label(
361 361 egui::RichText::new(format!("Destination: {}", dest.display()))
362 362 .small()
363 - .color(theme::text_muted()),
363 + .color(theme::content_muted()),
364 364 );
365 365 }
366 366
@@ -27,7 +27,7 @@ pub fn draw_review_errors(ui: &mut egui::Ui, state: &mut BrowserState) {
27 27 if analysis_count == 1 { "" } else { "s" },
28 28 ))
29 29 .strong()
30 - .color(theme::accent_red()),
30 + .color(theme::danger()),
31 31 );
32 32 ui.label(
33 33 egui::RichText::new(
@@ -35,7 +35,7 @@ pub fn draw_review_errors(ui: &mut egui::Ui, state: &mut BrowserState) {
35 35 You can remove them, ignore them, or re-analyse later.",
36 36 )
37 37 .small()
38 - .color(theme::text_muted()),
38 + .color(theme::content_muted()),
39 39 );
40 40 ui.add_space(theme::space::SM);
41 41
@@ -54,7 +54,7 @@ pub fn draw_review_errors(ui: &mut egui::Ui, state: &mut BrowserState) {
54 54 ui.label(
55 55 egui::RichText::new(&err.error)
56 56 .small()
57 - .color(theme::text_secondary()),
57 + .color(theme::content_secondary()),
58 58 );
59 59 if widgets::danger_small_button(ui, "Remove").clicked() {
60 60 remove_request = Some((i, err.name.clone()));
@@ -84,7 +84,7 @@ pub fn draw_review_errors(ui: &mut egui::Ui, state: &mut BrowserState) {
84 84 if import_count == 1 { "" } else { "s" },
85 85 ))
86 86 .strong()
87 - .color(theme::accent_red()),
87 + .color(theme::danger()),
88 88 );
89 89 ui.label(
90 90 egui::RichText::new(
@@ -92,7 +92,7 @@ pub fn draw_review_errors(ui: &mut egui::Ui, state: &mut BrowserState) {
92 92 is the only way to retry \u{2014} duplicates will be skipped.",
93 93 )
94 94 .small()
95 - .color(theme::text_muted()),
95 + .color(theme::content_muted()),
96 96 );
97 97 ui.add_space(theme::space::SM);
98 98
@@ -106,7 +106,7 @@ pub fn draw_review_errors(ui: &mut egui::Ui, state: &mut BrowserState) {
106 106 ui.label(
107 107 egui::RichText::new(&err.error)
108 108 .small()
109 - .color(theme::text_secondary()),
109 + .color(theme::content_secondary()),
110 110 );
111 111 });
112 112 }
@@ -54,7 +54,7 @@ pub fn draw_tag_folders(ui: &mut egui::Ui, state: &mut BrowserState) {
54 54 egui::RichText::new(format!(
55 55 "{entry_count} folders \u{00B7} {total_samples} samples"
56 56 ))
57 - .color(theme::text_muted()),
57 + .color(theme::content_muted()),
58 58 );
59 59 ui.add_space(theme::space::SM);
60 60 ui.label("Assign tags to imported folders. Comma-separated. Applied to all samples within each folder.");
@@ -119,7 +119,7 @@ pub fn draw_tag_folders(ui: &mut egui::Ui, state: &mut BrowserState) {
119 119 "Invalid: {}",
120 120 invalid.join(", ")
121 121 ))
122 - .color(theme::accent_red())
122 + .color(theme::danger())
123 123 .small(),
124 124 );
125 125 }
@@ -325,11 +325,11 @@ pub fn draw_review_suggestions(ui: &mut egui::Ui, state: &mut BrowserState) {
325 325 format!(" \u{00B7} {accepted}/{total}")
326 326 };
327 327 let color = if total == 0 {
328 - theme::text_muted()
328 + theme::content_muted()
329 329 } else if accepted < total {
330 - theme::accent_yellow()
330 + theme::warning()
331 331 } else {
332 - theme::text_muted()
332 + theme::content_muted()
333 333 };
334 334 let label = if suffix.is_empty() {
335 335 egui::RichText::new(&item.name)
@@ -371,7 +371,7 @@ pub fn draw_review_suggestions(ui: &mut egui::Ui, state: &mut BrowserState) {
371 371 // the review walk.
372 372 ui.label(
373 373 egui::RichText::new(format!("({} of {})", current_idx + 1, total))
374 - .color(theme::text_muted()),
374 + .color(theme::content_muted()),
375 375 );
376 376 ui.horizontal(|ui| {
377 377 ui.label(format!("{:.2}s", item.result.duration));
@@ -390,7 +390,7 @@ pub fn draw_review_suggestions(ui: &mut egui::Ui, state: &mut BrowserState) {
390 390 if let Some(ref class) = item.result.classification {
391 391 ui.label(
392 392 egui::RichText::new(class.as_str())
393 - .color(theme::accent_green()),
393 + .color(theme::success()),
394 394 );
395 395 }
396 396 });
@@ -407,11 +407,11 @@ pub fn draw_review_suggestions(ui: &mut egui::Ui, state: &mut BrowserState) {
407 407 // suggestions instead of reading every percentage.
408 408 let pct = sug.suggestion.confidence * 100.0;
409 409 let color = if pct >= 80.0 {
410 - theme::accent_green()
410 + theme::success()
411 411 } else if pct >= 60.0 {
412 - theme::accent_yellow()
412 + theme::warning()
413 413 } else {
414 - theme::text_muted()
414 + theme::content_muted()
415 415 };
416 416 ui.label(
417 417 egui::RichText::new(format!("{pct:.0}%")).color(color),
@@ -421,7 +421,7 @@ pub fn draw_review_suggestions(ui: &mut egui::Ui, state: &mut BrowserState) {
421 421 ui.label(
422 422 egui::RichText::new(&sug.suggestion.reason)
423 423 .small()
424 - .color(theme::text_muted()),
424 + .color(theme::content_muted()),
425 425 );
426 426 });
427 427 ui.add_space(theme::space::SM);
@@ -30,12 +30,12 @@ pub fn draw_midi_window(ctx: &egui::Context, state: &mut BrowserState) {
30 30 ui.add_space(theme::space::MD);
31 31 ui.label(
32 32 egui::RichText::new("No sample loaded")
33 - .color(theme::text_secondary()),
33 + .color(theme::content_secondary()),
34 34 );
35 35 ui.label(
36 36 egui::RichText::new("Right-click a sample \u{2192} \"Play as Instrument\", or drag samples onto the keyboard below")
37 37 .small()
38 - .color(theme::text_muted()),
38 + .color(theme::content_muted()),
39 39 );
40 40 ui.add_space(theme::space::MD);
41 41 });
@@ -87,7 +87,7 @@ fn draw_midi_device_picker(ui: &mut egui::Ui, state: &mut BrowserState) {
87 87 ui.horizontal(|ui| {
88 88 ui.label(
89 89 egui::RichText::new("No MIDI inputs detected")
90 - .color(theme::text_muted()),
90 + .color(theme::content_muted()),
91 91 );
92 92 if ui.small_button("Refresh").clicked() {
93 93 state.midi_pending_action = Some(MidiAction::RefreshPorts);
@@ -97,7 +97,7 @@ fn draw_midi_device_picker(ui: &mut egui::Ui, state: &mut BrowserState) {
97 97 }
98 98
99 99 ui.horizontal(|ui| {
100 - ui.label(egui::RichText::new("MIDI Input").color(theme::text_primary()));
100 + ui.label(egui::RichText::new("MIDI Input").color(theme::content()));
101 101
102 102 if ui.small_button("Refresh").clicked() {
103 103 state.midi_pending_action = Some(MidiAction::RefreshPorts);
@@ -148,13 +148,13 @@ fn draw_activity_display(ui: &mut egui::Ui, state: &mut BrowserState) {
148 148 ui.label(
149 149 egui::RichText::new(idle_text)
150 150 .small()
151 - .color(theme::text_muted()),
151 + .color(theme::content_muted()),
152 152 );
153 153 } else {
154 154 for note in state.midi_state.recent_notes.iter().rev().take(8) {
155 155 let age = now.duration_since(note.timestamp).as_secs_f32();
156 156 let alpha = ((2.0 - age) / 2.0).clamp(0.0, 1.0);
157 - let color = theme::text_primary().linear_multiply(alpha);
157 + let color = theme::content().linear_multiply(alpha);
158 158 ui.label(
159 159 egui::RichText::new(format!("{} v{}", note.note_name, note.velocity))
160 160 .small()
@@ -189,7 +189,7 @@ fn draw_mode_controls(ui: &mut egui::Ui, state: &mut BrowserState) {
189 189
190 190 ui.label(
191 191 egui::RichText::new(format!("Root: {}", note_name(state.instrument_root_note)))
192 - .color(theme::text_secondary()),
192 + .color(theme::content_secondary()),
193 193 );
194 194
195 195 ui.separator();
@@ -205,7 +205,7 @@ fn draw_mode_controls(ui: &mut egui::Ui, state: &mut BrowserState) {
205 205 ui.label(
206 206 egui::RichText::new("Multi-sample activates after you drop a second sample onto the keyboard.")
207 207 .small()
208 - .color(theme::text_muted()),
208 + .color(theme::content_muted()),
209 209 );
210 210 }
211 211 }
@@ -231,7 +231,7 @@ fn draw_piano_keyboard(ui: &mut egui::Ui, state: &mut BrowserState) {
231 231 ui.label(
232 232 egui::RichText::new(format!("Oct {}", base_octave))
233 233 .small()
234 - .color(theme::text_secondary()),
234 + .color(theme::content_secondary()),
235 235 );
236 236 if ui
237 237 .button("+")
@@ -448,17 +448,17 @@ fn draw_piano_keyboard(ui: &mut egui::Ui, state: &mut BrowserState) {
448 448 let is_root = note == state.instrument_root_note;
449 449
450 450 let fill = if is_active {
451 - theme::accent_blue()
451 + theme::action()
452 452 } else {
453 453 theme::piano_white_key()
454 454 };
455 455
456 456 painter.rect_filled(key_rect, 2.0, fill);
457 - painter.rect_stroke(key_rect, 2.0, egui::Stroke::new(1.0, theme::border_default()), egui::StrokeKind::Outside);
457 + painter.rect_stroke(key_rect, 2.0, egui::Stroke::new(1.0, theme::border()), egui::StrokeKind::Outside);
458 458
459 459 if is_root {
460 460 let dot_center = key_rect.center_bottom() - egui::vec2(0.0, 8.0);
461 - painter.circle_filled(dot_center, 3.0, theme::accent_purple());
461 + painter.circle_filled(dot_center, 3.0, theme::category_five());
462 462 }
463 463
464 464 white_x += key_width;
@@ -470,7 +470,7 @@ fn draw_piano_keyboard(ui: &mut egui::Ui, state: &mut BrowserState) {
470 470 let is_root = black_note == state.instrument_root_note;
471 471
472 472 let fill = if is_active {
473 - theme::accent_blue()
473 + theme::action()
474 474 } else {
475 475 theme::piano_black_key()
476 476 };
@@ -479,7 +479,7 @@ fn draw_piano_keyboard(ui: &mut egui::Ui, state: &mut BrowserState) {
479 479
480 480 if is_root {
481 481 let dot_center = key_rect.center_bottom() - egui::vec2(0.0, 5.0);
482 - painter.circle_filled(dot_center, 3.0, theme::accent_purple());
482 + painter.circle_filled(dot_center, 3.0, theme::category_five());
483 483 }
484 484 }
485 485
@@ -488,12 +488,12 @@ fn draw_piano_keyboard(ui: &mut egui::Ui, state: &mut BrowserState) {
488 488 // chip_clicked_index for the click consumption.
489 489 if num_zones > 1 {
490 490 let zone_colors = [
491 - theme::accent_blue(),
492 - theme::accent_green(),
493 - theme::accent_yellow(),
494 - theme::accent_purple(),
495 - theme::accent_cyan(),
496 - theme::accent_red(),
491 + theme::action(),
492 + theme::success(),
493 + theme::warning(),
494 + theme::category_five(),
495 + theme::category_six(),
496 + theme::danger(),
497 497 ];
498 498
499 499 for (i, (low, high, _root)) in zone_ranges.iter().enumerate() {
@@ -516,7 +516,7 @@ fn draw_piano_keyboard(ui: &mut egui::Ui, state: &mut BrowserState) {
516 516 let p2 = chip_rect.max - egui::vec2(pad, pad);
517 517 let p3 = egui::pos2(chip_rect.min.x + pad, chip_rect.max.y - pad);
518 518 let p4 = egui::pos2(chip_rect.max.x - pad, chip_rect.min.y + pad);
519 - let stroke = egui::Stroke::new(1.2, theme::text_primary());
519 + let stroke = egui::Stroke::new(1.2, theme::content());
520 520 painter.line_segment([p1, p2], stroke);
521 521 painter.line_segment([p3, p4], stroke);
522 522 }
@@ -546,7 +546,7 @@ fn draw_piano_keyboard(ui: &mut egui::Ui, state: &mut BrowserState) {
546 546 painter.rect_filled(
547 547 hover_rect,
548 548 2.0,
549 - theme::accent_blue().linear_multiply(0.3),
549 + theme::action().linear_multiply(0.3),
550 550 );
551 551 egui::Tooltip::always_open(
552 552 ui.ctx().clone(),
@@ -598,7 +598,7 @@ fn draw_adsr_controls(ui: &mut egui::Ui, state: &mut BrowserState) {
598 598 ("Stab", 0.001, 0.05, 0.0, 0.05),
599 599 ];
600 600 ui.horizontal(|ui| {
601 - ui.label(egui::RichText::new("Preset").small().color(theme::text_secondary()));
601 + ui.label(egui::RichText::new("Preset").small().color(theme::content_secondary()));
602 602 for (label, a, d, s, r) in presets {
603 603 // Highlight a preset when the envelope matches it exactly. Float
604 604 // compare is intentional: any user-driven slider edit drops the
@@ -619,7 +619,7 @@ fn draw_adsr_controls(ui: &mut egui::Ui, state: &mut BrowserState) {
619 619 draw_adsr_envelope_shape(ui, envelope.attack, envelope.decay, envelope.sustain, envelope.release);
620 620
621 621 ui.horizontal(|ui| {
622 - ui.label(egui::RichText::new("A").small().color(theme::text_secondary()))
622 + ui.label(egui::RichText::new("A").small().color(theme::content_secondary()))
623 623 .on_hover_text("Attack — time to reach full volume after key press");
624 624 let slider = egui::Slider::new(&mut envelope.attack, 0.001..=5.0)
625 625 .logarithmic(true)
@@ -627,7 +627,7 @@ fn draw_adsr_controls(ui: &mut egui::Ui, state: &mut BrowserState) {
627 627 .suffix("s");
628 628 ui.add(slider);
629 629
630 - ui.label(egui::RichText::new("D").small().color(theme::text_secondary()))
630 + ui.label(egui::RichText::new("D").small().color(theme::content_secondary()))
631 631 .on_hover_text("Decay — time to fall from peak to sustain level");
632 632 let slider = egui::Slider::new(&mut envelope.decay, 0.001..=5.0)
633 633 .logarithmic(true)
@@ -637,13 +637,13 @@ fn draw_adsr_controls(ui: &mut egui::Ui, state: &mut BrowserState) {
637 637 });
638 638
639 639 ui.horizontal(|ui| {
640 - ui.label(egui::RichText::new("S").small().color(theme::text_secondary()))
640 + ui.label(egui::RichText::new("S").small().color(theme::content_secondary()))
641 641 .on_hover_text("Sustain — held volume level while the key is down (0 to 1)");
642 642 let slider = egui::Slider::new(&mut envelope.sustain, 0.0..=1.0)
643 643 .max_decimals(2);
644 644 ui.add(slider);
645 645
646 - ui.label(egui::RichText::new("R").small().color(theme::text_secondary()))
646 + ui.label(egui::RichText::new("R").small().color(theme::content_secondary()))
647 647 .on_hover_text("Release — time to fade to silence after key release");
648 648 let slider = egui::Slider::new(&mut envelope.release, 0.001..=10.0)
649 649 .logarithmic(true)
@@ -664,7 +664,7 @@ fn draw_adsr_envelope_shape(ui: &mut egui::Ui, attack: f32, decay: f32, sustain:
664 664 let painter = ui.painter_at(rect);
665 665
666 666 // Background frame
667 - painter.rect_filled(rect, 2.0, theme::bg_tertiary());
667 + painter.rect_filled(rect, 2.0, theme::surface_sunken());
668 668
669 669 // Map each phase to a horizontal slice. Use a soft log so 0.001s isn't a
670 670 // single pixel: weight = (1 + t).ln() with t in seconds, clamped.
@@ -687,7 +687,7 @@ fn draw_adsr_envelope_shape(ui: &mut egui::Ui, attack: f32, decay: f32, sustain:
687 687 let y_base = rect.bottom() - 4.0;
688 688 let y_sustain = y_base + (y_peak - y_base) * sustain.clamp(0.0, 1.0);
689 689
690 - let stroke = egui::Stroke::new(1.5, theme::accent_blue());
690 + let stroke = egui::Stroke::new(1.5, theme::action());
691 691 let p0 = egui::pos2(x0, y_base);
692 692 let p_a = egui::pos2(x_a, y_peak);
693 693 let p_d = egui::pos2(x_d, y_sustain);
@@ -144,7 +144,7 @@ fn draw_shortcuts_tab(ui: &mut egui::Ui, state: &mut BrowserState) {
144 144 egui::RichText::new(*group_name)
145 145 .small()
146 146 .strong()
147 - .color(theme::text_muted()),
147 + .color(theme::content_muted()),
148 148 );
149 149 egui::Grid::new(format!("shortcuts_{group_name}")).show(ui, |ui| {
150 150 for (key, desc) in visible {
@@ -406,7 +406,7 @@ pub fn draw_import_preflight(ctx: &egui::Context, state: &mut BrowserState) {
406 406 "Files stay where they are \u{2014} audiofiles only indexes them.",
407 407 )
408 408 .small()
409 - .color(theme::text_secondary()),
409 + .color(theme::content_secondary()),
410 410 );
411 411 ui.add_space(theme::space::MD);
412 412 // M-9: checkbox state is transient on BrowserState; committed only
@@ -482,7 +482,7 @@ pub fn draw_loose_files_warning(ctx: &egui::Context, state: &mut BrowserState) {
482 482 be permanently deleted by Purge.",
483 483 )
484 484 .small()
485 - .color(theme::accent_yellow()),
485 + .color(theme::warning()),
486 486 );
487 487 ui.add_space(theme::space::MD);
488 488 ui.horizontal(|ui| {
@@ -622,7 +622,7 @@ fn draw_bulk_tag_modal(ctx: &egui::Context, state: &mut BrowserState) {
622 622 ui.label(
623 623 egui::RichText::new("None of the selected samples have this tag.")
624 624 .small()
625 - .color(theme::accent_yellow()),
625 + .color(theme::warning()),
626 626 );
627 627 apply_enabled = false;
628 628 apply_hover_disabled = Some("None of the selected samples have this tag.");
@@ -632,7 +632,7 @@ fn draw_bulk_tag_modal(ctx: &egui::Context, state: &mut BrowserState) {
632 632 "Will remove from selected samples that have this tag.",
633 633 )
634 634 .small()
635 - .color(theme::text_secondary()),
635 + .color(theme::content_secondary()),
636 636 );
637 637 }
638 638 ui.add_space(theme::space::SM);
@@ -643,7 +643,7 @@ fn draw_bulk_tag_modal(ctx: &egui::Context, state: &mut BrowserState) {
643 643 .show(ui, |ui| {
644 644 for name in names {
645 645 ui.label(
646 - egui::RichText::new(name).small().color(theme::text_secondary()),
646 + egui::RichText::new(name).small().color(theme::content_secondary()),
647 647 );
648 648 }
649 649 });
@@ -792,7 +792,7 @@ fn draw_bulk_rename_modal(ctx: &egui::Context, state: &mut BrowserState) {
792 792 .small_button(
793 793 egui::RichText::new(*token)
794 794 .small()
795 - .color(theme::accent_blue()),
795 + .color(theme::action()),
796 796 )
797 797 .clicked()
798 798 {
@@ -813,7 +813,7 @@ fn draw_bulk_rename_modal(ctx: &egui::Context, state: &mut BrowserState) {
813 813 }
814 814
815 815 if let Some(err) = error {
816 - ui.colored_label(theme::accent_red(), err);
816 + ui.colored_label(theme::danger(), err);
817 817 }
818 818
819 819 ui.add_space(theme::space::SM);
@@ -844,19 +844,19 @@ fn draw_bulk_rename_modal(ctx: &egui::Context, state: &mut BrowserState) {
844 844 ui.label(
845 845 egui::RichText::new("Old")
846 846 .strong()
847 - .color(theme::text_secondary()),
847 + .color(theme::content_secondary()),
848 848 );
849 849 ui.label(
850 850 egui::RichText::new("New")
851 851 .strong()
852 - .color(theme::accent_blue()),
852 + .color(theme::action()),
853 853 );
854 854 ui.end_row();
855 855
856 856 for (old, new) in previews.iter().take(visible_count) {
857 857 ui.label(old);
858 858 // M-8: duplicate output names render in
859 - // accent_yellow with an honest hover —
859 + // warning with an honest hover —
860 860 // backend collision behaviour isn't
861 861 // verified here, so we describe risk not
862 862 // outcome.
@@ -866,9 +866,9 @@ fn draw_bulk_rename_modal(ctx: &egui::Context, state: &mut BrowserState) {
866 866 .unwrap_or(0)
867 867 > 1;
868 868 let color = if is_dup {
869 - theme::accent_yellow()
869 + theme::warning()
870 870 } else {
871 - theme::accent_blue()
871 + theme::action()
872 872 };
873 873 let label = ui.label(
874 874 egui::RichText::new(new).color(color),
@@ -892,7 +892,7 @@ fn draw_bulk_rename_modal(ctx: &egui::Context, state: &mut BrowserState) {
892 892 PREVIEW_CAP,
893 893 ))
894 894 .small()
895 - .color(theme::text_muted()),
895 + .color(theme::content_muted()),
896 896 );
897 897 }
898 898 });
@@ -93,7 +93,7 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
93 93 ui.label(
94 94 egui::RichText::new("Each library is an independent sample collection with its own database and files. A library can contain multiple vaults (top-level browse buckets).")
95 95 .small()
96 - .color(theme::text_muted()),
96 + .color(theme::content_muted()),
97 97 );
98 98 ui.add_space(theme::space::SM);
99 99
@@ -115,9 +115,9 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
115 115 let is_active = path == &active_path;
116 116 ui.horizontal(|ui| {
117 117 let status = if is_active {
118 - egui::RichText::new("active").small().color(theme::accent_blue())
118 + egui::RichText::new("active").small().color(theme::action())
119 119 } else if !reachable {
120 - egui::RichText::new("offline").small().color(theme::accent_red())
120 + egui::RichText::new("offline").small().color(theme::danger())
121 121 } else {
122 122 egui::RichText::new("").small()
123 123 };
@@ -152,7 +152,7 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
152 152 ui.label(
153 153 egui::RichText::new(collapse_home(path))
154 154 .small()
155 - .color(theme::text_muted()),
155 + .color(theme::content_muted()),
156 156 )
157 157 .on_hover_text(path.display().to_string());
158 158 // Per-vault sample count + total size for the active vault when
@@ -168,7 +168,7 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
168 168 format_bytes(stats.total_bytes),
169 169 ))
170 170 .small()
171 - .color(theme::text_muted()),
171 + .color(theme::content_muted()),
172 172 );
173 173 }
174 174 ui.add_space(theme::space::SM);
@@ -267,7 +267,7 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
267 267 .unwrap_or(at);
268 268 let age_secs = now.saturating_sub(at).max(0);
269 269 let (text, stale) = format_scan_age(age_secs);
270 - let color = if stale { theme::accent_yellow() } else { theme::text_muted() };
270 + let color = if stale { theme::warning() } else { theme::content_muted() };
271 271 ui.label(
272 272 egui::RichText::new(text).small().color(color),
273 273 );
@@ -301,7 +301,7 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
301 301 ui.label(
302 302 egui::RichText::new("This library uses loose-files mode. Samples are referenced in place, not duplicated.")
303 303 .small()
304 - .color(theme::accent_yellow()),
304 + .color(theme::warning()),
305 305 );
306 306 }
307 307
@@ -320,14 +320,14 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
320 320 ui.label(
321 321 egui::RichText::new(p.display().to_string())
322 322 .small()
323 - .color(theme::text_secondary()),
323 + .color(theme::content_secondary()),
324 324 );
325 325 }
326 326 });
327 327 // Storage style is a significant choice (copy vs reference in
328 328 // place) — promote it from a buried checkbox to an explicit radio
329 329 // choice so users opt into loose-files mode deliberately.
330 - ui.label(egui::RichText::new("Storage style:").small().color(theme::text_secondary()));
330 + ui.label(egui::RichText::new("Storage style:").small().color(theme::content_secondary()));
331 331 let mut style = state.settings.create_loose_files;
332 332 if ui.radio_value(&mut style, false, "Copy samples into library (recommended)")
333 333 .on_hover_text("Samples are duplicated into the library's content-addressed store. Originals can be moved or deleted safely.")
@@ -345,7 +345,7 @@ fn draw_storage_section(ui: &mut egui::Ui, state: &mut BrowserState) {
345 345 ui.label(
346 346 egui::RichText::new("Moving or deleting originals will break references. This cannot be undone.")
347 347 .small()
348 - .color(theme::accent_yellow()),
348 + .color(theme::warning()),
349 349 );
350 350 }
351 351 ui.add_space(theme::space::SM);
@@ -458,9 +458,9 @@ fn draw_appearance_section(ui: &mut egui::Ui, state: &mut BrowserState) {
458 458 // Contrast-tier badge (text legibility, not the
459 459 // theme's accent palette).
460 460 let badge_color = match tier {
461 - theme::ContrastTier::High => theme::accent_green(),
462 - theme::ContrastTier::Standard => theme::text_muted(),
463 - theme::ContrastTier::Low => theme::accent_yellow(),
461 + theme::ContrastTier::High => theme::success(),
462 + theme::ContrastTier::Standard => theme::content_muted(),
463 + theme::ContrastTier::Low => theme::warning(),
464 464 };
465 465 ui.label(
466 466 egui::RichText::new(tier.badge())
@@ -538,7 +538,7 @@ fn draw_display_section(ui: &mut egui::Ui, state: &mut BrowserState) {
538 538 egui::CollapsingHeader::new(egui::RichText::new("Display").strong())
539 539 .default_open(false)
540 540 .show(ui, |ui| {
541 - ui.label(egui::RichText::new("Visible Columns").small().color(theme::text_secondary()));
541 + ui.label(egui::RichText::new("Visible Columns").small().color(theme::content_secondary()));
542 542
543 543 let mut col_changed = false;
544 544 col_changed |= ui.checkbox(&mut state.column_config.show_classification, "Classification").changed();
@@ -566,7 +566,7 @@ fn draw_display_section(ui: &mut egui::Ui, state: &mut BrowserState) {
566 566 ui.add_space(theme::space::MD);
567 567 ui.separator();
568 568 ui.add_space(theme::space::SM);
569 - ui.label(egui::RichText::new("Row Density").small().color(theme::text_secondary()));
569 + ui.label(egui::RichText::new("Row Density").small().color(theme::content_secondary()));
570 570 let mut row_height = state.row_height;
571 571 let label = if row_height <= 22.0 {
572 572 "Compact"
@@ -580,7 +580,7 @@ fn draw_display_section(ui: &mut egui::Ui, state: &mut BrowserState) {
580 580 ui.label(
581 581 egui::RichText::new(format!("{} px", row_height as i32))
582 582 .small()
583 - .color(theme::text_muted()),
583 + .color(theme::content_muted()),
584 584 );
585 585 if ui.add(egui::Slider::new(&mut row_height, 20.0..=32.0).step_by(2.0).show_value(false)).changed() {
586 586 state.row_height = row_height;
@@ -591,7 +591,7 @@ fn draw_display_section(ui: &mut egui::Ui, state: &mut BrowserState) {
591 591 ui.add_space(theme::space::MD);
592 592 ui.separator();
593 593 ui.add_space(theme::space::SM);
594 - ui.label(egui::RichText::new("Tag Suggestions").small().color(theme::text_secondary()));
594 + ui.label(egui::RichText::new("Tag Suggestions").small().color(theme::content_secondary()));
595 595 let dismissed_total: usize = state
596 596 .dismissed_suggestions
597 597 .values()
@@ -604,7 +604,7 @@ fn draw_display_section(ui: &mut egui::Ui, state: &mut BrowserState) {
604 604 if dismissed_total == 1 { "" } else { "s" }
605 605 ))
606 606 .small()
607 - .color(theme::text_muted()),
607 + .color(theme::content_muted()),
608 608 );
609 609 if ui
610 610 .add_enabled(dismissed_total > 0, egui::Button::new("Reset suggestions"))
@@ -626,7 +626,7 @@ fn draw_license_section(ui: &mut egui::Ui, state: &mut BrowserState) {
626 626 if let Some(ref masked) = state.settings.license_key_masked {
627 627 ui.horizontal(|ui| {
628 628 ui.label("Key:");
629 - ui.label(egui::RichText::new(masked).color(theme::text_secondary()));
629 + ui.label(egui::RichText::new(masked).color(theme::content_secondary()));
630 630 });
631 631 } else if let Some(days) = state.settings.trial_days_remaining {
632 632 // "Trial: 0 days" was technically correct but uncomfortably
@@ -639,11 +639,11 @@ fn draw_license_section(ui: &mut egui::Ui, state: &mut BrowserState) {
639 639 "Trial expired".to_string()
640 640 };
641 641 let color = if days > 7 {
642 - theme::text_secondary()
642 + theme::content_secondary()
643 643 } else if days > 0 {
644 - theme::accent_yellow()
644 + theme::warning()
645 645 } else {
646 - theme::text_muted()
646 + theme::content_muted()
647 647 };
648 648 ui.label(egui::RichText::new(text).color(color));
649 649 }
@@ -654,7 +654,7 @@ fn draw_license_section(ui: &mut egui::Ui, state: &mut BrowserState) {
654 654 // a keyboard shortcut, plus an explicit Copy button for
655 655 // pointer users. Common ask when contacting support (m-14).
656 656 ui.add(egui::Label::new(
657 - egui::RichText::new(mid).small().color(theme::text_muted()),
657 + egui::RichText::new(mid).small().color(theme::content_muted()),
658 658 ).selectable(true));
659 659 if ui.small_button("Copy").on_hover_text("Copy machine id to clipboard").clicked() {
660 660 ui.ctx().copy_text(mid.clone());
@@ -678,7 +678,7 @@ fn draw_advanced_section(ui: &mut egui::Ui, state: &mut BrowserState) {
678 678 .default_open(false)
679 679 .show(ui, |ui| {
680 680 // Theme import/export
681 - ui.label(egui::RichText::new("Custom Themes").small().color(theme::text_secondary()));
681 + ui.label(egui::RichText::new("Custom Themes").small().color(theme::content_secondary()));
682 682 ui.horizontal(|ui| {
683 683 if ui.button("Import Theme...").clicked()
684 684 && let Some(path) = rfd::FileDialog::new()
@@ -743,7 +743,7 @@ fn draw_advanced_section(ui: &mut egui::Ui, state: &mut BrowserState) {
743 743 ui.add_space(theme::space::MD);
744 744 ui.separator();
745 745 ui.add_space(theme::space::SM);
746 - ui.label(egui::RichText::new("Library Mirror").small().color(theme::text_secondary()));
746 + ui.label(egui::RichText::new("Library Mirror").small().color(theme::content_secondary()));
747 747 let mut mirror = state.mirror_enabled;
748 748 if ui.checkbox(&mut mirror, "Enable library mirror")
749 749 .on_hover_text("Create a symlink tree so DAWs can browse your library as a normal folder")
@@ -759,7 +759,7 @@ fn draw_advanced_section(ui: &mut egui::Ui, state: &mut BrowserState) {
759 759 ui.label(
760 760 egui::RichText::new(collapse_home(&state.mirror_path))
761 761 .small()
762 - .color(theme::text_muted()),
762 + .color(theme::content_muted()),
763 763 )
764 764 .on_hover_text(state.mirror_path.display().to_string());
765 765 if ui.small_button("Change...").on_hover_text("Pick a new location for the mirror").clicked()
@@ -156,9 +156,9 @@ fn draw_tag_node(
156 156 // but the label is not interactive — there are no samples
157 157 // tagged at this exact path to filter to.
158 158 let color = if has_active_descendant {
159 - theme::accent_blue()
159 + theme::action()
160 160 } else {
161 - theme::text_secondary()
161 + theme::content_secondary()
162 162 };
163 163 ui.label(egui::RichText::new(segment).color(color));
164 164 }
@@ -179,7 +179,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
179 179 // single-library installs see only the inner "Vaults" list below.
180 180 if state.settings.list.len() > 1 {
181 181 ui.horizontal(|ui| {
182 - ui.label(egui::RichText::new("Library").small().color(theme::text_muted()));
182 + ui.label(egui::RichText::new("Library").small().color(theme::content_muted()));
183 183 egui::ComboBox::from_id_salt("library_picker")
184 184 .selected_text(&state.settings.name)
185 185 .width(ui.available_width() - 8.0)
@@ -221,7 +221,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
221 221 } else if !state.settings.list.is_empty() {
222 222 // Single library — just show a "Settings..." link
223 223 ui.horizontal(|ui| {
224 - ui.label(egui::RichText::new(&state.settings.name).small().color(theme::text_muted()));
224 + ui.label(egui::RichText::new(&state.settings.name).small().color(theme::content_muted()));
225 225 if ui.small_button("Settings").on_hover_text("Open library settings").clicked() {
226 226 state.settings.show_manager = true;
227 227 }
@@ -310,8 +310,8 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
310 310 ui.collapsing("Collections", |ui| {
311 311 if state.collections.is_empty() && !state.show_collection_create {
312 312 ui.horizontal(|ui| {
313 - ui.label(egui::RichText::new("No collections yet.").color(theme::text_muted()));
314 - if ui.link(egui::RichText::new("Create one").color(theme::accent_blue())).clicked() {
313 + ui.label(egui::RichText::new("No collections yet.").color(theme::content_muted()));
314 + if ui.link(egui::RichText::new("Create one").color(theme::action())).clicked() {
315 315 state.show_collection_create = true;
316 316 state.collection_create_input.clear();
317 317 state.focus_inline_editor = true;
@@ -380,7 +380,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
380 380 ui.label(
381 381 egui::RichText::new(format!("Renaming: {orig}"))
382 382 .small()
383 - .color(theme::text_muted()),
383 + .color(theme::content_muted()),
384 384 );
385 385 }
386 386 let want_focus = std::mem::take(&mut state.focus_inline_editor);
@@ -457,7 +457,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
457 457 // Tags section — tree view for dot-separated tags
458 458 ui.collapsing("Tags", |ui| {
459 459 if state.all_tags.is_empty() {
460 - ui.label(egui::RichText::new("No tags yet").color(theme::text_muted()));
460 + ui.label(egui::RichText::new("No tags yet").color(theme::content_muted()));
461 461 } else {
462 462 // Compute the filtered set up front so the count indicator can
463 463 // render alongside the filter input.
@@ -491,7 +491,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
491 491 ui.label(
492 492 egui::RichText::new(format!("{} of {} tags", filtered_tags.len(), total))
493 493 .small()
494 - .color(theme::text_muted()),
494 + .color(theme::content_muted()),
495 495 );
496 496 }
497 497 ui.add_space(theme::space::SM);
@@ -506,7 +506,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
506 506 ui.label(
507 507 egui::RichText::new(format!("Renaming tag: {old_tag} \u{2192}"))
508 508 .small()
509 - .color(theme::text_muted()),
509 + .color(theme::content_muted()),
510 510 );
511 511 let Some((_, buf)) = state.tag_rename_target.as_mut() else { return };
512 512 let resp = ui.add(
@@ -549,7 +549,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
549 549 ui.label(
550 550 egui::RichText::new(summary)
551 551 .small()
552 - .color(theme::text_muted()),
552 + .color(theme::content_muted()),
553 553 );
554 554 if !descendants.is_empty() {
555 555 let preview: Vec<&str> = descendants
@@ -568,7 +568,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
568 568 "Descendant tags will not be renamed: {list}"
569 569 ))
570 570 .small()
571 - .color(theme::accent_yellow()),
571 + .color(theme::warning()),
572 572 );
573 573 }
574 574 }
@@ -584,7 +584,7 @@ pub fn draw_sidebar(ui: &mut egui::Ui, state: &mut BrowserState) {
584 584 }
585 585
586 586 if filtered_tags.is_empty() {
587 - ui.label(egui::RichText::new("No matching tags").color(theme::text_muted()));
587 + ui.label(egui::RichText::new("No matching tags").color(theme::content_muted()));
588 588 } else {
589 589 let tree = build_tag_tree(&filtered_tags);
590 590 for (segment, node) in &tree {
@@ -96,11 +96,11 @@ pub fn draw_sync_panel(
96 96 ui.separator();
97 97 ui.add_space(theme::space::SM);
98 98 egui::Frame::new()
99 - .fill(theme::bg_tertiary())
99 + .fill(theme::surface_sunken())
100 100 .corner_radius(egui::CornerRadius::same(4))
101 101 .inner_margin(egui::Margin::same(8))
102 102 .show(ui, |ui| {
103 - ui.label(egui::RichText::new(err).color(theme::accent_red()));
103 + ui.label(egui::RichText::new(err).color(theme::danger()));
104 104 ui.add_space(theme::space::SM);
105 105 ui.horizontal(|ui| {
106 106 let retryable = matches!(
@@ -474,7 +474,7 @@ fn draw_needs_encryption(
474 474 ui.label(
475 475 egui::RichText::new(msg)
476 476 .small()
477 - .color(theme::text_muted()),
477 + .color(theme::content_muted()),
478 478 );
479 479 }
480 480
@@ -642,7 +642,7 @@ fn draw_ready(
642 642 ui.label(
643 643 egui::RichText::new(format!(" {} across {}", widgets::format_bytes(*bytes), count_str))
644 644 .small()
645 - .color(theme::text_muted()),
645 + .color(theme::content_muted()),
646 646 );
647 647 }
648 648 }