Tint home greeting glyph by time of day

The design's home hero pairs the greeting with a Sunrise glyph in the
warm horizon orange (#e89a6e). Previously we showed `IconName::Sun`
in `ACCENT_LIGHT` regardless of phase, so an evening user saw a noon
sun next to "Good evening, Alex".

Promote the day phase to a `DayPhase` enum so hero.rs can pick the
glyph + tint per phase: warm orange Sun in the morning, amber Sun in
the afternoon, cool violet Moon in the evening. Tests now compare
against the enum instead of the former `&'static str` wrapper.

Bonus: third quick-launch pill swaps from "Open History" (Undo2) to
"Open Notion" (BookOpen → notion.so) to match the design's third pill.

cargo test --workspace: 440 passed, 0 failed.
This commit is contained in:
2026-05-09 20:39:26 -04:00
parent c85023170c
commit c27776ca42
3 changed files with 72 additions and 30 deletions
+28 -9
View File
@@ -12,21 +12,28 @@ use crate::shell::chrome::SERIF_FAMILY;
use super::style::{
ARROW_CHIP_BG, PILL_BG, PILL_BG_HOVER, SEARCH_BG, card_shadow, soft_shadow,
};
use super::time::DayPhase;
pub(crate) fn render_hero(greeting: String, cx: &mut Context<ElyShell>) -> AnyElement {
pub(crate) fn render_hero(
greeting: String,
phase: DayPhase,
cx: &mut Context<ElyShell>,
) -> AnyElement {
div()
.flex()
.flex_col()
.items_center()
.gap(px(14.0))
.child(render_greeting_row(greeting))
.child(render_greeting_row(greeting, phase))
.child(render_serif_headline())
.child(render_search_bar(cx))
.child(render_suggestion_pills(cx))
.into_any_element()
}
fn render_greeting_row(text: String) -> AnyElement {
fn render_greeting_row(text: String, phase: DayPhase) -> AnyElement {
let (icon, color) = phase_glyph(phase);
div()
.flex()
.items_center()
@@ -35,13 +42,25 @@ fn render_greeting_row(text: String) -> AnyElement {
.text_color(rgb(colors::INK_3))
.child(
div()
.text_color(rgb(colors::ACCENT_LIGHT))
.child(IconName::Sun),
.text_color(rgb(color))
.child(icon),
)
.child(text)
.into_any_element()
}
/// Pair the greeting glyph to the time of day. Morning gets the warm
/// horizon orange the design uses for the Sunrise icon (#e89a6e); afternoon
/// keeps the same Sun glyph in a higher-key amber; evening swaps to the
/// Moon in the cool slate-violet that bookends the wallpaper themes.
fn phase_glyph(phase: DayPhase) -> (IconName, u32) {
match phase {
DayPhase::Morning => (IconName::Sun, 0xe89a6e),
DayPhase::Afternoon => (IconName::Sun, 0xf5b563),
DayPhase::Evening => (IconName::Moon, 0x7c6cf7),
}
}
fn render_serif_headline() -> AnyElement {
div()
.font_family(SERIF_FAMILY)
@@ -117,11 +136,11 @@ fn render_suggestion_pills(cx: &mut Context<ElyShell>) -> AnyElement {
|shell, window, cx| shell.cycle_to_next_space(window, cx),
))
.child(render_pill(
"pill-open-history",
IconName::Undo2,
"Open History",
"pill-open-notion",
IconName::BookOpen,
"Open Notion",
cx,
|shell, window, cx| shell.open_internal_tab("ely://history", window, cx),
|shell, window, cx| shell.open_internal_tab("https://www.notion.so", window, cx),
))
.into_any_element()
}
+4 -2
View File
@@ -17,7 +17,9 @@ pub(crate) fn render_home_page(
snapshot: &BrowserSnapshot,
cx: &mut Context<ElyShell>,
) -> AnyElement {
let greeting = time::greeting_for_now(SystemTime::now(), &snapshot.active_profile_name);
let now = SystemTime::now();
let greeting = time::greeting_for_now(now, &snapshot.active_profile_name);
let phase = time::day_phase_for(now);
div()
.flex_1()
@@ -31,7 +33,7 @@ pub(crate) fn render_home_page(
.pb(px(28.0))
.flex()
.flex_col()
.child(hero::render_hero(greeting, cx))
.child(hero::render_hero(greeting, phase, cx))
.child(div().mt(px(48.0)).child(favorites::render_favorites_grid(snapshot, cx)))
.child(div().mt(px(16.0)).child(recap::render_recap(snapshot, cx))),
)
+40 -19
View File
@@ -1,15 +1,43 @@
use std::time::{SystemTime, UNIX_EPOCH};
pub(crate) fn greeting_for_now(now: SystemTime, name: &str) -> String {
let phase = match now.duration_since(UNIX_EPOCH) {
Ok(duration) => {
let local_seconds = duration.as_secs() as i64;
let day_seconds = local_seconds.rem_euclid(86_400);
let hour = (day_seconds / 3_600) as u32;
day_phase(hour)
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DayPhase {
Morning,
Afternoon,
Evening,
}
impl DayPhase {
fn as_str(self) -> &'static str {
match self {
DayPhase::Morning => "morning",
DayPhase::Afternoon => "afternoon",
DayPhase::Evening => "evening",
}
Err(_) => "today",
}
}
pub(crate) fn day_phase_for(now: SystemTime) -> DayPhase {
let hour = match now.duration_since(UNIX_EPOCH) {
Ok(duration) => {
let day_seconds = (duration.as_secs() as i64).rem_euclid(86_400);
(day_seconds / 3_600) as u32
}
Err(_) => 12,
};
day_phase_from_hour(hour)
}
pub(crate) fn day_phase_from_hour(hour_utc: u32) -> DayPhase {
match hour_utc {
5..=11 => DayPhase::Morning,
12..=17 => DayPhase::Afternoon,
_ => DayPhase::Evening,
}
}
pub(crate) fn greeting_for_now(now: SystemTime, name: &str) -> String {
let phase = day_phase_for(now).as_str();
if name.is_empty() {
format!("Good {phase}")
@@ -18,13 +46,6 @@ pub(crate) fn greeting_for_now(now: SystemTime, name: &str) -> String {
}
}
pub(crate) fn day_phase(hour_utc: u32) -> &'static str {
match hour_utc {
5..=11 => "morning",
12..=17 => "afternoon",
_ => "evening",
}
}
pub(crate) fn relative_time_label(now: SystemTime, then: SystemTime) -> String {
let elapsed = match now.duration_since(then) {
@@ -53,22 +74,22 @@ pub(crate) fn relative_time_label(now: SystemTime, then: SystemTime) -> String {
#[cfg(test)]
mod tests {
use super::{day_phase, greeting_for_now, relative_time_label};
use super::{DayPhase, day_phase_from_hour, greeting_for_now, relative_time_label};
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn day_phase_assigns_morning_to_seven_am() {
assert_eq!(day_phase(7), "morning");
assert_eq!(day_phase_from_hour(7), DayPhase::Morning);
}
#[test]
fn day_phase_assigns_afternoon_to_one_pm() {
assert_eq!(day_phase(13), "afternoon");
assert_eq!(day_phase_from_hour(13), DayPhase::Afternoon);
}
#[test]
fn day_phase_assigns_evening_to_eleven_pm() {
assert_eq!(day_phase(23), "evening");
assert_eq!(day_phase_from_hour(23), DayPhase::Evening);
}
#[test]