---
name: liquid-glass-card-surfaces
classification: feature
success_metric: |
  `components/CardItem.js`, `components/CardDetailView.js`, and
  `components/Card3D.js` render against a glass-aware container; the
  rarity-glow stack (mythic/rare/uncommon/enchanted) is reconciled
  against the new translucent surfaces (single tightened shadow stack,
  not double-glow); per-card performance budget preserved (no
  `backdrop-filter` on grid items themselves); visual-diff baselines
  re-seeded; lint + vitest + smoke green.
skip: []
status: architecture-ratified-impl-queued
created: 2026-06-03
architecture_ratified: 2026-06-03
depends_on:
  - liquid-glass-design-tokens
umbrella: liquid-glass-redesign
---

# Convoy: liquid-glass-card-surfaces

Sub-convoy #5 of the `liquid-glass-redesign` epic. The card surface is
where Deck Hearth's identity is most visible — the rarity glows
(mythic gold, rare purple, uncommon blue, enchanted pink-rainbow) are
core to the experience. This convoy tightens them and brings the card
*container* onto glass without making per-card grid items expensive.

## Why

Cards are the most repeated visual element in the app: card grids in
`/cards`, `/my-cards`, `/collection/[id]`, `/deck/[id]`,
`/deck-builder`, scanner results. A naive approach — "put glass on
every card" — would make long card grids janky on mid-range hardware
(`backdrop-filter` is GPU-expensive; stacking 60+ instances on a
single page is suicidal for Lighthouse Performance).

The right shape is:

- **Card grid items** stay cheap: solid background, cheap shadow,
  rarity-glow recipe tightened from 3-layer to 2-layer.
- **Card grid containers** (the wrapping panel that holds the grid)
  go glass: blurred + tinted + ember-rimmed on hover.
- **Card detail view** (the dialog-like full-card surface) goes
  full glass.
- **Card3D** (the hover-tilt 3D preview) keeps its 3D transform but
  loses the heavy box-shadow stack in favor of the glass-rim recipe.

## Scope

### In scope

