# Turbo IT brand
# Rules of record. The source is the Demo Builder (generate.turboit.uk), whose one
# shared stylesheet is turbo-it-generate/src/ui-css.js. Every value quoted here is
# that file's, and where it documents its own reasoning (an AA measurement, a
# contrast trap) that reasoning is kept: it is why the value is what it is.
# Tokens: tokens/turbo-it-tokens.css. Visual reference: index.html.

## 1. The mark

- The mark is the bolt. Never redraw it by hand, never recolour it outside the approved
  variants, never rotate it, never add a shadow or an outline.
- **As the builder actually ships it: the bare gradient bolt**, inlined as a data URI
  (`TURBO_LOGO` in `turbo-it-generate/src/index.js`), used for the favicon and the login
  lockup. That is the primary form. `logos/bolt-mark.svg` is the same shape as a clean
  vector.
- Files: `bolt-mark.svg` (gradient, primary), `bolt-mark-mono-*.svg` (electric, white, ink,
  cream), `favicon.svg` (bare bolt), `icon-tile.svg` (optional opaque app icon),
  `lockup-login-*.svg` and `lockup-topbar-*.svg`.
- Clear space: a quarter of the mark's height on every side. Minimums: 14px for the gradient
  mark, 12px for mono, 20px for the tile.
- The mark is 350 x 512 (0.684 aspect), taller than wide. Never squash it to a square.
- The tile (near-black rounded square) is NOT what the builder uses. Keep it for a favicon
  that needs an opaque square, an app icon or an avatar, never as the mark inside a
  product's own topbar.

## 2. Lockups

Two patterns, both the builder's, both using live text plus the bolt image:

- **Login (stacked)**: bolt 38px tall, "Turbo IT" at 15px/600 with `-0.01em`, product name
  at 12px in `--ti-muted-foreground` underneath. Use it for a centred sign-in or an
  interstitial page.
- **Topbar (inline)**: bolt 24px tall, "Turbo IT" at 14px/600, a 1px divider 18px tall, then
  the product name at 400 weight in muted. Use it in an app shell (`ui-css.js` `.ti-brand`).

The wordmark is monochrome. There is no two-tone "Turbo IT" in the builder, so do not invent
one. Never letter-space it, never set it in a serif, never stretch it.

## 3. Type

- Family: the system sans stack in the tokens (`ui-sans-serif, system-ui, -apple-system,
  "Segoe UI", Roboto`). Mono only for code, keys and tabular values.
- **The scale is 11 / 12 / 13 / 14 / 15 / 16 / 17 / 18 / 22 / 24 px, and it stops there.** A
  28px or 40px headline step does not exist in the builder. Do not add one to a product UI:
  a 40px headline in a dashboard reads as a marketing page.
- Weights: 400 body, 500 controls and pills, 600 headings and labels, 700 only for h1, the
  wordmark and a stat tile's number.
- Tracking: `-0.02em` on h1, `.ti-h1` and `.tile .v`; `-0.01em` on h2 and the brand name.
  Uppercase micro-labels take `+0.12em` at 11px, and that is the only uppercase in the kit.
- Line height 1.5 for prose, 1.2-1.45 for controls and dense rows.

## 4. Colour

Source: `ui-css.js` `:root`. Neutrals plus four status hues, each with a fill AND a text
variant, because the bright fill is not legible as small text.

| Token | Value | Use |
|---|---|---|
| `--ti-background` | `#f6f3ed` | page: warm off-white, never stark white |
| `--ti-surface` | `#f2eee6` | table heads, recessed panels, `.dotted` |
| `--ti-card` | `#fefcf8` | cards, topbar, rows |
| `--ti-input-bg` | `#ffffff` | form fields: the one true white |
| `--ti-foreground` | `#231f19` | body, headings |
| `--ti-muted-foreground` | `#605a52` | secondary text |
| `--ti-muted` | `#eee9e0` | hover fills, chips, skeletons |
| `--ti-border` | `#dfdad1` | hairline borders (hover: `#cfc8bb`) |
| `--ti-primary` | `#1276ce` | fills, focus, links |
| `--ti-primary-hover` | `#0064b5` | hover |
| `--ti-primary-foreground` | `#fcfcfc` | text ON the primary fill |
| `--ti-destructive` | `#c1000f` | destructive fill |
| `--ti-success` | `#0f6a2e` | success fill |
| `--ti-warning` | `#8a5400` | warning fill |
| `--ti-on-foreground` | `#fdfbf7` | text on a foreground-coloured surface (the toast) |
| `--ti-focus-ring` | `0 0 0 3px rgba(18,118,206,.18)` | the focus ring on any control |

