Skip to content
HUBIQ

01

The rules.

The system, written for a person or a model to read before making anything. Normative: where a site and this disagree, this is right.

From hiq-core-design.md, rendered at every build.

03

Read this before generating any HUBIQ surface. It is normative: where this file and a screenshot disagree, this file wins.

0. The one thing to understand first

HUBIQ is a portfolio of sixteen initiatives sharing one interface. That only works if colour is a variable. There is exactly one accent live on any surface, held in --ac, and every accented thing on that surface reads from it: rules, chips, button fills, card washes, hover borders, link colour, focus rings, chart series.

Three levels, and only three. Nothing may reach past this chain to a hex.

Level Meaning
palette Eleven raw hues. Stock. Never referenced directly by a component.
--house The theme's accent. In the HUBIQ theme, house green #A9F20C, on portfolio-level surfaces: home, initiatives index, approach, contact, footer. In an initiative's own theme, its leased hue.
--ac The per-surface accent. Defaults to --house; overridden once per page root, or once per card root in a grid of differently-accented initiatives.

A component written against --ac is reusable by all sixteen initiatives. A component written against #A9F20C is reusable by one.

Vocabulary: the businesses are initiatives, never "solutions" or "products".


1. Tokens

Grounds and text roles

A page changes ground in chapters, never the same one twice in a row, and each ground carries its own four text roles. The roles are core; the values are the theme's (themes/<name>/theme.css, laws and ratios in its THEME.md). HUBIQ's are in themes/hubiq/.

Ground Token Role
Paper --ground-paper The default reading ground. Heroes, arguments, anything with a long read.
Mist --ground-mist The second light chapter: inventories, stat rows, a ground ladder.
Grey --ground-grey Column bands, where the columns are the content (cards).
Grey deep --ground-grey-deep The darkest light ground. A call to action, often over the lattice (7.26).
Ink --ground-ink The drama chapter: display words, figures, the footer.
Panel --ground-panel Raised surfaces on ink. Never a chapter of its own.
Forest --ground-forest The theme's coloured dark chapter. The name is HUBIQ's (its green chapter); another theme puts its own dark colour here.
House --house The anchor: the theme's accent as a whole band, once a page at most.

Every ground sets the same four roles, --fg, --body, --label and --rule, so a component written against the roles works on any ground and in any theme.

Ground Heading --fg Running text --body Label --label Hairline --rule
The four light grounds --ink --ink-body --ink-label --rule-light
Ink, panel --paper --muted --muted2 --rule-dark, or --house
Forest --paper --muted --muted --rule-dark, or --house

A theme chooses --ink-body and --ink-label on its darkest light ground, so they pass on all four. --lead-start is the grey a scrubbed lead begins from: large text only, paper and mist only, never a resting state. Every pairing above must clear its threshold in every theme; the build checks it (KIT.md, section 0).

The accent rule

A theme's --house is its leased hue (below). A light accent on a light ground cannot carry a line, a label, a border or a focus ring, so wherever that is true the accent is a fill on a light ground, with ink on it, and may be a line or text only on the dark grounds, at ratios the theme measures and records. This is law 8. HUBIQ's green and TENANTIQ's gold both fall under it.

Palette and leases

Hue Hex Status
green #A9F20C Leased: ATOMIQ. Also --house.
blue #1AD3F0 Leased: MEDIQ.
pink #F005AD Leased: TRAFFIQ (was TENANTIQ, to August 2026). Also validation error, a live collision.
gold #FFBB00 Leased: TENANTIQ.
indigo #5271FF Leased: SITEIQ (was orange, to August 2026).
purple #B833FF Leased: HEURISTIQ. 4.63:1 on panel, tight; retune before reusing it anywhere text-bearing.
amber #FCC01A Unleased, and locked out: one degree from gold while TENANTIQ holds it. UI only, the 5Di shared-objective nodes.
orange #F78A20 Unleased, and held as --house-2, the 5Di highlight (7 October 2026). One braid strand. Came off lease when SITEIQ moved to indigo.
purple-deep #7C05F2 Fill and gradient only. Fails text contrast.
ice cyan #60F1F4 Locked out — within 40° of blue while MEDIQ holds it.
mint #B2F8AC Locked out — within 40° of green while ATOMIQ holds it.
QUIET #A8B6CC Not a lease. See below.

No two initiatives in market may hold the same hue, and no hue within 40° of a held hue may be leased.


2. Type

Two families. Poppins 400/500/600/700/800 for everything editorial. Space Mono 400/700 for labels, meta, buttons and any number that is a fact rather than a phrase.

The division is not decorative: mono marks machine-side content (status, sequence, category, measure); Poppins marks human-side content (argument, description, name).

The tracking rule

Tracking runs inversely to size, and it is compensation, not taste. Light text on a dark ground blooms — strokes optically thicken, counters fill. Large display type needs its letters pulled in (−0.022em to −0.035em); small text needs them pushed apart (+0.006em body, +0.12em to +0.24em on mono caps). Body leading sits at 1.62 for the same reason. If a size changes, its tracking changes with it.

Role Size Weight Tracking Leading Colour
display clamp(40, 5.6vw, 76) 800 −0.035em 1.04 paper
h2 clamp(28, 3.2vw, 42) 800 −0.025em 1.12 paper
h3 clamp(21, 1.9vw, 26) 700 −0.018em 1.22 paper
h4 18px 700 −0.012em 1.3 paper
lead clamp(18, 1.5vw, 21) 400 0 1.5 / 60ch paper
body 16px 400 +0.006em 1.6 / 70ch muted
small 14px 400 +0.006em 1.5 muted
meta 11px mono 700 +0.14em 1.4 / caps muted2

Tokens: --t-display to --t-meta, with --lh-* and --tr-* beside them, in tokens.css.

