neoagent 2.4.1-beta.19 → 2.4.1-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +4 -1
  2. package/docs/getting-started.md +9 -3
  3. package/flutter_app/assets/branding/app_icon_light_1024.png +0 -0
  4. package/flutter_app/assets/branding/app_icon_light_128.png +0 -0
  5. package/flutter_app/assets/branding/app_icon_light_192.png +0 -0
  6. package/flutter_app/assets/branding/app_icon_light_256.png +0 -0
  7. package/flutter_app/assets/branding/app_icon_light_32.png +0 -0
  8. package/flutter_app/assets/branding/app_icon_light_512.png +0 -0
  9. package/flutter_app/assets/branding/app_icon_light_64.png +0 -0
  10. package/flutter_app/assets/branding/tray_icon_light_template.png +0 -0
  11. package/flutter_app/lib/features/location/location_service.dart +3 -0
  12. package/flutter_app/lib/main.dart +1 -0
  13. package/flutter_app/lib/main_account_settings.dart +9 -33
  14. package/flutter_app/lib/main_app_shell.dart +237 -197
  15. package/flutter_app/lib/main_controller.dart +0 -25
  16. package/flutter_app/lib/main_devices.dart +2 -0
  17. package/flutter_app/lib/main_models.dart +144 -0
  18. package/flutter_app/lib/main_operations.dart +150 -19
  19. package/flutter_app/lib/main_shared.dart +642 -195
  20. package/flutter_app/lib/main_theme.dart +2 -0
  21. package/flutter_app/lib/src/android_apk_drop_zone_web.dart +3 -1
  22. package/flutter_app/lib/src/security/password_strength.dart +84 -0
  23. package/flutter_app/lib/src/theme/palette.dart +15 -15
  24. package/flutter_app/pubspec.yaml +3 -0
  25. package/flutter_app/web/favicon_light.svg +3 -0
  26. package/flutter_app/web/icons/Icon-192-light.png +0 -0
  27. package/flutter_app/web/icons/Icon-512-light.png +0 -0
  28. package/flutter_app/web/icons/Icon-maskable-192-light.png +0 -0
  29. package/flutter_app/web/icons/Icon-maskable-512-light.png +0 -0
  30. package/lib/manager.js +282 -81
  31. package/package.json +17 -3
  32. package/server/config/origins.js +3 -1
  33. package/server/db/database.js +73 -0
  34. package/server/public/.last_build_id +1 -1
  35. package/server/public/assets/AssetManifest.bin +1 -1
  36. package/server/public/assets/AssetManifest.bin.json +1 -1
  37. package/server/public/assets/assets/branding/app_icon_light_256.png +0 -0
  38. package/server/public/assets/assets/branding/app_icon_light_512.png +0 -0
  39. package/server/public/assets/assets/branding/tray_icon_light_template.png +0 -0
  40. package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
  41. package/server/public/favicon_light.svg +3 -0
  42. package/server/public/flutter_bootstrap.js +1 -1
  43. package/server/public/icons/Icon-192-light.png +0 -0
  44. package/server/public/icons/Icon-512-light.png +0 -0
  45. package/server/public/icons/Icon-maskable-192-light.png +0 -0
  46. package/server/public/icons/Icon-maskable-512-light.png +0 -0
  47. package/server/public/main.dart.js +68769 -68268
  48. package/server/routes/agent_profiles.js +3 -0
  49. package/server/routes/memory.js +22 -1
  50. package/server/services/account/password_policy.js +6 -1
  51. package/server/services/memory/intelligence.js +181 -0
  52. package/server/services/memory/manager.js +475 -25
  53. package/server/utils/security.js +3 -0
  54. package/server/services/memory/openhuman_uplift.test.js +0 -98
  55. package/server/utils/version.test.js +0 -39
@@ -11,6 +11,8 @@ EdgeInsets _pagePadding(BuildContext context) {
11
11
  return const EdgeInsets.fromLTRB(20, 20, 20, 28);
12
12
  }
13
13
 