Status text variants, which are the ones you put words in: `--ti-info-fg #0064b5`,
`--ti-success-fg #0f6a2e`, `--ti-destructive-fg #c1000f`, `--ti-warning-fg #8a5400`. Each has
a tint pair (`--ti-info-bg/-bd` and friends) for the pill background and border.

Measured contrast (WCAG, sRGB, 2026-09-24):

| Pair | Ratio | Verdict |
|---|---|---|
| foreground on background | 14.80 | AAA |
| muted-foreground on background | 6.15 | AA |
| primary-foreground on primary | 4.66 | AA |
| info-fg on its tint over card | 6.10 | AA |
| success-fg on its tint over card | 6.20 | AA |
| destructive-fg on its tint over card | 6.10 | AA |
| warning-fg on its tint over card | 6.60 | AA |
| bright warning fill as 12px text on its tint | 1.94 | fails: this is why the -fg set exists |
| white on the gradient's teal end | 3.50 | fails: never put white text on the gradient |

Rules that follow, and are not negotiable:

1. Status words use the `-fg` variant. The plain hue is for the fill only.
2. Never put white text on the gradient (`--ti-avatar` in `ui-css.js` is solid primary for
   exactly this reason).
3. `--ti-muted-foreground` is the floor for secondary text. There is no third grey for text.
4. If you need a colour that is not here, add it to the token file first, with its measured
   contrast, then use it.

## 5. The brand gradient

`--ti-grad`: `linear-gradient(100deg, #8a4af5 0%, #1276ce 46%, #0093cb 74%, #00b5bc 100%)`.
Three sanctioned uses, all from the builder: the topbar's 2px accent line at 50% opacity, the
wordmark lockups, and the progress fill. Never a button fill, never behind text, never under
the mark.

## 6. Shape, depth, motion, density

- Radii: 6 (kbd, skeletons) / 8 (buttons, fields, alerts) / 10 (the base `--radius`: segs,
  panels, dropzones) / 12 (cards, tiles, menus) / 14 (modals) / 999 (pills, chips, badges,
  switch, avatar). Nothing square, nothing at 4px.
- Depth: hairline borders plus warm low-contrast shadows (`--ti-shadow-sm`,
  `--ti-shadow-card`). The primary button additionally carries the electric glow
  (`0 0 40px -8px`, widening to `60px -5px` on hover). One border and one shadow per surface.
- Motion: 150ms ease (`--ti-tt`) on colour, border, shadow and background only. Skeleton
  shimmer and the 0.7s button spinner are the only loops. Honour
  `prefers-reduced-motion`: the builder collapses all animation to 0.001ms there.
- Density: controls are 40px (32px small, 48px large), fields 40px, table rows 48px, the
  topbar 60px (`--ti-nav-h`). On a coarse pointer every 32px and 36px control lifts to 44px.
- Focus: border to primary plus `0 0 0 3px rgba(18,118,206,.18)`. Never remove a focus ring.

## 7. The link row (extension)

Not in the Demo Builder. This is the hub's destination row, assembled out of the components
above (a card row, a pill and `.btn-sm`). It exists because a product needs one obvious
action per destination, always visible, in the same place on every row.

