Design system

The harperOS design system.

The design system holds the decisions that are true on every surface we make. How each block applies them is the Block Library.

Every value lives in the token file, and the design system names the value and gives its reason.

The test for what belongs in it: a sentence that stays true with no blocks at all belongs in the design system. A sentence that names a block, a class or a part of one belongs in the Library.

Principles

Five principles, each from the foremost authority in its discipline. A choice is right when you can name the principle it comes from.

PrincipleAuthorityIt asksIt demands
RestraintVignelliHow disciplined?A small, fixed set of tokens used everywhere, and generous space.
SignalTufteWhat survives?Every distinct style carries distinct information, and anything that carries none is cut.
VoiceHoeflerHow does it speak?Each typeface has one job: the serif opens, the sans says, the mono labels.
SystemFrostHow is it built?Every surface is assembled from existing parts, and a new part joins the system before any page uses it.
AffordanceNormanDoes it show what it does?Anything clickable looks clickable and answers when used, and the next move is obvious.

The first four govern how a surface looks. The fifth governs how it behaves.

Tokens

The token file (design-tokens.json) is the one machine source. The brand overlay (public/shared/brands/harperos.css) is what pages load, and it carries the same values.

Color

Grounds. A page is made of two surfaces and the lines between them.

Field

--field #F7F7F6

The page on a screen

Box

--alt #FFFFFF

The fill of a box, and the page of a document. Never a section background

Rule

--rule #E2E1DE

The line between sections and around a box

Rule soft

--rule-soft #EFEEEC

Lines inside a box

Ink. Type steps down in three weights of the same near-black.

Ink

--ink #181428

Headlines, strong text, the footer

Ink muted

--ink-muted #434343

Running text

Ink subtle

--ink-subtle #777270

Captions and metadata

The accent. Green marks one thing, and it steps down by hue when it has to carry text.

Accent

--accent #00AF7B

The one mark that carries the signal, and any fill that carries it

Accent text

--accent-text #00A373

Text at 24px and up in the accent, and the arrows and markers that carry it

Accent label

--accent-label #434343

A label under 24px, where no green light enough to read as ours passes

Accent soft

--accent-soft #E6F7F1

The product on screen, where a page shows it

Status colors encode information and appear nowhere else. They are used in diffs, charts and internal dashboards: success #00AF7B, error #FE5606, info #186FFF, warning #D4A017.

Contrast, measured. Text needs 4.5:1 against its ground, and large text 3:1.

PairRatioResult
Ink on the field16.75:1Passes
Ink muted on the field9.23:1Passes
Ink subtle on the field4.43:1Large text only
Ink subtle on a box4.75:1Passes
Accent on the field2.64:1Fails
Accent on a box2.83:1Fails
Accent text on the field3.02:1Large text and graphics
Accent text on a box3.24:1Large text and graphics
Footer tagline on ink5.77:1Passes
Footer notice on ink4.26:1Large text only

The bright accent fails as text at every size, so green text steps down by hue rather than by lightness.

Text of 24px and up takes the accent text, which passes the 3:1 a large line and a graphic need.

A label under 24px takes ink, because no green light enough to read as ours passes 4.5:1.

A link in prose carries a green underline, so it still signals itself with its text in ink.

The text greens ship on the staging drafts first. A staging page loads harperos-next, which adds the accent text, the accent label and the link underline to the brand overlay. The live overlay takes them with the public-site switch (open decision 4), and until then a live page sets its green text in the accent.

Type

Three faces, one job each. A fourth face is a defect.

FaceIts jobFallback stack
PonomarOpens: the page title, a section title, the next step, the wordmarkTiempos Headline, Georgia, serif
InterSays: titles below a section, every sentence, box text, quotes, the pull linesystem-ui, -apple-system, Segoe UI, sans-serif
JetBrains MonoLabels: section labels, row labels, attributions, captions, every numeralSF Mono, Menlo, monospace

Ponomar

