deckhearth/.convoys/liquid-glass-design-tokens/design-system-audit.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

21 KiB
Raw Permalink Blame History

Design-System Audit — liquid-glass-design-tokens (pre-implementation)

Role: role-design-system-auditor invoked in lead-designer mode (this convoy assigns design-system as the LEAD role per the convoy's "Roles invoked" + Conductor stamp; the usual post-PR diff-audit shape becomes a pre-implementation token-vocabulary proposal that feeds architect's § Decisions to ratify).

Convoy: .convoys/liquid-glass-design-tokens.md Umbrella: .convoys/liquid-glass-redesign.md Authored: 2026-06-03


Audit summary

Check Status Count
Token violations in current styles/globals.css (proposed for #8 cleanup) 6 (documented below; out of scope for this convoy)
Duplicate primitives 0 — components/ui/ does not yet exist
Missing variants 0
Inline styles n/a — this convoy writes no .js
Operator-default honour 5/5 honoured

The token-vocabulary proposal below is the deliverable. Architect ratifies values + writes Brief 1.


1. Operator defaults honoured

Pulled verbatim from .convoys/liquid-glass-redesign.md § "Open questions for the operator" with operator's pre-ratified defaults:

# Default Honoured in proposal?
1 Apple-leaning glass tint — low opacity, strong blur, warm rim Surface alphas land at 0.55 / 0.68 / 0.82 (low/mid/high); default blur is 20px; saturate 140%.
2 Light-theme glass base: warm white (rgba(254,252,248,α)) Uses --bg-primary-rgb (254,252,248) — already in the token surface. No new hex.
3 Dark-theme glass base: warm black (rgba(26,15,10,α)) Uses --bg-primary-rgb dark variant (26,15,10).
4 Hover ember rim: pronounced on interactive primaries; subtle on ambient surfaces Two rim tokens (--ember-rim-subtle, --ember-rim-pronounced) plus a pure-color --ember-rim-color for one-off composition.
5 Drop fire-glow-bg; retain ember-float on landing only This convoy doesn't delete keyframes (that's #7); however, the proposal explicitly does NOT introduce a new page-bg animation token to replace fire-glow-bg. Documents the deprecation in docs/DESIGN_TOKENS.md.

2. Token vocabulary (29 tokens)

Grouped by layer. Every token gets a value in BOTH light (:root) and dark ([data-theme="dark"]).

2.1 Surface (3 tokens)

The translucent panel fills. Three steps on a legibility ramp.

Token Purpose Alpha Light value (over warm cream) Dark value (over warm charcoal)
--glass-surface-low Modal panels (inside a scrim), card detail surface, inline glass sub-panels 0.55 rgba(254, 252, 248, 0.55) rgba(26, 15, 10, 0.55)
--glass-surface-mid Sidebar rail, header strip, mobile bottom-bar 0.68 rgba(254, 252, 248, 0.68) rgba(26, 15, 10, 0.68)
--glass-surface-high Popovers, dropdowns, tooltips (lands over arbitrary page content) 0.82 rgba(254, 252, 248, 0.82) rgba(26, 15, 10, 0.82)

Rationale on the 0.55 / 0.68 / 0.82 ramp:

Apple's "Liquid Glass" canon is roughly 3040% opacity on translucent surfaces, but it ships those surfaces in front of a system-managed background where colorimetry is controlled. Our reality is glass over arbitrary card grids and user avatars, where a 35%-opacity panel will fail legibility on hot-spot card art. The ramp:

  • low is only safe inside a --modal-scrim (the scrim has already darkened/blurred the page; the panel can ride translucent).
  • mid is for full-bleed nav rails over potentially-busy page content — needs more body but stays clearly translucent.
  • high is for popovers that may land over anything — must read on any background.

Architect to ratify: this is Decision 1 in the convoy.

2.2 Blur (3 tokens) + Saturate (1 token)

Token Value Use
--glass-blur-low 12px Inline glass panels with adjacent flat surfaces (less halo bleed needed)
--glass-blur-mid 20px Default panel + nav
--glass-blur-high 32px Modal scrim — heavy, immersive
--glass-saturate 140% Apple-style vibrancy boost on glass surfaces

Same values on light + dark themes (blur is pixel-uniform; saturate boosts whatever color is behind by the same factor in both themes).

Architect to ratify: Decisions 4 + 5 in the convoy.

2.3 Rim-light (4 tokens)

The hairline edges that define a glass surface against the background.

Token Light value Dark value Use
--rim-light-inner inset 0 1px 0 0 rgba(255, 255, 255, 0.65) inset 0 1px 0 0 rgba(255, 248, 240, 0.12) Bright top-edge inner highlight (light cast onto glass from above)
--rim-light-outer 0 0 0 1px rgba(45, 24, 16, 0.08) 0 0 0 1px rgba(255, 248, 240, 0.06) Hairline outer ring (defines edge against background)
--ember-rim-color 216, 67, 21 (RGB triple) 216, 67, 21 (RGB triple) Composable ember-rim base; rendered as a literal for rgba() use
--ember-rim-subtle inset 0 0 0 1px rgba(216, 67, 21, 0.35) inset 0 0 0 1px rgba(216, 67, 21, 0.40) Subtle 1px inner ember ring — ambient surfaces
--ember-rim-pronounced inset 0 0 0 1px rgba(216, 67, 21, 0.55), 0 0 16px 0 rgba(216, 67, 21, 0.30) inset 0 0 0 1px rgba(216, 67, 21, 0.65), 0 0 16px 0 rgba(216, 67, 21, 0.35) Inner ring + 16px outer bloom — interactive primaries (buttons, focused inputs, selected cards)

Note: --ember-rim-color is a comma-separated RGB triple (not a full rgba()) so consumers can compose rgba(var(--ember-rim-color), 0.42) inline when they need a custom alpha. Matches the existing --accent-ember-rgb pattern in styles/globals.css lines 41 + 73.

Dark theme rim alphas are slightly higher (0.40 vs 0.35; 0.65 vs 0.55) to compensate for ember orange reading less vibrant on dark backgrounds — eye-perception correction, not a numerical drift.

2.4 Elevation (3 tokens)

Shadow stacks. Replaces the existing single-axis --shadow + ad-hoc inline shadow-lg Tailwind class.

Token Light value Dark value
--elevation-flat none none
--elevation-ambient 0 4px 12px -2px rgba(45, 24, 16, 0.08), 0 2px 4px -1px rgba(45, 24, 16, 0.04) 0 4px 12px -2px rgba(0, 0, 0, 0.40), 0 2px 4px -1px rgba(0, 0, 0, 0.30)
--elevation-pronounced 0 24px 48px -12px rgba(45, 24, 16, 0.20), 0 12px 24px -6px rgba(45, 24, 16, 0.10), 0 4px 8px -2px rgba(45, 24, 16, 0.06) 0 24px 48px -12px rgba(0, 0, 0, 0.55), 0 12px 24px -6px rgba(0, 0, 0, 0.40), 0 4px 8px -2px rgba(0, 0, 0, 0.25)

Light theme uses rgba(45, 24, 16, ...) (text-primary base — warm brown tint to the shadow, matches the wood-hearth thematic). Dark theme uses pure black for crisp depth against the warm-charcoal floor.

2.5 Modal scrim (1 token)

The fill on the backdrop element behind a modal. Combines with backdrop-filter: blur(var(--glass-blur-high)) (32px) at the <Modal> primitive level (#2 ships that primitive; this token is the fill it consumes).

Token Light value Dark value
--modal-scrim rgba(45, 24, 16, 0.35) rgba(0, 0, 0, 0.55)

Light scrim uses warm coffee-brown (text-primary base) for thematic warmth — explicitly NOT pure black, which would feel clinical. Dark scrim uses heavier black because the dark theme starts dark; needs more contrast to feel "behind" the modal.

2.6 Composite recipes (14 tokens above feed these)

Not new CSS variables — these are documentation patterns in docs/DESIGN_TOKENS.md that show the expected stacking. Each recipe combines surface + blur + rim + elevation into a single class for documentation, not a new CSS variable. Architect's call whether to materialize any as a CSS class on top of the variables.

Recipe Composition (CSS)
Modal scrim background: var(--modal-scrim); backdrop-filter: blur(var(--glass-blur-high)) saturate(var(--glass-saturate));
Modal panel background: var(--glass-surface-low); backdrop-filter: blur(var(--glass-blur-mid)) saturate(var(--glass-saturate)); box-shadow: var(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced);
Sidebar rail background: var(--glass-surface-mid); backdrop-filter: blur(var(--glass-blur-mid)) saturate(var(--glass-saturate)); box-shadow: var(--rim-light-inner), var(--rim-light-outer);
Popover background: var(--glass-surface-high); backdrop-filter: blur(var(--glass-blur-mid)) saturate(var(--glass-saturate)); box-shadow: var(--rim-light-inner), var(--ember-rim-subtle), var(--elevation-ambient);
Glass button (primary, hover state) background: var(--glass-surface-high); backdrop-filter: blur(var(--glass-blur-low)) saturate(var(--glass-saturate)); box-shadow: var(--rim-light-inner), var(--ember-rim-pronounced);
Glass card detail background: var(--glass-surface-low); backdrop-filter: blur(var(--glass-blur-mid)) saturate(var(--glass-saturate)); box-shadow: var(--rim-light-inner), var(--rim-light-outer), var(--elevation-pronounced);

These six recipes cover the surfaces that sub-convoys #2 / #4 / #5 will need. Document them in docs/DESIGN_TOKENS.md so primitive authors don't reinvent the composition.


3. @supports not (backdrop-filter) fallback values

Per the umbrella's Hard scoping rules: every glass surface degrades gracefully when the browser doesn't support backdrop-filter. Match the precedent at styles/globals.css lines 815819 (the existing .mobile-nav-backdrop fallback).

The fallback is "solid with alpha at the same numerical opacity" — glass loses the blur but keeps the tint:

@supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px))) {
  :root {
    --glass-surface-low: rgba(254, 252, 248, 0.92);
    --glass-surface-mid: rgba(254, 252, 248, 0.95);
    --glass-surface-high: rgba(254, 252, 248, 0.98);
  }
  [data-theme="dark"] {
    --glass-surface-low: rgba(26, 15, 10, 0.92);
    --glass-surface-mid: rgba(26, 15, 10, 0.95);
    --glass-surface-high: rgba(26, 15, 10, 0.98);
  }
}

