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
118 lines
3.0 KiB
Dart
118 lines
3.0 KiB
Dart
import 'dart:math' as math;
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../utils/Colors.dart';
|
|
|
|
/// The Grounded mark: a lime sun resting on a ground line beneath two
|
|
/// breathing arcs, on a night-green tile. Drawn, not bitmapped, so the splash
|
|
/// can raise the sun and light the arcs. Geometry matches the launcher icon.
|
|
class GroundedMark extends StatelessWidget {
|
|
final double size;
|
|
|
|
/// 0 → the sun is below the line; 1 → fully risen.
|
|
final double rise;
|
|
|
|
/// 0 → arcs hidden; 1 → both lit. The inner arc lights first.
|
|
final double arcs;
|
|
|
|
final bool tile;
|
|
|
|
const GroundedMark({
|
|
super.key,
|
|
this.size = 120,
|
|
this.rise = 1,
|
|
this.arcs = 1,
|
|
this.tile = true,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return CustomPaint(
|
|
size: Size.square(size),
|
|
painter: _MarkPainter(rise, arcs, tile),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _MarkPainter extends CustomPainter {
|
|
final double rise;
|
|
|
|
final double arcs;
|
|
|
|
final bool tile;
|
|
|
|
_MarkPainter(this.rise, this.arcs, this.tile);
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
final double u = size.width / 1024;
|
|
final Rect bounds = Offset.zero & size;
|
|
|
|
if (tile) {
|
|
canvas.drawRRect(
|
|
RRect.fromRectAndRadius(bounds, Radius.circular(size.width * 0.235)),
|
|
Paint()
|
|
..shader = const LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: <Color>[colorNightLift, colorNight],
|
|
).createShader(bounds),
|
|
);
|
|
}
|
|
|
|
final double cx = 512 * u;
|
|
final double horizon = 630 * u;
|
|
|
|
// Arcs — inner then outer.
|
|
final List<double> radii = <double>[250, 330];
|
|
final List<double> alphas = <double>[0.6, 0.28];
|
|
for (int i = 0; i < 2; i++) {
|
|
final double lit = ((arcs * 2) - i).clamp(0.0, 1.0);
|
|
if (lit <= 0) {
|
|
continue;
|
|
}
|
|
final double r = radii[i] * u;
|
|
canvas.drawArc(
|
|
Rect.fromCircle(center: Offset(cx, horizon), radius: r - 15 * u),
|
|
math.pi * 198 / 180,
|
|
math.pi * 144 / 180,
|
|
false,
|
|
Paint()
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 30 * u
|
|
..strokeCap = StrokeCap.round
|
|
..color = colorAmbientMid.withValues(alpha: alphas[i] * lit),
|
|
);
|
|
}
|
|
|
|
// The sun, clipped at the horizon so it rises out of the ground.
|
|
final double radius = 168 * u;
|
|
canvas.save();
|
|
canvas.clipRect(Rect.fromLTRB(0, 0, size.width, horizon));
|
|
canvas.drawCircle(
|
|
Offset(cx, horizon + radius * 2 * (1 - rise)),
|
|
radius,
|
|
Paint()..color = colorLime,
|
|
);
|
|
canvas.restore();
|
|
|
|
// Ground line.
|
|
final double ground = horizon + 52 * u;
|
|
canvas.drawRRect(
|
|
RRect.fromRectAndRadius(
|
|
Rect.fromLTRB(250 * u, ground - 20 * u, 774 * u, ground + 20 * u),
|
|
Radius.circular(20 * u),
|
|
),
|
|
Paint()..color = colorAmbientMid,
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant _MarkPainter oldDelegate) {
|
|
return oldDelegate.rise != rise ||
|
|
oldDelegate.arcs != arcs ||
|
|
oldDelegate.tile != tile;
|
|
}
|
|
}
|