import 'dart:math' as math; import 'package:confetti/confetti.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; import '../modelos/gamificacion_usuario.dart'; import '../modelos/jugador.dart'; import '../modelos/palabra.dart'; import '../modelos/partida.dart'; import '../servicios/servicio_historial_partidas.dart'; import '../servicios/servicio_nearby.dart'; import '../servicios/servicio_perfil_usuario.dart'; import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_principal.dart'; import 'pantalla_ver_palabra.dart'; class PantallaFinPartida extends StatefulWidget { const PantallaFinPartida({super.key}); @override State createState() => _PantallaFinPartidaState(); } class _PantallaFinPartidaState extends State { bool _guardada = false; ProgresoGamificacionUsuario? _progreso; late final ConfettiController _confetti; @override void initState() { super.initState(); _confetti = ConfettiController(duration: const Duration(seconds: 5)); Future.delayed(const Duration(milliseconds: 450), () { if (mounted) _confetti.play(); }); } @override void dispose() { _confetti.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final estado = context.watch(); final partida = estado.partida; if (partida == null) return const SizedBox.shrink(); final ganaronJugadores = partida.ganador == 'jugadores'; final impostores = partida.jugadores.where((j) => j.esImpostor).toList(); _registrarResultadoSiHaceFalta(context, partida, ganaronJugadores); return Scaffold( extendBodyBehindAppBar: true, backgroundColor: const Color(0xFF05070D), body: FondoFarolero( intenso: true, child: Stack( children: [ Positioned.fill( child: IgnorePointer( child: CustomPaint( painter: _EscenarioFinPartidaPainter( color: ganaronJugadores ? TemaApp.colorVerde : TemaApp.colorAcento, ), ), ), ), Align( alignment: Alignment.topCenter, child: ConfettiWidget( confettiController: _confetti, blastDirectionality: BlastDirectionality.explosive, emissionFrequency: 0.09, numberOfParticles: 28, gravity: 0.18, colors: const [ TemaApp.colorDorado, TemaApp.colorNaranja, TemaApp.colorAcento, Color(0xFFFFECBE), ], ), ), Positioned.fill( child: IgnorePointer( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.transparent, Colors.black.withValues(alpha: 0.10), Colors.black.withValues(alpha: 0.52), ], stops: const [0.0, 0.54, 1.0], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ), ), SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 22, 20, 28), child: Column( children: [ _HeroResultado( encabezado: l10n.gameOver, titulo: ganaronJugadores ? l10n.playersWin : l10n.impostorsWin, icono: ganaronJugadores ? Icons.emoji_events : Icons.theater_comedy, color: ganaronJugadores ? TemaApp.colorVerde : TemaApp.colorAcento, ), Transform.translate( offset: const Offset(0, -18), child: Column( children: [ if (_progreso == null) const _TarjetaRecompensaCargando() else _TarjetaProgresoGamificacion(progreso: _progreso!), const SizedBox(height: 18), _TarjetaSecreto( palabra: partida.palabraSecreta, categoria: BancoPalabras.nombreBonitoCategoria( partida.categoriaReal, l10n, ), ), const SizedBox(height: 18), _TarjetaImpostores( titulo: impostores.length == 1 ? l10n.theImpostorWas : l10n.theImpostorsWere, impostores: impostores, ), const SizedBox(height: 18), if (partida.historialVotaciones.isNotEmpty) _TarjetaHistorialVotos(partida: partida, l10n: l10n), const SizedBox(height: 24), _BotonesFinPartida( estado: estado, onPrincipal: () async { await context.read().desconectar(); estado.limpiar(); if (!context.mounted) return; Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (_) => const PantallaPrincipal(), ), (route) => false, ); }, ), const SizedBox(height: 16), ], ), ), ], ), ), ), ], ), ), ); } void _registrarResultadoSiHaceFalta( BuildContext context, Partida partida, bool ganaronJugadores, ) { if (_guardada || partida.ganador == null) return; _guardada = true; WidgetsBinding.instance.addPostFrameCallback((_) async { if (!mounted) return; final historial = context.read(); final perfil = context.read(); await historial.guardarPartida(partida); final progreso = await perfil.registrarPartidaCompletada( victoria: ganaronJugadores, ); if (!mounted) return; setState(() => _progreso = progreso); if (progreso.nuevasMedallas.isNotEmpty || progreso.incrementoFuego > 0) { _confetti.play(); } }); } } class _HeroResultado extends StatelessWidget { final String encabezado; final String titulo; final IconData icono; final Color color; const _HeroResultado({ required this.encabezado, required this.titulo, required this.icono, required this.color, }); @override Widget build(BuildContext context) { final apertura = String.fromCharCode(0x00A1); final tituloLimpio = titulo .replaceAll(apertura, '') .replaceAll('!', '') .trim() .toUpperCase(); return Stack( alignment: Alignment.center, children: [ SizedBox( height: 420, width: double.infinity, child: CustomPaint(painter: _HeroCinematicoPainter(color: color)), ), Column( children: [ Text( encabezado, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: TemaApp.colorDorado, fontSize: 38, fontWeight: FontWeight.w900, letterSpacing: -0.5, shadows: [ Shadow( color: TemaApp.colorNaranja.withValues(alpha: 0.55), blurRadius: 18, ), ], ), ).animate().fadeIn(duration: 260.ms).slideY(begin: -0.18), const SizedBox(height: 62), Text( 'RESULTADOS', style: Theme.of(context).textTheme.labelLarge?.copyWith( color: TemaApp.colorDorado, fontWeight: FontWeight.w900, letterSpacing: 5, ), ).animate().fadeIn(duration: 350.ms).slideY(begin: -0.25), const SizedBox(height: 20), Stack( alignment: Alignment.center, children: [ Image.asset( 'assets/ui/generated/final_rewards/cinematic_burst.png', width: 260, height: 260, fit: BoxFit.contain, ), Container( width: 154, height: 154, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ color.withValues(alpha: 0.36), const Color(0xFF111116), Colors.black.withValues(alpha: 0.88), ], ), border: Border.all(color: TemaApp.colorDorado, width: 4), boxShadow: [ BoxShadow( color: TemaApp.colorNaranja.withValues(alpha: 0.75), blurRadius: 52, spreadRadius: 7, ), BoxShadow( color: color.withValues(alpha: 0.62), blurRadius: 36, spreadRadius: 2, ), ], ), child: _IconoResultadoPremium(icono: icono), ), ], ) .animate() .scale( begin: const Offset(0.55, 0.55), duration: 520.ms, curve: Curves.elasticOut, ) .shimmer(delay: 700.ms, duration: 1500.ms), const SizedBox(height: 12), Text( '$apertura$tituloLimpio!', textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: color, fontSize: 34, fontWeight: FontWeight.w900, letterSpacing: 1.2, shadows: [ Shadow(color: color.withValues(alpha: 0.90), blurRadius: 24), ], ), ).animate().fadeIn(delay: 180.ms).slideY(begin: 0.25), ], ), ], ); } } class _IconoResultadoPremium extends StatelessWidget { final IconData icono; const _IconoResultadoPremium({required this.icono}); @override Widget build(BuildContext context) { if (icono != Icons.theater_comedy) { return Icon(icono, size: 82, color: TemaApp.colorDorado); } return Stack( alignment: Alignment.center, children: [ Transform.translate( offset: const Offset(-18, 12), child: Transform.rotate( angle: -0.10, child: Icon( Icons.mood, size: 66, color: TemaApp.colorDorado.withValues(alpha: 0.98), ), ), ), Transform.translate( offset: const Offset(20, -13), child: Transform.rotate( angle: 0.12, child: Icon( Icons.sentiment_dissatisfied, size: 70, color: TemaApp.colorDorado.withValues(alpha: 0.98), ), ), ), ], ); } } class _TarjetaProgresoGamificacion extends StatelessWidget { final ProgresoGamificacionUsuario progreso; const _TarjetaProgresoGamificacion({required this.progreso}); @override Widget build(BuildContext context) { final nuevas = progreso.nuevasMedallas; final antes = progreso.antes.fuego.clamp(0, 100); final despues = progreso.despues.fuego.clamp(0, 100); return _PanelRecompensa( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 52, height: 52, decoration: BoxDecoration( shape: BoxShape.circle, gradient: LinearGradient( colors: [ TemaApp.colorNaranja.withValues(alpha: 0.95), TemaApp.colorDorado.withValues(alpha: 0.78), ], begin: Alignment.bottomLeft, end: Alignment.topRight, ), boxShadow: [ BoxShadow( color: TemaApp.colorNaranja.withValues(alpha: 0.42), blurRadius: 22, ), ], ), child: const Icon( Icons.local_fire_department, color: Color(0xFF1B1010), size: 30, ), ), const SizedBox(width: 14), Expanded( child: Text( 'RECOMPENSAS DE PARTIDA', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorDorado, fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 1.1, height: 1.05, ), ), ), _DeltaFuego(valor: progreso.incrementoFuego), ], ), const SizedBox(height: 16), _BarraFuegoPremium(antes: antes, despues: despues), const SizedBox(height: 20), if (nuevas.isEmpty) Text( 'Sin medallas nuevas esta vez. SeguĂ­ acumulando fuego.', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorTextoSecundario, height: 1.35, ), ) else ...[ Text( 'NUEVAS MEDALLAS', style: Theme.of(context).textTheme.labelLarge?.copyWith( color: TemaApp.colorDorado, fontWeight: FontWeight.w800, letterSpacing: 1.5, ), ), const SizedBox(height: 12), Wrap( spacing: 12, runSpacing: 12, alignment: WrapAlignment.center, children: [for (final id in nuevas) _MedallaDesbloqueada(id: id)], ), ], ], ), ).animate().fadeIn(delay: 250.ms).slideY(begin: 0.12); } } class _TarjetaRecompensaCargando extends StatelessWidget { const _TarjetaRecompensaCargando(); @override Widget build(BuildContext context) { return _PanelRecompensa( child: Row( children: [ const SizedBox( width: 28, height: 28, child: CircularProgressIndicator( strokeWidth: 3, color: TemaApp.colorNaranja, ), ), const SizedBox(width: 12), Expanded( child: Text( 'Calculando recompensas...', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorDorado, ), ), ), ], ), ); } } class _PanelRecompensa extends StatelessWidget { final Widget child; const _PanelRecompensa({required this.child}); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(32), child: Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: LinearGradient( colors: [ const Color(0xFF101A27).withValues(alpha: 0.96), const Color(0xFF1B0E24).withValues(alpha: 0.94), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(32), border: Border.all(color: TemaApp.colorDorado.withValues(alpha: 0.58)), boxShadow: [ BoxShadow( color: TemaApp.colorNaranja.withValues(alpha: 0.25), blurRadius: 44, offset: const Offset(0, 22), ), BoxShadow( color: Colors.black.withValues(alpha: 0.50), blurRadius: 28, offset: const Offset(0, 14), ), ], ), foregroundDecoration: BoxDecoration( borderRadius: BorderRadius.circular(32), gradient: LinearGradient( colors: [ Colors.white.withValues(alpha: 0.06), Colors.transparent, TemaApp.colorDorado.withValues(alpha: 0.04), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), child: child, ), ); } } class _DeltaFuego extends StatelessWidget { final int valor; const _DeltaFuego({required this.valor}); @override Widget build(BuildContext context) { final texto = valor >= 0 ? '+$valor' : '$valor'; return Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), decoration: BoxDecoration( gradient: LinearGradient( colors: [ TemaApp.colorNaranja.withValues(alpha: 0.28), TemaApp.colorDorado.withValues(alpha: 0.14), ], ), borderRadius: BorderRadius.circular(999), border: Border.all(color: TemaApp.colorNaranja), boxShadow: [ BoxShadow( color: TemaApp.colorNaranja.withValues(alpha: 0.28), blurRadius: 18, ), ], ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.local_fire_department, color: TemaApp.colorNaranja, size: 24), const SizedBox(width: 4), Text( texto, style: Theme.of(context).textTheme.headlineSmall?.copyWith( color: TemaApp.colorDorado, fontWeight: FontWeight.w900, ), ), ], ), ).animate().scale(delay: 520.ms, duration: 420.ms, curve: Curves.elasticOut); } } class _BarraFuegoPremium extends StatelessWidget { final int antes; final int despues; const _BarraFuegoPremium({required this.antes, required this.despues}); @override Widget build(BuildContext context) { return TweenAnimationBuilder( tween: Tween(begin: antes / 100, end: despues / 100), duration: const Duration(milliseconds: 1300), curve: Curves.easeOutCubic, builder: (context, value, _) { final normalizado = value.clamp(0.0, 1.0).toDouble(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( 'Fuego', style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, ), ), const Spacer(), Text( '${(normalizado * 100).round()}%', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorDorado, fontWeight: FontWeight.w900, ), ), ], ), const SizedBox(height: 12), Container( height: 38, decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.72), borderRadius: BorderRadius.circular(999), border: Border.all( color: TemaApp.colorDorado.withValues(alpha: 0.38), ), boxShadow: [ BoxShadow( color: TemaApp.colorNaranja.withValues(alpha: 0.24), blurRadius: 18, ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(999), child: Stack( children: [ FractionallySizedBox( widthFactor: normalizado, child: Container( decoration: const BoxDecoration( gradient: LinearGradient( colors: [ Color(0xFFE53935), TemaApp.colorNaranja, TemaApp.colorDorado, Color(0xFFFFECBE), ], ), ), ), ), Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: [ Colors.white.withValues(alpha: 0.32), Colors.transparent, Colors.black.withValues(alpha: 0.18), ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ), ), ), ), Positioned( left: 12, right: 12, top: 5, child: Container( height: 6, decoration: BoxDecoration( borderRadius: BorderRadius.circular(999), gradient: LinearGradient( colors: [ Colors.white.withValues(alpha: 0.62), Colors.transparent, ], ), ), ), ), ], ), ), ), ], ); }, ); } } class _MedallaDesbloqueada extends StatelessWidget { final String id; const _MedallaDesbloqueada({required this.id}); @override Widget build(BuildContext context) { final medalla = EstadisticasPerfilUsuario.catalogoMedallas[id]; if (medalla == null) return const SizedBox.shrink(); return Container( width: 94, padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.28), borderRadius: BorderRadius.circular(18), border: Border.all(color: TemaApp.colorDorado.withValues(alpha: 0.48)), ), child: Column( children: [ Stack( alignment: Alignment.center, children: [ Image.asset( 'assets/rewards/medal_unlock_burst.png', width: 82, height: 82, fit: BoxFit.cover, ), SizedBox( width: 70, height: 70, child: CustomPaint( painter: _MiniBurstPainter(color: TemaApp.colorDorado), ), ), Image.asset( medalla.assetPath, width: 58, height: 58, fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) => Text(medalla.emoji, style: const TextStyle(fontSize: 32)), ), ], ), const SizedBox(height: 6), Text( medalla.nombre, maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: TemaApp.colorTexto, fontWeight: FontWeight.w700, ), ), ], ), ) .animate() .scale(duration: 520.ms, curve: Curves.elasticOut) .shimmer(delay: 650.ms, duration: 1200.ms); } } class _TarjetaSecreto extends StatelessWidget { final String palabra; final String categoria; const _TarjetaSecreto({required this.palabra, required this.categoria}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return _PanelRecompensa( child: Column( children: [ Text( l10n.theSecretWordWas, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w900, color: Colors.white, ), ), const SizedBox(height: 14), Text( palabra.toUpperCase(), textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineLarge?.copyWith( color: TemaApp.colorNaranja, fontSize: 42, fontWeight: FontWeight.w900, shadows: [ Shadow( color: TemaApp.colorNaranja.withValues(alpha: 0.55), blurRadius: 18, ), ], ), ), const SizedBox(height: 4), Text( l10n.categoryLabel(categoria), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: TemaApp.colorTextoSecundario, ), ), ], ), ).animate().fadeIn(delay: 380.ms).slideY(begin: 0.1); } } class _TarjetaImpostores extends StatelessWidget { final String titulo; final List impostores; const _TarjetaImpostores({required this.titulo, required this.impostores}); @override Widget build(BuildContext context) { return _PanelRecompensa( child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.theater_comedy, color: TemaApp.colorAcento), const SizedBox(width: 8), Flexible( child: Text( titulo, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.w900, color: Colors.white, ), ), ), ], ), const SizedBox(height: 10), ...impostores.map( (j) => Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.theater_comedy, size: 20, color: TemaApp.colorAcento), const SizedBox(width: 8), Text( j.nombre, style: Theme.of(context) .textTheme .titleLarge ?.copyWith(color: TemaApp.colorAcento), ), if (j.eliminado) ...[ const SizedBox(width: 8), const Icon(Icons.close, size: 16, color: TemaApp.colorTextoSecundario), ], ], ), ), ), ], ), ).animate().fadeIn(delay: 450.ms); } } class _TarjetaHistorialVotos extends StatelessWidget { final Partida partida; final AppLocalizations l10n; const _TarjetaHistorialVotos({required this.partida, required this.l10n}); @override Widget build(BuildContext context) { return _PanelRecompensa( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(l10n.votingHistory, style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 12), ...partida.historialVotaciones.asMap().entries.map((entrada) { final ronda = entrada.key + 1; final resultado = entrada.value; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.roundElimination(ronda, resultado.eliminadoNombre), style: TextStyle( fontWeight: FontWeight.bold, color: resultado.eraImpostor ? TemaApp.colorVerde : TemaApp.colorAcento, ), ), ...resultado.votos.entries.map((v) { final votante = partida.jugadores.firstWhere((j) => j.id == v.key); final votado = partida.jugadores.firstWhere((j) => j.id == v.value); return Text( ' ${votante.nombre} -> ${votado.nombre}', style: Theme.of(context).textTheme.bodyMedium, ); }), ], ), ); }), ], ), ); } } class _BotonesFinPartida extends StatelessWidget { final EstadoJuego estado; final VoidCallback onPrincipal; const _BotonesFinPartida({required this.estado, required this.onPrincipal}); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Column( children: [ SizedBox( width: double.infinity, height: 56, child: ElevatedButton.icon( onPressed: () { estado.revancha(); Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const PantallaVerPalabra()), ); }, icon: const Icon(Icons.replay), label: Text(l10n.rematch), ), ), const SizedBox(height: 12), SizedBox( width: double.infinity, height: 56, child: OutlinedButton.icon( onPressed: onPrincipal, icon: const Icon(Icons.home), label: Text(l10n.mainMenu), ), ), ], ); } } class _EscenarioFinPartidaPainter extends CustomPainter { final Color color; const _EscenarioFinPartidaPainter({required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint()..isAntiAlias = true; paint.shader = const LinearGradient( colors: [ Color(0xFF050B14), Color(0xFF091322), Color(0xFF16091F), ], begin: Alignment.topCenter, end: Alignment.bottomCenter, ).createShader(Offset.zero & size); canvas.drawRect(Offset.zero & size, paint); paint.shader = RadialGradient( colors: [ color.withValues(alpha: 0.28), TemaApp.colorNaranja.withValues(alpha: 0.12), Colors.transparent, ], ).createShader( Rect.fromCircle( center: Offset(size.width * 0.5, size.height * 0.30), radius: size.width * 0.86, ), ); canvas.drawCircle( Offset(size.width * 0.5, size.height * 0.30), size.width * 0.86, paint, ); paint.shader = null; _drawSkyline(canvas, size, paint); for (var i = 0; i < 52; i++) { final x = (i * 71 % math.max(size.width, 1)).toDouble(); final y = (i * 137 % math.max(size.height, 1)).toDouble(); final palette = [ TemaApp.colorDorado, TemaApp.colorNaranja, TemaApp.colorAcento, const Color(0xFFFFF1C7), ]; final confettiPaint = Paint() ..isAntiAlias = true ..color = palette[i % palette.length].withValues(alpha: 0.72); canvas.save(); canvas.translate(x, y); canvas.rotate((i % 9 - 4) * 0.22); canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromCenter( center: Offset.zero, width: 8 + (i % 4) * 6, height: 4 + (i % 3) * 3, ), const Radius.circular(1.5), ), confettiPaint, ); canvas.restore(); } paint.shader = null; paint.color = TemaApp.colorDorado.withValues(alpha: 0.10); for (var i = 0; i < 34; i++) { final x = (i * 83 % math.max(size.width, 1)).toDouble(); final y = (i * 137 % math.max(size.height, 1)).toDouble(); canvas.drawCircle(Offset(x, y), 1.2 + (i % 4), paint); } } void _drawSkyline(Canvas canvas, Size size, Paint paint) { paint.color = Colors.black.withValues(alpha: 0.36); final base = size.height * 0.78; final path = Path() ..moveTo(0, size.height) ..lineTo(0, base) ..lineTo(size.width * 0.16, base - 42) ..lineTo(size.width * 0.30, base - 12) ..lineTo(size.width * 0.48, base - 60) ..lineTo(size.width * 0.66, base - 26) ..lineTo(size.width * 0.82, base - 72) ..lineTo(size.width, base - 34) ..lineTo(size.width, size.height) ..close(); canvas.drawPath(path, paint); paint.color = TemaApp.colorNaranja.withValues(alpha: 0.18); canvas.drawRRect( RRect.fromRectAndRadius( Rect.fromCenter( center: Offset(size.width * 0.52, base - 90), width: 28, height: 130, ), const Radius.circular(12), ), paint, ); } @override bool shouldRepaint(covariant _EscenarioFinPartidaPainter oldDelegate) { return oldDelegate.color != color; } } class _HeroCinematicoPainter extends CustomPainter { final Color color; const _HeroCinematicoPainter({required this.color}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height * 0.54); final paint = Paint()..isAntiAlias = true; paint.shader = RadialGradient( colors: [ TemaApp.colorNaranja.withValues(alpha: 0.30), color.withValues(alpha: 0.14), Colors.transparent, ], ).createShader(Rect.fromCircle(center: center, radius: size.width * 0.54)); canvas.drawCircle(center, size.width * 0.54, paint); paint.shader = null; for (var i = 0; i < 28; i++) { final angle = (math.pi * 2 / 28) * i; final inner = Offset( center.dx + math.cos(angle - 0.035) * 52, center.dy + math.sin(angle - 0.035) * 52, ); final outer = Offset( center.dx + math.cos(angle) * size.width * 0.58, center.dy + math.sin(angle) * size.width * 0.58, ); final inner2 = Offset( center.dx + math.cos(angle + 0.035) * 52, center.dy + math.sin(angle + 0.035) * 52, ); paint.color = (i.isEven ? TemaApp.colorDorado : color) .withValues(alpha: i.isEven ? 0.16 : 0.09); canvas.drawPath( Path() ..moveTo(inner.dx, inner.dy) ..lineTo(outer.dx, outer.dy) ..lineTo(inner2.dx, inner2.dy) ..close(), paint, ); } } @override bool shouldRepaint(covariant _HeroCinematicoPainter oldDelegate) { return oldDelegate.color != color; } } class _MiniBurstPainter extends CustomPainter { final Color color; const _MiniBurstPainter({required this.color}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final paint = Paint() ..isAntiAlias = true ..color = color.withValues(alpha: 0.22) ..strokeCap = StrokeCap.round; for (var i = 0; i < 16; i++) { final angle = math.pi * 2 * i / 16; paint.strokeWidth = i.isEven ? 3 : 1.4; canvas.drawLine( center, Offset( center.dx + math.cos(angle) * size.width * 0.48, center.dy + math.sin(angle) * size.height * 0.48, ), paint, ); } paint ..style = PaintingStyle.fill ..shader = RadialGradient( colors: [color.withValues(alpha: 0.38), Colors.transparent], ).createShader(Rect.fromCircle(center: center, radius: size.width * 0.42)); canvas.drawCircle(center, size.width * 0.42, paint); } @override bool shouldRepaint(covariant _MiniBurstPainter oldDelegate) { return oldDelegate.color != color; } }