# Cards

> All cards: 24px radius, 24px inset, --surface on --canvas, 1px --border, no resting shadow. Specs for 10 React components: BalanceCard, KpiCard, AccountCard, PaymentMethodCard and 6 more.

Source: https://lopifi.com/docs/catalog/02-cards
Part of: Lopifi — US GAAP books on autopilot, reviewed by a CPA (https://lopifi.com)

---

# Cards

All cards: 24px radius, 24px inset, `--surface` on `--canvas`, 1px `--border`, **no resting shadow**.

---

## 14. `BalanceCard`

> The hero. One trusted number. This is the component a user opens the product to see.

**Layer 1:** radius 24 · inset 24 · min-width 280 · height auto
**Layer 2:**
```
┌─ 24 ────────────────────────────────┐
│  [eyebrow]  ·  [account mark]        │  ← row, space-between
│   8                                  │
│  [figure]                            │
│   8                                  │
│  [delta]  ·  [as-of]                 │
│  16                                  │
│  ├─ full-bleed sparkline ──────────┤ │  ← optional, bleeds to 3 edges
└──────────────────────────────────────┘
```
**Layer 3:** eyebrow (1 line, truncate) · figure (never truncates) · delta (1 line) · media (`aspect-[4/1]`, full-bleed) · actions (max 2)
**Layer 4:**

| Slot | Token | Weight | Color |
|---|---|---|---|
| eyebrow | `label` 13/16 | 500 | `--text-secondary` |
| figure | `figure-hero` 40/48 | 500 | `--text-primary` |
| delta | `caption` 12/16 | 400 | semantic |

**3 sizes, 2 weights ✓**

**Available vs. current.** A bank balance has two values and they differ. Show **available** as the figure; current as a secondary line when they diverge. Conflating them is the most consequential copy error in consumer banking UI.

**Motion:** none at rest. Value flash when live. Sparkline draws once. Interactive variant gets press `scale(0.97)` and a 1px hover lift.

**Responsive:** figure steps 40 → 28 below a 280px container. Sparkline drops below 240px.

**Edge cases:** negative balance (overdraft — parens *and* a `--negative` figure, plus an explicit "Overdrawn" pill) · redacted · stale (dim to 0.7 + `StaleIndicator`) · very long account name.

---

## 15. `KpiCard`

Label · figure · delta · optional sparkline. The dashboard atom.

- **Baselines align across a `KpiRow`.** Use `grid` with `auto-rows-fr` and a shared figure row, or two cards with different label lengths will have their figures at different heights — the most visible alignment failure on any dashboard.
- **Display family forbidden** here. Figures align across the row.
- `polarity` required, no default. Forcing the decision is the point.
- The comparison basis is always shown: `vs. Aug`, never a bare `+5.3%`.

**Loading:** skeleton with the figure block at the exact figure width, or the card jumps when data lands.

---

## 16. `AccountCard`
Bank account: institution mark, masked number, type, available and current balances, sync status. Tapping opens the transaction list filtered to it.

Handles **disconnected** and **reauth-required** states prominently — a stale bank connection silently showing an old balance is a real financial hazard. Stale accounts dim and surface a "Reconnect" action.

---

## 17. `PaymentMethodCard`

> The physical object. Two of the four sanctioned delight moments — the flip and the tilt.

**Geometry, from ISO/IEC 7810 ID-1:**
```
aspect-ratio: 1.586          (85.60 × 53.98 mm)
border-radius: 3.7% / 5.9%     (3.18mm — 3.7% of width, 5.9% of height)
```
Real proportions matter — a card at 16:9 with a 24px radius reads as a rectangle, not as a card.

**Face:** flat, single tint from the issuer color, network mark bottom-right, masked PAN in `tnum` with `ss02` (so a `0` isn't an `O`), holder name in `label` uppercase, expiry `MM/YY`.

**Motion:**
- **Flip to reveal CVV** — `rotateY(0→180deg)`, `spring.playful`, real 3D with `preserve-3d` + `backface-visibility: hidden`. Not a crossfade. [Spec](../motion/03-catalog.md#card-flip).
- **Pointer tilt** — ±6° from pointer position via `spring.track`, plus a specular sheen at `opacity 0→0.12`. Desktop only, gated behind `@media (hover: hover) and (pointer: fine)`. Springs home on leave.
- Reduced motion: instant swap, no tilt.

The tilt is decorative and it's licensed **only because the card is decorative**. The same effect on a `KpiCard` is a review failure.

**Security:** CVV and full PAN are never in the DOM until revealed, never in analytics, never in a screenshot-able state without an explicit user action.

---

## 18. `RunwayCard`
Months of runway, current cash, monthly burn, projected zero date. A burn line with a projection segment rendered **dashed**, because a projection is not a fact.

`polarity="inverted"` on burn. Runway under 6 months triggers `--warning`; under 3, `--negative` — and the threshold is configurable, because it's a business judgment, not a universal truth.

## 19. `InvoiceCard`
Number, customer, amount, due date, aging state (current / 1-30 / 31-60 / 61-90 / 90+). Overdue is `--negative` **with the day count**, never color alone. Partial payments show paid vs. outstanding.

## 20. `SubscriptionCard`
Vendor mark, amount, cadence, next charge, annualized cost. **Annualized is the useful number** — `$49/mo` is `$588/yr`, and showing it is what makes a spend review actionable.

## 21. `AnomalyCard`
A flagged deviation, with the comparison that triggered it and one clear action (approve / flag / create rule). Never a bare alert — an anomaly without its baseline is noise.

## 22. `GoalCard`
Target vs. actual with a progress rule (a rule line, not a rounded progress bar — consistent with the statement grammar).

## 23. `StatCard`
The minimal unit: label + value. 16px inset, no delta, no media. For dense grids where a `KpiCard` is too much.
