# DESIGN.md — Banno Plugin Console

The visual contract for every prototype in this folder. It is **locked**: the
console is a Mesh Hub surface. Do not propose a new identity, do not
substitute a direction from a generic palette library, do not introduce
tokens that are not listed here.

Two visual worlds live in these files and **must never blend**:

| World | Where | System |
|---|---|---|
| **Console chrome** | Everything outside a preview frame | Mesh Hub |
| **Member surface** | Everything inside `.fi-frame` | The bank's real Banno/Garden theme |

The boundary is architectural, not stylistic. In HTML it is enforced by
`.fi-frame { all: initial; }`, which severs every inherited property and every
Hub custom property. **No `var(--…)` from the console may appear past that
line**, and no Garden hex may appear before it.

---

## 1. Console chrome — Mesh Hub tokens

Source of truth:
`/Users/tranqy/projects/mesh/mesh-platform/apps/hub/ui/src/styles/globals.css`
(Tailwind v4 `@theme`). Copy values verbatim; never retype from memory.

### Light (`:root`)

```css
--radius:0.625rem;
--background:oklch(1 0 0);          --foreground:oklch(0.145 0 0);
--card:oklch(1 0 0);                --card-foreground:oklch(0.145 0 0);
--popover:oklch(1 0 0);             --popover-foreground:oklch(0.145 0 0);
--primary:oklch(0.205 0 0);         --primary-foreground:oklch(0.985 0 0);
--secondary:oklch(0.97 0 0);        --secondary-foreground:oklch(0.205 0 0);
--muted:oklch(0.97 0 0);            --muted-foreground:oklch(0.556 0 0);
--accent:oklch(0.97 0 0);           --accent-foreground:oklch(0.205 0 0);
--destructive:oklch(0.577 0.245 27.325);
--border:oklch(0.922 0 0);          --input:oklch(0.922 0 0);
--ring:oklch(0.708 0 0);
--sidebar:oklch(0.985 0 0);         --sidebar-foreground:oklch(0.145 0 0);
--sidebar-accent:oklch(0.97 0 0);   --sidebar-border:oklch(0.922 0 0);
```

### Dark (`.dark`)

```css
--background:oklch(0.145 0 0);      --foreground:oklch(0.985 0 0);
--card:oklch(0.205 0 0);            --card-foreground:oklch(0.985 0 0);
--popover:oklch(0.205 0 0);         --popover-foreground:oklch(0.985 0 0);
--primary:oklch(0.922 0 0);         --primary-foreground:oklch(0.205 0 0);
--secondary:oklch(0.269 0 0);       --secondary-foreground:oklch(0.985 0 0);
--muted:oklch(0.269 0 0);           --muted-foreground:oklch(0.708 0 0);
--accent:oklch(0.269 0 0);          --accent-foreground:oklch(0.985 0 0);
--destructive:oklch(0.704 0.191 22.216);
--border:oklch(1 0 0 / 14%);        --input:oklch(1 0 0 / 15%);
--ring:oklch(0.556 0 0);
--sidebar:oklch(0.205 0 0);         --sidebar-foreground:oklch(0.985 0 0);
--sidebar-accent:oklch(0.269 0 0);  --sidebar-border:oklch(1 0 0 / 12%);
```

**Dark mode is first-class, not an afterthought.** Every key screen ships in
both. Hub follows the OS via the `.dark` class; prototypes expose an explicit
toggle because a reviewer needs to flip modes on demand.

### Status colors — the one deliberate deviation

Hub's raw status tokens are `#22c55e` / `#f59e0b` / `#ef4444`. As **text on
`--card`** those fail 4.5:1. Prototypes therefore use the darkened ramp in
light mode and the lightened ramp in dark mode:

```css
:root { --success:#15803d; --warning:#b45309; --error:#dc2626; }
.dark { --success:#4ade80; --warning:#fbbf24; --error:#f87171; }
```

Raw Hub status values are still correct for **dots, fills, and borders** — the
substitution applies to text only. Record this any time it comes up in review;
it is a contrast fix, not a palette drift.

### Charts

Use the Hub `--chart-*` tokens, never ad-hoc colors:

```css
:root { --chart-1:oklch(0.646 0.222 41.116); --chart-2:oklch(0.6 0.118 184.704);
        --chart-3:oklch(0.398 0.07 227.392); --chart-4:oklch(0.828 0.189 84.429);
        --chart-5:oklch(0.769 0.188 70.08); }
.dark { --chart-1:oklch(0.488 0.243 264.376); --chart-2:oklch(0.696 0.17 162.48);
        --chart-3:oklch(0.769 0.188 70.08);   --chart-4:oklch(0.627 0.265 303.9); }
```

Filled encoding only — no outline-only series.

### Type and density

```css
--font-body: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
```

