The splash now raises the sun in the mark and over a painted valley, floats four feature chips around the mark, shimmers the wordmark, shows a three-beat "how it works" card, drifts light motes upward and walks a status line through the session check. A tap skips it. The web build hung on the native splash because the asset manifest could not be published: "assets/icons/splash_orb.png" is 27 characters, so its length prefix in AssetManifest.bin is an ESC byte. Renamed it to splash_mark.png and removed the unused anchor assets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
120 lines
3.7 KiB
Dart
120 lines
3.7 KiB
Dart
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;
|
|
|
|
/// 1 → the sun sits in its place; 0 → it is down behind the ridges.
|
|
final double sunLift;
|
|
|
|
ScenePainter(this.palette, this.seed, {this.sunLift = 1});
|
|
|
|
@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) + h * 0.5 * (1 - sunLift),
|
|
);
|
|
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 ||
|
|
oldDelegate.sunLift != sunLift;
|
|
}
|
|
}
|