Opens: the page title, a section title, the next step, the wordmark

Aa Bb Cc Dd 0123456789

Inter

Says: titles below a section, every sentence, box text, quotes, the pull line

Aa Bb Cc Dd 0123456789

JetBrains Mono

Labels: section labels, row labels, attributions, captions, every numeral

Aa Bb Cc Dd 0123456789

Email cannot load web fonts, so an email uses the fallback stacks: Georgia to open, a system sans to say, a system mono to label.

The scale. Every size on a screen is one of these rungs.

A register sets the headline rungs for a whole family of pages, never for one page.

A child never outweighs its parent: a label under a heading sits on a smaller rung than the heading, and two labels at one level share one size.

RungStandardPrivate overviewDenseArticle
Page title (H1)Ponomar clamp(40px, 8vw, 64px)Ponomar clamp(32px, 4.8vw, 48px)As standardAs private overview
Section title (H2)Ponomar clamp(32px, 4.8vw, 48px)Ponomar clamp(32px, 4.8vw, 48px)Ponomar clamp(32px, 5.5vw, 40px)Ponomar clamp(24px, 3vw, 30px)
Next stepPonomar clamp(24px, 3vw, 30px)As standardAs standardAs standard
Pull lineInter 600 20px, held to 640px with the accent ruleAs standardAs standardAs standard
Subsection title (H3)Inter 600 clamp(20px, 2.6vw, 24px)As standardAs standardAs standard
SubheadInter clamp(16px, 1.8vw, 18px)Inter clamp(16px, 1.8vw, 18px)As standardInter 18px
QuoteInter 16pxAs standardAs standardAs standard
Body and point titleInter 16px, the point title at 600As standardAs standardAs standard
Box textInter 15pxAs standardAs standardAs standard
FigureJetBrains Mono clamp(26px, 4vw, 34px)As standardAs standardAs standard
PriceJetBrains Mono clamp(30px, 4vw, 40px)As standardAs standardAs standard
LabelsJetBrains Mono 13, 12 and 11px, capitals, trackedAs standardAs standardAs standard

Page title (H1)

Ponomar clamp(40px, 8vw, 64px)

Every expert you need.

Section title (H2)

Ponomar clamp(32px, 4.8vw, 48px)

Pull line

Inter 600 20px, held to 640px with the accent rule

People direct. Agents execute.

Subsection title (H3)

Inter 600 clamp(20px, 2.6vw, 24px)

Subhead

Inter clamp(16px, 1.8vw, 18px)

The subhead states the idea and holds to two lines.

Body and point title

Inter 16px, the point title at 600

Body text carries the argument, one idea to a sentence.

Box text

Inter 15px

Box text sits one step below body.

Figure

JetBrains Mono clamp(26px, 4vw, 34px)

24
A figure

Price

JetBrains Mono clamp(30px, 4vw, 40px)

$15,000 a month

Labels

JetBrains Mono 13, 12 and 11px, capitals, tracked

Section label

Every private overview uses the private-overview register, and competitive-intelligence pages use the dense one.

The site’s articles and press release use the article register, named Oct 2, 2026 from the rungs above, with no size of its own.

Every other page uses the standard register (open decision 1).

Running text never drops below 16px. The only smaller text is a mono label, or box text at 15px.

The document surface has its own scale in points, in the token file under type_scale.page.

Space

One scale of ten steps, and every distance on a page is a step on it.

--s14px
--s28px
--s312px
--s416px
--s524px
--s632px
--s748px
--s864px
--s996px
--s10128px

Layout

  • Width. Content runs to 1,180px.
  • Gutters. The side margin is 16px on a phone, 32px from 768px and 64px from 1024px.
  • Breakpoints. 768, 1024 and 1440px, and no others.
  • Measure. Body text holds to 60 characters. On a private overview a title runs to 816px and a subhead to 720px.
  • Grid. No column grid is defined, so each block sets its own columns (open decision 2).

