Cambios visuales completos

This commit is contained in:
2026-05-10 17:28:35 +02:00
parent 8b4ca132aa
commit 42f01949c4
33 changed files with 887 additions and 397 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -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.

View File

@@ -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.

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:farolero/l10n/generated/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../estado/estado_juego.dart'; import '../estado/estado_juego.dart';
import '../tema/componentes_farolero.dart';
import '../tema/tema_app.dart'; import '../tema/tema_app.dart';
import 'pantalla_debate.dart'; import 'pantalla_debate.dart';
import 'pantalla_fin_partida.dart'; import 'pantalla_fin_partida.dart';
@@ -41,24 +42,24 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
title: Text(l10n.impostorGuessTitle), title: Text(l10n.impostorGuessTitle),
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: Center( body: FondoFarolero(
child: SingleChildScrollView( intenso: true,
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(32),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text('🎭', style: TextStyle(fontSize: 64)), EncabezadoFarolero(
const SizedBox(height: 16), icono: Icons.theater_comedy,
Text( titulo: l10n.impostorCanGuess,
l10n.impostorCanGuess, subtitulo: l10n.ifCorrectImpostorsWin,
style: Theme.of(context).textTheme.titleLarge, color: TemaApp.colorAcento,
textAlign: TextAlign.center, trailing: Image.asset(
), 'assets/ui/premium/vote_danger_glow.png',
const SizedBox(height: 8), width: 42,
Text( height: 42,
l10n.ifCorrectImpostorsWin, opacity: const AlwaysStoppedAnimation(0.64),
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: TemaApp.colorNaranja,
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@@ -231,6 +232,7 @@ class _PantallaAdivinanzaState extends State<PantallaAdivinanza> {
], ],
], ],
), ),
),
), ),
), ),
); );

View File