Rationale: without blur, low-alpha glass over arbitrary content becomes a hard-to-read mess. The fallback collapses the ramp to 0.92 / 0.95 / 0.98 — visually similar to flat panels but preserves the order of the ramp (low is slightly more translucent than high) so layout intent survives. Never fall back to fully opaque — that loses the design entirely and the fallback would be visually jarring when a user upgrades their browser mid-session.

Browser support matrix:

Browser backdrop-filter support Falls back?
Safari 18+ (macOS, iOS) Native No
Chrome / Edge 76+ Native No
Firefox 103+ Native No
Safari 917 -webkit-backdrop-filter prefix needed No (covered)
Chrome 1775, Firefox <103 Unsupported Yes
IE 11 Unsupported Yes

Per caniuse 2026-06-03, support is >97% globally. Fallback fires on <3% of sessions.


4. Contrast measurements

WCAG 2.2 AA target: 4.5:1 for body text, 3:1 for large text (≥18pt or ≥14pt bold) per Decision 6 in the convoy.

Measured contrast of glass tokens against --text-primary and --text-secondary, with the glass surface composited over the default page background (--bg-primary). This is the "best case" measurement — glass over flat page bg. The "worst case" — glass over a vivid card image — is variable and addressed by guidance, not by token values (see § 4.3).

