# Stitch Brand System

The written companion to `index.html`. Same rules, same tokens, structured for repos,
wikis and agent context.

Light is the ground. Dark is opt-in via `[data-theme="dark"]`. **Ink is the voice; violet is
an accent** - `--accent` resolves to ink, never brand. Violet, amber and neon are fills that
carry meaning; they reach type only where it has to correlate with a graph, or as `--amber`
on a negative figure in a table.

| Section | Anchor |
| --- | --- |
| Logo | [#logo](index.html#logo) |
| Colour palette | [#colour](index.html#colour) |
| Typography | [#typography](index.html#typography) |
| Cards | [#cards](index.html#cards) |
| Buttons | [#buttons](index.html#buttons) |
| Chips | [#chips](index.html#chips) |
| Data visualisation | [#charts](index.html#charts) |
| Downloads | [#downloads](index.html#downloads) |

---

## 1. Logo

One wordmark, one symbol, one weight. The mark is a **single flat colour object**. It takes
the colour of the ink or the paper around it - never a gradient, never an effect.

### Lockups

| Lockup | Use |
| --- | --- |
| Wordmark | Default for everything |
| Symbol | Square crops, favicons, app tiles, avatars - anywhere the wordmark would fall below its minimum |

### Approved colour usage

Four treatments. Everything else is a misuse. `--white` and `--paper` are interchangeable
as the light ground.

| Treatment | Mark | Ground | Contrast |
| --- | --- | --- | --- |
| **Solid** | `--ink` | `--white` / `--paper` | 18.8:1 |
| **Reversed** | `--paper` / `--white` | `--ink` | 18.8:1 |
| **Brand** | `--paper` / `--white` | `--brand` | 5.9:1 |
| **Muted** | `--titanium` | `--white` / `--paper` / `--ink` | 2.0:1 light, 9.3:1 dark |

Solid is the default. Reversed takes over the moment the ground is darker than `--silver`.

#### Muted

One `--titanium` file sits legibly on a light *or* a dark ground, so a single asset covers
both modes where CSS cannot follow it - email templates, PDF exports, third-party embeds,
app-store art.

It is a fallback, not a style. At `2.0:1` on `--paper` the mark is deliberately quiet, which
is permissible because WCAG 1.4.3 exempts logotypes from contrast minimums - but it is the
wrong choice anywhere Solid or Reversed can be used instead. If a more balanced midpoint is
ever needed, `#8e8d93` measures `3.2:1` light and `5.9:1` dark.

**Do**

- Render the mark in one flat colour drawn from the palette.
- Switch from Solid to Reversed the moment the ground is darker than `--silver`.
- Sit the mark on a solid field, or on a clear, even area of a photograph.
- Keep the paper mark on violet - never ink on violet.
- Reach for Muted only when one file has to serve both modes.

**Don't**

- Fill the mark with a gradient, a photo, or the chart fade.
- Add a stroke, a shadow, a glow or an outline.
- Recolour the mark to amber, neon, electric or a categorical violet.
- Sit violet on ink, or ink on violet - the contrast fails.
- Use Muted as a style choice where Solid or Reversed would work.
- Stretch, rotate, condense, or rebuild the mark from type.

### Clear space

Measured in **x**, the height of the symbol's lower loop.

- Minimum clear space on all four sides: **1x**.
- Dense UI chrome (topbar, rail, card header): **0.5x**, and only against a plain surface.
- Nothing enters the gutter - no type, no rule, no crop edge, no other logo.

### Minimum sizes

| Context | Minimum |
| --- | --- |
| Wordmark, digital | 120 px wide |
| Wordmark, UI chrome | 88 px wide (sidebar and topbar only) |
| Symbol | 24 px wide |
| Wordmark, print | 25 mm wide |
| Symbol, print | 8 mm wide |

Below the symbol minimum, drop the mark entirely rather than shrinking it further.

### Position in layouts

| Placement | Where |
| --- | --- |
| Top left | Product UI, decks, documents. The default anchor. |
| Bottom left | Advertising and social, where the headline owns the top. |
| Optically centred | Covers, splash and end frames. Sits 4% above true centre. |

**Margin rule.** The mark sits on the layout's safe margin - 8% of the short edge for
advertising and covers, 24 px for product UI. It never sits closer to an edge than its own
clear space, whichever is larger.

### Files

```
assets/logo/
  wordmark-mono/                    current palette, single colour
    stitch-wordmark-ink.svg           #0f0c15, for light backgrounds
    stitch-wordmark-paper.svg         #fcfbfb, for dark backgrounds
    stitch-wordmark-currentcolor.svg  fill="currentColor", for embedding in code
    stitch-symbol-currentcolor.svg    loop mark only
assets/bundles/stitch-logos.zip     everything above
```

The mark ships single-colour only. The two-colour lockups from the 2021 palette are not
part of this system - if you need one, it has to be redrawn against the current palette.

---

## 2. Colour palette

A greyscale system with three loud colours held in reserve. **Ink does the work**: it is the
accent, the primary button, the selected state and the link.

### Neutrals

Nine steps, white to ink. The names hold across themes; the values move.

| Token | Light | Dark |
| --- | --- | --- |
| `--white` | `#ffffff` | `#16131c` |
| `--paper` | `#fcfbfb` | `#0f0c15` |
| `--platinum` | `#f5f4f4` | `#2e2b33` |
| `--silver` | `#dddcdd` | `#56545a` |
| `--titanium` | `#b5b3b6` | `#56545a` |
| `--steel` | `#56545a` | `#2e2b33` |
| `--graphite` | `#2e2b33` | `#2e2b33` |
| `--carbon` | `#16131c` | `#16131c` |
| `--ink` | `#0f0c15` | `#f5f4f4` |
| `--ink-reversed` | `#f5f4f4` | `#0f0c15` |

`--ink-reversed` is the mirror of `--ink`: the two swap places with the theme, so anything
sitting on an ink fill reads at 17.65:1 in either mode.

Derived ink: `--ink2` is ink at 62%, `--muted` is ink at 45%, `--ink3` is steel (light) /
titanium (dark).

### Accents

Fixed in both themes, and ranked. The rank is how much of the interface a colour may touch,
and the palette is laid out in those groups rather than as one block.

| Rank | Role | Token | Hex |
| --- | --- | --- | --- |
| Primary | Brand, chart highlight fill | `--brand` | `#6e2cff` |
| Secondary | Warning, cost, lapse, needs action | `--amber` | `#ff5b00` |
| Tertiary | Positive, live, on, protected | `--signal` | `#daff35` |
| Tertiary | Heatmap cold stop | `--electric` | `#59fcf9` |

`--danger` still exists for destructive confirmation but now resolves to `var(--amber)`, so
there is no separate red in the system. It is not shown in the palette.

### Accent ramps

Every accent carries a dark end for type and a light end for fills, with the base in the
middle. Like the accents, these are fixed in both themes. The two tertiary ramps share slate
as their dark end.

| Ramp | Dark | Base | Light |
| --- | --- | --- | --- |
| Primary | `--indigo` `#221244` | `--brand` `#6e2cff` | `--lavender` `#c4c0ff`, `--mist` `#e3deff` |
| Secondary | `--bronze` `#3f1711` | `--amber` `#ff5b00` | `--peach` `#fddcc9` |
| Tertiary, neon | `--slate` `#0e242c` | `--signal` `#daff35` | `--spring` `#f8ffda` |
| Tertiary, electric | `--slate` `#0e242c` | `--electric` `#59fcf9` | `--ice` `#d1fffe` |

The dark end of each ramp is also its ink, which is what `--signal-ink` always was. That
generalises to the other two:

```css
--brand-ink:  var(--indigo);
--amber-ink:  var(--bronze);
--signal-ink: var(--slate);
```

They do **not** all behave the same way, so check the pairing before you use one:

| Ink | Goes on | Contrast |
| --- | --- | --- |
| `--brand-ink` | `--lavender`, `--mist` | 9.96:1, 13.05:1 |
| `--amber-ink` | `--amber`, `--peach` | 5.03:1, 12.11:1 |
| `--signal-ink` | `--signal`, `--spring`, `--electric`, `--ice` | 14.03:1, 15.53:1, 12.79:1, 14.87:1 |
| `--paper` | `--brand` | 5.86:1 |

Two rules come out of that table. **`--brand-ink` never goes on base violet** - it only
reaches 2.80:1, so type on violet stays `--paper`. And **`--amber-ink`, not `--paper`, is the
type on amber** - bronze clears AA at 5.03:1 where paper manages only 3.01:1.

All five light tints clear 10:1 on both dark surfaces, and all three dark ends clear 15:1 on
paper, so the ramps are safe at either end of a theme.

One exception on the light side: **`--spring` is the same luminance as `--paper`** - 0.965
against 0.967, which is 1.00:1. As a fill on the light stage it reads as hue and nothing
else, so it needs the inset hairline to hold an edge, exactly like a white card on paper. It
is unaffected in dark mode, where it clears 17:1.

### Surfaces

| Token | Light | Dark | Where it goes |
| --- | --- | --- | --- |
| `--bg` | `--paper` `#fcfbfb` | `--paper` `#0f0c15` | The stage |
| `--raised` | `--white` `#ffffff` | `--white` `#16131c` | Cards and the sidebar, solid |
| `--surface-glass` | white 62% | white 68% | Overlays, drawers, tooltips |
| `--chrome-glass` | white 78% | white 84% | Popovers and floating controls |
| `--grid` | ink 8% | ink 9% | Chart gridlines, layout guides |
| `--line` | transparent | transparent | There are no 1 px borders |

Because `--bg` and `--raised` are only a few steps apart, a card is separated from the stage
by its **inset hairline**, not by a fill contrast.

### Status states

| State | Fill | Type |
| --- | --- | --- |
| Up / ok / live / on | signal 55% | `--signal-ink` |
| Down / warn / needs action | `--warn-s` | `--amber` |
| Flat | ink 6% | `--ink2` |
| Selected | `--ink` | `--ink-reversed` |

### Rules

1. **Ink is the voice.** Primary buttons, selected thumbs, links and "ok" cells are all ink.
   A button is never `--brand` - see section 5.
2. **Selection is platinum, not ink.** A selected card or an active nav item takes a
   `--platinum` fill with ink type and keeps its hairline. An ink fill at that size reads as a
   primary button, and neither one is something you press.
3. **Colour lives on fills, strokes and legend swatches.** It reaches type in two places
   only: a label that has to be read against a graph, and `--amber` on a negative figure in a
   table. Never a chart title, a KPI value, or body copy.
4. **Status is never colour alone.** Every state carries a label.
5. **Up is neon, warning is amber.** No green, and no red at all - `--danger` resolves to
   `--amber`.

---

## 3. Typography

**Suisse Intl, and only Suisse Intl.** One family covers display, interface, body and
numerals. Aeonik is retired. There is no separate mono - Suisse's tabular figures do the job,
so `--sans` and `--mono` are the same stack.

### The family

| Weight | File | Notes |
| --- | --- | --- |
| 300 | `SuisseIntl-Light.woff2` | |
| 400 | `SuisseIntl-Regular.woff2` | |
| 500 | `SuisseIntl-Regular.woff2` | No Medium cut is licensed; 500 is aliased to Regular |
| 600 | `SuisseIntl-Semibold.woff2` | Headings |
| 900 | `SuisseIntl-Black.woff2` | Display only - hero and page titles, never inline |

### Typescale

Body is 16 px with `-0.011em` tracking.

| Role | Size | Weight | Tracking |
| --- | --- | --- | --- |
| Hero | `4rem` | 600 | `-0.05em` |
| Page title | `1.75rem` | 600 | `-0.03em` |
| Section head | `1.75rem` | 600 | `-0.03em`, title `--ink` over headline `--muted` |
| Section title | `1.35rem` | 600 | `-0.03em` |
| Subhead | `1.25rem` | 600 | `-0.02em`, paired with a note in the adjacent column |
| Card title | `1rem` | 600 | `-0.02em` |
| KPI value | `2.5rem` | 400 | `-0.02em`, lining nums |
| Lede | `1.125rem` | 400 | `-0.011em`, `--ink2`, caps at 62 ch |
| Body | `1rem` | 400 | `-0.011em` |
| Label / eyebrow | `0.75rem` | 600 | `0.02em`, uppercase |
| Table header | `0.6875rem` | 600 | `0.06em`, uppercase |
| Button | `0.75rem` | 600 | `-0.02em` |
| Chart axis | `10.5px` | 400 | `0` |

### Section heads

Eyebrows are rare. A section opens with its **title and headline set at the same size and
weight** - `1.75rem`, 600, `-0.03em` - stacked with no gap between them and separated only by
colour: title in `--ink`, headline in `--muted`.

Because the headline carries display type, keep it to **one sentence, two lines at most**.
Detail belongs in the body of the section, not the head.

**No full stop at the end.** A period closes a thought and a headline is meant to stay open.
Internal punctuation is fine - it is only the last character that goes.

```html
<div class="section__head">
  <h2 class="t-page">Cards and the inset shadow</h2>
  <p class="lede">Stitch surfaces have no borders. An edge is a hairline pressed into the inside of the shape</p>
</div>
```

### Subheads

Inside a section, a subhead is **two equal columns**: the `h3` on the left, its note on the
right. Both sit on the same baseline, so the note reads as a caption to the heading rather
than a paragraph under it. The columns stack below 900 px.

The eyebrow variant, `.subhead--tight`, does not split. An uppercase `h4` label is too short
to hold a column, so its note sits directly underneath and caps at 62 ch.

```html
<div class="subhead">
  <h3>Approved colour usage</h3>
  <p>Four treatments. Everything else is a misuse.</p>
</div>
```

### Colour in type

| Level | Token | Use |
| --- | --- | --- |
| Primary | `--ink` | Headings, KPI values, table first column, active nav |
| Secondary | `--ink2` (ink 62%) | Body copy, descriptions, labels, table cells |
| Tertiary | `--ink3` | Table headers, axis ticks, placeholders, captions |
| Muted | `--muted` (ink 45%) | Section headlines at 24 px and up. Clears AA for large type at 3.05:1, so never body copy |

### Rules

**Do**

- **Tabular numerals where figures stack.** Anything read down a column - tables, chart
  values - carries `tabular-nums`. KPI values are lining figures.
- One family for display, UI, body and code.
- **Emphasis is 600, never 900.** Black is a display weight: hero and page titles only,
  never inside a sentence.
- Sentence case for headings and buttons. Uppercase only for labels, eyebrows and table headers.
- **No full stop on a headline.** Titles, section headlines, ledes, labels and button text end
  without one. Internal punctuation is fine; it is only the last character that goes.
- Cap the measure at 62 ch for body copy.

**Never**

- Violet, amber or neon type.
- A synthesised Medium, Italic or Bold - only the four licensed cuts.
- `font-weight: bold` or `700`. **There is no 700 cut**, and CSS font matching resolves a
  missing weight above 500 *upward*, so `bold` silently lands on Black. Always write `600`.
  `strong` and `b` are set to 600 globally for exactly this reason.
- Aeonik, Neue Rational, Reckless, or a system fallback in shipped work.
- Tabular figures in running text. Even widths are what make a column align; in a sentence
  they just leave gaps.
- Letter-spacing on body copy beyond the `-0.011em` default.
- Underlines on anything but inline links.

### Loading

```css
@font-face {
  font-family: 'Suisse Intl';
  src: url('./fonts/SuisseIntl-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
/* No Medium cut is licensed - alias 500 to Regular. */
@font-face {
  font-family: 'Suisse Intl';
  src: url('./fonts/SuisseIntl-Regular.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}

body {
  font-family: 'Suisse Intl', -apple-system, BlinkMacSystemFont, sans-serif;
  font-size: 16px;
  letter-spacing: -0.011em;
}

/* figures read down a column, and nowhere else */
table,
.bar__val {
  font-variant-numeric: tabular-nums;
}
```

### Files

```
assets/fonts/web/      SuisseIntl-{Light,Regular,Semibold,Black}.woff2
assets/fonts/desktop/  SuisseIntl-{Light,Regular,Semibold,Black}.{otf,ttf}
assets/bundles/suisse-intl-web.zip
assets/bundles/suisse-intl-desktop.zip
assets/bundles/stitch-fonts-suisse-intl.zip
```

> **Licensing.** Suisse Intl is licensed from Swiss Typefaces. Load the web cuts with
> `@font-face` from your own origin. Do not ask people to install the desktop cuts to view a
> page, and do not redistribute either set outside Stitch.

---

## 4. Cards and the inset shadow

Stitch surfaces have **no borders**. An edge is described by a hairline pressed into the
inside of the shape - light catching the top-left, shadow settling into the bottom-right. It
reads as a physical lip rather than a drawn line, and it survives on glass where a 1 px
border would not.

### The recipe

```css
:root {
  --inset-edge: color-mix(in srgb, var(--ink) 20%, transparent);

  --inset:
    inset -0.75px -0.75px 0.5px 0 var(--inset-edge),
    inset  0.75px  0.75px 0.5px 0 var(--inset-edge);

  --inset-strong:
    inset -0.75px -0.75px 0.5px 0 color-mix(in srgb, var(--ink) 30%, transparent),
    inset  0.75px  0.75px 0.5px 0 color-mix(in srgb, var(--ink) 30%, transparent);
}

.card--glass {
  background: var(--surface-glass);
  backdrop-filter: blur(20px) saturate(140%);
  border-radius: 1.5rem;
  box-shadow: var(--inset);
  border: 0;
}
```

### Asset cards

For specimens - a mark, a colour, a type cut. The plate is **full-bleed and carries the
card's own radius**, so the card shows through at the plate's two lower corners. Only the
label block is padded.

The inset hairline is drawn by `::after` rather than on the card itself, so it sits **above**
the plate instead of hiding behind it.

| Part | Spec |
| --- | --- |
| Card | `--r` radius, `--raised` fill, no padding |
| Plate | Full width, `3 / 2`, capped at 220 px, `--r` radius, `overflow: hidden` |
| Label | `1.1rem 1.25rem 1.25rem`, line-height 1.3 |
| Title | `0.875rem`, 600, `--ink` |
| Subtitle | `0.875rem`, 400, `--muted` - the same pairing as a section head |

**Swatches** are the compact cut of the same component: the card is locked to `1 / 1`, so a
`3 / 2` plate leaves exactly a third of the height for the label. That is two lines, not
three, so the swatch drops the token line and carries it in the tooltip instead. It also
uses `--r-s` rather than `--r`, since a 24 px radius swallows a 120 px card. The row sizing
is `auto minmax(0, 1fr)` - without the `minmax(0, ...)` the label grows and steals height
from the plate, which breaks the 3:2.

The ink plate carries its own `inset 0 0 0 1px` hairline at paper 16%, because ink on a
carbon card has no edge in dark mode. The paper plate carries none - it separates from every
card fill on its own.

```css
.asset { position: relative; border-radius: var(--r); background: var(--raised); }

.asset::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: var(--inset);
  pointer-events: none;
}

.asset__plate {
  width: 100%;
  aspect-ratio: 3 / 2;
  max-height: 220px;
  border-radius: var(--r);
  overflow: hidden;
}
```

Used by the logo lockups, the approved colour plates, the colour swatches and the type cuts.

### Two tiers

| Tier | Fill | Shadow | Where |
| --- | --- | --- | --- |
| **1 - content card** | `--raised` | None | Cards holding content on the stage |
| **2 - chrome card** | Glass + backdrop blur | `--inset` | Popovers, drawers, tooltips, buttons, chips, controls |

Tier 1 reads as a sheet laid on the `--paper` stage. Tier 2 floats *over* content, so it
needs an edge.

The sidebar and topbar are chrome but sit beside content rather than over it, so they take
the solid `--raised` fill and keep only their directional hairline.

### Directional insets

Chrome that meets the viewport only takes the hairline on the edge that faces content.

| Surface | Inset | Declaration |
| --- | --- | --- |
| Card, popover, drawer, tooltip | All four edges | `var(--inset)` |
| Sidebar | Right only | `inset -0.75px 0 0.5px 0 var(--inset-edge)` |
| Rail | Right only | `inset -0.75px 0 0.5px 0 var(--inset-edge)` |
| Topbar | Bottom only | `inset 0 -0.75px 0.5px 0 var(--inset-edge)` |
| Table header | Bottom only | `inset 0 -0.75px 0.5px 0 var(--inset-edge)` |
| Selected table row | Left, 3 px | `inset 3px 0 0 0 var(--ink)` |

### Radius

| Radius | Token | Use |
| --- | --- | --- |
| 24 px | `--r` | Cards, drawers, overlays, the composer |
| 16 px | `--r-m` | A card inside a card, and demo surfaces |
| 12 px | `--r-s` | Inner plates, inputs, code blocks, media |
| 999 px | - | Pills: buttons, chips, toggles, legend swatches, bar tracks |

### States

| State | Treatment |
| --- | --- |
| Rest | `--inset` (ink 20%) |
| Hover | `--inset-strong` (ink 30%) |
| Selected | `--platinum` fill, ink type, keeps `--inset` |
| Focus | 2 px `--brand` outline, 2 px offset |

Selection is `--platinum`, not `--ink`. An ink fill at card size reads as a primary button, and
a selected card is not something you press. Platinum is theme-aware - `#f5f4f4` on light,
`#2e2b33` on dark - so the fill stays a step off the surface and ink type stays legible in both.

### What the inset replaces

- **1 px borders** - a hard line competes with content and breaks over glass.
- **Drop shadows** - reserved for tooltips and the Ask bubble. Never on a card.

**Layout note.** Never leave a hole larger than about 48 px under a card's last child. If a
card looks empty at the bottom, the padding is wrong or the content belongs somewhere else.

---

## 5. Buttons

**A button is never `--brand`.** Violet is the chart highlight and the focus ring; it is
not something a person presses, in either theme.

| Button | Fill | Label | Where it goes |
| --- | --- | --- | --- |
| Primary | `--ink` | `--ink-reversed` | Every action inside the product. The default. |
| CTA, B2B | `--amber` | `--ink-reversed` | Marketing calls to action aimed at businesses. |
| CTA, B2C | `--signal` | `--ink` | Marketing calls to action aimed at consumers. |
| Secondary | none | `--ink` | Hollow. The inset hairline is the whole button. |

```css
.btn          { background: var(--ink);    color: var(--ink-reversed); }
.btn--b2b     { background: var(--amber);  color: #f5f4f4; }
.btn--b2c     { background: var(--signal); color: #0f0c15; }
.btn--ghost   { background: transparent;   color: var(--ink); box-shadow: var(--inset); }
```

The B2C fill is `--signal`, the colour the palette calls **Neon**. There is no `--neon`
token - that name now belongs to the cyan, which is `--electric`.

**A button label is only ever `--ink` or `--ink-reversed`** - never muted, never an accent,
never a ramp ink. The two are a mirror, `#0f0c15` and `#f5f4f4`, so the primary button flips
with the theme and holds 17.65:1 in both.

Amber and neon carry the same value in light and dark, so their labels do not swap with the
theme. They are frozen at the light-mode pair: `--ink` on neon at 16.92:1, `--ink-reversed`
on amber at 2.83:1.

Two more rules: **a CTA is not an interface button** - amber and neon belong on marketing
surfaces, and inside the product the primary action is ink. And **one CTA to a view**, since
two competing fills means neither is the call.

---

## 6. Chips

A chip is a short categorical label in a pill of air: no fill, no colour, just the inset
hairline and ink. Ported from the Kantha deck system, where the same component carries
product names, channels and tags.

### Style rules

| Property | Rule |
| --- | --- |
| Shape | Pill, `999px` |
| Fill | `none` |
| Edge | `var(--inset)` - the card hairline bent around a pill |
| Type | `0.75rem`, weight 600, `--ink` |
| Type as a card footer | Steps down to `0.6875rem` on `.card--chips` |
| Layout | `.chips` is a wrapping flex row |

The hairline is `--inset-edge`, ink at 20%, so it inverts with the theme and the chip holds
its edge on either ground without ever taking a fill.

### Spacing

| Context | Rule |
| --- | --- |
| Standalone | `margin-top: 1.3rem` |
| Directly under a `.chips__head` | `.chips--tight`, `margin-top: 0.25rem` |
| As a card footer | `.card--chips` makes the card a column; the chips take `margin-top: auto` |

Kantha scopes the footer behaviour to `.card` because there every card is a slide panel.
Here `.card` is the generic container, so it is opt-in via `.card--chips` - otherwise every
card in the guide would shrink its chips and push them to the bottom.

```html
<p class="chips__head">Products live &amp; launching</p>
<div class="chips chips--tight">
  <span class="chip">Pay by bank</span>
  <span class="chip">Card</span>
</div>

<!-- as a card footer -->
<div class="card card--chips">
  ...
  <div class="chips"><span class="chip">Slack</span></div>
</div>
```

### Chip or pill?

They look alike and they are not interchangeable. **A chip names a thing; a pill carries a
state.** Every chip in a row has the same weight, so the set reads as a list. A pill takes a
fill and a colour precisely because it is saying something about status.

**Never**

- A filled chip. No `--brand`, no `--amber`, no neon - the moment a chip takes a fill it
  reads as a CTA.
- Chip type in anything but `--ink`. Not muted, not an accent. A chip that needs a colour
  is a pill.
- Sentences. A few words: products, channels, tags, categories.

---

## 7. Data visualisation

Charts sit on white cards on the paper stage. Colour lives on fills and strokes, and reaches
type only where it has to tie back to a graph. Type on a chart is Suisse; axis labels are
10.5 px; gridlines are `--grid`; axis ticks are `--ink3`.

### Chart ramps

Categorical, violet - `CAT()` maps to `--c0` ... `--c6`. It is the brand ramp interpolated
to seven steps: `--c0` is `--indigo`, `--c2` is `--brand`, `--c5` is `--lavender` and `--c6`
is `--mist`, with three steps filling the gaps.

| Token | Light | Dark |
| --- | --- | --- |
| `--c0` | `#221244` | `#e3deff` |
| `--c1` | `#33156f` | `#c4c0ff` |
| `--c2` | `#6e2cff` | `#aca3ff` |
| `--c3` | `#896fff` | `#896fff` |
| `--c4` | `#aca3ff` | `#6e2cff` |
| `--c5` | `#c4c0ff` | `#33156f` |
| `--c6` | `#e3deff` | `#221244` |

**It inverts in dark mode** rather than shifting, the same way the neutrals do, with `--c3`
as the pivot that does not move. That keeps the meaning of a step constant: `--c0` is always
the one that carries against the stage and `--c6` is always the quietest, whichever theme
you are in.

**Draw series from `--c1` to `--c4`.** The tail of the ramp is deliberately close to the
stage at both ends - `--c6` sits at 1.30:1 on a light card and 1.08:1 on a dark one - so the
last two steps are for tails, fades and fills that already have an edge, not for a series
that has to be found. `--c0` is the extreme at the other end and is held back for emphasis
rather than spent on a default series. A four-series chart should use `--c1`, `--c2`,
`--c3`, `--c4`.

Sequential, ink - `SER()` maps to `--s0` ... `--s6`. Seven neutrals from `--ink` to
`--silver`, with `--carbon` sitting between `--ink` and `--graphite`:

| Token | Light | Dark | Neutral |
| --- | --- | --- | --- |
| `--s0` | `#0f0c15` | `#dddcdd` | `--ink` |
| `--s1` | `#16131c` | `#b5b3b6` | `--carbon` |
| `--s2` | `#2e2b33` | `#8e8d93` | `--graphite` |
| `--s3` | `#56545a` | `#56545a` | `--steel` |
| `--s4` | `#8e8d93` | `#2e2b33` | - |
| `--s5` | `#b5b3b6` | `#16131c` | `--titanium` |
| `--s6` | `#dddcdd` | `#0f0c15` | `--silver` |

It inverts on `--s3` exactly as the categorical ramp inverts on `--c3`, so the same rule
holds for both: index 0 carries, index 6 is the quietest, and the tail sinks into the
surface by design. In dark mode `--s5` is the card fill and `--s6` is the stage, so they
read as absence rather than as a step - which is the point of them.

The `Neutral` column is where each step comes from, not an alias. The dark neutrals collapse
(`--titanium` and `--silver` are both `#56545a`, and `--steel`, `--graphite` and
`--platinum` are all `#2e2b33`), so the ramp is declared as literals to keep seven distinct
steps in both themes.

### The fade

Violet and amber **fills** ramp from full opacity to **15%**. Direction depends on the shape.

**Bars and pillars - solid at the tip:**

| Shape | Gradient | Solid end |
| --- | --- | --- |
| Horizontal bar growing right | `270deg` | Right |
| Horizontal bar growing left | `90deg` | Left |
| Vertical pillar | `from: 'top'` | Top |
| Waterfall down bar | `from: 'left'` | Left |

```css
background: linear-gradient(
  270deg,
  var(--brand) 0%,
  color-mix(in srgb, var(--brand) 15%, transparent) 100%
);
```

**Areas - solid at the series:** the area under a line runs full opacity where it meets the
stroke and 15% at the baseline (`from: 'top'`). On a 100% stack, only the bottom band fades;
upper bands stay solid at about 0.92 opacity.

In SVG: an `objectBoundingBox` `linearGradient` with stop 0% at opacity 1 and stop 100% at
opacity 0.15.

**Do not fade:** line and stroke colour, legend swatches, ring segments, scatter bubbles,
cohort cells.

### Chart types

**Horizontal stat bars.** Track 7-9 px, pill radius. Default fill `--silver`.

| Variant | Fill | Use |
| --- | --- | --- |
| Default | `--silver` | Quiet comparison |
| Violet | `--brand` fade | The series in focus |
| Amber | `--amber` fade | Cost, warning, lapse |
| Steel | `--s4` light / `--steel` dark | Long tail, "other" |
| Ranked | mix `--bar-fade` by `val/max` | Ordered league tables |

Reference marker: a 1.5 px `--ink2` tick.

**Vertical pillars.** `rx="3"`, default silver, violet and amber fade top to base.

**Lines.** 2 px stroke, round caps and joins, optional last-point dot at `r="3.2"`. Hover
guide is dashed `--ink3` at 50%. A violet or amber series fills with the series-to-baseline
fade; any other fill is the series colour at 0.12.

**Stacked areas (100%).** Series from `CAT()`. Only the bottom band fades.

**Rings.** Solid strokes, no fade. Track is `--lift2`. Legend swatches match the stroke.

**Waterfall.** Totals and up-bars are silver. Down and lapse bars are `--warn` with the
left-to-right fade.

**Divergence.** Full-height fills on an 8 px track. Grow-in is brand at `90deg`; the opposite
side is solid `--c1`. Ink is too stark against a card at bar weight, so the deep end of the
categorical ramp carries it instead - and it inverts with the theme.

**Heatmaps.** Hue ramps electric -> neon -> amber. Opacity ramps 10% -> 100% with `--i`. Hover
outline is `--ink`.

**Cohort cells.** Quantised onto the categorical ramp, not a fade of `--brand`:
`--c{round((1 - retention) * 6)}`, so month 0 is `--c0` and a spent cohort is `--c6`. Type
is chosen per cell by measuring contrast against the fill, which keeps the grid legible when
the ramp inverts in dark mode. Eight columns onto seven steps means adjacent months
sometimes share a step - that is quantisation, not a bug.

**Scatter (volume against margin).** Solid bubbles, no fade.

| Quadrant | Colour |
| --- | --- |
| Heroes | `--brand` |
| Traffic | `--ink` |
| Protect | `--signal` |
| Delist | `--amber` |

Split lines (own median, margin rate) are dashed `--accent-l` with `stroke-dasharray: 4 4`.

### Colour on type

Type on a chart is ink by default. Colour reaches it in two places only.

**A label read against a graph.** A series name, an inline value, a legend label - anything
the eye has to match back to a fill or a stroke takes that series colour.

**A negative value in a table.** `--amber`, with the minus sign kept:

```html
<td class="neg">&minus;3.8%</td>
```

```css
.tbl .neg {
  color: var(--amber);
}
```

Amber on a white card is 3.11:1, which is below AA for body-size text. The minus sign is what
carries the meaning; the colour only makes the column scannable. It passes comfortably in dark
mode at 5.9:1 against `--carbon`.

Never a chart title, a KPI value, or body copy - nothing correlates to those, so colour on
them is decoration. And never violet type anywhere: brand is a fill and a focus ring, so it
never becomes a letterform. Use the series colour from the ramp instead.

### Quick checks

1. Grey bars are silver, and silver goes darker in dark mode.
2. Filled violet and amber fade 100% -> 15%. Bars solid at the tip, areas solid at the series.
3. Rings, bubbles, legend chips and lines stay solid.
4. Legend swatches are pills matching the series *before* any fade.

---

## Token sheet

```css
:root {
  --white: #ffffff;   --paper: #fcfbfb;     --platinum: #f5f4f4;
  --silver: #dddcdd;  --titanium: #b5b3b6;  --steel: #56545a;
  --graphite: #2e2b33; --carbon: #16131c;   --ink: #0f0c15;

  --brand: #6e2cff;   --amber: #ff5b00;     --signal: #daff35;
  --electric: #59fcf9;    --danger: var(--amber);

  /* accent ramps */
  --indigo: #221244;  --lavender: #c4c0ff; --mist: #e3deff;
  --bronze: #3f1711;  --peach: #fddcc9;
  --slate: #0e242c;   --spring: #f8ffda;  --ice: #d1fffe;

  /* type on an accent */
  --brand-ink: var(--indigo); --amber-ink: var(--bronze); --signal-ink: var(--slate);

  --bg: var(--paper);      /* the stage */
  --raised: var(--white);  /* cards and the sidebar */

  --ink2: color-mix(in srgb, var(--ink) 62%, transparent);
  --ink3: var(--steel);
  --muted: color-mix(in srgb, var(--ink) 45%, transparent);
  --grid: color-mix(in srgb, var(--ink) 8%, transparent);
  --inset-edge: color-mix(in srgb, var(--ink) 20%, transparent);

  --r: 1.5rem;  --r-m: 16px;  --r-s: 12px;
  --sans: 'Suisse Intl', -apple-system, BlinkMacSystemFont, sans-serif;
}
```

---

## Repo layout

```
STITCH BRAND SYSTEM/
  index.html            the guide
  brand-system.css      tokens, shell, components
  brand-system.js       theme, nav, clipboard, charts
  BRAND-SYSTEM.md       this file
  PRESENTATIONS.md      slide template rules - grid, layouts, deck data-vis
  fonts/                woff2 cuts the guide itself loads
  assets/
    logo/               downloadable marks
    fonts/web/          downloadable woff2
    fonts/desktop/      downloadable otf and ttf
    bundles/            zipped packs
```

Preview locally with `python3 -m http.server 8777` from this folder, then open
<http://localhost:8777/>. There is no build step.

---

Stitch brand system - 2026 - Maintained by Brand & Design.
