Ten colours, each carrying one meaning. Two grotesques and seven roles. Six shapes,
four elevations, four durations and one ceremony. Every specimen below resolves
through the same tokens the product ships, so a change to the token layer changes
this page. Where this page and a pack file disagree, the file is right.
Nothing on this page is a claim about the service. Product strings appear only as
format specimens, quoted from the pack and labelled as quotations.
01 Colour
Each colour carries one meaning, and may never be spent elsewhere.
Ten colours are named as tokens in ruzayo.css. Every one
of them has a job and a prohibition. The prohibition is the useful half: a palette is
only a system if a colour can be spent wrongly.
Source: CANON.md §9 · tokens.json · values from ruzayo.css
Moss
Means The system itself; primary surface and primary action.
Never An error or a warning.
Measured 10.4:1 on oat, AAA.
--rz-moss#22372C
Oat
Means The page.
Never Replaced with #FFFFFF.
Measured Not as a foreground. Oat is the background eight of the eleven measured pairings are measured against.
--rz-oat#EFE7DC
Shell
Means A raised panel holding facts.
Never An accent.
Measured Nothing. tokens.json carries eleven pairings and none against shell. Stated rather than estimated.
--rz-shell#F7F2E9
Terracotta
Means The exit, and the one thing that wants a person.
Never Decoration, or success.
Measured 4.6:1 on oat. AA body, not AAA. Do not use below 16px.
--rz-terracotta#9E5231
Amber
Means A live quantity, something held.
Never Good — it means in hand. And never text on oat.
Measured Fails at 1.8:1 on oat. 5.6:1 on moss, AA — moss is the surface that licenses amber as a letterform, and the two places this page spends it are the vessel figure and the label on a moss band.
--rz-amber#E0A040
Sage
Means Quiet support, secondary marks.
Never Text. And never the only carrier of a state.
Measured Fails at 2.5:1 on oat. Sage is a surface and a dot; it sets no word anywhere in the system.
--rz-sage#7F9A82
Ink
Means Body text.
Never Dropped below the AA floor on oat.
Measured 13.4:1 on oat, AAA.
--rz-ink#1B211C
Slate
Means Secondary text.
Never Dropped below the AA floor on oat.
Measured 5.9:1 on oat, AA.
--rz-slate#4E5A50
Cream
Means Text on terracotta — the band around this card.
Never Used at alpha.
Measured 5.3:1 on terracotta, AA.
--rz-cream#FBF7EF
Bone
Means Text on moss — the band around this card.
Never Not stated. tokens.json gives bone a meaning and no prohibition, and CANON §9 does not list it. Stated rather than invented.
Measured 10.9:1 on moss, AAA.
--rz-bone#F3EDE3
Measured, not asserted
Eleven pairings were measured against the stated background. Two of them fail, and both
are recorded as failures rather than quietly avoided — a stated failure survives a
redesign, and a convention does not.
Measured contrast ratios for every named pairing
PairingSpecimenRatioVerdict
ink on oatAa13.4:1AAA
lead on oatAa7.9:1AAA
moss on oatAa10.4:1AAA
slate on oatAa5.9:1AA
terra deep on oatAa5.7:1AA
terracotta on oatAa4.6:1AA Body, not AAA. Do not use below 16px.
bone on mossAa10.9:1AAA
amber on mossAa5.6:1AA The vessel figure. Moss is the only surface that licenses amber as a letterform.
cream on terracottaAa5.3:1AA
amber on oat1.8:1Fails Amber is never text on the page.
sage on oat2.5:1Fails Sage is a surface, never text.
The amber rule
Amber is a fill, a tint and a figure on moss. It is never a letterform on the page.
At 1.8:1 on oat it is not dimmed text, it is not-text. Where a live quantity needs a
word beside it, the word is ink or slate and the amber is the surface behind it.
9.8 GBamber on moss, 5.6:1 — licensed
The sage rule
Sage is a surface. It tints a panel, it fills a dot, it never sets a word. And
because colour never carries a state alone, a sage dot is never the whole message:
the word travels with it, every time.
a sage surface--rz-tint-sage — a surface, not an ink
Named conflict · two colours with no token
tokens.json names twelve colours.
ruzayo.css names ten. The two without a
token are lead (#3C4740, lead paragraphs on oat, 7.9:1) and
terra deep (#8E4527, text on a terracotta tint, 5.7:1); the
stylesheet writes both as literals inside
.rz-lead,
.rz-btn--exit:hover and
.rz-state--wants. Reported, not resolved
here: ruzayo.css is not this page's to
edit. Until it names them, this page declares the two values once, in its own
stylesheet, cited to tokens.json.
02 Proportion and alpha
If the accent reads as a colour scheme, it has been overspent.
The palette is not a set of equals. It is a ratio, and the ratio is most of the
identity: a page that is three-quarters neutral makes the one terracotta thing on it
unmistakable.
Source: tokens.json → proportion, alpha_ladder
Proportion, enforced
~75% neutrals — oat, shell, ink, slate, cream, bone
~18% moss
a few per cent, together terracotta and amber
The bar's third band is whatever is left when the first two are drawn. The pack states
the accent share as a few per cent, together and puts no number on it, so
neither does this page.
The alpha ladder
Three steps and a floor. Nothing readable below the floor — under
0.66 an ink is a rule or a wash, not text.
The address people write to
1
Full. Every fact, every body line.
The address people write to
0.74
Secondary. The detail beside a fact.
The address people write to
0.66
The floor. The last step that may carry a word.
Below the floor
A rule, a hairline, a wash. Never a word. This step is drawn as a bar, because setting it as text would break the rule it states.
tokens.json measures eleven colour pairings and no alpha
step, so no contrast ratio is printed beside the three steps above. Stated rather than
estimated.
03 Type
Two families. Seven roles. Three floors.
Bricolage Grotesque takes display, titles and figures. Familjen Grotesk takes
everything else. There is no third family, and nothing sits between two of these seven
roles.
Source: tokens.json → type · classes in ruzayo.css
12px labels, 14.5px facts, 16px body. Nothing in the product is set smaller than the role it belongs to.
The floors outside the product
Print 12pt. Presentation 24px. The most important line on any surface, 26px.
Named conflict · the accent label
.rz-label--accent sets the label role in
terracotta. The label role is 12px; terracotta measures 4.6:1 on oat and the pack's own
verdict is do not use below 16px. The class as written asks for both at once.
So this page sets every eyebrow in the slate label, and shows the accent variant once,
raised to the terracotta floor.
Raised to 16px.rz-label--accent on oat at 16px — terracotta, 4.6:1, AA body
04 Shape
A capsule means you act on it. A panel or a tile means you read it.
Six forms, and the radius is the whole grammar. Nothing needs a label to say whether it
is a control, because its corner already said so.
The signature. One per view, always holding a real figure — which is why the specimen here is the radius alone, and the one real vessel on this page is on the band below.
The signature element
The vessel.
An asymmetric organic form, heavier at its base, holding a light inside it and
crossed once at the shoulder by the diagonal borrowed from the z. It is the name
drawn: something open that carries weight and settles.
It appears once per surface and it always contains something real. It is never
decoration, never repeated as a pattern, and never shown empty — which is
why the shape card above shows the radius and this band shows the vessel.
In this system
10
colours, each carrying one meaning, none spent elsewhere
The deep-corner padding formula
A deep corner eats its own content unless the padding clears the arc.
inset = R − √(R² − (R − dy)²)
Where dy is how far down the content starts. A 110px
corner with content 46px down needs 21px. The token pads 52px, which still clears if
the top edge tightens to 16px. This panel is a deep A, padded by
--rz-deep-min-padding. Do not lower it.
The opposing pair
Deep B is deep A turned a quarter. They are used against each other so a run of
panels has a direction, and they are the only two shapes in the system that are not
symmetrical.
A capsule means you act on it. A panel or a tile means you read it. A deep corner
means neither: it is composition, and it will never be a control.
Targets, and the focus ring
Touch target minimum 44px. Input minimum 46px.
Focus is a 2px terracotta outline at 2px offset, and it is never removed. Move a keyboard through the three controls below and it appears on each of them.
.rz-btn is the primary.
.rz-btn--ghost is the secondary.
.rz-btn--exit is terracotta, and terracotta is the exit
— which is why there is never a fourth button colour to invent. The input is
the one white surface in the system: oat is the page and may never be white, and a
field is not the page.
05 Depth
One blurred shadow in the whole system.
Four elevations. Three of them are surface value and a hairline; only the fourth leaves
the page, and it is reserved for the thing that is being held.
Source: tokens.json → depth
0 · the oat page
The ground. Nothing sits behind it.
1 · shell panel, 1.5px rule
Raised by value and a hairline, not by a shadow.
2 · moss panel, no rule
The system speaking. It needs no border; the value is the edge.
3 · held
The one blurred shadow, spent once per view.
The shadow
0 40px 70px -40px rgba(27,33,28,0.55)
--rz-shadow-held. There is exactly one. Everything else
is surface value and a hairline, and a second shadow is how a system starts to look
like every other system.
The light
One source, upper left, low opacity, every frame. The light pool behind the masthead
and the light inside the vessel in §4 come from the same place the shadow falls,
because two light sources read as an accident.
06 Motion
It lands. Nothing overshoots, nothing bounces.
Four durations and one easing. Every animation has a reduced-motion equivalent, and
nothing important is ever carried by motion alone.
Source: tokens.json → motion
The settle easing
cubic-bezier(0.2, 0, 0, 1)
--rz-ease-settle. The curve never rises above its end
value, which is the whole reason nothing bounces.
The four durations
leave180ms
settle260ms
arrive340ms
swell420ms
--rz-dur-leave,
--rz-dur-settle,
--rz-dur-arrive,
--rz-dur-swell. Each track runs at its own token when it
is pointed at, and the figure beside it is the value, so the reading never depends on
the movement.
The one ceremony
listening
The swell, once: the moment a name reads as listening. It is 420ms,
it scales to 1.05 and returns, and it happens one time. There is no second ceremony
in the system and nothing else may borrow this one — a celebration that fires
twice is a celebration of nothing.
.rz-swell · it played on load. Reload the page to
see it again, which is exactly as often as the product plays it.
Arrive
held
.rz-arrive is 340ms: fourteen pixels in from the left,
into place, no overshoot. It is how a row joins a list, and it is the only entrance.
Under prefers-reduced-motion both of these resolve to
their end state immediately, and the page loses nothing — because neither of
them was carrying information.
07 State
Colour never carries a state alone. Every state is a dot and the word.
Five states, no sixth. Each is a real value on the core object, each renders as a chip
carrying both marks, and each has a reason a person can read.
Source: mailbox.d.ts → MailboxState · lifecycle.md · classes in ruzayo.css
listening
held
wants you
refused
resting
The five mailbox states, with the class, the enum, and what each one means in life
StateClass and enumEntered whenExits whenStill receives
listening.rz-state--listening 'listening'All six gates hold.A gate breaks, or a hold is placed.Yes
held.rz-state--held 'held'The customer places a hold.The hold is lifted.Yes — kept, nothing forwarded
wants you.rz-state--wants 'wants_you'A record broke, a webhook is failing, or the ramp paused.The cause clears.Yes
refused.rz-state--refused 'refused'We declined it.Never. It is not reversible in place.No
resting.rz-state--resting 'resting'Thirty days with no arrival.Mail arrives.Yes
Named conflict · one state, three spellings
The enum in mailbox.d.ts is
wants_you. The class in
ruzayo.css is
.rz-state--wants. The word a person reads is
wants you. Three spellings of one state is a bug waiting to be written, so
it is written down here instead: the modifier is
--wants, and it is not
--wants-you.
The row, and its reserved gutter
sara@herstudio.com
listening · all set
4 Aug 06:40
hooks@herstudio.com
wants you · a webhook is failing
2 Aug 09:41
press@herstudio.com
resting · nothing has arrived for thirty days
5 Jul 18:02
The third row has no dot and does not shift left: the 8px gutter is always reserved,
or a list goes ragged the moment one row falls quiet. Every dot here has its word in
the detail line beside it, because a dot alone is a colour, and colour is not a
state.
08 The mark
Small, large, small. Never four the same.
Three dots in the name's own rhythm, ru-ZA-yo. The sizes and the colours come from the
class, not from the markup: three empty <i> elements
are the whole mark.
Source: ruzayo.css → THE MARK · the stress pattern is recorded in LATER.md
The mark
9px terracotta, 19px moss, 9px sage, with a 4px gap. Three sizes, three inks, one rhythm.
ruzayo
The lockup
Bricolage 500 at 25px, tracking −0.02em, moss. In prose the product is Ruzayo; the lockup is set lowercase.
Under 20px
The middle dot stands in alone. .rz-mark--small hides the outer two rather than shrinking all three into mush.
On terracotta
All three dots take one ink — cream — or the left one vanishes into the surface. The rule lives on .rz-panel--terra .rz-mark and .rz-on-terra.
On moss
Only the middle dot changes: moss on moss is nothing, so it takes bone. The outer two keep their own inks, because they still read.
The dots carry no text, so they are hidden from the reader and the accessible name
comes from the word beside them. Where a mark and a wordmark are one link, the wordmark
is the name and a single .rz-sr word finishes the sentence
rather than repeating it: the link at the top of this page announces
ruzayo homepage. Where the mark stands alone with no wordmark, the whole name
goes in the .rz-sr span, because otherwise the link has
none at all.
09 Accessible names
Three requirements the stylesheet cannot enforce on its own.
These are markup rules, so they are written into
ruzayo.css beside the class they apply to. An implementer
reads the stylesheet; they do not read the audit. Each is a requirement, not a
recommendation.
Source: ruzayo.css → ACCESSIBLE NAMES · closed at the 1.0 lock, CHANGELOG.md
Requirement 1 · the vessel
A vessel carries a real figure, so it is an image with a name.
role="img" and aria-label
on the vessel. The figure inside takes
aria-hidden="true", because the label already announces
it and announcing it twice is worse than not announcing it once.
<div class="rz-vessel" role="img"
aria-label="9.8 GB held under herstudio.com">
<div class="rz-vessel__figure" aria-hidden="true">9.8 GB</div>
Requirement 2 · the table
A table of divs needs the roles a real table gets for free.
Every .rz-table takes
role="table" and
aria-labelledby; every row takes
role="row"; header cells take
role="columnheader" and body cells
role="cell". Any wrapper between the table and its rows
takes role="presentation", or the association breaks
silently.
Under 600px the grid reflows to one cell per line. Keep the roles and add a visible
label per cell: a headerless column is unreadable to a screen reader and to a thumb
alike. All three tables on this page do it, from a
data-label attribute. Narrow the window and the labels
appear.
Requirement 3 · icon-only controls
A control with no text node has no name.
Each takes aria-label; the glyph inside takes
aria-hidden="true". There is no exception for an obvious
icon, because obvious is a visual property and the person who needs the name is not
looking at it.
Both are 44px square, which is the touch-target minimum, and both keep the focus ring. focusable="false" is on each SVG because older engines put the graphic itself in the tab order, which would give the control two stops and one name.
Also non-negotiable
Focus is a 2px terracotta outline at 2px offset and is never removed. Nothing important
is carried by motion alone. And no state is carried by colour alone — §7 is that
rule in five chips.
10 Content rules
The mechanical half of voice.
Formats, not semantics. Every string below is quoted from
content-rules.md as a format specimen: it demonstrates a
shape. None of them is a statement about what the service does, and the figures inside
them are the pack's own examples.
Source: content-rules.md · semantics in CANON.md §9
Dates and times
4 Aug 06:40:12 in the record of events — D MMM HH:mm:ss
4 Aug 06:40 everywhere else — D MMM HH:mm
11 min ago · 4 h ago · yesterday under 48 hours
2 Aug 09:41 older than 48 hours — absolute, never relative
next rotation 4 Nov a known future
Checking again in 8 s a short future, counting
24-hour clock always. The timezone is named when it is not the reader's — 13:41 CEST. Never just now, never a while ago. No ordinals.
Numbers
61,204 messages thousands separated with a comma
9.8 GB bytes as GB to one decimal. MB only under 0.1 GB. Never KB
1.4 s one decimal under ten seconds, none above
6 minutes in prose, 6 min in a table
2% whole numbers, unless the decimal changes a decision
one mailbox a count of one is written out
Tabular figures wherever numbers stack in a column — that is what .rz-fact is for. And where 1.4 s names the measured create-to-listening time, it carries its qualifier every time: only on a name that already listens.
Money
€3 / mo symbol, space, slash, space, unit. Not €3/month
€0.20 / GB two decimals when there are cents, none when there are not
780–920k SEK salary bands in SEK, thousands, en dash, no space
Every price shown is the real price, including a competitor's.
Addresses, names, records
sara@herstudio.com addresses and domains always lowercase
mx · spf · dkim · dmarc lowercase in prose and in data
MX · SPF uppercase only as a column label or a chip
_dmarc DNS hostnames in mono, with the underscore where it is real
rz_live_…c241 keys truncated in the middle, never at the end
Never truncate a value a person may need to copy. Wrap it — .rz-table__cell already does. That includes addresses, record values, checksums and message ids.
Capitalisation
Sentence case everywhere: headings, buttons, labels, table headers, notification
titles. No title case, ever.
Eyebrows and column headers are uppercase with letter-spacing, which is a type
treatment and not capitalisation — the underlying string stays sentence case.
Every .rz-label on this page is written in sentence case
in the markup, and shouted by the stylesheet.
Pluralisation
Written out, never with a parenthesised s. Zero takes the plural —
0 mailboxes — except where a stated absence reads
better, which is most of the time: nothing has arrived yet, not
0 messages.
Empty states
An empty state names what would be there, why it is not, and the one thing that changes
it. It never apologises and it never uses an illustration. Absence is a designed output.
No names yet
No names under service. Add one you already own — nothing to buy here
No mailboxes on a name
herstudio.com is listening with nothing under it yet. That is a finished state, not a missing one
Nothing has arrived
Nothing has arrived at this address. It is listening and it will keep whatever comes
No events in range
Nothing happened in these seven days
Nothing needs you
Nothing needed you. Fourteen days, unbroken
Errors
Four parts, in this order: what was expected, what was found, what happens next,
and whether it needs the person at all. Never two apologies. Never blame. Never
an error code alone, and never an error code hidden.
Record missing
Expected TXT at _dmarc. Found nothing. We can write it back
Send refused
acme.se refused this at the door: 550, sender not aligned. Here is what they checked
Payment failed
The card was declined. Nothing is suspended; mail keeps arriving while you sort it
Ramp paused
Refusals crossed 2% in the last day. Sending holds at 200 a day until it clears. Nothing is lost
Export failed
The archive stopped at 4.2 GB of 9.8. We are retrying once; the manifest lists what is already in it
Signup refused
We do not serve names for bulk outreach. This one matched on three signals, listed below. If we are wrong, one reply fixes it
Two mechanical bans
No exclamation marks in any product string. No emoji in any surface, ever. Neither
appears on this page, including inside the specimens above — the pack's own
annotation marks were stripped when those strings were quoted, because an annotation is
not part of the string.
11 Provenance
What this page is, and what it is not.
A rendering of eleven locked files, not a document with opinions of its own. Everything
above cites the file it came from, and the disagreements found while rendering are
listed rather than smoothed over.
The five rules that govern the pack, and this page
One source of truth per fact. A value lives in one file; everywhere else cites it.
The data file wins. Where a designed page and a pack file disagree, the file is right.
Every claim traces to the capability matrix. Not a row, not on a public surface.
Absence is stated, never blank.
Marked assumptions are not facts, and may not appear on a customer-facing surface.
What this page does not do
It makes no capability claim, quotes no customer, and prints no measurement of the
service. The only figures on it describe the system itself — ten colours, seven
type roles, six shapes, four elevations, four durations, five states, eleven measured
contrast pairings — plus the format specimens in §10, which are quotations and
are labelled as quotations.
Disagreements found while rendering
Disagreements between pack files, found while building this page
WhereWhat disagreesWhat this page did
tokens.json · ruzayo.cssTwelve colours are named as data; ten are named as tokens. Lead and terra deep exist only as literals inside three rules.Rendered the ten through their tokens. Declared the other two once, in this page's own stylesheet, cited to tokens.json.
ruzayo.css · tokens.json.rz-label--accent is terracotta at 12px, and terracotta measures 4.6:1 with the verdict do not use below 16px.Used the slate label for every eyebrow. Showed the accent variant once, raised to the 16px floor.
mailbox.d.ts · ruzayo.cssThe state is wants_you; the modifier is --wants.Printed both beside the chip in §7, so neither can be guessed wrong.
tokens.json · README.mdThe pack is one version and nothing in it carries a version of its own. tokens.json carries $meta.version 0.2, refreshed 2026-08-06.Versioned as the pack does: 1.1, locked 7 August 2026. Reported the per-file stamp rather than repeating it.
tokens.json · CANON.mdBone has a meaning and no prohibition in either file.Stated the absence in the swatch, rather than leaving the line blank or inventing a rule.
content-rules.mdDNS hostnames are set in mono, and no mono family is named in tokens.json or ruzayo.css.Used the platform mono stack for code and hostnames, and named the gap here.