Three rules, added at 2.0, each from a page that had gone wrong.

  1. One display size per page, the H1, and the section heading is a clear step below it. The old table put h-hero at 88 and h-sec at 58, which on a wide screen read as a run of H1s. Now 76 against 42. A statement inside a band is an h3 at weight 600, never an h2, because a band's heading is its heading and nothing inside it may compete.
  2. Body copy has one size and one colour. 16px in --muted, 1.6 leading. Seven body sizes between 13 and 20 had accumulated on one site and nothing told a reader which was which. Captions, table cells and notes are small, 14px, and that is the only other running-text size.
  3. Colour follows role, not mood. Headings and leads are --paper; running text is --muted; labels are --muted2. A lead is the one sentence under a heading and it earns full contrast; a paragraph dimmed to --muted2 because it "felt secondary" is the thing this rule exists to stop.

v2 type roles

Added at 3.0. v1's problem was that H1 and H2 differed by one step of size and nothing else, so a page read as one loud voice. v2 builds hierarchy from four levers, and weight is the least of them: scale gap, colour, family and placement. The H1 is deliberately modest; the display word above it is a different kind of object altogether.

Role Face Size token Weight Leading Colour
Display word Poppins --type-word, clamp(88, 14vw, 232) 800 .86 --fg
H1 option face --type-h1, clamp(40, 4.2vw, 62) 600 1.06 --fg
H2 option face --type-h2, clamp(28, 2.6vw, 38) 600 1.12 --fg
Lead option face --type-lead, clamp(24, 2.2vw, 32) 600 1.18 --fg, may fill from --lead-start
H3 option face --type-h3, clamp(20, 1.6vw, 24) 600 1.2 --fg
Body option face --type-body, 17px 400 1.72 --body
Small option face --type-small, 14.5px 400 1.6 --body
Label Space Mono --type-label, 13px 400 1.45 --label, sentence case
Stat Space Mono --type-stat, clamp(56, 6.4vw, 104) 400 1 --fg

Display words are decoration, not headings. They bleed off both edges, may break mid-word, may drift. They are aria-hidden and the sentence they come from is always present as real text nearby. One set per band at most.

Space Mono is the second face, and it is used with confidence: the rail labels at 13px in sentence case rather than 11px caps, and every stat numeral. v1 used it as small print.

The option face is Inter Tight, chosen 25 September 2026 over Poppins throughout: Poppins is kept for display words only, and H1 down is set in Inter Tight at 600, tracking -0.024em on headings. Three families, one job each: Poppins 800 for decoration, Inter Tight for everything read, Space Mono for labels and numbers. The table's "option face" means Inter Tight. Inter Tight is free in Google Fonts, so Google Workspace can use it and the brand basics page does not split into web and documents. It is self-hosted by next/font like Poppins, so the site makes no request to Google.

The tracking rule above still applies: tracking runs inversely to size. On light grounds text does not bloom, so the positive tracking v1 gave body copy on ink is dropped; body is set at 0.


3. Space and layout

Four-unit base: 4, 8, 12, 16, 24, 32, 48, 64, 88, 120, 160. Content column caps at 1180px, 28px gutters. Heroes fill 100svh, never 100vh — mobile browser chrome makes vh lie and a hero's job is to be exactly one screen.

Section rhythm is deliberately non-uniform:

  • Wide rhythm, 120–160 padding. Statement, worldview, five-beat panel, hero. One idea per screen. Never more than one full-contrast line.
  • Tight rhythm, 64–88 padding. Portfolio grid, nine-box, tier grid, footer, ticker. Scannable, repeating, no argument carried in the whitespace.

Uniform padding flattens the difference between a claim and an inventory, and making that difference legible is the whole job.

Breakpoints

Range Behaviour
< 720px One column. Cards stack, nav collapses to drawer, nine-box becomes nine rows, five-beat becomes five rows. Hero keeps 100svh at smallest clamp.
720–1024px Two columns for card grids, three for nine-box. Tier grid stays horizontal — it is a sequence.
1024–1180px Full desktop at fluid width, gutters at 28px.
> 1180px Column caps at 1180 and centres. Only full-bleed elements (hero film, divider wash, ticker) ignore the cap.

The rail and the six columns, v2

Added at 3.0. A v2 band is laid on six columns inside a 1440px wrap, gutters clamp(16px, 3.2vw, 48px). A label rail takes the first two columns, one third, and the content takes the right two thirds. The rail holds one mono label, numbered, sticky within its band. The rail is what makes a page look designed rather than stacked.

Column hairlines are visible, and they are drawn on the same six columns the content snaps to. Three densities:

  • Frame, the default: the two edges and the rail boundary. A vertical line through running text makes it harder to read, so no line ever crosses a paragraph.
  • Full, all seven, only in a band where nothing but column content sits, such as a bare stat row. Revised 25 September 2026 after the first v2 page: a card grid under a heading and lead draws its own column rules instead (a left rule on each card, a right rule on the last), so the band keeps frame lines and no line crosses the heading.
  • Edges, for a half-width ground such as the two greens side by side.

Hairlines are 1px elements snapped to whole pixels with round(), not borders on fractional columns: on a fractional position a hairline is split across two pixels at partial strength and reads softer than its neighbours.

Below 720px the grid is one column, the rail stacks above the content, frame keeps only the edges and full keeps the edges and the centre.


4. Surface and elevation

There are no shadows. On a near-black ground a drop shadow is invisible, so raising a surface means adding light.

Level Name Rule
E0 Ink Page ground. No border, no wash. Sections separate by rhythm alone.
E1 Hairline A --line border on ink. Groups without raising: nine-box cells, footer columns, table rows.
E2 Panel --panel fill + --line border, 10px radius. Tables, drawer, form containers, dense data.
E3 Accent wash linear-gradient(180deg, color-mix(in srgb, var(--ac) 12%, transparent), transparent 68%) over a 1px --line border at 10px radius. Every card.

Hover lift is geometric, not tonal: translateY(-3px) plus border to color-mix(in srgb, var(--ac) 55%, transparent).


5. Motion

This is brochureware. Its job is to be read and believed, and a page that never moves reads as a document rather than as a working business. Use more of it than feels necessary.

