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';
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),
],
),
),
),
],
),
);
},
);
}
}