Shape, depth and motion

  • Square. Every corner is square, photos and icons included.
  • Flat. No shadow and no gradient. Depth comes from a 1px rule and a white box on the field.

Motion

A block arrives once, as it scrolls into view.

It rises 12px and fades in over 400ms on an ease-out curve.

Each child after the first follows 70ms behind the one above it, to a ninth child at 560ms.

Nothing moves again once it has arrived.

A page that opts into motion runs shorter and sharper. Every piece takes 300ms on cubic-bezier(0.23, 1, 0.32, 1), a strong ease-out, so a thing responds at once and settles.

What movesHow it moves
Workflow steps120ms apart, to a tenth step at 1080ms
An arrow between stepsDraws across in 300ms
A branch bracketDraws down in 300ms, 150ms after its step; the tie across in 250ms
A function in the columnLights 100ms after the one above it, and its underline 120ms after that
The steps after a columnWait 900ms, so the column reads before the line moves on
A figureRises 8px behind its own label, in 300ms
Logo rows45ms apart, and the marks within a row 30ms apart

The nav moves at its own pace.

The dropdown chevron turns in 120ms and the menu bars fold to the X in 200ms, while the panel itself appears at once with no fade.

A hover is quieter still: a button fades to 0.85 in 150ms, and the next step’s border wakes while its arrow leans 4px, both in 200ms.

Animated images

Every GIF we make comes from one engine, br-gif, so the homepage, a client page and a proof-run email look alike and improve together.

  • Two sources, said plainly. A drawn session shows how the product works in general. A recorded session shows one real run, every line from its record, and says "a real session" on its frame.
  • The first frame is the finished state. A reader who never sees it move still sees the point.
  • Ten frames a second, 96 colors, ordered dither. Desktop is drawn at the size it is shown, and phone at 390 pixels wide.
  • On a page, it plays once and holds, with a still for readers who turn motion off, as the rest of the motion canon does. In an email it may loop, because a reader opens it later and should still see it move.
  • The web may take an MP4 beside the GIF, at a fraction of the size. Email takes the GIF, which every client plays.
  • An email GIF stays under 20 MB and carries a link under it. Gmail’s 25 MB cap is the ceiling, and a client that blocks the image still reaches the session through the link.

Charts and diagrams

Charts

  • A small dataset is a table. A reader takes eight numbers off a table faster than off a picture of them.
  • Color scales with the number of series. One series takes the green and everything else stays in the ink ramp, because the green marks the point the chart is making.
  • Several series take the status colors, which are success, error, info and warning, and never an invented color. Four is the cap: a chart that needs a fifth is the wrong chart.
  • A diff reads in three of them, with success for added, error for removed and info for modified or context, and no other color.
  • Status colors encode and never decorate. They belong in diffs, charts and internal dashboards. On a page that sells, a second series stays in the ink ramp.
  • Labels and values are mono, and the rest is data ink. Every gridline separates data, and a third dimension or a fill for mood comes out.
  • A trend line may run diagonally, because it is data. A connector never does.

Diagrams

  • One idea per diagram. A second idea is a second diagram.
  • Nodes are square. A rounded corner fails the build, which is how the geometry stays one thing across every surface.
  • Lines run only across or down. A diagonal makes the reader measure an angle that carries no meaning.
  • Radial, hub-and-spoke and orbit layouts are out. A center with satellites forces diagonal spokes and reads as limbs rather than as a system.
  • A line is a hairline. It is 1px in the rule color where it separates. A connector carrying an arrowhead takes the icon’s 1.5 line in the muted ink, so the line and its head read as one stroke.
  • Labels are mono, and none renders below 11px. Below that a label is decoration, because nobody reads it on a phone.
  • One green mark, on the element that carries the point.
  • Measure at 375px and at 1280px. The size you drew it at is the one size that proves nothing.

The document surface

A published Google Doc is the same system at document scale.

Publishing applies it in the same call, so a published Doc cannot be unformatted.

