// Welcome — first surface a new operator sees, default sidebar // destination on launch. Pure introduction: what F∆I is, the // three concepts, the trust posture. No live data, no // federation chrome — that's the Store's role. // // See docs/landing-page-design.md for the multi-phase plan; // this file is Phase A (scaffolding + static content). Phase B // adds an embedded markdown reader; Phase C adds a live // getting-started checklist. import 'package:flutter/material.dart'; import '../l10n/app_localizations.dart'; import '../theme/tokens.dart'; class WelcomePage extends StatelessWidget { const WelcomePage({super.key}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final l = AppLocalizations.of(context)!; return Scaffold( backgroundColor: theme.scaffoldBackgroundColor, appBar: AppBar( titleSpacing: FaiSpace.xl, title: Text(l.navWelcome), ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.fromLTRB( FaiSpace.xl, FaiSpace.lg, FaiSpace.xl, FaiSpace.xxl, ), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 960), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: const [ _Hero(), SizedBox(height: FaiSpace.xxl), _SectionLabel(textKey: _SectionLabelKey.pillars), SizedBox(height: FaiSpace.md), _PillarRow(), SizedBox(height: FaiSpace.xxl), _SectionLabel(textKey: _SectionLabelKey.trust), SizedBox(height: FaiSpace.md), _TrustPosture(), ], ), ), ), ), ); } } /// Top hero — gradient backdrop matching the Today-Hero in the /// store so the visual language carries between pages. Title + /// subtitle, no CTAs. class _Hero extends StatelessWidget { const _Hero(); @override Widget build(BuildContext context) { final theme = Theme.of(context); final l = AppLocalizations.of(context)!; return Container( width: double.infinity, padding: const EdgeInsets.all(FaiSpace.xxl), decoration: BoxDecoration( borderRadius: BorderRadius.circular(FaiRadius.md), gradient: LinearGradient( colors: [ theme.colorScheme.primary.withValues(alpha: 0.18), theme.colorScheme.primary.withValues(alpha: 0.04), theme.colorScheme.surfaceContainer.withValues(alpha: 0.0), ], stops: const [0.0, 0.55, 1.0], begin: Alignment.topLeft, end: Alignment.bottomRight, ), border: Border.all( color: theme.colorScheme.primary.withValues(alpha: 0.25), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.all(FaiSpace.md), decoration: BoxDecoration( color: theme.colorScheme.primary.withValues(alpha: 0.14), shape: BoxShape.circle, ), child: Icon( Icons.waving_hand_outlined, size: 28, color: theme.colorScheme.primary, ), ), const SizedBox(width: FaiSpace.lg), Expanded( child: Text( l.welcomeHeroTitle, style: theme.textTheme.displaySmall?.copyWith( fontWeight: FontWeight.w700, ), ), ), ], ), const SizedBox(height: FaiSpace.lg), Text( l.welcomeHeroSubtitle, style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, height: 1.45, ), ), ], ), ); } } enum _SectionLabelKey { pillars, trust } class _SectionLabel extends StatelessWidget { final _SectionLabelKey textKey; const _SectionLabel({required this.textKey}); @override Widget build(BuildContext context) { final theme = Theme.of(context); final l = AppLocalizations.of(context)!; final text = switch (textKey) { _SectionLabelKey.pillars => l.welcomePillarsHeader, _SectionLabelKey.trust => l.welcomeTrustHeader, }; return Text( text, style: theme.textTheme.labelSmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, letterSpacing: 0.6, fontSize: 10, ), ); } } /// "Hub. Module. Flow." three-card row. Stacks on narrow /// windows so card text never gets cropped. class _PillarRow extends StatelessWidget { const _PillarRow(); @override Widget build(BuildContext context) { final l = AppLocalizations.of(context)!; final pillars = <(IconData, String, String)>[ ( Icons.hub_outlined, l.welcomePillarHubTitle, l.welcomePillarHubBody, ), ( Icons.extension_outlined, l.welcomePillarModuleTitle, l.welcomePillarModuleBody, ), ( Icons.account_tree_outlined, l.welcomePillarFlowTitle, l.welcomePillarFlowBody, ), ]; return LayoutBuilder( builder: (context, constraints) { // ~640 dp is the comfortable threshold for three cards // side by side; below that we stack so each card // gets its full text width. final stacked = constraints.maxWidth < 640; if (stacked) { return Column( children: [ for (final p in pillars) ...[ _Pillar(icon: p.$1, title: p.$2, body: p.$3), const SizedBox(height: FaiSpace.md), ], ], ); } return Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ for (var i = 0; i < pillars.length; i++) ...[ if (i > 0) const SizedBox(width: FaiSpace.md), Expanded( child: _Pillar( icon: pillars[i].$1, title: pillars[i].$2, body: pillars[i].$3, ), ), ], ], ); }, ); } } class _Pillar extends StatelessWidget { final IconData icon; final String title; final String body; const _Pillar({ required this.icon, required this.title, required this.body, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( padding: const EdgeInsets.all(FaiSpace.lg), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(FaiRadius.md), border: Border.all(color: theme.colorScheme.outlineVariant), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 28, color: theme.colorScheme.primary), const SizedBox(height: FaiSpace.md), Text( title, style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(height: FaiSpace.sm), Text( body, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, height: 1.45, ), ), ], ), ); } } /// Three trust-posture rows: Sandbox / Audit / Air-gap. Used to /// live as carousel slides in the store; reading them here as /// a single deck reads better than rotating through them. class _TrustPosture extends StatelessWidget { const _TrustPosture(); @override Widget build(BuildContext context) { final l = AppLocalizations.of(context)!; final rows = <(IconData, String, String)>[ ( Icons.shield_outlined, l.welcomeTrustSandboxTitle, l.welcomeTrustSandboxBody, ), ( Icons.verified_outlined, l.welcomeTrustAuditTitle, l.welcomeTrustAuditBody, ), ( Icons.rocket_launch_outlined, l.welcomeTrustAirgapTitle, l.welcomeTrustAirgapBody, ), ]; return Column( children: [ for (var i = 0; i < rows.length; i++) ...[ if (i > 0) const SizedBox(height: FaiSpace.sm), _TrustRow(icon: rows[i].$1, title: rows[i].$2, body: rows[i].$3), ], ], ); } } class _TrustRow extends StatelessWidget { final IconData icon; final String title; final String body; const _TrustRow({ required this.icon, required this.title, required this.body, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( padding: const EdgeInsets.all(FaiSpace.lg), decoration: BoxDecoration( color: theme.colorScheme.surfaceContainer, borderRadius: BorderRadius.circular(FaiRadius.md), border: Border.all(color: theme.colorScheme.outlineVariant), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Icon(icon, size: 22, color: theme.colorScheme.primary), const SizedBox(width: FaiSpace.lg), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), Text( body, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, height: 1.45, ), ), ], ), ), ], ), ); } }