deckhearth/.convoys/liquid-glass-card-surfaces.md

273 lines
11 KiB
Markdown
Raw Permalink Normal View History

feat(design-system): Liquid Glass redesign portfolio — foundation + primitives + Layout (#95) * feat(design-system): Liquid Glass redesign portfolio — foundation + primitive kit + Layout shell Operator-requested epic to migrate the UI from the current "warm panel + side-highlight + heavy gradient" visual language to a Liquid Glass aesthetic that retains Deck Hearth's fireplace warmth as accent / gradient / motion (not as panel fill). This squash carries the full 8-convoy portfolio drive-through; 5 sub-convoys reach merged state, 3 land architecture-only and queue impl for follow-up turns gated on dedicated visual-diff baseline re-seeds. Sub-convoy #1 (liquid-glass-design-tokens) — MERGED. 29 CSS custom properties: glass-surface {low,mid,high} alpha ramp + blur/saturate + rim-light (inner/outer) + ember-rim (subtle/pronounced; RGB triple) + 3-tier elevation + modal-scrim, both light + dark themes with eye-perception-corrected alphas; @supports not (backdrop-filter) fallback collapsing surfaces toward solid (preserves ramp ordering). Authored docs/DESIGN_TOKENS.md (270 LOC reference with WCAG AA contrast tables, composite recipes, when-NOT-to-use-glass guidance, per-card grid GPU budget). AGENTS.md gains a § Visual language section as the new agent-contract surface. Sub-convoy #2 (liquid-glass-modal-and-surface-primitive) — Brief 1 MERGED. Adds <GlassSurface> (forwardRef composable; tint / rim / elevation / blur props) and <Modal> primitive (focus-trap, ESC + backdrop close, body-scroll lock, ARIA dialog shape, built-in close button) consuming the token surface. lib/use-focus-trap.js — homegrown hook (~60 LOC, no dep). 10 new vitest cases covering open/close render, ARIA, ESC + closeOnEsc gate, backdrop gate, hideCloseButton, body-scroll lock + restore. 4 reference modal migrations as proof-of-pattern: ShareModal, CollectionDeleteModal, CollectionsCreateModal, CardDetailQuantityModal. Brief 2 (11 remaining modals) queued; CI grandfather list locks the pattern in. Sub-convoy #3 (liquid-glass-form-primitives) — Brief 1 MERGED. Adds <Button> (primary ember-gradient with ember-rim-pronounced; secondary glass-mid; danger; ghost), <Input> (glass-high with ember focus ring + label + helperText + error + aria-invalid + describedby wiring + leadingIcon decorative + trailingAction interactive), <SearchBar> (composes Input with leading search icon + conditional clear button). 10 new vitest cases. pages/login.js + pages/signup.js fully migrated — 2 submit buttons + 7 inputs total; existing test/pages/login.test.js assertion ("Sign in to Deck Hearth" button text) preserved. Brief 2 (profile/settings + deck-builder + scanner + card-editor + collection-cluster modal forms) queued. Sub-convoy #4 (liquid-glass-layout-shell) — MERGED. 6 shell surfaces glass-migrated: desktop sidebar rail (glass-mid + rim + ambient elevation), mobile drawer (glass-mid + pronounced elevation), mobile overlay scrim (modal-scrim + blur-high — visually consistent with <Modal>), search header strip (glass-mid + rim), UserProfileDropdown popover (glass-high + ember-rim-subtle + ambient — matches popover recipe), MobileNavigation bottom bar (replaces legacy mobile-nav-backdrop class). The 5 Layout regression-lock tests (logged-out CTA, no maintainer-email default, "Sign in" link present, supplied email renders, no "Guest" placeholder) all still pass — every edit preserved the documented contract. Sub-convoy #5 (liquid-glass-card-surfaces) — ARCHITECTURE RATIFIED; implementation queued. Pixel-sensitive (rarity-glow reconciliation) so wants a dedicated visual-diff baseline re-seed PR. Pre-blocked on a fix-card3d-state convoy (Card3D has pre-existing state-management bug: state setters used without useState declarations). Sub-convoy #6 (liquid-glass-public-and-auth) — ARCHITECTURE RATIFIED; partial impl shipped via #3 (login + signup form primitives migrated). Landing page editorial + public collection/deck views + login/signup outer-wrapper sweep queued. Sub-convoy #7 (motion-system-pass) — MERGED. 8 motion tokens (5-tier duration taxonomy: instant/quick/default/slow/deliberate; 3 easings: ease-out default, spring for delight, linear for progress) added to the token surface. prefers-reduced-motion upgraded from a narrow nav-item rule to a site-wide universal sweep collapsing animation-duration + transition-duration to 0.01ms (preserves end states, no flicker); .motion-essential class is the opt-in escape hatch for state-meaningful animation (loading spinners, scan reticles). Authored docs/MOTION_SYSTEM.md with WCAG SC 2.3.3 contract, composition recipes, audit of existing keyframes, and adding-new-animation checklist. Sub-convoy #8 (cleanup-legacy-design-css) — Brief 1 MERGED. Two new CI jobs in .github/workflows/ci.yml: (1) forbidden-modal-shell-without-primitive (BLOCKING) — fails build if any new file outside the 9 grandfathered legacy modals uses the fixed inset-0 bg-black bg-opacity- shell pattern; locks in the discipline that every modal must compose <Modal> from components/ui. (2) forbidden-deprecated-color-aliases (WARN-only) — audits pre-Deck-Hearth blue/purple/pink aliases (gradient-text-purple/pink/blue, glow-purple/pink/blue, gradient-bg-purple/blue/pink) as a baseline; graduates to FAIL after #8 Brief 2 sweeps consumers. .cursor/rules/ui-and-theming.mdc updated to document the components/ui/ primitive kit and point at the new canonical reference modals. Verification: lint 0 errors (2 pre-existing warnings in unrelated CardEditorForm.js + CollectionsPageView.js — out of scope); vitest 104/104 passing (was 84 — +20 from new primitive tests: 10 Modal + 10 ui-primitives); ci.yml valid YAML; both new CI gates locally exercised and pass on the current tree. Operator follow-ups documented in .convoys/ship-readiness.md § "Design-system redesign portfolio": - Re-seed Linux visual-diff baselines via Docker workflow (AGENTS.md § 6) after this merges. - preview-smoke.yml runs against the preview; auth + scanner specs touch the migrated surfaces. - Vercel promote to production once smoke + visual gates pass. - Queued follow-up implementer turns: #2 Brief 2 (11 modals), #3 Brief 2 (other forms), #5 Brief 1 (cards, after fix-card3d-state), #6 Brief 1 (landing editorial), #8 Brief 2 (legacy CSS deletion + WARN→FAIL graduation). The user-visible promise — "modern fireplace aesthetic; modals blur the page behind them; reusable components" — is delivered TODAY by the merged work. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(use-focus-trap): preserve named useFocusTrap export for ScannerPageView The portfolio squash inadvertently overwrote the pre-existing lib/use-focus-trap.js (named `export function useFocusTrap(active)` returning a ref — used by ScannerPageView, line 21) with a default- only export shaped for the new `<Modal>` primitive. Vercel build failed: "Export useFocusTrap doesn't exist in target module". Fix: the file now exports BOTH — - `useFocusTrap(active)` (named, original) — returns a ref; pre-Liquid-Glass call sites (ScannerPageView) keep working. - `useFocusTrapContainer({ active, containerRef, ... })` (default, new) — takes a caller-owned ref so panel refs can forward through forwardRef chains (Modal.js consumes this shape). Both hooks are commented to document which to use when. Modal.js imports default already, so no change needed there. Verified: npm run build passes (was failing in CI); lint 0 errors; vitest 104/104 still green. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-03 21:12:33 -04:00
---
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.