The profile is code: tools/runtime/google-workspace/src/format_gdoc.py reads its sizes, spacing, faces and colors from the token file.

Two tiers. document is the default that every published Doc inherits.

proposal is the document tier plus co-brand restraint: the client is named in our type, the client-logo placeholder is skipped, and the header meta reads APPROACH · CONFIDENTIAL where a document reads CONFIDENTIAL.

A legacy tier survives for rollback, and v2 is an older name for proposal.

The scale is in points, and it lives in the token file under type_scale.page: the title at 34pt, H1 at 28, H2 at 24, H3 at 20, H4 at 11, body at 11 and the dek at 12. The spacing above and below each level lives beside it under type_scale.page_spacing, and the formatter hardcodes none of it.

The faces carry their jobs. A heading takes the display face and is never bold.

H4 is the mono label, in the accent.

Body is ink-muted on a white page, and the dek, which is the single paragraph after the title, is the body face one step above it.

Green reaches the links and that mono label, and nothing else.

The one gray rule is the section line. It is a 0.5pt border above every section heading, with 24pt of padding under it. A native horizontal rule cannot be brand-styled through the API, so the publisher emits none for a markdown --- and the formatter strips any it finds.

The header and the footer are typeset, never images, and neither carries a rule. The header sets the wordmark in the display face at 13pt with the underscore in green, left, and the mono meta line right. The footer sets the wordmark alone at 12pt in the subtle ink.

A table reads from type and one rule. A data table sets 9pt.

Its header row and its first column are mono, the header row in the accent and the body in muted ink.

Rows are separated by 0.5pt horizontal rules, and there is no vertical rule, no outer border and no header underline.

The header row takes the field color and the body rows take white.

A meta block or a table of contents takes white cells, with one hairline above the block and one below.

The sheet surface

The Build & Deployment Master is the same system at grid scale, in every deployment that carries one: its Task, Milestones, Plugins, System, People, Schedule, Delivery Timeline and loop tabs.

The profile is code: tools/runtime/build_master/tab_format.py reads its faces, sizes and colors from type_scale.sheet in the token file.

The other workbooks we render, such as the sales CRM and the trackers, still use the older profile in tools/runtime/lib/sheet_format.py, and this section does not govern them yet.

The top five rows are the same on every tab.

  • Row 1 is the title, in Inter 14 in ink, and it is never bold.
  • Row 2 is the subtitle, in Inter 9 in the subtle ink.
  • Row 3 is an empty spacer.
  • Row 4, the band that names each group of columns, and row 5, the column headers, are JetBrains Mono 9 bold in the muted ink on the screen field, with no line under either.
  • Rows 1 to 5 are frozen.

The definition rows are folded away. The DEFINITION, SOURCE and TRIGGER rows under the headers are one row group, closed when the tab opens.

They are JetBrains Mono 8 in the subtle ink.

Nothing on a sheet is ever italic.

A column’s face says where its values come from. A computed or designed column is mono, because code wrote it or the agent proposed it.

A captured column is Inter, because a person stated it.

A column whose cells are sentences is Inter too, whatever its source.

A value the code derives from what is built, such as a skill’s agent, its knowledge, its adversarial agent, its output or its status, is computed; "designed" names only a proposal that is not built yet.

Color marks a link or a status, and nothing else.

  • Body text is one ink in every column.
  • A link is the brand blue, #186FFF, underlined, so a reader tells a link from text at a glance.
  • A status colors its own text, on white.
  • A chart on a sheet, such as the Delivery Timeline, takes its series from the status colors, as every chart does.
  • The grid itself is ink, muted ink, subtle ink, the screen field, white and the rule color. Gridlines are off, and one hairline in the rule color runs under each data row, across only.

Every column is as wide as its content needs.

  • A header is never cut.
  • A column is sized where the bulk of its content is: its median line, and wide enough that nine words in ten never break across a line.
  • A column that lists links, like Knowledge, is as wide as its longest link, so each reads whole on one line.
  • A filter button or a dropdown adds its own width.
  • The widest a column grows is set in the token file. Past it the cell wraps, and nothing is ever cut off.

