BRAND.md says what the brand is: four colours, one accent, the five words, what
not to use. This file says how to build with it — the tokens as they exist in code, the
components that already ship, and what a pull request that adds an interface has to satisfy.
Read BRAND.md first. Where the two disagree, BRAND.md wins and this file is the bug.
01
The stylesheet is a file, and it is the only one
docs/hpp.css
It is emitted, not written by hand: tools/catalog_md.py holds one constant and produces
both this file and the <style> block inlined in every page. That is why they cannot drift, and
tests/test_design_system.py fails if they ever do.
Two ways to use it, and both are correct:
<!-- a page inside this repository -->
<link rel="stylesheet" href="hpp.css">
<!-- a page that must survive on its own: paste the file's contents -->
<style>/* contents of docs/hpp.css */</style>
⚠️ Never link it from a URL. Every published page here is self-contained by rule — a page
that fetches its stylesheet from someone else's domain stops working the day that domain does,
and the test suite refuses any @import, any https:// in url(), and any @font-face.
02
Tokens
Four come from the brand and are the only colours in the system. Four are derived and exist so that nobody invents a fifth.
| token | value | what it is for |
|---|---|---|
--hpp-black | #000000 | background of brand material and of the lockup |
--hpp-ink | #0F1113 | interface background, elevated surface |
--hpp-paper | #F4F1EB | primary text on dark |
--hpp-signal | #FF6A00 | the single accent |
--line | rgba(15,17,19,.18) | rules and borders — the ink at 18% |
--surface | rgba(15,17,19,.04) | a raised block — the ink at 4% |
--muted | rgba(15,17,19,.72) | secondary text — the ink at 72% |
--max | 1120px | the measure of .wrap |
The derived tokens are the ink at reduced alpha, never a new colour. BRAND.md states it:
a lighter step is the same token at reduced opacity. A test reads every rgb() in the
stylesheet and fails on any triple that is not 15,17,19, because a fifth colour arriving
through an rgba() is invisible to a hex scan.
03
The components that already exist
Use these before inventing. Each one is in docs/hpp.css and is already rendered by the
published pages, so anything you build with them looks like the rest of the project for free.
| selector | what it is |
|---|---|
.wrap | the container. Everything sits inside it, at --max |
.lang · .lang a[aria-current] | the language switcher. The current side carries aria-current, the other is a link |
.hero | the page opening: lockup, .eyebrow, h1, one paragraph |
.eyebrow | the small monospace label above the title — upper case, wide tracking |
.lead | the first paragraph of a section, one step larger |
section · h2 · h2 .version · h3 | the body. h2 .version prints a version next to a heading |
table · th · td | data. td.n / th.n right-align a number; tr.total marks the totals row |
.chips code | a list of short names as inline chips |
footer · footer .five | the close. .five carries the five words |
a:focus-visible | the focus ring. Never remove it |
04
The rules that are not negotiable
- One accent. The orange marks the point of attention. A screen with two points of attention has none — so if something new needs the orange, something else loses it.
- Colour never carries meaning alone. A label or an icon always goes with it. Someone who cannot distinguish the orange must still be able to read the state.
- No webfont, no external request. The type stack is system-only (
Sora,Inter,Segoe UIfor headings;JetBrains Mono,Cascadia Code,Consolasfor code and labels). - No red in the palette, on purpose. For state, use the terminal's own red and never paint a background: a light terminal has to stay readable.
- Declare the language.
<html lang>, andlangon any block in the other language. A screen reader that reads Portuguese with English phonemes is a page that excluded someone. - The focus ring stays. Keyboard is not a fallback.
- Every root document is a bilingual pair.
X.mdandX.pt-BR.md, linked to each other at the top. A test enforces it.
05
What a pull request that adds an interface has to satisfy
- uses docs/hpp.css — linked inside the repo, or inlined. Never a URL
- adds no fifth colour, in hex or in rgb()
- fetches nothing: no @import, no @font-face, no https:// in url()
- reuses the components above before adding a selector
- declares lang, and keeps the focus ring
- ships the .pt-BR pair if it is a root document
- is generated, if it belongs next to generated pages -- see the next section
Then: python -m pytest tests/test_design_system.py tests/test_docs_index.py -q.
06
If it belongs in docs/, generate it
The pages in docs/ are emitted artefacts. index.html, hpp.css and the catalogue pair are
written by catalog_md.py --write, which overwrites a hand edit without warning. If you want a
page of that kind to live there, add it to render_all() in tools/catalog_md.py and to
OWN_FILES, as index.html and hpp.css already are — then it is generated, it is listed once,
and it cannot rot.
The document pages — X.html beside each X.md, and the pages of the contract documents at the
root — are rendered from their Markdown at release and listed in docs/site-pages.json, which
catalog_md.py turns into the navigation of the landing page. Edit the Markdown, never the page:
tests/test_docs_html_pages.py fails when a page no longer carries its Markdown's headings.
A page that belongs anywhere else (a template, an example, a report) is yours to write by hand.
multi-session/lane-kit-*/templates/status-stakeholder.template.html is the reference for that
case: same tokens, written by hand, and it reads the board without ever writing to it.
07
Where the artwork lives
BRAND.md has the whole table. The two you will reach for:
assets/hpp-logo-header.png— the lockup for a page header, 1440 px. It is the approved artwork resized; do not redraw, recolour or crop it.assets/hpp-mark.svg— the symbol as vector, for print or large scale. Where fidelity decides, use the raster lockup.