<style> :root { color-scheme: dark } </style>
---
version: "2.0"
name: Bandicot
description: The Bandicot identity across the website and the native desktop app. Warm near-blacks, stone-white type, one olive accent, Inter with Geist Mono, and liquid characters for every identity.
colors:
  root: "#0f0f0e"
  surface: "#111110"
  raised: "#131312"
  subtle: "#151514"
  elevated: "#191918"
  hover: "#1b1b1a"
  inset: "#0d0d0c"
  border: "#252523"
  border-strong: "#30302d"
  text: "#e2dfd6"
  text-muted: "#8f8c82"
  text-faint: "#85827a"
  accent: "#a5a77a"
  accent-hover: "#b4b68b"
  accent-on: "#0f0f0e"
  spark: "#d9c873"
  success: "#a5a77a"
  warning: "#e6c36a"
  danger: "#f09595"
  light-root: "#f2f0ea"
  light-accent: "#5c5e38"
  overlay: "rgba(5,5,5,0.52)"
typography:
  hero:
    fontFamily: Questrial
    fontSize: 38px
    fontWeight: 400
    lineHeight: 1.1
    letterSpacing: -0.02em
  heading:
    fontFamily: Questrial
    fontSize: 28px
    fontWeight: 400
    lineHeight: 1.15
    letterSpacing: -0.015em
  subhead:
    fontFamily: Inter
    fontSize: 18px
    fontWeight: 500
    lineHeight: 1.3
  body:
    fontFamily: Inter
    fontSize: 13.5px
    fontWeight: 300
    lineHeight: 1.65
  ui:
    fontFamily: Inter
    fontSize: 12.5px
    fontWeight: 500
    lineHeight: 1.4
  meta:
    fontFamily: Inter
    fontSize: 11.5px
    fontWeight: 300
    lineHeight: 1.4
  code:
    fontFamily: Geist Mono
    fontSize: 12px
    fontWeight: 400
    lineHeight: 1.5
rounded:
  control: 14px
  full: 9999px
spacing:
  1: 2px
  2: 4px
  3: 6px
  4: 8px
  5: 12px
  6: 16px
  7: 20px
  8: 24px
  9: 32px
  10: 40px
  11: 48px
  12: 64px
components:
  button-primary:
    backgroundColor: "{colors.accent}"
    textColor: "{colors.accent-on}"
    rounded: "{rounded.control}"
    padding: 0 20px
    height: 36px
  button-primary-hover:
    backgroundColor: "{colors.accent-hover}"
  button-ghost:
    backgroundColor: "{colors.raised}"
    textColor: "{colors.text}"
    rounded: "{rounded.control}"
    padding: 0 20px
    height: 36px
  chip:
    backgroundColor: "{colors.subtle}"
    textColor: "{colors.text}"
    rounded: "{rounded.full}"
    padding: 4px 10px
  card:
    backgroundColor: "{colors.surface}"
    textColor: "{colors.text}"
    rounded: "{rounded.control}"
    padding: 20px
---

# Bandicot design

The written contract for how Bandicot looks and sounds. The YAML above holds
the values; the prose below holds the reasons and the rules. MUST is a hard
rule. SHOULD allows a deviation with a written reason.

Runtime token sources:

* `apps/desktop-ui/ui/src/app/brand.css`: the desktop app, and the source of
  truth for the brand (`data-brand="olive"`, themes `black` and `light`).
* `apps/ui/src/app/globals.css`: the website (`--bc-*` primitives, `--ds-*`
  semantic aliases, `--ds-ramp-1..11`). It restates the desktop's black theme.
* `design.md` at the repository root: the desktop contract this document
  mirrors.

One brand, two surfaces. The desktop app is where the work happens; the
website sells that work and MUST look like the app a visitor is about to
install.

## Overview

Bandicot is a work platform for people and agents working on the same
projects. The identity is a quiet desk: warm near-blacks, stone-white type and
one olive accent, spent once per view: the primary action, the recommended
plan, or the one element a figure is about. Everything else is stone and
hairline. It earns attention by making work legible: what each agent
did, what it cost, and what it is waiting on.