Weight marks hierarchy, and nothing else. A heading’s name on Milestones, a task title and its function, a plugin’s name and the Status pill are bold. Every other body cell is regular.

Every task row is four lines tall. A task reads the same height whatever it holds, so the grid scans as a list. A People row is the same height.

The check runs on every tab. master_look.py T1 reads every tab and names each departure from this profile. On People, Schedule and Delivery Timeline it reads the top five rows only, because their bodies come from delivery_plan.py, which runs its own fit check.

harperOS carries an older copy. This profile lives in the Amerikas deployment. harperOS’s own tab_format.py and master_look.py predate it, so a new deployment built from harperOS starts on the older profile until the two are merged.

Voice and downloads

The words on every surface follow the company’s writing standard, which is a separate canon: plugins/system/system-workspace/knowledge/A08-Voice-and-Formatting-Standards.md.

It holds the principles, the sentence and paragraph discipline, the format each kind of document takes, and the line read every client-facing page passes.

The design system sets how a surface looks, and that standard sets how it reads.

The published assets live at harperos.ai/assets.

Three banners sit there, each with its own PNG download: the X header at 1500 by 500, the LinkedIn personal banner at 1584 by 396, and the LinkedIn company banner at 1128 by 191.

All three are rendered from one source file, so the tagline is changed in one place and every banner re-renders at its exact published size.

LinkedIn and X publish no API for a profile banner, so a new file is uploaded to each by hand.

Laws

A law holds on every block and every page. Each carries its reason and says how it is held.

  • 1 · The display face opens, and it never stacks. Ponomar marks where a page or a section starts. It sets the H1, the H2 and the next step, and nothing below them. A pull line is set in the text face, never the display face (MC, Oct 6, 2026). Two Ponomar lines never touch, because two openings side by side cancel each other. The renderer’s heading structure holds it; a reviewer judges stacking.
  • 2 · A section carries one subhead, and it fits in two lines. A third line turns the subhead into body text at the wrong size. On a phone two lines hold about 76 characters. The renderer holds it on the private overview, and the page check measures it in a browser.
  • 3 · A heading is a sentence, and so is every line under it. A reader takes in a sentence without effort and has to rebuild a fragment, so a section’s heading carries a subject and a verb in the active voice. Two things are not sentences: a box’s title, which is a name of four words or fewer with the statement in the box’s text, and a section label. The renderer holds box titles, and the line read judges the headings.
  • 4 · Green marks one thing and never floods. The accent marks a link, the phrase a heading turns on, a section label, a box footer and the next step. It never fills an area and never sets a mood. The build refuses a green fill, and a reviewer judges placement.
  • 5 · One field, and the footer is the only dark surface. Sections separate by space and a 1px rule, never by a change of background. A page stripped of every background still reads from type and space. The block canon holds it, because it offers no section background.
  • 6 · One primary action per view. The primary button is ink, a ghost button beside it is the quieter way to read further, and a text link moves within prose. Two primary actions compete and neither wins. The renderer holds it on the hero, which takes one primary and at most one ghost.
  • 7 · A set fills whole rows. Three items sit as three and four as two by two, never three and one. A broken row reads as a missing item. The renderer refuses a ragged row.
  • 8 · Everything clickable shows its states and is big enough to hit. It has a hover, a visible focus ring and a pressed state, plus a disabled state where one applies. It measures at least 44 by 44px. A thing that does nothing never looks like a control. The phone check measures tap size, and no block documents its states yet (open decision 3).
  • 9 · Phone first. A page is built for a 375px screen and enhanced upward with min-width rules only. Nothing scrolls the page sideways. The build’s overflow checks and the 375px render hold it.
  • 10 · A visual shows what text cannot, and it is built from the atoms. Its atoms are square boxes, 1px lines that run only across or down, mono labels and one green mark. A visual that restates its points in boxes is text in costume. The embed check holds its tokens, and a reviewer judges the rest.
  • 11 · Text meets the contrast floor. Text holds 4.5:1 against its ground, and large text and graphics hold 3:1. The design-system page computes every pair from the brand overlay and refuses a ratio that differs from the table above.