Rewritten at 3.0, 25 September 2026, and the brief changed again. Versions 1.6 to 2.0 opened the door to motion but kept limits that came from a taste for restraint rather than from protecting anyone. v2 wants much more movement: scroll-linked parallax and horizontal drift on display type, scrubbed fills, pinned sequences, hover states with real character, count-ups. What survives is everything that protects a reader. The table says which is which.

2.0 rule 3.0
Distance 12 to 20px, everything rises Arrivals still rise 12 to 20px. Other motion is sized to the gesture: display words may cross the screen, cards may slide, flip or scale
Stagger caps at 400ms Capped by where the eye is: a sequence may run as long as it is on screen and nothing waits on it
No parallax on text, ever Drift and parallax allowed on display words and decorative type. Never on body copy, a lead being read, or navigation
No count-ups Allowed, on the conditions under What does not move
One gesture per band One lead gesture per band; supporting motion inside it is fine
At most one scroll-linked figure per page Scroll-linked motion is its own register, with its own rules
Nothing scales past 1.02 A surface still never scales past 1.02. A small element may pop in from 0

Easings

Four curves in v2, one per job. v1's two (--ease-state, --ease-panel) stay on v1 pages until they are rebuilt.

Curve Token Use Duration
cubic-bezier(.165,.84,.44,1) --ease-reveal Content arriving; a count-up 1 to 1.2s
cubic-bezier(.77,0,.175,1) --ease-ui State that moves a surface: a card inverting, a switch committing .25 to .4s
cubic-bezier(.34,1.56,.64,1) --ease-pop Small pops with a little overshoot: the pill's arrow .5s
cubic-bezier(.25,.46,.45,.94) --ease-hover Hover micro-states: a footer bullet .2 to .25s

Four registers

Motion answers to whoever asked for it, and the cases have opposite instincts. Deciding which register something is in is the first question, not a detail. v1 had three; 3.0 adds scroll-linked as the fourth rather than renaming any, because ambient still earns its place.

Interface motion is a control replying to a person: hover, focus, a panel opening. It may have character now (an inverting card, an arrow that jumps) but it is still a reply, so it stays inside .5s and never delays the thing asked for.

Editorial motion is content arriving, driven by a timer once it crosses the trigger line: an eyebrow, heading and lead settling, a grid landing, a figure building. Once per arrival, never again on the way back up.

Ambient motion is continuous and unprompted: the ticker, the dot mesh, a hero film. Texture, not content. Never in the reader's focal path while they read, carries no information, stops off-screen and when the tab is hidden.

Scroll-linked motion, new at 3.0, is driven by scroll position: the reader holds the clock. Four kinds:

  • Drift: display words moving sideways against the scroll, or parallax on decoration.
  • Scrub: a lead filling grey to ink, a figure drawing, a value tracking position.
  • Build: a field assembling from its base as it comes into view, row by row: the lattice (7.26).
  • Pin: a sequence held in place while its steps change. Two forms: the scroll story (7.28), whose states are discrete, and the pinned scrub (7.30), whose parts build continuously across the pin. In a pinned scrub the writer reports one progress value for the whole time the stage is stuck, and each part takes its own slice of it, eased out, so one idea moves at a time. It pins only with motion on and above 720px; otherwise the same markup is a normal band at its finished state, which is the version to design first.

Its rules:

  1. Native scroll only. Never hijack it. Nothing listens to the wheel, sets the scroll position or changes scroll speed. No Locomotive. A pinned sequence pins with position: sticky inside a taller track, never by trapping input. Lenis only if smooth scroll is wanted after seeing native.
  2. It moves decoration, not reading. Only display words and decorative type may drift. A drifting word is aria-hidden and its sentence is real text nearby. Body copy, a lead being read and the nav never move.
  3. A scrub runs while it is read, and the reader holds the clock. A filling lead starts as its top crosses 85% of the viewport and is fully ink as its last line reaches 30% from the top (Gideon, 6 October 2026; it had finished at the middle, which read as done before it arrived). Its start colour is --lead-start, 3:1 or better, large text only.
  4. Reversible, and right at both ends. Scroll back and it runs back. Both ends must read on their own.
  5. The rest state is the finished state, and it is the default. With motion off, reduced motion or no script, a lead is ink, a drifting word sits centred, a lattice is fully built. The CSS defaults are those values, so nothing depends on a script to become readable.
  6. One writer, one property. A single progress writer sets --p, 0 to 1, on each element; CSS decides what --p does, and only transform, opacity and colour may follow it. Reads are batched before writes in one animation frame. CSS animation-timeline may replace the writer where it produces the same curves; the JavaScript path exists because Firefox has no scroll-driven animations and one tested curve beats two.

The budget, which is what makes more movement work

More motion fails in one specific way, and it does not look like failure in a review: every block fades up as it arrives, the reader is permanently waiting a beat for text, and the page feels slower. That is wading, not liveliness.

  1. Every band gets an arrival, and one lead gesture per screenful. Supporting motion inside it is fine: a drift behind a heading, a card grid landing. Two competing lead gestures in one eyeful means the reader loses both. A band taller than about 1.4 viewports splits into a gesture per screenful.
  2. Nothing waits on anything above it. A reader who scrolls quickly never finds a queue working its way down the page after them.
  3. A sequence runs only while it is on screen. Past the edge of the screen, whatever is left lands together.
  4. Travel is sized to the gesture. Arrivals rise 12 to 20px, never sideways. Decoration may travel as far as it likes.

Four invariants

  1. No reader ever waits. Anything inside the viewport is readable within 600ms of arriving there.
  2. Motion may not carry meaning alone. If the only way to understand something is to have watched it move, it fails the reader who arrived halfway down the page and everyone with reduced motion on.
  3. The finished state is the real design. Author the end frame first and make it read on its own.
  4. Normalise, never measure. Drive stroke animations from pathLength="1", never from getTotalLength(), and a stroke carrying its own dash must not carry pathLength. Both traps are in section 11.