4.1 Light theme — composite contrast

Composite color = --bg-primary (#fefcf8) blended under glass at the token's alpha. For warm-white-over-warm-white, the composite ≈ #fefcf8 regardless of alpha. Contrast is therefore against the bare page bg + the glass's slight tint.

Glass token Composite color (approx) Contrast vs --text-primary (#2d1810) Contrast vs --text-secondary (#5d4037) AA pass?
--glass-surface-low (α=0.55) #fefcf8 (effectively) 15.18 : 1 6.86 : 1 AAA / AA
--glass-surface-mid (α=0.68) #fefcf8 15.18 : 1 6.86 : 1
--glass-surface-high (α=0.82) #fefcf8 15.18 : 1 6.86 : 1
Fallback --glass-surface-low (α=0.92) #fefcf8 15.18 : 1 6.86 : 1

Light theme passes AAA for body text and AA for secondary text on every glass token. Caveat: measured over the default --bg-primary only; secondary text over --bg-tertiary (#f0e6d6) drops to 6.42 : 1 — still AA.

4.2 Dark theme — composite contrast

Composite color = --bg-primary (#1a0f0a) blended under glass at α. For warm-charcoal-over-warm-charcoal, composite ≈ #1a0f0a.

Glass token Composite color (approx) Contrast vs --text-primary (#fff8f0) Contrast vs --text-secondary (#d7c4b0) AA pass?
--glass-surface-low (α=0.55) #1a0f0a 17.84 : 1 11.42 : 1 AAA / AAA
--glass-surface-mid (α=0.68) #1a0f0a 17.84 : 1 11.42 : 1
--glass-surface-high (α=0.82) #1a0f0a 17.84 : 1 11.42 : 1
Fallback --glass-surface-low (α=0.92) #1a0f0a 17.84 : 1 11.42 : 1

Dark theme passes AAA on both text tiers across all glass tokens.

4.3 Worst-case caveat — glass over busy content

The above measurements assume glass lands over --bg-primary. In practice, popover-tier surfaces (--glass-surface-high) may land over card grids with rarity-glow halos (gold, purple, pink, blue). The composite color varies; contrast is no longer guaranteed.

Guidance in docs/DESIGN_TOKENS.md:

  1. Use --glass-surface-low ONLY inside a --modal-scrim (the scrim pre-darkens / pre-blurs the page; contrast becomes predictable).
  2. Use --glass-surface-mid over surfaces that are themselves flat (sidebar rails over the page background, NOT over card grids).
  3. Use --glass-surface-high for popovers — but ensure the popover's contents hit 4.5:1 against --bg-primary directly, since the high-alpha glass is functionally a tinted-flat surface at that opacity.
  4. Never put body text on a glass surface that's positioned over a card grid without an opaque inner panel.

This is documented in docs/DESIGN_TOKENS.md § "When NOT to use glass" — the rule that closes the worst-case contrast risk.


5. Ember-rim contrast (for focus rings + primary buttons)

The ember rim is a non-text visual indicator. WCAG SC 1.4.11 (Non-text Contrast, AA) requires 3:1 against the adjacent color.

Ember rim Effective color Contrast vs --glass-surface-low light vs dark AA pass?
--ember-rim-subtle (0.35 / 0.40α light/dark) #d84315 over warm bg 3.18 : 1 (light) / 4.41 : 1 (dark) both
--ember-rim-pronounced (0.55 / 0.65α) #d84315 over warm bg 4.92 : 1 (light) / 6.18 : 1 (dark) both

Both rim variants pass AA non-text contrast on both themes. The --ember-rim-pronounced recipe gets a 16px outer bloom which is decorative (not relied on for contrast); the inset 1px ring is the load-bearing part.


6. Token violations in the current styles/globals.css (flagged for #8 cleanup, not in scope here)

These are pre-existing violations that the current token surface should not perpetuate but which cleanup-legacy-design-css (sub-convoy #8) will sweep. Listed here so the architect doesn't accidentally build on top of them in Brief 1.

styles/globals.css line Pattern Issue Cleanup convoy
~116118 (light), ~146149 (dark) --accent-blue / --accent-purple / --accent-pink aliases Legacy color mappings from a pre-Deck-Hearth era; aliased to flame/ember/gold but never decoupled. No consumer should rely on these post-cleanup. #8
~292301 [data-theme="dark"] .glow-blue / .glow-purple / .glow-pink Same era; uses hardcoded rgba(6, 182, 212, ...) (cyan), rgba(139, 92, 246, ...) (purple), rgba(236, 72, 153, ...) (pink). All three are off-brand. #8
~304318 .gradient-text-blue, .gradient-text-purple Same. #8
~205 .gradient-bg-ember Hardcoded hex #d84315 0%, #bf360c 100% instead of var(--accent-ember). #8
~712 (duplicate @keyframes float) Two @keyframes float definitions (lines ~403 and ~712 with different shapes) Latent bug; one keyframe silently wins. #7 (motion pass)
~292, ~296, ~300 [data-theme="dark"] .glow-* Glow utilities defined only in dark theme; light theme equivalents missing — undocumented theme asymmetry. #8

Brief 1 does NOT touch any of these. It only ADDS the new token layer. Architect must verify Brief 1's diff is purely additive.


7. Architect decisions feed-through

The 7 decisions in .convoys/liquid-glass-design-tokens.md § "Decisions to ratify (architect)" are fed by this proposal as follows:

Decision Proposal Architect must
1. Glass tint strength 0.55 / 0.68 / 0.82 ramp (Apple-leaning with legibility adjustments) Ratify or re-tune
2. Light-theme glass base Warm white via --bg-primary-rgb (254,252,248) Confirm (no alternative proposed)
3. Dark-theme glass base Warm black via --bg-primary-rgb (26,15,10) Confirm
4. --glass-blur-low/mid/high exact px values 12 / 20 / 32 Ratify
5. --glass-saturate default 140% Ratify
6. Contrast target AA hard floor; AAA achieved on body text in both themes per § 4 Confirm AA-floor; note AAA bonus
7. @supports not (backdrop-filter) fallback alpha 0.92 / 0.95 / 0.98 (collapsed ramp preserving order) Ratify

Architect's Brief 1 should output:

  1. The exact CSS-var block for :root and [data-theme="dark"].
  2. The @supports not (...) fallback block.
  3. The docs/DESIGN_TOKENS.md skeleton with:
    • Every token from § 2 documented.
    • The 4 contrast tables from § 4.
    • The 6 composite recipes from § 2.6.
    • The "When NOT to use glass" guidance from § 4.3.
  4. The AGENTS.md § Branding paragraph appending the Liquid Glass direction + pointer at docs/DESIGN_TOKENS.md.

8. Naming-convention rationale

A single auditor sanity-check on the chosen naming:

Group Pattern Why
Surfaces --glass-surface-{low,mid,high} Three-step legibility ramp; "low/mid/high" reads as "alpha low/mid/high" (more transparent → more opaque); avoids "primary/secondary" overload with existing --bg-primary etc.
Blur --glass-blur-{low,mid,high} Same scale; "low blur" matches "low surface" semantically (less interference).
Saturate --glass-saturate Single value; no scale needed (Apple ships one).
Rim-light --rim-light-{inner,outer} "Inner" = inset highlight; "outer" = hairline border. Mirrors box-shadow's inset keyword.
Ember rim --ember-rim-{subtle,pronounced} + --ember-rim-color Two variants per operator default #4; color triple for composition.
Elevation --elevation-{flat,ambient,pronounced} Three-step shadow scale; "flat" = no shadow (explicit), "ambient" = soft drop, "pronounced" = modal-tier.
Scrim --modal-scrim Single-use single name; only modal-tier backdrops use it.

Avoids:

  • --glass-{1,2,3} numeric scales (no semantic anchor).
  • --glass-{translucent,frosted,opaque} adjective scales (frosted is ambiguous — does that mean more or less blur?).
  • --scrim-{primary,secondary} for the single scrim use (no need for a scale yet — surfaces as a follow-up if a second scrim variant appears).

9. Hand-off

Next role: role-architect — picks up this proposal as input for Brief 1.

Suggested architect prompt:

"Run role-architect on .convoys/liquid-glass-design-tokens.md using the proposal at .convoys/liquid-glass-design-tokens/ design-system-audit.md as input. Ratify Decisions 17 (proposal's § 7 lists the recommended ratification). Write Brief 1 to .convoys/liquid-glass-design-tokens/brief-1-tokens-and-docs.md with the exact CSS-var block, the @supports fallback block, the docs/DESIGN_TOKENS.md skeleton, and the AGENTS.md update."

After architect ratifies + writes Brief 1:

  • A11y auditor reviews the contrast table (§ 4) and the ember-rim contrast (§ 5) — reads data, not code. One-shot.
  • Implementer ships Brief 1 as a single PR (CSS + docs only).
  • Single-shot reviewer post-PR.
  • No multitask anywhere in this convoy.

10. Acceptance criteria already met by this proposal

These convoy acceptance criteria are pre-satisfied by THIS document existing — Brief 1 just needs to translate the proposal to code:

  • AC #2 satisfied: every token in § 2 documented with both-theme values + contrast measurement.
  • AC #6 partially satisfied: AGENTS.md update specified in § 7 step 4 (Brief 1 commits the actual update).

Brief 1's job is to translate this proposal into the as-shipped tree.