# Orbit — Orbit Spec

Top-level machine-readable reference for the Orbit. Load as context for component generation, token usage, accessibility checking, or spec writing. For detailed specs see `specs/foundations/` and `specs/tokens/`.

---

## What is Orbit

Orbit is the unified design system for Openpoint's two products:
- **Open Point** — stakeholder relationship management
- **Social Point** — community engagement

Guiding principle: **suite-first, product-second** — components, tokens, and patterns form one coherent system.

---

## Token naming conventions

Three tiers, one chain: **Primitive → Semantic → Component**. Semantic is the meaning layer and holds the real resolved values; Component mirrors Semantic under Web Awesome's own native naming so `variant=` theming works — it does not introduce new values. Always reference a Semantic or Component token — never a raw value.
Token source of truth: Figma Variables, exported to `src/tokens/` as JSON, merged into `src/tokens/orbit-tokens.json` and built to CSS by `scripts/generate-wa-theme.js` in this repo, then published as the `@open-point-design/orbit-theme` npm package (GitHub Packages, renamed from `@open-point-design/wa-theme` on 2026-08-27) for orbit-storybook and product consumers. Install it, then `import '@open-point-design/orbit-theme'` and `import '@open-point-design/orbit-theme/theme.css'`.

### Primitive tokens
Pattern: `color.{ramp}.{step}`, step `05` (darkest) → `95` (lightest) — matches Web Awesome's own ramp convention exactly (neutral also has an extra `100`, the lightest true-white-adjacent step). **JSON-only** — lives in `src/tokens/Primitives.Value.tokens.json`, resolved directly into Semantic token values at build time. There is no `--wa-color-{ramp}-{step}` or any other CSS custom property for a primitive; it's never emitted on its own.
- `color.green.60` → #bace42
- `color.neutral.10` → #211f1b

### Semantic tokens (the meaning layer)
Pattern: `--orbit-{category}-{group}-{role}` (and `--orbit-radius-*`/`--orbit-spacing-*`/`--orbit-elevation-*` for non-colour categories) — a **verbatim mirror** of `Semantic.Value.tokens.json`'s resolved groups (brand/neutral/surface/text/icon/status/state/border), key-for-key. Every role WA's Component tier reads is also exposed here directly, under Orbit's own naming, for non-WA consumption.
- `--orbit-color-text-normal` → #141210
- `--orbit-color-surface-default` → #f7f4f0
- `--orbit-color-border-normal` → #afa9a0
- `--orbit-color-brand-fill-loud` → #bace42 (Open Point) / #5ec5dd (Social Point)
- `--orbit-color-state-focus` → #24a4fc

As of `scripts/generate-wa-theme.js`'s current version, this is generated directly from the same resolved Semantic map Component tokens use — there is no separate hand-maintained mapping any more, so these two layers cannot drift apart. (They used to: a legacy hand-written snapshot backed `--orbit-*` and had gone stale — missing `surface.raised`/`lowered`/`border` entirely, among other drift. Fixed 2026-08-27.)

**Product switching happens here, in one place:** the Semantic layer's **Brand** collection (`Brand.Open Point.tokens.json` / `Brand.Social Point.tokens.json`). Both files remap the same semantic refs to a different primitive hue — green for Open Point, teal for Social Point. Everything downstream — Semantic's own `brand` colour roles, and every Component token mirroring them — just inherits whichever hue that product's Brand file resolved to. `neutral` and other non-brand roles resolve straight to Primitives and never vary by product.

### Component tokens
Pattern: `--wa-color-{group}-{role}`, where role is one of `fill-quiet`/`fill-normal`/`fill-loud`/`border-quiet`/`border-normal`/`border-loud`/`on-quiet`/`on-normal`/`on-loud`. Group is one of `brand`/`success`/`warning`/`danger`/`info`/`neutral` — all six mirror Semantic values natively in `wa-theme.generated.css`, so every WA component using `variant=` themes itself from these automatically, no per-component CSS needed. (`info` used to require a hand-authored mirror in `component-overrides.css`; that's no longer true — it's populated upstream like the rest.) A small number of Orbit-only extensions still need `component-overrides.css`: `data-color="info"/"scheduled"/"pending"/"purple"` on badges/callouts, and `data-style="inverse"` on buttons.
- `--wa-color-brand-fill-loud` → #bace42 (Open Point) / #5ec5dd (Social Point)
- `--wa-color-success-fill-loud` → #677819
- `--wa-color-danger-on-loud` → #fdfcfb

### Token categories
- `color` — primitives (8 ramps × 11 steps, WA-numbered) + component tokens (WA-native fill/border/on roles) + Orbit-only aliases (background, text, interactive, status, border)
- `radius` — primitive scale + component-level (button, input, card, modal, badge)
- `spacing` — component padding/gap + layout (content-gap, section-gap, page-gutter)
- `elevation` — shadow levels (subtle, low, med, high)

---

## Colour system