Hub is a `system-ui` product. **Do not import a display serif here** — the
generic web-prototype seed's serif/sans pairing does not apply to this project;
the design system wins over the seed. Mono carries numerics, correlation IDs,
timestamps, and the uppercase `.lbl` eyebrow (10px, `.09em` tracking).

Body 14px / 1.5. Page titles 21px. Sidebar 236px. Radius `0.625rem` base.
Density target: back-office calm, not dashboard density — this is a compliance
tool, and whitespace is part of the argument that it is safe.

### Components

Compose from the shadcn/ui set actually in use at
`/Users/tranqy/projects/mesh/mesh-platform/apps/hub/ui/src/components/ui/`
(button, card, table, tabs, dialog, sheet, dropdown-menu, badge, alert, select,
switch, calendar, command, skeleton, sonner, tooltip, progress). Do not invent
a parallel primitive where one exists. Shell geometry follows
`components/layout/` — `app-shell.tsx`, `sidebar.tsx`, `topbar.tsx`.

In a static HTML prototype these become plain classes, but the **geometry,
spacing, and states must match the real component**, so the mock reads as the
same product.

---

## 2. Member surface — Garden's real palette

Source of truth: `trabian-mesh-apps/memories/banno-fi-theming.md`. These are
**verified values pulled from the live institutions endpoint** (ARGB-stripped),
not brand guesses. The earlier hardcoded green `#0a7d6b` was wrong; Garden's
brand is teal.

```
accent / links / active   #23a1b2   (bodyTextThemeColor)
primary button            #56b7c4   (actionButtonColor), button text #ffffff
app bar / toolbar navy    #272d40   (toolbarColor) → the sidebar chrome
slate header              #515b65   (bodyHeaderBackgroundColor)
body text                 #515b65   (bodyTextPrimaryColor)
muted text                #abafb2   (bodyTextSecondaryColor)
dividers                  #e5e5e5   (dividerColor)
dark surface              #282e33   (controllerViewBackgroundColor)
page background           #f2f4f6
```

Rules inside the frame:

- **Card face never scrolls.** It is glanceable by JH rule. 392px wide;
  `overflow:hidden` is intentional, not a bug to fix.
- **Card anatomy: the title and the primary action button are Banno chrome,
  not plugin content.** Title, icon, and the primary action button (label +
  target) are static Banno People config that Banno renders *outside* the
  plugin iframe; only the region between them is the plugin. Mocks must draw
  this boundary honestly — the orchestrator prototype has a "show seams"
  toggle that annotates it. Because that chrome is static per registration,
  copy like "For you" / "View more" is chosen to work for every plugin the
  router can serve into the slot. (jackhenry.dev `architecture/userinterface`
  + `overview/configuration`.)
- **Expanded View** is a full-screen member surface: navy left rail, **no top
  bar** (Array/Experian parity). Grid is `186px 1fr`.
- **Logo variant pairs to background.** The sidebar is navy in both schemes, so
  the header always requests the `dark` (cream) wordmark:
  `<img src="/fi/logo?mode=dark">`. Served through a same-origin proxy — CSP
  blocks loading the FI origin directly from a plugin.
- **View-only is retired for the journey class (team decision 2026-08-31).**
  Campaigns and feature tiles stay render-only, but *journeys* (e.g.
  confirm-address) carry forms and commit writes to the core through governed
  Mesh ops — the mesh-platform `skip-a-payment` extension set the precedent.
  A journey's completion screen must show its receipt: the core op, "via Mesh
  governed op", a correlation ID, and the audit destination. As before, never
  claim Jack Henry mandates a stance in either direction
  (`memories/banno-view-only.md`).

---

## 3. Interaction states

Applies to both worlds. Foreground and background are defined **as a pair**;
contrast after a state change never drops below the default state.

- Hover: move background ±0.06–0.12 on the OKLch L channel, or shift border /
  shadow. Never fade the foreground toward `--muted-foreground`.
- Solid buttons that invert on hover swap **both** fg and bg in one rule.
- `:focus-visible` → `2px solid var(--ring)`, `2px` offset, on every focusable
  element. Inside `.fi-frame`, the ring is `#23a1b2` — the console's ring token
  cannot cross the boundary.
- Disabled is the only state permitted to reduce contrast.
- Text ≥ 4.5:1; large text and icons ≥ 3:1.

---

## 4. Vocabulary

**"Plugin"** everywhere in bank-facing UI — never "extension", never mixed.
(The router skill calls the code artifact an extension; that is internal.)
A **card** is the slot in Banno. A **rule** targets. A **release** is what
publishing produces.

---

## 5. Inspectability

Every region, heading, CTA, control, and repeated card carries
`data-od-id="kebab-case-id"` so comment mode can target it. Repeated items get
unique ids (`rule-r1`, `feature-card-speed`).
