import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; Color contentBgColor(BuildContext context) { final theme = Theme.of(context); final h = HSLColor.fromColor(theme.colorScheme.border).hue; final dark = theme.brightness == Brightness.dark; return dark ? HSLColor.fromAHSL(1, h, 0.35, 0.13).toColor() : HSLColor.fromAHSL(1, h, 0.30, 0.88).toColor(); } // The main shell. replaces Scaffold in all pages. class AugorShell extends StatelessWidget { const AugorShell({ super.key, required this.child, required this.titleTag, this.headerLeading = const [], this.headerTrailing = const [], this.statusLeft, this.statusRight, }); final Widget child; final String titleTag; final List headerLeading; final List headerTrailing; final Widget? statusLeft; final Widget? statusRight; @override Widget build(BuildContext context) { final theme = Theme.of(context); final monoStyle = GoogleFonts.ibmPlexMono( fontSize: 11, height: 1, fontWeight: FontWeight.w600, color: theme.colorScheme.mutedForeground, ); return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // ── header ────────────────────────────────────────────────────────── Container( decoration: BoxDecoration( color: theme.colorScheme.background, border: Border(bottom: BorderSide(color: theme.colorScheme.border, width: 1)), ), child: IntrinsicHeight( child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), child: Row( children: headerLeading, ), ), const Spacer(), ...headerTrailing, // title tag box Container( color: theme.colorScheme.border, constraints: const BoxConstraints(minWidth: 100), alignment: Alignment.center, padding: const EdgeInsets.symmetric(horizontal: 10), child: RichText( text: TextSpan(text: titleTag.toUpperCase(), style: monoStyle), ), ), const SizedBox(width: 64), // mac traffic lights gap ], ), ), ), // ── content ───────────────────────────────────────────────────────── Expanded( child: Stack( fit: StackFit.expand, children: [ ColoredBox( color: contentBgColor(context), child: child, ), const Positioned.fill( child: IgnorePointer( child: CustomPaint(painter: _InsetShadowPainter()), ), ), ], ), ), // ── footer / status bar ───────────────────────────────────────────── Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: theme.colorScheme.background, border: Border(top: BorderSide(color: theme.colorScheme.border, width: 1)), ), child: Row( children: [ Expanded( child: statusLeft ?? const SizedBox.shrink(), ), Expanded( child: Center( child: _NavItems(), ), ), Expanded( child: Align( alignment: Alignment.centerRight, child: statusRight ?? const SizedBox.shrink(), ), ), ], ), ), ], ); } } // shared footer nav — home and settings links class _NavItems extends StatelessWidget { @override Widget build(BuildContext context) { final location = GoRouterState.of(context).uri.toString(); final theme = Theme.of(context); final monoStyle = GoogleFonts.ibmPlexMono( fontSize: 11, height: 1, fontWeight: FontWeight.w600, ); Widget navItem(String label, String path) { final active = location == path || (path == '/' && location == '/'); return GestureDetector( onTap: () => GoRouter.of(context).go(path), child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: active ? theme.colorScheme.border : Colors.transparent, borderRadius: BorderRadius.circular(3), ), child: RichText( text: TextSpan( text: label, style: monoStyle.copyWith( color: active ? theme.colorScheme.foreground : theme.colorScheme.mutedForeground, ), ), ), ), ); } return Row( mainAxisSize: MainAxisSize.min, children: [ navItem('Home', '/'), const SizedBox(width: 2), navItem('Settings', '/settings'), ], ); } } // footer status text helper — use this for statusLeft / statusRight class StatusText extends StatelessWidget { const StatusText(this.text, {super.key}); final String text; @override Widget build(BuildContext context) { final theme = Theme.of(context); return RichText( text: TextSpan( text: text, style: GoogleFonts.ibmPlexMono( fontSize: 11, height: 1, fontWeight: FontWeight.w600, color: theme.colorScheme.mutedForeground, ), ), ); } } class _InsetShadowPainter extends CustomPainter { const _InsetShadowPainter(); @override void paint(Canvas canvas, Size size) { const blur = 12.0; final rect = Offset.zero & size; canvas.save(); canvas.clipRect(rect); final innerRect = rect.deflate(24); final ringClip = Path() ..addRect(rect) ..addRect(innerRect) ..fillType = PathFillType.evenOdd; canvas.clipPath(ringClip); final path = Path() ..addRect(rect.inflate(blur * 2)) ..addRect(rect) ..fillType = PathFillType.evenOdd; final paint = Paint() ..color = const Color(0x55000000) ..maskFilter = const MaskFilter.blur(BlurStyle.normal, blur); canvas.drawPath(path, paint); canvas.restore(); } @override bool shouldRepaint(covariant CustomPainter old) => false; }