import 'package:flutter/material.dart'; // Pyramid design tokens — matching mockup CSS variables exactly class PyramidColors { const PyramidColors._(); // Dark theme static const bg0 = Color(0xFF0A0A0C); static const bg1 = Color(0xFF111114); static const bg2 = Color(0xFF17171C); static const bg3 = Color(0xFF1E1E25); static const bgHover = Color(0xFF232330); static const bgActive = Color(0xFF2A2A38); static const border = Color(0xFF25252E); static const borderStrong = Color(0xFF32323D); static const fg = Color(0xFFECECF0); static const fgMuted = Color(0xFFA4A4B0); static const fgDim = Color(0xFF6F6F7D); // Light theme static const bg0Light = Color(0xFFF5F4F0); static const bg1Light = Color(0xFFFFFFFF); static const bg2Light = Color(0xFFFAF9F5); static const bg3Light = Color(0xFFF0EEEA); static const bgHoverLight = Color(0xFFE9E7E1); static const bgActiveLight = Color(0xFFDFDCD3); static const borderLight = Color(0xFFE5E2DC); static const borderStrongLight = Color(0xFFD3CFC6); static const fgLight = Color(0xFF1A1A1F); static const fgMutedLight = Color(0xFF54545E); static const fgDimLight = Color(0xFF8A8A92); // Semantic colors (same in both themes) static const danger = Color(0xFFE5484D); static const success = Color(0xFF30A46C); static const online = Color(0xFF4ADE80); static const away = Color(0xFFFACC15); static const busy = Color(0xFFF87171); // Accent presets — default: amber hsl(42 95% 58%) static const accentAmber = Color(0xFFF5A614); static const accentCoral = Color(0xFFED6951); static const accentMint = Color(0xFF3DB88A); static const accentViolet = Color(0xFFB67EEA); static const accentCobalt = Color(0xFF5BA3F5); static const accentLime = Color(0xFF8EC63F); static const accentFg = Color(0xFF0B0B0D); } class PyramidTheme extends InheritedWidget { final bool isDark; final Color accent; final double radius; final double density; final double motion; const PyramidTheme({ super.key, required super.child, this.isDark = true, this.accent = PyramidColors.accentAmber, this.radius = 12, this.density = 1, this.motion = 1, }); static PyramidTheme of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType()!; } Color get bg0 => isDark ? PyramidColors.bg0 : PyramidColors.bg0Light; Color get bg1 => isDark ? PyramidColors.bg1 : PyramidColors.bg1Light; Color get bg2 => isDark ? PyramidColors.bg2 : PyramidColors.bg2Light; Color get bg3 => isDark ? PyramidColors.bg3 : PyramidColors.bg3Light; Color get bgHover => isDark ? PyramidColors.bgHover : PyramidColors.bgHoverLight; Color get bgActive => isDark ? PyramidColors.bgActive : PyramidColors.bgActiveLight; Color get border => isDark ? PyramidColors.border : PyramidColors.borderLight; Color get borderStrong => isDark ? PyramidColors.borderStrong : PyramidColors.borderStrongLight; Color get fg => isDark ? PyramidColors.fg : PyramidColors.fgLight; Color get fgMuted => isDark ? PyramidColors.fgMuted : PyramidColors.fgMutedLight; Color get fgDim => isDark ? PyramidColors.fgDim : PyramidColors.fgDimLight; Color get danger => PyramidColors.danger; Color get online => PyramidColors.online; Color get away => PyramidColors.away; Color get busy => PyramidColors.busy; Color get accentSoft => accent.withAlpha(36); // ~14% Color get accentGlow => accent.withAlpha(89); // ~35% Color get accentFg => PyramidColors.accentFg; double get rSm => radius * 0.6; double get rBase => radius; double get rLg => radius * 1.5; double get rXl => radius * 2; double get rowPadY => 10 * density; double get rowPadX => 12 * density; double get msgGap => 16 * density; Duration get durationFast => const Duration(milliseconds: 150); Duration get durationNormal => const Duration(milliseconds: 220); Curve get springBounce => Curves.easeOutBack; Curve get springSoft => Curves.easeOutCubic; @override bool updateShouldNotify(PyramidTheme oldWidget) => isDark != oldWidget.isDark || accent != oldWidget.accent || radius != oldWidget.radius || density != oldWidget.density || motion != oldWidget.motion; } // Inter ist als Asset gebündelt (pubspec `fonts:`) — kein Laufzeit-Download. TextStyle _base(Color color) => TextStyle(fontFamily: 'Inter', color: color); ThemeData buildPyramidThemeData(bool isDark) { final fg = isDark ? PyramidColors.fg : PyramidColors.fgLight; final bg1 = isDark ? PyramidColors.bg1 : PyramidColors.bg1Light; final accent = PyramidColors.accentAmber; return ThemeData( useMaterial3: true, fontFamily: 'Inter', brightness: isDark ? Brightness.dark : Brightness.light, scaffoldBackgroundColor: bg1, colorScheme: ColorScheme( brightness: isDark ? Brightness.dark : Brightness.light, primary: accent, onPrimary: PyramidColors.accentFg, secondary: accent, onSecondary: PyramidColors.accentFg, error: PyramidColors.danger, onError: Colors.white, surface: bg1, onSurface: fg, ), textTheme: TextTheme( bodyMedium: _base(fg).copyWith(fontSize: 14), bodySmall: _base(fg).copyWith(fontSize: 12, color: isDark ? PyramidColors.fgMuted : PyramidColors.fgMutedLight), labelSmall: _base(fg).copyWith(fontSize: 11, color: isDark ? PyramidColors.fgDim : PyramidColors.fgDimLight), ), dividerTheme: DividerThemeData( color: isDark ? PyramidColors.border : PyramidColors.borderLight, thickness: 1, space: 1, ), splashFactory: NoSplash.splashFactory, highlightColor: Colors.transparent, ); }