diff --git a/assets/brand/moonbot-logo.svg b/assets/brand/moonbot-logo.svg
deleted file mode 100644
index 151dc60b..00000000
--- a/assets/brand/moonbot-logo.svg
+++ /dev/null
@@ -1,19 +0,0 @@
-
\ No newline at end of file
diff --git a/assets/brand/moonterminal-logo-blue-dark.svg b/assets/brand/moonterminal-logo-blue-dark.svg
new file mode 100644
index 00000000..d375f93c
--- /dev/null
+++ b/assets/brand/moonterminal-logo-blue-dark.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/brand/moonterminal-logo-blue-light.svg b/assets/brand/moonterminal-logo-blue-light.svg
new file mode 100644
index 00000000..190ecff1
--- /dev/null
+++ b/assets/brand/moonterminal-logo-blue-light.svg
@@ -0,0 +1,9 @@
+
diff --git a/crates/moon-ui-gpui/src/chart_tabs/add_stack.rs b/crates/moon-ui-gpui/src/chart_tabs/add_stack.rs
index a37ca888..3bbf8040 100644
--- a/crates/moon-ui-gpui/src/chart_tabs/add_stack.rs
+++ b/crates/moon-ui-gpui/src/chart_tabs/add_stack.rs
@@ -1130,7 +1130,10 @@ impl Render for AddChartStack {
.flex()
.items_center()
.justify_center()
- .child(crate::design::logo_glow_sized(cx, 220.0))
+ .child(crate::design::logo_glow_sized(
+ cx,
+ crate::design::EMPTY_STACK_LOGO_W,
+ ))
.into_any_element();
// Measured here too: a window resized while the tab holds no charts would otherwise
// leave a stale size behind, and the first frame WITH charts would divide by it.
diff --git a/crates/moon-ui-gpui/src/chart_tabs/main_stack.rs b/crates/moon-ui-gpui/src/chart_tabs/main_stack.rs
index fcd88f31..2645db64 100644
--- a/crates/moon-ui-gpui/src/chart_tabs/main_stack.rs
+++ b/crates/moon-ui-gpui/src/chart_tabs/main_stack.rs
@@ -1621,7 +1621,10 @@ impl Render for MainChartStack {
.flex()
.items_center()
.justify_center()
- .child(crate::design::logo_glow_sized(cx, 220.0))
+ .child(crate::design::logo_glow_sized(
+ cx,
+ crate::design::EMPTY_STACK_LOGO_W,
+ ))
.into_any_element();
// Measured here too, for the reason the fullscreen branch keeps its probe: a resize
// taken while the stack is empty must not leave a size the first divided frame uses.
diff --git a/crates/moon-ui-gpui/src/chrome/terminal_chrome.rs b/crates/moon-ui-gpui/src/chrome/terminal_chrome.rs
index 2c41b416..8515898d 100644
--- a/crates/moon-ui-gpui/src/chrome/terminal_chrome.rs
+++ b/crates/moon-ui-gpui/src/chrome/terminal_chrome.rs
@@ -9,7 +9,7 @@ use gpui::*;
use moon_ui::{
MoonButton, MoonButtonIconSlot, MoonButtonSize, MoonButtonVariant, MoonDropdown, MoonMenuItem,
MoonMenuSize, MoonPalette, MoonPopover, MoonPopoverPlacement, MoonPopupMenu, MoonRect,
- MoonSelectorPill, MoonSelectorSegment, MoonTag, MoonWindowFrame, MoonWindowFrameBrand, h_flex,
+ MoonSelectorPill, MoonSelectorSegment, MoonTag, MoonWindowFrame, h_flex,
};
use rust_i18n::t;
@@ -136,19 +136,29 @@ pub fn header(
// internally, so the seams line up.
.gap(design::ui_px(cx, design::CHROME_GAP))
.bg(rgb(p.shell_high))
- // Brand draws its OWN trailing separator (MoonWindowFrame::brand_cluster), so the
- // groups below add only the seams after them. It is also the FIRST thing this row gives
- // up on a narrow window (`design::header_brand_visible`): dropping the mark to `None`
- // keeps the drag region and takes the rule with it, so the space goes to the readouts
- // that carry information instead.
+ // The brand carries its OWN trailing separator, so the groups below add only the seams
+ // after them. It is also the FIRST thing this row gives up on a narrow window
+ // (`design::header_brand_visible`): dropping the mark keeps the drag region and takes the
+ // rule with it, so the space goes to the readouts that carry information instead.
+ //
+ // The cluster is assembled here rather than through `MoonWindowFrame::brand_cluster`,
+ // which draws MoonUI's own Moonbot lockup: the terminal ships the MoonTerminal one from
+ // `assets/brand`. Only the drag handle comes from the frame — that region is what makes
+ // the titlebar grabbable, and `brand_cluster` is nothing more than that handle plus the
+ // two children added below, at the same `CHROME_GAP` and the same rule height. The rule
+ // itself is `chrome_divider`, not MoonUI's fainter `border` copy of it: owning the cluster
+ // means this seam answers to the same contrast rule as every other seam in the row.
.child(
MoonWindowFrame::main("terminal-header-brand-drag", 0.0)
- .brand(if design::header_brand_visible(cx, chrome_width) {
- MoonWindowFrameBrand::Default
- } else {
- MoonWindowFrameBrand::None
+ .drag_handle()
+ .flex()
+ .items_center()
+ .gap(design::ui_px(cx, design::CHROME_GAP))
+ .when(design::header_brand_visible(cx, chrome_width), |cluster| {
+ cluster
+ .child(design::header_logo(cx))
+ .child(design::chrome_divider(cx, p))
})
- .brand_cluster(cx)
.flex_none()
.h_full(),
)
diff --git a/crates/moon-ui-gpui/src/design.rs b/crates/moon-ui-gpui/src/design.rs
index f2cdadd9..99b092fa 100644
--- a/crates/moon-ui-gpui/src/design.rs
+++ b/crates/moon-ui-gpui/src/design.rs
@@ -7,7 +7,7 @@ use gpui::*;
use moon_core::util::fmt::DeltaSign;
use moon_ui::{MoonMetrics, MoonPalette, MoonTheme, MoonTone, rgba_from};
use std::collections::HashMap;
-use std::sync::Arc;
+use std::sync::{Arc, LazyLock};
const M: MoonMetrics = MoonMetrics::TERMINAL;
@@ -191,6 +191,11 @@ pub const HEADER_LABEL_MAX_W: f32 = 260.0;
/// readouts would disappear at the same width and the priority above would be a comment rather
/// than a behaviour. Usable only because [`HEADER_LABEL_MAX_W`] bounds the clusters that would
/// otherwise grow without limit.
+///
+/// These are TUNED window widths, not values derived from what the row holds, so the brand's own
+/// width does not enter them: the MoonTerminal lockup is 36px wider than the Moonbot one these
+/// were set against, and the `flex_1` drag spacer absorbs the difference. Re-tune them by looking
+/// at the row, not by arithmetic on the mark.
const HEADER_BRAND_MIN_W: f32 = 1400.0;
const TICKER_DELTAS_MIN_W: f32 = 1200.0;
const TICKER_MIN_W: f32 = 1000.0;
@@ -252,11 +257,52 @@ pub fn platform_window_decorations() -> Option {
}
}
-const LOGO_GLOW_SVG_RAW: &str = include_str!("../../../assets/brand/moonbot-logo.svg");
-const LOGO_SRC_W: f32 = 199.0;
+/// The MoonTerminal lockup, one cut per colour scheme.
+///
+/// Each file already carries its own wordmark colour — `#E7E7E7` in the dark cut, `#17202A` in the
+/// light one, with the blue "Terminal" shared — so the terminal PICKS a file instead of patching a
+/// fill the way the old single-cut Moonbot asset needed. A custom palette therefore no longer
+/// recolours the brand, which is the point of a brand asset.
+const LOGO_SVG_DARK: &str = include_str!("../../../assets/brand/moonterminal-logo-blue-dark.svg");
+const LOGO_SVG_LIGHT: &str = include_str!("../../../assets/brand/moonterminal-logo-blue-light.svg");
+const LOGO_SRC_W: f32 = 283.23;
const LOGO_SRC_H: f32 = 43.0;
-const LOGO_GLOW_VIEW_W: f32 = LOGO_SRC_W * 1.2;
-const LOGO_GLOW_VIEW_H: f32 = LOGO_SRC_W * 1.2;
+/// Factor carrying every lockup size across the brand swap, from the 199px-wide Moonbot mark the
+/// sizes below were tuned against to the 283.23px-wide MoonTerminal one.
+///
+/// The port keeps the ICON's drawn size rather than the box it sat in: only the wordmark got
+/// longer, and holding a width tuned for the shorter mark would shrink the whole lockup by a third
+/// and read as a different, smaller logo. Spelling the factor ONCE is the load-bearing part —
+/// these five sizes live in two files, and hand-multiplying each one is how four of them stay
+/// right while the fifth quietly does not.
+const LOGO_PORT_SCALE: f32 = LOGO_SRC_W / 199.0;
+
+/// Width of the placeholder lockup a chart stack draws while it holds no charts.
+pub const EMPTY_STACK_LOGO_W: f32 = 220.0 * LOGO_PORT_SCALE;
+
+/// Share of a chart slot's width the same placeholder covers, bounded by [`CHART_LOGO_MIN_W`] and
+/// [`CHART_LOGO_MAX_W`] — a floor so it stays legible in a small pane, a ceiling so it stays a
+/// watermark in a large one.
+///
+/// The floor is ported like everything else: leaving it where it was would have kept exactly the
+/// small panes — the ones that need it — drawing an icon a third smaller than before. It buys that
+/// at the far end: a pane narrower than the floor itself now clips the LOCKUP, where the shorter
+/// mark still fitted. That pane is under 256 logical px wide — narrower than the placeholder it
+/// would be showing — and between there and 643 px, where the share takes over, the floor is what
+/// keeps the icon at its designed size. The glow frame is [`LOGO_GLOW_SCALE`] times these widths
+/// and overhangs sooner, which costs nothing visible: its aura reaches zero opacity at that edge.
+pub const CHART_LOGO_SLOT_SHARE: f32 = 0.28 * LOGO_PORT_SCALE;
+pub const CHART_LOGO_MIN_W: f32 = 180.0 * LOGO_PORT_SCALE;
+pub const CHART_LOGO_MAX_W: f32 = 280.0 * LOGO_PORT_SCALE;
+
+/// Header lockup width: the header strip is height-constrained, so the longer wordmark has to buy
+/// its room in width, or the mark shrinks inside a row whose height cannot follow it.
+const HEADER_LOGO_W: f32 = 86.0 * LOGO_PORT_SCALE;
+
+/// How much wider than the lockup the glow canvas is; the aura fills the difference.
+const LOGO_GLOW_SCALE: f32 = 1.2;
+/// The glow canvas is SQUARE, so the aura reads as a disc rather than a flat ellipse.
+const LOGO_GLOW_VIEW: f32 = LOGO_SRC_W * LOGO_GLOW_SCALE;
pub fn solid(hex: u32) -> Rgba {
rgb(hex)
@@ -1056,32 +1102,72 @@ pub fn r_container(cx: &App) -> Pixels {
ui_px(cx, R_CONTAINER_BASE)
}
-/// Brand-book navy for the Moonbot wordmark in the light theme.
+/// Return the lockup cut that belongs to a colour scheme.
///
-/// Navy lacks contrast in the dark theme, where the wordmark uses `p.text` instead.
-const LOGO_WORDMARK_NAVY: u32 = 0x0C2C4A;
+/// Each file carries its own wordmark colour, so the scheme picks a FILE; nothing recolours the
+/// brand at runtime, and a custom palette therefore cannot repaint it.
+fn logo_svg(light: bool) -> &'static str {
+ if light { LOGO_SVG_LIGHT } else { LOGO_SVG_DARK }
+}
-pub fn logo_glow_sized(cx: &App, width: f32) -> impl IntoElement {
- let p = MoonPalette::active(cx);
- let text = if p.is_light() {
- LOGO_WORDMARK_NAVY
- } else {
- p.text
- };
- let text_fill = format!("#{text:06X}");
- let logo =
- LOGO_GLOW_SVG_RAW.replace(r##"fill="#E7E7E7""##, &format!(r##"fill="{text_fill}""##));
- let paths = logo
- .split_once(r#""#)
- .and_then(|(_, rest)| rest.split_once(""))
- .map(|(paths, _)| paths)
- .unwrap_or("");
- let cx = LOGO_GLOW_VIEW_W * 0.5;
- let cy = LOGO_GLOW_VIEW_H * 0.5;
- let r = LOGO_GLOW_VIEW_W * 0.5;
- let logo_x = (LOGO_GLOW_VIEW_W - LOGO_SRC_W) * 0.5;
- let logo_y = (LOGO_GLOW_VIEW_H - LOGO_SRC_H) * 0.5;
- let (aura_0_color, aura_1_color, aura_2_color, aura_0, aura_1, aura_2) = if p.is_light() {
+/// Return everything the lockup's root `