Where the gesture fires is part of the gesture. An arrival triggered as an element's top edge grazes the viewport is finished before the reader's eye gets to it: measured across six bands at 800px tall, an -8% root margin started every gesture with its heading between 639px and 710px down the screen, so a correct, running animation was never seen by anybody. The trigger line sits 28% up from the bottom of the viewport, for arrivals and count-ups alike. An element at the foot of a document can never cross it, so a scroll-settled check at the bottom of the page is required alongside it.

What does not move

  • The nav. It takes a panel and a hairline past 32px of scroll. That is all.
  • Anything that shifts layout after paint. Transform, opacity and colour only.
  • The first screen's headline. Painted and readable at once. A hero may move everything around its headline, never the headline.
  • Body copy. A lead may fill as it is read, scrubbed by the reader's own scroll (section 5, rule 3); body copy never moves or fills.
  • Text under parallax, unless it is a display word.
  • Numbers, unless they count up properly. The final value is in the HTML: it is what a screen reader hears (the moving digits are aria-hidden beside a visually hidden final value) and what shows with no script or motion off. The count runs once, at the trigger line, over about 1.2s on --ease-reveal, with the digits' width reserved so nothing shifts. A number already above the line when the page loads stays at its final value. No typewriter text, no character scrambles.

The floor, and it is not optional