The brand never shouts. A page that needs a louder colour to be read is a page
that needs a clearer sentence.

## Colors

### Ladder (the canvas)

| Token | Hex | Role |
| --- | --- | --- |
| `--ds-inset` | `#0d0d0c` | wells, code beds, the deepest bands |
| `--ds-canvas` | `#0f0f0e` | the page |
| `--ds-surface` | `#111110` | cards and plates |
| `--ds-ramp-8` | `#131312` | raised controls |
| `--ds-subtle` | `#151514` | chips, quiet fills |
| `--ds-raised` | `#191918` | menus, popovers |
| `--ds-hover` | `#1b1b1a` | hover wash |

Steps are a few RGB values apart on purpose: the page reads as one material.
Separation comes from hairlines (`#252523`, strong `#30302d`) and text
contrast, never from shadow. A component MUST NOT type a near-black hex; it
uses a `--ds-*` token.

### Text

| Role | Token | Value | Use |
| --- | --- | --- | --- |
| Primary | `--ds-fg` | `#e2dfd6` | headings, body, control labels |
| Secondary | `--ds-muted` | `#8f8c82` | supporting copy |
| Tertiary | `--ds-faint` | `#85827a` | metadata, captions |

The desktop's faint tier (`#68665e`) is 3.3:1 on the site's hover wash, so
the website raises it to `#85827a` (4.49:1). Body copy never sits in faint.

### Accent

| Token | Value | Use |
| --- | --- | --- |
| `--ds-accent` | `#a5a77a` | the one primary action or recommended plan in a view |
| `--ds-accent-hover` | `#b4b68b` | hover of the accent |
| `--ds-accent-on` | `#0f0f0e` | text on an accent fill |
| `--ds-spark` | `#d9c873` | one highlight per view at most: a new item, a live count |

Olive is scarce. A button, a dot, a ring, a word. If more than a handful of
olive marks show on one screen, cut some.

### Status

Done and ok are olive (`--ds-success` is the accent), warning `#e6c36a`,
danger `#f09595`, each with `-bg` and `-border` mixes. Status is never
colour alone: a glyph or dot always travels with a word.

### Light theme

The desktop app ships a light mirror: root `#f2f0ea`, accent `#5c5e38`. The
website is dark-first: the home, docs and pricing are dark, and a use-case page
may take the light mirror when its market reads in daylight (`scheme: "light"`).

## Typography

**Inter** at two weights, 300 for reading and 500 for emphasis and controls,
with **Geist Mono** for code, keys and tabular numbers. No faux bold
(`font-synthesis: none`); `.font-bold` and `.font-semibold` resolve to 500.

* Hero 38px (mobile 30px) at 300 with -0.02em tracking; headings 28px at 300;
  subheads 18px at 500; body 13.5px at 300; UI labels 12.5px at 500; meta
  11.5px.
* Numbers in columns, prices and counts use tabular figures.
* Prose measures `max-w-[52ch]` at line-height 1.65. Body copy is never
  letterspaced or set in capitals.
* The fonts are self-hosted (`/fonts/*.woff2`, OFL) with metric-matched
  fallbacks, so text never shifts when the face arrives. OG images draw with
  Inter Regular and Medium TTFs.

## Shapes

* One radius: 14px (`--ds-radius`) for controls, cards and panels. The
  desktop app uses 10px (15px squircle) at its tighter density; the website's
  larger type takes the larger corner.
* Full round only for pills, avatars and status dots.
* Borders are 1px hairlines.

## Elevation

Contrast and hairline, not shadow. Moving layers (popover, dialog, drawer)
earn `--ds-shadow-2` and a strong hairline; static cards never float.
Focus is a 2px stone-white ring (`--ds-focus`) with 2px offset, on
`:focus-visible` only.

## Liquid characters

Every identity in Bandicot is a liquid character: a near-black body drawn from
joined circles, light eyes with no pupils, no filters. One family, four bodies:

| Body | Who | Drawn from |
| --- | --- | --- |
| Agent | an automation or automaton | its key; its state adds drops, an orbit or a ring |
| Crest | a profile | one head per project it splits time across; round eyes when it works at night |
| Pond | a project | a seeded surface; one drop rising per session, up to three |
| Comet | a session | a head trailing one drop per ten turns |