8 ramps, 11 steps each (`05`–`95`, descending — `05` is darkest, matching Web Awesome's own convention). Steps follow a perceptual lightness curve. Neutral has an additional step `100` (#FDFCFB, the lightest). Neutral is true gray — not warm.

| Ramp | Primary use |
|---|---|
| green | Primary interactive, positive states, brand |
| teal | Secondary UI, scheduled status, supplementary |
| blue | Informational states, external links |
| purple | Discovery, tags, new features |
| red | Error, destructive actions only |
| orange | Pending-approval status, in-progress states |
| yellow | Warning, mild caution |
| neutral | Text, borders, backgrounds, disabled |

**Critical contrast rules:**
- brand-60 (#BACE42) on neutral-100 (#FDFCFB): ~1.71:1 — FAIL. Never use as text on light surfaces.
- brand-60 (#BACE42) on neutral-05 (#141210): ~10.67:1 — passes AAA. Use for text/brand on dark surfaces.
- neutral-05 (#141210) on neutral-100 (#FDFCFB): ~18.24:1 — AAA.

These are computed live from the token source at build time on `/foundations/colour` — check there for current values rather than trusting hardcoded numbers, including these.

---

## Typography

Two type roles in product UI, each paired with a screen-optimised Google Fonts typeface —
**not** the brand identity fonts:
- **Heading** — Lora (`--orbit-font-family-heading`, aliased onto `--wa-font-family-heading`),
  all headings, section labels, nav
- **Body** — Hanken Grotesk (`--orbit-font-family-body`, aliased onto `--wa-font-family-body`),
  body copy, prose
- **Code/tokens** — JetBrains Mono (Orbit's own site CSS only — not yet published to
  `@open-point-design/orbit-theme`)

**ABC Otto, ABC Social, and Memoir are Open Point's brand identity typefaces — they are not
used in product UI.** They live in marketing/print contexts; Lora and Hanken Grotesk are the
deliberate screen-optimised alternatives, chosen because the brand fonts aren't licensed for
broad ongoing web delivery and aren't optimised for legibility at small sizes across every
browser/OS/screen density a government stakeholder might be using. See
`/foundations/typography#typeface-decisions` for the full reasoning.

Both stacks carry a multi-lingual fallback (Noto Sans / Noto Serif) before generic system
fonts, for broad Unicode/script coverage in languages Lora/Hanken Grotesk don't cover.
`@open-point-design/orbit-theme`'s published `theme.css` loads both fonts via `@import` —
no separate `<link>` step needed for consumers of the package.

The full type scale (heading-xl, body-md, etc. — font, size, weight, tracking, line-height per style) is defined in Figma and exported to `src/tokens/text.styles.tokens.json`, documented at `/foundations/typography`, but **not yet exposed as consumable CSS custom properties** — there is no `--wa-text-*` or `--orbit-text-*` scale today. The only typography currently wired into the published theme CSS is a handful of hand-set globals: `--wa-font-family-body`, `--wa-font-family-heading`, and `--wa-font-weight-{normal,semibold,bold,body,heading,action}`.

(`--op-text-xs` through `--op-text-6xl` and `--op-leading-*` exist, but only inside Orbit's own site CSS — they are not published to `@open-point-design/orbit-theme` and are not available to product consumers.)

---

## Spacing

Base-4px scale. Component tokens: `--orbit-spacing-component-*`, layout tokens: `--orbit-spacing-layout-*`.

Common component values:
- `--orbit-spacing-component-padding-block-sm` (8px) — compact padding
- `--orbit-spacing-component-padding-inline-lg` (16px) — standard horizontal padding
- `--orbit-spacing-layout-content-gap` (24px) — gap between content sections
- `--orbit-spacing-layout-section-gap` (48px) — gap between major page sections

---

## Component conventions

- Component names: PascalCase
- Built on Web Awesome (framework-agnostic web components)
- Every component has: Overview, Style, Code, Accessibility tabs in Orbit docs
- Status lifecycle: `under-consideration` → `planned` → `designing` → `feasibility-review` → `for-implementation` → `beta` → `stable` → `deprecated` (deprecated must link to its replacement)

### Figma layer naming
- Layers: kebab-case
- Components: PascalCase
- Variant properties: Title Case (e.g. Size, State, Variant)

### AI-optimised Figma descriptions
Four-part format on every main component:
1. **Purpose** — what it is and what it does
2. **When to use** — right contexts
3. **When not to use** — what to use instead
4. **Related components** — connected components

---

## Accessibility baseline

- WCAG 2.1 AA minimum for all components
- Minimum touch target: 44×44px
- Focus ring: 3px, `--orbit-color-state-focus` (#24a4fc blue) — consistent across themes
- `prefers-reduced-motion` respected throughout
- All interactive elements require keyboard access and visible focus state

---

## Detailed specs

- `specs/foundations/color.md` — colour ramp hex values (current, but written in Orbit's internal `--op-color-*` site-CSS naming — for the naming a product consumes, use `--wa-color-*`/`--orbit-color-*` from this file instead)
- `specs/foundations/spacing.md` — spacing scale (same caveat: `--op-space-*` site-CSS naming, not the `--orbit-spacing-*` product tokens)
- `specs/foundations/typography.md` — type scale as documented for design, not yet wired into consumable CSS (see Typography above)
- `specs/tokens/token-reference.md` — the full semantic token table (every `--orbit-*` colour/radius/spacing/elevation token, with current hex/px values and the Open Point/Social Point brand diff). Fixed 2026-08-27 — previously described a pre-rename token layer that predated the WA ramp-numbering alignment.
- `src/pages/components/` — component documentation as MDX (authoritative source)