Surfaces

Every artifact belongs to one of five surfaces, and the tokens map onto each.

SurfaceFieldFacesInteractionCanonical instance
Screen#F7F7F6Web fontsFullThe website and every client page
Document#FFFFFFEmbedded fonts, the point scaleNoneA published Google Doc; a PDF is the same surface
Sheet#FFFFFF, the band and headers on #F7F7F6Inter and JetBrains Mono, the point scaleLinksThe Build & Deployment Master
EmailThe screen tokens, inlinedFallback stacksLinksA newsletter; a plain reply stays unstyled
Social tile#181428Inter headline, Ponomar wordmarkNoneThe LinkedIn and X banners

The social tile inverts the field because LinkedIn and X surround it in white, and a light tile would disappear into the feed.

Who can open a surface is separate from how it looks.

  • noindex hides a page from search and protects nothing.
  • A public page opens for anyone.
  • A page under /staging asks for the team’s password.
  • A client page under /clients/{slug} opens only through that client’s key link.
  • Source kept under local/ is never deployed.

Identity

  • The wordmark is harperOS_, in Ponomar with the underscore in green. It recolors for its ground and never changes form.
  • A client’s OS is named in our type as {Client}OS_, with only the underscore in green.
  • The mark is the bare h, recolored like the wordmark. The rounded badge exists only where a platform requires an app icon.
  • A partner or client is named in our type, never set beside our wordmark as a second logo. Other companies' products appear as their own logos, at equal optical size.
  • People are photographed square on the brand green, with no frame and no filter.
  • The footer carries the wordmark, the tagline "People direct. Agents execute. Knowledge compounds." and the copyright. A public page adds its legal links: Terms, Privacy and Support.

Marks

Each mark is made for one ground, and the file name says which: a light ground takes the ink wordmark, and the ink ground takes the light one.

Photography

People are photographed square, on the brand green, with no frame and no filter. The same file works on the site, on LinkedIn and in a deck.

Michael Chauliac
Michael Chauliac
Patrick Adams
Patrick Adams
Pierre Merzeau
Pierre Merzeau
Mike Lamb
Mike Lamb

Icons

One icon style: a 24px grid, a single 1.5px line, square caps and miter joins, in ink. Each icon carries exactly one concept, and every icon comes from one registry.

A sparse section stays sparse. A section may carry one registry icon above its label, when the icon names the section’s concept.

It arrives with the section’s reveal and never moves again.

No section gets its own drawing (MC, Oct 6, 2026).

grid

The functions of a company, the whole company

doc

What people know, written down

step

Getting better, one step at a time

person

Expertise, what one person knows

bolt

Getting the work done

check

Quality, done right

cycle

Learning, the loop that repeats

archive

Kept by the company

moon

Outside the working day

sun

During the working day

compass

Direction, people direct

play

Execution, agents run the work

layers

Compounding, knowledge that builds on itself

chat

A conversation, a message

key

Ownership, the permission to change

sliders

Control, deciding what runs on its own

scale

Parity, as good as your person

shield

Verification, work that has been checked

search

Evidence, the sources behind an answer

globe

The website, and being found in search

wave

Voice

calendar

Time, how soon

dash

General management

trend

Sales, growth

megaphone

Marketing

package

Operations, the goods that move

banknote

Finance

people

People and HR, the team

plus

More of the same kind

quote

Someone else's words

chip

The system the work runs on

cap

Training, people learning the work

pause

Waiting for a person

book

The rules the system runs by

lock

Private, data that stays yours

addperson

Adding a person to the system

badge

A person's own access

segments

Customers, grouped by what they do

film

Content, the series a studio makes

app

The app, the product people use

