Design, menus, footer and branding
The five full-bleed studios that decide how the site looks, the menu override rule that grants nothing, the two mutually exclusive page editors, and every branding upload with the exact size it expects.
Every decision about how your platform looks is taken somewhere different, and only two of them are on the Settings screen. This page is the map.
| Decision | Where | Permission |
|---|---|---|
| Colour, typeface, radius, elevation, motion, navbar and footer layout | /admin/design |
access.design |
| Which entries the navigation offers | /admin/menus |
access.design |
| Footer brand line, copyright, socials, link columns | /admin/footer |
access.design |
| Custom landing pages | /admin/builder |
falls back to access.admin |
| The built-in Home / About / Contact / Privacy / Terms pages | /admin/default-editor |
falls back to access.admin |
| Logos, favicons, app icons | Settings → Branding (/admin/system/settings) |
access.settings |
| One-image logo regeneration | /admin/system/logo |
access.system |
| Missing coin icons | /admin/system/icon |
view.currency.icon |
| PWA manifest and install screenshots | PWA tab of /admin/system/notification |
falls back to access.admin |
access.design is one grant covering Site Design, Menus and Footer
deliberately: whoever can repaint every surface is not a different person from
the one who decides what the footer says. It is a real capability worth granting
separately from access.settings — see
Roles and permissions.
The studios render with no admin header
/admin/design, /admin/design/specimen, /admin/menus, /admin/footer,
/admin/default-editor (and its editor) and /admin/builder/{id} are on the
admin layout's chromeless list. They are control-rail-plus-live-preview surfaces
that own the whole viewport, and the admin header is fixed top-0 z-50 — left
in place it sits on top of the studio's own bar and its Save button stops
responding.
So when you open one of these there is no top nav. Each studio draws its own bar with its own way back to the admin. Use it rather than the browser Back button, which will drop any unsaved draft.
/admin/appearance is a permanent redirect into /admin/design. It used to own
the navbar and footer layout while /admin/design owned colour; both are one
screen now. Old bookmarks and the old menu entry still land correctly.
Site Design
/admin/design. Twelve panels in three groups, all previewed live against your
real site in an iframe beside the rail.
Theme — Presets, Palette studio, Colours, Type & shape, Accessibility.
Chrome — Navbar layout, Footer layout.
Components — Tables, Buttons & inputs, Cards, Forms & dialogs, Charts &
metrics. Selecting any of these swings the preview to the component specimen at
/admin/design/specimen, which is a swatch page of invented rows rather than
real data. It is deliberately not in the menu.
Accessibility is the panel to open before you ship a palette. It computes the contrast of what you have built as you type, and it reports rather than prevents — you can ship a 3:1 accent if you want one, but not without being told. "It looked fine to me" is how unreadable text ships.
Which panel is open lives in the URL as ?section=, so a reload keeps your
place and you can paste a link to a specific panel into a bug report.
One Save, two stores
The theme is stored as a single platform setting — key designTheme, a JSON
document written through PUT /api/admin/system/settings. The navbar and footer
layout are stored on the site-chrome row through
PUT /api/admin/content/chrome. The Save button reflects "are there unsaved
changes on this screen" and writes only the halves that are actually dirty.
The save takes effect on the very next request — the settings fetch is
no-store, so there is no cache to wait out. The tab you are looking at
repaints itself; other tabs already open elsewhere pick it up on reload.
Navbar and footer layouts
Four navbar variants — classic (the default), centered, stacked and
minimal — and three footer variants — columns (the default), compact and
centered. The backend validates the id you send against its own copy of the
registry and refuses an unknown one, because an unknown id would render as the
default anyway and accepting it would mean telling you a choice was saved that
the site never honours.
stacked is the variant that changes the header's height, and the header height
is a CSS variable other pages reserve space against. If a page looks like it has
a gap or an overlap after a navbar change, that is the thing to suspect.
Menus
/admin/menus edits every menu on the site: the Admin sidebar, the Site
navigation, and one entry per installed extension for each of its admin and
public menus. Each is edited on its own; there is no global menu.
The model has three layers and stores only the middle one:
SHIPPED (code) -> OVERRIDE (database) -> RESOLVED (what renders)Nothing here stores a menu. It stores a patch keyed by each shipped item's
key, and the resolution rule is: an item nobody mentioned is unchanged. That
is why installing an extension adds its entries in their shipped position
without touching your edits, and why uninstalling one leaves an inert override
entry rather than a broken menu.
Per item you can change the label, the description, the icon, the order (up/down buttons), and whether it is hidden. You can also add items of your own, at the top level or inside any group, with their own label and link.
The override is applied last — after the permission filter and after the extension filter have already run. Two things follow, and both matter:
Hiding is cosmetic. It removes the link. The URL still works for anyone
whose role permits it, because the API enforces the same access.* key on the
route behind it. Hiding a page is not a way to make it private, and an admin
page "hidden" from the menu is still reachable by typing its address.
Un-hiding grants nothing either. Because the override runs on the tree that
already survived the permission filter, an order entry naming a key the role
cannot reach cannot put it back, and a custom item parented into a group the
role cannot see does not resurrect it. If the menu could do that, it would
advertise pages the API refuses.
Nothing in the override engine reads or writes permission, and any
permission key found in a saved document is stripped before storage. Access is
decided on Roles and permissions and nowhere else.
Two more behaviours are worth knowing before you edit:
Renaming an item opts it out of translation. An item's key is its
translation path in this codebase, so a label you type cannot participate in it.
The renamed item is flagged and its literal text appears in all languages
exactly as typed. On a multi-language site that is a real trade, not a detail.
Items you add are shown to everyone who can see that menu, and they are not translated either.
The editor list shows hidden items struck through rather than removing them — an editor that hid what the override hides would be one you could never un-hide anything with.
Footer
/admin/footer owns footer content: the brand text, the copyright line, the
social links and the link sections. Footer layout — which of the three
variants renders — is in Site Design, because you have to see it to judge it.
The link sections use the same override engine as the menus, so the same rule applies: the footer is built from the shipped sections with your patch on top, and a section you hide is a link removed, not a page protected.
The two page editors are mutually exclusive
Settings → General → Landing Page → Landing Page Type, key
landingPageType, with two values:
landingPageType |
Menu entry that appears | Screen |
|---|---|---|
CUSTOM |
Page Builder | /admin/builder |
DEFAULT |
Default Pages | /admin/default-editor |
Only one is ever in the menu. If you cannot find the page builder, this setting
is why. When the setting has never been saved the menu treats it as DEFAULT,
so a fresh install shows Default Pages.
Page Builder (/admin/builder) is the visual canvas over the CMS page
table. Creating a page from a template writes a DRAFT row and drops you into
/admin/builder/{id}. The slug rules — including why some slugs are refused
outright — are covered in
Blog, media, announcements and market news.
Default Pages (/admin/default-editor) edits the five built-in routes and
nothing else: home, about, contact, privacy and terms. Home
is a structured document with its own section rail; the other four are one HTML
body in a rich-text editor. Each row links out to its live public path so you
can check the result.
Neither route is in permissions.json, so both open for anyone holding
access.admin.
Branding uploads
Settings → Branding (/admin/system/settings, the tab labelled Branding,
internally logos). Every field is an image upload and every one declares an
expected size. Upload at the stated size — the field is not a resizer.
Site Logos
| Setting key | Field | Expected |
|---|---|---|
logo |
Square Logo (Light) | 96 × 96 |
darkLogo |
Square Logo (Dark) | 96 × 96 |
fullLogo |
Full Logo (Light) | 350 × 75 |
darkFullLogo |
Full Logo (Dark) | 350 × 75 |
cardLogo |
Card Logo — sharing cards and previews | 256 × 256 |
Favicons — favicon16 (16 × 16), favicon32 (32 × 32), favicon96
(96 × 96).
Apple touch icons — nine of them, each its own key and its own size:
appleIcon57, appleIcon60, appleIcon72, appleIcon76, appleIcon114,
appleIcon120, appleIcon144, appleIcon152, appleIcon180.
Android icons — androidIcon192, androidIcon256, androidIcon384,
androidIcon512. The 512 × 512 is the one the PWA install prompt uses; skip
it and installing the app to a home screen produces a blank tile.
Microsoft — msIcon144 (144 × 144), the Windows tile.
Which logo the header actually draws is a separate setting: Settings →
General → Appearance → Navbar Logo Display, key navbarLogoDisplay, with
SQUARE_WITH_NAME (square logo plus the site name) or FULL_LOGO_ONLY. Upload
a full logo and it still will not appear in the navbar until this is set to
FULL_LOGO_ONLY.
The Branding tab writes settings keys through the ordinary settings save, so the
changed-keys-only contract applies — see
Settings reference. The images themselves land under
frontend/public/uploads, which is not in the database backup; see
Backup and restore.
One image, every icon: /admin/system/logo
/admin/system/logo is a different tool from the Branding tab, and it is not in
the admin menu — you reach it by typing the URL.
You upload one image and pick logo or logo-text. The endpoint resizes it
into roughly forty named files under frontend/public/img/logo/ — logo,
logo-text, favicon-16x16 through favicon-96x96, every apple-icon-*,
android-icon-*, android-chrome-*, ms-icon-* and mstile-* — writing each
as both PNG and WebP under its existing filename.
That is the difference between the two screens. The Branding tab stores a path per setting key; this screen overwrites the files on disk that the shipped markup already points at. It is the fast path for a rebrand and the slow path for precision, because every size is derived from the one image you gave it.
Currency icons
System → System Monitoring → Currency Icons, /admin/system/icon. This is
not branding — it is the coin logos beside every symbol in the product — but it
is the same class of job and it reopens on its own, because exchange providers
list new tokens continuously.
Reading the report needs view.currency.icon; fetching needs the separate
edit.currency.icon, and without it the sync controls are disabled while the
page still renders.
The report scans every catalogue table that defines a tradable asset, normalises
each symbol exactly the way the frontend does, and counts four things: Missing
icons, Resolvable now, Icons on disk, and Blank placeholders —
files that exist but are byte-identical copies of generic.webp, which draw as
an empty coin and would otherwise look present. Rows can be filtered by asset
class (cex, eco, fiat, fx) and by resolution outcome, and the scan is
cached for 60 seconds, so Rescan is the button that forces a fresh one.
The sync resolves each symbol through a fixed chain —
local-blockchains → alias → trustwallet → tokenlist → coingecko → flag → alias-word — and writes 64 × 64 alpha WebP files into
frontend/public/img/crypto. It is idempotent: a symbol that already has a real
icon is skipped, so re-running only fills what is still missing. Fiat codes
always resolve to a flag and are never given a crypto logo.
Two options on the panel matter:
- Enabled only restricts the run to symbols on enabled rows — the icons a customer can actually see today. Most imported currencies are created disabled, so the unrestricted list is largely latent. Use this.
- Offline limits the chain to
local-blockchainsandalias. Fast, and it cannot be rate-limited.
The request is synchronous, so the screen sends limit: 200 and a 90-second
timeout — shorter than a typical reverse-proxy read timeout, because a longer
run just produces a response nobody receives. A full backfill of roughly two
thousand icons belongs on the CLI: npm run icons:sync from the backend.
PWA manifest
The PWA editor is the PWA tab of System → Communication Tools →
Notification Service (/admin/system/notification) — not a screen of its own,
which is why it is hard to find.
It edits frontend/public/manifest.json directly — a file, not a table — and it
is gated on the settings permissions rather than on a PWA-specific key.
Five sub-tabs:
- General —
name,short_name,description,start_url(default/),scope(default/), text direction andcategories. - Appearance —
display(fullscreen,standalone— the default —minimal-uiorbrowser),orientation,background_color(default#ffffff) andtheme_color(default#000000). - Icons — square PNG or WebP, saved into
img/logo. The 512 × 512 is the one used for the install prompt; maskable icons need their important content inside the centre 80%. - Screenshots — what a customer sees on the install prompt. Each carries a form factor: wide (16:9, 1280 × 720 or larger) for desktop and narrow (9:16, 720 × 1280 or larger) for mobile, plus an optional label. Include at least one of each.
- Shortcuts — the quick actions offered from the installed app icon.
Changing the manifest does not re-prompt anyone who has already installed the app; browsers refresh a manifest on their own schedule.