diff --git a/app/lib/core/design/app_colors.dart b/app/lib/core/design/app_colors.dart index e36b3926..6a5cf08a 100644 --- a/app/lib/core/design/app_colors.dart +++ b/app/lib/core/design/app_colors.dart @@ -22,19 +22,25 @@ import 'package:flutter/material.dart'; @immutable class AppColors extends ThemeExtension { const AppColors._({ - this.seedColor = const Color(0xFF167C80), - this.privateTabPurple = const Color(0xFF8000D7), - this.privateTabBackground = const Color(0xFF25003E), - this.privateTabForeground = const Color(0xFFFFFFFF), - this.privateSelectionOverlay = const Color(0x648000D7), - this.isolatedTabTeal = const Color(0xFF00897B), - this.isolatedTabBackground = const Color(0xFF003D36), - this.isolatedTabForeground = const Color(0xFFFFFFFF), - this.isolatedSelectionOverlay = const Color(0x6400897B), - this.torPurple = const Color(0xFF7D4698), - this.torActiveGreen = const Color(0xFF68B030), - this.torBackgroundGrey = const Color(0xFF333A41), - this.warningAmber = const Color(0xFFFFA000), + required this.seedColor, + required this.privateTabPurple, + required this.privateTabBackground, + required this.privateTabForeground, + required this.privateSelectionOverlay, + required this.isolatedTabTeal, + required this.isolatedTabBackground, + required this.isolatedTabForeground, + required this.isolatedSelectionOverlay, + required this.torPurple, + required this.torActiveGreen, + required this.torBackgroundGrey, + required this.warningAmber, + required this.auraPurple, + required this.auraGold, + required this.auraShadow, + required this.auraShadowHighlight, + required this.auraTint, + required this.brandLink, }); final Color seedColor; @@ -50,9 +56,63 @@ class AppColors extends ThemeExtension { final Color torActiveGreen; final Color torBackgroundGrey; final Color warningAmber; + final Color auraPurple; + final Color auraGold; + final Color auraShadow; + final Color auraShadowHighlight; + final Color auraTint; + final Color brandLink; - static const light = AppColors._(); - static const dark = AppColors._(); + /// Brand colors derived from the logo (constant across themes). + static const brandPurple = Color(0xFF9C83F8); + static const brandYellow = Color(0xFFFBDC6B); + static const brandGrey = Color(0xFFA7A7A7); + + static const light = AppColors._( + seedColor: Color(0xFF167C80), + privateTabPurple: Color(0xFF8000D7), + privateTabBackground: Color(0xFFF3E5F5), + privateTabForeground: Color(0xFF4A0072), + privateSelectionOverlay: Color(0x648000D7), + isolatedTabTeal: Color(0xFF00897B), + isolatedTabBackground: Color(0xFFE0F2F1), + isolatedTabForeground: Color(0xFF004D40), + isolatedSelectionOverlay: Color(0x6400897B), + torPurple: Color(0xFF7D4698), + torActiveGreen: Color(0xFF68B030), + torBackgroundGrey: Color(0xFFECEFF1), + warningAmber: Color(0xFFFFA000), + // Aura: brand colors lightened toward white + auraPurple: Color(0xFFE0D6FC), + auraGold: Color(0xFFFDF1D6), + auraShadow: Color(0xFFEDEDF0), + auraShadowHighlight: Color(0xFFE2E2E7), + auraTint: Color(0xFFFFFFFF), + brandLink: Color(0xFF7A5AF0), + ); + + static const dark = AppColors._( + seedColor: Color(0xFF167C80), + privateTabPurple: Color(0xFF8000D7), + privateTabBackground: Color(0xFF25003E), + privateTabForeground: Color(0xFFFFFFFF), + privateSelectionOverlay: Color(0x648000D7), + isolatedTabTeal: Color(0xFF00897B), + isolatedTabBackground: Color(0xFF003D36), + isolatedTabForeground: Color(0xFFFFFFFF), + isolatedSelectionOverlay: Color(0x6400897B), + torPurple: Color(0xFF7D4698), + torActiveGreen: Color(0xFF68B030), + torBackgroundGrey: Color(0xFF333A41), + warningAmber: Color(0xFFFFA000), + // Aura: brand colors darkened toward black + auraPurple: Color(0xFF2C2543), + auraGold: Color(0xFF3C3827), + auraShadow: Color(0xFF222224), + auraShadowHighlight: Color(0xFF2D2D31), + auraTint: Color(0xFF000000), + brandLink: Color(0xFFFBDC6B), + ); @override AppColors copyWith({ @@ -69,6 +129,12 @@ class AppColors extends ThemeExtension { Color? torActiveGreen, Color? torBackgroundGrey, Color? warningAmber, + Color? auraPurple, + Color? auraGold, + Color? auraShadow, + Color? auraShadowHighlight, + Color? auraTint, + Color? brandLink, }) { return AppColors._( seedColor: seedColor ?? this.seedColor, @@ -88,6 +154,12 @@ class AppColors extends ThemeExtension { torActiveGreen: torActiveGreen ?? this.torActiveGreen, torBackgroundGrey: torBackgroundGrey ?? this.torBackgroundGrey, warningAmber: warningAmber ?? this.warningAmber, + auraPurple: auraPurple ?? this.auraPurple, + auraGold: auraGold ?? this.auraGold, + auraShadow: auraShadow ?? this.auraShadow, + auraShadowHighlight: auraShadowHighlight ?? this.auraShadowHighlight, + auraTint: auraTint ?? this.auraTint, + brandLink: brandLink ?? this.brandLink, ); } @@ -143,6 +215,16 @@ class AppColors extends ThemeExtension { t, )!, warningAmber: Color.lerp(warningAmber, other.warningAmber, t)!, + auraPurple: Color.lerp(auraPurple, other.auraPurple, t)!, + auraGold: Color.lerp(auraGold, other.auraGold, t)!, + auraShadow: Color.lerp(auraShadow, other.auraShadow, t)!, + auraShadowHighlight: Color.lerp( + auraShadowHighlight, + other.auraShadowHighlight, + t, + )!, + auraTint: Color.lerp(auraTint, other.auraTint, t)!, + brandLink: Color.lerp(brandLink, other.brandLink, t)!, ); } diff --git a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_home.dart b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_home.dart index 179d98b1..f43c176c 100644 --- a/app/lib/features/geckoview/features/browser/presentation/widgets/browser_home.dart +++ b/app/lib/features/geckoview/features/browser/presentation/widgets/browser_home.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/core/routing/routes.dart'; import 'package:weblibre/features/geckoview/domain/providers/tab_list.dart'; import 'package:weblibre/features/geckoview/domain/repositories/tab.dart'; @@ -72,148 +73,148 @@ class BrowserHome extends ConsumerWidget { child: BrowserPageContent( bottomViewportInset: bottomViewportInset, child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - if (containerData != null) - _ContainerHeader(container: containerData) - else - BrandHeader(colorScheme: colorScheme), - const SizedBox(height: 24), - if (!hasTabs) ...[ - Text( - 'WebLibre is ready', - textAlign: TextAlign.center, - style: theme.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 10), - Text( - 'A browser that respects you. Open a tab and experience the web, libre.', - textAlign: TextAlign.center, - style: theme.textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - height: 1.45, - ), - ), - const SizedBox(height: 28), - ] else if (containerData != null) ...[ - Text( - containerData.name?.isNotEmpty == true - ? containerData.name! - : 'Container', - textAlign: TextAlign.center, - style: theme.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w700, - ), - ), - const SizedBox(height: 10), - Text( - hasContainerTabs - ? 'No matching tab selected' - : 'No open tabs in this container', - textAlign: TextAlign.center, - style: theme.textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - height: 1.45, - ), - ), - const SizedBox(height: 28), - ], - Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: colorScheme.surfaceContainer.withValues(alpha: 0.9), - borderRadius: BorderRadius.circular(28), - border: Border.all( - color: Color.alphaBlend( - BrowserPage.brandGrey.withValues(alpha: 0.18), - colorScheme.outlineVariant, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (containerData != null) + _ContainerHeader(container: containerData) + else + BrandHeader(colorScheme: colorScheme), + const SizedBox(height: 24), + if (!hasTabs) ...[ + Text( + 'WebLibre is ready', + textAlign: TextAlign.center, + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w700, ), ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - width: 36, - height: 36, - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainerHigh, - borderRadius: BorderRadius.circular(12), + const SizedBox(height: 10), + Text( + 'A browser that respects you. Open a tab and experience the web, libre.', + textAlign: TextAlign.center, + style: theme.textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + height: 1.45, + ), + ), + const SizedBox(height: 28), + ] else if (containerData != null) ...[ + Text( + containerData.name?.isNotEmpty == true + ? containerData.name! + : 'Container', + textAlign: TextAlign.center, + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 10), + Text( + hasContainerTabs + ? 'No matching tab selected' + : 'No open tabs in this container', + textAlign: TextAlign.center, + style: theme.textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + height: 1.45, + ), + ), + const SizedBox(height: 28), + ], + Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: colorScheme.surfaceContainer.withValues(alpha: 0.9), + borderRadius: BorderRadius.circular(28), + border: Border.all( + color: Color.alphaBlend( + AppColors.brandGrey.withValues(alpha: 0.18), + colorScheme.outlineVariant, + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 36, + height: 36, + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(12), + ), + child: const Icon(MdiIcons.formatQuoteOpen, size: 18), ), - child: const Icon(MdiIcons.formatQuoteOpen, size: 18), - ), - const SizedBox(width: 12), - Expanded( - child: Text( - 'A thought for the road', - style: theme.textTheme.titleMedium?.copyWith( - fontWeight: FontWeight.w600, + const SizedBox(width: 12), + Expanded( + child: Text( + 'A thought for the road', + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w600, + ), ), ), - ), - IconButton.filledTonal( - tooltip: 'Refresh quote', - onPressed: () { - ref.invalidate(randomQuoteProvider); - }, - icon: const Icon(Icons.refresh_rounded), - ), - ], - ), - const SizedBox(height: 16), - switch (quoteAsync) { - AsyncData(:final value) => _QuoteBlock(quote: value), - AsyncError() => Text( - 'Open a new tab and make this space your own.', - style: theme.textTheme.bodyLarge?.copyWith( - color: colorScheme.onSurfaceVariant, - height: 1.5, - ), + IconButton.filledTonal( + tooltip: 'Refresh quote', + onPressed: () { + ref.invalidate(randomQuoteProvider); + }, + icon: const Icon(Icons.refresh_rounded), + ), + ], + ), + const SizedBox(height: 16), + switch (quoteAsync) { + AsyncData(:final value) => _QuoteBlock(quote: value), + AsyncError() => Text( + 'Open a new tab and make this space your own.', + style: theme.textTheme.bodyLarge?.copyWith( + color: colorScheme.onSurfaceVariant, + height: 1.5, + ), + ), + _ => const LinearProgressIndicator(minHeight: 3), + }, + ], + ), + ), + const SizedBox(height: 24), + Wrap( + alignment: WrapAlignment.center, + spacing: 12, + runSpacing: 12, + children: [ + if (hasTabs) + OutlinedButton.icon( + onPressed: viewTabs, + icon: const Icon(Icons.tab_rounded), + label: const Text('View tabs'), + ), + FilledButton.icon( + onPressed: openNewTab, + icon: const Icon(Icons.add_rounded), + label: const Text('New tab'), + ), + if (hasContainerTabs) + FilledButton.tonalIcon( + onPressed: resumeLatestContainerTab, + icon: const Icon(Icons.history_rounded), + label: const Text('Resume last tab'), + ) + else if (hasTabs && containerData == null) + FilledButton.tonalIcon( + onPressed: resumeLatestTab, + icon: const Icon(Icons.history_rounded), + label: const Text('Resume last tab'), ), - _ => const LinearProgressIndicator(minHeight: 3), - }, ], ), - ), - const SizedBox(height: 24), - Wrap( - alignment: WrapAlignment.center, - spacing: 12, - runSpacing: 12, - children: [ - if (hasTabs) - OutlinedButton.icon( - onPressed: viewTabs, - icon: const Icon(Icons.tab_rounded), - label: const Text('View tabs'), - ), - FilledButton.icon( - onPressed: openNewTab, - icon: const Icon(Icons.add_rounded), - label: const Text('New tab'), - ), - if (hasContainerTabs) - FilledButton.tonalIcon( - onPressed: resumeLatestContainerTab, - icon: const Icon(Icons.history_rounded), - label: const Text('Resume last tab'), - ) - else if (hasTabs && containerData == null) - FilledButton.tonalIcon( - onPressed: resumeLatestTab, - icon: const Icon(Icons.history_rounded), - label: const Text('Resume last tab'), - ), - ], - ), - ], - ), + ], + ), ), ); } diff --git a/app/lib/features/onboarding/presentation/pages/welcome.dart b/app/lib/features/onboarding/presentation/pages/welcome.dart index 5e5c471d..9e3443a3 100644 --- a/app/lib/features/onboarding/presentation/pages/welcome.dart +++ b/app/lib/features/onboarding/presentation/pages/welcome.dart @@ -21,6 +21,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/core/design/app_colors.dart'; import 'package:weblibre/features/onboarding/domain/entities/onboarding_mode.dart'; import 'package:weblibre/features/onboarding/domain/providers.dart'; import 'package:weblibre/presentation/widgets/browser_page.dart'; @@ -130,9 +131,9 @@ class _UpdateNotice extends StatelessWidget { children: [ Row( children: [ - const Icon( + Icon( Icons.auto_awesome, - color: BrowserPage.brandYellow, + color: AppColors.of(context).brandLink, size: 20, ), const SizedBox(width: 8), @@ -244,7 +245,7 @@ class _ModeOption extends StatelessWidget { Icon( icon, color: selected - ? BrowserPage.brandYellow + ? AppColors.of(context).brandLink : colorScheme.onSurfaceVariant, size: 28, ), @@ -320,9 +321,9 @@ class _EulaCheckbox extends StatelessWidget { child: Text( 'EULA', style: theme.textTheme.bodySmall?.copyWith( - color: BrowserPage.brandYellow, + color: AppColors.of(context).brandLink, decoration: TextDecoration.underline, - decorationColor: BrowserPage.brandYellow, + decorationColor: AppColors.of(context).brandLink, ), ), ), @@ -341,9 +342,9 @@ class _EulaCheckbox extends StatelessWidget { child: Text( 'Privacy Policy', style: theme.textTheme.bodySmall?.copyWith( - color: BrowserPage.brandYellow, + color: AppColors.of(context).brandLink, decoration: TextDecoration.underline, - decorationColor: BrowserPage.brandYellow, + decorationColor: AppColors.of(context).brandLink, ), ), ), diff --git a/app/lib/presentation/widgets/browser_page.dart b/app/lib/presentation/widgets/browser_page.dart index 724a2346..2d0fbb57 100644 --- a/app/lib/presentation/widgets/browser_page.dart +++ b/app/lib/presentation/widgets/browser_page.dart @@ -4,6 +4,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:weblibre/core/design/app_colors.dart'; class BrowserPage extends ConsumerWidget { final double bottomViewportInset; @@ -11,20 +12,11 @@ class BrowserPage extends ConsumerWidget { const BrowserPage({super.key, this.bottomViewportInset = 0, required this.child}); - static const brandPurple = Color(0xFF9C83F8); - static const brandYellow = Color(0xFFFBDC6B); - static const brandGrey = Color(0xFFA7A7A7); - - static const auraPurple = Color(0xFF2C2543); - static const auraGold = Color(0xFF3C3827); - static const auraShadow = Color(0xFF222224); - static const auraShadowHighlight = Color(0xFF2D2D31); - static const auraTint = Color(0xFF000000); - @override Widget build(BuildContext context, WidgetRef ref) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; + final appColors = AppColors.of(context); return DecoratedBox( decoration: BoxDecoration( @@ -33,15 +25,15 @@ class BrowserPage extends ConsumerWidget { end: Alignment.bottomRight, colors: [ Color.alphaBlend( - auraPurple.withValues(alpha: 0.38), + appColors.auraPurple.withValues(alpha: 0.38), colorScheme.surfaceContainerLowest, ), Color.alphaBlend( - auraShadow.withValues(alpha: 0.72), + appColors.auraShadow.withValues(alpha: 0.72), colorScheme.surface, ), Color.alphaBlend( - auraGold.withValues(alpha: 0.34), + appColors.auraGold.withValues(alpha: 0.34), colorScheme.surfaceContainerHigh, ), ], @@ -50,23 +42,23 @@ class BrowserPage extends ConsumerWidget { child: Stack( fit: StackFit.expand, children: [ - const Positioned( + Positioned( top: -70, left: -120, - child: _BackdropOrb(width: 400, height: 400, color: auraPurple), + child: _BackdropOrb(width: 400, height: 400, color: appColors.auraPurple), ), - const Positioned( + Positioned( top: 220, right: -150, - child: _BackdropOrb(width: 340, height: 340, color: auraGold), + child: _BackdropOrb(width: 340, height: 340, color: appColors.auraGold), ), - const Positioned( + Positioned( bottom: 18, left: -8, child: _BackdropOrb( width: 320, height: 320, - color: auraShadowHighlight, + color: appColors.auraShadowHighlight, ), ), Positioned.fill( @@ -74,7 +66,7 @@ class BrowserPage extends ConsumerWidget { child: ClipRect( child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 72, sigmaY: 72), - child: ColoredBox(color: auraTint.withValues(alpha: 0.12)), + child: ColoredBox(color: appColors.auraTint.withValues(alpha: 0.12)), ), ), ), @@ -146,11 +138,11 @@ class BrandHeader extends StatelessWidget { end: Alignment.bottomRight, colors: [ Color.alphaBlend( - BrowserPage.brandPurple.withValues(alpha: 0.18), + AppColors.brandPurple.withValues(alpha: 0.18), colorScheme.surfaceContainerHighest, ), Color.alphaBlend( - BrowserPage.brandYellow.withValues(alpha: 0.12), + AppColors.brandYellow.withValues(alpha: 0.12), colorScheme.surfaceContainer, ), ],