On the website the characters are the mascots: they stand in for people,
projects and agents in diagrams and in the hero. They MUST be drawn with the
same geometry as the app, never redrawn as flat icons or emoji. A picture a
user chose always wins over a character inside the product.

## Motion

One engine: **Motion** (`motion/mini`, Web Animations API). Springs reach
WAAPI as `linear()` easing, so they run on the compositor, off React.

* Characters breathe, blink and glance on one shared clock; one observer
  pauses everything off screen; `prefers-reduced-motion` leaves them still.
* Hover is CSS `transition-colors` at 150ms. Press is `active:scale-[0.98]`.
* State arrivals end under 300ms including stagger (40-80ms per item).
* The page morph (420ms) is the one longer transition.
* Springs belong to characters only. Interface panels, menus and text never
  bounce.
* Keyboard actions never animate.

## Layout

* 4px base unit with a 2px optical step (`--bc-space-1..12`).
* Content column `max-w-6xl`, gutters `px-4 md:px-6`, never a horizontal
  scroll.
* Band rhythm: `py-16` quiet, `py-20` standard, `py-24 md:py-28` featured.
* Home page order (MUST keep): hero (promise and the proof we have), pain,
  plan, picture (people and agents on one project), offer, questions, call to
  action, footer.
* Cards in a band share one grid and one height. No masonry, no tilt.

## Voice and offers

Short, exact, calm. No exclamation marks, no "magic", no em dashes in copy.
Buttons name the action (`Download for Mac`, `See pricing`).

Every page is built on the same selling method:

* **Value:** name the outcome the reader wants, why they should believe it,
  how soon they get it and what effort it removes.
* **Five Ps:** proof, promise, pain, plan, picture; then the questions that
  stop a reader, answered; then the offer and the call to action again.
* **Call out:** say who the page is for and who it is not for.
* **Named offers:** each offer has a name and lists only what its plan already
  gives. A bonus (a setup file, a skill, an automation) is named only once it
  ships. Scarcity and urgency appear only when they are true.
* **Truth:** a claim is one of shipped, measured, in development or direction,
  and a page says which. A number appears only with its evidence. There is no
  measured whole-task token saving yet, so efficiency is sold by mechanism
  (you see every token each turn spends) and never by a percentage.

No invented testimonials, logos or counts.

## Iconography

* Interface icons: 24x24 viewBox, 2px stroke, round caps, 14px default size.
* The app icon is the plush bandicoot on a dark squircle
  (`apps/ui/public/brand/icon-*.png`, `favicon.ico`, `apple-icon.png`).
  Scale the file; never redraw it.

## Accessibility

WCAG 2.2 AA. Body text holds 4.5:1 on every surface; every control is
keyboard reachable with a visible focus ring; icon-only controls carry
`aria-label`; decorative art is `aria-hidden`; every status has a word.

## Do and don't

- Do use the tokens. Don't type a near-black hex in a component.
- Do let hairlines and type separate. Don't shadow static cards.
- Do keep olive scarce and meaningful.
- Do draw identities as liquid characters. Don't use emoji as mascots.
- Do mark every claim's status. Don't print a number without evidence.
- Don't use raw Tailwind palette colours (`blue-500`, `purple-400`) in new
  work.
- Don't add a second face or a third weight.

## File map

| What | Path |
| --- | --- |
| Brand source (desktop) | `apps/desktop-ui/ui/src/app/brand.css` |
| Liquid characters | `apps/desktop-ui/ui/src/components/characters/` |
| Website tokens | `apps/ui/src/app/globals.css`, `apps/ui/src/app/fonts.css` |
| Website components | `apps/ui/src/components/site/`, `apps/ui/src/components/home/` |
| Landings | `apps/ui/src/landings/`, `apps/ui/src/components/landings/` |
| Brand icons | `apps/ui/public/brand/` |
| Desktop contract | `design.md` |

---
*Bandicot design contract. Maintained in `apps/ui/src/lib/design-doc.ts`;
served at https://bandicot.app/design.md. Contact: hi@apparatdynamics.com.*
