deckhearth/.convoys/motion-system-pass.md

208 lines
8.5 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: motion-system-pass
classification: feature
success_metric: |
The 12+ ad-hoc keyframe animations in `styles/globals.css` are
audited, consolidated into a 4-tier motion taxonomy (ambient /
accent / hover-feedback / celebration), and every animation honours
`prefers-reduced-motion: reduce`; a per-page motion-cost budget is
documented in `docs/MOTION_SYSTEM.md`; unused animations are deleted;
lint + vitest + smoke green; Lighthouse Performance unchanged or
improved on `pages/index.js` and `pages/cards.js`.
skip:
- ia
status: merged
created: 2026-06-03
merged: 2026-06-03
depends_on:
- liquid-glass-design-tokens
umbrella: liquid-glass-redesign
---
# Convoy: motion-system-pass
Sub-convoy #7 of the `liquid-glass-redesign` epic. Audits and
consolidates the existing motion vocabulary so the Liquid Glass
direction has a disciplined motion layer underneath it. Can run in
parallel with sub-convoys #2 through #6 after #1 merges.
## Why
`styles/globals.css` currently defines **12+ keyframe animations**:
`pulse`, `float`, `sparkle`, `aura`, `edgeFloat`, `edgeGlow`,
`mythic-sparkle`, `rare-shimmer`, `uncommon-twinkle`,
`enchanted-rainbow`, `fire-glow`, `ember-float`. Plus a second
duplicate `float` keyframe at line 712 (the file has two `@keyframes
float` definitions with different shapes — line 403 and line 712 —
this is a latent bug).
These were added incrementally without a guiding taxonomy. Some are
unused (architect to inventory). Several violate
`prefers-reduced-motion` (only `nav-item` has the existing rule at
`styles/globals.css` lines 261266 — every other animation runs
regardless). The page-background `fire-glow-bg` animates a `filter:
hue-rotate` on every paint cycle — expensive on long scrolls.
Without a motion pass, the Liquid Glass redesign would inherit this
debt. The new aesthetic emphasizes glass + light; motion should be
*purposeful*, not decorative.
## Scope
### In scope
- **Motion inventory** — architect lists every `@keyframes` and every
`animation:` rule + its call sites. Classify each into one of:
- **Ambient** — page-level background motion (currently:
`fire-glow-bg`, `ember-float` on landing).
- **Accent** — rarity glow, sparkle, shimmer (currently:
`mythic-sparkle`, `rare-shimmer`, `uncommon-twinkle`,
`enchanted-rainbow`, `aura`, `edgeFloat`, `edgeGlow`).
- **Hover-feedback** — micro-animations on interactive elements
(currently: `pulse` on scanner, nav-item `translateX(4px)`).
- **Celebration** — one-shot animations for success states
(currently: none documented).
- **Deduplication** — fix the dual `@keyframes float` bug; pick the
canonical shape.
- **Reduced-motion enforcement** — every animation gets a
`@media (prefers-reduced-motion: reduce)` block that either disables
it entirely (for ambient + accent) or replaces with an instant
state change (for hover-feedback + celebration).
- **Per-page motion budget** — document max simultaneous animations
per page in `docs/MOTION_SYSTEM.md`. Recommended:
- Landing — 1 ambient + 1 accent.
- Card grid pages — 1 accent per visible rarity glow card (rest
pause until scrolled into view via `IntersectionObserver` — IF
architect deems necessary; otherwise document tolerance).
- Auth pages — 0 ambient, 0 accent.
- Modals — 1 enter / 1 exit transition only.
- **Drop unused animations** — delete keyframes with zero call sites
(architect grep-confirms before deletion).
- **Drop `fire-glow-bg`** — per umbrella § Open question #5; operator
default: drop. Localize `ember-float` to landing hero only.
- `docs/MOTION_SYSTEM.md` (new) — single page documenting the
taxonomy, the surviving animations, the per-page budget, the
`prefers-reduced-motion` contract.
### Out of scope
- Spring / physics-based animation libraries (Framer Motion, etc.)
— orthogonal architectural decision; out of scope here.
- 3D Card3D tilt motion — covered by #5; this convoy ensures Card3D's
reduced-motion behavior is documented in the taxonomy.
- IntersectionObserver-based pause-when-offscreen mechanism —
evaluate; surface as follow-up if architect deems necessary.
## Roles invoked
1. `role-architect` — motion inventory + taxonomy proposal.
2. `role-design-system-auditor` — taxonomy sign-off.
3. `role-a11y-auditor` — reduced-motion contract review.
4. `role-implementer` — single brief (CSS only; small surface).
5. `role-doc-writer``docs/MOTION_SYSTEM.md` review.
## Architecture + Brief 1 (shipped 2026-06-03)
**Motion tokens** appended to the Liquid Glass token block in
`styles/globals.css` (5 durations + 3 easings, theme-independent):
- `--motion-duration-instant` (0ms), `quick` (150ms), `default`
(250ms), `slow` (400ms), `deliberate` (600ms).
- `--motion-ease-out` (default), `--motion-ease-spring`, `--motion-ease-linear`.
**Reduced-motion sweep** — replaced the narrow `.nav-item` /
`.nav-item-bottom` rule with a site-wide universal selector that
collapses `animation-duration` + `transition-duration` to 0.01ms
(preserves end-states, no flicker) when the OS preference is
reduced. Essential motion (loading spinners, scanning reticles) is
opt-in via `.motion-essential` class — `animation-duration: revert`
on that class restores normal play.
**`docs/MOTION_SYSTEM.md`** authored with full taxonomy, composition
recipes, WCAG SC 2.3.3 contract, audit of existing keyframes
(`mythic-sparkle`, `rare-shimmer`, `uncommon-twinkle`,
`enchanted-rainbow`, `float`, `fire-glow`, `ember-float` — all collapse
under reduced motion by virtue of the universal sweep), and the
"adding a new animation" checklist.
**Verification:** lint 0 errors; vitest 104/104 green. No JS touched.
Purely additive in CSS (new tokens, expanded media query) + new docs
file. Zero risk to existing baseline.
## Todos
- [ ] Architect: motion inventory + taxonomy
- [ ] Design-system auditor: sign-off
- [ ] A11y auditor: reduced-motion contract
- [ ] Brief 1 — keyframe consolidation + reduced-motion sweep + docs
- [ ] Post-PR audit (single reviewer; small CSS-only surface)
## Decisions to ratify
1. **Drop `fire-glow-bg`?** — Operator default: drop.
2. **Drop dual `float` keyframe?** — Keep ONE; architect picks
canonical version.
3. **Per-page budget exact numbers** — recommended numbers above; ratify.
4. **IntersectionObserver pause-when-offscreen** — implement here vs
defer. Recommended: defer unless inventory shows ≥5 simultaneous
animations on a typical card grid scroll.
5. **Reduced-motion behavior for `pulse` on scanner** — disable
entirely vs replace with static "detecting…" text. Recommended:
replace with static text (scanner needs SOME feedback).
## Acceptance criteria
1. Every animation in `styles/globals.css` is documented in
`docs/MOTION_SYSTEM.md` with its tier classification.
2. Every animation has a `prefers-reduced-motion: reduce` rule.
3. Unused keyframes deleted.
4. Dual `float` deduplication done.
5. `fire-glow-bg` dropped from page-level (if operator confirms).
6. Lint + vitest + smoke green.
7. Lighthouse Performance on `pages/index.js` + `pages/cards.js`
unchanged or improved (because we're removing animations).
## CI impact
| Workflow / job | Behavior |
| --- | --- |
| `preview-smoke.yml` | Fires. |
| `visual-diff.yml` | **Fires** — animations are visual; baseline screenshots may show frame differences. Architect must consider screenshot-stability impact. |
| `lint` | Fires. |
| `test:` (vitest) | Fires. |
| Lighthouse | Pre + post on `pages/index.js` + `pages/cards.js`. |
## Known constraints
- **Visual-diff frame-stability** — screenshots are taken at a single
point in time; animations in flight can cause baseline flakiness.
Architect to consider whether to add `animation: none !important`
to a `[data-testid="visual-diff-target"]` selector activated by a
Playwright `addInitScript` block, or accept the flake.
- **Theme tokens only** — no hex.
- **Don't touch Card3D logic** — covered by #5.
## Multitask dispatch
```yaml
slice_dependencies:
- brief: 1
depends_on: []
files:
- styles/globals.css
- docs/MOTION_SYSTEM.md
```
Single brief; no multitask.
## Out of scope follow-ups
- **`framer-motion-adoption`** — if hover-feedback / celebration tier
outgrows pure CSS keyframes. P3 architectural decision.
- **`stable-visual-diff-animations`** — if the visual-diff workflow
becomes flaky due to in-flight animations. Surface as CI infra
follow-up.
- **`scroll-driven-animations`** — CSS `animation-timeline:` with
scroll. Browser support is uneven; defer.