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>
207 lines
8.5 KiB
Markdown
207 lines
8.5 KiB
Markdown
---
|
||
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 261–266 — 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.
|