14
+ final ValueNotifier<bool> _partyModeEnabled = ValueNotifier<bool>(false);
15
+
14
16
  class _AmbientBackdrop extends StatefulWidget {
15
17
  const _AmbientBackdrop({required this.child});
16
18
 
@@ -41,81 +43,241 @@ class _AmbientBackdropState extends State<_AmbientBackdrop>
41
43
 
42
44
  @override
43
45
  Widget build(BuildContext context) {
44
- return DecoratedBox(
45
- decoration: BoxDecoration(gradient: _appBackgroundGradient),
46
- child: AnimatedBuilder(
47
- animation: _controller,
48
- builder: (context, _) {
49
- final t = Curves.easeInOut.transform(_controller.value);
50
- return Stack(
51
- children: <Widget>[
52
- Positioned(
53
- top: -120 + (t * 22),
54
- left: -90 + (t * 18),
55
- child: _BlurOrb(
56
- size: 340,
57
- color: _accent.withValues(alpha: 0.9),
58
- ),
59
- ),
60
- Positioned(
61
- top: 90 - (t * 26),
62
- right: -120 + (t * 22),
63
- child: _BlurOrb(
64
- size: 280,
65
- color: _accentAlt.withValues(alpha: 0.85),
66
- ),
67
- ),
68
- Positioned(
69
- bottom: -140 + (t * 16),
70
- left: 100 - (t * 24),
71
- child: _BlurOrb(
72
- size: 360,
73
- color: _accent.withValues(alpha: 0.45),
74
- ),
75
- ),
76
- Positioned.fill(
77
- child: IgnorePointer(
78
- child: DecoratedBox(
79
- decoration: BoxDecoration(
80
- gradient: LinearGradient(
81
- colors: <Color>[
82
- Colors.white.withValues(alpha: 0.05),
83
- Colors.transparent,
84
- Colors.black.withValues(alpha: 0.12),
85
- ],
86
- stops: const <double>[0, 0.32, 1],
87
- begin: Alignment.topCenter,
88
- end: Alignment.bottomCenter,
46
+ return ValueListenableBuilder<bool>(
47
+ valueListenable: _partyModeEnabled,
48
+ builder: (context, partyMode, _) {
49
+ return DecoratedBox(
50
+ decoration: BoxDecoration(gradient: _appBackgroundGradient),
51
+ child: AnimatedBuilder(
52
+ animation: _controller,
53
+ builder: (context, _) {
54
+ final t = Curves.easeInOut.transform(_controller.value);
55
+ return Stack(
56
+ children: <Widget>[
57
+ Positioned.fill(
58
+ child: IgnorePointer(
59
+ child: CustomPaint(
60
+ painter: _AuroraFieldPainter(
61
+ progress: t,
62
+ partyMode: partyMode,
63
+ primary: _accent,
64
+ secondary: _accentAlt,
65
+ base: _bgPrimary,
66
+ ),
89
67
  ),
90
68
  ),
91
69
  ),
92
- ),
93
- ),
94
- Positioned.fill(
95
- child: IgnorePointer(
96
- child: DecoratedBox(
97
- decoration: BoxDecoration(
98
- gradient: RadialGradient(
99
- center: Alignment(0.75 - (t * 0.15), -0.9 + (t * 0.1)),
100
- radius: 0.95,
101
- colors: <Color>[
102
- _glassHighlight.withValues(alpha: 0.14),
103
- Colors.transparent,
104
- ],
70
+ Positioned.fill(
71
+ child: IgnorePointer(
72
+ child: CustomPaint(
73
+ painter: _ArcadeGridPainter(
74
+ progress: t,
75
+ color: _accentAlt,
76
+ partyMode: partyMode,
77
+ ),
105
78
  ),
106
79
  ),
107
80
  ),
108
- ),
109
- ),
110
- widget.child,
111
- ],
112
- );
113
- },
114
- ),
81
+ Positioned.fill(
82
+ child: IgnorePointer(
83
+ child: DecoratedBox(
84
+ decoration: BoxDecoration(
85
+ gradient: LinearGradient(
86
+ colors: <Color>[
87
+ Colors.white.withValues(alpha: 0.05),
88
+ Colors.transparent,
89
+ Colors.black.withValues(alpha: 0.12),
90
+ ],
91
+ stops: const <double>[0, 0.32, 1],
92
+ begin: Alignment.topCenter,
93
+ end: Alignment.bottomCenter,
94
+ ),
95
+ ),
96
+ ),
97
+ ),
98
+ ),
99
+ Positioned.fill(
100
+ child: IgnorePointer(
101
+ child: CustomPaint(
102
+ painter: _ConfettiBitsPainter(
103
+ progress: t,
104
+ active: partyMode,
105
+ primary: _accent,
106
+ secondary: _accentAlt,
107
+ ),
108
+ ),
109
+ ),
110
+ ),
111
+ widget.child,
112
+ ],
113
+ );
114
+ },
115
+ ),
116
+ );
117
+ },
115
118
  );
116
119
  }
117
120
  }
118
121
 
122
+ class _AuroraFieldPainter extends CustomPainter {
123
+ const _AuroraFieldPainter({
124
+ required this.progress,
125
+ required this.partyMode,
126
+ required this.primary,
127
+ required this.secondary,
128
+ required this.base,
129
+ });
130
+
131
+ final double progress;
132
+ final bool partyMode;
133
+ final Color primary;
134
+ final Color secondary;
135
+ final Color base;
136
+
137
+ @override
138
+ void paint(Canvas canvas, Size size) {
139
+ final rect = Offset.zero & size;
140
+ final energy = partyMode ? 1.45 : 1.0;
141
+ final wash = Paint()
142
+ ..shader = LinearGradient(
143
+ colors: <Color>[
144
+ primary.withValues(alpha: 0.18 * energy),
145
+ secondary.withValues(alpha: 0.12 * energy),
146
+ base.withValues(alpha: 0),
147
+ ],
148
+ stops: const <double>[0, 0.42, 1],
149
+ begin: Alignment(-0.95 + progress * 0.35, -1),
150
+ end: Alignment(0.85 - progress * 0.25, 1),
151
+ ).createShader(rect);
152
+ canvas.drawRect(rect, wash);
153
+
154
+ for (var lane = 0; lane < 4; lane++) {
155
+ final yBase = size.height * (0.16 + lane * 0.18);
156
+ final path = Path()..moveTo(-size.width * 0.12, yBase);
157
+ for (var i = 0; i <= 8; i++) {
158
+ final x = size.width * (i / 8);
159
+ final phase = progress * math.pi * 2 + lane * 0.9 + i * 0.28;
160
+ final y = yBase + math.sin(phase) * (28 + lane * 9) * energy;
161
+ path.lineTo(x, y);
162
+ }
163
+ path.lineTo(size.width * 1.12, yBase + size.height * 0.34);
164
+ path.lineTo(-size.width * 0.12, yBase + size.height * 0.28);
165
+ path.close();
166
+
167
+ final lanePaint = Paint()
168
+ ..shader = LinearGradient(
169
+ colors: <Color>[
170
+ (lane.isEven ? primary : secondary).withValues(
171
+ alpha: (0.07 + lane * 0.012) * energy,
172
+ ),
173
+ Colors.transparent,
174
+ ],
175
+ begin: Alignment.topLeft,
176
+ end: Alignment.bottomRight,
177
+ ).createShader(rect)
178
+ ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 42);
179
+ canvas.drawPath(path, lanePaint);
180
+ }
181
+ }
182
+
183
+ @override
184
+ bool shouldRepaint(covariant _AuroraFieldPainter oldDelegate) {
185
+ return oldDelegate.progress != progress ||
186
+ oldDelegate.partyMode != partyMode ||
187
+ oldDelegate.primary != primary ||
188
+ oldDelegate.secondary != secondary ||
189
+ oldDelegate.base != base;
190
+ }
191
+ }
192
+
193
+ class _ArcadeGridPainter extends CustomPainter {
194
+ const _ArcadeGridPainter({
195
+ required this.progress,
196
+ required this.color,
197
+ required this.partyMode,
198
+ });
199
+
200
+ final double progress;
201
+ final Color color;
202
+ final bool partyMode;
203
+
204
+ @override
205
+ void paint(Canvas canvas, Size size) {
206
+ final paint = Paint()
207
+ ..color = color.withValues(alpha: partyMode ? 0.13 : 0.055)
208
+ ..strokeWidth = 1;
209
+ const step = 48.0;
210
+ final drift = progress * step;
211
+ for (double x = -step + drift; x < size.width + step; x += step) {
212
+ canvas.drawLine(
213
+ Offset(x, 0),
214
+ Offset(x - size.width * 0.08, size.height),
215
+ paint,
216
+ );
217
+ }
218
+ for (double y = size.height * 0.58; y < size.height + step; y += step) {
219
+ canvas.drawLine(
220
+ Offset(0, y),
221
+ Offset(size.width, y - progress * 12),
222
+ paint,
223
+ );
224
+ }
225
+ }
226
+
227
+ @override
228
+ bool shouldRepaint(covariant _ArcadeGridPainter oldDelegate) {
229
+ return oldDelegate.progress != progress ||
230
+ oldDelegate.color != color ||
231
+ oldDelegate.partyMode != partyMode;
232
+ }
233
+ }
234
+
235
+ class _ConfettiBitsPainter extends CustomPainter {
236
+ const _ConfettiBitsPainter({
237
+ required this.progress,
238
+ required this.active,
239
+ required this.primary,
240
+ required this.secondary,
241
+ });
242
+
243
+ final double progress;
244
+ final bool active;
245
+ final Color primary;
246
+ final Color secondary;
247
+
248
+ @override
249
+ void paint(Canvas canvas, Size size) {
250
+ if (!active) return;
251
+ final paint = Paint()..style = PaintingStyle.fill;
252
+ for (var i = 0; i < 26; i++) {
253
+ final seed = i * 37.0;
254
+ final x = ((seed * 17 + progress * size.width * 0.35) % size.width);
255
+ final y = ((seed * 29 + progress * size.height * 0.9) % size.height);
256
+ final color = i.isEven ? primary : secondary;
257
+ paint.color = color.withValues(alpha: 0.16 + (i % 4) * 0.035);
258
+ canvas.save();
259
+ canvas.translate(x, y);
260
+ canvas.rotate(progress * math.pi * 2 + i);
261
+ canvas.drawRRect(
262
+ RRect.fromRectAndRadius(
263
+ Rect.fromCenter(center: Offset.zero, width: 5 + (i % 3), height: 2.5),
264
+ const Radius.circular(2),
265
+ ),
266
+ paint,
267
+ );
268
+ canvas.restore();
269
+ }
270
+ }
271
+
272
+ @override
273
+ bool shouldRepaint(covariant _ConfettiBitsPainter oldDelegate) {
274
+ return oldDelegate.progress != progress ||
275
+ oldDelegate.active != active ||
276
+ oldDelegate.primary != primary ||
277
+ oldDelegate.secondary != secondary;
278
+ }
279
+ }
280
+
119
281
  class _EntranceMotion extends StatefulWidget {
120
282
  const _EntranceMotion({required this.child});
121
283
 
@@ -548,6 +710,7 @@ class _ToolEventTimelineRow extends StatelessWidget {
548
710
  @override
549
711
  Widget build(BuildContext context) {
550
712
  Color color;
713
+ final running = tool.status == 'running';
551
714
  switch (tool.status) {
552
715
  case 'running':
553
716
  color = _warning;
@@ -565,14 +728,26 @@ class _ToolEventTimelineRow extends StatelessWidget {
565
728
  width: 28,
566
729
  child: Column(
567
730
  children: <Widget>[
568
- Container(
569
- width: 28,
570
- height: 28,
571
- decoration: BoxDecoration(
572
- color: color.withValues(alpha: 0.14),
573
- shape: BoxShape.circle,
731
+ _PulseHalo(
732
+ color: color,
733
+ animate: running,
734
+ child: Container(
735
+ width: 28,
736
+ height: 28,
737
+ decoration: BoxDecoration(
738
+ gradient: LinearGradient(
739
+ colors: <Color>[
740
+ color.withValues(alpha: running ? 0.26 : 0.16),
741
+ color.withValues(alpha: 0.08),
742
+ ],
743
+ begin: Alignment.topLeft,
744
+ end: Alignment.bottomRight,
745
+ ),
746
+ shape: BoxShape.circle,
747
+ border: Border.all(color: color.withValues(alpha: 0.24)),
748
+ ),
749
+ child: Icon(tool.laneIcon, size: 16, color: color),
574
750
  ),
575
- child: Icon(tool.laneIcon, size: 16, color: color),
576
751
  ),
577
752
  if (!isLast)
578
753
  Container(
@@ -592,9 +767,18 @@ class _ToolEventTimelineRow extends StatelessWidget {
592
767
  child: Container(
593
768
  padding: const EdgeInsets.all(12),
594
769
  decoration: BoxDecoration(
595
- color: _bgSecondary,
770
+ gradient: LinearGradient(
771
+ colors: <Color>[
772
+ _bgSecondary,
773
+ if (running) color.withValues(alpha: 0.055) else _bgCard,
774
+ ],
775
+ begin: Alignment.topLeft,
776
+ end: Alignment.bottomRight,
777
+ ),
596
778
  borderRadius: BorderRadius.circular(14),
597
- border: Border.all(color: _border),
779
+ border: Border.all(
780
+ color: running ? color.withValues(alpha: 0.25) : _border,
781
+ ),
598
782
  ),
599
783
  child: Column(
600
784
  crossAxisAlignment: CrossAxisAlignment.start,
@@ -770,28 +954,119 @@ class _DotStatus extends StatelessWidget {
770
954
 
771
955
  @override
772
956
  Widget build(BuildContext context) {
773
- return _GlassSurface(
774
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
775
- borderRadius: BorderRadius.circular(999),
776
- blurSigma: 16,
777
- fillColor: _bgSecondary.withValues(alpha: 0.28),
778
- borderColor: _glassBorder.withValues(alpha: 0.8),
779
- child: Row(
780
- mainAxisSize: MainAxisSize.min,
781
- children: <Widget>[
782
- Container(
783
- width: 8,
784
- height: 8,
785
- decoration: BoxDecoration(color: color, shape: BoxShape.circle),
786
- ),
787
- const SizedBox(width: 8),
788
- Text(label),
789
- ],
957
+ return _PulseHalo(
958
+ color: color,
959
+ child: _GlassSurface(
960
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
961
+ borderRadius: BorderRadius.circular(999),
962
+ blurSigma: 16,
963
+ fillColor: _bgSecondary.withValues(alpha: 0.3),
964
+ borderColor: color.withValues(alpha: 0.24),
965
+ child: Row(
966
+ mainAxisSize: MainAxisSize.min,
967
+ children: <Widget>[
968
+ Container(
969
+ width: 8,
970
+ height: 8,
971
+ decoration: BoxDecoration(
972
+ color: color,
973
+ shape: BoxShape.circle,
974
+ boxShadow: <BoxShadow>[
975
+ BoxShadow(
976
+ color: color.withValues(alpha: 0.42),
977
+ blurRadius: 9,
978
+ spreadRadius: 1,
979
+ ),
980
+ ],
981
+ ),
982
+ ),
983
+ const SizedBox(width: 8),
984
+ Text(label),
985
+ ],
986
+ ),
790
987
  ),
791
988
  );
792
989
  }
793
990
  }
794
991
 
992
+ class _PulseHalo extends StatefulWidget {
993
+ const _PulseHalo({
994
+ required this.child,
995
+ required this.color,
996
+ this.animate = true,
997
+ });
998
+
999
+ final Widget child;
1000
+ final Color color;
1001
+ final bool animate;
1002
+
1003
+ @override
1004
+ State<_PulseHalo> createState() => _PulseHaloState();
1005
+ }
1006
+
1007
+ class _PulseHaloState extends State<_PulseHalo>
1008
+ with SingleTickerProviderStateMixin {
1009
+ late final AnimationController _controller;
1010
+
1011
+ @override
1012
+ void initState() {
1013
+ super.initState();
1014
+ _controller = AnimationController(
1015
+ vsync: this,
1016
+ duration: const Duration(milliseconds: 1600),
1017
+ );
1018
+ if (widget.animate) {
1019
+ _controller.repeat(reverse: true);
1020
+ }
1021
+ }
1022
+
1023
+ @override
1024
+ void didUpdateWidget(_PulseHalo oldWidget) {
1025
+ super.didUpdateWidget(oldWidget);
1026
+ if (widget.animate != oldWidget.animate) {
1027
+ if (widget.animate) {
1028
+ _controller.repeat(reverse: true);
1029
+ } else {
1030
+ _controller.stop();
1031
+ _controller.value = 0.0;
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ @override
1037
+ void dispose() {
1038
+ _controller.dispose();
1039
+ super.dispose();
1040
+ }
1041
+
1042
+ @override
1043
+ Widget build(BuildContext context) {
1044
+ if (!widget.animate) {
1045
+ return widget.child;
1046
+ }
1047
+ return AnimatedBuilder(
1048
+ animation: _controller,
1049
+ builder: (context, child) {
1050
+ final t = Curves.easeInOut.transform(_controller.value);
1051
+ return DecoratedBox(
1052
+ decoration: BoxDecoration(
1053
+ borderRadius: BorderRadius.circular(999),
1054
+ boxShadow: <BoxShadow>[
1055
+ BoxShadow(
1056
+ color: widget.color.withValues(alpha: 0.04 + t * 0.08),
1057
+ blurRadius: 12 + t * 10,
1058
+ spreadRadius: t * 1.5,
1059
+ ),
1060
+ ],
1061
+ ),
1062
+ child: child,
1063
+ );
1064
+ },
1065
+ child: widget.child,
1066
+ );
1067
+ }
1068
+ }
1069
+
795
1070
  class _SidebarButton extends StatelessWidget {
796
1071
  const _SidebarButton({
797
1072
  required this.label,
@@ -817,25 +1092,23 @@ class _SidebarButton extends StatelessWidget {
817
1092
  Widget build(BuildContext context) {
818
1093
  return Padding(
819
1094
  padding: const EdgeInsets.only(bottom: 6),
820
- child: AnimatedScale(
821
- duration: const Duration(milliseconds: 220),
822
- curve: Curves.easeOutCubic,
823
- scale: active ? 1.01 : 1,
1095
+ child: _HoverLift(
1096
+ active: active,
824
1097
  child: _GlassSurface(
825
1098
  borderRadius: BorderRadius.circular(18),
826
- blurSigma: 18,
1099
+ blurSigma: active ? 22 : 18,
827
1100
  fillColor: active
828
- ? _accentMuted.withValues(alpha: 0.32)
829
- : _bgCard.withValues(alpha: 0.2),
1101
+ ? _accentMuted.withValues(alpha: 0.36)
1102
+ : _bgCard.withValues(alpha: 0.22),
830
1103
  borderColor: active
831
- ? _accent.withValues(alpha: 0.32)
832
- : Colors.white.withValues(alpha: 0.03),
1104
+ ? _accent.withValues(alpha: 0.45)
1105
+ : Colors.white.withValues(alpha: 0.04),
833
1106
  boxShadow: active
834
1107
  ? <BoxShadow>[
835
1108
  BoxShadow(
836
- color: _accent.withValues(alpha: 0.12),
837
- blurRadius: 22,
838
- offset: const Offset(0, 8),
1109
+ color: _accent.withValues(alpha: 0.16),
1110
+ blurRadius: 24,
1111
+ offset: const Offset(0, 10),
839
1112
  ),
840
1113
  ]
841
1114
  : null,
@@ -844,52 +1117,81 @@ class _SidebarButton extends StatelessWidget {
844
1117
  child: InkWell(
845
1118
  borderRadius: BorderRadius.circular(18),
846
1119
  onTap: onTap,
847
- child: Container(
848
- width: double.infinity,
849
- padding: EdgeInsets.fromLTRB(12 + indent, 12, 12, 12),
850
- child: Row(
851
- children: <Widget>[
852
- if (active)
853
- Container(
854
- width: 6,
855
- height: 26,
856
- margin: const EdgeInsets.only(right: 10),
857
- decoration: BoxDecoration(
858
- gradient: LinearGradient(
859
- colors: <Color>[
860
- _accentHover,
861
- _accentAlt.withValues(alpha: 0.9),
862
- ],
863
- begin: Alignment.topCenter,
864
- end: Alignment.bottomCenter,
1120
+ child: Stack(
1121
+ children: <Widget>[
1122
+ if (active)
1123
+ Positioned.fill(
1124
+ child: IgnorePointer(
1125
+ child: DecoratedBox(
1126
+ decoration: BoxDecoration(
1127
+ borderRadius: BorderRadius.circular(18),
1128
+ gradient: LinearGradient(
1129
+ colors: <Color>[
1130
+ Colors.white.withValues(alpha: 0.08),
1131
+ Colors.transparent,
1132
+ _accentAlt.withValues(alpha: 0.08),
1133
+ ],
1134
+ stops: const <double>[0, 0.44, 1],
1135
+ begin: Alignment.topLeft,
1136
+ end: Alignment.bottomRight,
1137
+ ),
865
1138
  ),
866
- borderRadius: BorderRadius.circular(999),
867
1139
  ),
868
1140
  ),
869
- Icon(
870
- icon,
871
- size: iconSize,
872
- color: active ? _accentHover : _textSecondary,
873
1141
  ),
874
- const SizedBox(width: 10),
875
- Expanded(
876
- child: Text(
877
- label,
878
- style: TextStyle(
879
- fontSize: fontSize,
880
- fontWeight: active
881
- ? FontWeight.w700
882
- : FontWeight.w600,
883
- color: active ? _textPrimary : _textSecondary,
1142
+ Container(
1143
+ width: double.infinity,
1144
+ padding: EdgeInsets.fromLTRB(12 + indent, 12, 12, 12),
1145
+ child: Row(
1146
+ children: <Widget>[
1147
+ AnimatedContainer(
1148
+ duration: const Duration(milliseconds: 220),
1149
+ curve: Curves.easeOutCubic,
1150
+ width: active ? 6 : 3,
1151
+ height: active ? 26 : 16,
1152
+ margin: EdgeInsets.only(right: active ? 10 : 13),
1153
+ decoration: BoxDecoration(
1154
+ gradient: LinearGradient(
1155
+ colors: <Color>[
1156
+ active
1157
+ ? _accentHover
1158
+ : _textMuted.withValues(alpha: 0.35),
1159
+ active
1160
+ ? _accentAlt.withValues(alpha: 0.95)
1161
+ : _textMuted.withValues(alpha: 0.08),
1162
+ ],
1163
+ begin: Alignment.topCenter,
1164
+ end: Alignment.bottomCenter,
1165
+ ),
1166
+ borderRadius: BorderRadius.circular(999),
1167
+ ),
884
1168
  ),
885
- ),
1169
+ Icon(
1170
+ icon,
1171
+ size: iconSize,
1172
+ color: active ? _accentHover : _textSecondary,
1173
+ ),
1174
+ const SizedBox(width: 10),
1175
+ Expanded(
1176
+ child: Text(
1177
+ label,
1178
+ style: TextStyle(
1179
+ fontSize: fontSize,
1180
+ fontWeight: active
1181
+ ? FontWeight.w800
1182
+ : FontWeight.w600,
1183
+ color: active ? _textPrimary : _textSecondary,
1184
+ ),
1185
+ ),
1186
+ ),
1187
+ if (trailing != null) ...<Widget>[
1188
+ const SizedBox(width: 8),
1189
+ trailing!,
1190
+ ],
1191
+ ],
886
1192
  ),
887
- if (trailing != null) ...<Widget>[
888
- const SizedBox(width: 8),
889
- trailing!,
890
- ],
891
- ],
892
- ),
1193
+ ),
1194
+ ],
893
1195
  ),
894
1196
  ),
895
1197
  ),
@@ -899,6 +1201,42 @@ class _SidebarButton extends StatelessWidget {
899
1201
  }
900
1202
  }
901
1203
 
1204
+ class _HoverLift extends StatefulWidget {
1205
+ const _HoverLift({required this.child, this.active = false});
1206
+
1207
+ final Widget child;
1208
+ final bool active;
1209
+
1210
+ @override
1211
+ State<_HoverLift> createState() => _HoverLiftState();
1212
+ }
1213
+
1214
+ class _HoverLiftState extends State<_HoverLift> {
1215
+ bool _hovering = false;
1216
+
1217
+ @override
1218
+ Widget build(BuildContext context) {
1219
+ final lifted = widget.active || _hovering;
1220
+ return MouseRegion(
1221
+ onEnter: (_) => setState(() => _hovering = true),
1222
+ onExit: (_) => setState(() => _hovering = false),
1223
+ child: AnimatedScale(
1224
+ duration: const Duration(milliseconds: 180),
1225
+ curve: Curves.easeOutCubic,
1226
+ scale: lifted ? 1.012 : 1,
1227
+ child: AnimatedSlide(
1228
+ duration: const Duration(milliseconds: 180),
1229
+ curve: Curves.easeOutCubic,
1230
+ offset: _hovering && !widget.active
1231
+ ? const Offset(0.01, 0)
1232
+ : Offset.zero,
1233
+ child: widget.child,
1234
+ ),
1235
+ ),
1236
+ );
1237
+ }
1238
+ }
1239
+
902
1240
  class _SidebarIconButton extends StatelessWidget {
903
1241
  const _SidebarIconButton({
904
1242
  required this.tooltip,
@@ -936,49 +1274,87 @@ class _SidebarIconButton extends StatelessWidget {
936
1274
  }
937
1275
  }
938
1276
 
939
- class _BlurOrb extends StatelessWidget {
940
- const _BlurOrb({required this.size, required this.color});
1277
+ class _LogoBadge extends StatefulWidget {
1278
+ const _LogoBadge({required this.size});
941
1279
 
942
1280
  final double size;
943
- final Color color;
944
1281
 
945
1282
  @override
946
- Widget build(BuildContext context) {
947
- return IgnorePointer(
948
- child: Container(
949
- width: size,
950
- height: size,
951
- decoration: BoxDecoration(
952
- shape: BoxShape.circle,
953
- boxShadow: <BoxShadow>[
954
- BoxShadow(
955
- color: color.withValues(alpha: 0.18),
956
- blurRadius: 120,
957
- spreadRadius: 30,
958
- ),
959
- ],
960
- ),
961
- ),
962
- );
963
- }
1283
+ State<_LogoBadge> createState() => _LogoBadgeState();
964
1284
  }
965
1285
 
966
- class _LogoBadge extends StatelessWidget {
967
- const _LogoBadge({required this.size});
1286
+ class _LogoBadgeState extends State<_LogoBadge> {
1287
+ int _tapCount = 0;
1288
+ Timer? _tapResetTimer;
1289
+ Timer? _partyResetTimer;
968
1290
 
969
- final double size;
1291
+ @override
1292
+ void dispose() {
1293
+ _tapResetTimer?.cancel();
1294
+ if (_partyResetTimer != null && _partyResetTimer!.isActive) {
1295
+ _partyResetTimer?.cancel();
1296
+ _partyModeEnabled.value = false;
1297
+ }
1298
+ super.dispose();
1299
+ }
1300
+
1301
+ void _handleTap() {
1302
+ _tapResetTimer?.cancel();
1303
+ _tapCount += 1;
1304
+ if (_tapCount >= 5) {
1305
+ _tapCount = 0;
1306
+ _partyModeEnabled.value = true;
1307
+ _partyResetTimer?.cancel();
1308
+ _partyResetTimer = Timer(const Duration(seconds: 7), () {
1309
+ _partyModeEnabled.value = false;
1310
+ });
1311
+ } else {
1312
+ _tapResetTimer = Timer(const Duration(milliseconds: 1250), () {
1313
+ _tapCount = 0;
1314
+ });
1315
+ }
1316
+ }
970
1317
 
971
1318
  @override
972
1319
  Widget build(BuildContext context) {
973
- return SizedBox(
974
- width: size,
975
- height: size,
976
- child: Image.asset(
977
- 'assets/branding/app_icon_512.png',
978
- width: size,
979
- height: size,
980
- filterQuality: FilterQuality.high,
981
- ),
1320
+ final isDark = MediaQuery.platformBrightnessOf(context) == Brightness.dark;
1321
+ return ValueListenableBuilder<bool>(
1322
+ valueListenable: _partyModeEnabled,
1323
+ builder: (context, partyMode, _) {
1324
+ return GestureDetector(
1325
+ onTap: _handleTap,
1326
+ child: AnimatedScale(
1327
+ duration: const Duration(milliseconds: 240),
1328
+ curve: Curves.easeOutBack,
1329
+ scale: partyMode ? 1.08 : 1,
1330
+ child: AnimatedContainer(
1331
+ duration: const Duration(milliseconds: 260),
1332
+ width: widget.size,
1333
+ height: widget.size,
1334
+ decoration: BoxDecoration(
1335
+ borderRadius: BorderRadius.circular(widget.size * 0.24),
1336
+ boxShadow: <BoxShadow>[
1337
+ BoxShadow(
1338
+ color: (partyMode ? _accentAlt : _accent).withValues(
1339
+ alpha: partyMode ? 0.34 : 0.16,
1340
+ ),
1341
+ blurRadius: partyMode ? 28 : 14,
1342
+ spreadRadius: partyMode ? 1 : 0,
1343
+ ),
1344
+ ],
1345
+ ),
1346
+ child: Image.asset(
1347
+ isDark
1348
+ ? 'assets/branding/app_icon_512.png'
1349
+ : 'assets/branding/app_icon_light_512.png',
1350
+ width: widget.size,
1351
+ height: widget.size,
1352
+ filterQuality: FilterQuality.high,
1353
+ ),
1354
+ ),
1355
+ ),
1356
+ );
1357
+ },
982
1358
  );
983
1359
  }
984
1360
  }
@@ -1033,7 +1409,6 @@ class _BrandLockup extends StatelessWidget {
1033
1409
  }
1034
1410
  }
1035
1411
 
1036
-
1037
1412
  class _EmptyState extends StatelessWidget {
1038
1413
  const _EmptyState({required this.title, required this.subtitle});
1039
1414
 
@@ -1045,7 +1420,27 @@ class _EmptyState extends StatelessWidget {
1045
1420
  return Column(
1046
1421
  mainAxisSize: MainAxisSize.min,
1047
1422
  children: <Widget>[
1048
- const _LogoBadge(size: 52),
1423
+ Stack(
1424
+ alignment: Alignment.center,
1425
+ children: <Widget>[
1426
+ Container(
1427
+ width: 74,
1428
+ height: 74,
1429
+ decoration: BoxDecoration(
1430
+ shape: BoxShape.circle,
1431
+ gradient: SweepGradient(
1432
+ colors: <Color>[
1433
+ _accent.withValues(alpha: 0.18),
1434
+ _accentAlt.withValues(alpha: 0.28),
1435
+ Colors.transparent,
1436
+ _accent.withValues(alpha: 0.18),
1437
+ ],
1438
+ ),
1439
+ ),
1440
+ ),
1441
+ const _LogoBadge(size: 52),
1442
+ ],
1443
+ ),
1049
1444
  const SizedBox(height: 12),
1050
1445
  Text(
1051
1446
  title,
@@ -1104,26 +1499,50 @@ class _ChatBubble extends StatelessWidget {
1104
1499
  const SizedBox(width: 12),
1105
1500
  ],
1106
1501
  Flexible(
1107
- child: Container(
1502
+ child: AnimatedContainer(
1503
+ duration: const Duration(milliseconds: 220),
1504
+ curve: Curves.easeOutCubic,
1108
1505
  padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 11),
1109
1506
  decoration: BoxDecoration(
1110
- color: isUser ? _accent : _bgCard,
1507
+ gradient: isUser
1508
+ ? LinearGradient(
1509
+ colors: <Color>[_accent, _accentAlt],
1510
+ begin: Alignment.topLeft,
1511
+ end: Alignment.bottomRight,
1512
+ )
1513
+ : LinearGradient(
1514
+ colors: <Color>[
1515
+ _bgCard,
1516
+ _bgSecondary.withValues(alpha: 0.94),
1517
+ ],
1518
+ begin: Alignment.topLeft,
1519
+ end: Alignment.bottomRight,
1520
+ ),
1111
1521
  borderRadius: BorderRadius.only(
1112
1522
  topLeft: const Radius.circular(14),
1113
1523
  topRight: const Radius.circular(14),
1114
1524
  bottomLeft: Radius.circular(isUser ? 14 : 4),
1115
1525
  bottomRight: Radius.circular(isUser ? 4 : 14),
1116
1526
  ),
1117
- border: isUser ? null : Border.all(color: _border),
1118
- boxShadow: isUser
1119
- ? <BoxShadow>[
1120
- BoxShadow(
1121
- color: _accentAlt.withValues(alpha: 0.30),
1122
- blurRadius: 12,
1123
- offset: const Offset(0, 2),
1124
- ),
1125
- ]
1126
- : null,
1527
+ border: Border.all(
1528
+ color: isUser
1529
+ ? Colors.white.withValues(alpha: 0.18)
1530
+ : _borderLight.withValues(alpha: 0.72),
1531
+ ),
1532
+ boxShadow: <BoxShadow>[
1533
+ if (isUser)
1534
+ BoxShadow(
1535
+ color: _accentAlt.withValues(alpha: 0.30),
1536
+ blurRadius: 12,
1537
+ offset: const Offset(0, 2),
1538
+ ),
1539
+ if (!isUser)
1540
+ BoxShadow(
1541
+ color: Colors.black.withValues(alpha: 0.08),
1542
+ blurRadius: 16,
1543
+ offset: const Offset(0, 8),
1544
+ ),
1545
+ ],
1127
1546
  ),
1128
1547
  child: Column(
1129
1548
  crossAxisAlignment: isUser
@@ -1511,14 +1930,18 @@ class _MessageAvatar extends StatelessWidget {
1511
1930
  decoration: BoxDecoration(
1512
1931
  borderRadius: BorderRadius.circular(8),
1513
1932
  gradient: assistant
1514
- ? LinearGradient(colors: <Color>[_accent, _accentAlt])
1933
+ ? LinearGradient(
1934
+ colors: <Color>[_accentHover, _accent, _accentAlt],
1935
+ begin: Alignment.topLeft,
1936
+ end: Alignment.bottomRight,
1937
+ )
1515
1938
  : null,
1516
1939
  color: assistant ? null : _bgTertiary,
1517
1940
  boxShadow: assistant
1518
1941
  ? <BoxShadow>[
1519
1942
  BoxShadow(
1520
- color: _accentAlt.withValues(alpha: 0.35),
1521
- blurRadius: 10,
1943
+ color: _accentAlt.withValues(alpha: 0.42),
1944
+ blurRadius: 14,
1522
1945
  offset: const Offset(0, 2),
1523
1946
  ),
1524
1947
  ]
@@ -1546,8 +1969,22 @@ class _StatusPill extends StatelessWidget {
1546
1969
  padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
1547
1970
  decoration: BoxDecoration(
1548
1971
  borderRadius: BorderRadius.circular(999),
1549
- color: color.withValues(alpha: 0.14),
1550
- border: Border.all(color: color.withValues(alpha: 0.18)),
1972
+ gradient: LinearGradient(
1973
+ colors: <Color>[
1974
+ color.withValues(alpha: 0.2),
1975
+ color.withValues(alpha: 0.08),
1976
+ ],
1977
+ begin: Alignment.topLeft,
1978
+ end: Alignment.bottomRight,
1979
+ ),
1980
+ border: Border.all(color: color.withValues(alpha: 0.28)),
1981
+ boxShadow: <BoxShadow>[
1982
+ BoxShadow(
1983
+ color: color.withValues(alpha: 0.08),
1984
+ blurRadius: 12,
1985
+ offset: const Offset(0, 4),
1986
+ ),
1987
+ ],
1551
1988
  ),
1552
1989
  child: Text(
1553
1990
  label,
@@ -3201,6 +3638,16 @@ int _asInt(dynamic value) {
3201
3638
  return int.tryParse(value?.toString() ?? '') ?? 0;
3202
3639
  }
3203
3640
 
3641
+ double _asDouble(dynamic value, {double fallback = 0}) {
3642
+ if (value is double) {
3643
+ return value;
3644
+ }
3645
+ if (value is int) {
3646
+ return value.toDouble();
3647
+ }
3648
+ return double.tryParse(value?.toString() ?? '') ?? fallback;
3649
+ }
3650
+
3204
3651
  DateTime _parseTimestamp(String? raw) {
3205
3652
  if (raw == null || raw.isEmpty) {
3206
3653
  return DateTime.now();