diff --git a/assets/intro_thumb.jpg b/assets/intro_thumb.jpg new file mode 100644 index 000000000..438125dbd Binary files /dev/null and b/assets/intro_thumb.jpg differ diff --git a/src/app/mod.rs b/src/app/mod.rs index 0c9af8605..a0b5f92a6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1009,6 +1009,8 @@ pub enum Message { /// Open a path from the Start tab's recent-documents list (skips the /// file picker; the path is already known). OpenRecent(PathBuf), + /// Open a URL in the system browser (start-page intro video, links). + OpenUrl(String), /// Drop a path from the recent-documents list. RecentRemove(PathBuf), /// User clicked Cancel on the loading overlay. The parser thread keeps diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index 5c66c9367..b3daebfb7 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -208,6 +208,11 @@ impl OpenCADStudio { Message::OpenPathPicked(None) => Task::none(), + Message::OpenUrl(url) => { + crate::sys::open_url(&url); + Task::none() + } + Message::OpenRecent(path) => { // Recents are read from disk every save → the path may be // stale. Skip silently if the file no longer exists; the diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 3bcc531c5..b84ddfb37 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1997,44 +1997,76 @@ pub(super) fn start_page_view<'a>() -> Element<'a, Message> { } let secondary_row = secondary_row.spacing(12).align_y(iced::Center); - let card_section = |heading: &'static str, sub: &'static str, body: &'static str| { - container( - column![ - text(heading).size(20).color(TEXT), - text(sub).size(13).color(MUTED), - Space::new().height(iced::Length::Fixed(10.0)), - text(body).size(12).color(MUTED), - ] - .spacing(6), - ) - .padding(20) - .width(Fill) - .style(|_: &Theme| container::Style { - background: Some(Background::Color(CARD_BG)), - border: Border { - color: CARD_BORDER, - width: 1.0, - radius: 8.0.into(), - }, - ..Default::default() + // Intro video: a clickable thumbnail with a play badge. 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"; + // Build the image Handle ONCE — `Handle::from_bytes` mints a fresh unique id + // on every call, so doing it per-view re-decodes + re-uploads the JPEG each + // frame (the thumbnail appeared only after a long delay). A cached Handle + // decodes once; cloning it per view shares the id + bytes. + let thumb_handle = { + use std::sync::OnceLock; + static H: OnceLock = OnceLock::new(); + H.get_or_init(|| { + iced::widget::image::Handle::from_bytes( + include_bytes!("../../../assets/intro_thumb.jpg").to_vec(), + ) }) + .clone() }; - - let cards = row![ - card_section( - "Free and open source", - "Open CAD Studio belongs to its users.", - "Source is openly developed and distributed under a permissive licence. \ - You are free to use, study, modify, and share Open CAD Studio.", - ), - card_section( - "Community driven", - "Be part of the story.", - "Anyone can contribute — translations, bug reports, feature ideas, code, \ - or simply spreading the word. Your involvement shapes the project.", - ), - ] - .spacing(16); + let thumb = iced::widget::image(thumb_handle) + .width(Fill) + .height(Fill) + .content_fit(iced::ContentFit::Cover); + // White play triangle on a translucent dark disc, centred over the thumb. + let play_badge = container( + container(crate::ui::icons::arrow_right(30.0, Color::WHITE)) + .width(iced::Length::Fixed(72.0)) + .height(iced::Length::Fixed(72.0)) + .center_x(iced::Length::Fixed(72.0)) + .center_y(iced::Length::Fixed(72.0)) + .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: 36.0.into(), + }, + ..Default::default() + }), + ) + .center_x(Fill) + .center_y(Fill); + let cards = container( + mouse_area( + container(iced::widget::stack![thumb, play_badge]) + .width(iced::Length::Fixed(720.0)) + .height(iced::Length::Fixed(405.0)) + .style(|_: &Theme| container::Style { + background: Some(Background::Color(CARD_BG)), + border: Border { + color: CARD_BORDER, + width: 1.0, + radius: 8.0.into(), + }, + ..Default::default() + }) + .clip(true), + ) + .interaction(iced::mouse::Interaction::Pointer) + .on_press(Message::OpenUrl(INTRO_VIDEO_URL.to_string())), + ) + .center_x(Fill); // Buttons sit on a transparent container with a large, brand-tinted // ambient shadow (offset = 0, big blur) — produces a soft halo behind