From 3a54cce7df3bf86e0e900865b880855845c5f766 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Wed, 19 Aug 2026 02:54:05 +0200 Subject: feat(ui): the M of the wordmark is the logo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The stylised M replaces the letter M in "MeshBay" at the top left; `eshBay` beside it stays text. **The source artwork is now in git.** It was in `QE/`, which is gitignored, so it would not have survived a clone — and the tree is about to be moved to another machine. `assets/brand/` holds the original and the `convert` command that derives the 10 KB nav asset from it (trimmed, 72px tall for a picture shown at 30, so it survives a 2× screen). Verified: the documented command reproduces the committed file byte for byte. The derived file is in `_ASSETS`, which is what the `/a//` fingerprint is computed from. A file missing from that list is a file whose change never moves the URL, so a browser holding the old one never asks again — the comment on that list says so, and a logo is exactly the kind of asset one would forget. Its URL is resolved from `import.meta.url`, so the hub's fingerprinted path and the application's `app://` scheme both come out right without either being named in the source. **The two sizes are independent, and that took four rounds to get right.** The picture's height was written in `em` — a fraction of the lettering — so every adjustment to the text resized the picture by the same stroke and the ratio between them could never change. The operator spotted the coupling before I did ("si on change la taille du M, tu vas encore tout décaler"). The height is in pixels now: `font-size: 1.375em` (22px) and `height: 30px`, two knobs that move one thing each. The vertical nudge went with it — it was correcting a misalignment I was causing myself. Also: the wordmark blue goes one step down from sky-400 towards sky-500. It sat brighter than the picture next to it, which made the two read as different materials rather than one object. A note on how this went wrong first: I checked the result on a mock HTML page I wrote, not in the application. The picture was loading the whole time, but at 25px against a 29px line box it was *smaller* than the letters, so the wordmark read as unchanged and the report was "none of what I asked for was done". Every size since has been measured in the running window over CDP. 883 tests pass. Co-Authored-By: Claude Opus 5 --- assets/brand/README.md | 35 ++++++++++++++++++++ assets/brand/meshbay-M-transparent.png | Bin 0 -> 56523 bytes packages/meshbay-hub/src/meshbay_hub/api/webapp.py | 2 +- packages/meshbay-hub/src/meshbay_hub/static/app.js | 9 +++++- .../src/meshbay_hub/static/meshbay-m.png | Bin 0 -> 10066 bytes .../meshbay-hub/src/meshbay_hub/static/style.css | 36 +++++++++++++++++++-- 6 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 assets/brand/README.md create mode 100644 assets/brand/meshbay-M-transparent.png create mode 100644 packages/meshbay-hub/src/meshbay_hub/static/meshbay-m.png diff --git a/assets/brand/README.md b/assets/brand/README.md new file mode 100644 index 0000000..b673589 --- /dev/null +++ b/assets/brand/README.md @@ -0,0 +1,35 @@ +# Brand assets + +## `meshbay-M-transparent.png` + +The stylised M, 214 × 190, RGBA with a transparent background. **This is the +source.** It lived in `QE/` until 2026-08-19, which is gitignored — the artwork +would not have survived a clone. + +## Deriving the nav wordmark + +`packages/meshbay-hub/src/meshbay_hub/static/meshbay-m.png` is generated from +it, not drawn separately: + +```bash +convert assets/brand/meshbay-M-transparent.png \ + -trim +repage -resize x72 -strip \ + packages/meshbay-hub/src/meshbay_hub/static/meshbay-m.png +``` + +- `-trim +repage` removes the transparent margin, so the CSS controls the + spacing rather than the file's padding doing it invisibly. +- `-resize x72` is 72 px tall for a picture displayed at 30 px — enough for a + 2× screen, and 56 KB becomes 10 KB. + +The derived file is listed in `_ASSETS` in `meshbay_hub/api/webapp.py`. It has +to be: that list is what the `/a//` fingerprint is computed from, so a +file missing from it is a file whose change never moves the URL, and a browser +holding the old one never asks again. Regenerate the picture, and the wordmark +changes for everyone; forget the list, and it changes for nobody. + +The M is a picture standing in for the letter M; `eshBay` beside it is text. +Their sizes are set independently in `style.css` — `.nav-brand { font-size }` +and `.nav-brand-m { height }`, the second in **pixels on purpose**. Written in +`em` it was a fraction of the lettering, so resizing the words resized the +picture by the same stroke and the balance between them could never be changed. diff --git a/assets/brand/meshbay-M-transparent.png b/assets/brand/meshbay-M-transparent.png new file mode 100644 index 0000000..6fea08c Binary files /dev/null and b/assets/brand/meshbay-M-transparent.png differ diff --git a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py index 7028071..19d22d4 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/webapp.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/webapp.py @@ -28,7 +28,7 @@ router = APIRouter(tags=["webapp"]) # which is the failure this list exists to prevent, and it is silent. _ASSETS = ("style.css", "keyderive.js", "crypto.js", "transport.js", "app.js", "i18n.js", "downloads.js", "transfers.js", "zipstream.js", - "platform.js") + "platform.js", "meshbay-m.png") def _asset_version() -> str: diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js index 0843d1d..2e3c422 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/app.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js @@ -400,6 +400,11 @@ const ICON_PATHS = { close: ['M6 6l12 12M18 6L6 18'], }; +// The M of the wordmark is a picture; the rest is text. Resolved from this +// module's own URL so the hub's fingerprinted path and the application's +// app:// scheme both come out right without either being named here. +const BRAND_M = new URL('./meshbay-m.png', import.meta.url).href; + function Icon({ name, cls = '' }) { const paths = ICON_PATHS[name]; if (!paths) return null; @@ -579,7 +584,9 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount, `} - MeshBay + + MeshBay +