diff --git a/assets/ui/premium/card_sheen_overlay.png b/assets/ui/premium/card_sheen_overlay.png new file mode 100644 index 0000000..88afd1a Binary files /dev/null and b/assets/ui/premium/card_sheen_overlay.png differ diff --git a/assets/ui/premium/corner_orange_glow.png b/assets/ui/premium/corner_orange_glow.png new file mode 100644 index 0000000..3841a4d Binary files /dev/null and b/assets/ui/premium/corner_orange_glow.png differ diff --git a/assets/ui/premium/lantern_radial_glow.png b/assets/ui/premium/lantern_radial_glow.png new file mode 100644 index 0000000..ef8e8cb Binary files /dev/null and b/assets/ui/premium/lantern_radial_glow.png differ diff --git a/assets/ui/premium/qr_frame_overlay.png b/assets/ui/premium/qr_frame_overlay.png new file mode 100644 index 0000000..4629c0e Binary files /dev/null and b/assets/ui/premium/qr_frame_overlay.png differ diff --git a/assets/ui/premium/sparks_overlay.png b/assets/ui/premium/sparks_overlay.png new file mode 100644 index 0000000..8c27c49 Binary files /dev/null and b/assets/ui/premium/sparks_overlay.png differ diff --git a/assets/ui/premium/timer_ring_glow.png b/assets/ui/premium/timer_ring_glow.png new file mode 100644 index 0000000..c79fe6f Binary files /dev/null and b/assets/ui/premium/timer_ring_glow.png differ diff --git a/assets/ui/premium/vote_danger_glow.png b/assets/ui/premium/vote_danger_glow.png new file mode 100644 index 0000000..486c561 Binary files /dev/null and b/assets/ui/premium/vote_danger_glow.png differ diff --git a/assets/ui/premium/word_reveal_glow.png b/assets/ui/premium/word_reveal_glow.png new file mode 100644 index 0000000..66f55b8 Binary files /dev/null and b/assets/ui/premium/word_reveal_glow.png differ diff --git a/docs/premium_screen_mockups.md b/docs/premium_screen_mockups.md new file mode 100644 index 0000000..b1f056a --- /dev/null +++ b/docs/premium_screen_mockups.md @@ -0,0 +1,58 @@ +# Maquetas premium por pantalla + +Estas maquetas son la guía de implementación. No son screenshots finales: definen composición, jerarquía y assets por pantalla para mantener coherencia sin romper Flutter/localización/estado. + +## Sistema común + +- Fondo: noche azul-negro + glow de farol + chispas. +- Paneles: cristal oscuro, borde dorado fino, sheen transparente. +- CTAs: primario dorado/naranja, secundarios cristal oscuro. +- Iconografía: farol/fuego/máscara/voto/QR según fase. +- Assets base: `assets/ui/premium/lantern_radial_glow.png`, `card_sheen_overlay.png`, `sparks_overlay.png`, `corner_orange_glow.png`. +- Assets específicos ya creados: `word_reveal_glow.png`, `vote_danger_glow.png`, `qr_frame_overlay.png`, `timer_ring_glow.png`. + +## Entrada y lobby + +| Pantalla | Maqueta objetivo | Assets | +| --- | --- | --- | +| Principal | Perfil glass arriba, hero FAROLERO, CTA JUGAR dominante, secundarios debajo. | base | +| Selección modo | Hero “¿Cómo querés jugar?”, dos cards grandes con iconos y glow diferenciado. | base | +| Crear partida | Cabecera de configuración, cards por sección: modo, categoría, jugadores, reglas. CTA fijo visual al final. | base | +| Unirse | Paso guiado: nombre → búsqueda → sala → espera. Estados con cards amplias y feedback luminoso. | base + futuro scan frame | +| Lobby host | QR como pieza hero en marco dorado, stats compactos, lista de usuarios en panel alto, CTA iniciar fuerte. | base + futuro qr frame | + +## Palabra y debate + +| Pantalla | Maqueta objetivo | Assets | +| --- | --- | --- | +| Ver palabra | Card secreta teatral, gesto de “tap/reveal”, aviso de privacidad. | base + word glow | +| Palabra cliente | Igual que ver palabra, adaptado a remoto. | base + word glow | +| Palabras cliente | Carrusel/lista de jugadores controlados con estado “visto/no visto”. | base | +| Debate host/cliente | Timer o estado central como módulo hero, lista de jugadores activa, accesos a notas/palabra. | base + timer ring futuro | +| Revisión palabra | Bottom sheet oscuro, cards de roles/palabra con glow discreto. | base | +| Notas host/online | Selector de jugador arriba, editor en panel cristal, guardado visible. | base | + +## Votación y resultados + +| Pantalla | Maqueta objetivo | Assets | +| --- | --- | --- | +| Votación host/cliente | Header “Votación”, candidatos como cards con avatar, peligro rojo y CTA votar. | base + vote glow | +| Resultado host/online | Eliminado como hero card, barras/votos en paneles, decisión clara de siguiente fase. | base | +| Adivinanza | Máscara impostor grande, input protagonista, dos CTAs con tensión visual. | base + danger glow | +| Fin partida host/online | Cinemática de resultado, recompensas, palabra, impostor y votos como cards premium. | rewards + base | + +## Soporte + +| Pantalla | Maqueta objetivo | Assets | +| --- | --- | --- | +| Gestor host | Dashboard de fase: estado arriba, jugadores, acciones de host, controles claros. | base | +| Historial | Timeline/cards de partidas con resultado y medallas/fuego. | base | +| Reglas | Secciones tipo manual premium con iconos y numeración. | base | +| Ajustes | Perfil, idioma, avatar y opciones en secciones glass. | base | + +## Assets específicos + +- `assets/ui/premium/word_reveal_glow.png` — usado por `TarjetaPalabraFarolero`. +- `assets/ui/premium/vote_danger_glow.png` — usado en votación cliente. +- `assets/ui/premium/qr_frame_overlay.png` — usado en lobby host. +- `assets/ui/premium/timer_ring_glow.png` — usado en debate cliente. diff --git a/docs/premium_screen_redesign_plan.md b/docs/premium_screen_redesign_plan.md new file mode 100644 index 0000000..17cbddb --- /dev/null +++ b/docs/premium_screen_redesign_plan.md @@ -0,0 +1,78 @@ +# Rediseño premium global de pantallas + +Objetivo: que todas las pantallas de Farolero compartan una misma dirección visual premium: noche, farol, cristal oscuro, dorado/naranja, impostores, fuego, partículas y jerarquía clara. + +## Regla de implementación + +- Las pantallas siguen siendo Flutter real: textos, botones, listas, progreso, formularios y navegación. +- Los assets generados se usan como capas transparentes: glows, chispas, sheens, rayos y overlays. +- Los fondos full-screen pueden ser opacos, pero los overlays deben tener alpha real. +- Primero se aplica un sistema visual común; después se retoca cada pantalla por grupos. + +## Asset kit base + +| Asset | Uso | Transparencia | +| --- | --- | --- | +| `assets/ui/premium/lantern_radial_glow.png` | Glow hero/farol global | Alpha real, esquinas transparentes | +| `assets/ui/premium/card_sheen_overlay.png` | Brillo sutil para panels/botones | Alpha real, esquinas transparentes | +| `assets/ui/premium/sparks_overlay.png` | Partículas/chispas atmosféricas | Alpha real | +| `assets/ui/premium/corner_orange_glow.png` | Glow decorativo de esquina | Alpha real, visible en una esquina por diseño | +| `assets/ui/premium/word_reveal_glow.png` | Glow para revelar palabra | Alpha real, esquinas transparentes | +| `assets/ui/premium/vote_danger_glow.png` | Glow rojo de votación/peligro | Alpha real, esquinas transparentes | +| `assets/ui/premium/qr_frame_overlay.png` | Marco transparente para QR | Alpha real, esquinas transparentes | +| `assets/ui/premium/timer_ring_glow.png` | Aro de temporizador | Alpha real, esquinas transparentes | + +## Pantallas por lote + +### Lote 1 — Core shell y entrada + +- `pantalla_principal.dart` +- `pantalla_seleccion_modo_juego.dart` +- `pantalla_crear_partida.dart` +- `pantalla_unirse.dart` +- `pantalla_lobby_host.dart` + +### Lote 2 — Flujo de palabra y partida + +- `pantalla_ver_palabra.dart` +- `pantalla_palabra_cliente.dart` +- `pantalla_palabras_cliente.dart` +- `pantalla_debate.dart` +- `pantalla_debate_cliente.dart` +- `pantalla_revision_palabra.dart` + +### Lote 3 — Votación y resultado + +- `pantalla_votacion.dart` +- `pantalla_votacion_cliente.dart` +- `pantalla_resultado.dart` +- `pantalla_resultado_online.dart` +- `pantalla_adivinanza.dart` +- `pantalla_fin_partida.dart` +- `pantalla_fin_partida_online.dart` + +### Lote 4 — Soporte y configuración + +- `pantalla_gestor_host.dart` +- `pantalla_notas.dart` +- `pantalla_notas_online.dart` +- `pantalla_historial.dart` +- `pantalla_reglas.dart` +- `pantalla_ajustes.dart` + +## Aplicación global ya iniciada + +- `FondoFarolero` ahora usa assets transparentes de glow y sparks para elevar todas las pantallas que ya lo usan. +- `PanelFarolero` ahora incorpora sheen transparente para una base glassmorphism común. +- `BotonFarolero` se hizo más redondeado, con brillo y sombra premium. +- Todas las pantallas con `Scaffold` principal quedan bajo `FondoFarolero` para unificar atmósfera. +- `EncabezadoFarolero` y `EstadoVacioFarolero` centralizan headers/estados premium reutilizables. + +## Siguiente paso recomendado + +Implementar por lotes, no todo a ciegas en un único mega-cambio. Cada lote debe: + +1. Tener maqueta/objetivo visual. +2. Reutilizar el asset kit base. +3. Crear assets nuevos solo si el lote lo necesita. +4. Verificarse visualmente antes de seguir al siguiente. diff --git a/lib/pantallas/pantalla_adivinanza.dart b/lib/pantallas/pantalla_adivinanza.dart index 26ea9e5..951679d 100644 --- a/lib/pantallas/pantalla_adivinanza.dart +++ b/lib/pantallas/pantalla_adivinanza.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; +import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_debate.dart'; import 'pantalla_fin_partida.dart'; @@ -41,24 +42,24 @@ class _PantallaAdivinanzaState extends State { title: Text(l10n.impostorGuessTitle), automaticallyImplyLeading: false, ), - body: Center( - child: SingleChildScrollView( + body: FondoFarolero( + intenso: true, + child: Center( + child: SingleChildScrollView( padding: const EdgeInsets.all(32), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Text('🎭', style: TextStyle(fontSize: 64)), - const SizedBox(height: 16), - Text( - l10n.impostorCanGuess, - style: Theme.of(context).textTheme.titleLarge, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - l10n.ifCorrectImpostorsWin, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: TemaApp.colorNaranja, + EncabezadoFarolero( + icono: Icons.theater_comedy, + titulo: l10n.impostorCanGuess, + subtitulo: l10n.ifCorrectImpostorsWin, + color: TemaApp.colorAcento, + trailing: Image.asset( + 'assets/ui/premium/vote_danger_glow.png', + width: 42, + height: 42, + opacity: const AlwaysStoppedAnimation(0.64), ), ), const SizedBox(height: 32), @@ -231,6 +232,7 @@ class _PantallaAdivinanzaState extends State { ], ], ), + ), ), ), ); diff --git a/lib/pantallas/pantalla_ajustes.dart b/lib/pantallas/pantalla_ajustes.dart index 58557ac..55adc88 100644 --- a/lib/pantallas/pantalla_ajustes.dart +++ b/lib/pantallas/pantalla_ajustes.dart @@ -23,6 +23,7 @@ class _PantallaAjustesState extends State { return Scaffold( appBar: AppBar(title: Text(l10n.settingsTitle)), body: FondoFarolero( + intenso: true, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( diff --git a/lib/pantallas/pantalla_crear_partida.dart b/lib/pantallas/pantalla_crear_partida.dart index 64c7a58..ab9f45c 100644 --- a/lib/pantallas/pantalla_crear_partida.dart +++ b/lib/pantallas/pantalla_crear_partida.dart @@ -317,42 +317,23 @@ class _PantallaCrearPartidaState extends State { return Scaffold( appBar: AppBar(title: Text(l10n.createGame)), body: FondoFarolero( + intenso: true, child: SingleChildScrollView( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(18, 18, 18, 28), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - PanelFarolero( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18), - child: Row( - children: [ - const Icon(Icons.groups, color: TemaApp.colorNaranja, size: 42), - const SizedBox(width: 14), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '¿Cómo quieres jugar?', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 3), - Text( - l10n.playersRange, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - ), - ], - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + EncabezadoFarolero( + icono: Icons.groups, + titulo: '¿Cómo quieres jugar?', + subtitulo: l10n.playersRange, ), const SizedBox(height: 12), if (!widget.bloquearModo) ...[ // Modo de juego Card( child: Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(18, 18, 18, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -416,7 +397,7 @@ class _PantallaCrearPartidaState extends State { // Categoría Card( child: Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(18, 18, 18, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -453,7 +434,7 @@ class _PantallaCrearPartidaState extends State { // Jugadores Card( child: Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(18, 18, 18, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -542,7 +523,7 @@ class _PantallaCrearPartidaState extends State { // Configuración de partida Card( child: Padding( - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.fromLTRB(18, 18, 18, 28), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -614,22 +595,12 @@ class _PantallaCrearPartidaState extends State { const SizedBox(height: 24), // Botón iniciar - SizedBox( - width: double.infinity, - height: 56, - child: ElevatedButton.icon( - onPressed: (_modoMultimovil || _jugadores.length >= 3) - ? _iniciarPartida - : null, - icon: const Icon(Icons.play_arrow), - label: Text(l10n.startGame), - style: ElevatedButton.styleFrom( - textStyle: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - ), + BotonFarolero( + texto: l10n.startGame, + icono: Icons.play_arrow, + onPressed: (_modoMultimovil || _jugadores.length >= 3) + ? _iniciarPartida + : null, ), const SizedBox(height: 16), ], diff --git a/lib/pantallas/pantalla_debate.dart b/lib/pantallas/pantalla_debate.dart index e98b0ea..5083715 100644 --- a/lib/pantallas/pantalla_debate.dart +++ b/lib/pantallas/pantalla_debate.dart @@ -77,6 +77,7 @@ class _PantallaDebateState extends State { automaticallyImplyLeading: false, ), body: FondoFarolero( + intenso: true, child: Padding( padding: const EdgeInsets.all(16), child: Column( @@ -95,40 +96,56 @@ class _PantallaDebateState extends State { ? Border.all(color: TemaApp.colorAcento, width: 2) : null, ), - child: Column( + child: Stack( + alignment: Alignment.center, children: [ - Text( - _tiempoAgotado ? l10n.timeUp : l10n.timeRemaining, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: _tiempoAgotado - ? TemaApp.colorAcento - : TemaApp.colorTextoSecundario, + Positioned.fill( + child: Image.asset( + 'assets/ui/premium/timer_ring_glow.png', + fit: BoxFit.contain, + opacity: const AlwaysStoppedAnimation(0.36), ), ), - const SizedBox(height: 8), - Text( - _formatearTiempo(_segundosRestantes), - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - fontSize: 48, - fontWeight: FontWeight.bold, - color: _segundosRestantes < 10 && !_tiempoAgotado - ? TemaApp.colorAcento - : TemaApp.colorTexto, - ), - ), - const SizedBox(height: 8), - ClipRRect( - borderRadius: BorderRadius.circular(4), - child: LinearProgressIndicator( - value: progreso, - backgroundColor: TemaApp.colorSuperficie, - valueColor: AlwaysStoppedAnimation( - _segundosRestantes < 10 - ? TemaApp.colorAcento - : TemaApp.colorVerde, + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _tiempoAgotado ? l10n.timeUp : l10n.timeRemaining, + style: + Theme.of(context).textTheme.titleMedium?.copyWith( + color: _tiempoAgotado + ? TemaApp.colorAcento + : TemaApp.colorTextoSecundario, + ), ), - minHeight: 6, - ), + const SizedBox(height: 8), + Text( + _formatearTiempo(_segundosRestantes), + style: + Theme.of(context).textTheme.headlineLarge?.copyWith( + fontSize: 48, + fontWeight: FontWeight.bold, + color: _segundosRestantes < 10 && + !_tiempoAgotado + ? TemaApp.colorAcento + : TemaApp.colorTexto, + ), + ), + const SizedBox(height: 8), + ClipRRect( + borderRadius: BorderRadius.circular(4), + child: LinearProgressIndicator( + value: progreso, + backgroundColor: TemaApp.colorSuperficie, + valueColor: AlwaysStoppedAnimation( + _segundosRestantes < 10 + ? TemaApp.colorAcento + : TemaApp.colorVerde, + ), + minHeight: 6, + ), + ), + ], ), ], ), diff --git a/lib/pantallas/pantalla_debate_cliente.dart b/lib/pantallas/pantalla_debate_cliente.dart index 74e5807..33a4ae3 100644 --- a/lib/pantallas/pantalla_debate_cliente.dart +++ b/lib/pantallas/pantalla_debate_cliente.dart @@ -7,6 +7,7 @@ import 'package:farolero/pantallas/pantalla_notas_online.dart'; import 'package:farolero/pantallas/pantalla_revision_palabra.dart'; import 'package:farolero/pantallas/pantalla_votacion_cliente.dart'; import 'package:farolero/servicios/servicio_nearby.dart'; +import 'package:farolero/tema/componentes_farolero.dart'; import 'package:farolero/tema/tema_app.dart'; import 'package:provider/provider.dart'; @@ -157,9 +158,11 @@ class _PantallaDebateClienteState extends State { ), ], ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( + body: FondoFarolero( + intenso: true, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( children: [ const Spacer(), @@ -173,23 +176,37 @@ class _PantallaDebateClienteState extends State { : TemaApp.colorTarjeta, borderRadius: BorderRadius.circular(24), ), - child: Column( + child: Stack( + alignment: Alignment.center, children: [ - Text( - _segundosRestantes == 0 - ? l10n.timeUp - : l10n.timeRemaining, - style: Theme.of(context).textTheme.titleMedium, + Positioned.fill( + child: Image.asset( + 'assets/ui/premium/timer_ring_glow.png', + fit: BoxFit.contain, + opacity: const AlwaysStoppedAnimation(0.42), + ), ), - const SizedBox(height: 8), - Text( - _formatearTiempo(_segundosRestantes), - style: Theme.of(context).textTheme.displayMedium?.copyWith( - fontWeight: FontWeight.bold, - color: _segundosRestantes == 0 - ? TemaApp.colorAcento - : TemaApp.colorTexto, - ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _segundosRestantes == 0 + ? l10n.timeUp + : l10n.timeRemaining, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 8), + Text( + _formatearTiempo(_segundosRestantes), + style: + Theme.of(context).textTheme.displayMedium?.copyWith( + fontWeight: FontWeight.bold, + color: _segundosRestantes == 0 + ? TemaApp.colorAcento + : TemaApp.colorTexto, + ), + ), + ], ), ], ), @@ -273,6 +290,7 @@ class _PantallaDebateClienteState extends State { ), ), ], + ), ), ), ); diff --git a/lib/pantallas/pantalla_fin_partida_online.dart b/lib/pantallas/pantalla_fin_partida_online.dart index 38bccc1..d723ec2 100644 --- a/lib/pantallas/pantalla_fin_partida_online.dart +++ b/lib/pantallas/pantalla_fin_partida_online.dart @@ -96,9 +96,11 @@ class _PantallaFinPartidaOnlineState extends State { ), ], ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( + body: FondoFarolero( + intenso: true, + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: Column( children: [ Container( width: double.infinity, @@ -266,6 +268,7 @@ class _PantallaFinPartidaOnlineState extends State { ), ), ], + ), ), ), ); diff --git a/lib/pantallas/pantalla_gestor_host.dart b/lib/pantallas/pantalla_gestor_host.dart index a5fd006..8ea1258 100644 --- a/lib/pantallas/pantalla_gestor_host.dart +++ b/lib/pantallas/pantalla_gestor_host.dart @@ -201,6 +201,7 @@ class _PantallaGestorHostState extends State { ], ), body: FondoFarolero( + intenso: true, child: Padding( padding: const EdgeInsets.all(16), child: Column( diff --git a/lib/pantallas/pantalla_historial.dart b/lib/pantallas/pantalla_historial.dart index 4ffbb30..c5dedca 100644 --- a/lib/pantallas/pantalla_historial.dart +++ b/lib/pantallas/pantalla_historial.dart @@ -16,6 +16,7 @@ class PantallaHistorial extends StatelessWidget { return Scaffold( appBar: AppBar(title: const Text('Historial')), body: FondoFarolero( + intenso: true, child: partidas.isEmpty ? const Center(child: Text('Todavía no hay partidas guardadas.')) : ListView.builder( diff --git a/lib/pantallas/pantalla_lobby_host.dart b/lib/pantallas/pantalla_lobby_host.dart index 34f9952..85b9ddb 100644 --- a/lib/pantallas/pantalla_lobby_host.dart +++ b/lib/pantallas/pantalla_lobby_host.dart @@ -48,25 +48,66 @@ class _PantallaLobbyHostState extends State { ), ), body: FondoFarolero( + intenso: true, child: Padding( padding: const EdgeInsets.all(24), child: Column( - children: [ - Container( + children: [ + EncabezadoFarolero( + icono: Icons.wifi_tethering, + titulo: widget.nombreSala, + subtitulo: l10n.scanToJoin, + ), + const SizedBox(height: 14), + PanelFarolero( padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - ), - child: QrImageView( - data: nearby.generarDatosQR(widget.nombreSala), - version: QrVersions.auto, - size: 160, - backgroundColor: Colors.white, + child: Column( + children: [ + SizedBox( + width: 196, + height: 196, + child: Stack( + alignment: Alignment.center, + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(18), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.18), + blurRadius: 24, + ), + ], + ), + child: QrImageView( + data: nearby.generarDatosQR(widget.nombreSala), + version: QrVersions.auto, + size: 156, + backgroundColor: Colors.white, + ), + ), + Positioned.fill( + child: IgnorePointer( + child: Image.asset( + 'assets/ui/premium/qr_frame_overlay.png', + fit: BoxFit.cover, + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + Text( + 'Escanea este código desde otro móvil', + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ], ), ), - const SizedBox(height: 12), - Text(l10n.scanToJoin), const SizedBox(height: 16), _buildResumenSala(context, seleccionados, nearby.jugadores.length), const SizedBox(height: 12), @@ -119,15 +160,15 @@ class _PantallaLobbyHostState extends State { const SizedBox(height: 12), SizedBox( width: double.infinity, - child: ElevatedButton.icon( + child: BotonFarolero( + texto: _iniciando ? l10n.starting : l10n.startGame, + icono: Icons.play_arrow, onPressed: puedeIniciar && !_iniciando ? () { setState(() => _iniciando = true); widget.onIniciar(); } : null, - icon: const Icon(Icons.play_arrow), - label: Text(_iniciando ? l10n.starting : l10n.startGame), ), ), ], diff --git a/lib/pantallas/pantalla_notas.dart b/lib/pantallas/pantalla_notas.dart index 9a17dff..0708761 100644 --- a/lib/pantallas/pantalla_notas.dart +++ b/lib/pantallas/pantalla_notas.dart @@ -3,6 +3,7 @@ import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:provider/provider.dart'; import '../estado/estado_juego.dart'; import '../servicios/servicio_notas.dart'; +import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; class PantallaNotas extends StatefulWidget { @@ -86,9 +87,12 @@ class _PantallaNotasState extends State { ), ], ), - body: _jugadorSeleccionadoId == null - ? _construirSelectorJugador(partida) - : _construirNotas(partida), + body: FondoFarolero( + intenso: true, + child: _jugadorSeleccionadoId == null + ? _construirSelectorJugador(partida) + : _construirNotas(partida), + ), ); } diff --git a/lib/pantallas/pantalla_notas_online.dart b/lib/pantallas/pantalla_notas_online.dart index b36dc8c..37b0253 100644 --- a/lib/pantallas/pantalla_notas_online.dart +++ b/lib/pantallas/pantalla_notas_online.dart @@ -3,6 +3,7 @@ import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:farolero/modelos/inicio_partida_multijugador.dart'; import 'package:farolero/modelos/jugador.dart'; import 'package:farolero/servicios/servicio_notas.dart'; +import 'package:farolero/tema/componentes_farolero.dart'; import 'package:farolero/tema/tema_app.dart'; class PantallaNotasOnline extends StatefulWidget { @@ -115,7 +116,10 @@ class _PantallaNotasOnlineState extends State { ), ], ), - body: _autor == null ? _buildSelector(context) : _buildNotas(context), + body: FondoFarolero( + intenso: true, + child: _autor == null ? _buildSelector(context) : _buildNotas(context), + ), ), ); } diff --git a/lib/pantallas/pantalla_principal.dart b/lib/pantallas/pantalla_principal.dart index 0c5b811..1172723 100644 --- a/lib/pantallas/pantalla_principal.dart +++ b/lib/pantallas/pantalla_principal.dart @@ -25,6 +25,7 @@ class PantallaPrincipal extends StatelessWidget { final gamificacion = servicioPerfil.resumenGamificacion; return Scaffold( + extendBodyBehindAppBar: true, backgroundColor: const Color(0xFF05070D), body: FondoFarolero( intenso: true, diff --git a/lib/pantallas/pantalla_reglas.dart b/lib/pantallas/pantalla_reglas.dart index f7020cd..671735b 100644 --- a/lib/pantallas/pantalla_reglas.dart +++ b/lib/pantallas/pantalla_reglas.dart @@ -13,6 +13,7 @@ class PantallaReglas extends StatelessWidget { return Scaffold( appBar: AppBar(title: Text(l10n.rulesTitle)), body: FondoFarolero( + intenso: true, child: SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( diff --git a/lib/pantallas/pantalla_resultado.dart b/lib/pantallas/pantalla_resultado.dart index 60b8431..80b4d39 100644 --- a/lib/pantallas/pantalla_resultado.dart +++ b/lib/pantallas/pantalla_resultado.dart @@ -64,6 +64,7 @@ class _PantallaResultadoState extends State automaticallyImplyLeading: false, ), body: FondoFarolero( + intenso: true, child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(32), diff --git a/lib/pantallas/pantalla_resultado_online.dart b/lib/pantallas/pantalla_resultado_online.dart index 1e058b5..e2922a3 100644 --- a/lib/pantallas/pantalla_resultado_online.dart +++ b/lib/pantallas/pantalla_resultado_online.dart @@ -4,6 +4,7 @@ import '../modelos/inicio_partida_multijugador.dart'; import '../modelos/partida.dart'; import '../modelos/snapshot_partida_online.dart'; import '../servicios/servicio_nearby.dart'; +import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_debate_cliente.dart'; import 'pantalla_fin_partida_online.dart'; @@ -168,11 +169,14 @@ class _PantallaResultadoOnlineState extends State { elevation: 0, actions: _acciones(context, l10n), ), - body: Padding( - padding: const EdgeInsets.all(24), - child: resultado == null - ? _buildEsperaAdivinanza(context, l10n) - : _buildResultado(context, l10n, resultado), + body: FondoFarolero( + intenso: true, + child: Padding( + padding: const EdgeInsets.all(24), + child: resultado == null + ? _buildEsperaAdivinanza(context, l10n) + : _buildResultado(context, l10n, resultado), + ), ), ); } diff --git a/lib/pantallas/pantalla_seleccion_modo_juego.dart b/lib/pantallas/pantalla_seleccion_modo_juego.dart index 4abd61d..9d1d840 100644 --- a/lib/pantallas/pantalla_seleccion_modo_juego.dart +++ b/lib/pantallas/pantalla_seleccion_modo_juego.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import '../tema/componentes_farolero.dart'; import '../tema/tema_app.dart'; @@ -10,40 +11,27 @@ class PantallaSeleccionModoJuego extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Elegir modo de juego')), + extendBodyBehindAppBar: true, + appBar: AppBar(title: const Text('Elegir modo')), body: FondoFarolero( + intenso: true, child: SafeArea( child: Center( child: SingleChildScrollView( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.fromLTRB(20, 24, 20, 28), child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 460), + constraints: const BoxConstraints(maxWidth: 470), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const Icon( - Icons.sports_esports, - size: 64, - color: TemaApp.colorNaranja, - ), - const SizedBox(height: 16), - Text( - '¿Cómo querés jugar?', - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - 'Elegí primero el tipo de partida para configurar solo lo necesario.', - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 28), + const SizedBox(height: 12), + const _ModoHero().animate().fadeIn(duration: 320.ms).slideY(begin: -0.12), + const SizedBox(height: 34), _ModoCard( - icono: Icons.phone_android, - titulo: 'Partida en este dispositivo', - descripcion: - 'Todos los jugadores usan este móvil. Acá se agregan los nombres manualmente.', + icono: Icons.phone_android_rounded, + titulo: 'Un móvil', + subtitulo: 'Partida en este dispositivo', + descripcion: 'Ideal para jugar todos juntos pasando el móvil. Configuración rápida y directa.', onTap: () => Navigator.push( context, MaterialPageRoute( @@ -53,13 +41,13 @@ class PantallaSeleccionModoJuego extends StatelessWidget { ), ), ), - ), - const SizedBox(height: 14), + ).animate().fadeIn(delay: 120.ms).slideX(begin: -0.08), + const SizedBox(height: 16), _ModoCard( - icono: Icons.devices, - titulo: 'Partida multidispositivo', - descripcion: - 'Este móvil crea el servidor. Los usuarios se gestionan después en el lobby.', + icono: Icons.devices_rounded, + titulo: 'Multidispositivo', + subtitulo: 'Cada jugador en su móvil', + descripcion: 'Crea una sala premium, comparte QR y gestiona usuarios desde el lobby.', destacado: true, onTap: () => Navigator.push( context, @@ -70,7 +58,7 @@ class PantallaSeleccionModoJuego extends StatelessWidget { ), ), ), - ), + ).animate().fadeIn(delay: 200.ms).slideX(begin: 0.08), ], ), ), @@ -82,9 +70,82 @@ class PantallaSeleccionModoJuego extends StatelessWidget { } } +class _ModoHero extends StatelessWidget { + const _ModoHero(); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 230, + child: Stack( + alignment: Alignment.center, + children: [ + Positioned.fill( + child: Image.asset( + 'assets/ui/premium/lantern_radial_glow.png', + fit: BoxFit.contain, + opacity: const AlwaysStoppedAnimation(0.58), + ), + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 90, + height: 90, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + TemaApp.colorDorado.withValues(alpha: 0.95), + TemaApp.colorNaranja.withValues(alpha: 0.58), + Colors.black.withValues(alpha: 0.76), + ], + ), + border: Border.all(color: TemaApp.colorDorado, width: 3), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.55), + blurRadius: 42, + spreadRadius: 5, + ), + ], + ), + child: const Icon(Icons.sports_esports_rounded, size: 48, color: Color(0xFF241103)), + ), + const SizedBox(height: 18), + Text( + '¿Cómo querés jugar?', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: TemaApp.colorDorado, + fontSize: 32, + fontWeight: FontWeight.w900, + shadows: [ + Shadow(color: TemaApp.colorNaranja.withValues(alpha: 0.45), blurRadius: 16), + ], + ), + ), + const SizedBox(height: 8), + Text( + 'Elegí el tipo de partida y arrancá sin fricción.', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: TemaApp.colorTextoSecundario, + ), + ), + ], + ), + ], + ), + ); + } +} + class _ModoCard extends StatelessWidget { final IconData icono; final String titulo; + final String subtitulo; final String descripcion; final bool destacado; final VoidCallback onTap; @@ -92,6 +153,7 @@ class _ModoCard extends StatelessWidget { const _ModoCard({ required this.icono, required this.titulo, + required this.subtitulo, required this.descripcion, required this.onTap, this.destacado = false, @@ -100,45 +162,86 @@ class _ModoCard extends StatelessWidget { @override Widget build(BuildContext context) { final color = destacado ? TemaApp.colorNaranja : TemaApp.colorAcento; - return Card( - color: TemaApp.colorTarjeta, + return Material( + color: Colors.transparent, child: InkWell( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(28), onTap: onTap, - child: Padding( - padding: const EdgeInsets.all(18), - child: Row( - children: [ - Container( - width: 52, - height: 52, - decoration: BoxDecoration( - color: color.withValues(alpha: 0.18), - borderRadius: BorderRadius.circular(16), - border: Border.all(color: color.withValues(alpha: 0.7)), - ), - child: Icon(icono, color: color, size: 30), + child: Ink( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color(0xFF111C29).withValues(alpha: 0.94), + (destacado ? const Color(0xFF2A1620) : const Color(0xFF15111F)).withValues(alpha: 0.92), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(28), + border: Border.all(color: color.withValues(alpha: destacado ? 0.78 : 0.48)), + boxShadow: [ + BoxShadow( + color: color.withValues(alpha: destacado ? 0.26 : 0.14), + blurRadius: destacado ? 34 : 22, + offset: const Offset(0, 14), ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + ], + ), + child: Stack( + children: [ + Positioned.fill( + child: Image.asset( + 'assets/ui/premium/card_sheen_overlay.png', + fit: BoxFit.cover, + opacity: AlwaysStoppedAnimation(destacado ? 0.34 : 0.22), + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: Row( children: [ - Text(titulo, style: Theme.of(context).textTheme.titleLarge), - const SizedBox(height: 6), - Text( - descripcion, - style: Theme.of(context).textTheme.bodyMedium, + Container( + width: 64, + height: 64, + decoration: BoxDecoration( + color: color.withValues(alpha: 0.18), + borderRadius: BorderRadius.circular(22), + border: Border.all(color: color.withValues(alpha: 0.72)), + ), + child: Icon(icono, color: color, size: 34), ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + titulo.toUpperCase(), + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: destacado ? TemaApp.colorDorado : Colors.white, + fontWeight: FontWeight.w900, + letterSpacing: 0.8, + ), + ), + const SizedBox(height: 3), + Text( + subtitulo, + style: Theme.of(context).textTheme.titleMedium?.copyWith(color: color), + ), + const SizedBox(height: 7), + Text(descripcion, style: Theme.of(context).textTheme.bodyMedium), + ], + ), + ), + const SizedBox(width: 8), + Icon(Icons.chevron_right_rounded, color: color, size: 32), ], ), ), - const SizedBox(width: 8), - const Icon(Icons.chevron_right), ], ), ), ), ); } -} \ No newline at end of file +} diff --git a/lib/pantallas/pantalla_unirse.dart b/lib/pantallas/pantalla_unirse.dart index a22f46f..c85f5f6 100644 --- a/lib/pantallas/pantalla_unirse.dart +++ b/lib/pantallas/pantalla_unirse.dart @@ -443,28 +443,19 @@ class _PantallaUnirseState extends State { return Scaffold( appBar: AppBar(title: Text(l10n.joinGameTitle)), body: FondoFarolero( + intenso: true, child: Padding( padding: const EdgeInsets.all(32), child: Form( - key: _formKey, - child: Column( + key: _formKey, + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const Icon( - Icons.bluetooth_searching, + EncabezadoFarolero( + icono: Icons.bluetooth_searching, + titulo: l10n.joinGameTitle, + subtitulo: l10n.enterNameToSearch, color: TemaApp.colorAzul, - size: 70, - ), - const SizedBox(height: 24), - Text( - l10n.joinGameTitle, - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 8), - Text( - l10n.enterNameToSearch, - style: Theme.of(context).textTheme.bodyLarge, - textAlign: TextAlign.center, ), const SizedBox(height: 32), TextFormField( @@ -481,13 +472,10 @@ class _PantallaUnirseState extends State { onFieldSubmitted: (_) => _iniciarBusqueda(), ), const SizedBox(height: 24), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - onPressed: _iniciarBusqueda, - icon: const Icon(Icons.search), - label: Text(l10n.searchGames), - ), + BotonFarolero( + texto: l10n.searchGames, + icono: Icons.search, + onPressed: _iniciarBusqueda, ), if (_error != null) ...[ const SizedBox(height: 16), @@ -525,63 +513,41 @@ class _PantallaUnirseState extends State { ), ), body: FondoFarolero( + intenso: true, child: Padding( padding: const EdgeInsets.all(24), child: Column( - children: [ - // Estado - if (_conectando) ...[ - const CircularProgressIndicator(color: TemaApp.colorAcento), - const SizedBox(height: 12), - Text( - '${l10n.connectingTo} ${_salaSeleccionada ?? ""}...', - style: Theme.of(context).textTheme.bodyLarge, + children: [ + EncabezadoFarolero( + icono: _conectando ? Icons.sync : Icons.radar, + titulo: _conectando + ? '${l10n.connectingTo} ${_salaSeleccionada ?? ""}...' + : l10n.searchingGames, + subtitulo: _conectando + ? 'Preparando la sala segura' + : 'Buscando partidas cercanas por Bluetooth', + color: _conectando ? TemaApp.colorAcento : TemaApp.colorNaranja, + trailing: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2.4, + color: _conectando + ? TemaApp.colorAcento + : TemaApp.colorNaranja, + ), ), - const SizedBox(height: 24), - ] else ...[ - // Buscando - Row( - children: [ - const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: TemaApp.colorNaranja, - ), - ), - const SizedBox(width: 12), - Text( - l10n.searchingGames, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), - const SizedBox(height: 24), - ], + ), + const SizedBox(height: 18), // Lista de hosts encontrados Expanded( child: hosts.isEmpty && !_conectando ? Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('📡', style: TextStyle(fontSize: 48)), - const SizedBox(height: 16), - Text( - l10n.noGamesFound, - style: Theme.of(context).textTheme.bodyLarge, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - l10n.noGamesFoundHint, - style: Theme.of(context).textTheme.bodyMedium - ?.copyWith(color: Colors.grey), - textAlign: TextAlign.center, - ), - ], + child: EstadoVacioFarolero( + icono: Icons.radar, + titulo: l10n.noGamesFound, + subtitulo: l10n.noGamesFoundHint, ), ) : ListView.builder( @@ -627,42 +593,50 @@ class _PantallaUnirseState extends State { Widget _buildHostTile(String endpointId, String nombre) { return Container( - margin: const EdgeInsets.only(bottom: 8), + margin: const EdgeInsets.only(bottom: 10), child: Material( - color: TemaApp.colorTarjeta, - borderRadius: BorderRadius.circular(12), + color: Colors.transparent, + borderRadius: BorderRadius.circular(18), child: InkWell( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(18), onTap: _conectando ? null : () => _conectarAHost(endpointId, nombre), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), - child: Row( - children: [ - const Text('🎭', style: TextStyle(fontSize: 28)), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - nombre, - style: Theme.of(context).textTheme.titleMedium, - ), - Text( - 'Toca para unirte', - style: Theme.of( - context, - ).textTheme.bodySmall?.copyWith(color: Colors.grey), - ), - ], + child: Ink( + decoration: TemaApp.decoracionPanel( + color: TemaApp.colorTarjeta.withValues(alpha: 0.90), + borderColor: TemaApp.colorNaranja.withValues(alpha: 0.42), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Row( + children: [ + const Icon( + Icons.theater_comedy, + color: TemaApp.colorNaranja, + size: 30, ), - ), - const Icon( - Icons.arrow_forward_ios, - size: 16, - color: Colors.grey, - ), - ], + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + nombre, + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + 'Toca para unirte', + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + ), + const Icon( + Icons.arrow_forward_ios, + size: 16, + color: TemaApp.colorDorado, + ), + ], + ), ), ), ), @@ -738,26 +712,26 @@ class _PantallaUnirseState extends State { ), ), body: FondoFarolero( + intenso: true, child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // Estado de conexión - const Text('✅', style: TextStyle(fontSize: 64)), - const SizedBox(height: 24), - Text( - l10n.connectedWaiting, - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + EncabezadoFarolero( + icono: Icons.check_circle, + titulo: l10n.connectedWaiting, + subtitulo: '${l10n.yourName}: ${_nombreController.text}', + color: TemaApp.colorVerde, + trailing: const SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + strokeWidth: 2.4, + color: TemaApp.colorNaranja, + ), + ), ), - const SizedBox(height: 12), - Text( - '${l10n.yourName}: ${_nombreController.text}', - style: Theme.of(context).textTheme.bodyLarge, - ), - const SizedBox(height: 32), - const CircularProgressIndicator(color: TemaApp.colorNaranja), const SizedBox(height: 16), Text( l10n.waitingForHost, diff --git a/lib/pantallas/pantalla_ver_palabra.dart b/lib/pantallas/pantalla_ver_palabra.dart index 8799e9c..e2eb6d2 100644 --- a/lib/pantallas/pantalla_ver_palabra.dart +++ b/lib/pantallas/pantalla_ver_palabra.dart @@ -33,21 +33,15 @@ class _PantallaVerPalabraState extends State { automaticallyImplyLeading: false, ), body: FondoFarolero( + intenso: true, child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ - Text( - l10n.eachPlayerMustSee, - style: Theme.of(context).textTheme.bodyMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - l10n.roundNumber(partida.rondaActual), - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: TemaApp.colorNaranja, - ), + EncabezadoFarolero( + icono: Icons.visibility, + titulo: l10n.roundNumber(partida.rondaActual), + subtitulo: l10n.eachPlayerMustSee, ), const SizedBox(height: 16), Expanded( diff --git a/lib/pantallas/pantalla_votacion.dart b/lib/pantallas/pantalla_votacion.dart index f986535..2ddd555 100644 --- a/lib/pantallas/pantalla_votacion.dart +++ b/lib/pantallas/pantalla_votacion.dart @@ -60,6 +60,7 @@ class _PantallaVotacionState extends State { automaticallyImplyLeading: false, ), body: FondoFarolero( + intenso: true, child: Padding( padding: const EdgeInsets.all(16), child: Column( @@ -68,9 +69,9 @@ class _PantallaVotacionState extends State { Container( width: double.infinity, padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: TemaApp.colorTarjeta, - borderRadius: BorderRadius.circular(12), + decoration: TemaApp.decoracionPanel( + color: TemaApp.colorTarjeta.withValues(alpha: 0.90), + borderColor: TemaApp.colorNaranja.withValues(alpha: 0.38), ), child: Column( children: [ @@ -105,9 +106,17 @@ class _PantallaVotacionState extends State { ), const SizedBox(height: 16), - Text( - l10n.whoIsImpostor, - style: Theme.of(context).textTheme.titleMedium, + EncabezadoFarolero( + icono: Icons.how_to_vote, + titulo: l10n.whoIsImpostor, + subtitulo: l10n.selectOnePlayer, + color: TemaApp.colorAcento, + trailing: Image.asset( + 'assets/ui/premium/vote_danger_glow.png', + width: 42, + height: 42, + opacity: const AlwaysStoppedAnimation(0.64), + ), ), const SizedBox(height: 12), @@ -178,6 +187,7 @@ class _PantallaVotacionState extends State { automaticallyImplyLeading: false, ), body: FondoFarolero( + intenso: true, child: Center( child: Padding( padding: const EdgeInsets.all(32), diff --git a/lib/pantallas/pantalla_votacion_cliente.dart b/lib/pantallas/pantalla_votacion_cliente.dart index 3808724..448d0ef 100644 --- a/lib/pantallas/pantalla_votacion_cliente.dart +++ b/lib/pantallas/pantalla_votacion_cliente.dart @@ -7,6 +7,7 @@ import 'package:farolero/pantallas/pantalla_notas_online.dart'; import 'package:farolero/pantallas/pantalla_revision_palabra.dart'; import 'package:farolero/pantallas/pantalla_resultado_online.dart'; import 'package:farolero/servicios/servicio_nearby.dart'; +import 'package:farolero/tema/componentes_farolero.dart'; import 'package:farolero/tema/tema_app.dart'; import 'package:provider/provider.dart'; @@ -126,21 +127,26 @@ class _PantallaVotacionClienteState extends State { ), ], ), - body: Padding( - padding: const EdgeInsets.all(16), - child: Column( + body: FondoFarolero( + intenso: true, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - l10n.whoDoYouThinkIsTheImpostor, - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 8), - Text( - _modoMultiVotante + EncabezadoFarolero( + icono: Icons.how_to_vote, + titulo: l10n.whoDoYouThinkIsTheImpostor, + subtitulo: _modoMultiVotante ? 'Emití un voto por cada jugador que manejás.' : l10n.selectOnePlayer, - style: TextStyle(color: TemaApp.colorTextoSecundario), + color: TemaApp.colorAcento, + trailing: Image.asset( + 'assets/ui/premium/vote_danger_glow.png', + width: 42, + height: 42, + opacity: const AlwaysStoppedAnimation(0.64), + ), ), const SizedBox(height: 16), Expanded( @@ -174,6 +180,7 @@ class _PantallaVotacionClienteState extends State { ], ), ), + ), ); } @@ -240,9 +247,11 @@ class _PantallaVotacionClienteState extends State { ), ], ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( + body: FondoFarolero( + intenso: true, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( @@ -339,6 +348,7 @@ class _PantallaVotacionClienteState extends State { ), ), ], + ), ), ), ); diff --git a/lib/tema/componentes_farolero.dart b/lib/tema/componentes_farolero.dart index 2fbbc4a..b144dd6 100644 --- a/lib/tema/componentes_farolero.dart +++ b/lib/tema/componentes_farolero.dart @@ -20,9 +20,36 @@ class FondoFarolero extends StatelessWidget { Widget build(BuildContext context) { return DecoratedBox( decoration: const BoxDecoration(gradient: TemaApp.gradienteFondo), - child: CustomPaint( - painter: _FondoFaroleroPainter(intenso: intenso), - child: child, + child: Stack( + children: [ + Positioned.fill( + child: CustomPaint(painter: _FondoFaroleroPainter(intenso: intenso)), + ), + Positioned( + top: intenso ? -180 : -140, + left: -220, + right: -220, + child: IgnorePointer( + child: Image.asset( + 'assets/ui/premium/lantern_radial_glow.png', + height: intenso ? 720 : 560, + fit: BoxFit.contain, + opacity: AlwaysStoppedAnimation(intenso ? 0.56 : 0.34), + ), + ), + ), + Positioned.fill( + child: IgnorePointer( + child: Image.asset( + 'assets/ui/premium/sparks_overlay.png', + fit: BoxFit.cover, + repeat: ImageRepeat.repeat, + opacity: AlwaysStoppedAnimation(intenso ? 0.38 : 0.22), + ), + ), + ), + Positioned.fill(child: child), + ], ), ); } @@ -49,9 +76,138 @@ class PanelFarolero extends StatelessWidget { return Container( width: double.infinity, margin: margin, - padding: padding, decoration: TemaApp.decoracionPanel(color: color, borderColor: borderColor), - child: child, + child: ClipRRect( + borderRadius: BorderRadius.circular(14), + child: Stack( + children: [ + Positioned.fill( + child: Image.asset( + 'assets/ui/premium/card_sheen_overlay.png', + fit: BoxFit.cover, + opacity: const AlwaysStoppedAnimation(0.26), + ), + ), + Padding(padding: padding, child: child), + ], + ), + ), + ); + } +} + +class EncabezadoFarolero extends StatelessWidget { + final IconData icono; + final String titulo; + final String? subtitulo; + final Color color; + final Widget? trailing; + final EdgeInsetsGeometry padding; + + const EncabezadoFarolero({ + super.key, + required this.icono, + required this.titulo, + this.subtitulo, + this.color = TemaApp.colorNaranja, + this.trailing, + this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 18), + }); + + @override + Widget build(BuildContext context) { + return PanelFarolero( + padding: padding, + child: Row( + children: [ + Container( + width: 52, + height: 52, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + color.withValues(alpha: 0.34), + TemaApp.colorSuperficie.withValues(alpha: 0.72), + ], + ), + border: Border.all(color: color.withValues(alpha: 0.72)), + boxShadow: [ + BoxShadow( + color: color.withValues(alpha: 0.22), + blurRadius: 22, + ), + ], + ), + child: Icon(icono, color: color, size: 30), + ), + const SizedBox(width: 14), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + titulo, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: TemaApp.colorDorado, + fontWeight: FontWeight.w900, + ), + ), + if (subtitulo != null) ...[ + const SizedBox(height: 3), + Text( + subtitulo!, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ], + ), + ), + if (trailing != null) ...[ + const SizedBox(width: 12), + trailing!, + ], + ], + ), + ); + } +} + +class EstadoVacioFarolero extends StatelessWidget { + final IconData icono; + final String titulo; + final String subtitulo; + + const EstadoVacioFarolero({ + super.key, + required this.icono, + required this.titulo, + required this.subtitulo, + }); + + @override + Widget build(BuildContext context) { + return PanelFarolero( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 28), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icono, color: TemaApp.colorNaranja, size: 46), + const SizedBox(height: 14), + Text( + titulo, + style: Theme.of(context).textTheme.titleLarge, + textAlign: TextAlign.center, + ), + const SizedBox(height: 8), + Text( + subtitulo, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center, + ), + ], + ), ); } } @@ -138,17 +294,17 @@ class BotonFarolero extends StatelessWidget { return Material( color: Colors.transparent, child: InkWell( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(18), onTap: onPressed, child: Ink( - height: 54, + height: 58, decoration: BoxDecoration( gradient: habilitado ? gradient : const LinearGradient( colors: [TemaApp.colorTarjeta, TemaApp.colorSuperficie], ), - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(18), border: Border.all( color: habilitado ? TemaApp.colorDorado.withValues(alpha: 0.74) @@ -157,29 +313,46 @@ class BotonFarolero extends StatelessWidget { boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.34), - blurRadius: 14, - offset: const Offset(0, 8), + blurRadius: 18, + offset: const Offset(0, 10), ), + if (habilitado) + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.16), + blurRadius: 22, + ), ], ), - child: Row( + child: Stack( children: [ - SizedBox( - width: 58, - child: Icon(icono, color: foreground, size: 28), - ), - Expanded( - child: Text( - texto.toUpperCase(), - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: foreground, - fontSize: 18, - fontWeight: FontWeight.w800, - ), + Positioned.fill( + child: Image.asset( + 'assets/ui/premium/card_sheen_overlay.png', + fit: BoxFit.cover, + opacity: const AlwaysStoppedAnimation(0.18), ), ), - const SizedBox(width: 58), + Row( + children: [ + SizedBox( + width: 58, + child: Icon(icono, color: foreground, size: 28), + ), + Expanded( + child: Text( + texto.toUpperCase(), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: foreground, + fontSize: 18, + fontWeight: FontWeight.w900, + letterSpacing: 0.8, + ), + ), + ), + const SizedBox(width: 58), + ], + ), ], ), ), @@ -254,15 +427,27 @@ class TarjetaPalabraFarolero extends StatelessWidget { ), ], ), - child: Text( - palabra.toUpperCase(), - textAlign: TextAlign.center, - style: GoogleFonts.oswald( - color: const Color(0xFF1B0C05), - fontSize: 42, - fontWeight: FontWeight.w900, - letterSpacing: 0, - ), + child: Stack( + alignment: Alignment.center, + children: [ + Positioned.fill( + child: Image.asset( + 'assets/ui/premium/word_reveal_glow.png', + fit: BoxFit.cover, + opacity: const AlwaysStoppedAnimation(0.28), + ), + ), + Text( + palabra.toUpperCase(), + textAlign: TextAlign.center, + style: GoogleFonts.oswald( + color: const Color(0xFF1B0C05), + fontSize: 42, + fontWeight: FontWeight.w900, + letterSpacing: 0, + ), + ), + ], ), ); } diff --git a/lib/tema/tema_app.dart b/lib/tema/tema_app.dart index db682a6..69ecd5f 100644 --- a/lib/tema/tema_app.dart +++ b/lib/tema/tema_app.dart @@ -89,8 +89,8 @@ class TemaApp { elevation: 0, margin: EdgeInsets.zero, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide(color: colorBorde), + borderRadius: BorderRadius.circular(18), + side: BorderSide(color: colorDorado.withValues(alpha: 0.34)), ), ), elevatedButtonTheme: ElevatedButtonThemeData( @@ -99,12 +99,13 @@ class TemaApp { foregroundColor: Colors.black, disabledBackgroundColor: colorTarjeta, disabledForegroundColor: colorTextoSecundario, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 15), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + elevation: 0, + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), textStyle: GoogleFonts.oswald( fontWeight: FontWeight.w700, - fontSize: 16, - letterSpacing: 0, + fontSize: 17, + letterSpacing: 0.6, ), ), ), @@ -112,8 +113,8 @@ class TemaApp { style: OutlinedButton.styleFrom( foregroundColor: colorTexto, side: const BorderSide(color: colorBorde), - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 15), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)), textStyle: GoogleFonts.oswald( fontWeight: FontWeight.w700, fontSize: 16, @@ -125,22 +126,22 @@ class TemaApp { filled: true, fillColor: const Color(0xFF0B1117), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: colorBorde), ), enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: colorBorde), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: colorNaranja), ), labelStyle: const TextStyle(color: colorTextoSecundario), hintStyle: const TextStyle(color: colorTextoSecundario), ), appBarTheme: AppBarTheme( - backgroundColor: colorFondo, + backgroundColor: Colors.transparent, foregroundColor: colorNaranja, centerTitle: true, elevation: 0, @@ -199,13 +200,19 @@ class TemaApp { }) { return BoxDecoration( color: color ?? colorTarjeta.withValues(alpha: 0.84), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: borderColor ?? colorBorde), + borderRadius: BorderRadius.circular(18), + border: Border.all( + color: borderColor ?? colorDorado.withValues(alpha: 0.30), + ), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.35), - blurRadius: 18, - offset: const Offset(0, 10), + blurRadius: 22, + offset: const Offset(0, 12), + ), + BoxShadow( + color: colorNaranja.withValues(alpha: 0.10), + blurRadius: 24, ), ], ); diff --git a/pubspec.yaml b/pubspec.yaml index 30498fe..2ab7898 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,3 +38,4 @@ flutter: - assets/avatars/ - assets/medals/ - assets/rewards/ + - assets/ui/premium/