# Payfacto Tip Portal — Branding Guide

Source of truth: Figma file **Payfacto-ShadCN** (`vAO7ZdIapEbKm3podqOOOq`), pulled directly via the
Figma MCP — every value below is either a variable actually bound to a real component (`Button`,
`Alert`, `Badge`, `Card`, `Input`, `Header`), or a pixel value sampled directly from a rendered
screenshot of that component. Nothing here is inferred from filename frequency or guessed from a
raw hex dump — see "How this was verified" at the bottom.

This supersedes `DESIGN.md` and `Payfacto-ShadCN-DESIGN.md` (both auto-extracted, both had noise or
errors — see notes at the end).

## 1. Color

### Core semantic tokens (bound in real components)

| Token | Hex | Role |
|---|---|---|
| `primary` | `#ba955c` | Brand gold/tan — primary button fill |
| `foreground` | `#171717` | Default body/heading text |
| `background` | `#ffffff` | Page background |
| `card` / `popover` | `#ffffff` | Elevated surfaces |
| `card-foreground` / `popover-foreground` | `#171717` | Text on elevated surfaces |
| `muted` | `#f5f5f5` | Low-emphasis fill (subtle panels) |
| `muted-foreground` | `#737373` | Low-emphasis text (helper text, captions) |
| `accent` | `#f8f5ee` | Cream — secondary button / alert fill |
| `Brim/900` | `#754d33` | Dark brown — text on cream, secondary/outline button text |
| `border` / `input` | `#d4d4d4` | Dividers, input borders |
| `destructive` | `#dc2626` | Error/danger fill |
| `destructive-hover` | `#b91c1c` | Error hover |
| `destructive-foreground` | `#ffffff` | Text on destructive fill |
| `secondary` | `#f5f5f5` | Defined but not used by the Secondary button (see §5) |
| `secondary-foreground` | `#525252` | Defined but not used by the Secondary button (see §5) |
| `base/black` | `#262626` | Actual text color on the primary gold button (see §2) |
| `base/white` | `#ffffff` | — |

### Verified contrast (pixel-sampled from rendered components, WCAG AA needs 4.5:1)