@@ -23,6 +23,7 @@ class _PantallaAjustesState extends State<PantallaAjustes> {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(l10n.settingsTitle)), appBar: AppBar(title: Text(l10n.settingsTitle)),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(

View File

@@ -317,42 +317,23 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(l10n.createGame)), appBar: AppBar(title: Text(l10n.createGame)),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
PanelFarolero( EncabezadoFarolero(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 18), icono: Icons.groups,
child: Row( titulo: '¿Cómo quieres jugar?',
children: [ subtitulo: l10n.playersRange,
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,
),
],
),
),
],
),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
if (!widget.bloquearModo) ...[ if (!widget.bloquearModo) ...[
// Modo de juego // Modo de juego
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -416,7 +397,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
// Categoría // Categoría
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -453,7 +434,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
// Jugadores // Jugadores
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -542,7 +523,7 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
// Configuración de partida // Configuración de partida
Card( Card(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.fromLTRB(18, 18, 18, 28),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -614,22 +595,12 @@ class _PantallaCrearPartidaState extends State<PantallaCrearPartida> {
const SizedBox(height: 24), const SizedBox(height: 24),
// Botón iniciar // Botón iniciar
SizedBox( BotonFarolero(
width: double.infinity, texto: l10n.startGame,
height: 56, icono: Icons.play_arrow,
child: ElevatedButton.icon( onPressed: (_modoMultimovil || _jugadores.length >= 3)
onPressed: (_modoMultimovil || _jugadores.length >= 3) ? _iniciarPartida
? _iniciarPartida : null,
: null,
icon: const Icon(Icons.play_arrow),
label: Text(l10n.startGame),
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
], ],

View File

@@ -77,6 +77,7 @@ class _PantallaDebateState extends State<PantallaDebate> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@@ -95,40 +96,56 @@ class _PantallaDebateState extends State<PantallaDebate> {
? Border.all(color: TemaApp.colorAcento, width: 2) ? Border.all(color: TemaApp.colorAcento, width: 2)
: null, : null,
), ),
child: Column( child: Stack(
alignment: Alignment.center,
children: [ children: [
Text( Positioned.fill(
_tiempoAgotado ? l10n.timeUp : l10n.timeRemaining, child: Image.asset(
style: Theme.of(context).textTheme.titleMedium?.copyWith( 'assets/ui/premium/timer_ring_glow.png',
color: _tiempoAgotado fit: BoxFit.contain,
? TemaApp.colorAcento opacity: const AlwaysStoppedAnimation(0.36),
: TemaApp.colorTextoSecundario,
), ),
), ),
const SizedBox(height: 8), Column(
Text( mainAxisSize: MainAxisSize.min,
_formatearTiempo(_segundosRestantes), children: [
style: Theme.of(context).textTheme.headlineLarge?.copyWith( Text(
fontSize: 48, _tiempoAgotado ? l10n.timeUp : l10n.timeRemaining,
fontWeight: FontWeight.bold, style:
color: _segundosRestantes < 10 && !_tiempoAgotado Theme.of(context).textTheme.titleMedium?.copyWith(
? TemaApp.colorAcento color: _tiempoAgotado
: TemaApp.colorTexto, ? TemaApp.colorAcento
), : TemaApp.colorTextoSecundario,
), ),
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, 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,
),
),
],
), ),
], ],
), ),

View File

@@ -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_revision_palabra.dart';
import 'package:farolero/pantallas/pantalla_votacion_cliente.dart'; import 'package:farolero/pantallas/pantalla_votacion_cliente.dart';
import 'package:farolero/servicios/servicio_nearby.dart'; import 'package:farolero/servicios/servicio_nearby.dart';
import 'package:farolero/tema/componentes_farolero.dart';
import 'package:farolero/tema/tema_app.dart'; import 'package:farolero/tema/tema_app.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -157,9 +158,11 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
), ),
], ],
), ),
body: Padding( body: FondoFarolero(
padding: const EdgeInsets.all(24), intenso: true,
child: Column( child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [ children: [
const Spacer(), const Spacer(),
@@ -173,23 +176,37 @@ class _PantallaDebateClienteState extends State<PantallaDebateCliente> {
: TemaApp.colorTarjeta, : TemaApp.colorTarjeta,
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
), ),
child: Column( child: Stack(
alignment: Alignment.center,
children: [ children: [
Text( Positioned.fill(
_segundosRestantes == 0 child: Image.asset(
? l10n.timeUp 'assets/ui/premium/timer_ring_glow.png',
: l10n.timeRemaining, fit: BoxFit.contain,
style: Theme.of(context).textTheme.titleMedium, opacity: const AlwaysStoppedAnimation(0.42),
),
), ),
const SizedBox(height: 8), Column(
Text( mainAxisSize: MainAxisSize.min,
_formatearTiempo(_segundosRestantes), children: [
style: Theme.of(context).textTheme.displayMedium?.copyWith( Text(
fontWeight: FontWeight.bold, _segundosRestantes == 0
color: _segundosRestantes == 0 ? l10n.timeUp
? TemaApp.colorAcento : l10n.timeRemaining,
: TemaApp.colorTexto, 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<PantallaDebateCliente> {
), ),
), ),
], ],
),
), ),
), ),
); );

View File

@@ -96,9 +96,11 @@ class _PantallaFinPartidaOnlineState extends State<PantallaFinPartidaOnline> {
), ),
], ],
), ),
body: SingleChildScrollView( body: FondoFarolero(
padding: const EdgeInsets.all(24), intenso: true,
child: Column( child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
children: [ children: [
Container( Container(
width: double.infinity, width: double.infinity,
@@ -266,6 +268,7 @@ class _PantallaFinPartidaOnlineState extends State<PantallaFinPartidaOnline> {
), ),
), ),
], ],
),
), ),
), ),
); );

View File