- `components/CardItem.js`:
  - Background stays solid (token: `--bg-secondary`).
  - Drop legacy `.fire-glow` / `.ember-glow` / `.card-mythic-glow`
    / `.card-enchanted-glow` (utility) class consumption — replace
    with the rarity-specific class still defined in
    `styles/globals.css` (those have the right rarity colors and
    stay; the generic `.fire-glow` / `.ember-glow` go).
  - Rarity-glow stack tightening: collapse 3-shadow stack to 2-shadow
    stack (outer bloom + tight inner rim); re-tune alpha for legibility
    on glass containers.
  - Hover state — replace `.card-side-panel`'s ad-hoc `backdrop-filter:
    blur(8px)` with a `<GlassSurface tint="high" rim="ember"
    elevation="ambient">` panel.
- `components/CardDetailView.js`:
  - Outer surface — `<GlassSurface tint="low" rim="subtle"
    elevation="pronounced">`.
  - If `CardDetailView` is rendered inside a route page (not a modal),
    it gets the full glass treatment; if it's also used as modal
    content (via `<Modal>` from #2), the modal already provides the
    glass panel — `CardDetailView` skips the outer surface in that case.
    Architect inspects and decides per usage.
- `components/Card3D.js`:
  - Keep 3D transform.
  - Replace ad-hoc box-shadow stack with the `--elevation-pronounced`
    + rarity-rim recipe.
  - Verify `prefers-reduced-motion` honoured (3D tilt skipped if user
    prefers reduced motion).
- `styles/globals.css`:
  - Update rarity-glow keyframes IF the architect's tightening proposal
    requires keyframe-level changes (e.g. swapping `mythic-sparkle`
    keyframe alpha). Otherwise leave keyframes untouched.
  - Drop `.fire-glow` + `.ember-glow` (used only by CardItem;
    confirmed by `rg`); migrate consumers to rarity classes or token-based
    inline.
- Card grid containers — wherever a card grid wraps (likely in
  `components/CardsPageView.js`, `components/CollectionPageView.js`,
  `components/DeckBuilderCardBrowser.js`, etc.) get `<GlassSurface>`
  treatment. Architect inventories.

### Out of scope

- Card grid layout / density (spacious / comfortable / compact toggle)
  — `.convoys/ship-readiness.md` § Role-design-system-auditor flagged
  this as a separate `<CardGrid>` extraction. Out of scope here; tracked
  as a P2 follow-up.
- Card data shape, ownership badge, rarity classification — orthogonal.
- Scanner card surfaces (`components/ScannedCardItem.js`,
  `components/CameraScannerView.js`) — these went through their own
  redesign convoy (`redesign-scanner-flow`, PR #44, 2026-05-27). Touch
  ONLY if they consume the legacy `.fire-glow` / `.ember-glow`
  utilities; otherwise leave to a downstream polish convoy.

## Roles invoked

1. `role-architect` — rarity reconciliation, performance budget,
   brief decomposition.
2. `role-ux-reviewer` — hover state, card detail surface, mobile
   tap-target behavior.
3. `role-design-system-auditor` — rarity-glow recipe sign-off.
4. `role-implementer` — multiple briefs.
5. Post-PR audit fleet.

## Architecture (ratified 2026-06-03 — implementation deferred)

**Per-card GPU contract (locked in `docs/DESIGN_TOKENS.md`):**
no `backdrop-filter` on `CardItem.js` (long card grids; GPU
budget). Glass goes on grid CONTAINERS + detail views only.

**Targeted scope when Brief 1 runs:**

1. **`CardItem.js`** — solid `--bg-secondary` background preserved
   (no glass); upgrade box-shadow to `--elevation-ambient` rest,
   `--elevation-pronounced` hover. Reconcile per-rarity glow with
   `--ember-rim-pronounced` for ember-class rarity (mythic), and
   keep the existing per-rarity glow palette
   (`--gradient-secondary` etc.) for non-ember rarities so the
   gameplay-visual identity is preserved.

2. **`CardDetailView.js`** — convert the hero outer wrapper at
   `<div style={{ backgroundColor: 'var(--bg-primary)' … }}>` to
   compose `--glass-surface-low` + `--glass-blur-mid` + rim-light;
   the existing inner gradient `linear-gradient(135deg, …)` stays
   for visual depth.

3. **`Card3D.js`** — **DEFERRED**. Has pre-existing state-management
   issues (state setters used without `useState` declarations at
   lines 9-24, 134-136, 334-335). Glass migration would mask the
   underlying bug. Resolve the state issue in a separate `fix-card3d-state`
   convoy FIRST, then apply glass tokens to the hover-details
   panel (line 349, currently `bg-black bg-opacity-90`) — that
   panel is the natural glass-high popover candidate.

4. **Grid containers** — `pages/cards.js`, `pages/my-cards.js`,
   `pages/dashboard.js`, `components/CollectionsPageView.js`,
   `components/DeckBuilderCardBrowser.js` — wrap the outer card-grid
   panel in `<GlassSurface tint="low" elevation="ambient" rim="subtle">`
   so the cards float on a tinted backdrop. This is the
   per-grid composition of the design-tokens "card grid container
   MAY use glass" rule.

**Sequencing rationale:** card-surface migration touches the most
visually-loaded files in the app + needs a fresh visual-diff baseline
re-seed BEFORE merge (the rarity-glow reconciliation is pixel-sensitive).
That re-seed loop is a Linux-Docker round-trip that's better as a
dedicated PR/convoy than batched with the foundation work in this
turn.

## Todos

- [ ] Architect: rarity reconciliation + performance budget + briefs
- [ ] UX reviewer: hover / detail / mobile audit
- [ ] Design-system auditor: rarity-glow recipe
- [ ] Brief 1 — `CardItem` + rarity tightening
- [ ] Brief 2 — `CardDetailView`
- [ ] Brief 3 — `Card3D`
- [ ] Brief 4 — grid containers (cluster across pages)
- [ ] Post-PR audit per brief
- [ ] Re-seed Linux visual baselines

## Decisions to ratify (architect)

1. **Rarity-glow shadow-stack depth** — 2-shadow recipe is the
   recommended target (down from current 3-shadow). Architect ratifies
   the exact px + alpha values per rarity tier.
2. **`Card3D` reduced-motion behavior** — full disable vs muted tilt.
   Recommended: full disable (the 3D effect is decorative).
3. **`CardDetailView` modal vs route usage** — architect grep-inventories
   call sites and decides whether the component renders its own glass
   shell or delegates to the parent `<Modal>` from #2.
4. **Card grid container per-page partition** — list every page that
   wraps a card grid and decide which get glass containers in this
   convoy vs deferred to the `<CardGrid>` extraction follow-up.
5. **Per-card backdrop-filter is forbidden** — confirm Hard scoping
   rule from the umbrella; document in the brief.
6. **Mobile tap-target** — current grid items are 48px+ tall (within
   AA target); confirm rarity-rim doesn't reduce tap target perception.

## Acceptance criteria

1. `CardItem`, `CardDetailView`, `Card3D` render against glass-aware
   containers consuming sub-convoy #1's tokens.
2. Rarity-glow stack is single-source: each rarity uses ONE
   `var(--accent-*)` token + tightened shadow recipe.
3. Per-card grid item has NO `backdrop-filter` (perf budget).
4. `.fire-glow` and `.ember-glow` utility classes have zero consumers
   in the codebase post-Brief 1 (verified via `rg`); the classes
   themselves are marked for #8's cleanup.
5. Lint + vitest + smoke green.
6. Linux visual-diff baselines re-seeded.
7. Lighthouse Performance on `pages/cards.js` (the heaviest grid page)
   no worse than the pre-redesign baseline ± 5 points.

## CI impact

| Workflow / job | Behavior |
| --- | --- |
| `preview-smoke.yml` | Fires. |
| `visual-diff.yml` | **Fires + LOUD** — card grids change app-wide. Re-seed baselines per brief. |
| `lint` | Fires. |
| `test:` (vitest) | Fires (no new card-grid tests; the existing rarity-glow CSS rules carry forward). |
| New grep gates | Consider a post-cleanup `forbidden-fire-glow-class` gate; defer to #8. |
| Lighthouse | Run on `pages/cards.js` pre + post; require ± 5 points Performance, ± 0 Accessibility. |

## Known constraints

- **Per-card `backdrop-filter` is FORBIDDEN.** GPU budget. Document
  in the brief; reviewer fails the PR if any grid item uses it.
- **`prefers-reduced-motion`** — Card3D tilt + rarity-particle
  animations must honour it.
- **Theme tokens only** — no hex.
- **Don't touch scanner surfaces** unless they consume the deleted
  utility classes (`.fire-glow` / `.ember-glow`).

## Multitask dispatch

Pre-ratification proposal:

```yaml
slice_dependencies:
  - brief: 1
    depends_on: []
    files:
      - components/CardItem.js
      - styles/globals.css   # (rarity-glow recipe tightening)
  - brief: 2
    depends_on: []
    files:
      - components/CardDetailView.js
  - brief: 3
    depends_on: []
    files:
      - components/Card3D.js
  - brief: 4
    depends_on: [1]
    files:
      - components/CardsPageView.js
      - components/CollectionPageView.js
      - components/DeckBuilderCardBrowser.js
      # ... (architect completes inventory)
```

Briefs 2 + 3 are file-disjoint and can run in parallel with 1.
Brief 4 depends on Brief 1's grid item shape.

Post-PR audit per brief:

```
/multitask role-reviewer + role-design-system-auditor + role-a11y-auditor
```

## Out of scope follow-ups

- **`<CardGrid>` primitive extraction + density toggle** — surfaced by
  `.convoys/ship-readiness.md` § Role-design-system-auditor. P2 polish;
  natural successor to this convoy.
- **Skeleton loaders for card grids** — surfaced by
  `.convoys/ship-readiness.md` § Role-ux-reviewer. Orthogonal scope.
- **`<CardSidePanel>` primitive** — the hover panel pattern in
  `CardItem`. Consider once #4 ships and the popover surface is
  proven out.
