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>
8.5 KiB
| name | classification | success_metric | skip | status | created | merged | depends_on | umbrella | ||
|---|---|---|---|---|---|---|---|---|---|---|
| motion-system-pass | feature | 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`. |
|
merged | 2026-06-03 | 2026-06-03 |
|
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
@keyframesand everyanimation:rule + its call sites. Classify each into one of:- Ambient — page-level background motion (currently:
fire-glow-bg,ember-floaton 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:
pulseon scanner, nav-itemtranslateX(4px)). - Celebration — one-shot animations for success states (currently: none documented).
- Ambient — page-level background motion (currently:
- Deduplication — fix the dual
@keyframes floatbug; 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. Localizeember-floatto landing hero only. docs/MOTION_SYSTEM.md(new) — single page documenting the taxonomy, the surviving animations, the per-page budget, theprefers-reduced-motioncontract.
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
role-architect— motion inventory + taxonomy proposal.role-design-system-auditor— taxonomy sign-off.role-a11y-auditor— reduced-motion contract review.role-implementer— single brief (CSS only; small surface).role-doc-writer—docs/MOTION_SYSTEM.mdreview.
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
- Drop
fire-glow-bg? — Operator default: drop. - Drop dual
floatkeyframe? — Keep ONE; architect picks canonical version. - Per-page budget exact numbers — recommended numbers above; ratify.
- IntersectionObserver pause-when-offscreen — implement here vs defer. Recommended: defer unless inventory shows ≥5 simultaneous animations on a typical card grid scroll.
- Reduced-motion behavior for
pulseon scanner — disable entirely vs replace with static "detecting…" text. Recommended: replace with static text (scanner needs SOME feedback).
Acceptance criteria
- Every animation in
styles/globals.cssis documented indocs/MOTION_SYSTEM.mdwith its tier classification. - Every animation has a
prefers-reduced-motion: reducerule. - Unused keyframes deleted.
- Dual
floatdeduplication done. fire-glow-bgdropped from page-level (if operator confirms).- Lint + vitest + smoke green.
- Lighthouse Performance on
pages/index.js+pages/cards.jsunchanged 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 !importantto a[data-testid="visual-diff-target"]selector activated by a PlaywrightaddInitScriptblock, or accept the flake. - Theme tokens only — no hex.
- Don't touch Card3D logic — covered by #5.
Multitask dispatch
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— CSSanimation-timeline:with scroll. Browser support is uneven; defer.