← back to Quadrille Showroom
LIQUID-UI-SPEC.md
90 lines
# LIQUID-UI-SPEC — Quadrille Showroom HUD elevation (Figma + 21st-dev sourced)
Source: 21st.dev "liquid glass" component inspiration (SVG-displacement technique) + Figma
(team::1520578458021404621, authenticated). Goal: kill the "juvenile" HUD — replace flat
chips / basic top bar / plain spec card with a **liquid-glass luxury** system that stays
**Phillip-Jeffries-grade** and **senior-usable**, WITHOUT tanking FPS over the live WebGL canvas.
## The hard performance rule (read first)
The showroom canvas re-renders every frame. A full SVG `feDisplacementMap` `backdrop-filter`
composited over it **every frame** will wreck FPS (Steve's "maximize FPS" rule). Therefore:
- **Tier 1 — always-on HUD (top bar, chips, slider rail):** CSS-only frosted glass.
`backdrop-filter: blur(14px) saturate(140%)` + translucent fill + 1px hairline + inset
specular highlight. Cheap, steady 60fps. This is the default for anything persistently visible.
- **Tier 2 — liquid refraction (the "wow"):** the real SVG `feTurbulence → feDisplacementMap`
glass, applied ONLY to **static / hover / open moments** — the spec card when it opens, a chip
on `:hover`/`:focus`, the slider thumb on grab. No per-frame cost because the underlying scene
is paused or the element is transient. NEVER put Tier 2 on a per-frame-updating full-width bar.
## Design tokens (drop into showroom.css `:root`)
```css
:root{
--glass-fill: rgba(22,22,24,0.46); /* smoked, not white — luxury reads dark */
--glass-fill-hi: rgba(255,255,255,0.06); /* faint top sheen layer */
--glass-stroke: rgba(255,255,255,0.14); /* 1px hairline */
--glass-blur: 14px;
--glass-sat: 140%;
--glass-shadow: 0 8px 30px rgba(0,0,0,0.38), 0 -10px 24px inset rgba(255,255,255,0.05);
--ink: #ece9e3; /* warm off-white text */
--ink-dim: rgba(236,233,227,0.62);
--accent: #b9935a; /* brushed-brass, the ONE metal accent */
--font-display: "Cormorant Garamond", "Times New Roman", serif; /* thin luxury display */
--font-ui: -apple-system, "Helvetica Neue", system-ui, sans-serif;
--tr: 420ms cubic-bezier(.22,.61,.36,1); /* slow morph, never snappy */
}
```
## Tier 1 — frosted glass base (top bar, chips, slider rail)
```css
.glass{
background: linear-gradient(var(--glass-fill-hi), transparent 60%), var(--glass-fill);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
border: 1px solid var(--glass-stroke);
border-radius: 16px;
box-shadow: var(--glass-shadow);
color: var(--ink);
}
/* Top bar: thin, letter-spaced display wordmark left, controls right, generous padding. */
/* Version/theme chips: pill .glass, ink-dim default, brass underline + ink on active;
morph the active indicator with --tr (transform, not width-jump). */
/* Open-angle slider: .glass rail, brass thumb, value label in --font-display. */
```
## Tier 2 — liquid refraction (spec card open / chip hover only)
Inject ONE shared SVG filter once (hidden `<svg>` at end of body), reference via class:
```html
<svg width="0" height="0" style="position:absolute"><defs>
<filter id="liquid" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.001 0.005" numOctaves="2" seed="7" result="noise"/>
<feGaussianBlur in="noise" stdDeviation="3" result="soft"/>
<feDisplacementMap in="SourceGraphic" in2="soft" scale="60" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs></svg>
```
```css
.liquid:hover, .spec-card.open{
-webkit-backdrop-filter: blur(10px) url(#liquid);
backdrop-filter: blur(10px) url(#liquid); /* scale kept modest (60) so refraction is elegant, not warped */
}
@media (prefers-reduced-motion: reduce){ .liquid:hover,.spec-card.open{ backdrop-filter: blur(12px) saturate(140%);} }
```
Note: `scale=200` (the 21st source) is too much for a UI panel — it smears text. Use **scale 40–70**.
Gate Tier 2 behind a feature flag + an FPS check: if measured FPS < 55 with it on, fall back to Tier 1.
## Senior-usable guardrails (do NOT regress)
- Min 16px UI text, 18px+ for the spec card body; hit targets ≥ 44px.
- Contrast: ink on smoked glass must clear APCA Lc ≥ 60 (run hero-readability-auditor on a screenshot).
- Motion slow (--tr ~420ms); respect prefers-reduced-motion (drop Tier 2 + shorten morphs).
- The big guided buttons (Prev / Auto-Tour / Next / All Designs) keep their large size — just reskin to .glass.
## Where it lands (next officer slice, AFTER geometry Slice 1 commits)
- `public/css/showroom.css` — tokens + `.glass` + `.liquid` + chip/topbar/slider/spec-card reskin.
- `public/js/showroom.js` / `versions.js` — add the hidden `<svg>` filter once; add `.glass`/`.liquid`
classes to the HUD nodes; FPS-gate Tier 2. (Officer is sole editor — do NOT touch while Slice 1 runs.)
- Verify with /screenrecord (5 combos) + /cta (Chrome+Safari — WebKit's `backdrop-filter: url()`
support is the #1 risk; Tier 1 blur is safe, Tier 2 url-filter must be WebKit-tested) → /contrarian gate.
```
```