Redesign the UI after the fitness reference and replace the logo

Light sage-to-cream ambient backdrop, frosted glass controls and painted
SceneArt landscapes in place of photography. Home becomes a dashboard with a
two-tone greeting, a frosted summary ring, category portraits, an up-next rail
of image-led cards and a floating glass tab bar. The routine library gets an
image header and painted cards; the player gets a scene stage, steps on an
arc, a dotted clock and a night pill with a lime spark. Goals become painted
cards in their own colour.

New logo: a lime sun resting on a ground line under two arcs. The splash
raises the sun and lights the arcs; launcher icons and native splash are
regenerated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
alvocool
2026-11-02 12:33:13 +03:00
parent b31bc47cc0
commit fe268cb8aa
100 changed files with 3394 additions and 1387 deletions

View File

@@ -0,0 +1,114 @@
import 'dart:math' as math;
import 'package:flutter/material.dart';
import '../../utils/Colors.dart';
/// Paints the landscape behind [SceneArt]. Everything is derived from the
/// palette and the seed, so the same routine always gets the same picture.
class ScenePainter extends CustomPainter {
final List<Color> palette;
final int seed;
ScenePainter(this.palette, this.seed);
@override
void paint(Canvas canvas, Size size) {
final double w = size.width;
final double h = size.height;
final math.Random random = math.Random(seed);
final Rect bounds = Offset.zero & size;
// Sky.
canvas.drawRect(
bounds,
Paint()
..shader = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[palette[0], palette[1]],
stops: const <double>[0.0, 0.62],
).createShader(bounds),
);
// Sun, with a wide soft glow.
final double radius = math.min(w, h) * (0.13 + random.nextDouble() * 0.05);
final Offset sun = Offset(
w * (0.18 + random.nextDouble() * 0.64),
h * (0.26 + random.nextDouble() * 0.12),
);
canvas.drawCircle(
sun,
radius * 2.6,
Paint()
..shader = RadialGradient(
colors: <Color>[
palette[2].withValues(alpha: 0.55),
palette[2].withValues(alpha: 0.0),
],
).createShader(Rect.fromCircle(center: sun, radius: radius * 2.6)),
);
canvas.drawCircle(sun, radius, Paint()..color = palette[2]);
// A haze band along the horizon so the far ridge sits back.
final Rect haze = Rect.fromLTWH(0, h * 0.42, w, h * 0.22);
canvas.drawRect(
haze,
Paint()
..shader = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[
colorWhite.withValues(alpha: 0.0),
colorWhite.withValues(alpha: 0.35),
colorWhite.withValues(alpha: 0.0),
],
).createShader(haze),
);
// Three ridges, far to near.
final List<double> baselines = <double>[0.56, 0.68, 0.82];
final List<double> amplitudes = <double>[0.07, 0.06, 0.05];
for (int layer = 0; layer < 3; layer++) {
final double base = h * baselines[layer];
final double amp = h * amplitudes[layer];
final double f1 = 0.8 + random.nextDouble() * 1.4;
final double f2 = 2.0 + random.nextDouble() * 2.5;
final double p1 = random.nextDouble() * math.pi * 2;
final double p2 = random.nextDouble() * math.pi * 2;
final Path ridge = Path()..moveTo(0, h);
const int steps = 36;
for (int i = 0; i <= steps; i++) {
final double x = w * i / steps;
final double t = i / steps;
final double y = base -
amp *
(math.sin(t * f1 * math.pi * 2 + p1) * 0.65 +
math.sin(t * f2 * math.pi * 2 + p2) * 0.35);
ridge.lineTo(x, y);
}
ridge
..lineTo(w, h)
..close();
final Color tone = palette[3 + layer];
canvas.drawPath(
ridge,
Paint()
..shader = LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: <Color>[tone, Color.lerp(tone, colorBlack, 0.18)!],
).createShader(Rect.fromLTWH(0, base - amp, w, h - base + amp)),
);
}
}
@override
bool shouldRepaint(covariant ScenePainter oldDelegate) {
return oldDelegate.seed != seed || oldDelegate.palette != palette;
}
}