UI-08: migrate theme to Modern Professional Dark

Replaces V3-16's safety-orange identity with the palette actually backing
every fetched Stitch mockup (confirmed by hex-matching against all three
design systems in the Stitch project, not by trusting its declared default).
Ground drops to #0a0a0c with a three-level surface/card stack, primary
becomes Professional Blue (#4090fe), and reference readings move to a new
tertiary orange since blue is no longer free for that role. Re-verified
theme_test.dart's AA contrast assertions against the new values (all pass
with wide margin) and left mounted mode's separate daylight palette alone,
per the ticket's own reasoning.

Also resolves the ticket's named risk: RideMap's speed-gradient hardcoded
hex literal, which would have washed to a single hue now that primary is
blue, now lerps from the theme's own secondary token instead.

Inter is not wired in -- no bundled font asset exists to back the family
name, and setting one without an asset would silently fall back to Roboto.
Documented as a deliberate follow-up rather than a silent gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Xki7YAcc2TiN2PRZJ2tXr
This commit is contained in:
2026-08-23 19:37:25 -05:00
parent 5ab4934a4d
commit 13017b8ee8
5 changed files with 145 additions and 31 deletions

View File

@@ -20,6 +20,7 @@ import 'package:latlong2/latlong.dart' as ll;
import '../../domain/models.dart';
import '../../geo/geo.dart' as geo;
import '../theme.dart' show ripprRadiusLarge;
/// Metres. Render-only: a 3-hour ride is ~21,600 points and would jank undecimated.
const double simplifyEpsilonM = 5.0;
@@ -176,7 +177,8 @@ class _RideMapState extends State<RideMap> with WidgetsBindingObserver {
final map = ClipRRect(
// The map draws to the edge of its box; clipping keeps it from painting over
// adjacent controls, which osmdroid did until it was explicitly bounded.
borderRadius: widget.fill ? BorderRadius.zero : BorderRadius.circular(12),
borderRadius:
widget.fill ? BorderRadius.zero : BorderRadius.circular(ripprRadiusLarge),
child: FlutterMap(
mapController: _controller,
options: MapOptions(
@@ -305,9 +307,16 @@ class _RideMapState extends State<RideMap> with WidgetsBindingObserver {
/// Cool for slow, accent for fast. A single hue ramp reads better through a visor than
/// a rainbow.
///
/// UI-08: was a hardcoded instrument-blue lerped to `colors.primary`. Under the old
/// safety-orange theme that gave a clean blue-to-orange ramp; under Modern
/// Professional Dark, `colors.primary` is itself blue, so lerping from another
/// hardcoded blue would wash the whole gradient into one indistinct hue. Lerping
/// from `colors.secondary` (the theme's own cool slate-blue) to `colors.primary`
/// (Professional Blue) keeps a real, theme-driven cool-to-accent ramp instead.
Color _colorFor(int bucket, double maxSpeed, ColorScheme colors) {
if (bucket < 0 || maxSpeed <= 0) return colors.primary;
final t = ((bucket * speedBucketKmh) / maxSpeed).clamp(0.0, 1.0);
return Color.lerp(const Color(0xFF4FC3F7), colors.primary, t)!;
return Color.lerp(colors.secondary, colors.primary, t)!;
}
}

View File

@@ -77,9 +77,10 @@ class StatRow extends StatelessWidget {
final String value;
/// V3-16: true for a figure you compare the live reading against (a max, an average)
/// rather than the live reading itself. Colours the value with the instrument-blue
/// tertiary instead of the default ink, so the same colour means the same kind of
/// number everywhere it appears. See the ticket's Direction section.
/// rather than the live reading itself. Colours the value with the theme's tertiary
/// (UI-08: a warm orange, chosen specifically to read as distinct from the new
/// Professional Blue primary) instead of the default ink, so the same colour means
/// the same kind of number everywhere it appears. See the ticket's Direction section.
final bool reference;
@override