deckhearth/.convoys/liquid-glass-design-tokens.md
varutasu 334612ad79
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 20:12:33 -05:00

17 KiB
Raw Permalink Blame History

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.
ux
ia
qa
flag
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 (default 12px), --glass-blur-mid (20px), --glass-blur-high (32px)
    • --glass-saturate (default 140%)
    • --rim-light-innerrgba(255,255,255,0.55) light / rgba(255,255,255,0.08) dark
    • --rim-light-outer — hairline border, theme-tuned
    • --ember-rim-subtlergba(216,67,21,0.35) 1px ring
    • --ember-rim-pronouncedrgba(216,67,21,0.45) 1px ring + 12px bloom
    • --elevation-ambient — soft outer shadow (replaces inline shadow-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-primary and --text-secondary. Use https://webaim.org/resources/contrastchecker/ values.
  • AGENTS.md § "Branding" — append one paragraph linking docs/DESIGN_TOKENS.md and naming the Liquid Glass direction.

Out of scope

  • Any change to a .js file under components/** or pages/**.
  • 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.md is hand-curated; a real Storybook is its own future convoy.

Roles invoked

  1. role-design-system-auditor — proposes token names + structure.
  2. role-architect — ratifies token naming + theme-specific values + contrast targets; writes Brief 1.
  3. role-implementer — single brief; adds tokens + docs.
  4. role-doc-writer — reviews docs/DESIGN_TOKENS.md shape.

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 + @supports fallback values; all 5 operator defaults honoured; 6 pre-existing token violations flagged for #8 cleanup)
  • Architect: ratify Decisions 17 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.186.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 LOC docs/DESIGN_TOKENS.md, 23 LOC AGENTS.md § Visual language. Lint 0 errors; vitest 84/84 green (baseline preserved).
  • Post-PR audit — role-reviewer (single-shot): zero .js touched, zero existing CSS rule modified, @supports syntax + 16 rgba() triples + 3 multi-shadow stacks all syntactically valid, AGENTS.md insertion at correct topology.

Decisions to ratify (architect)

  1. Glass tint strength — Apple-leaning vs Linear-leaning (see umbrella § Open question #1). Operator default: Apple-leaning.
  2. Light-theme glass base — warm white vs cool white (umbrella § #2). Operator default: warm white.
  3. Dark-theme glass base — warm black vs cool black (umbrella § #3). Operator default: warm black.
  4. --glass-blur-low/mid/high exact px values — proposal: 12 / 20 / 32.
  5. --glass-saturate default — proposal: 140% (Apple-style vibrancy).
  6. 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.
  7. @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

  1. styles/globals.css defines every token listed in § Scope for both themes.
  2. docs/DESIGN_TOKENS.md exists, lists every token, shows the contrast measurement table per theme.
  3. No .js file is modified.
  4. npm run lint + npm run test:run + npm run test:smoke all green.
  5. Screenshot diff is invoked (CSS path matches styles/**) 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).
  6. AGENTS.md § Branding mentions Liquid Glass + links docs/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 :root and [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 extending tailwind.config.js theme.extend.backdropBlur / backgroundColor with 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-curated docs/DESIGN_TOKENS.md is 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

  1. 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.css should show only inserted hunks. (Risk: low.)
  2. @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.)
  3. @supports block 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.)
  4. docs/DESIGN_TOKENS.md path referenced from AGENTS.md before the file exists. Brief is atomic — all three files commit together. (Risk: nil with atomic commit.)
  5. Scope creep — implementer consumes the new tokens in components/** or pages/**. This brief is foundation-only; consumption begins in sub-convoy #2. Brief explicitly forbids .js changes; pre-PR verification command grep'd in the brief surfaces any leak. (Risk: low; gated by explicit anti-scope + verification.)
  6. No-go zone violation. styles/globals.css is not a no-go zone; AGENTS.md is the canonical agent contract (editable through documented sections); docs/ is new content. All in scope.
  7. Token-name collision. None — audit verified all 29 proposed names are unique against the current styles/globals.css namespace. (Risk: nil.)
  8. Browser-fallback misfire on Safari 18+. Safari 18+ supports backdrop-filter unprefixed; @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)

  1. Dep set check. Zero new packages added. Zero peer-dep concerns. Pass.
  2. 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 @supports reference. Verified.
    • The 16 rgba(...) triples in the brief — all integer RGB values 0255, all alpha values 0.01.0. Syntactically valid.
    • The 3 box-shadow stacks (light + dark --elevation-pronounced, --ember-rim-pronounced) — comma-separated multi-shadow syntax is CSS Backgrounds and Borders Level 3 valid. Verified.
    • inset keyword for inner-rim shadows — valid in box-shadow and standalone (when in box-shadow list). 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 at styles/globals.css lines 100154. Verified.
  3. 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-shadow stacks.
  • If the implementer hits any unexpected obstacle (e.g. Screenshot diff shows 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 vocabulary and ## 1. Project overview). Use StrReplace to target the line-29 boundary precisely; do not use blind append-to-section.