| Pairing | Ratio | Result |
|---|---|---|
| Primary button: `#262626` text on `#ba955c` fill | 5.43:1 | ✅ Pass |
| Primary button hover: `#ffffff` text on `#262626` fill | 15.13:1 | ✅ Pass |
| Destructive button: `#ffffff` text on `#dc2626` fill | 4.83:1 | ✅ Pass |
| Secondary button: `#754d33` text on `#f8f5ee` fill | 6.73:1 | ✅ Pass |
| Body text: `#171717` on `#ffffff` | 17.93:1 | ✅ Pass |
| Muted text: `#737373` on `#ffffff` | 4.74:1 | ✅ Pass (barely — don't use below 16px or at reduced weight) |

Every real, rendered pairing in this file passes WCAG AA. (An earlier auto-extraction implied the
primary button uses white text on the gold fill — that combination fails at 2.78:1. It doesn't
matter: the actual button, verified pixel-by-pixel, uses `#262626`, not white. Ignore any doc that
says otherwise.)

### Flagged for design review, not yet resolved
- `sky/500` (`#0ea5e9`) appears once, bound inside the `Card` component. Doesn't match anything else
  in the palette — confirm with design whether this is intentional (e.g. an info/link accent) or a
  leftover default Tailwind color that should be replaced.
- `stone/200` (`#e7e5e4`) appears once, bound inside `Header`. Likely a divider — confirm it's meant
  to differ from `border` (`#d4d4d4`).

## 2. Buttons — verified per-variant (pixel-sampled)

| Variant | Fill | Border | Text |
|---|---|---|---|
| Primary | `#ba955c` | none | `#262626` |
| Primary (hover) | `#262626` | none | `#ffffff` |
| Secondary | `#f8f5ee` | none | `#754d33` |
| Outline | `#ffffff` | `#ba955c` | `#754d33` |
| Ghost | transparent | none | `#171717` |
| Destructive | `#dc2626` | none | `#ffffff` |
| Destructive (hover) | `#b91c1c` | none | `#ffffff` |
| Disabled | ~50% opacity of the variant's normal state, composited over white | — | — |

Radius: `6px` (`border-radius/md`) on all standard buttons.

## 3. Typography

Font family actually bound in every sampled component: **Source Sans 3**. (`Inter` did not appear
bound in any working component — if it's meant to be used somewhere, get an explicit component
reference from design before adopting it.)

Named styles actually used (not the full weight × size matrix — this is the real, applied subset):

| Style | Size / Line-height | Weight |
|---|---|---|
| `text-sm/normal` | 14px / 20px | 400 |
| `text-sm/medium` | 14px / 20px | 500 |
| `text-base/normal` | 16px / 24px | 400 |
| `text-base/medium` | 16px / 24px | 500 |
| `text-base/semibold` | 16px / 24px | 600 |
| `text-lg/normal` | 18px / 28px | 400 |
| `text-lg/semibold` | 18px / 28px | 600 |
| `text-xl/normal` | 20px / 28px | 400 |
| `text-2xl/semibold` | 24px / 32px | 600 |
| `text-3xl/semibold` | 30px / 36px | 600 |

## 4. Spacing, Radius, Shadow

**Spacing** (px, confirmed bound): 2, 4, 6, 8, 10, 12, 16, 20, 24, 32

**Radius** (confirmed bound): `md` = 6px (default for buttons/inputs/cards), `lg` = 8px, `3xl` = 24px, `full` = 9999px

**Shadow** (confirmed bound):
- `shadow/sm`: `0px 1px 2px rgba(0,0,0,0.05)`
- `shadow/base`: `0px 1px 2px rgba(0,0,0,0.06), 0px 1px 3px rgba(0,0,0,0.1)`
- `shadow/xl`: `0px 10px 10px rgba(0,0,0,0.04), 0px 20px 25px rgba(0,0,0,0.1)`

**Disabled opacity**: 50%

## 5. Notes / inconsistencies found in the source file

- The **Secondary button** doesn't consume the `secondary`/`secondary-foreground` variables at all —
  it's bound to `accent`/`Brim-900` instead. `secondary` (`#f5f5f5`) and `secondary-foreground`
  (`#525252`) exist and are used elsewhere (e.g. `Badge`), just not on the button with that name.
  Not a bug, just worth knowing the names don't map 1:1 across components.
- This file is a full shadcn/ui component kit (Toast, Sheet, Table, Tabs, Sonner, etc.) — most of it
  is React/Tailwind component scaffolding, not applicable to this app's CodeIgniter 3 + Bootstrap 3
  stack. Only the tokens in this doc (color, type, spacing, radius, shadow) are portable; the
  components themselves need to be rebuilt against existing Bootstrap primitives, not copied.

## 6. Mapping to this app's CSS

The actual CSS lives outside this repo, on `static.brim.ca/maitred/tipportal/css/custom.css` — see
the standing note about that CDN not being in git. When that file is available to edit, the mapping is:

- `.global_btn` base rule (currently teal) → replace with Primary above (`#ba955c` fill / `#262626` text)
- Any `.blue_Btn` / secondary CTA → Secondary above (`#f8f5ee` fill / `#754d33` text)
- Body text color → `#171717`
- Helper/muted text → `#737373`
- Default border-radius on buttons/inputs/cards → `6px`
- Font stack → Source Sans 3 (with the existing system fallback stack kept behind it)

## How this was verified

Every color/typography/spacing value above came from `get_variable_defs` called on real component
nodes (`Button`, `Alert`, `Badge`, `Card`, `Input`, `Header`) via the Figma MCP — not from scanning
every node in the file, which is what produced the noisy 236-color dump in earlier drafts. The
button color pairings were additionally cross-checked by downloading the rendered PNG of each
button variant and sampling actual pixel values, because the variable *names* (e.g.
`primary-foreground: #ffffff`) turned out not to match what's actually painted on the primary
button (`#262626`) — trust the pixels over the variable name when the two disagree.
