Files
picpeak/frontend/src/utils
Paul Nothaft 0c80abd57b fix(gallery): WCAG-safe Download button text + extract HeaderDownloadButton (#401 follow-ups)
Two follow-ups from PR #401's review:

1. Download button text was hardcoded `color: '#ffffff'`. Once admins
   start picking palettes via #400's expanded customizer, a pale accent
   (yellow, pastel blue, etc.) leaves the button unreadable — white
   text on near-white background.

   Fix: derive the foreground colour from the accent's WCAG relative
   luminance and expose it as the new `--color-accent-fg` CSS variable
   in ThemeContext.applyTheme. Light backgrounds (L >= 0.5) get black
   text; dark backgrounds get white. Same treatment applied to
   `--color-accent-dark-fg` for the filled-CTA token.

   The Download button now reads `var(--color-accent-fg, #ffffff)` so
   any future component that paints on accent gets the same treatment
   for free, and legacy deployments before the variable is set fall
   back to the previous hardcoded white.

   Threshold-based (rather than "highest contrast ratio") to preserve
   how saturated mid-tone accents have always rendered. The Picpeak
   default green (#5C8762, L≈0.20) keeps white text — same visual
   identity as before. Only genuinely pale accents flip to black,
   which is the actual scenario the review flagged.

2. The Download button JSX was duplicated three times in
   GalleryLayout.tsx (standard/banner, minimal, hero — ~15 lines
   each). Extracted into a small inline `HeaderDownloadButton`
   component above the GalleryLayout export. Three call sites now
   collapse to a 5-line component invocation each. Markup,
   accessibility, and styling live in one place — future tweaks
   only need to happen once.

## Files

- `frontend/src/utils/contrast.ts` — new helper module:
  `relativeLuminance(hex)` (WCAG 2.x sRGB luminance) and
  `getReadableForeground(hex)` (white-or-black picker).
- `frontend/src/utils/__tests__/contrast.test.ts` — 10 cases:
  fallbacks, saturated mid-tones, pale accents, near-black,
  shorthand `#RGB`, no-leading-`#`, case-insensitive, anchors
  (black/white luminance).
- `frontend/src/contexts/ThemeContext.tsx` — wire the helper into
  `applyTheme`: set `--color-accent-fg` from `accentColor` and
  `--color-accent-dark-fg` from `accentDarkColor`/`primaryColor`.
- `frontend/src/components/gallery/GalleryLayout.tsx` — extract
  `HeaderDownloadButton` component above `GalleryLayout`, replace
  three inline button blocks with the component, update its inline
  style to read `--color-accent-fg` (with the legacy `#ffffff` as
  the CSS-variable fallback).

## Verified

- `npx vitest run src/utils/__tests__/contrast.test.ts` — 10/10 pass
- `npx tsc --noEmit` — clean
- `npx eslint` clean on every touched file
- Default PicPeak green still renders white text (no regression)
- Pale accent (#fef9c3 yellow-100) now correctly renders black text
2026-05-07 11:42:25 +02:00
..