```
.row          min-height 56px; padding 9px 12px 9px 14px; gap 12px; radius 10;
              1px --ti-border; background --ti-card
.row:hover    background --ti-muted          (the builder's table hover)
.ti-icon      36x36; radius 8; white tile; 1px --ti-border; image inset 5px
.ti-title     14px / 600 / --ti-foreground, truncated
.ti-desc      13px / 400 / --ti-muted-foreground, truncated, one line
.ti-host      mono 11.5px / --ti-muted-foreground; pill: --ti-muted bg, radius 999,
              padding 3px 10px, max-width 30%, truncated
.ti-badge     the builder's .pill-info: 12px / 500, --ti-info-bg, 1px --ti-info-bd,
              --ti-info-fg, a 6px dot, radius 999
.ti-open      .btn-sm: 32px tall, min-width 78px, radius 8, 13px / 500; idle = secondary
              (card bg, border, shadow-sm), row hover = primary (electric + glow)
.ti-delslot   a reserved 22px before the button, so a delete button never shifts it
below 640px   hide .ti-host and .ti-desc, .ti-open min-width 64px (76px in practice)
```

Positioning rules: the action is the last element in the row, always visible (never
hover-revealed), its right edge 12px from the row edge on every row, and it keeps its width
whether or not the row has a delete button. Rows are 56px; a two-line body in a single row
is a bug, not a variant.

Two placements, one component: standalone the row is its own bordered card with 8px gaps;
inside a panel it is hairline-separated (`.row + .row`) with no border of its own. The hub
uses the panel placement. On a phone the rows keep identical metrics to each other: what
changes is the row's content, never one row's size. Under 640px `.ti-host` and `.ti-desc` are
hidden, `.ti-open` takes a 64px minimum (75px in practice) with 9px padding, and the badge
collapses to its dot (the words cost the title about 48px; carry the meaning on the badge's
`title` and `aria-label`).

## 8. Dark mode

**The Demo Builder has no dark mode.** Anything dark in a Turbo IT product is an extension,
and the tokens file keeps it in a clearly separated block. If a product ships dark, derive it
from the light palette and keep the relationships: electric is the fill, a lighter electric is
the text on the surface, and text ON an electric fill is dark (white on `#0090ff` is 3.26:1).
Do not let a dark theme become the default for an internal tool that also has to match the
builder's screenshots.

## 9. Voice

Plain English, sentence case, no exclamation marks, no emoji in product UI (the hub's category
icons are the estate's one existing exception). Say what a thing does: "Instant AI client-site
mock-ups for sales calls", not "Supercharge your pipeline!". Status words are the plain ones
the builder already uses: Live, Degraded, Failed, Queued, Locked.

**No em dashes, ever** (owner rule, estate-wide). Use a comma, a colon or a full stop. Watch
the hidden forms: `&mdash;`, `\u2014`, and a lone dash on its own line. En dashes are fine in
genuine ranges.

## 10. The gate

`node scripts/brand-check.mjs [dir] [--strict]` lints a product against this kit. It fails on an
em dash and on a literal hex colour outside the token definitions, and it warns on an off-scale
radius or font size, or a page that never references the tokens. It is a lint, not a proof: it
catches drift, it does not judge a design.

Every product vendors a copy at `scripts/brand-check.mjs` and its CI runs that copy first, always,
before anything can fail to reach the network. It then compares it with this kit's copy and warns
if they differ. That order matters: the first version of this gate fetched the script and skipped
when the fetch failed, which is exactly the failure a gate must not have. Vendor the file, run it,
and let the kit tell you when the rules move on:
`https://brand.turboit.uk/scripts/brand-check.mjs`.

Run it before pushing, in the product's repo root:

```
node /path/to/turbo-it-brand/scripts/brand-check.mjs .
```

If a value genuinely has to be a literal (a theme-color meta, a demo that shows a brand value),
put it in the product's `brand-allow.json` with the reason:

```json
{ "#f6f3ed": "theme-color meta must be a literal, this is --ti-background" }
```

Anything else the checker complains about is the kit telling you the value belongs in
tokens/turbo-it-tokens.css first, where every product gets it.

## 11. Starting a new internal app

Do not start from a blank page, and do not start from another product. Start from the template,
so the app is branded before the first line of its own logic exists:

```
~/repos/turbo-it-brand/scripts/new-internal-app.sh <app-name>
```

It scaffolds `~/repos/<app-name>` with the branded shell (topbar, tokens, cards, the link row,
the empty state), a deploy script, a CI workflow with the brand gate already wired, and a README
listing what to rename. Then it prints the commands to create the repo and the Pages project.