@@ -201,6 +201,7 @@ class _PantallaGestorHostState extends State<PantallaGestorHost> {
], ],
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(

View File

@@ -16,6 +16,7 @@ class PantallaHistorial extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Historial')), appBar: AppBar(title: const Text('Historial')),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: partidas.isEmpty child: partidas.isEmpty
? const Center(child: Text('Todavía no hay partidas guardadas.')) ? const Center(child: Text('Todavía no hay partidas guardadas.'))
: ListView.builder( : ListView.builder(

View File

@@ -48,25 +48,66 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
), ),
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
children: [ children: [
Container( EncabezadoFarolero(
icono: Icons.wifi_tethering,
titulo: widget.nombreSala,
subtitulo: l10n.scanToJoin,
),
const SizedBox(height: 14),
PanelFarolero(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( child: Column(
color: Colors.white, children: [
borderRadius: BorderRadius.circular(16), SizedBox(
), width: 196,
child: QrImageView( height: 196,
data: nearby.generarDatosQR(widget.nombreSala), child: Stack(
version: QrVersions.auto, alignment: Alignment.center,
size: 160, children: [
backgroundColor: Colors.white, 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), const SizedBox(height: 16),
_buildResumenSala(context, seleccionados, nearby.jugadores.length), _buildResumenSala(context, seleccionados, nearby.jugadores.length),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -119,15 +160,15 @@ class _PantallaLobbyHostState extends State<PantallaLobbyHost> {
const SizedBox(height: 12), const SizedBox(height: 12),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton.icon( child: BotonFarolero(
texto: _iniciando ? l10n.starting : l10n.startGame,
icono: Icons.play_arrow,
onPressed: puedeIniciar && !_iniciando onPressed: puedeIniciar && !_iniciando
? () { ? () {
setState(() => _iniciando = true); setState(() => _iniciando = true);
widget.onIniciar(); widget.onIniciar();
} }
: null, : null,
icon: const Icon(Icons.play_arrow),
label: Text(_iniciando ? l10n.starting : l10n.startGame),
), ),
), ),
], ],

View File

@@ -3,6 +3,7 @@ import 'package:farolero/l10n/generated/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../estado/estado_juego.dart'; import '../estado/estado_juego.dart';
import '../servicios/servicio_notas.dart'; import '../servicios/servicio_notas.dart';
import '../tema/componentes_farolero.dart';
import '../tema/tema_app.dart'; import '../tema/tema_app.dart';
class PantallaNotas extends StatefulWidget { class PantallaNotas extends StatefulWidget {
@@ -86,9 +87,12 @@ class _PantallaNotasState extends State<PantallaNotas> {
), ),
], ],
), ),
body: _jugadorSeleccionadoId == null body: FondoFarolero(
? _construirSelectorJugador(partida) intenso: true,
: _construirNotas(partida), child: _jugadorSeleccionadoId == null
? _construirSelectorJugador(partida)
: _construirNotas(partida),
),
); );
} }

View File

@@ -3,6 +3,7 @@ import 'package:farolero/l10n/generated/app_localizations.dart';
import 'package:farolero/modelos/inicio_partida_multijugador.dart'; import 'package:farolero/modelos/inicio_partida_multijugador.dart';
import 'package:farolero/modelos/jugador.dart'; import 'package:farolero/modelos/jugador.dart';
import 'package:farolero/servicios/servicio_notas.dart'; import 'package:farolero/servicios/servicio_notas.dart';
import 'package:farolero/tema/componentes_farolero.dart';
import 'package:farolero/tema/tema_app.dart'; import 'package:farolero/tema/tema_app.dart';
class PantallaNotasOnline extends StatefulWidget { class PantallaNotasOnline extends StatefulWidget {
@@ -115,7 +116,10 @@ class _PantallaNotasOnlineState extends State<PantallaNotasOnline> {
), ),
], ],
), ),
body: _autor == null ? _buildSelector(context) : _buildNotas(context), body: FondoFarolero(
intenso: true,
child: _autor == null ? _buildSelector(context) : _buildNotas(context),
),
), ),
); );
} }

View File

