fix(frontend): raise dark contrast, expand hit areas, and surface input limits
Independent Staging Quality Gate / validate (push) Successful in 9m19s
Independent Staging Quality Gate / publish (push) Successful in 9m54s

Dark muted surfaces missed WCAG AA; action and tertiary tokens plus a 32-pair contract close that. Hit targets, remaining-count, and Enter-to-send follow the interaction audit without changing visual sizes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-30 11:19:00 +08:00
parent b43808b016
commit e8fa1ce4ea
20 changed files with 555 additions and 40 deletions
+8 -5
View File
@@ -94,16 +94,19 @@ identical.
| Page floor | `#f3f2ee` | `#1a1a19` | On light, raised surfaces step *down* in lightness; on dark they step *up*. The floor is the darkest surface in dark mode, the second-lightest in light mode. |
| Reading surface | `#fbfaf7` | `#262624` | Warm, never neutral black. A cool grey would change the product's identity rather than its brightness. |
| Warm card | `#ebe9e3` | `#30302d` | One step above the reading surface in both themes, by opposite directions. |
| Primary action | `#85432f` | `#d4785a` | The clay hue is kept and lifted. `#85432f` is 2.1:1 on `#262624` — unreadable. |
| Primary action | `#85432f` | `#d78064` | The clay hue is kept and lifted. `#85432f` is 2.1:1 on `#262624` — unreadable. |
| Action hover | `#6f3627` | `#e59273` | Hover darkens on light and lightens on dark. |
| Dark punctuation | `#1d1d1f` | `#f2f0ea` | A dark block on a light page becomes a light block on a dark page; the token name describes the light-theme value, the role is "highest-contrast block". |
| Semantic hues | `#9a2f2f` / `#28633e` / `#b07b22` | `#e08573` / `#74b189` / `#d8a758` | Lifted for contrast; their `-muted` pairs stop being pale washes and become dark tints of the same hue. |
| Scrim | `rgba(29,29,31,.34)` | `rgba(0,0,0,.58)` | A scrim has to darken a surface that is already dark. |
| Shadow | low alpha | ~5× alpha | Without a light ground to fall on, a soft shadow registers as nothing. |
Contrast is asserted, not assumed: ink, action, danger, success and warning must
all clear 4.5:1 against the dark reading surface, and the four neutral surfaces
must increase in lightness in floor → canvasmuted → strong order.
Contrast is asserted, not assumed: eight inks (ink, ink-strong, ink-secondary,
ink-tertiary, action, danger, success, warning) must all clear 4.5:1 against
four dark surfaces (canvas-soft, canvas, canvas-muted, sidebar-solid). The four
neutral surfaces must increase in lightness in floor → canvas → muted → strong
order. Adding a text or surface token means adding it to that 32-pair matrix;
checking only the reading surface is not enough.
**Surfaces that do not follow the theme, on purpose:**
@@ -162,7 +165,7 @@ The base unit is 4px. Tokens are `--space-1: 4px`, `--space-2: 8px`, `--space-3:
- Desktop shell: 288px sidebar plus flexible reading panel.
- Layout breakpoints: mobile below 768px, tablet 7681023px, desktop 1024px and above. These three decide the shell — sidebar mode, grid columns, drawer versus rail.
- Component breakpoints are separate and narrower, each owned by one surface rather than the shell: 900px and 480px (redemption and profile forms), 760px and 720px (personal report and report centre), 640px (rectification candidates), 639px (membership header), 620px (onboarding cards), 430px (toasts and the conversational rectification panel). Do not repurpose one for another surface, and do not assume testing the three layout tiers covers them.
- All full-height surfaces use `100dvh`. Touch targets are at least 44px.
- All full-height surfaces use `100dvh`. Touch targets are at least 44px. Where adjacent controls sit closer than 44px, the hit expands to the largest size that does not overlap; those exceptions are locked by `frontend/tests/touch-target-contract.test.ts`.
## 5. Components