feat(start): add a second intro video next to the first

Show two clickable video thumbnails side by side on the Start page; a
click opens the video in the system browser. Refactor the card into a
shared builder and bundle the second thumbnail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-02 00:27:25 +03:00
1 parent 78670a2a27
commit 80ad27ec67
2 files changed
+64 -44

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+64 -44
View File
@@ -2057,14 +2057,16 @@ pub(super) fn start_page_view<'a>() -> Element<'a, Message> {
} }
let secondary_row = secondary_row.spacing(12).align_y(iced::Center); let secondary_row = secondary_row.spacing(12).align_y(iced::Center);
// Intro video: a clickable thumbnail with a play badge. Native iced has no // Intro videos: clickable thumbnails with a play badge, side by side.
// embedded web player, so a click opens the video in the system browser. // Native iced has no embedded web player, so a click opens the video in the
// system browser.
const INTRO_VIDEO_URL: &str = "https://youtu.be/uN9zxM7p_fc"; const INTRO_VIDEO_URL: &str = "https://youtu.be/uN9zxM7p_fc";
// Build the image Handle ONCE — `Handle::from_bytes` mints a fresh unique id const INTRO_VIDEO_URL_2: &str = "https://youtu.be/4_glyJFo0qI";
// on every call, so doing it per-view re-decodes + re-uploads the JPEG each // Build each image Handle ONCE — `Handle::from_bytes` mints a fresh unique
// frame (the thumbnail appeared only after a long delay). A cached Handle // id on every call, so doing it per-view re-decodes + re-uploads the JPEG
// decodes once; cloning it per view shares the id + bytes. // each frame (the thumbnail then appears only after a long delay). A cached
let thumb_handle = { // Handle decodes once; cloning it per view shares the id + bytes.
fn intro_thumb_1() -> iced::widget::image::Handle {
use std::sync::OnceLock; use std::sync::OnceLock;
static H: OnceLock<iced::widget::image::Handle> = OnceLock::new(); static H: OnceLock<iced::widget::image::Handle> = OnceLock::new();
H.get_or_init(|| { H.get_or_init(|| {
@@ -2073,45 +2075,56 @@ pub(super) fn start_page_view<'a>() -> Element<'a, Message> {
) )
}) })
.clone() .clone()
}; }
let thumb = iced::widget::image(thumb_handle) fn intro_thumb_2() -> iced::widget::image::Handle {
.width(Fill) use std::sync::OnceLock;
.height(Fill) static H: OnceLock<iced::widget::image::Handle> = OnceLock::new();
.content_fit(iced::ContentFit::Cover); H.get_or_init(|| {
// White play triangle on a translucent dark disc, centred over the thumb. iced::widget::image::Handle::from_bytes(
let play_badge = container( include_bytes!("../../../assets/intro_thumb2.jpg").to_vec(),
container(crate::ui::icons::arrow_right(30.0, Color::WHITE)) )
.width(iced::Length::Fixed(72.0)) })
.height(iced::Length::Fixed(72.0)) .clone()
.center_x(iced::Length::Fixed(72.0)) }
.center_y(iced::Length::Fixed(72.0)) // One clickable video card: cover-fitted thumbnail with a white play triangle
.style(|_: &Theme| container::Style { // on a translucent disc, opening `url` in the browser on click.
background: Some(Background::Color(Color { let video_card = |handle: iced::widget::image::Handle, url: &str| {
r: 0.0, let thumb = iced::widget::image(handle)
g: 0.0, .width(Fill)
b: 0.0, .height(Fill)
a: 0.55, .content_fit(iced::ContentFit::Cover);
})), let play_badge = container(
border: Border { container(crate::ui::icons::arrow_right(24.0, Color::WHITE))
color: Color { .width(iced::Length::Fixed(56.0))
r: 1.0, .height(iced::Length::Fixed(56.0))
g: 1.0, .center_x(iced::Length::Fixed(56.0))
b: 1.0, .center_y(iced::Length::Fixed(56.0))
a: 0.85, .style(|_: &Theme| container::Style {
background: Some(Background::Color(Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.55,
})),
border: Border {
color: Color {
r: 1.0,
g: 1.0,
b: 1.0,
a: 0.85,
},
width: 2.0,
radius: 28.0.into(),
}, },
width: 2.0, ..Default::default()
radius: 36.0.into(), }),
}, )
..Default::default() .center_x(Fill)
}), .center_y(Fill);
)
.center_x(Fill)
.center_y(Fill);
let cards = container(
mouse_area( mouse_area(
container(iced::widget::stack![thumb, play_badge]) container(iced::widget::stack![thumb, play_badge])
.width(iced::Length::Fixed(720.0)) .width(iced::Length::Fixed(352.0))
.height(iced::Length::Fixed(405.0)) .height(iced::Length::Fixed(198.0))
.style(|_: &Theme| container::Style { .style(|_: &Theme| container::Style {
background: Some(Background::Color(CARD_BG)), background: Some(Background::Color(CARD_BG)),
border: Border { border: Border {
@@ -2124,7 +2137,14 @@ pub(super) fn start_page_view<'a>() -> Element<'a, Message> {
.clip(true), .clip(true),
) )
.interaction(iced::mouse::Interaction::Pointer) .interaction(iced::mouse::Interaction::Pointer)
.on_press(Message::OpenUrl(INTRO_VIDEO_URL.to_string())), .on_press(Message::OpenUrl(url.to_string()))
};
let cards = container(
iced::widget::row![
video_card(intro_thumb_1(), INTRO_VIDEO_URL),
video_card(intro_thumb_2(), INTRO_VIDEO_URL_2),
]
.spacing(12),
) )
.center_x(Fill); .center_x(Fill);