polish(ux): AA-contrast chip inks, 14-tone palette, calendar month auto-scroll

Blind-build polish pass over the recent sales/chips/calendar work:

- Chip label contrast: 7 palette inks (terracotta, ochre, rose, seedling,
  plant, bulb, cutting) sat below WCAG AA on their own fill; darkened
  (hue preserved) to ≥4.8:1. New category_palette_contrast_test locks it.
- Palette expanded 8 → 14 earthy tones (hues spread around the wheel) so
  family colours rarely collide for a typical 6–10 family inventory,
  keeping the stable-by-name, cross-screen-consistent scheme. Every new
  ink meets AA on its fill.
- Calendar month strip now actually scrolls the selected month into view
  (the comment promised it but it was never wired) — late-year months are
  no longer left off-screen when the screen opens.
- Minor: dead code removed from the month chip.

dart analyze clean; palette/contrast/calendar/quantity/overflow/inventory
suites green.
This commit is contained in:
vjrj 2026-07-11 06:48:19 +02:00
parent a476f17cdf
commit 62ed4f79b3
3 changed files with 97 additions and 16 deletions

View file

@ -0,0 +1,38 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tane/db/enums.dart';
import 'package:tane/ui/category_palette.dart';
/// Chip labels are drawn in [Swatch.ink] over [Swatch.fill]; that pairing must
/// clear WCAG AA for normal text (4.5:1), like the rest of the palette
/// (see theme_contrast_test.dart). Locks the tuned inks against regressions.
void main() {
double lin(double c) =>
c <= 0.03928 ? c / 12.92 : math.pow((c + 0.055) / 1.055, 2.4).toDouble();
double luminance(Color c) =>
0.2126 * lin(c.r) + 0.7152 * lin(c.g) + 0.0722 * lin(c.b);
double contrast(Color a, Color b) {
final la = luminance(a), lb = luminance(b);
final hi = math.max(la, lb), lo = math.min(la, lb);
return (hi + 0.05) / (lo + 0.05);
}
test('every category swatch label meets AA on its fill', () {
// Cover the whole palette by probing many distinct category names.
for (var i = 0; i < 60; i++) {
final s = categorySwatch('family-$i');
expect(contrast(s.ink, s.fill), greaterThanOrEqualTo(4.5),
reason: 'category-$i ink on fill');
}
});
test('every lot-form swatch label meets AA on its fill', () {
for (final type in LotType.values) {
final s = lotTypeSwatch(type);
expect(contrast(s.ink, s.fill), greaterThanOrEqualTo(4.5),
reason: '$type ink on fill');
}
});
}