Brand Book
The premise
A brand book is usually a colour chart with opinions attached. This one starts from a measurement, because the measurement turned out to be the whole system.
The gold in the mark and the beige it sits on are not two colours that happen to go together. Converted to Oklch, they share a hue to within a fifth of a degree: the gold sits at 81.57 degrees and the beige at 81.78 degrees. They are the same colour at different lightness and chroma. Metal and paper, one hue.
L 0.652 C 0.1322 H 81.6°
L 0.933 C 0.0206 H 81.8°
So every neutral in the product is built on that one hue at twelve lightness steps. Nothing is grey. Nothing is imported. A card, a table rule, a disabled label and the logo are all relatives, which is why the interface can look like the identity without ever repeating it.
Two caveats, stated rather than buried. The lightness steps are not evenly spaced: they are deliberately compressed at both ends so the palest steps stay paper and the darkest stay ink. And at the top of the ramp, where chroma is under 0.015, eight-bit rounding moves the hue by a few degrees. Neither is visible. Both are in the source.
Everything downstream of that is restraint: one typeface, two corner radii, one border width, two durations. The character comes from the mark and from the warmth of the ramp. The interface itself gets out of the way.
The mark
One continuous ECG trace running a single cardiac cycle. P wave in from the left, the S of Samora sitting where the PR segment runs, QRS complex out to the right. Both tails run into the ring.
Behaviour at size
Rules
Construction, for reference
| S box width | 0.60 of its height |
| Trace stroke | 0.154 of the S box height |
| Ring band | 0.150 of the outer radius |
| P wave | depth 0.085, width 0.560, raised cosine |
| Interior clearance | 0.84 of the inner radius |
| Tail burial | 0.60 into the band, so the cap leaves no crescent |
| Joins | miter, limit 6 |
Colour
Twelve neutrals on one hue, four status colours rotated off the gold, and nothing else. There is no blue in this brand unless it is carrying information, and even then it is desaturated to the point of being a slate.
The sand ramp
sand 10 is the brand beige exactly. That is not a
coincidence, it is the anchor the ramp was solved around.
Roles
| Light | Value |
|---|---|
| --c-canvas | #F6F0E7 |
| --c-surface | #FDFAF4 |
| --c-surface-2 | #F0E8DA |
| --c-surface-3 | #E0D6C5 |
| --c-line | #C8BCA9 |
| --c-line-strong | #A99D88 |
| --c-text | #171005 |
| --c-text-2 | #504635 |
| --c-text-3 | #6D624F |
| --c-accent | #B8860B |
| --c-accent-text | #886100 |
| --c-accent-solid | #926B14 |
| --c-on-accent | #FDFAF4 |
| --c-focus | #AB8230 |
| Dark | Value |
|---|---|
| --c-canvas | #171005 |
| --c-surface | #261E10 |
| --c-surface-2 | #382F1F |
| --c-surface-3 | #504635 |
| --c-line | #504635 |
| --c-line-strong | #6D624F |
| --c-text | #F6F0E7 |
| --c-text-2 | #C8BCA9 |
| --c-text-3 | #A99D88 |
| --c-accent | #E8B84B |
| --c-accent-text | #DEB771 |
| --c-accent-solid | #E8B84B |
| --c-on-accent | #171005 |
| --c-focus | #DEB771 |
Always use the role, never the ramp step. The role is what makes light and dark mode a single decision instead of two stylesheets.
Status
Olive for verified, the brand gold for partial, terracotta for risk. Each is a rotation off the gold's own hue rather than a colour borrowed from somewhere else, which is why they sit on the beige without arguing with it.
Contrast, measured
Every pairing below is computed, not assumed, and the build fails if any row fails.
Two of these are worth pointing at. The filled gold button is measured against
--c-accent-solid rather than --c-accent, because the display
gold only manages 3.1:1 behind text and cannot be a button. And each status colour is
measured against its own ten percent wash rather than the bare card, because the wash is
what the text actually sits on. Measuring against the card overstates every one of them.
| Mode | Foreground | Background | Measured | Minimum | Result | Where |
|---|---|---|---|---|---|---|
| light | text | canvas | 16.66:1 | 7.0 | pass | body text |
| light | text | surface | 18.11:1 | 7.0 | pass | body text on a card |
| light | text-2 | canvas | 8.17:1 | 4.5 | pass | secondary text |
| light | text-2 | surface | 8.89:1 | 4.5 | pass | secondary text on a card |
| light | text-3 | canvas | 5.28:1 | 3.0 | pass | micro labels, 11px uppercase |
| light | accent-text | canvas | 4.93:1 | 4.5 | pass | gold used as text |
| light | accent-text | surface | 5.37:1 | 4.5 | pass | gold used as text on a card |
| light | line | canvas | 1.65:1 | 1.3 | pass | hairline must be visible but quiet |
| light | on-accent | accent-solid | 4.65:1 | 4.5 | pass | label on a filled gold button |
| light | focus | canvas | 3.11:1 | 3.0 | pass | focus ring, WCAG 2.2 non-text contrast |
| light | focus | surface | 3.38:1 | 3.0 | pass | focus ring on a card |
| dark | text | canvas | 16.66:1 | 7.0 | pass | body text |
| dark | text | surface | 14.53:1 | 7.0 | pass | body text on a card |
| dark | text-2 | canvas | 10.09:1 | 4.5 | pass | secondary text |
| dark | text-2 | surface | 8.80:1 | 4.5 | pass | secondary text on a card |
| dark | text-3 | canvas | 7.07:1 | 3.0 | pass | micro labels, 11px uppercase |
| dark | accent-text | canvas | 9.99:1 | 4.5 | pass | gold used as text |
| dark | accent-text | surface | 8.72:1 | 4.5 | pass | gold used as text on a card |
| dark | line | canvas | 2.04:1 | 1.3 | pass | hairline must be visible but quiet |
| dark | on-accent | accent-solid | 10.24:1 | 4.5 | pass | label on a filled gold button |
| dark | focus | canvas | 9.99:1 | 3.0 | pass | focus ring, WCAG 2.2 non-text contrast |
| dark | focus | surface | 8.72:1 | 3.0 | pass | focus ring on a card |
| light | verified | its own wash | 4.66:1 | 4.5 | pass | verified tag text on its 10 percent wash |
| light | partial | its own wash | 4.65:1 | 4.5 | pass | partial tag text on its 10 percent wash |
| light | risk | its own wash | 4.66:1 | 4.5 | pass | risk tag text on its 10 percent wash |
| light | info | its own wash | 4.66:1 | 4.5 | pass | info tag text on its 10 percent wash |
| dark | verified_dk | its own wash | 4.64:1 | 4.5 | pass | verified tag text on its 10 percent wash |
| dark | partial_dk | its own wash | 4.65:1 | 4.5 | pass | partial tag text on its 10 percent wash |
| dark | risk_dk | its own wash | 4.64:1 | 4.5 | pass | risk tag text on its 10 percent wash |
| dark | info_dk | its own wash | 4.65:1 | 4.5 | pass | info tag text on its 10 percent wash |
Typography
One family: Archivo. It carries the wordmark, the product and print. Three weights are loaded, so three weights are all that can be used.
Archivo is a grotesque drawn for high performance at small sizes, with unambiguous figures and a real tabular set. That matters more here than in most products, because Samora is mostly numbers in columns. It is also not one of the faces that ships as a framework default, which is half the reason an interface reads as generic.
The scale
Rules
Space, radius, line
A 4px grid, eight steps, no exceptions. Two corner radii. One border width. This is the part of the system that most changes how deliberate a product feels.
Spacing
Radius
--r-sm 2px | buttons, inputs, chips, tags |
--r-md 3px | cards, panels, menus, dialogs |
--r-round | avatars and the 6px status dot. Nothing else. |
Density
| Control height | 32px |
| Small control | 26px |
| Table row | 36px |
| Border width | 1px, one colour |
| Text measure | 68ch |
Elevation
Two levels, both nearly invisible, both tinted with the ink rather than pure black. Shadows are for things that genuinely float. A card does not float.
Motion
Two durations, one curve, and a named property every time.
| Token | Value | Use |
|---|---|---|
--dur-fast | 120ms | hover, focus, colour change |
--dur-slow | 200ms | enter, exit, size change |
--ease | cubic-bezier(0.2, 0, 0, 1) | everything |
/* wrong */
transition: all 0.2s;
/* right */
transition: background-color var(--dur-fast) var(--ease),
border-color var(--dur-fast) var(--ease);
The fast duration belongs to hover, focus and colour. The slow one belongs to
things that enter and leave, which in this system is exactly one component:
.pop, used for menus and dialogs. If a third duration seems necessary,
the interaction is wrong, not the scale.
Everything respects prefers-reduced-motion. That is handled once in the token
file, so no component has to think about it.
Components
Live, not pictures. Everything below is rendered by the same token file you will paste into the product, so what you see here is what ships.
| Account | Tier | Health | Stakeholders | Close | Value |
|---|---|---|---|---|---|
| Acme Manufacturing | Verified | 82 | 4 | 12 Sep | 1,180,000 |
| Northgate Logistics | Partial | 61 | 2 | 30 Sep | 465,500 |
| Illinois Bell Holdings | At risk | 38 | 1 | 10 Oct | 90,000 |
| Fairweather Group | Unverified | – | 0 | – | 240,000 |
The ten rules
If you only read one section, read this one. These are the decisions that separate a product that looks authored from one that looks assembled.
--c-print-paper, and it only applies on paper.Voice
Samora sells certainty about numbers. The writing should sound like someone who has already checked.
Specific over enthusiastic. A count, a date, a name. Never a promise, never an exclamation mark, never an apology dressed up as a mascot. When something fails, say what failed and what the user is looking at instead.
This
Three deals need multithreading before Friday.
Illinois Bell Holdings has had one stakeholder for 31 days.
Pipeline verified against 412 emails and 18 meetings.
We could not reach Gmail. Showing intelligence from 9:04 this morning.
Not this
🚀 Your pipeline is looking great!
Oops! Something went wrong.
AI-powered insights, supercharged for your revenue team.
No data available.
House rules
How to adopt this
The token file does most of the work. The rest is deletion.
- Load Archivo at 400, 500 and 600. Nothing else.
- Paste
samora-tokens.cssin place of the current:rootblock and the light and dark overrides. - Run the mapping table below and replace every old variable.
- No emoji in the markup. Where an icon is needed, draw one; where it is not, use a word.
- No colour literal outside the token file. A stray
#orrgba(is a bug. - No
transition:all. Name the properties you animate. - Every
border-radiusisvar(--r-sm)orvar(--r-md).
Variable mapping
| Today | Replace with | Note |
|---|---|---|
| --bg | --c-canvas | page ground |
| --surface | --c-surface | cards |
| --surface2 | --c-surface-2 | hover, inset |
| --surface3 | --c-surface-3 | wells, tracks |
| --border | --c-line | one width, one colour |
| --border2 | --c-line-strong | controls only |
| --text | --c-text | |
| --text2 | --c-text-2 | |
| --text3 | --c-text-3 | |
| --gold, --gold-lt, --gold-pl | --c-accent | three golds collapse to one |
| --green, --green-lt | --c-verified, --c-verified-wash | |
| --amber, --amber-lt, --miss | --c-partial, --c-partial-wash | |
| --coral, --coral-lt | --c-risk, --c-risk-wash | |
| --radius-sm, --radius, --radius-lg, --radius-xl | --r-sm, --r-md | four collapse to two |
| --serif, --sans | --font | one family |
| --ease | --ease | value changes, name stays |
Icons
Emoji come out. In their place, use a single stroke set at 1.5px on a 20px grid so the icons carry the same line weight as the ECG trace. Phosphor Icons at regular weight is a good fit and is MIT licensed. Avoid Lucide: it is the default that ships with the component library everyone recognises, which puts you back where you started.
Files
| File | What it is |
|---|---|
| samora-tokens.css | the drop-in token and component layer |
| palette.py | generates the colour, with the contrast audit |
| marks/ | logo lockups, SVG and PNG, on the single family |
| samora-brand-book.html | this document |
| samora-brand-book.pdf | the sendable version |