# 1. BaseCard owns card geometry, locked at 12px / 0.5px

Date: 2026-09-17

## Status

Accepted

## Context

The card surface — white background, rounded corners, thin border, soft shadow — was
re-implemented independently across the product, and the implementations did not agree. At the time
of this decision the codebase carried **three different card geometries**:

| Radius | Border | Where |
|---|---|---|
| 8px (`--radius-lg`) | 1px `--color-border` | Estimate page cards (`public/assets/css/custom/estimates/estimate-modern.css`), reports index tiles (`public/assets/css/custom/reports/index.css`), `BaseAlert` |
| 10px (`--grf-radius`) | 1px `#e1e8ec` | Reports filter card (`resources/js/components/GenericReportFilterCard.vue`) |
| 12px (`--ard-radius-lg`) | 0.5px `rgba(26, 43, 54, 0.12)` | Reports V2 report surfaces (`ard-hero--surface`), redeclared locally in at least six components under `Modules/Reports/Resources/assets/js/components/` |

The written standard was 8px: VOM Style Guide v1.1 §5 assigned cards `--radius-lg`, and the estimate
page followed it precisely. The Reports V2 work shipped 12px with a hairline border and its own
`--ard-*` variable set, never reconciled with the guide. The reports filter card added a third
value, and a banner teal of `#0d8a7a` that is not the brand teal (`#25B395`) and is not in
`resources/sass/_tokens.scss` at all.

Beyond the raw values, the duplication itself was the cost: `ard-hero--surface` is copy-pasted
scoped CSS in roughly ten Vue components, so any change to the card surface meant ten edits, and
defects like stacked shadows on nested cards had to be cancelled per consumer
(`asrt-table-card--nested`, `ssrt-table-card--nested`).

## Decision

A single `BaseCard` component (`resources/js/components/base/BaseCard.vue`) owns the card surface.
It is registered globally in `resources/js/app.js`, so it is usable from Vue SFCs and from Blade
views alike — all Blade page content renders inside the `#app` Vue root.

Its geometry is **the reports geometry**: `12px` radius via a new `--radius-2xl` token, and a
hairline `0.5px` border. Internal rules — the header/body separator and the footer edge — are
`0.5px` as well, so no line on a card is heavier than its border.

That geometry is **locked**. `BaseCard` exposes no props, and no CSS custom properties, for radius,
padding, border, or shadow. Variation is expressed only through named, meaningful axes:
`header-variant`, `body-padding`, `elevation`, `interactive`, `collapsible`, `loading`.

`--radius-2xl` was added as a new token rather than redefining `--radius-lg` to 12px, because
`estimate-modern.css` and the reports index stylesheet consume `var(--radius-lg)` directly and
redefining it would have changed the estimate page immediately, outside the scope of the task that
introduced `BaseCard`. §5 of the guide now assigns cards `--radius-2xl` and leaves `--radius-lg`
(8px) to panels, modals, and filter areas.

Brand teal (`--color-primary`, `#25B395`) remains the only banner colour. The reports' `#0d8a7a`
was not adopted.

## Consequences

- The written standard changed: cards are 12px, not the 8px that guide v1.1 specified. The style
  guide was bumped to **v1.2** to record this, along with the previously undocumented card regions
  (footer, nested/flat elevation, loading overlay, collapsible header).
- The estimate page's cards (8px, 1px) and the reports filter card (10px) are now the outliers.
  They change appearance when they adopt `BaseCard` — a deliberate, deferred convergence, not a
  regression.
- Reports lose `#0d8a7a` and their local `--ard-*` variables on adoption, and roughly ten copies of
  `ard-hero--surface` CSS are deleted.
- Two differences from the reports surface were kept intentionally: the border colour stays
  `--color-border` (`#E2E8F0`) rather than the reports' warmer `rgba(26, 43, 54, 0.12)`, and cards
  retain `--shadow-sm` per §6 where `ard-hero--surface` has no shadow at all. Both are revisitable;
  neither was changed silently.
- `0.5px` borders are sub-pixel. On 1× displays browsers round them to a hairline or, in some
  engines, drop them. Reports already ship this, so the behaviour matches production rather than
  introducing new risk.
- **Adding a radius, padding, or shadow prop to `BaseCard` reverses this decision.** The absence of
  those props is the decision, not an oversight. A screen that appears to need one is evidence
  either that the guide should change for every card, or that the element is not a card.

## Alternatives considered

- **Keep the guide-exact 8px / 1px geometry** and treat the reports' 12px as drift to correct. It
  preserved the written standard and required no token change, but discarded the lighter surface of
  the newest and most heavily designed screens.
- **Expose CSS custom properties** (`--base-card-radius`, `--base-card-pad`) on the component root
  as an escape hatch. Consumers could deviate without forking, which is precisely how the three
  competing geometries arose in the first place.
- **Expose enum props** for radius, border, and shadow so reports could keep their look while
  estimates kept theirs. This encodes the disagreement into the shared component instead of
  resolving it.
