feat: full onboarding + quiz flow (Flutter from scratch)

- splash → intro → 5-step quiz → result screen
- Plus Jakarta Sans typography, indigo brand system
- animated quiz rows, multi-select concerns, progress bar
- Shaynee mascot reveal on result screen only
- flutter_animate for all transitions
This commit is contained in:
mav
2026-05-28 16:20:18 +02:00
parent 74c712dbae
commit 30cee5a486
12 changed files with 1567 additions and 144 deletions

View File

@@ -0,0 +1,226 @@
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:google_fonts/google_fonts.dart';
import '../theme.dart';
import '../widgets/primary_button.dart';
import 'quiz/quiz_flow.dart';
class IntroScreen extends StatelessWidget {
const IntroScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: SColors.bgPage,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 24),
_badge()
.animate()
.fadeIn(duration: 400.ms),
const SizedBox(height: 20),
Text(
'Skin that glows,\nroutine that works.',
style: GoogleFonts.plusJakartaSans(
fontSize: 32,
fontWeight: FontWeight.w800,
color: SColors.textPrimary,
height: 1.2,
),
)
.animate()
.fadeIn(duration: 500.ms, delay: 100.ms)
.slideY(begin: 0.1, end: 0, duration: 500.ms),
const SizedBox(height: 12),
Text(
'Answer 5 quick questions and get your\npersonalised skincare plan — in 60 seconds.',
style: GoogleFonts.plusJakartaSans(
fontSize: 15,
color: SColors.textSecondary,
height: 1.5,
),
)
.animate()
.fadeIn(duration: 500.ms, delay: 200.ms),
const SizedBox(height: 32),
_statsRow()
.animate()
.fadeIn(duration: 500.ms, delay: 300.ms),
const SizedBox(height: 32),
_featureList()
.animate()
.fadeIn(duration: 500.ms, delay: 400.ms),
const Spacer(),
PrimaryButton(
label: 'Build my routine',
icon: Icons.arrow_forward_rounded,
onTap: () => Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (_, a, __) => const QuizFlow(),
transitionsBuilder: (_, a, __, child) =>
SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: a, curve: Curves.easeOut)),
child: child,
),
transitionDuration: const Duration(milliseconds: 350),
),
),
)
.animate()
.fadeIn(duration: 400.ms, delay: 600.ms)
.slideY(begin: 0.2, end: 0, duration: 400.ms),
const SizedBox(height: 12),
Center(
child: Text(
'Free · No credit card · 1 min setup',
style: GoogleFonts.plusJakartaSans(
fontSize: 12,
color: SColors.textSecondary,
),
),
)
.animate()
.fadeIn(duration: 400.ms, delay: 700.ms),
const SizedBox(height: 24),
],
),
),
),
);
}
Widget _badge() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: SColors.primary.withAlpha(18),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 8,
height: 8,
decoration: const BoxDecoration(
color: SColors.accent,
shape: BoxShape.circle,
),
),
const SizedBox(width: 6),
Text(
'Trusted by 50,000+ women',
style: GoogleFonts.plusJakartaSans(
fontSize: 12,
fontWeight: FontWeight.w600,
color: SColors.primary,
),
),
],
),
);
}
Widget _statsRow() {
return Row(
children: [
_statCard('50K+', 'Users'),
const SizedBox(width: 12),
_statCard('4.9★', 'Rating'),
const SizedBox(width: 12),
_statCard('94%', 'See results'),
],
);
}
Widget _statCard(String value, String label) {
return Expanded(
child: Container(
padding: const EdgeInsets.symmetric(vertical: 16),
decoration: BoxDecoration(
color: SColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: SColors.border),
),
child: Column(
children: [
Text(
value,
style: GoogleFonts.plusJakartaSans(
fontSize: 20,
fontWeight: FontWeight.w800,
color: SColors.primary,
),
),
const SizedBox(height: 2),
Text(
label,
style: GoogleFonts.plusJakartaSans(
fontSize: 12,
color: SColors.textSecondary,
),
),
],
),
),
);
}
Widget _featureList() {
const items = [
(Icons.auto_awesome_rounded, 'AI-powered analysis', 'Personalised to your skin'),
(Icons.science_rounded, 'Ingredient science', 'Evidence-based recommendations'),
(Icons.lock_outline_rounded, 'Private & secure', 'Your data stays with you'),
];
return Column(
children: items.map((item) => _featureRow(item.$1, item.$2, item.$3)).toList(),
);
}
Widget _featureRow(IconData icon, String title, String sub) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: SColors.primary.withAlpha(18),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: SColors.primary, size: 20),
),
const SizedBox(width: 12),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: GoogleFonts.plusJakartaSans(
fontSize: 14,
fontWeight: FontWeight.w600,
color: SColors.textPrimary,
),
),
Text(
sub,
style: GoogleFonts.plusJakartaSans(
fontSize: 12,
color: SColors.textSecondary,
),
),
],
),
],
),
);
}
}