diff --git a/.github/social-preview-brand.svg b/.github/social-preview-brand.svg new file mode 100644 index 00000000..c9c6bbcc --- /dev/null +++ b/.github/social-preview-brand.svg @@ -0,0 +1,22 @@ + + + C + lash + diff --git a/.github/social-preview.png b/.github/social-preview.png index 4d1cc82c..dc3c4f23 100644 Binary files a/.github/social-preview.png and b/.github/social-preview.png differ diff --git a/apps/desktop/build/icon-1024.png b/apps/desktop/build/icon-1024.png index b3eb47ba..9118e56f 100644 Binary files a/apps/desktop/build/icon-1024.png and b/apps/desktop/build/icon-1024.png differ diff --git a/apps/desktop/build/icon.icns b/apps/desktop/build/icon.icns index 45143ca6..bce48907 100644 Binary files a/apps/desktop/build/icon.icns and b/apps/desktop/build/icon.icns differ diff --git a/apps/desktop/build/icon.svg b/apps/desktop/build/icon.svg index bda8f1db..8af796ef 100644 --- a/apps/desktop/build/icon.svg +++ b/apps/desktop/build/icon.svg @@ -1,22 +1,11 @@ - + - - - - - - + diff --git a/apps/desktop/src/electron-version.test.ts b/apps/desktop/src/electron-version.test.ts index eb5ce32a..e04b96f5 100644 --- a/apps/desktop/src/electron-version.test.ts +++ b/apps/desktop/src/electron-version.test.ts @@ -176,7 +176,7 @@ describe("desktop Electron runtime", () => { expect(readFileSync(iconUrl).subarray(0, 4).toString("ascii")).toBe("icns"); }); - it("uses a full-size centered desktop icon source", () => { + it("uses a full-size centered Clash C desktop icon source", () => { const iconSvg = readFileSync( new URL("../build/icon.svg", import.meta.url), "utf8", @@ -184,9 +184,9 @@ describe("desktop Electron runtime", () => { expect(iconSvg).toContain('viewBox="0 0 1024 1024"'); expect(iconSvg).toContain('rx="216"'); - expect(iconSvg).toContain( - "translate(512 512) scale(0.86) translate(-636 -601)", - ); + expect(iconSvg).toContain('aria-label="Clash C app icon"'); + expect(iconSvg).toContain('stroke="#FF6B50"'); + expect(iconSvg).not.toMatch(/ { diff --git a/apps/web/app/brand-assets.test.ts b/apps/web/app/brand-assets.test.ts new file mode 100644 index 00000000..aea88bd9 --- /dev/null +++ b/apps/web/app/brand-assets.test.ts @@ -0,0 +1,67 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +const repoRoot = join(import.meta.dirname, "../../.."); + +describe("Clash brand assets", () => { + it("uses the theme-colored Clash C for the browser tab favicon", () => { + const favicon = readFileSync( + join(repoRoot, "apps/web/public/favicon-c.svg"), + "utf8", + ); + const mark = readFileSync( + join(repoRoot, "apps/web/public/brand/logo-mark.svg"), + "utf8", + ); + const html = readFileSync(join(repoRoot, "apps/web/index.html"), "utf8"); + + expect(favicon).toBe(mark); + expect(favicon).toContain("#FF6B50"); + expect(favicon).toContain('aria-label="Clash C"'); + expect(favicon).not.toMatch(/ { + const source = readFileSync( + join(repoRoot, ".github/social-preview-brand.svg"), + "utf8", + ); + + expect(source).toContain('aria-label="Clash"'); + expect(source).toContain('class="clash-c"'); + expect(source).toContain('fill="#FF6B50"'); + expect(source).toContain(">C"); + expect(source).toContain(">lash"); + expect(source).not.toMatch(/ { + const source = readFileSync( + join(repoRoot, "apps/desktop/build/icon.svg"), + "utf8", + ); + + expect(source).toContain('aria-label="Clash C app icon"'); + expect(source).toContain("#FF6B50"); + expect(source).not.toMatch(/ { + const consumers = [ + "apps/web/app/root.tsx", + "apps/web/app/routes/login.tsx", + "packages/web-ui/src/components/ProjectsClient.tsx", + "packages/web-ui/src/components/SettingsClient.tsx", + "packages/web-ui/src/components/TopNavigation.tsx", + ] + .map((path) => readFileSync(join(repoRoot, path), "utf8")) + .join("\n"); + + expect(consumers).toContain("/brand/logo-c.svg"); + expect(consumers).toContain("/brand/logo-c-animated.svg"); + expect(consumers).toContain("/brand/logo-c-error.svg"); + expect(consumers).not.toContain("/brand/logo-mark"); + }); +}); diff --git a/apps/web/app/root-route-primitives.test.ts b/apps/web/app/root-route-primitives.test.ts index e5daacd8..311bff64 100644 --- a/apps/web/app/root-route-primitives.test.ts +++ b/apps/web/app/root-route-primitives.test.ts @@ -60,6 +60,24 @@ describe("root route primitives", () => { expect(sitemap).not.toContain("/login"); }); + it("publishes cache-busted Clash C install icons", () => { + const html = readFileSync( + new URL("../index.html", import.meta.url), + "utf8", + ); + const webManifest = JSON.parse( + readFileSync(new URL("../public/site.webmanifest", import.meta.url), "utf8"), + ) as { icons?: Array<{ src?: string }> }; + + expect(html).toContain('href="/favicon-c.svg"'); + expect(html).toContain('href="/apple-touch-icon-c.png"'); + expect(webManifest.icons?.map((icon) => icon.src)).toEqual([ + "/icon-c-192.png", + "/icon-c-512.png", + "/maskable-icon-c-512.png", + ]); + }); + it("keeps the desktop runtime callout legible in dark mode", () => { const css = readFileSync(new URL("./globals.css", import.meta.url), "utf8"); diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx index 181a4d10..c78dbe7d 100644 --- a/apps/web/app/root.tsx +++ b/apps/web/app/root.tsx @@ -92,7 +92,7 @@ export function ErrorBoundary() {
- Clash + Clash

Route paused

diff --git a/apps/web/app/routes/login.tsx b/apps/web/app/routes/login.tsx index f42aea8f..badafdfd 100644 --- a/apps/web/app/routes/login.tsx +++ b/apps/web/app/routes/login.tsx @@ -178,13 +178,13 @@ export default function LoginRoute() { > { expect(css).toMatch( /\.dark \.clash-copilot-launcher \.clash-agent-motion__pen\s*\{[\s\S]*?fill: #ff6b50/, ); - expect(topNavigation).toContain('/brand/logo-mark-dark.svg'); + expect(topNavigation).toContain('/brand/logo-c.svg'); expect(topNavigation).not.toContain("dark:grayscale"); }); @@ -333,7 +333,7 @@ describe("application theme contract", () => { expect(login).toContain("dark:text-neutral-50"); expect(login).toContain("dark:text-neutral-100"); expect(login).toContain("dark:placeholder:text-neutral-500"); - expect(login).toContain('/brand/logo-mark-dark.svg'); + expect(login).toContain('/brand/logo-c.svg'); }); it("exposes Appearance as a real persisted settings section", () => { diff --git a/apps/web/index.html b/apps/web/index.html index 2695d8fe..188c151b 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -2,8 +2,9 @@ - - + + + + + + + diff --git a/apps/web/public/brand/logo-c-error.svg b/apps/web/public/brand/logo-c-error.svg new file mode 100644 index 00000000..84c5da91 --- /dev/null +++ b/apps/web/public/brand/logo-c-error.svg @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/public/brand/logo-c.svg b/apps/web/public/brand/logo-c.svg new file mode 100644 index 00000000..84c5da91 --- /dev/null +++ b/apps/web/public/brand/logo-c.svg @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/public/brand/logo-lockup-horizontal.svg b/apps/web/public/brand/logo-lockup-horizontal.svg index d482fa16..79d76cad 100644 --- a/apps/web/public/brand/logo-lockup-horizontal.svg +++ b/apps/web/public/brand/logo-lockup-horizontal.svg @@ -1,31 +1,10 @@ - - - - - - - - - + Clash + font-size="64" + font-weight="700" + letter-spacing="-2" + >Clash diff --git a/apps/web/public/brand/logo-mark-animated.svg b/apps/web/public/brand/logo-mark-animated.svg index 5afad8c1..9cdf8bd4 100644 --- a/apps/web/public/brand/logo-mark-animated.svg +++ b/apps/web/public/brand/logo-mark-animated.svg @@ -1,100 +1,20 @@ - - - - - - - - - - - - - + + + + diff --git a/apps/web/public/brand/logo-mark-dark.svg b/apps/web/public/brand/logo-mark-dark.svg index d8f7a2f5..84c5da91 100644 --- a/apps/web/public/brand/logo-mark-dark.svg +++ b/apps/web/public/brand/logo-mark-dark.svg @@ -1,20 +1,10 @@ - - + - - - + stroke-linejoin="round" + /> diff --git a/apps/web/public/brand/logo-mark-error.svg b/apps/web/public/brand/logo-mark-error.svg index a498e061..84c5da91 100644 --- a/apps/web/public/brand/logo-mark-error.svg +++ b/apps/web/public/brand/logo-mark-error.svg @@ -1,23 +1,10 @@ - - - - - - - - - + + diff --git a/apps/web/public/brand/logo-mark.svg b/apps/web/public/brand/logo-mark.svg index 2561f53a..84c5da91 100644 --- a/apps/web/public/brand/logo-mark.svg +++ b/apps/web/public/brand/logo-mark.svg @@ -1,21 +1,10 @@ - - - - - - - + + diff --git a/apps/web/public/favicon-c.svg b/apps/web/public/favicon-c.svg new file mode 100644 index 00000000..84c5da91 --- /dev/null +++ b/apps/web/public/favicon-c.svg @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg index 2561f53a..84c5da91 100644 --- a/apps/web/public/favicon.svg +++ b/apps/web/public/favicon.svg @@ -1,21 +1,10 @@ - - - - - - - + + diff --git a/apps/web/public/icon-192.png b/apps/web/public/icon-192.png index b53d2012..fe1292f2 100644 Binary files a/apps/web/public/icon-192.png and b/apps/web/public/icon-192.png differ diff --git a/apps/web/public/icon-512.png b/apps/web/public/icon-512.png index 3e6383e1..ae3369ca 100644 Binary files a/apps/web/public/icon-512.png and b/apps/web/public/icon-512.png differ diff --git a/apps/web/public/icon-c-192.png b/apps/web/public/icon-c-192.png new file mode 100644 index 00000000..fe1292f2 Binary files /dev/null and b/apps/web/public/icon-c-192.png differ diff --git a/apps/web/public/icon-c-512.png b/apps/web/public/icon-c-512.png new file mode 100644 index 00000000..ae3369ca Binary files /dev/null and b/apps/web/public/icon-c-512.png differ diff --git a/apps/web/public/maskable-icon-512.png b/apps/web/public/maskable-icon-512.png index 3e6383e1..ae3369ca 100644 Binary files a/apps/web/public/maskable-icon-512.png and b/apps/web/public/maskable-icon-512.png differ diff --git a/apps/web/public/maskable-icon-c-512.png b/apps/web/public/maskable-icon-c-512.png new file mode 100644 index 00000000..ae3369ca Binary files /dev/null and b/apps/web/public/maskable-icon-c-512.png differ diff --git a/apps/web/public/og-image.png b/apps/web/public/og-image.png index cb36c9b7..995f5a1f 100644 Binary files a/apps/web/public/og-image.png and b/apps/web/public/og-image.png differ diff --git a/apps/web/public/site.webmanifest b/apps/web/public/site.webmanifest index a3201f4c..4c5ecdb4 100644 --- a/apps/web/public/site.webmanifest +++ b/apps/web/public/site.webmanifest @@ -8,17 +8,17 @@ "theme_color": "#FF6B50", "icons": [ { - "src": "/icon-192.png", + "src": "/icon-c-192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/icon-512.png", + "src": "/icon-c-512.png", "sizes": "512x512", "type": "image/png" }, { - "src": "/maskable-icon-512.png", + "src": "/maskable-icon-c-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/packages/web-ui/src/components/ProjectsClient.tsx b/packages/web-ui/src/components/ProjectsClient.tsx index c16f188e..0eb2f871 100644 --- a/packages/web-ui/src/components/ProjectsClient.tsx +++ b/packages/web-ui/src/components/ProjectsClient.tsx @@ -47,7 +47,7 @@ export default function ProjectsClient({ projects }: ProjectsClientProps) { First cut, open frame

- + Agent pass
diff --git a/packages/web-ui/src/components/SettingsClient.tsx b/packages/web-ui/src/components/SettingsClient.tsx index f3de0d7b..2f6bf3e8 100644 --- a/packages/web-ui/src/components/SettingsClient.tsx +++ b/packages/web-ui/src/components/SettingsClient.tsx @@ -2355,7 +2355,7 @@ function modelCardBrand(model: ModelCatalogEntryInfo['model']): ModelBrand { return { id: 'recraft', label: 'Recraft', src: '/brand/models/recraft.svg' }; } if (/(clash mock|mock image|mock text)/.test(identity)) { - return { id: 'clash', label: 'Clash', src: '/brand/logo-mark.svg' }; + return { id: 'clash', label: 'Clash', src: '/brand/logo-c.svg' }; } if (/(nano-banana|gemini|veo|google)/.test(identity)) { return { id: 'google', label: 'Google', src: '/brand/providers/google.svg' }; @@ -2392,7 +2392,7 @@ function modelCardBrand(model: ModelCatalogEntryInfo['model']): ModelBrand { } if (/(local|mlx|whisper|kokoro|piper)/.test(identity)) { if (/(local agent)/.test(identity)) { - return { id: 'clash', label: 'Clash', src: '/brand/logo-mark.svg' }; + return { id: 'clash', label: 'Clash', src: '/brand/logo-c.svg' }; } return { id: 'local', label: 'Local' }; } diff --git a/packages/web-ui/src/components/TopNavigation.desktop.test.tsx b/packages/web-ui/src/components/TopNavigation.desktop.test.tsx index fa23a92b..10baf8f1 100644 --- a/packages/web-ui/src/components/TopNavigation.desktop.test.tsx +++ b/packages/web-ui/src/components/TopNavigation.desktop.test.tsx @@ -177,7 +177,7 @@ describe("TopNavigation desktop chrome", () => { const home = await screen.findByRole("button", { name: "Clash home" }); const logos = Array.from(home.querySelectorAll("img")); expect(logos.map((logo) => logo.getAttribute("src"))).toEqual( - expect.arrayContaining(["/icon-192.png", "/brand/logo-mark-dark.svg"]), + expect.arrayContaining(["/brand/logo-c.svg"]), ); expect(logos.some((logo) => logo.className.includes("dark:block"))).toBe(true); expect(logos.every((logo) => !logo.className.includes("dark:grayscale"))).toBe(true); diff --git a/packages/web-ui/src/components/TopNavigation.tsx b/packages/web-ui/src/components/TopNavigation.tsx index 849fd6d9..c7e554b6 100644 --- a/packages/web-ui/src/components/TopNavigation.tsx +++ b/packages/web-ui/src/components/TopNavigation.tsx @@ -347,13 +347,13 @@ export default function TopNavigation() { icon={ - - - + + ); } diff --git a/packages/web-ui/src/components/landing/LandingNav.test.tsx b/packages/web-ui/src/components/landing/LandingNav.test.tsx index 98f1bd40..0cbd4830 100644 --- a/packages/web-ui/src/components/landing/LandingNav.test.tsx +++ b/packages/web-ui/src/components/landing/LandingNav.test.tsx @@ -32,16 +32,17 @@ describe("LandingNav", () => { expect(headerSurface?.className).toContain("clash-control-surface"); }); - it("uses the current Clash agent logo", () => { + it("uses the Clash wordmark with a brand-colored C", () => { const { container, getByLabelText } = render(); expect(getByLabelText("Clash")).toBeTruthy(); - expect( - container.querySelector('img[src="/brand/logo-mark.svg"]'), - ).toBeTruthy(); - expect( - container.querySelector('img[src="/brand/logo-mark-dark.svg"]'), - ).toBeTruthy(); - expect(container.querySelector(".clash-wordmark-slash")).toBeNull(); + const c = container.querySelector(".clash-wordmark-c"); + expect(c).toBeTruthy(); + expect(c?.className).toContain("text-brand"); + expect(c?.textContent).toBe("C"); + expect(container.querySelector(".clash-wordmark-rest")?.textContent).toBe( + "lash", + ); + expect(container.querySelector('img[src*="/brand/logo-mark"]')).toBeNull(); }); }); diff --git a/packages/web-ui/src/components/visualLanguage.test.tsx b/packages/web-ui/src/components/visualLanguage.test.tsx index 21be1ba5..9c65f5a1 100644 --- a/packages/web-ui/src/components/visualLanguage.test.tsx +++ b/packages/web-ui/src/components/visualLanguage.test.tsx @@ -284,14 +284,6 @@ describe("visual language surfaces", () => { expect(source).toMatch(/clash-node-badge-draft/); }); - it("uses the transparent Clash mark for the browser tab favicon", () => { - const favicon = readFileSync(join(process.cwd(), "apps/web/public/favicon.svg"), "utf8"); - const mark = readFileSync(join(process.cwd(), "apps/web/public/brand/logo-mark.svg"), "utf8"); - - expect(favicon).toBe(mark); - expect(favicon).not.toMatch(/ { const source = [ "packages/web-ui/src/components/nodes/TextNode.tsx", @@ -517,7 +509,7 @@ describe("visual language surfaces", () => { expect(source).toMatch(/clash-hero-stage/); expect(source).toMatch(/clash-hero-prompt/); expect(source).not.toMatch(/lg:pl-(12|16)|xl:pl-(12|16)/); - expect(source).toMatch(/\/brand\/logo-mark-animated\.svg/); + expect(source).toMatch(/\/brand\/logo-c-animated\.svg/); expect(source).toMatch(/clash-dashboard-shell/); expect(source).toMatch(/clash-projects-empty-workbench/); expect(source).toMatch(/clash-projects-empty-canvas/); @@ -723,7 +715,7 @@ describe("visual language surfaces", () => { expect(source).toMatch(/error\.code/); expect(source).toMatch(/Reload/); expect(source).toMatch(/Go home/); - expect(source).toMatch(/\/brand\/logo-mark-error\.svg/); + expect(source).toMatch(/\/brand\/logo-c-error\.svg/); expect(errorMark).toMatch(/aria-label="Clash error logo"/); expect(errorMark.match(/stroke-linecap="round"/g)?.length).toBeGreaterThanOrEqual(5); });