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:
114
frontend/lib/Grounded/designs/scene/SceneArt.dart
Normal file
114
frontend/lib/Grounded/designs/scene/SceneArt.dart
Normal file
@@ -0,0 +1,114 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../utils/Colors.dart';
|
||||
import 'ScenePainter.dart';
|
||||
|
||||
/// A painted landscape that stands in for photography: sky, a low sun, three
|
||||
/// ridges and an oversized figure glyph standing in front of them. The [seed]
|
||||
/// reshapes the ridges and moves the sun, so two routines of the same kind
|
||||
/// never wear the identical picture.
|
||||
class SceneArt extends StatelessWidget {
|
||||
/// sky top, horizon, sun, far ridge, mid ridge, near ridge, ember.
|
||||
final List<Color> palette;
|
||||
|
||||
final int seed;
|
||||
|
||||
/// The glyph standing in the scene — the kind's icon, usually.
|
||||
final IconData? figure;
|
||||
|
||||
/// Figure size as a fraction of the shorter side.
|
||||
final double figureScale;
|
||||
|
||||
final Alignment figureAlignment;
|
||||
|
||||
/// Darkens the lower half so white type laid over it stays legible.
|
||||
final bool shade;
|
||||
|
||||
const SceneArt({
|
||||
super.key,
|
||||
required this.palette,
|
||||
this.seed = 0,
|
||||
this.figure,
|
||||
this.figureScale = 0.5,
|
||||
this.figureAlignment = const Alignment(0.35, 0.15),
|
||||
this.shade = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
final double shortest = math.min(
|
||||
constraints.maxWidth.isFinite ? constraints.maxWidth : 200,
|
||||
constraints.maxHeight.isFinite ? constraints.maxHeight : 200,
|
||||
);
|
||||
final double glyph = shortest * figureScale;
|
||||
|
||||
return RepaintBoundary(
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CustomPaint(painter: ScenePainter(palette, seed)),
|
||||
if (figure != null)
|
||||
Align(
|
||||
alignment: figureAlignment,
|
||||
child: SizedBox(
|
||||
width: glyph * 1.6,
|
||||
height: glyph * 1.6,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
// The ember halo behind the figure — the reference's
|
||||
// orange gi, reduced to light.
|
||||
Container(
|
||||
width: glyph * 1.5,
|
||||
height: glyph * 1.5,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
gradient: RadialGradient(
|
||||
colors: <Color>[
|
||||
palette[6].withValues(alpha: 0.45),
|
||||
palette[6].withValues(alpha: 0.0),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
figure,
|
||||
size: glyph,
|
||||
color: colorWhite.withValues(alpha: 0.96),
|
||||
shadows: <Shadow>[
|
||||
Shadow(
|
||||
color: palette[5].withValues(alpha: 0.55),
|
||||
blurRadius: glyph * 0.22,
|
||||
offset: Offset(0, glyph * 0.08),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (shade)
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const <double>[0.35, 1.0],
|
||||
colors: <Color>[
|
||||
colorBlack.withValues(alpha: 0.0),
|
||||
colorBlack.withValues(alpha: 0.55),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
114
frontend/lib/Grounded/designs/scene/ScenePainter.dart
Normal file
114
frontend/lib/Grounded/designs/scene/ScenePainter.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user