* 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>
17 KiB
| name | classification | success_metric | skip | status | created | merged | depends_on | umbrella | conductor_started | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| liquid-glass-design-tokens | feature | styles/globals.css gains the canonical Liquid Glass token layer (`--glass-surface-*`, `--glass-blur-*`, `--rim-light-*`, `--ember-rim-*`, `--elevation-*`) for light + dark themes; docs/DESIGN_TOKENS.md documents every token with a contrast measurement vs --text-primary AND --text-secondary in both themes; ZERO component changes; visual diff baselines remain stable; lint + vitest + smoke green. |
|
merged | 2026-06-03 | 2026-06-03 | liquid-glass-redesign | 2026-06-03 |
Convoy: liquid-glass-design-tokens
Foundation sub-convoy #1 of the liquid-glass-redesign epic. Adds the
token layer that every subsequent sub-convoy consumes. Touches CSS +
docs only — no component or page file is edited here.
Conductor stamp (2026-06-03)
Classification: feature (full pipeline with skips below).
Skips ratified:
ux— zero user-visible change; UX reviewer has nothing to evaluate.ia— no information-architecture concern; navigation / URL / labels untouched.qa— no UI to manually click through; lint + vitest + visual-diff + smoke fully cover the surface.flag— repo has no feature-flag wrapper (per.convoys/liquid-glass-redesign.md§ Hard scoping rules).
Kept in pipeline: arch (architect must ratify the 7 decisions
listed below), design (design-system auditor is the LEAD role here),
a11y (contrast measurements are a11y concern — auditor reviews the
contrast table, not code), test (lint + vitest baseline), visual
(visual-diff fires on styles/**), smoke (preview-smoke runs on
every PR), review (single-shot reviewer post-PR), docs (this convoy
writes docs/DESIGN_TOKENS.md).
Next role: role-design-system-auditor (proposes token names +
structure), then role-architect (ratifies + writes Brief 1).
Why
The Liquid Glass redesign cannot proceed without a documented,
measurable token surface. Today styles/globals.css defines:
- Color tokens (
--bg-primary/--bg-secondary/ etc.) - Three gradients (
--gradient-primary/--gradient-secondary/.gradient-bg-ember) - A handful of glow utility classes (
.fire-glow,.ember-glow,.card-mythic-glow, etc.) - RGB-component triples for backdrop-blur effects
(
--bg-primary-rgb, etc.)
What it does NOT define is a surface token (translucency + blur + rim-light + elevation) — every component currently composes those ad-hoc inline. This sub-convoy adds that layer and freezes it as the single source of truth.
Scope
In scope
styles/globals.css— add new tokens to both:root(light) and[data-theme="dark"]:--glass-surface-low— primary panel background (modals, cards-in-detail)--glass-surface-mid— sidebar / header / navigation rails--glass-surface-high— overlays, tooltips, dropdowns--glass-blur-low(default12px),--glass-blur-mid(20px),--glass-blur-high(32px)--glass-saturate(default140%)--rim-light-inner—rgba(255,255,255,0.55)light /rgba(255,255,255,0.08)dark--rim-light-outer— hairline border, theme-tuned--ember-rim-subtle—rgba(216,67,21,0.35)1px ring--ember-rim-pronounced—rgba(216,67,21,0.45)1px ring + 12px bloom--elevation-ambient— soft outer shadow (replaces inlineshadow-lg)--elevation-pronounced— stacked elevation for modals--modal-scrim— backdrop fill behind a modal (theme-tuned alpha)
docs/DESIGN_TOKENS.md(new) — reference doc listing every token, its purpose, both-theme values, and measured contrast ratios against--text-primaryand--text-secondary. Use https://webaim.org/resources/contrastchecker/ values.AGENTS.md§ "Branding" — append one paragraph linkingdocs/DESIGN_TOKENS.mdand naming the Liquid Glass direction.
Out of scope
- Any change to a
.jsfile undercomponents/**orpages/**. - Deletion of any existing token or utility class (cleanup is sub-convoy #8).
- Tailwind config changes — Liquid Glass is implemented in CSS vars, not Tailwind theme extensions.
- Storybook adoption —
docs/DESIGN_TOKENS.mdis hand-curated; a real Storybook is its own future convoy.
Roles invoked
role-design-system-auditor— proposes token names + structure.role-architect— ratifies token naming + theme-specific values + contrast targets; writes Brief 1.role-implementer— single brief; adds tokens + docs.role-doc-writer— reviewsdocs/DESIGN_TOKENS.mdshape.
Todos
- Design-system auditor: token naming + structure proposal
→ see
.convoys/liquid-glass-design-tokens/design-system-audit.md(29 tokens proposed; both-theme values + contrast tables + composite recipes +@supportsfallback values; all 5 operator defaults honoured; 6 pre-existing token violations flagged for #8 cleanup) - Architect: ratify Decisions 1–7 using the audit's § 7
recommendations; write Brief 1 → see § Architecture below +
.convoys/liquid-glass-design-tokens/brief-1-tokens-and-docs.md. All 7 decisions ratified verbatim from audit; all 3 boot-the-brief checks passed. - Human gate 1 (plan approval) — approved by operator 2026-06-03 in the full-portfolio drive-through prompt.
- A11y auditor: contrast tables in audit § 4 + § 5 verified (light text-primary 15.18:1 AAA, text-secondary 6.86:1 AA; dark text-primary 17.84:1 AAA, text-secondary 11.42:1 AAA; ember rim non-text 3.18–6.18:1 all clear 3:1 SC 1.4.11 floor).
- Brief 1 — tokens + docs (committed 2026-06-03). 110 LOC inserted
into
styles/globals.css(purely additive, comment-fenced), 270 LOCdocs/DESIGN_TOKENS.md, 23 LOCAGENTS.md § Visual language. Lint 0 errors; vitest 84/84 green (baseline preserved). - Post-PR audit —
role-reviewer(single-shot): zero.jstouched, zero existing CSS rule modified,@supportssyntax + 16rgba()triples + 3 multi-shadow stacks all syntactically valid, AGENTS.md insertion at correct topology.
Decisions to ratify (architect)
- Glass tint strength — Apple-leaning vs Linear-leaning (see umbrella § Open question #1). Operator default: Apple-leaning.
- Light-theme glass base — warm white vs cool white (umbrella § #2). Operator default: warm white.
- Dark-theme glass base — warm black vs cool black (umbrella § #3). Operator default: warm black.
--glass-blur-low/mid/highexact px values — proposal: 12 / 20 / 32.--glass-saturatedefault — proposal: 140% (Apple-style vibrancy).- Contrast target — WCAG AA (4.5:1 for text-primary, 3:1 for text-secondary on large text) vs AAA. Recommended: AA hard floor; AAA where achievable without losing the glass effect.
@supports not (backdrop-filter: blur(20px))fallback alpha — solid-with-alpha values for each--glass-surface-*so non-supporting browsers degrade to a flat tinted panel, not a hard opaque box.
Acceptance criteria
styles/globals.cssdefines every token listed in § Scope for both themes.docs/DESIGN_TOKENS.mdexists, lists every token, shows the contrast measurement table per theme.- No
.jsfile is modified. npm run lint+npm run test:run+npm run test:smokeall green.Screenshot diffis invoked (CSS path matchesstyles/**) and shows zero or trivially-noisy diff (sub-pixel color reordering only). If non-trivial diff appears, the architect must explain why before merge (most likely cause: an accidental selector reorder; rollback that change).AGENTS.md§ Branding mentions Liquid Glass + linksdocs/DESIGN_TOKENS.md.
CI impact
| Workflow / job | Behavior |
|---|---|
preview-smoke.yml |
Fires (any PR). |
visual-diff.yml |
Fires (styles/** matches paths). Expected diff: none. |
lint |
Fires. |
test: (vitest) |
Fires. |
| New grep gates | None. |
Known constraints
- No hardcoded hex outside
styles/globals.css. The token surface is the only place hex appears post-sub-convoy. - Both themes ship together — every token gets a value in both
:rootand[data-theme="dark"]. Reviewer fails the PR if any token is one-theme-only. @supports not (backdrop-filter)fallback — every glass surface token has a documented fallback per Hard scoping rule of the umbrella.
Multitask dispatch
slice_dependencies:
- brief: 1
depends_on: []
files:
- styles/globals.css
- docs/DESIGN_TOKENS.md
- AGENTS.md
Single brief; no multitask. Post-PR audit is a single
role-reviewer invocation.
Out of scope follow-ups
tailwind-theme-extension(P3 polish): if downstream sub-convoys find themselves repeatedly composing the same Tailwind-class shape (e.g.bg-[var(--glass-surface-low)] backdrop-blur-[var(--glass-blur-mid)]), consider extendingtailwind.config.jstheme.extend.backdropBlur/backgroundColorwith named aliases. Surface only if at least 3 downstream sub-convoys hit the same shape.storybook-adoption(P2 DX): would let the design-system-auditor inspect tokens + primitives in isolation. Out of scope here; hand-curateddocs/DESIGN_TOKENS.mdis the v1 surface.
Architecture (2026-06-03)
Decisions ratified
All 7 decisions in § "Decisions to ratify (architect)" ratified
verbatim from the audit's § 7 recommendation table at
.convoys/liquid-glass-design-tokens/design-system-audit.md. No
re-tuning required — the audit's proposal honoured all 5 operator
defaults from the umbrella and passed AA contrast on all surfaces in
both themes (AAA on body text).
| Decision | Ratified value | Source |
|---|---|---|
| 1. Glass tint strength | --glass-surface-{low,mid,high} α = 0.55 / 0.68 / 0.82 |
Audit § 2.1 |
| 2. Light-theme glass base | Warm white rgba(254, 252, 248, α) (via existing --bg-primary-rgb) |
Audit § 2.1 |
| 3. Dark-theme glass base | Warm black rgba(26, 15, 10, α) (via existing --bg-primary-rgb dark variant) |
Audit § 2.1 |
4. --glass-blur-{low,mid,high} px |
12 / 20 / 32 | Audit § 2.2 |
5. --glass-saturate default |
140% | Audit § 2.2 |
| 6. Contrast target | AA hard floor (AAA achieved on body text in both themes per audit § 4) | Audit § 4 |
7. @supports fallback alpha |
Collapsed ramp 0.92 / 0.95 / 0.98 under @supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px))) |
Audit § 3 |
File plan
| File | Action | Purpose |
|---|---|---|
styles/globals.css |
modified (purely additive) | Insert the 29-variable Liquid Glass token block between the existing dark-theme ] (line 74) and the /* Apply theme colors */ comment (line 76); append the @supports not (...) fallback block immediately after. Zero pre-existing rules touched. |
docs/DESIGN_TOKENS.md |
new | Reference doc — surface ramp, blur, rim-light, ember rim, elevation, modal scrim, composite recipes, contrast tables, "When NOT to use glass", performance budget, browser support, deprecations. |
AGENTS.md |
modified | Insert a new ## Visual language section between ## Product vocabulary (line ~27) and ## 1. Project overview (line 29). Three rules of thumb + pointer at docs/DESIGN_TOKENS.md. |
API surface
N/A — CSS + docs only. No new route, no API contract change, no authentication surface, no rate-limit consideration.
Schema diff
N/A — no database change.
Test plan
| Stage | Action |
|---|---|
vitest (npm run test:run) |
No new tests added in this brief (CSS-var surface has no functional unit-test target). Existing 21/21 baseline must stay green. |
lint (npm run lint) |
Must exit 0. |
smoke (npm run test:smoke) |
Existing 3 specs (home / sign-in / health) must stay green. None depend on visual tokens. |
visual-diff (Screenshot diff workflow) |
Fires (styles/** matches paths). Expected output: empty diff — no consumer of the new tokens is added in this brief. Any non-trivial rendered-pixel diff is a bug and must be explained before merge. |
| manual | None required for this brief — no UI to click through (per skip: qa). |
No existing tests are affected. No new test files are created. The "tokens added but unused" property is the test: visual diff is the implicit assertion.
Risk list
- Visual-diff noise from CSS file reorganization. Mitigation: the
block is inserted at a single contiguous location with comment
fences; no existing rule is renumbered or moved.
git diff styles/globals.cssshould show only inserted hunks. (Risk: low.) @supports not ((...) or (...))syntax error. The negation of an OR group requires the outer parentheses around the whole group. Brief 1 provides the verbatim shape, copy-pasted; implementer must not reformat. (Risk: low; mitigated by verbatim copy.)@supportsblock placement order. The fallback MUST come AFTER the base:root+[data-theme="dark"]blocks so the override fires when supported. Brief specifies the insertion location. (Risk: low; mitigated by exact-insertion-point instruction.)docs/DESIGN_TOKENS.mdpath referenced fromAGENTS.mdbefore the file exists. Brief is atomic — all three files commit together. (Risk: nil with atomic commit.)- Scope creep — implementer consumes the new tokens in
components/**orpages/**. This brief is foundation-only; consumption begins in sub-convoy #2. Brief explicitly forbids.jschanges; pre-PR verification command grep'd in the brief surfaces any leak. (Risk: low; gated by explicit anti-scope + verification.) - No-go zone violation.
styles/globals.cssis not a no-go zone;AGENTS.mdis the canonical agent contract (editable through documented sections);docs/is new content. ✅ All in scope. - Token-name collision. None — audit verified all 29 proposed
names are unique against the current
styles/globals.cssnamespace. (Risk: nil.) - Browser-fallback misfire on Safari 18+. Safari 18+ supports
backdrop-filterunprefixed;@supports not (...)will NOT fire, so the fallback alphas remain unused — the canonical 0.55 / 0.68 / 0.82 surfaces ship as intended. Cross-checked against caniuse 2026- 06-03. (Risk: nil.)
Decomposition
| Brief # | Title | Files | Depends on | Estimated PR size |
|---|---|---|---|---|
| 1 | tokens-and-docs |
styles/globals.css, docs/DESIGN_TOKENS.md, AGENTS.md |
— | ~110 LOC inserted into styles/globals.css; ~260 LOC docs/DESIGN_TOKENS.md; ~25 LOC AGENTS.md. Total ≈ 395 LOC, all additive. No deletions. |
Single brief; no multitask possible (single implementer, single PR). Comfortably under the 400-LOC architect anti-pattern threshold.
Slice dependencies (multitask-ready)
slice_dependencies:
- brief: 1
depends_on: []
files:
- styles/globals.css
- docs/DESIGN_TOKENS.md
- AGENTS.md
Multitask verdict: no /multitask dispatch — single-brief convoy.
Conductor's serial-implementer path is the right shape here.
Boot-the-brief check (architect verification, completed 2026-06-03)
- Dep set check. Zero new packages added. Zero peer-dep concerns. ✅ Pass.
- Verbatim code shape check.
@supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px)))— CSS Conditional Rules Level 3 valid syntax (negation of a compound condition requires the outer parens around the OR group). Cross-checked against MDN's@supportsreference. ✅ Verified.- The 16
rgba(...)triples in the brief — all integer RGB values 0–255, all alpha values 0.0–1.0. ✅ Syntactically valid. - The 3
box-shadowstacks (light + dark--elevation-pronounced,--ember-rim-pronounced) — comma-separated multi-shadow syntax is CSS Backgrounds and Borders Level 3 valid. ✅ Verified. insetkeyword for inner-rim shadows — valid inbox-shadowand standalone (when inbox-shadowlist). ✅ Verified.- The CSS variable inheritance pattern (theme-independent blur/saturate defined only in
:root; theme-dependent surfaces redefined in[data-theme="dark"]) — matches the existing precedent atstyles/globals.csslines 100–154. ✅ Verified.
- Cross-brief commitments check. Single brief; no commitments to downstream briefs in this convoy. (Downstream convoys #2–#8 are separate convoys with their own architect passes.) ✅ N/A.
All boot-the-brief checks pass. No brief revision needed.
Architect notes (mid-implementation guidance)
- The brief explicitly forbids reformatting the verbatim CSS block.
Implementer must copy-paste, not retype. This avoids whitespace
drift on the multi-line
box-shadowstacks. - If the implementer hits any unexpected obstacle (e.g.
Screenshot diffshows a non-trivial rendered diff despite no consumer being added), STOP and surface to the operator. Do not "fix it" by editing components. - The 26-line AGENTS.md insertion is at a specific topological
position (between
## Product vocabularyand## 1. Project overview). UseStrReplaceto target the line-29 boundary precisely; do not use blind append-to-section.