diff --git a/locales/en.yml b/locales/en.yml index dcd9b5b7..e058f6f3 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -644,22 +644,22 @@ goblin: delete_final: "Delete permanently" privacy: title: "Network privacy" - intro: "Goblin sends only a few things over the network. Here's what leaves your device, and how it stays private." + intro: "Goblin sends only a few things over the network, each sealed with end-to-end encryption so relays can't read them or link them to you." payments: "Payments" payments_blurb: "Every nostr message carrying a slatepack." - usernames: "usernames" - usernames_blurb: "NIP-05 name lookups to and from goblin.st." + usernames: "Usernames" + usernames_blurb: "NIP-05 name lookups to and from name authorities." price_avatars: "Price" price_avatars_blurb: "The live fiat rate shown next to amounts." grin_node: "Grin node" - grin_node_blurb: "Block sync and broadcasting your transaction to the network. This is public chain data, the same for everyone, and isn't linked to your identity." + grin_node_blurb: "Syncs blocks and broadcasts your transactions. Public chain data, the same for everyone, never linked to your identity." how_privacy: "How your privacy works" how_privacy_blurb: "Payments and messages are sealed with authenticated encryption and delivered as private messages, so a relay can't read them or link a payment back to you." always_direct: "Always direct" tor_off: "Tor off" tor_on: "Tor on" tor_switch: "Route through Tor" - tor_switch_sub: "Send your private traffic through the Tor network to hide your IP from relays." + tor_switch_sub: "Hide your IP from relays." vpn_note: "With Tor off, relays can see your IP address. We recommend using at least a VPN." pairing: title: "Pairing" diff --git a/src/gui/theme.rs b/src/gui/theme.rs index 09ed0741..288efac5 100644 --- a/src/gui/theme.rs +++ b/src/gui/theme.rs @@ -117,6 +117,12 @@ const AVATAR_PAIRS: [(Color32, Color32); 8] = [ ), // lime / dark moss ]; +/// The Goblin brand yellow (#FFD60A). Unlike [`ThemeTokens::accent`] (which +/// inverts to dark on the yellow theme so it reads on a bright bg), this is the +/// same brand yellow in every theme — used for status dots that must always +/// carry the brand color, e.g. the always-direct Grin-node dot. +pub const GOBLIN_YELLOW: Color32 = Color32::from_rgb(0xFF, 0xD6, 0x0A); + pub const LIGHT: ThemeTokens = ThemeTokens { bg: Color32::from_rgb(0xFA, 0xFA, 0xF7), surface: Color32::from_rgb(0xFF, 0xFF, 0xFF), diff --git a/src/gui/views/goblin/onboarding.rs b/src/gui/views/goblin/onboarding.rs index f090471a..9e6a748c 100644 --- a/src/gui/views/goblin/onboarding.rs +++ b/src/gui/views/goblin/onboarding.rs @@ -235,13 +235,15 @@ impl OnboardingContent { self.error = None; self.step = back; } - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - ui.label( - RichText::new(kicker) - .font(fonts::kicker()) - .color(t.text_mute), - ); - }); + if !kicker.is_empty() { + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + ui.label( + RichText::new(kicker) + .font(fonts::kicker()) + .color(t.text_mute), + ); + }); + } }); ui.add_space(18.0); ui.label( @@ -873,12 +875,10 @@ impl OnboardingContent { } else { Step::ConfirmWords }; - self.step_header( - ui, - &t!("goblin.onboarding.privacy.kicker"), - &t!("goblin.privacy.title"), - back, - ); + // No top-right kicker here — the Network-privacy screen shows just the + // back chip + title, matching the settings sub-pages (Advanced Privacy, + // Node) so the two entry points read identically. + self.step_header(ui, "", &t!("goblin.privacy.title"), back); if let Some(new_val) = super::network_privacy_panels(ui, self.tor_choice) { self.tor_choice = new_val; } diff --git a/src/gui/views/goblin/privacy.rs b/src/gui/views/goblin/privacy.rs index d481943f..17210be3 100644 --- a/src/gui/views/goblin/privacy.rs +++ b/src/gui/views/goblin/privacy.rs @@ -70,32 +70,22 @@ pub(super) fn settings_group_colored( /// onboarding stashes it and writes it before the service starts). pub(super) fn network_privacy_panels(ui: &mut egui::Ui, tor_on: bool) -> Option { let t = theme::tokens(); + // One short intro block: what Goblin sends + how it stays private + // (deliberately no crypto version numbers). ui.label( RichText::new(t!("goblin.privacy.intro")) .font(FontId::new(14.0, fonts::regular())) .color(t.text_dim), ); - ui.add_space(14.0); - ui.label( - RichText::new(t!("goblin.privacy.how_privacy")) - .font(FontId::new(14.0, fonts::semibold())) - .color(t.text), - ); - ui.add_space(4.0); - ui.label( - RichText::new(t!("goblin.privacy.how_privacy_blurb")) - .font(FontId::new(13.0, fonts::regular())) - .color(t.text_dim), - ); ui.add_space(16.0); - // Grin node — ALWAYS DIRECT, grey dot, full width. Public chain data, the - // same for everyone, never routed over Tor. + // Grin node — ALWAYS DIRECT, brand-yellow dot, full width. Public chain + // data, the same for everyone, never routed over Tor. settings_group(ui, &t!("goblin.privacy.always_direct"), |ui| { ui.set_min_width(ui.available_width()); privacy_line( ui, - t.surface_text_mute, + theme::GOBLIN_YELLOW, &t!("goblin.privacy.grin_node"), &t!("goblin.privacy.grin_node_blurb"), ); @@ -132,30 +122,30 @@ pub(super) fn network_privacy_panels(ui: &mut egui::Ui, tor_on: bool) -> Option< }); ui.add_space(18.0); - // Large Tor switch — dormant gray when off, blueviolet when on (never - // yellow). The color state lives on the dots above; this reads on/off. + // Large Tor switch — the whole card is stacked and centered so it reads as + // "flip between Tor and direct": title on top, the big switch on its own + // row, then a full-width caption below that can never clip. The switch is + // dormant gray when off, blueviolet when on (never yellow) — the color state + // lives on the dots above; this reads on/off. let mut toggled = None; w::card(ui, |ui| { ui.set_min_width(ui.available_width()); - ui.horizontal(|ui| { - ui.vertical(|ui| { - ui.label( - RichText::new(t!("goblin.privacy.tor_switch")) - .font(FontId::new(15.0, fonts::semibold())) - .color(t.surface_text), - ); - ui.add_space(2.0); - ui.label( - RichText::new(t!("goblin.privacy.tor_switch_sub")) - .font(FontId::new(12.5, fonts::regular())) - .color(t.surface_text_dim), - ); - }); - ui.with_layout(Layout::right_to_left(Align::Center), |ui| { - if w::toggle_large(ui, tor_on).clicked() { - toggled = Some(!tor_on); - } - }); + ui.vertical_centered(|ui| { + ui.label( + RichText::new(t!("goblin.privacy.tor_switch")) + .font(FontId::new(15.0, fonts::semibold())) + .color(t.surface_text), + ); + ui.add_space(12.0); + if w::toggle_large(ui, tor_on).clicked() { + toggled = Some(!tor_on); + } + ui.add_space(12.0); + ui.label( + RichText::new(t!("goblin.privacy.tor_switch_sub")) + .font(FontId::new(12.5, fonts::regular())) + .color(t.surface_text_dim), + ); }); }); ui.add_space(12.0); diff --git a/src/gui/views/goblin/widgets.rs b/src/gui/views/goblin/widgets.rs index f5544de2..d6d89bdb 100644 --- a/src/gui/views/goblin/widgets.rs +++ b/src/gui/views/goblin/widgets.rs @@ -244,15 +244,15 @@ pub fn toggle(ui: &mut Ui, on: bool) -> Response { /// the app accent. Returns the response; the caller flips the bound state. pub fn toggle_large(ui: &mut Ui, on: bool) -> Response { let t = theme::tokens(); - let (rect, resp) = ui.allocate_exact_size(Vec2::new(64.0, 36.0), Sense::click()); + let (rect, resp) = ui.allocate_exact_size(Vec2::new(96.0, 54.0), Sense::click()); let track = if on { t.tor_purple } else { t.surface2 }; ui.painter() - .rect_filled(rect, CornerRadius::same(18), track); - let knob_r = 15.0; + .rect_filled(rect, CornerRadius::same(27), track); + let knob_r = 22.0; let knob_x = if on { - rect.right() - knob_r - 3.0 + rect.right() - knob_r - 5.0 } else { - rect.left() + knob_r + 3.0 + rect.left() + knob_r + 5.0 }; let knob = if on { Color32::WHITE