import 'dart:math' as math; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import '../modelos/gamificacion_usuario.dart'; import '../modelos/jugador.dart'; import '../modelos/partida.dart'; import 'tema_app.dart'; class ResultadoRondaFarolero extends StatelessWidget { final ResultadoVotacion resultado; final List jugadores; final String? mensaje; final Widget? acciones; const ResultadoRondaFarolero({ super.key, required this.resultado, required this.jugadores, this.mensaje, this.acciones, }); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final color = resultado.eraImpostor ? TemaApp.colorVerde : TemaApp.colorAcento; return Column( children: [ const SizedBox(height: 8), Stack( alignment: Alignment.center, children: [ SizedBox( height: 188, width: double.infinity, child: CustomPaint( painter: HeroCinematicoResultadoPainter(color: color), ), ), Image.asset( 'assets/ui/generated/meta/result_verdict_art.webp', height: 150, fit: BoxFit.contain, ).animate().scale( duration: 520.ms, curve: Curves.elasticOut, begin: const Offset(0.78, 0.78), ), ], ), const SizedBox(height: 10), Text( resultado.eliminadoNombre, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineLarge?.copyWith( fontSize: 36, fontWeight: FontWeight.w900, shadows: [ Shadow(color: color.withValues(alpha: 0.55), blurRadius: 18), ], ), ), const SizedBox(height: 12), _BadgeResultadoRonda( texto: resultado.eraImpostor ? l10n.wasImpostor : l10n.wasInnocent, color: color, ), if (mensaje != null && mensaje!.trim().isNotEmpty) ...[ const SizedBox(height: 14), PanelResultadoFarolero( padding: const EdgeInsets.all(16), child: Text( mensaje!, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleMedium, ), ), ], const SizedBox(height: 22), DetalleVotosFarolero(resultado: resultado, jugadores: jugadores), if (acciones != null) ...[ const SizedBox(height: 24), acciones!, ], ], ); } } class DetalleVotosFarolero extends StatelessWidget { final ResultadoVotacion resultado; final List jugadores; const DetalleVotosFarolero({ super.key, required this.resultado, required this.jugadores, }); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final jugadoresPorId = {for (final jugador in jugadores) jugador.id: jugador}; final conteo = {}; for (final votadoId in resultado.votos.values) { conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; } final maxVotos = conteo.values.isEmpty ? 1 : conteo.values.reduce((a, b) => a > b ? a : b); final ranking = conteo.entries.toList() ..sort((a, b) => b.value.compareTo(a.value)); return PanelResultadoFarolero( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.bar_chart, color: TemaApp.colorNaranja), const SizedBox(width: 8), Expanded( child: Text( l10n.votesThisRound, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, ), ), ), ], ), const SizedBox(height: 14), ...ranking.map((entry) { final jugador = jugadoresPorId[entry.key]; return BarraVotosFarolero( nombre: jugador?.nombre ?? '?', votos: entry.value, total: maxVotos, destacado: entry.key == resultado.eliminadoId, ); }), const Divider(height: 26), ...resultado.votos.entries.map((entry) { final votante = jugadoresPorId[entry.key]?.nombre ?? '?'; final votado = jugadoresPorId[entry.value]?.nombre ?? '?'; final fueAlEliminado = entry.value == resultado.eliminadoId; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Icon( fueAlEliminado ? Icons.how_to_vote : Icons.arrow_forward, size: 18, color: fueAlEliminado ? TemaApp.colorAcento : TemaApp.colorTextoSecundario, ), const SizedBox(width: 8), Expanded( child: Text( '$votante → $votado', style: TextStyle( color: fueAlEliminado ? TemaApp.colorTexto : TemaApp.colorTextoSecundario, fontWeight: fueAlEliminado ? FontWeight.bold : FontWeight.normal, ), ), ), ], ), ); }), ], ), ).animate().fadeIn(delay: 160.ms).slideY(begin: 0.08); } } class BarraVotosFarolero extends StatelessWidget { final String nombre; final int votos; final int total; final bool destacado; const BarraVotosFarolero({ super.key, required this.nombre, required this.votos, required this.total, required this.destacado, }); @override Widget build(BuildContext context) { final color = destacado ? TemaApp.colorAcento : TemaApp.colorNaranja; final proporcion = total == 0 ? 0.0 : votos / total; return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( nombre, style: TextStyle( fontWeight: destacado ? FontWeight.bold : FontWeight.w600, ), ), ), Text( '$votos', style: TextStyle(color: color, fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 6), ClipRRect( borderRadius: BorderRadius.circular(999), child: LinearProgressIndicator( value: proporcion.clamp(0.0, 1.0).toDouble(), minHeight: 12, backgroundColor: Colors.black.withValues(alpha: 0.42), valueColor: AlwaysStoppedAnimation(color), ), ), ], ), ); } } class HeroFinalPartidaFarolero extends StatelessWidget { final String encabezado; final String titulo; final IconData icono; final Color color; const HeroFinalPartidaFarolero({ super.key, 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: 520, width: double.infinity, child: CustomPaint(painter: HeroCinematicoResultadoPainter(color: color)), ), Column( children: [ Text( encabezado, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: TemaApp.colorDorado, fontSize: 38, fontWeight: FontWeight.w900, shadows: [ Shadow( color: TemaApp.colorNaranja.withValues(alpha: 0.55), blurRadius: 18, ), ], ), ).animate().fadeIn(duration: 260.ms).slideY(begin: -0.18), const SizedBox(height: 58), Stack( alignment: Alignment.center, children: [ Image.asset( 'assets/ui/generated/final_rewards/cinematic_burst.webp', 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: IconoResultadoPremiumFarolero(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: 14), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Text( '$apertura$tituloLimpio!', maxLines: 2, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: color, fontSize: 32, fontWeight: FontWeight.w900, letterSpacing: 1.0, shadows: [ Shadow( color: color.withValues(alpha: 0.90), blurRadius: 24, ), ], ), ), ).animate().fadeIn(delay: 180.ms).slideY(begin: 0.25), ], ), ], ); } } class IconoResultadoPremiumFarolero extends StatelessWidget { final IconData icono; const IconoResultadoPremiumFarolero({super.key, 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 TarjetaProgresoGamificacionPremium extends StatelessWidget { final ProgresoGamificacionUsuario progreso; const TarjetaProgresoGamificacionPremium({ super.key, 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); final l10n = AppLocalizations.of(context)!; return PanelResultadoFarolero( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const _IconoFuegoRecompensa(), const SizedBox(width: 14), Expanded( child: Text( l10n.matchRewards.toUpperCase(), style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorDorado, fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 1.1, height: 1.05, ), ), ), _DeltaFuegoPremium(valor: progreso.incrementoFuego), ], ), const SizedBox(height: 16), BarraFuegoPremiumFarolero(antes: antes, despues: despues), const SizedBox(height: 20), if (nuevas.isEmpty) Text( l10n.noNewMedalsKeepFire, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorTextoSecundario, height: 1.35, ), ) else ...[ Text( l10n.newMedals.toUpperCase(), 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) MedallaDesbloqueadaPremium(id: id), ], ), ], ], ), ).animate().fadeIn(delay: 250.ms).slideY(begin: 0.12); } } class TarjetaRecompensaCargandoPremium extends StatelessWidget { const TarjetaRecompensaCargandoPremium({super.key}); @override Widget build(BuildContext context) { return PanelResultadoFarolero( child: Row( children: [ const SizedBox( width: 28, height: 28, child: CircularProgressIndicator( strokeWidth: 3, color: TemaApp.colorNaranja, ), ), const SizedBox(width: 12), Expanded( child: Text( AppLocalizations.of(context)!.calculatingRewards, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorDorado, ), ), ), ], ), ); } } class PanelResultadoFarolero extends StatelessWidget { final Widget child; final EdgeInsetsGeometry padding; const PanelResultadoFarolero({ super.key, required this.child, this.padding = const EdgeInsets.all(24), }); @override Widget build(BuildContext context) { return ClipRRect( borderRadius: BorderRadius.circular(32), child: Container( width: double.infinity, padding: padding, 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 BarraFuegoPremiumFarolero extends StatelessWidget { final int antes; final int despues; const BarraFuegoPremiumFarolero({ super.key, 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, child) { final normalizado = value.clamp(0.0, 1.0).toDouble(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.local_fire_department, color: TemaApp.colorNaranja, size: 18), const SizedBox(width: 6), Text( '${AppLocalizations.of(context)!.fireLabel} ' '${(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, ), ), ), ), ], ), ), ), ], ); }, ); } } class MedallaDesbloqueadaPremium extends StatelessWidget { final String id; const MedallaDesbloqueadaPremium({super.key, 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.webp', width: 82, height: 82, fit: BoxFit.cover, ), SizedBox( width: 70, height: 70, child: CustomPaint( painter: MiniBurstFaroleroPainter(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 TarjetaSecretoPremium extends StatelessWidget { final String palabra; final String categoria; const TarjetaSecretoPremium({ super.key, required this.palabra, required this.categoria, }); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return PanelResultadoFarolero( 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 TarjetaImpostoresPremium extends StatelessWidget { final String titulo; final List impostores; const TarjetaImpostoresPremium({ super.key, required this.titulo, required this.impostores, }); @override Widget build(BuildContext context) { return PanelResultadoFarolero( 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( (jugador) => 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), Flexible( child: Text( jugador.nombre, textAlign: TextAlign.center, style: Theme.of(context) .textTheme .titleLarge ?.copyWith(color: TemaApp.colorAcento), ), ), if (jugador.eliminado) ...[ const SizedBox(width: 8), const Icon( Icons.close, size: 16, color: TemaApp.colorTextoSecundario, ), ], ], ), ), ), ], ), ).animate().fadeIn(delay: 450.ms); } } class TarjetaHistorialVotosPremium extends StatelessWidget { final List historial; final List jugadores; const TarjetaHistorialVotosPremium({ super.key, required this.historial, required this.jugadores, }); @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final jugadoresPorId = {for (final jugador in jugadores) jugador.id: jugador}; return PanelResultadoFarolero( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.votingHistory, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w900, ), ), const SizedBox(height: 12), ...historial.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((voto) { final votante = jugadoresPorId[voto.key]?.nombre ?? '?'; final votado = jugadoresPorId[voto.value]?.nombre ?? '?'; return Text( ' $votante → $votado', style: Theme.of(context).textTheme.bodyMedium, ); }), ], ), ); }), ], ), ); } } class EscenarioFinPartidaFaroleroPainter extends CustomPainter { final Color color; const EscenarioFinPartidaFaroleroPainter({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; 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); 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(); } } @override bool shouldRepaint(covariant EscenarioFinPartidaFaroleroPainter oldDelegate) { return oldDelegate.color != color; } } class HeroCinematicoResultadoPainter extends CustomPainter { final Color color; const HeroCinematicoResultadoPainter({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 HeroCinematicoResultadoPainter oldDelegate) { return oldDelegate.color != color; } } class MiniBurstFaroleroPainter extends CustomPainter { final Color color; const MiniBurstFaroleroPainter({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 MiniBurstFaroleroPainter oldDelegate) { return oldDelegate.color != color; } } class _BadgeResultadoRonda extends StatelessWidget { final String texto; final Color color; const _BadgeResultadoRonda({required this.texto, required this.color}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), decoration: BoxDecoration( color: color.withValues(alpha: 0.24), borderRadius: BorderRadius.circular(30), border: Border.all(color: color), boxShadow: [ BoxShadow(color: color.withValues(alpha: 0.28), blurRadius: 18), ], ), child: Text( texto, textAlign: TextAlign.center, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: color, ), ), ).animate().scale(delay: 180.ms, duration: 380.ms); } } class _IconoFuegoRecompensa extends StatelessWidget { const _IconoFuegoRecompensa(); @override Widget build(BuildContext context) { return 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, ), ); } } class _DeltaFuegoPremium extends StatelessWidget { final int valor; const _DeltaFuegoPremium({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); } }