prefers-reduced-motion and data-motion="off" (the site's own control) are honoured by everything. Transitions drop to 0.01ms rather than being removed, so state still commits; scroll-linked elements go to their finished state; the dot mesh renders one static frame; looping video is replaced by its poster frame, not paused. Switching motion off mid-page finishes everything immediately, including a count in progress.

Two failure modes, both found in shipped code, both invisible in a diff, and both more likely as the amount of motion grows:

  • An override that loses on specificity. .is-in .mark-fig *{animation:none} scores (0,2,0) and the rule it must beat scores (0,2,1), so for a week it did nothing while appearing to work. An accessibility escape hatch is one of the few places !important is correct.
  • A hidden state with nothing left to unhide it. Gate every hidden or pre-motion state on a class the script itself sets, so that no script means nothing hidden.

Neither is caught by reading the CSS. Check motion by looking at it: screenshots or a recording at several scroll positions, with motion on, motion off and no script. Three motion bugs on v1 were invisible in the code and obvious in one screenshot.


6. Focus and forms

One treatment, every focusable thing, accent-driven.

Aspect Spec
Ring 2px solid var(--ac), outline-offset: 2px, radius inherited. Never outline: none without a replacement in the same rule.
On accent fill An --ac ring on an --ac button is invisible, so any element whose background is the accent switches its ring to --paper.
On a light ground Added at 3.0. An accent ring on a light ground is invisible (house green is 1.06 to 1.52:1 there), so the ring is --ink, 9.78:1 or better on all four light grounds. A control that inverts to ink on focus, like a column card, takes a house-green ring inset inside itself.
Trigger :focus-visible only. :focus is never styled on its own.
Contrast Ring clears 3:1 against both the element and the adjacent ground.
Skip link One skip-to-content link, first in tab order, hidden until focused, then a primary button pinned 16px top-left.

Forms. Five controls built from surfaces already in the system: panel fill, hairline border, mono label above, accent on focus. Labels sit above fields, never inside them as placeholders — a placeholder doubling as a label disappears exactly when it is needed. Error state is pink #F005AD; on TRAFFIQ surfaces (which lease pink) error falls back to orange #F78A20 plus an icon and a text prefix. That fallback is a known workaround, not a resolved rule.


7. Components

Twenty components. Each entry below gives its rhythm/elevation, its governing rule, and the mistake it invites.

# Component Rhythm Governing rule Never
7.1 Nav with initiatives dropdown Sticky / E0→E2 Only place several accents appear together, as 6px dots. Takes --panel + --line2 past 32px scroll — the only component that changes on scroll. The whole bar in an initiative's accent; a primary button in the nav.
7.2 Hero with optional film 100svh / wide Three layers: video, one opaque scrim, content. Poster is art-directed. Stacking scrim and vignette; a hero on a page not making an argument.
7.3 Section band 64–160 One h-sec per band. The eyebrow must pass the cover test. Two headings in one band; uniform padding across bands.
7.4 Card Any / E3 Wash written at point of use, never at :root. Per-card --ac is the one exception to one-accent-per-surface, contained by the card boundary. Nesting cards; two washes; a link inside a card that is itself a link.
7.5 Chip Any States a fact about the thing it sits on. If clickable it is a filter and needs a selected state and a ring; if not, no hover styling at all. A chip as a button; a filled non-interactive chip; more than two per card.
7.6 Button Any Exactly one primary per surface. Mono because it is an instruction, not a sentence. 44px minimum hit area, carried by padding. Two primaries; a primary in nav or footer; a dimmed accent as disabled.
7.7 Tier grid Tight / E2 Wave 01 --ac, Wave 02 QUIET, Wave 03 --muted2, marked by dot + mono label. Must not stack above 720px — it is a sequence. State colour on any card edge; a fourth tier; reordering.
7.8 Portfolio card grid Tight / E3 Challenge before Response, both labelled. No leader → accent is QUIET and the link changes from Explore to Lead. Ellipsis truncation; sorting by anything but portfolio state.
7.9 Five-beat sentence panel Wide / E3 Five fragments form one grammatical sentence read across. Dim with colour, never opacity. A sixth beat; nesting opacity to imply sequence.
7.10 Nine-box grid Tight / E1 One non-wrapping line per cell. Shared hairlines (1px gap over a --line background). Nine because 3×3 reads as a set. Individual cell borders; hover lift.
7.11 Footer Tight / E1 Footer accent is always --house, even on an initiative's page — the footer speaks as HUBIQ. In-market initiatives only, then a link to the index. A primary button; repeating the nav wholesale; social icons instead of labels.
7.12 Ticker Tight A texture, max four claims, linear translate, duplicate copy aria-hidden. Unique information in it; making items links; hover pause.
7.13 Animated dot mesh Background Invisible to comprehension. Behind headings and space, never body copy. Reads --ac. Raising alpha; more than one per page; painting off-screen.
7.14 Scroll-drawn line Argument sections May only connect genuinely sequential things — it is a claim about order. Driven by scroll position, never a timer. Decorative use between unrelated sections.
7.15 Initiative braid Illustration Portfolio-level only. Strand colours generated from the lease table. A single-initiative page; hand-picked strands; QUIET strands.
7.16 Reference table Tight / E2 Mono for machine-side columns, Poppins for human-side. Max six columns. Filtered rows are removed, never dimmed. Zebra striping; centring anything but a swatch; a button in a cell.
7.17 Disclosure Tight / E2 Content must be genuinely secondary. Whole row is the button, not the 20px glyph. Hiding the argument to shorten a page; nesting; using it as navigation.
7.18 Measure strip Tight / E1 Figure in Poppins 800 at 38–56px, mono label beneath. Max three external statistics on a surface. A figure without a unit or period is decoration. Accenting the number; padding to a round grid.
7.19 Pull quote Wide / E1 One per page, and it must say what the surrounding copy cannot. Unattributed does not ship. Giant decorative quote glyphs; quoting ourselves.
7.20 Media frame Any / E1 16:9, 4:3 or 1:1 only. Aspect held by the frame so a replacement asset cannot shift layout. Photographs are evidence. Tinting or duotoning to match an accent; a diagram in a frame.

v2 components

Added at 3.0, each built and checked on /specimen in hubiq-web-v2. KIT.md says which class or component carries each one and how to use it; the CSS and components are in kit/, synced into every v2 site (6 October 2026).

# Component Governing rule Never
7.21 Label rail One mono label per band, numbered, in the first two columns, sticky within its band. Set at 15 to 16px, larger than other mono labels. On ink and forest the name is paper and the number house green; on light grounds the name is the label colour and the number ink (7 October 2026: at 13px in grey it read faint on dark grounds). A second label; a heading in the rail.
7.22 Pill CTA with ringed arrow A house-green pill, ink text, the label and a ringed arrow side by side (redrawn 10 October 2026; the detached arrow is retired). On hover or focus the arrow nudges 5px forward and the green deepens; on press the pill scales to .95. Transform only. Ink on house green. Focus ring ink on light grounds, house green on dark. Green text or a green outline on a light ground; a green pill on house green.
7.23 Column card The columns are the cards, on the full hairlines. Hover and focus invert to ink and the initiative's own colour appears where it passes on ink (4.98:1 or better for every current lease). The extra line rises in only where hover exists; on touch it is shown. Names start at one height across a row. Hiding anything a keyboard cannot reach; an accent on the light state.
7.24 Filling lead One per band at most, on paper or mist. Word by word, scrubbed by the reader's scroll, finishing 30% from the top (section 5). A fill on body copy; a fill on grey.
7.25 Display word Decoration: Poppins 800 at --type-word, aria-hidden, its sentence present as real text. May drift. Using one as the heading; more than one set per band.
7.26 Lattice House green arriving on a light ground as light, not a flat panel. A dot field computed at build: solid green dots on a 20-unit grid, radius largest below a horizon that waves across the width like a signal, about four full rows at the base. Height follows width (200/1280), 150px floor, cropped at the sides on narrow screens rather than shrunk. Builds row by row from the base as the reader scrolls (section 5, build), and the finished field is the markup. Sits in the band's bottom padding, below the content. Light grounds only. Chosen 25 September 2026 over a travelling beam and the reference site's radial bloom. Under paper-coloured text; on an ink ground; a radial glow; more than one per page.
7.27 Stat row Space Mono numerals at --type-stat, counted from the source data at build, not typed. Count-up per section 5. A number without a unit or label; an external statistic past the three-per-page limit.
7.28 Scroll story A diagram pinned beside steps of text, changing state as each step's top crosses 55% of the viewport, forwards and back. Pinned with position: sticky in a taller track, never by trapping scroll. States are discrete (the part in focus), set whatever the motion setting because they are information, and instant with motion off. The step text always carries the meaning. Highlight on a light ground is a green fill with ink outline; the rest drops to a solid pale mix, never QUIET. With no script, the steps read in order beside the whole diagram. The same diagram set statically with one highlight is how a section page carries its part of a story. On phones the diagram pins under the nav on its own ground and the steps scroll beneath. The vehicle for Colin's animation (decisions, 24 September 2026). Scroll-jacking; a state the text does not also describe; dimming with opacity; highlight rules that lose to the dim on specificity (seen on the specimen: scope the dim with :where()), or state that leaks from a live story into a static copy nested inside it (scope with the child combinator).
7.29 Statement A section's closing line, set at --type-h2, 600, ink on a light ground or paper on a dark one. Its emphasis is a mark: on a light ground a house-green fill behind ink text (14.86:1), the one way green carries words there; on ink or forest the words may simply turn house green. The fill sweeps in from the left as the line is read, scrubbed by scroll; at rest and with no script it is full. One per section, closing it, inside the same band as what it concludes (Gideon, 6 October 2026: a statement moved to its own band read as disconnected). Green text on a light ground; a mark on more than one phrase; a statement split from its section onto its own band.
7.30 Pinned scrub A chapter told across a stretch of scroll: the band's stage sticks for --len screens while its parts build, each over its own slice of the pin's progress, eased out. One idea moves at a time (no more than a third of the parts at once). Text that is being read stays still; only display words, figures and tracks travel. Captions that belong to a moving part are held still elsewhere in the stage. Pinned only with motion on and above 720px; the unpinned version is the same markup at its finished state, every word in reading order, and is designed first. Any ground; line drawings on ink. First built for the Solution Foundry story, 6 October 2026. Trapping the wheel or changing scroll speed; text moving while it is read; a pin whose finished state does not read without the motion; hairlines crossing a moving track (give the track its own opaque strip).
7.31 Strike and replace One claim struck through and its replacement rising beneath it, at --type-h2. The strike is drawn across by the reader's scroll and the struck words fade to the body colour as it lands; the replacement rises after. House green bar and replacement on ink and forest, ink on light grounds. Both lines stay: the reader sees what was replaced. First used for "from building software, to building the right solution" and "Software factories, The Solution Foundry" (Gideon, 6 October 2026). A strike drawn on a timer; green as the bar or the text on a light ground; the struck line removed once replaced.
7.34 Burst A small ornament, eight rays of three dots around an empty centre, in house green: a spark of energy beside a heading or a number. Blooms ring by ring as its band arrives; simply there otherwise. Decorative. (Gideon, 10 October 2026.) Several on one screen; using it as a bullet, an icon or a status; green dots carrying meaning on a light ground.
7.33 Typed line A short line (a tagline, a strapline) types itself in, character by character, with a caret at the frontier: house green on ink and forest, the heading colour on light grounds. Driven by the reader's scroll (a slice of a pin, or the line crossing the screen like a filling lead), or once on arrival for a hero line. Characters keep their space while hidden, so nothing reflows. Finished, with no caret, with motion off, reduced motion or no script; the real text is in the page once for screen readers. For the Hub, where most of the page is words (Gideon, 8 October 2026). Typing a paragraph; typing the H1; a typing loop or a timer anywhere but a hero's arrival; a caret left blinking on a finished line.
7.32 Named and lit When a line names a part of the picture, that part turns house green as the line is read, scrubbed by the same progress; once lit it stays lit, so the finished state shows everything named. The line sits where the eye already is: beside or directly under the part it lights, and the part being named carries a mark pointing at its line until the next is named (Gideon, 7 October 2026: a line placed at the top of the stage while its part lit at the foot went unread). Green text only on ink and forest; on any ground a part may light as a fill with ink on it. A 5Di part lights in the second highlight instead (--lit-to, see "The second highlight"). The device for every scroll story whose copy names its parts (the Solution Foundry, Gideon, 7 October 2026). Lighting a part no line names; green text on a light ground; lighting on a timer; the line and its part in different places on the screen.
7.35 Buttons One primary per band, the pill (7.22). Everything else is the secondary button, an outline in the heading colour that fills on hover, or a text link. An icon-only button is square and carries an aria-label. Disabled dims and stops. 44px minimum hit area (48px default, 36px small, padded where it sits). A second pill on a band; a green outline; colour as the only sign of a state.
7.36 Form field A mono label in the heading colour above the control, help beneath, the error beneath that, in words, with the error icon, and the control's border thickened. One class for input, select and textarea. Two columns, one on a phone. A placeholder standing in for a label; red, or any second colour, for an error; an error the control does not point at (aria-describedby, aria-invalid).
7.37 Choice and switch Checkbox and radio are the native inputs redrawn: checked fills house green with an ink mark and an edge in the heading colour, so the state reads on a light ground without relying on green. A switch is for a setting that applies at once, never for a form answer. A custom control that is not a real input or role="switch"; a switch inside a form that is sent.
7.38 Segmented control and picks Segmented: one view of several, the chosen one filled in the heading colour. Picks: filters, any number on, a pressed pick filled house green with ink text. Both are buttons with aria-pressed in a labelled group. A segmented control that navigates to another page; picks that filter content with no script to filter it.
7.39 Site head and skip link Wordmark, up to five links, a menu and drawer below 860px; on ink always, clear over the hero and solid once scrolled. The skip link is the first thing a keyboard reaches. More than five links; a primary button in the head; a head that changes colour with the ground under it.
7.40 Motion toggle WCAG 2.2.2's control on every site, usually in the footer: stops everything that moves, pauses looping video, remembers the choice, starts by following the operating system. Hiding it in a settings page; a toggle that starts a narrated film.

7.6 Button and 7.17 Disclosure, for v2: buttons are 7.35 (sentence case, the text face, not mono); the disclosure is a native <details> whose summary is a text link with a turning chevron, so it opens with no script (KIT.md, Controls). 7.1 Nav, for v2: 7.39.

7.11 Footer, for v2: on ink, with house-green column rules, and a 6px house-green bullet that grows in beside a link on hover or focus. The link also underlines on focus, so the bullet is never the only signal.

7.18 Measure strip is superseded on v2 pages by 7.27: numerals move from Poppins 800 to Space Mono.


8. Iconography

Material Symbols Outlined (Apache 2.0), adopted rather than commissioned, self-hosted as a subset in production. Icons never replace a label — the mono label carries the meaning, the icon carries the glance.

Axis Setting Why
FILL 0, always A filled icon reads as a selected state; selection here is colour + mono label.
wght 300 @24px, 350 @20px, 400 @16px Bloom compensation — the inverse of the type tracking rule.
GRAD −25 on dark, 0 on light Thins the stroke without changing glyph width.
opsz Matched to rendered size (20/24/40) Never scale a 24px glyph to 40px.

Sizes 16, 20, 24, plus 40 for a single-figure case. Colour is currentColor or var(--ac), never a hex. Spacing 8px at 16px, 10px at 20px.

Approved subset, 39 icons: arrow_forward arrow_outward expand_more close menu search · check error schedule verified visibility lock · description table_chart bar_chart trending_up calendar_month flag · groups handshake account_balance payments mail download · send group bookmark explore (the four stage icons on hubiq.co.uk, September 2026) · done_all swap_horiz (TenantIQ: read receipts, and whose turn it is; 25 October 2026) · apartment home (TenantIQ: the landlord's and the tenant's view, 25 October 2026) · format_list_numbered (TenantIQ: everything in order, 25 October 2026) · key local_shipping (TenantIQ: moving in and moving out, 25 October 2026) · build trending_down (TenantIQ: repairs caught early, fewer complaints, 25 October 2026) · check_box person_book (TenantIQ: the document checklist, contacts in one place, 25 October 2026). Anything outside it is a request, not a choice. The paths live in the kit, kit/components/Icon.tsx.

Never: a glyph standing for an initiative; two variants of one icon on a surface; a second icon family; an icon-only control without an aria-label; emoji in any medium.

Editorial illustration is not iconography

The rules above govern icons: small marks that label a control or mark a state. They do not govern illustration: a figure that sits beside prose and carries the argument the prose is making. Confusing the two is the most common way a considered page turns decorative.

The test. If it labels a control, it is an icon and everything above applies. If it sits next to a paragraph and carries an idea, it is illustration and this does.

Material Symbols are a UI set. They are excellent as affordances and weak as editorial work, because their whole value is that they are already familiar. A rocket beside a paragraph about partnership is the most generic image in technology, on a site whose entire argument is that it is not generic. Reach for the icon set to point at a thing. Draw something when you need to say a thing.

The rules for a drawn figure.

  1. It says what the paragraph says, in another medium. Delete it: if nothing is lost, it should not have been there.
  2. It joins the existing drawn-line family. Thin strokes, no fill except as emphasis, sitting on the dark ground alongside the foundry line and the braid. A second visual language is worse than no illustration.
  3. Colour arrives through color on the parent so the figure inherits --ac. Never a hex inside the artwork. A figure inside a TRAFFIQ surface comes out pink for free.
  4. It may build on arrival, and it must read without having done so. A figure is editorial motion under section 5: it builds once, inside the 1.5s ceiling, and the end frame is the real design. Nothing in a figure may exist only in the animation.
  5. Never a stock photograph, and never a borrowed metaphor: rockets, lightbulbs, jigsaw pieces, chess pieces, mountain summits, handshakes across a globe.
  6. Never words inside the artwork. Live text only, so it is readable, translatable and indexable.
  7. Derived from the product or the argument, not from a mood.
  8. A set on one page shares a viewBox, a stroke weight and a scale, or it reads as clip art rather than as a family.
  9. aria-hidden. If a figure carries meaning the prose does not, the prose is wrong.

A figure marking a category may depict; a figure beside a paragraph must argue. The seventeen figures on the site split into two jobs. Most sit next to prose and restate its argument, and rule 1 governs them. Three mark the fields of impact at the head of the initiatives page, where the job is to make a category recognisable rather than to carry a claim, and depiction is allowed there.

What is not allowed, in either case, is illustrating the category's name. That is where a skyline for places, a row of user glyphs for people and a microchip for intelligence come from, and all three are the rocket-beside-partnership mistake with a different subject. Derive it from what the category actually contains: eight initiatives about a scattered physical estate gave a site plan with a route threading it, not buildings in a row.

Two constructions that will read as a chart however they are drawn, both found by rendering them: forms of unequal height standing on a shared baseline are bars, and any line above them is a trend. A sequence that climbs left to right is a trend on its own. If a figure is not about quantity, it must not have a baseline or a monotonic rise.

Worked example: the four focus areas on Our story. Each figure restates its own paragraph. A network with no centre, because the copy says community rather than platform, with one node per kind of participant named in the sentence. An adoption curve, because "as disproportionate adoption gives way to ubiquitous application" is a curve described in words, with the marker on the inflection the sentence is about. Three beams on fulcrums, none of them level, because the copy says few people hold all three tensions at once and balanced scales would have contradicted it. And twelve scattered marks resolving into one, because the claim is that complexity becomes clarity.

None of the four would survive rule 1 being applied carelessly, which is the point: the figure has to be derived from the sentence, not selected for it.


Riso prints

The other kind of illustration is a riso print: a picture, not a diagram, printed as if on a risograph in three inks (forest offset, one house-green accent object, a paper key) and kept in one print run by one script. It opens a chapter, marks a field or carries a value; a drawn figure argues beside a paragraph. The standard, the brief they are drawn from, the layering and the scripts are in illustration/README.md. The two rules most often broken: one solid accent object, never an outline; and a print placed on a light ground is reprinted from its plates (ink key, #ACB6C6 offset, #5E8C14 accent), never reused.

9. Data visualisation

  • Sequential, accent-derived, six steps: color-mix(in srgb, var(--ac) 100/78/56/34/18/8%, var(--ink)).
  • Categorical, fixed and ordered so identity is stable: #A9F20C #1AD3F0 #F005AD #F78A20 #B833FF #B2F8AC.

Max six categories; a seventh means the chart is the wrong chart. Never encode an initiative in a series with a colour other than its leased accent. Colour is never the only encoding — series carry direct labels, patterns are doubled with a legend. Gridlines use --line and never sit above data.


10. Light mode

Light grounds are not light mode. From 3.0 the website uses light grounds as chapters within a page (section 1, Grounds). That is a fixed design, not a mode: there is still no site-wide toggle, no prefers-color-scheme and no class on html, and a page looks the same for everyone.

This section, light mode, remains scoped to print, PDF and email. Its values below predate the v2 ladder and differ from it (--ink-l is warmer than --ground-paper). Reconciling them is an open decision, section 15.

--ink-l:    #F4F7FB;   /* warm-neutral, not white: pure white glares in print */
--panel-l:  #FFFFFF;   /* elevation inverts in order, not in value */
--paper-l:  #06101F;   /* 17.30:1 */
--muted-l:  #4A5A72;   /* 6.36:1 */
--muted2-l: #5C6B82;   /* 4.91:1 — do not go lighter */
--line-l:   #E4E9F1;
--line2-l:  #D2D9E4;
--wash-l:   8%;        /* down from 12%: a bright hue reads stronger on light */

Deep accent pairs (bright hue survives only as the wash; text, marks and fills take the deep pair): green #3F6B0A · amber #8A5F00 · blue #04697A · pink #A2016F · orange #8A4600 · QUIET #5C6B82 · purple #7C05F2.

What a token swap cannot settle: elevation inverts in order (panel now lighter than ground); focus ring becomes the deep pair; charts re-cut from the deep pairs in the same order; QUIET-deep collides with --muted2-l, so on light a vacancy always carries the mono label as well as the hue; display weight drops 800 → 700; tracking and leading do not change. Print and PDF drop the film, ticker, dot mesh and scroll line; email also drops the braid.

The trap: never implement light mode with a CSS filter or invert. That produces a hue-rotated palette that is nobody's brand.


11. Accessibility, measured

The ratios in this section are the HUBIQ theme's. Each theme keeps its own in its THEME.md, and the build checks the pairings the kit relies on (KIT.md, section 0).

Ratios computed against composited values, not declared. Against --panel they run ~3% lower than against --ink, enough to move two colours across a threshold — design to the panel column.

Colour Hex On ink On panel Verdict
--paper #EEF3FA 18.18 17.61 AAA
--muted #98A6BC 8.22 7.96 AAA body text
--muted2 #8494AC 6.57 6.36 AA. Mono caps and meta only, never a paragraph
QUIET #A8B6CC 9.87 9.56 AAA. Dim by meaning, not contrast
green / house #A9F20C 14.85 14.38 AAA
amber #FCC01A 12.25 11.87 AAA
blue #1AD3F0 11.21 10.86 AAA
orange #F78A20 8.31 8.05 AAA
pink #F005AD 5.14 4.98 AA only. Headings, marks and buttons
purple #B833FF 4.78 4.63 AA with no margin. Do not lease without retuning
purple-deep #7C05F2 3.05 2.95 Fails. Fill and gradient stop only

On the light grounds, 3.0. Computed from the hex values in tokens.css; truncated, never rounded up.

Colour On paper On mist On grey On grey deep Use
--ink #000614 18.51 15.72 13.28 9.78 Headings, stat numerals
--ink-body #2C394A 10.71 9.10 7.68 5.66 Running text
--ink-label #364355 9.17 7.79 6.58 4.85 Labels, meta
--lead-start #6D7C8F 3.89 3.30 2.79 2.06 Start of a filling lead. Large text, paper and mist only
house green #A9F20C 1.25 1.06 1.12 1.52 Fill only
ink on house green 14.86 Anything on a pill or a lattice dot

On forest #1B330B: paper 12.35, muted 5.58, muted2 4.47 (fails), house green as a line 10.09.

Lease colours on ink, for the inverted column card: green 14.86, gold 11.93, blue 11.21, QUIET 9.87, pink 5.14, indigo 4.98.

Also: hairlines are grouping, never signal (--line 1.15:1, --line2 1.43:1). Card hover border measures 4.82:1. Every interactive element is at least 44×44 CSS px of hit area. A leased accent must clear 4.5:1 against both --ink and --panel before it can be leased — that is a lease condition, not a nice-to-have.


12. The laws

Each cost something. A rule without its reason gets negotiated away by the next person under deadline.

  1. One accent per surface, and --ac cascades. Nothing hardcodes a hex a token already covers.
  2. Never nest opacity to dim something. Nested opacities multiply and static checkers cannot see it — a panel once measured 14.86:1 on paper and 1.47:1 on screen and passed every automated check. Dim with colour; keep opacity at 1 on text.
  3. Only one layer in a stack may be opaque through the middle. Two fade layers multiply transmittance; a band that looks solid can leave a background film showing on someone else's screen.
  4. Tokenise the numbers, not the gradient. A custom property containing var(--ac) declared at :root resolves at the root and inherits baked in, so every card loses its own colour. Only --wash and --wash-fade are tokens.
  5. Edit CSS by selector, never by line range. A build guard fails the build if a class in the app has no matching rule, and the failure surfaces nowhere near the change that caused it.
  6. Grey carries meaning (HUBIQ theme; themes/hubiq/THEME.md). QUIET marks an initiative without a leader, so grey is unavailable for chrome, disabled states, decorative washes or placeholder art.
  7. State is never a coloured edge on a container. No accent border-top/left/right above 1px on a card, row, callout or tier. The wash is how a container takes an accent; a dot and a mono label are how it declares a state. The only coloured edges are the 1px --line hairline and the transient 55% hover border.
  8. A light accent is a fill on a light ground and a line on a dark one. Added at 3.0 for house green, generalised to every theme on 23 October 2026. House green measures 1.06 to 1.52:1 on the light grounds and TENANTIQ's gold about 1.7:1 on white, so there each can only be a surface with ink on it; on the dark grounds they may draw. A light-accent line, label or ring on a light ground passes every token check and is invisible.

13. Building a new surface

On v2, start from the recipe in KIT.md, section 2: it covers the page root, the band stack, ground order, rail numbering and the closing call. The list below still applies.

  1. Start from a section band, not a blank page. Decide first whether the band argues or lists — that sets padding, type role, and whether an eyebrow is allowed.
  2. Set --ac once, on the page root, from the initiative's lease. Portfolio-level pages leave it as --house and do not set it at all.
  3. Use the four text tokens and no others. A fifth value means the text is competing with something that should outrank it.
  4. Write the wash at the point of use. Never move a var(--ac) expression up to :root (Law 04).
  5. Check the stack. If a band has a film behind it, count the semi-transparent layers above it; more than one is a bug (Law 03).
  6. Add the focus ring in the same commit as the interactive element, not the next one.
  7. Edit CSS by selector, and run the build before pushing.

14. Voice

Plain meaning first, precise term alongside — the audience includes sector experts with no software background, so this is a comprehension requirement, not a style preference. Claims are specific and measured. No adjectives awarded to ourselves. The problem sits at the centre, not the product.

15. Known open decisions

  • Brand basics follows the type decision. Inter Tight joins Poppins and Space Mono on /brand-voice/basics/, which specifies fonts for documents and email. Do it at cutover, with the page.

  • Light mode against the v2 ladder. Section 10's print values predate the ground ladder. Either print adopts the ladder or the two stay separate on purpose; decide before the brand basics page is next touched.

  • Pink collision, moved not closed. Error #F005AD versus TRAFFIQ's lease. TENANTIQ vacated pink for gold in August 2026 and TRAFFIQ took it, so the collision changed hands rather than going away. Orange #F78A20 came off lease at the same time and is now the obvious escape route for the error hue. Either pink is permanently reserved for validation and leaves the leasable pool, or error stops being a hue and becomes a pattern (heavier border plus mono prefix) that works under any accent. The second is more robust; the first is less work.

  • Skip link is specified but not yet shipped.

  • Braid strand colours currently substitute a coral to avoid two amber strands; the braid should read the lease table rather than carry its own exception.