agent

An agent, one role the system runs (every agent card carries it)

delivery

Delivery, the client work the firm sells

data

Data, the systems it is read from

harp

Holding and passing on knowledge, the harper (the name's origin)

arrow

Direction of travel: the diagram arrow and the next step

Buttons and links

A view carries one primary action.

A ghost button beside it is the quieter way to read further, and a text link moves within prose.

Each shows a hover, a visible focus ring and a pressed state.

A whole card that goes somewhere is one link. The entire card is the target, never a word inside it, and it shows a hover, a focus ring and an arrow that says go.

ActionKindWhere it goesUse
Book a problem-mapping sessionprimaryhttps://calendar.notion.so/meet/mchauliac/harperosThe one main action on a page, the same label in the menu and in the page (Oct 2, 2026)
Read our thinkingghost/articlesThe quieter way to read further
The Block Librarylink/design-system/blocksA link inside prose

Theming

A brand is one token file. Everything else in the system is shared.

Three layers, and only the first one changes per brand.

  • The brand overlay (public/shared/brands/{slug}.css) holds the values. It carries every color, the three faces with the font files behind them, the ten space steps, the content width and the gutter.
  • The block sheet (public/shared/client-site.css) holds what a block is. It reads var() and states no value of its own.
  • The page shell is the renderer. It links the overlay first and the block sheet second, preloads that brand’s fonts, and emits the same markup whichever brand it is.

A client’s design system is a copy of the overlay with their values in it. The content modules do not change and no block changes.

Three things land outside the overlay: the family names its brand (brand: "slug" in its _family.mjs), the renderer’s brand registry names the font files worth preloading, and those woff2 files ship under /shared/fonts/.

The renderer refuses a brand slug it does not know, so a missing registration fails the build instead of the page.

The contract is 26 tokens, and the block sheet uses every one of them.

GroupTokens
Color--accent --accent-soft --ink --ink-muted --ink-subtle --rule --rule-soft --field --alt --footer-line --footer-conf
Type--font-display --font-body --font-mono
Space--s1 through --s10
Layout--maxw --gutter

A token no overlay defines is read with a fallback. The block sheet writes var(--accent-text, var(--accent)), so a brand that names no text green falls back to its own accent.

That is how a preview overlay adds a token without touching any other brand.

--measure is the opposite case: nobody defines it, and that undefined value is the approved full-width rendering.

A value in the wrong layer fails the build. The block sheet may carry no raw color and no font literal.

An embedded visual is held to the same bar inside its own file, so a swap reaches the visuals as well as the pages.

That check is the standing guarantee that a rebrand is a file.

The swap is proved by rendering it. public/shared/brands/_swap-test.css is a deliberately contrasting fixture: a blue accent, a Georgia display face and its own ink ramp.

It is never assigned to a client.

An acceptance case renders the same page under it and under ours, and it fails when the markup differs by anything other than the token file the page links.

Building a client’s design system, in order.

  • 1 · Choose the values: the accent, the ink at three weights, two rule weights, the field, the box and the two footer tints.
  • 2 · Measure each pair against the ground it sits on before adopting it, and hold running text to 4.5:1.
  • 3 · Choose three faces, one job each, and ship their woff2 files.
  • 4 · Copy the overlay, replace the values, and keep every token name exactly as it is.
  • 5 · Name the brand in the family, register its preloads, render a page, and read it at 375px and at 1280px.

A brand face Google cannot serve takes a page stand-in, chosen by probing. A licensed face (Futura PT, an Adobe or foundry font) loads on the web and in email but never in a Google Doc, Slide or Sheet.

  • The token file names the stand-in per role in font.{role}.stack_page, and the Google renderers read it through tokens.DISPLAY_PAGE, BODY_PAGE and MONO_PAGE.
  • The weight and the case are tokens too. font.display.weight of 600 or more makes Doc headlines bold, and font.display.case: "upper" sets them in capitals, because a Doc has no text-transform.
  • Probe the stand-in on real documents before adopting it. The Docs API keeps a face it can render and silently drops one it cannot, and the Google Fonts catalog does not predict which.

- Jost was refused on every document tested.

- League Spartan was refused on some documents and kept on others.

- Montserrat was kept on every document.

  • Send bold as the bold flag alone. bold: true together with weight: 700 renders regular.
  • Read a Doc back against its named styles. A run whose size or bold equals its named style returns neither, so a check that reads only inline values reports a correct Doc as broken.

Accessibility

The floor every surface holds.

  • Contrast. Text holds 4.5:1 against its ground and large text holds 3:1, and the measured pairs are in the contrast table under Color. The design-system page computes each pair from the brand overlay and refuses to build when a stated ratio differs from the shipped one.
  • Focus. Every interactive element takes one ring on :focus-visible: a 2px outline in the accent text green, offset 2px. That green measures 3.02:1 against the field, the ratio a control holds.
  • Tap targets. A control measures at least 44 by 44px, which the token file states. The block sheet sets it on the wordmark, the phase tabs, the nav steps, the footer links, the menu button and the calculator controls. No build check measures a rendered target yet, so the phone pass measures them in a browser.
  • Reduced motion. Every reveal sits inside prefers-reduced-motion: no-preference, and the page script returns before it starts anything when a reader asks for less. A reduce rule then stops what is left: every transition, every animation and the smooth scroll.
  • Alt text. The renderer refuses a portrait that carries no alt text, and it stops on a logo or a mark whose alt is missing. An empty alt still passes, so an image that says something says it in words. An icon is marked aria-hidden, because the phrase beside it carries the meaning.
  • Heading order. A page carries one h1, and it is the page title. A section title is an h2 and a subsection title is an h3. The renderer emits no h4, and the type specimen’s sample headings are hidden from the outline, so a specimen never enters it.
  • Language. Every page declares lang="en", the one value the renderer writes.

What is not enforced. No build check measures a tap target, a contrast ratio inside an embedded visual, or the order a screen reader reads a page in. A person runs that pass.

How the system is held

  • Values. The token file is the one machine source, and a renderer reads it and never types a value. The brand overlay repeats its colors, and a test fails when the two differ, because a Doc, an email, a Sheet or an invoice reads the file while a page reads the overlay.
  • The design-system page. It is generated from this canon and the brand overlay, through the blocks. It refuses to render when a color, a contrast ratio or a heading size stated here differs from what the pages load.
  • The build refuses what it can check: raw colors and fonts, rounded corners, shadows, off-scale sizes on block pages, ragged rows and desktop-first media queries.
  • Judgment covers the rest, through the creative director’s review against these principles and laws.

Open decisions

  • 1 · Three heading registers or one. Standard, private overview and dense all ship today.
  • 2 · A column grid. Restraint names a strict grid, and none is defined.
  • 3 · States per block. Law 8 requires them, and the Library documents no block’s states.
  • 4 · The text greens on the live site. Adopted (MC, Oct 2, 2026): the site family keeps the text-green overlay, and it goes live with the one-step cutover of the public site, every page together.

Sources

  • plugins/custom/marketing/harper-identity/knowledge/KNW_BR_MC_Block-Library.md, the faces and the ladder.
  • plugins/custom/marketing/harper-identity/knowledge/design-tokens.json, the token file, and tools/runtime/google-workspace/src/format_gdoc.py, the Doc profile.
  • public/shared/brands/harperos.css, the brand overlay, and public/shared/client-site.css, the block canon, both in the website repository.
  • HarperOS-Website/src/components/brand/brand.ts and BrandFooter.tsx, the live site.
  • An independent check of the canon and the /design-system page against the code, Sep 19, 2026. It measured the page in a browser at 1280 and 375px.
  • Contrast ratios computed from the shipped values with the WCAG 2 formula; page counts per register from the rendered HTML under public/.

Your agents. Your OS. Deployed in 60 days.