@@ -25,6 +25,7 @@ class PantallaPrincipal extends StatelessWidget {
final gamificacion = servicioPerfil.resumenGamificacion; final gamificacion = servicioPerfil.resumenGamificacion;
return Scaffold( return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: const Color(0xFF05070D), backgroundColor: const Color(0xFF05070D),
body: FondoFarolero( body: FondoFarolero(
intenso: true, intenso: true,

View File

@@ -13,6 +13,7 @@ class PantallaReglas extends StatelessWidget {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(l10n.rulesTitle)), appBar: AppBar(title: Text(l10n.rulesTitle)),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(

View File

@@ -64,6 +64,7 @@ class _PantallaResultadoState extends State<PantallaResultado>
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(32),

View File

@@ -4,6 +4,7 @@ import '../modelos/inicio_partida_multijugador.dart';
import '../modelos/partida.dart'; import '../modelos/partida.dart';
import '../modelos/snapshot_partida_online.dart'; import '../modelos/snapshot_partida_online.dart';
import '../servicios/servicio_nearby.dart'; import '../servicios/servicio_nearby.dart';
import '../tema/componentes_farolero.dart';
import '../tema/tema_app.dart'; import '../tema/tema_app.dart';
import 'pantalla_debate_cliente.dart'; import 'pantalla_debate_cliente.dart';
import 'pantalla_fin_partida_online.dart'; import 'pantalla_fin_partida_online.dart';
@@ -168,11 +169,14 @@ class _PantallaResultadoOnlineState extends State<PantallaResultadoOnline> {
elevation: 0, elevation: 0,
actions: _acciones(context, l10n), actions: _acciones(context, l10n),
), ),
body: Padding( body: FondoFarolero(
padding: const EdgeInsets.all(24), intenso: true,
child: resultado == null child: Padding(
? _buildEsperaAdivinanza(context, l10n) padding: const EdgeInsets.all(24),
: _buildResultado(context, l10n, resultado), child: resultado == null
? _buildEsperaAdivinanza(context, l10n)
: _buildResultado(context, l10n, resultado),
),
), ),
); );
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import '../tema/componentes_farolero.dart'; import '../tema/componentes_farolero.dart';
import '../tema/tema_app.dart'; import '../tema/tema_app.dart';
@@ -10,40 +11,27 @@ class PantallaSeleccionModoJuego extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Elegir modo de juego')), extendBodyBehindAppBar: true,
appBar: AppBar(title: const Text('Elegir modo')),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: SafeArea( child: SafeArea(
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.fromLTRB(20, 24, 20, 28),
child: ConstrainedBox( child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 460), constraints: const BoxConstraints(maxWidth: 470),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const Icon( const SizedBox(height: 12),
Icons.sports_esports, const _ModoHero().animate().fadeIn(duration: 320.ms).slideY(begin: -0.12),
size: 64, const SizedBox(height: 34),
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),
_ModoCard( _ModoCard(
icono: Icons.phone_android, icono: Icons.phone_android_rounded,
titulo: 'Partida en este dispositivo', titulo: 'Un móvil',
descripcion: subtitulo: 'Partida en este dispositivo',
'Todos los jugadores usan este móvil. Acá se agregan los nombres manualmente.', descripcion: 'Ideal para jugar todos juntos pasando el móvil. Configuración rápida y directa.',
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@@ -53,13 +41,13 @@ class PantallaSeleccionModoJuego extends StatelessWidget {
), ),
), ),
), ),
), ).animate().fadeIn(delay: 120.ms).slideX(begin: -0.08),
const SizedBox(height: 14), const SizedBox(height: 16),
_ModoCard( _ModoCard(
icono: Icons.devices, icono: Icons.devices_rounded,
titulo: 'Partida multidispositivo', titulo: 'Multidispositivo',
descripcion: subtitulo: 'Cada jugador en su móvil',
'Este móvil crea el servidor. Los usuarios se gestionan después en el lobby.', descripcion: 'Crea una sala premium, comparte QR y gestiona usuarios desde el lobby.',
destacado: true, destacado: true,
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, 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 { class _ModoCard extends StatelessWidget {
final IconData icono; final IconData icono;
final String titulo; final String titulo;
final String subtitulo;
final String descripcion; final String descripcion;
final bool destacado; final bool destacado;
final VoidCallback onTap; final VoidCallback onTap;
@@ -92,6 +153,7 @@ class _ModoCard extends StatelessWidget {
const _ModoCard({ const _ModoCard({
required this.icono, required this.icono,
required this.titulo, required this.titulo,
required this.subtitulo,
required this.descripcion, required this.descripcion,
required this.onTap, required this.onTap,
this.destacado = false, this.destacado = false,
@@ -100,45 +162,86 @@ class _ModoCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final color = destacado ? TemaApp.colorNaranja : TemaApp.colorAcento; final color = destacado ? TemaApp.colorNaranja : TemaApp.colorAcento;
return Card( return Material(
color: TemaApp.colorTarjeta, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(28),
onTap: onTap, onTap: onTap,
child: Padding( child: Ink(
padding: const EdgeInsets.all(18), decoration: BoxDecoration(
child: Row( gradient: LinearGradient(
children: [ colors: [
Container( const Color(0xFF111C29).withValues(alpha: 0.94),
width: 52, (destacado ? const Color(0xFF2A1620) : const Color(0xFF15111F)).withValues(alpha: 0.92),
height: 52, ],
decoration: BoxDecoration( begin: Alignment.topLeft,
color: color.withValues(alpha: 0.18), end: Alignment.bottomRight,
borderRadius: BorderRadius.circular(16), ),
border: Border.all(color: color.withValues(alpha: 0.7)), borderRadius: BorderRadius.circular(28),
), border: Border.all(color: color.withValues(alpha: destacado ? 0.78 : 0.48)),
child: Icon(icono, color: color, size: 30), 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( child: Stack(
crossAxisAlignment: CrossAxisAlignment.start, 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: [ children: [
Text(titulo, style: Theme.of(context).textTheme.titleLarge), Container(
const SizedBox(height: 6), width: 64,
Text( height: 64,
descripcion, decoration: BoxDecoration(
style: Theme.of(context).textTheme.bodyMedium, 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),
], ],
), ),
), ),
), ),
); );
} }
} }

View File

@@ -443,28 +443,19 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(l10n.joinGameTitle)), appBar: AppBar(title: Text(l10n.joinGameTitle)),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Padding( child: Padding(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(32),
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Icon( EncabezadoFarolero(
Icons.bluetooth_searching, icono: Icons.bluetooth_searching,
titulo: l10n.joinGameTitle,
subtitulo: l10n.enterNameToSearch,
color: TemaApp.colorAzul, 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), const SizedBox(height: 32),
TextFormField( TextFormField(
@@ -481,13 +472,10 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
onFieldSubmitted: (_) => _iniciarBusqueda(), onFieldSubmitted: (_) => _iniciarBusqueda(),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
SizedBox( BotonFarolero(
width: double.infinity, texto: l10n.searchGames,
child: ElevatedButton.icon( icono: Icons.search,
onPressed: _iniciarBusqueda, onPressed: _iniciarBusqueda,
icon: const Icon(Icons.search),
label: Text(l10n.searchGames),
),
), ),
if (_error != null) ...[ if (_error != null) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -525,63 +513,41 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
), ),
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
children: [ children: [
// Estado EncabezadoFarolero(
if (_conectando) ...[ icono: _conectando ? Icons.sync : Icons.radar,
const CircularProgressIndicator(color: TemaApp.colorAcento), titulo: _conectando
const SizedBox(height: 12), ? '${l10n.connectingTo} ${_salaSeleccionada ?? ""}...'
Text( : l10n.searchingGames,
'${l10n.connectingTo} ${_salaSeleccionada ?? ""}...', subtitulo: _conectando
style: Theme.of(context).textTheme.bodyLarge, ? '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 ...[ const SizedBox(height: 18),
// 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),
],
// Lista de hosts encontrados // Lista de hosts encontrados
Expanded( Expanded(
child: hosts.isEmpty && !_conectando child: hosts.isEmpty && !_conectando
? Center( ? Center(
child: Column( child: EstadoVacioFarolero(
mainAxisSize: MainAxisSize.min, icono: Icons.radar,
children: [ titulo: l10n.noGamesFound,
const Text('📡', style: TextStyle(fontSize: 48)), subtitulo: l10n.noGamesFoundHint,
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,
),
],
), ),
) )
: ListView.builder( : ListView.builder(
@@ -627,42 +593,50 @@ class _PantallaUnirseState extends State<PantallaUnirse> {
Widget _buildHostTile(String endpointId, String nombre) { Widget _buildHostTile(String endpointId, String nombre) {
return Container( return Container(
margin: const EdgeInsets.only(bottom: 8), margin: const EdgeInsets.only(bottom: 10),
child: Material( child: Material(
color: TemaApp.colorTarjeta, color: Colors.transparent,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(18),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(18),
onTap: _conectando ? null : () => _conectarAHost(endpointId, nombre), onTap: _conectando ? null : () => _conectarAHost(endpointId, nombre),
child: Padding( child: Ink(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), decoration: TemaApp.decoracionPanel(
child: Row( color: TemaApp.colorTarjeta.withValues(alpha: 0.90),
children: [ borderColor: TemaApp.colorNaranja.withValues(alpha: 0.42),
const Text('🎭', style: TextStyle(fontSize: 28)), ),
const SizedBox(width: 16), child: Padding(
Expanded( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ const Icon(
Text( Icons.theater_comedy,
nombre, color: TemaApp.colorNaranja,
style: Theme.of(context).textTheme.titleMedium, size: 30,
),
Text(
'Toca para unirte',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.grey),
),
],
), ),
), const SizedBox(width: 16),
const Icon( Expanded(
Icons.arrow_forward_ios, child: Column(
size: 16, crossAxisAlignment: CrossAxisAlignment.start,
color: Colors.grey, 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<PantallaUnirse> {
), ),
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
// Estado de conexión EncabezadoFarolero(
const Text('', style: TextStyle(fontSize: 64)), icono: Icons.check_circle,
const SizedBox(height: 24), titulo: l10n.connectedWaiting,
Text( subtitulo: '${l10n.yourName}: ${_nombreController.text}',
l10n.connectedWaiting, color: TemaApp.colorVerde,
style: Theme.of(context).textTheme.headlineMedium, trailing: const SizedBox(
textAlign: TextAlign.center, 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), const SizedBox(height: 16),
Text( Text(
l10n.waitingForHost, l10n.waitingForHost,

View File

@@ -33,21 +33,15 @@ class _PantallaVerPalabraState extends State<PantallaVerPalabra> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
children: [ children: [
Text( EncabezadoFarolero(
l10n.eachPlayerMustSee, icono: Icons.visibility,
style: Theme.of(context).textTheme.bodyMedium, titulo: l10n.roundNumber(partida.rondaActual),
textAlign: TextAlign.center, subtitulo: l10n.eachPlayerMustSee,
),
const SizedBox(height: 8),
Text(
l10n.roundNumber(partida.rondaActual),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: TemaApp.colorNaranja,
),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Expanded( Expanded(

View File

@@ -60,6 +60,7 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@@ -68,9 +69,9 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
Container( Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: TemaApp.decoracionPanel(
color: TemaApp.colorTarjeta, color: TemaApp.colorTarjeta.withValues(alpha: 0.90),
borderRadius: BorderRadius.circular(12), borderColor: TemaApp.colorNaranja.withValues(alpha: 0.38),
), ),
child: Column( child: Column(
children: [ children: [
@@ -105,9 +106,17 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( EncabezadoFarolero(
l10n.whoIsImpostor, icono: Icons.how_to_vote,
style: Theme.of(context).textTheme.titleMedium, 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), const SizedBox(height: 12),
@@ -178,6 +187,7 @@ class _PantallaVotacionState extends State<PantallaVotacion> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
), ),
body: FondoFarolero( body: FondoFarolero(
intenso: true,
child: Center( child: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(32), padding: const EdgeInsets.all(32),

View File

@@ -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_revision_palabra.dart';
import 'package:farolero/pantallas/pantalla_resultado_online.dart'; import 'package:farolero/pantallas/pantalla_resultado_online.dart';
import 'package:farolero/servicios/servicio_nearby.dart'; import 'package:farolero/servicios/servicio_nearby.dart';
import 'package:farolero/tema/componentes_farolero.dart';
import 'package:farolero/tema/tema_app.dart'; import 'package:farolero/tema/tema_app.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -126,21 +127,26 @@ class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
), ),
], ],
), ),
body: Padding( body: FondoFarolero(
padding: const EdgeInsets.all(16), intenso: true,
child: Column( child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( EncabezadoFarolero(
l10n.whoDoYouThinkIsTheImpostor, icono: Icons.how_to_vote,
style: Theme.of(context).textTheme.titleLarge, titulo: l10n.whoDoYouThinkIsTheImpostor,
), subtitulo: _modoMultiVotante
const SizedBox(height: 8),
Text(
_modoMultiVotante
? 'Emití un voto por cada jugador que manejás.' ? 'Emití un voto por cada jugador que manejás.'
: l10n.selectOnePlayer, : 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), const SizedBox(height: 16),
Expanded( Expanded(
@@ -174,6 +180,7 @@ class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
], ],
), ),
), ),
),
); );
} }
@@ -240,9 +247,11 @@ class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
), ),
], ],
), ),
body: Padding( body: FondoFarolero(
padding: const EdgeInsets.all(24), intenso: true,
child: Column( child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
@@ -339,6 +348,7 @@ class _PantallaVotacionClienteState extends State<PantallaVotacionCliente> {
), ),
), ),
], ],
),
), ),
), ),
); );

View File

@@ -20,9 +20,36 @@ class FondoFarolero extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DecoratedBox( return DecoratedBox(
decoration: const BoxDecoration(gradient: TemaApp.gradienteFondo), decoration: const BoxDecoration(gradient: TemaApp.gradienteFondo),
child: CustomPaint( child: Stack(
painter: _FondoFaroleroPainter(intenso: intenso), children: [
child: child, 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( return Container(
width: double.infinity, width: double.infinity,
margin: margin, margin: margin,
padding: padding,
decoration: TemaApp.decoracionPanel(color: color, borderColor: borderColor), 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( return Material(
color: Colors.transparent, color: Colors.transparent,
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(18),
onTap: onPressed, onTap: onPressed,
child: Ink( child: Ink(
height: 54, height: 58,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: habilitado gradient: habilitado
? gradient ? gradient
: const LinearGradient( : const LinearGradient(
colors: [TemaApp.colorTarjeta, TemaApp.colorSuperficie], colors: [TemaApp.colorTarjeta, TemaApp.colorSuperficie],
), ),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(18),
border: Border.all( border: Border.all(
color: habilitado color: habilitado
? TemaApp.colorDorado.withValues(alpha: 0.74) ? TemaApp.colorDorado.withValues(alpha: 0.74)
@@ -157,29 +313,46 @@ class BotonFarolero extends StatelessWidget {
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.34), color: Colors.black.withValues(alpha: 0.34),
blurRadius: 14, blurRadius: 18,
offset: const Offset(0, 8), offset: const Offset(0, 10),
), ),
if (habilitado)
BoxShadow(
color: TemaApp.colorNaranja.withValues(alpha: 0.16),
blurRadius: 22,
),
], ],
), ),
child: Row( child: Stack(
children: [ children: [
SizedBox( Positioned.fill(
width: 58, child: Image.asset(
child: Icon(icono, color: foreground, size: 28), 'assets/ui/premium/card_sheen_overlay.png',
), fit: BoxFit.cover,
Expanded( opacity: const AlwaysStoppedAnimation(0.18),
child: Text(
texto.toUpperCase(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: foreground,
fontSize: 18,
fontWeight: FontWeight.w800,
),
), ),
), ),
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( child: Stack(
palabra.toUpperCase(), alignment: Alignment.center,
textAlign: TextAlign.center, children: [
style: GoogleFonts.oswald( Positioned.fill(
color: const Color(0xFF1B0C05), child: Image.asset(
fontSize: 42, 'assets/ui/premium/word_reveal_glow.png',
fontWeight: FontWeight.w900, fit: BoxFit.cover,
letterSpacing: 0, 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,
),
),
],
), ),
); );
} }

View File

@@ -89,8 +89,8 @@ class TemaApp {
elevation: 0, elevation: 0,
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(18),
side: const BorderSide(color: colorBorde), side: BorderSide(color: colorDorado.withValues(alpha: 0.34)),
), ),
), ),
elevatedButtonTheme: ElevatedButtonThemeData( elevatedButtonTheme: ElevatedButtonThemeData(
@@ -99,12 +99,13 @@ class TemaApp {
foregroundColor: Colors.black, foregroundColor: Colors.black,
disabledBackgroundColor: colorTarjeta, disabledBackgroundColor: colorTarjeta,
disabledForegroundColor: colorTextoSecundario, disabledForegroundColor: colorTextoSecundario,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 15), elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
textStyle: GoogleFonts.oswald( textStyle: GoogleFonts.oswald(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 16, fontSize: 17,
letterSpacing: 0, letterSpacing: 0.6,
), ),
), ),
), ),
@@ -112,8 +113,8 @@ class TemaApp {
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: colorTexto, foregroundColor: colorTexto,
side: const BorderSide(color: colorBorde), side: const BorderSide(color: colorBorde),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 15), padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
textStyle: GoogleFonts.oswald( textStyle: GoogleFonts.oswald(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 16, fontSize: 16,
@@ -125,22 +126,22 @@ class TemaApp {
filled: true, filled: true,
fillColor: const Color(0xFF0B1117), fillColor: const Color(0xFF0B1117),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: colorBorde), borderSide: const BorderSide(color: colorBorde),
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: colorBorde), borderSide: const BorderSide(color: colorBorde),
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(16),
borderSide: const BorderSide(color: colorNaranja), borderSide: const BorderSide(color: colorNaranja),
), ),
labelStyle: const TextStyle(color: colorTextoSecundario), labelStyle: const TextStyle(color: colorTextoSecundario),
hintStyle: const TextStyle(color: colorTextoSecundario), hintStyle: const TextStyle(color: colorTextoSecundario),
), ),
appBarTheme: AppBarTheme( appBarTheme: AppBarTheme(
backgroundColor: colorFondo, backgroundColor: Colors.transparent,
foregroundColor: colorNaranja, foregroundColor: colorNaranja,
centerTitle: true, centerTitle: true,
elevation: 0, elevation: 0,
@@ -199,13 +200,19 @@ class TemaApp {
}) { }) {
return BoxDecoration( return BoxDecoration(
color: color ?? colorTarjeta.withValues(alpha: 0.84), color: color ?? colorTarjeta.withValues(alpha: 0.84),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(18),
border: Border.all(color: borderColor ?? colorBorde), border: Border.all(
color: borderColor ?? colorDorado.withValues(alpha: 0.30),
),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withValues(alpha: 0.35), color: Colors.black.withValues(alpha: 0.35),
blurRadius: 18, blurRadius: 22,
offset: const Offset(0, 10), offset: const Offset(0, 12),
),
BoxShadow(
color: colorNaranja.withValues(alpha: 0.10),
blurRadius: 24,
), ),
], ],
); );

View File

@@ -38,3 +38,4 @@ flutter:
- assets/avatars/ - assets/avatars/
- assets/medals/ - assets/medals/
- assets/rewards/ - assets/rewards/
- assets/ui/premium/