Brand System — Developer Reference
The canonical visual spec is docs/design_handoff_brand/README.md.
That document is the authority. This page is the developer-facing
implementation guide: where each primitive lives, how to consume the
design tokens, and the small rules that keep the brand from drifting.
Quick map
| You want to render… | Use… | Lives in… |
|---|---|---|
| The full mark (large hero / dashboard) | <SvMark size={n} /> |
SvMark.tsx |
| The monogram (no read-line, ≤32px) | <MarkMono size={n} /> |
MarkMono.tsx |
| An animated mark (splash, reconnect) | <MarkAnimated size={n} /> |
MarkAnimated.tsx |
| The wordmark "ENGRAM" | <Wordmark size={n} /> |
Wordmark.tsx |
| One of four lockups (mark + wordmark) | <LockupHorizontal> / <LockupStacked> / <LockupWithDescriptor> / <LockupMarkOnly> |
Lockup.tsx |
| A platform-style app icon | <AppIcon size={128} edition="dark" /> |
AppIcon.tsx |
| Full-viewport splash | <Splash label="INITIALIZING" /> |
Splash.tsx |
| A bordered panel with corner ticks | <SvPanel> (auto-wraps <SvCorners>) |
SvPanel.tsx |
| A status icon (idle / scan / ripping / …) | <IcoIdle /> … <IcoError /> |
icons/status.tsx |
| A media-type icon (disc / movie / TV / …) | <IcoDisc /> … <IcoLibrary /> |
icons/media.tsx |
| An action / nav icon (play / settings / …) | <IcoPlay /> … <IcoBytes /> |
icons/action.tsx |
Re-exports are aggregated in
frontend/src/app/components/synapse/index.ts
and frontend/src/app/components/icons/index.ts — import from there.
Design tokens
There are two places the tokens live, kept in lockstep:
-
TypeScript —
frontend/src/app/components/synapse/tokens.tsexportssv, a plain object with color/typography constants. Use it for inlinestyle={{ … }}, SVGfill="…", or wherever a CSS variable doesn't reach (motion props, conditional gradients, etc.). -
CSS custom properties —
frontend/src/styles/theme.css@theme inline { … }block defines--color-sv-cyan,--color-sv-magenta,--color-sv-line-mid, and so on. Use these from.cssfiles and external stylesheets likeConfigWizard.css.
Never hardcode #5eead4 or #ff3d7f in new code — they always come
from one of the two surfaces above. If you find yourself wanting a new
hex, add it to both files in the same commit.
The hard rules (from the handoff)
These are the rules you'll forget and accidentally break. Memorize them.
- Cyan is brand. Magenta is action. Magenta only appears on actively
ripping things, on the primary CTA in any flow, and on the read-line
node of the mark. Don't decorate static chrome with magenta. (When
refactoring
ConfigWizard.css, this is why the form<label>color was moved from magenta to cyan-dim.) - Mono is for labels, not body. Don't render running paragraphs in
JetBrains Mono. Mono is for telemetry, paths, labels, code, and
numeric callouts. Body text uses Chakra Petch (
sv.sans/sv.display). - The wordmark stays uppercase, always. Title case is reserved for body and headings.
- Resist over-glow. The atmosphere stack (scanlines, vignette, grain)
is applied at the app root via
<SvAtmosphere>. Do not re-apply scanlines or heavy bloom on every panel. Reserve it for splash, hero / dashboard background, and major modal backdrops. - Sharp 90° corners everywhere. No
border-radius. The "squircle" rounding is only for<AppIcon>(Apple HIGsize * 0.2237).
Icons: when to use the Engram set vs. Lucide
The 30-icon Engram set covers brand-meaningful glyphs: state
indicators, media types, primary navigation, action callouts. The
existing codebase still uses lucide-react for
utility primitives that don't carry brand meaning — chevrons, the
plus sign, save, trash, info, X-for-close (not the brand "cancel"
glyph), and so on.
Rule of thumb:
| Glyph reads as… | Use |
|---|---|
| A state, a media type, a primary action, or a nav entry | Ico* from icons/ |
| A pure UI affordance (chevron, plus, save, trash, info) | Lucide |
If you can describe the icon in the brand handoff's vocabulary ("ripping", "matching", "library", "search"), it's brand-set. If you'd describe it as a UI primitive ("collapse this section", "delete row"), it's Lucide.
Adding a new icon
- Confirm it's not already in the 30-icon set —
icons/index.tsis the full inventory. - If it's a true brand glyph, draft it on a 24×24 grid with 1.5px stroke, round caps + joins, no fill (except deliberately "lit" elements).
- Add the path data to the appropriate file (
status.tsx/media.tsx/action.tsx) wrapped in<Ico>. - Re-export from
icons/index.ts. - If the new icon is brand-significant enough to ship as an external
asset, add it to the canonical handoff
docs/design_handoff_brand/brand/icons.jsxso future implementations stay in sync.
Generating raster assets
The pipeline is:
This runs two scripts in sequence:
scripts/render-svg-sources.mjsemits canonical SVG sources tofrontend/public/brand/sources/:mark.svg,mark-mono.svg,app-icon-dark.svg,app-icon-light.svg.scripts/generate-brand-assets.mjsrasterizes withsharpand packs.icoand.icnswithpng2icons.
Outputs land under frontend/public/brand/ in a structured layout:
brand/
sources/ SVG sources (one per artwork)
favicons/ 16/24/32/48/64 PNG + .svg + multi-resolution .ico
app-icons/
windows/ engram.ico (16…256)
macos/ engram.icns (16…1024) + iconset/ loose PNGs
linux/ engram-32.png … engram-256.png
manifest.json index of every emitted artifact + timestamp
The pipeline is cross-platform — png2icons is pure JS and writes
both .ico and .icns on Windows/macOS/Linux dev machines. No
iconutil or ImageMagick required.
When the mark geometry changes (SvMark.tsx), mirror the change in
render-svg-sources.mjs and
re-run npm run brand:export. Commit the regenerated assets.
For PyInstaller-frozen Windows builds: point --icon at
frontend/public/brand/app-icons/windows/engram.ico in the build spec.
Splash and pre-React paint
There are two splashes, by design:
- HTML pre-React splash in
frontend/index.html. Renders before the bundle parses — uses only inline CSS + an inline SVG of the mark, no external fonts (system fallback until Chakra Petch loads).main.tsxremoves<html class="pre-splash">once React mounts; the splash fades over 240ms via a CSS transition and stays in the DOM withpointer-events: none. - React
<Splash />inSplash.tsx. Use this when you need to surface a "connecting" or "reconnecting" state in the running app — the WebSocket-disconnected case is the canonical example.
Keep them visually consistent (same mark, same wordmark, same "INITIALIZING…" / "RECONNECTING…" label).
Out of scope (deliberately)
The handoff documents two artifacts that are not implemented because no native desktop shell exists yet:
- Dock notification badges (5 states: idle / active / count / complete / error). Revisit when Engram ships an Electron / Tauri / Wails shell.
- Terminal banner (ASCII-art "ENGRAM" with phosphor glow). The team decided this isn't needed for the current CLI / about-screen story.
If either of those becomes useful later, the handoff documents the exact look and behavior — start there.
Accepted deviations from the original plan
Two items in the rollout plan (.claude/plans/take-a-look-at-deep-clarke.md)
were scoped down during implementation. Both are deliberate and documented
here so future maintainers don't re-litigate the choice.
1. lucide-react is kept as a dependency for utility primitives
The plan said "drop lucide-react from package.json once grep comes back
empty." This was scoped down because the brand handoff's 30-icon set is
explicitly a brand icon set — it covers status, media type, primary
nav, and primary actions. It does not cover utility primitives like
chevrons (<ChevronLeft>, <ChevronRight>, <ChevronDown> for
collapsibles and pagination), the plus sign (<Plus> for "Add"
affordances), <Save>, <Trash2>, <Info>, <Bug>, <Database>,
<Clock>, <Vote>, <Loader2>, <X>-for-close (distinct from the
brand's circled-X IcoCancel), etc.
Adding those to the brand set would have:
1. Exceeded the brand spec — drawing ~8–10 new icons that aren't in
the canonical 30. The handoff would then need a v2 update.
2. Diluted the brand meaning — having IcoChevronDown next to
IcoRipping blurs the "brand icons signal state and action" rule.
The current rule (see § Icons: when to use the Engram set vs. Lucide)
is: brand-meaningful glyphs come from Ico*; UI primitives stay on
Lucide. If the brand spec is ever extended to cover utility primitives,
revisit and complete the retirement.
2. ConfigWizard.css survives in tokenized form
The plan said "delete ConfigWizard.css entirely" and rewrite
ConfigWizard.tsx
to consume <SvPanel> / <SvBadge> / <SvLabel> etc.
The actual refactor stopped one step short of deletion: the .css
file (~669 lines, now ~600 after cleanup) was retained but rewritten to
consume CSS custom properties from theme.css
(--color-sv-cyan, --color-sv-line-mid, etc.) instead of hardcoded
hexes. The visible-from-outside chrome (modal overlay, modal border,
button styling, form labels, scanlines) was brought into brand
alignment — duplicate scanline overlays were removed (handoff: "resist
over-glow"), labels moved from magenta to cyan-dim (handoff: "magenta
is action"), and the modal frame matches <SvPanel> chrome.
Fully deleting the .css would have required rewriting nearly all
983 lines of TSX as inline style props or extracted Sv primitives —
high-effort, low-marginal-value once the colors and chrome are aligned.
The remaining .css is purely layout (form-group, wizard-step,
spacing) and not brand-bearing. Revisit if/when the ConfigWizard gets a
structural overhaul.