-
- shux
-
-
+
+
+
+
+
Loading Shux
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
index 351f330a25..d982c0a23f 100644
Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ
diff --git a/public/favicon-dark.ico b/public/favicon-dark.ico
index d887f7e61a..ffd11ab43c 100644
Binary files a/public/favicon-dark.ico and b/public/favicon-dark.ico differ
diff --git a/public/favicon.ico b/public/favicon.ico
index 5710a4d9ed..8c3d1b321d 100644
Binary files a/public/favicon.ico and b/public/favicon.ico differ
diff --git a/public/icon-192.png b/public/icon-192.png
index 886d539786..9450c96cbc 100644
Binary files a/public/icon-192.png and b/public/icon-192.png differ
diff --git a/public/icon-512.png b/public/icon-512.png
index 0c1f7184f6..37bd8efc0c 100644
Binary files a/public/icon-512.png and b/public/icon-512.png differ
diff --git a/public/icon.png b/public/icon.png
index 0c1f7184f6..37bd8efc0c 100644
Binary files a/public/icon.png and b/public/icon.png differ
diff --git a/public/tray-icon-black.png b/public/tray-icon-black.png
index 445076da22..db70be3ecd 100644
Binary files a/public/tray-icon-black.png and b/public/tray-icon-black.png differ
diff --git a/public/tray-icon-black@2x.png b/public/tray-icon-black@2x.png
index b2f5a9f59a..cb0293372c 100644
Binary files a/public/tray-icon-black@2x.png and b/public/tray-icon-black@2x.png differ
diff --git a/public/tray-icon-black@3x.png b/public/tray-icon-black@3x.png
index d8c6725ab4..ecbe72aa13 100644
Binary files a/public/tray-icon-black@3x.png and b/public/tray-icon-black@3x.png differ
diff --git a/public/tray-icon-white.png b/public/tray-icon-white.png
index a9bab0cf3e..cebfc459b1 100644
Binary files a/public/tray-icon-white.png and b/public/tray-icon-white.png differ
diff --git a/public/tray-icon-white@2x.png b/public/tray-icon-white@2x.png
index 7f3984fd85..e8549f635c 100644
Binary files a/public/tray-icon-white@2x.png and b/public/tray-icon-white@2x.png differ
diff --git a/public/tray-icon-white@3x.png b/public/tray-icon-white@3x.png
index dae2290bdc..5be7a8a8a3 100644
Binary files a/public/tray-icon-white@3x.png and b/public/tray-icon-white@3x.png differ
diff --git a/scripts/generate-icons.ts b/scripts/generate-icons.ts
index 01af89d257..74253aedbc 100644
--- a/scripts/generate-icons.ts
+++ b/scripts/generate-icons.ts
@@ -6,7 +6,7 @@
* bun scripts/generate-icons.ts [commands...]
*
* Commands:
- * update - Regenerate all derived icons from docs/img/logo-*.svg
+ * update - Regenerate all logo sources and derived assets
* png - Generate build/icon.png (512x512)
* icns - Generate build/icon.icns (macOS app icon)
* linux-icons - Generate build/icons/{16x16..512x512}.png (Linux icon set)
@@ -17,6 +17,12 @@ import { mkdir, rm, copyFile, readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import sharp from "sharp";
+import {
+ renderAppWordmark,
+ renderDocsWordmark,
+ renderInlineAppWordmark,
+ renderSquareLogo,
+} from "./logoAssets";
const ICONSET_SIZES = [16, 32, 64, 128, 256, 512];
const FAVICON_SIZES = [16, 32, 48, 64, 128, 256];
@@ -29,6 +35,18 @@ const ROOT = path.resolve(__dirname, "..");
const SOURCE_BLACK = path.join(ROOT, "docs", "img", "logo-black.svg");
const SOURCE_WHITE = path.join(ROOT, "docs", "img", "logo-white.svg");
+const WORDMARK_TARGETS = {
+ "docs/img/black-shux.svg": renderDocsWordmark("black"),
+ "docs/img/white-shux.svg": renderDocsWordmark("white"),
+ "src/browser/assets/logos/shux-logo-dark.svg": renderAppWordmark("white"),
+ "src/browser/assets/logos/shux-logo-light.svg": renderAppWordmark("black"),
+} as const;
+
+const INLINE_WORDMARK_TARGETS = {
+ "index.html": "boot-loader__logo",
+ "static/splash.html": "logo",
+} as const;
+
// Build outputs
const BUILD_DIR = path.join(ROOT, "build");
const ICONSET_DIR = path.join(BUILD_DIR, "icon.iconset");
@@ -72,12 +90,10 @@ type LogoTargetConfig = RasterTargetConfig | SvgTargetConfig;
const MONO_ICON = { source: SOURCE_BLACK, bg: false } as const;
const APP_ICON = { source: SOURCE_WHITE, bg: true } as const;
-// The source SVGs use viewBox="0 0 72 72" with a translate transform, leaving
-// ~68% internal padding around the actual "m" + cursor mark. This cropped
-// viewBox eliminates that padding so the mark fills the rendered image.
-// Content bounds (after transform): x 8.85…63.15, y 24.5…47.5 → 54.3×23 units.
-// Tight crop with ~0.5u breathing room: "8 24 56 24" → aspect ratio ≈ 2.33:1.
-const TRAY_MARK_CROP = "8 24 56 24";
+// Crop the centered square source to the outlined "s" + cursor bounds so the
+// mark fills small tray images without clipping the glyph's natural overshoot.
+// Content bounds: x 14.37…57.63, y 24.5…47.99 → 43.26×23.49 units.
+const TRAY_MARK_CROP = "13.5 24 45 24.5";
// Targets to update (path -> config)
const LOGO_TARGETS = {
@@ -98,13 +114,13 @@ const LOGO_TARGETS = {
// iOS Safari uses apple-touch-icon for home screen installs.
"public/apple-touch-icon.png": { size: 180, ...APP_ICON },
- // Electron Tray Icons – Wide Canvas with "m" Mark (Monochrome on Transparent)
+ // Electron Tray Icons – Wide Shux Mark (Monochrome on Transparent)
//
// The source SVGs have heavy internal padding (mark uses ~32% of canvas).
// We crop to TRAY_MARK_CROP before rendering so the mark fills the output.
//
// Pixel dimensions: 24×24 @1x → 48×48 @2x → 72×72 @3x.
- // Square canvas; the mark (aspect ≈ 2.33:1) is height-constrained and
+ // Square canvas; the mark (aspect ≈ 1.84:1) is height-constrained and
// centered horizontally with transparent side padding.
//
// macOS treats the black variant as a template image (adapts to light/dark
@@ -225,9 +241,48 @@ async function generateThemeFaviconSvg(output: string) {
await writeFile(output, themedSvg);
}
+async function replaceInlineWordmark(relativePath: string, className: string) {
+ const outputPath = path.join(ROOT, relativePath);
+ const html = await readFile(outputPath, "utf8");
+ const pattern = new RegExp(
+ String.raw`^([ \t]*)
`,
+ "m"
+ );
+ const match = html.match(pattern);
+ if (!match) {
+ throw new Error(`Could not find ${className} logo in ${relativePath}`);
+ }
+
+ const indent = match[1];
+ const svg = renderInlineAppWordmark(className)
+ .split("\n")
+ .map((line) => `${indent}${line}`)
+ .join("\n");
+ await writeFile(outputPath, html.replace(pattern, svg));
+}
+
+async function generateSourceLogos() {
+ await writeFile(SOURCE_BLACK, renderSquareLogo("black"));
+ console.log("✓ docs/img/logo-black.svg");
+ await writeFile(SOURCE_WHITE, renderSquareLogo("white"));
+ console.log("✓ docs/img/logo-white.svg");
+
+ for (const [relativePath, svg] of Object.entries(WORDMARK_TARGETS)) {
+ await writeFile(path.join(ROOT, relativePath), svg);
+ console.log(`✓ ${relativePath}`);
+ }
+
+ for (const [relativePath, className] of Object.entries(INLINE_WORDMARK_TARGETS)) {
+ await replaceInlineWordmark(relativePath, className);
+ console.log(`✓ ${relativePath}`);
+ }
+}
+
async function updateAllLogos() {
console.log(`Updating all logos...\n`);
+ await generateSourceLogos();
+
for (const [relativePath, config] of Object.entries(LOGO_TARGETS)) {
const outputPath = path.join(ROOT, relativePath);
diff --git a/scripts/logoAssets.ts b/scripts/logoAssets.ts
new file mode 100644
index 0000000000..79966ddcdb
--- /dev/null
+++ b/scripts/logoAssets.ts
@@ -0,0 +1,63 @@
+/**
+ * Canonical Shux logo geometry.
+ *
+ * The outlined paths use Geist Sans Bold at the same scale and spacing as the prior Mux
+ * logos. Keeping the glyphs as paths avoids platform font fallbacks changing the mark.
+ */
+
+const SHUX_MARK_PATH =
+ "M15.06501 51.48912Q11.80420 51.48912 9.48087 50.55164Q7.15754 49.61415 5.95511 47.96337Q4.75269 46.31258 4.58965 44.11153L10.78519 43.90773Q11.07052 45.49738 12.10990 46.33296Q13.14929 47.16854 15.10577 47.16854Q16.77694 47.16854 17.67367 46.61828Q18.57039 46.06802 18.57039 45.00825Q18.57039 44.27457 18.22393 43.82621Q17.87747 43.37785 16.91960 43.03138Q15.96174 42.68492 14.12753 42.39960Q10.66291 41.82896 8.70643 40.99338Q6.74994 40.15779 5.93473 38.87385Q5.11953 37.58990 5.11953 35.67417Q5.11953 32.45412 7.60590 30.55877Q10.09227 28.66342 14.82045 28.66342Q19.26331 28.66342 21.77006 30.70143Q24.27682 32.73944 24.68442 36.16329L18.57039 36.40785Q18.36659 34.81821 17.36796 33.90110Q16.36934 32.98400 14.73893 32.98400Q13.14929 32.98400 12.27294 33.65654Q11.39660 34.32909 11.39660 35.42961Q11.39660 36.24481 11.78382 36.75432Q12.17104 37.26382 13.04738 37.58990Q13.92373 37.91598 15.47262 38.11978Q18.97799 38.64966 21.01600 39.48525Q23.05401 40.32083 23.95073 41.62516Q24.84746 42.92948 24.84746 44.88597Q24.84746 48.02451 22.21843 49.75681Q19.58939 51.48912 15.06501 51.48912";
+const SHUX_MARK_TRANSFORM = "translate(9.77845 -3.5)";
+const SHUX_MARK_CURSOR_X = 27.85346;
+const SHUX_MARK_CURSOR_Y = 28;
+const SHUX_MARK_CURSOR_WIDTH = 20;
+const SHUX_MARK_CURSOR_HEIGHT = 23;
+
+const SHUX_WORDMARK_PATH =
+ "M14.2080 48.5760Q10.3680 48.5760 7.6320 47.4720Q4.8960 46.3680 3.4800 44.4240Q2.0640 42.4800 1.8720 39.8880L9.1680 39.6480Q9.5040 41.5200 10.7280 42.5040Q11.9520 43.4880 14.2560 43.4880Q16.2240 43.4880 17.2800 42.8400Q18.3360 42.1920 18.3360 40.9440Q18.3360 40.0800 17.9280 39.5520Q17.5200 39.0240 16.3920 38.6160Q15.2640 38.2080 13.1040 37.8720Q9.0240 37.2000 6.7200 36.2160Q4.4160 35.2320 3.4560 33.7200Q2.4960 32.2080 2.4960 29.9520Q2.4960 26.1600 5.4240 23.9280Q8.3520 21.6960 13.9200 21.6960Q19.1520 21.6960 22.1040 24.0960Q25.0560 26.4960 25.5360 30.5280L18.3360 30.8160Q18.0960 28.9440 16.9200 27.8640Q15.7440 26.7840 13.8240 26.7840Q11.9520 26.7840 10.9200 27.5760Q9.8880 28.3680 9.8880 29.6640Q9.8880 30.6240 10.3440 31.2240Q10.8000 31.8240 11.8320 32.2080Q12.8640 32.5920 14.6880 32.8320Q18.8160 33.4560 21.2160 34.4400Q23.6160 35.4240 24.6720 36.9600Q25.7280 38.4960 25.7280 40.8000Q25.7280 44.4960 22.6320 46.5360Q19.5360 48.5760 14.2080 48.5760M28.8480 48L28.8480 13.9200L36.0480 13.9200L36.0480 28.2720L35.0880 28.2720Q35.5680 25.0560 37.7760 23.3760Q39.9840 21.6960 43.1520 21.6960Q47.3280 21.6960 49.5840 24.3600Q51.8400 27.0240 51.8400 31.4400L51.8400 48L44.6400 48L44.6400 33.1680Q44.6400 30.2400 43.7280 28.8240Q42.8160 27.4080 40.7520 27.4080Q38.4960 27.4080 37.2720 28.9440Q36.0480 30.4800 36.0480 33.3600L36.0480 48M65.6640 48.5760Q61.6800 48.5760 59.4480 46.0320Q57.2160 43.4880 57.2160 38.8320L57.2160 22.2720L64.4160 22.2720L64.4160 37.1520Q64.4160 40.1280 65.3280 41.4960Q66.2400 42.8640 68.2080 42.8640Q70.4160 42.8640 71.6400 41.3280Q72.8640 39.7920 72.8640 36.8160L72.8640 22.2720L80.0640 22.2720L80.0640 48L73.4880 48L73.2960 40.6080L74.2080 40.8000Q73.6320 44.6400 71.4720 46.6080Q69.3120 48.5760 65.6640 48.5760M84.3360 48L93.5040 34.9440L84.6240 22.2720L92.2560 22.2720L97.7280 30.5280L103.1040 22.2720L110.8800 22.2720L102 34.9920L111.1680 48L103.4880 48L97.8720 39.2640L92.1600 48";
+const SHUX_WORDMARK_HEIGHT = 62;
+const SHUX_WORDMARK_CURSOR_X = 120.5354;
+const SHUX_WORDMARK_CURSOR_Y = 13;
+const SHUX_WORDMARK_CURSOR_HEIGHT = 35;
+const APP_WORDMARK_CURSOR_WIDTH = 26;
+const DOCS_WORDMARK_CURSOR_WIDTH = 28;
+
+export type LogoFill = "black" | "white" | "currentColor";
+
+export function renderSquareLogo(fill: LogoFill): string {
+ return `
+
+
+
+
+
+
+`;
+}
+
+export function renderWordmarkLogo(fill: LogoFill, cursorWidth: number): string {
+ const width = Number((SHUX_WORDMARK_CURSOR_X + cursorWidth).toFixed(4));
+ return `
+
+
+
+
+`;
+}
+
+export function renderDocsWordmark(fill: LogoFill): string {
+ return renderWordmarkLogo(fill, DOCS_WORDMARK_CURSOR_WIDTH);
+}
+
+export function renderAppWordmark(fill: LogoFill): string {
+ return renderWordmarkLogo(fill, APP_WORDMARK_CURSOR_WIDTH);
+}
+
+export function renderInlineAppWordmark(className: string): string {
+ const width = Number((SHUX_WORDMARK_CURSOR_X + APP_WORDMARK_CURSOR_WIDTH).toFixed(4));
+ return `
+
+
+
+ `;
+}
diff --git a/src/browser/assets/icons/shux.svg b/src/browser/assets/icons/shux.svg
index 9e3e8c7e41..887b5a21df 100644
--- a/src/browser/assets/icons/shux.svg
+++ b/src/browser/assets/icons/shux.svg
@@ -1,4 +1,7 @@
-
-
+
+
+
+
+
diff --git a/src/browser/assets/logos/shux-logo-dark.svg b/src/browser/assets/logos/shux-logo-dark.svg
index bd86a94214..d7c1fbe0c7 100644
--- a/src/browser/assets/logos/shux-logo-dark.svg
+++ b/src/browser/assets/logos/shux-logo-dark.svg
@@ -1,4 +1,5 @@
-
- shux
-
+
+
+
+
diff --git a/src/browser/assets/logos/shux-logo-light.svg b/src/browser/assets/logos/shux-logo-light.svg
index 093a4619ab..fb94ac13bb 100644
--- a/src/browser/assets/logos/shux-logo-light.svg
+++ b/src/browser/assets/logos/shux-logo-light.svg
@@ -1,4 +1,5 @@
-
- shux
-
+
+
+
+
diff --git a/src/browser/components/ProjectSidebar/ProjectSidebar.tsx b/src/browser/components/ProjectSidebar/ProjectSidebar.tsx
index b9d99c8eed..49fdc762dc 100644
--- a/src/browser/components/ProjectSidebar/ProjectSidebar.tsx
+++ b/src/browser/components/ProjectSidebar/ProjectSidebar.tsx
@@ -1968,7 +1968,7 @@ const ProjectSidebarInner: React.FC = ({
className="shrink-0 cursor-pointer border-none bg-transparent p-0"
aria-label="Home"
>
-
+