From 3c5d98d6dd42383833d934c14b6f6451e2dc85f3 Mon Sep 17 00:00:00 2001 From: freetlab Date: Mon, 11 May 2026 21:57:46 +0200 Subject: [PATCH] =?UTF-8?q?literales=20y=20unificaci=C3=B3n=20de=20resulta?= =?UTF-8?q?dos=20en=20los=20dos=20modos=20de=20juego?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/l10n/app_ar.arb | 5 + lib/l10n/app_ca.arb | 5 + lib/l10n/app_de.arb | 5 + lib/l10n/app_en.arb | 5 + lib/l10n/app_es.arb | 5 + lib/l10n/app_eu.arb | 5 + lib/l10n/app_fr.arb | 5 + lib/l10n/app_hi.arb | 5 + lib/l10n/app_it.arb | 5 + lib/l10n/app_ja.arb | 5 + lib/l10n/app_ko.arb | 5 + lib/l10n/app_nl.arb | 5 + lib/l10n/app_pl.arb | 5 + lib/l10n/app_pt.arb | 5 + lib/l10n/app_ru.arb | 5 + lib/l10n/app_tr.arb | 5 + lib/l10n/app_zh.arb | 5 + lib/l10n/app_zh_TW.arb | 5 + lib/l10n/generated/app_localizations.dart | 31 + lib/l10n/generated/app_localizations_ar.dart | 15 + lib/l10n/generated/app_localizations_ca.dart | 15 + lib/l10n/generated/app_localizations_de.dart | 15 + lib/l10n/generated/app_localizations_en.dart | 15 + lib/l10n/generated/app_localizations_es.dart | 15 + lib/l10n/generated/app_localizations_eu.dart | 15 + lib/l10n/generated/app_localizations_fr.dart | 15 + lib/l10n/generated/app_localizations_hi.dart | 15 + lib/l10n/generated/app_localizations_it.dart | 15 + lib/l10n/generated/app_localizations_ja.dart | 15 + lib/l10n/generated/app_localizations_ko.dart | 15 + lib/l10n/generated/app_localizations_nl.dart | 15 + lib/l10n/generated/app_localizations_pl.dart | 15 + lib/l10n/generated/app_localizations_pt.dart | 15 + lib/l10n/generated/app_localizations_ru.dart | 15 + lib/l10n/generated/app_localizations_tr.dart | 15 + lib/l10n/generated/app_localizations_zh.dart | 15 + lib/pantallas/pantalla_fin_partida.dart | 11 +- .../pantalla_fin_partida_online.dart | 499 +++---- lib/pantallas/pantalla_resultado.dart | 185 +-- lib/pantallas/pantalla_resultado_online.dart | 130 +- lib/pantallas/pantalla_votacion_cliente.dart | 195 +-- lib/tema/componentes_resultado_farolero.dart | 1205 +++++++++++++++++ 42 files changed, 1815 insertions(+), 786 deletions(-) create mode 100644 lib/tema/componentes_resultado_farolero.dart diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index b38575f..15b93d2 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -3,6 +3,11 @@ "appTitle": "المنتحل", "subtitle": "لعبة تخمين اجتماعية", "loadingWords": "جارٍ تحميل الكلمات...", + "matchRewards": "مكافآت المباراة", + "newMedals": "ميداليات جديدة", + "noNewMedalsKeepFire": "لا توجد ميداليات جديدة هذه المرة. واصل إشعال حماسك.", + "calculatingRewards": "جارٍ حساب المكافآت...", + "fireLabel": "النار", "playersRange": "3-20 لاعبًا • بدون إنترنت", "createGame": "إنشاء لعبة", "joinGame": "الانضمام إلى لعبة", diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb index a45c019..de075e8 100644 --- a/lib/l10n/app_ca.arb +++ b/lib/l10n/app_ca.arb @@ -3,6 +3,11 @@ "appTitle": "L'Impostor", "subtitle": "Joc de deducció social", "loadingWords": "Carregant paraules...", + "matchRewards": "Recompenses de partida", + "newMedals": "Noves medalles", + "noNewMedalsKeepFire": "Sense medalles noves aquesta vegada. Continua acumulant foc.", + "calculatingRewards": "Calculant recompenses...", + "fireLabel": "Foc", "playersRange": "3-20 jugadors • Sense internet", "createGame": "Crear partida", "joinGame": "Unir-se a partida", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index c320f28..70315a5 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -3,6 +3,11 @@ "appTitle": "Farolero", "subtitle": "Soziales Deduktionsspiel", "loadingWords": "Wörter werden geladen...", + "matchRewards": "Spielbelohnungen", + "newMedals": "Neue Medaillen", + "noNewMedalsKeepFire": "Diesmal keine neuen Medaillen. Baue dein Feuer weiter aus.", + "calculatingRewards": "Belohnungen werden berechnet...", + "fireLabel": "Feuer", "playersRange": "3-20 Spieler • Ohne Internet", "createGame": "Spiel erstellen", "joinGame": "Spiel beitreten", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 32a3762..dd9c55c 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -3,6 +3,11 @@ "appTitle": "Farolero", "subtitle": "Social deduction game", "loadingWords": "Loading words...", + "matchRewards": "Game rewards", + "newMedals": "New medals", + "noNewMedalsKeepFire": "No new medals this time. Keep building your fire.", + "calculatingRewards": "Calculating rewards...", + "fireLabel": "Fire", "playersRange": "3-20 players • No internet needed", "createGame": "Create game", "joinGame": "Join game", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 0399bc4..f1bd582 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -3,6 +3,11 @@ "appTitle": "Farolero", "subtitle": "Juego de deducción social", "loadingWords": "Cargando palabras...", + "matchRewards": "Recompensas de partida", + "newMedals": "Nuevas medallas", + "noNewMedalsKeepFire": "Sin medallas nuevas esta vez. Sigue acumulando fuego.", + "calculatingRewards": "Calculando recompensas...", + "fireLabel": "Fuego", "playersRange": "3-20 jugadores • Sin internet", "createGame": "Crear partida", "joinGame": "Unirse a partida", diff --git a/lib/l10n/app_eu.arb b/lib/l10n/app_eu.arb index 68c708a..35512e3 100644 --- a/lib/l10n/app_eu.arb +++ b/lib/l10n/app_eu.arb @@ -3,6 +3,11 @@ "appTitle": "Inpostorrea", "subtitle": "Dedukzio sozialeko jokoa", "loadingWords": "Hitzak kargatzen...", + "matchRewards": "Partidako sariak", + "newMedals": "Domina berriak", + "noNewMedalsKeepFire": "Oraingoan ez dago domina berririk. Jarraitu sua pilatzen.", + "calculatingRewards": "Sariak kalkulatzen...", + "fireLabel": "Sua", "playersRange": "3-20 jokalari • Internetik gabe", "createGame": "Partida sortu", "joinGame": "Partidara batu", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d6bed28..15ea069 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -3,6 +3,11 @@ "appTitle": "Farolero", "subtitle": "Jeu de déduction sociale", "loadingWords": "Chargement des mots...", + "matchRewards": "Récompenses de partie", + "newMedals": "Nouvelles médailles", + "noNewMedalsKeepFire": "Pas de nouvelles médailles cette fois. Continue à entretenir ta flamme.", + "calculatingRewards": "Calcul des récompenses...", + "fireLabel": "Flamme", "playersRange": "3-20 joueurs • Sans internet", "createGame": "Créer une partie", "joinGame": "Rejoindre une partie", diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index 79f87b8..304aa53 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -3,6 +3,11 @@ "appTitle": "धोखेबाज़", "subtitle": "सामाजिक अनुमान का खेल", "loadingWords": "शब्द लोड हो रहे हैं...", + "matchRewards": "गेम पुरस्कार", + "newMedals": "नई पदक", + "noNewMedalsKeepFire": "इस बार कोई नया पदक नहीं। अपनी आग बढ़ाते रहें।", + "calculatingRewards": "पुरस्कार गिने जा रहे हैं...", + "fireLabel": "आग", "playersRange": "3-20 खिलाड़ी • इंटरनेट की ज़रूरत नहीं", "createGame": "गेम बनाएँ", "joinGame": "गेम में शामिल हों", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 5904e77..8c8e540 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -3,6 +3,11 @@ "appTitle": "Farolero", "subtitle": "Gioco di deduzione sociale", "loadingWords": "Caricamento parole...", + "matchRewards": "Ricompense partita", + "newMedals": "Nuove medaglie", + "noNewMedalsKeepFire": "Nessuna nuova medaglia questa volta. Continua ad alimentare il fuoco.", + "calculatingRewards": "Calcolo ricompense...", + "fireLabel": "Fuoco", "playersRange": "3-20 giocatori • Senza internet", "createGame": "Crea partita", "joinGame": "Unisciti alla partita", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 4c64326..0b35c17 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -3,6 +3,11 @@ "appTitle": "インポスター", "subtitle": "正体推理ゲーム", "loadingWords": "ワードを読み込み中...", + "matchRewards": "ゲーム報酬", + "newMedals": "新しいメダル", + "noNewMedalsKeepFire": "今回は新しいメダルはありません。炎を積み上げ続けましょう。", + "calculatingRewards": "報酬を計算中...", + "fireLabel": "炎", "playersRange": "3-20人 • インターネット不要", "createGame": "ゲームを作成", "joinGame": "ゲームに参加", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index bf8ed6d..cf971c3 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -3,6 +3,11 @@ "appTitle": "임포스터", "subtitle": "사회적 추리 게임", "loadingWords": "단어 불러오는 중...", + "matchRewards": "게임 보상", + "newMedals": "새 메달", + "noNewMedalsKeepFire": "이번에는 새 메달이 없습니다. 불꽃을 계속 키우세요.", + "calculatingRewards": "보상 계산 중...", + "fireLabel": "불꽃", "playersRange": "3-20명 • 인터넷 불필요", "createGame": "게임 만들기", "joinGame": "게임 참가", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 170cc0e..220485d 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -3,6 +3,11 @@ "appTitle": "De Bedrieger", "subtitle": "Sociaal deductiespel", "loadingWords": "Woorden laden...", + "matchRewards": "Spelbeloningen", + "newMedals": "Nieuwe medailles", + "noNewMedalsKeepFire": "Deze keer geen nieuwe medailles. Blijf je vuur opbouwen.", + "calculatingRewards": "Beloningen berekenen...", + "fireLabel": "Vuur", "playersRange": "3-20 spelers • Zonder internet", "createGame": "Spel aanmaken", "joinGame": "Deelnemen aan spel", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index b8c91ba..81117cf 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -3,6 +3,11 @@ "appTitle": "Oszust", "subtitle": "Gra dedukcji społecznej", "loadingWords": "Ładowanie słów...", + "matchRewards": "Nagrody za grę", + "newMedals": "Nowe medale", + "noNewMedalsKeepFire": "Tym razem bez nowych medali. Podtrzymuj swój ogień.", + "calculatingRewards": "Obliczanie nagród...", + "fireLabel": "Ogień", "playersRange": "3-20 graczy • Bez internetu", "createGame": "Utwórz grę", "joinGame": "Dołącz do gry", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 51e09fb..cbeb295 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -3,6 +3,11 @@ "appTitle": "O Impostor", "subtitle": "Jogo de dedução social", "loadingWords": "Carregando palavras...", + "matchRewards": "Recompensas da partida", + "newMedals": "Novas medalhas", + "noNewMedalsKeepFire": "Sem medalhas novas desta vez. Continua a acumular fogo.", + "calculatingRewards": "Calculando recompensas...", + "fireLabel": "Fogo", "playersRange": "3-20 jogadores • Sem internet", "createGame": "Criar partida", "joinGame": "Entrar na partida", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 22589a7..b14a281 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -3,6 +3,11 @@ "appTitle": "Самозванец", "subtitle": "Социальная игра на дедукцию", "loadingWords": "Загрузка слов...", + "matchRewards": "Награды за игру", + "newMedals": "Новые медали", + "noNewMedalsKeepFire": "В этот раз новых медалей нет. Продолжай разжигать огонь.", + "calculatingRewards": "Подсчёт наград...", + "fireLabel": "Огонь", "playersRange": "3-20 игроков • Без интернета", "createGame": "Создать игру", "joinGame": "Присоединиться к игре", diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index d73ad9b..1b849f5 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -3,6 +3,11 @@ "appTitle": "Sahtekar", "subtitle": "Sosyal çıkarım oyunu", "loadingWords": "Kelimeler yükleniyor...", + "matchRewards": "Oyun ödülleri", + "newMedals": "Yeni madalyalar", + "noNewMedalsKeepFire": "Bu kez yeni madalya yok. Ateşini büyütmeye devam et.", + "calculatingRewards": "Ödüller hesaplanıyor...", + "fireLabel": "Ateş", "playersRange": "3-20 oyuncu • İnternet gerektirmez", "createGame": "Oyun oluştur", "joinGame": "Oyuna katıl", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 6e08acc..f82f2c8 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -3,6 +3,11 @@ "appTitle": "冒牌者", "subtitle": "社交推理游戏", "loadingWords": "正在加载词汇...", + "matchRewards": "游戏奖励", + "newMedals": "新奖牌", + "noNewMedalsKeepFire": "这次没有新奖牌。继续积累火焰。", + "calculatingRewards": "正在计算奖励...", + "fireLabel": "火焰", "playersRange": "3-20名玩家 • 无需联网", "createGame": "创建游戏", "joinGame": "加入游戏", diff --git a/lib/l10n/app_zh_TW.arb b/lib/l10n/app_zh_TW.arb index 126086b..3c283ee 100644 --- a/lib/l10n/app_zh_TW.arb +++ b/lib/l10n/app_zh_TW.arb @@ -3,6 +3,11 @@ "appTitle": "冒牌者", "subtitle": "社交推理遊戲", "loadingWords": "正在載入詞彙...", + "matchRewards": "遊戲獎勵", + "newMedals": "新獎牌", + "noNewMedalsKeepFire": "這次沒有新獎牌。繼續累積火焰。", + "calculatingRewards": "正在計算獎勵...", + "fireLabel": "火焰", "playersRange": "3-20 位玩家 • 無需網路", "createGame": "建立遊戲", "joinGame": "加入遊戲", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 403cb3f..23116f8 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -147,6 +147,37 @@ abstract class AppLocalizations { /// **'Cargando palabras...'** String get loadingWords; + + /// No description provided for @matchRewards. + /// + /// In en, this message translates to: + /// **'Game rewards'** + String get matchRewards; + + /// No description provided for @newMedals. + /// + /// In en, this message translates to: + /// **'New medals'** + String get newMedals; + + /// No description provided for @noNewMedalsKeepFire. + /// + /// In en, this message translates to: + /// **'No new medals this time. Keep building your fire.'** + String get noNewMedalsKeepFire; + + /// No description provided for @calculatingRewards. + /// + /// In en, this message translates to: + /// **'Calculating rewards...'** + String get calculatingRewards; + + /// No description provided for @fireLabel. + /// + /// In en, this message translates to: + /// **'Fire'** + String get fireLabel; + /// No description provided for @playersRange. /// /// In es, this message translates to: diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index c5805f7..c0f6805 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -17,6 +17,21 @@ class AppLocalizationsAr extends AppLocalizations { @override String get loadingWords => 'جارٍ تحميل الكلمات...'; + @override + String get matchRewards => "مكافآت المباراة"; + + @override + String get newMedals => "ميداليات جديدة"; + + @override + String get noNewMedalsKeepFire => "لا توجد ميداليات جديدة هذه المرة. واصل إشعال حماسك."; + + @override + String get calculatingRewards => "جارٍ حساب المكافآت..."; + + @override + String get fireLabel => "النار"; + @override String get playersRange => '3-20 لاعبًا • بدون إنترنت'; diff --git a/lib/l10n/generated/app_localizations_ca.dart b/lib/l10n/generated/app_localizations_ca.dart index de73bd8..9f0456e 100644 --- a/lib/l10n/generated/app_localizations_ca.dart +++ b/lib/l10n/generated/app_localizations_ca.dart @@ -17,6 +17,21 @@ class AppLocalizationsCa extends AppLocalizations { @override String get loadingWords => 'Carregant paraules...'; + @override + String get matchRewards => "Recompenses de partida"; + + @override + String get newMedals => "Noves medalles"; + + @override + String get noNewMedalsKeepFire => "Sense medalles noves aquesta vegada. Continua acumulant foc."; + + @override + String get calculatingRewards => "Calculant recompenses..."; + + @override + String get fireLabel => "Foc"; + @override String get playersRange => '3-20 jugadors • Sense internet'; diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 9310100..9560b5a 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -17,6 +17,21 @@ class AppLocalizationsDe extends AppLocalizations { @override String get loadingWords => 'Wörter werden geladen...'; + @override + String get matchRewards => "Spielbelohnungen"; + + @override + String get newMedals => "Neue Medaillen"; + + @override + String get noNewMedalsKeepFire => "Diesmal keine neuen Medaillen. Baue dein Feuer weiter aus."; + + @override + String get calculatingRewards => "Belohnungen werden berechnet..."; + + @override + String get fireLabel => "Feuer"; + @override String get playersRange => '3-20 Spieler • Ohne Internet'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 9e7cf19..4837d1d 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -17,6 +17,21 @@ class AppLocalizationsEn extends AppLocalizations { @override String get loadingWords => 'Loading words...'; + @override + String get matchRewards => "Game rewards"; + + @override + String get newMedals => "New medals"; + + @override + String get noNewMedalsKeepFire => "No new medals this time. Keep building your fire."; + + @override + String get calculatingRewards => "Calculating rewards..."; + + @override + String get fireLabel => "Fire"; + @override String get playersRange => '3-20 players • No internet needed'; diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 83de2a1..b2e78ef 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -17,6 +17,21 @@ class AppLocalizationsEs extends AppLocalizations { @override String get loadingWords => 'Cargando palabras...'; + @override + String get matchRewards => "Recompensas de partida"; + + @override + String get newMedals => "Nuevas medallas"; + + @override + String get noNewMedalsKeepFire => "Sin medallas nuevas esta vez. Sigue acumulando fuego."; + + @override + String get calculatingRewards => "Calculando recompensas..."; + + @override + String get fireLabel => "Fuego"; + @override String get playersRange => '3-20 jugadores • Sin internet'; diff --git a/lib/l10n/generated/app_localizations_eu.dart b/lib/l10n/generated/app_localizations_eu.dart index 58f5714..e0b9504 100644 --- a/lib/l10n/generated/app_localizations_eu.dart +++ b/lib/l10n/generated/app_localizations_eu.dart @@ -17,6 +17,21 @@ class AppLocalizationsEu extends AppLocalizations { @override String get loadingWords => 'Hitzak kargatzen...'; + @override + String get matchRewards => "Partidako sariak"; + + @override + String get newMedals => "Domina berriak"; + + @override + String get noNewMedalsKeepFire => "Oraingoan ez dago domina berririk. Jarraitu sua pilatzen."; + + @override + String get calculatingRewards => "Sariak kalkulatzen..."; + + @override + String get fireLabel => "Sua"; + @override String get playersRange => '3-20 jokalari • Internetik gabe'; diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index cf57073..7e50200 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -17,6 +17,21 @@ class AppLocalizationsFr extends AppLocalizations { @override String get loadingWords => 'Chargement des mots...'; + @override + String get matchRewards => "Récompenses de partie"; + + @override + String get newMedals => "Nouvelles médailles"; + + @override + String get noNewMedalsKeepFire => "Pas de nouvelles médailles cette fois. Continue à entretenir ta flamme."; + + @override + String get calculatingRewards => "Calcul des récompenses..."; + + @override + String get fireLabel => "Flamme"; + @override String get playersRange => '3-20 joueurs • Sans internet'; diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index dee6572..069ae4a 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -17,6 +17,21 @@ class AppLocalizationsHi extends AppLocalizations { @override String get loadingWords => 'शब्द लोड हो रहे हैं...'; + @override + String get matchRewards => "गेम पुरस्कार"; + + @override + String get newMedals => "नई पदक"; + + @override + String get noNewMedalsKeepFire => "इस बार कोई नया पदक नहीं। अपनी आग बढ़ाते रहें।"; + + @override + String get calculatingRewards => "पुरस्कार गिने जा रहे हैं..."; + + @override + String get fireLabel => "आग"; + @override String get playersRange => '3-20 खिलाड़ी • इंटरनेट की ज़रूरत नहीं'; diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 6efff8b..4bb0ad7 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -17,6 +17,21 @@ class AppLocalizationsIt extends AppLocalizations { @override String get loadingWords => 'Caricamento parole...'; + @override + String get matchRewards => "Ricompense partita"; + + @override + String get newMedals => "Nuove medaglie"; + + @override + String get noNewMedalsKeepFire => "Nessuna nuova medaglia questa volta. Continua ad alimentare il fuoco."; + + @override + String get calculatingRewards => "Calcolo ricompense..."; + + @override + String get fireLabel => "Fuoco"; + @override String get playersRange => '3-20 giocatori • Senza internet'; diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index 69eea5a..d8933cd 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -17,6 +17,21 @@ class AppLocalizationsJa extends AppLocalizations { @override String get loadingWords => 'ワードを読み込み中...'; + @override + String get matchRewards => "ゲーム報酬"; + + @override + String get newMedals => "新しいメダル"; + + @override + String get noNewMedalsKeepFire => "今回は新しいメダルはありません。炎を積み上げ続けましょう。"; + + @override + String get calculatingRewards => "報酬を計算中..."; + + @override + String get fireLabel => "炎"; + @override String get playersRange => '3-20人 • インターネット不要'; diff --git a/lib/l10n/generated/app_localizations_ko.dart b/lib/l10n/generated/app_localizations_ko.dart index e3d427d..5789d7e 100644 --- a/lib/l10n/generated/app_localizations_ko.dart +++ b/lib/l10n/generated/app_localizations_ko.dart @@ -17,6 +17,21 @@ class AppLocalizationsKo extends AppLocalizations { @override String get loadingWords => '단어 불러오는 중...'; + @override + String get matchRewards => "게임 보상"; + + @override + String get newMedals => "새 메달"; + + @override + String get noNewMedalsKeepFire => "이번에는 새 메달이 없습니다. 불꽃을 계속 키우세요."; + + @override + String get calculatingRewards => "보상 계산 중..."; + + @override + String get fireLabel => "불꽃"; + @override String get playersRange => '3-20명 • 인터넷 불필요'; diff --git a/lib/l10n/generated/app_localizations_nl.dart b/lib/l10n/generated/app_localizations_nl.dart index a9c9e3a..1d2d1fe 100644 --- a/lib/l10n/generated/app_localizations_nl.dart +++ b/lib/l10n/generated/app_localizations_nl.dart @@ -17,6 +17,21 @@ class AppLocalizationsNl extends AppLocalizations { @override String get loadingWords => 'Woorden laden...'; + @override + String get matchRewards => "Spelbeloningen"; + + @override + String get newMedals => "Nieuwe medailles"; + + @override + String get noNewMedalsKeepFire => "Deze keer geen nieuwe medailles. Blijf je vuur opbouwen."; + + @override + String get calculatingRewards => "Beloningen berekenen..."; + + @override + String get fireLabel => "Vuur"; + @override String get playersRange => '3-20 spelers • Zonder internet'; diff --git a/lib/l10n/generated/app_localizations_pl.dart b/lib/l10n/generated/app_localizations_pl.dart index 25c0e60..81e48aa 100644 --- a/lib/l10n/generated/app_localizations_pl.dart +++ b/lib/l10n/generated/app_localizations_pl.dart @@ -17,6 +17,21 @@ class AppLocalizationsPl extends AppLocalizations { @override String get loadingWords => 'Ładowanie słów...'; + @override + String get matchRewards => "Nagrody za grę"; + + @override + String get newMedals => "Nowe medale"; + + @override + String get noNewMedalsKeepFire => "Tym razem bez nowych medali. Podtrzymuj swój ogień."; + + @override + String get calculatingRewards => "Obliczanie nagród..."; + + @override + String get fireLabel => "Ogień"; + @override String get playersRange => '3-20 graczy • Bez internetu'; diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 7e4e297..f7736f4 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -17,6 +17,21 @@ class AppLocalizationsPt extends AppLocalizations { @override String get loadingWords => 'Carregando palavras...'; + @override + String get matchRewards => "Recompensas da partida"; + + @override + String get newMedals => "Novas medalhas"; + + @override + String get noNewMedalsKeepFire => "Sem medalhas novas desta vez. Continua a acumular fogo."; + + @override + String get calculatingRewards => "Calculando recompensas..."; + + @override + String get fireLabel => "Fogo"; + @override String get playersRange => '3-20 jogadores • Sem internet'; diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index 9a68ae4..0fedc1a 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -17,6 +17,21 @@ class AppLocalizationsRu extends AppLocalizations { @override String get loadingWords => 'Загрузка слов...'; + @override + String get matchRewards => "Награды за игру"; + + @override + String get newMedals => "Новые медали"; + + @override + String get noNewMedalsKeepFire => "В этот раз новых медалей нет. Продолжай разжигать огонь."; + + @override + String get calculatingRewards => "Подсчёт наград..."; + + @override + String get fireLabel => "Огонь"; + @override String get playersRange => '3-20 игроков • Без интернета'; diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index 9dfab03..5b445ce 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -17,6 +17,21 @@ class AppLocalizationsTr extends AppLocalizations { @override String get loadingWords => 'Kelimeler yükleniyor...'; + @override + String get matchRewards => "Oyun ödülleri"; + + @override + String get newMedals => "Yeni madalyalar"; + + @override + String get noNewMedalsKeepFire => "Bu kez yeni madalya yok. Ateşini büyütmeye devam et."; + + @override + String get calculatingRewards => "Ödüller hesaplanıyor..."; + + @override + String get fireLabel => "Ateş"; + @override String get playersRange => '3-20 oyuncu • İnternet gerektirmez'; diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index 0e52930..9493b6a 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -17,6 +17,21 @@ class AppLocalizationsZh extends AppLocalizations { @override String get loadingWords => '正在加载词汇...'; + @override + String get matchRewards => "游戏奖励"; + + @override + String get newMedals => "新奖牌"; + + @override + String get noNewMedalsKeepFire => "这次没有新奖牌。继续积累火焰。"; + + @override + String get calculatingRewards => "正在计算奖励..."; + + @override + String get fireLabel => "火焰"; + @override String get playersRange => '3-20名玩家 • 无需联网'; diff --git a/lib/pantallas/pantalla_fin_partida.dart b/lib/pantallas/pantalla_fin_partida.dart index 6aa4821..339c2ad 100644 --- a/lib/pantallas/pantalla_fin_partida.dart +++ b/lib/pantallas/pantalla_fin_partida.dart @@ -384,6 +384,7 @@ class _TarjetaProgresoGamificacion extends StatelessWidget { final nuevas = progreso.nuevasMedallas; final antes = progreso.antes.fuego.clamp(0, 100); final despues = progreso.despues.fuego.clamp(0, 100); + final l10n = AppLocalizations.of(context)!; return _PanelRecompensa( child: Column( @@ -420,7 +421,7 @@ class _TarjetaProgresoGamificacion extends StatelessWidget { const SizedBox(width: 14), Expanded( child: Text( - 'RECOMPENSAS DE PARTIDA', + l10n.matchRewards.toUpperCase(), style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorDorado, fontSize: 20, @@ -438,7 +439,7 @@ class _TarjetaProgresoGamificacion extends StatelessWidget { const SizedBox(height: 20), if (nuevas.isEmpty) Text( - 'Sin medallas nuevas esta vez. Seguí acumulando fuego.', + l10n.noNewMedalsKeepFire, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorTextoSecundario, height: 1.35, @@ -446,7 +447,7 @@ class _TarjetaProgresoGamificacion extends StatelessWidget { ) else ...[ Text( - 'NUEVAS MEDALLAS', + l10n.newMedals.toUpperCase(), style: Theme.of(context).textTheme.labelLarge?.copyWith( color: TemaApp.colorDorado, fontWeight: FontWeight.w800, @@ -486,7 +487,7 @@ class _TarjetaRecompensaCargando extends StatelessWidget { const SizedBox(width: 12), Expanded( child: Text( - 'Calculando recompensas...', + AppLocalizations.of(context)!.calculatingRewards, style: Theme.of(context).textTheme.titleMedium?.copyWith( color: TemaApp.colorDorado, ), @@ -617,7 +618,7 @@ class _BarraFuegoPremium extends StatelessWidget { Row( children: [ Text( - 'Fuego', + AppLocalizations.of(context)!.fireLabel, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w700, ), diff --git a/lib/pantallas/pantalla_fin_partida_online.dart b/lib/pantallas/pantalla_fin_partida_online.dart index 6334c36..59fa40c 100644 --- a/lib/pantallas/pantalla_fin_partida_online.dart +++ b/lib/pantallas/pantalla_fin_partida_online.dart @@ -1,15 +1,18 @@ +import 'package:confetti/confetti.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; -import '../modelos/inicio_partida_multijugador.dart'; + import '../modelos/gamificacion_usuario.dart'; +import '../modelos/inicio_partida_multijugador.dart'; +import '../modelos/jugador.dart'; import '../modelos/palabra.dart'; import '../modelos/snapshot_partida_online.dart'; import '../servicios/servicio_historial_partidas.dart'; import '../servicios/servicio_nearby.dart'; import '../servicios/servicio_perfil_usuario.dart'; import '../tema/componentes_farolero.dart'; +import '../tema/componentes_resultado_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_notas_online.dart'; import 'pantalla_principal.dart'; @@ -35,202 +38,216 @@ class PantallaFinPartidaOnline extends StatefulWidget { class _PantallaFinPartidaOnlineState extends State { bool _guardada = false; ProgresoGamificacionUsuario? _progreso; + late final ConfettiController _confetti; + + @override + void initState() { + super.initState(); + _confetti = ConfettiController(duration: const Duration(seconds: 5)); + Future.delayed(const Duration(milliseconds: 450), () { + if (mounted) _confetti.play(); + }); + } + + @override + void dispose() { + _confetti.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final snapshot = widget.snapshot; - final jugadoresControlados = widget.jugadoresControlados; - final pistaCategoria = widget.pistaCategoria; final ganaronJugadores = snapshot.ganador == 'jugadores'; + final color = ganaronJugadores ? TemaApp.colorVerde : TemaApp.colorAcento; - if (!_guardada && snapshot.ganador != null) { - _guardada = true; - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (mounted) { - final historial = context.read(); - final perfil = context.read(); - await historial.guardarSnapshotOnline(snapshot); - final progreso = await perfil.registrarPartidaCompletada( - victoria: _perfilLocalGano(snapshot, jugadoresControlados), - comoImpostor: jugadoresControlados.any((j) => j.esImpostor), - victoriaComoImpostor: snapshot.ganador == 'impostores' && - jugadoresControlados.any((j) => j.esImpostor), - ); - if (mounted) setState(() => _progreso = progreso); - } - }); - } + _registrarResultadoSiHaceFalta(context, snapshot); return Scaffold( + extendBodyBehindAppBar: true, + backgroundColor: const Color(0xFF05070D), appBar: AppBar( title: Text(l10n.gameOver), automaticallyImplyLeading: false, - actions: [ - IconButton( - tooltip: l10n.seeYourWord, - icon: const Icon(Icons.visibility), - onPressed: jugadoresControlados.isEmpty - ? null - : () => mostrarRevisionPalabraOnline( - context: context, - jugadoresControlados: jugadoresControlados, - pistaCategoria: pistaCategoria, - ), - ), - IconButton( - tooltip: l10n.notesTitle, - icon: const Icon(Icons.edit_note), - onPressed: snapshot.roomId == null || jugadoresControlados.isEmpty - ? null - : () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => PantallaNotasOnline( - partidaId: snapshot.roomId!, - jugadores: snapshot.jugadores, - autoresControlados: jugadoresControlados, - ), - ), - ), - ), - ], + backgroundColor: Colors.transparent, + elevation: 0, + actions: _acciones(context, l10n, snapshot), ), body: FondoFarolero( intenso: true, - child: SingleChildScrollView( - padding: const EdgeInsets.all(24), - child: Column( + child: Stack( children: [ - _ResultadoOnlineHero( - ganaronJugadores: ganaronJugadores, - titulo: ganaronJugadores ? l10n.playersWin : l10n.impostorsWin, + Positioned.fill( + child: IgnorePointer( + child: CustomPaint( + painter: EscenarioFinPartidaFaroleroPainter(color: color), + ), + ), ), - const SizedBox(height: 24), - if (_progreso != null) ...[ - _TarjetaProgresoGamificacion(progreso: _progreso!), - const SizedBox(height: 16), - ], - Card( - child: Padding( - padding: const EdgeInsets.all(20), + Align( + alignment: Alignment.topCenter, + child: ConfettiWidget( + confettiController: _confetti, + blastDirectionality: BlastDirectionality.explosive, + emissionFrequency: 0.09, + numberOfParticles: 28, + gravity: 0.18, + colors: const [ + TemaApp.colorDorado, + TemaApp.colorNaranja, + TemaApp.colorAcento, + Color(0xFFFFECBE), + ], + ), + ), + Positioned.fill( + child: IgnorePointer( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.transparent, + Colors.black.withValues(alpha: 0.10), + Colors.black.withValues(alpha: 0.52), + ], + stops: const [0.0, 0.54, 1.0], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + ), + ), + SafeArea( + child: SingleChildScrollView( + padding: const EdgeInsets.fromLTRB(20, 72, 20, 28), child: Column( children: [ - Text( - l10n.theSecretWordWas, - style: Theme.of(context).textTheme.titleMedium, + HeroFinalPartidaFarolero( + encabezado: l10n.gameOver, + titulo: + ganaronJugadores ? l10n.playersWin : l10n.impostorsWin, + icono: ganaronJugadores + ? Icons.emoji_events + : Icons.theater_comedy, + color: color, ), - const SizedBox(height: 8), - Text( - snapshot.palabraSecreta ?? '?', - style: Theme.of(context).textTheme.headlineLarge?.copyWith( - color: TemaApp.colorNaranja, - fontSize: 32, - ), - ), - const SizedBox(height: 4), - Text( - l10n.categoryLabel( - BancoPalabras.nombreBonitoCategoria( + const SizedBox(height: 12), + if (_progreso == null) + const TarjetaRecompensaCargandoPremium() + else + TarjetaProgresoGamificacionPremium(progreso: _progreso!), + const SizedBox(height: 18), + if (snapshot.palabraSecreta != null) + TarjetaSecretoPremium( + palabra: snapshot.palabraSecreta!, + categoria: BancoPalabras.nombreBonitoCategoria( snapshot.categoria, l10n, ), ), - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - ), - ), - const SizedBox(height: 16), - Card( - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - children: [ - Text( - snapshot.impostores.length == 1 + const SizedBox(height: 18), + TarjetaImpostoresPremium( + titulo: snapshot.impostores.length == 1 ? l10n.theImpostorWas : l10n.theImpostorsWere, - style: Theme.of(context).textTheme.titleMedium, + impostores: _impostores(snapshot), ), - const SizedBox(height: 8), - ...snapshot.impostores.map( - (nombre) => Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Text( - '🎭 $nombre', - style: Theme.of(context).textTheme.titleLarge?.copyWith( - color: TemaApp.colorAcento, - ), - ), + const SizedBox(height: 18), + if (snapshot.historialVotaciones.isNotEmpty) + TarjetaHistorialVotosPremium( + historial: snapshot.historialVotaciones, + jugadores: snapshot.jugadores, ), + const SizedBox(height: 24), + BotonFarolero.oscuro( + texto: l10n.mainMenu, + icono: Icons.home, + onPressed: () async { + await context.read().desconectar(); + if (!context.mounted) return; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (_) => const PantallaPrincipal(), + ), + (route) => false, + ); + }, ), + const SizedBox(height: 16), ], ), ), ), - const SizedBox(height: 16), - if (snapshot.historialVotaciones.isNotEmpty) - Card( - child: Padding( - padding: const EdgeInsets.all(20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - l10n.votingHistory, - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 12), - ...snapshot.historialVotaciones.asMap().entries.map( - (entrada) { - final ronda = entrada.key + 1; - final resultado = entrada.value; - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Text( - l10n.roundElimination( - ronda, - resultado.eliminadoNombre, - ), - style: TextStyle( - fontWeight: FontWeight.bold, - color: resultado.eraImpostor - ? TemaApp.colorVerde - : TemaApp.colorAcento, - ), - ), - ); - }, - ), - ], - ), - ), - ), - const SizedBox(height: 24), - BotonFarolero.oscuro( - texto: l10n.mainMenu, - icono: Icons.home, - onPressed: () async { - await context.read().desconectar(); - if (!context.mounted) return; - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (_) => const PantallaPrincipal(), - ), - (route) => false, - ); - }, - ), ], - ), ), ), ); } + List _acciones( + BuildContext context, + AppLocalizations l10n, + SnapshotPartidaOnline snapshot, + ) { + return [ + IconButton( + tooltip: l10n.seeYourWord, + icon: const Icon(Icons.visibility), + onPressed: widget.jugadoresControlados.isEmpty + ? null + : () => mostrarRevisionPalabraOnline( + context: context, + jugadoresControlados: widget.jugadoresControlados, + pistaCategoria: widget.pistaCategoria, + ), + ), + IconButton( + tooltip: l10n.notesTitle, + icon: const Icon(Icons.edit_note), + onPressed: snapshot.roomId == null || widget.jugadoresControlados.isEmpty + ? null + : () => Navigator.push( + context, + MaterialPageRoute( + builder: (_) => PantallaNotasOnline( + partidaId: snapshot.roomId!, + jugadores: snapshot.jugadores, + autoresControlados: widget.jugadoresControlados, + ), + ), + ), + ), + ]; + } + + void _registrarResultadoSiHaceFalta( + BuildContext context, + SnapshotPartidaOnline snapshot, + ) { + if (_guardada || snapshot.ganador == null) return; + _guardada = true; + WidgetsBinding.instance.addPostFrameCallback((_) async { + if (!mounted) return; + final historial = context.read(); + final perfil = context.read(); + await historial.guardarSnapshotOnline(snapshot); + final progreso = await perfil.registrarPartidaCompletada( + victoria: _perfilLocalGano(snapshot, widget.jugadoresControlados), + comoImpostor: widget.jugadoresControlados.any((j) => j.esImpostor), + victoriaComoImpostor: snapshot.ganador == 'impostores' && + widget.jugadoresControlados.any((j) => j.esImpostor), + ); + if (!mounted) return; + setState(() => _progreso = progreso); + if (progreso.nuevasMedallas.isNotEmpty || progreso.incrementoFuego > 0) { + _confetti.play(); + } + }); + } + bool _perfilLocalGano( SnapshotPartidaOnline snapshot, List jugadoresControlados, @@ -241,161 +258,15 @@ class _PantallaFinPartidaOnlineState extends State { (jugador) => jugador.esImpostor ? ganaronImpostores : !ganaronImpostores, ); } -} -class _ResultadoOnlineHero extends StatelessWidget { - final bool ganaronJugadores; - final String titulo; - - const _ResultadoOnlineHero({ - required this.ganaronJugadores, - required this.titulo, - }); - - @override - Widget build(BuildContext context) { - final color = ganaronJugadores ? TemaApp.colorVerde : TemaApp.colorAcento; - return Container( - width: double.infinity, - padding: const EdgeInsets.fromLTRB(20, 18, 20, 24), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - color.withValues(alpha: 0.20), - const Color(0xFF101827).withValues(alpha: 0.68), - ], - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - ), - borderRadius: BorderRadius.circular(28), - border: Border.all(color: color.withValues(alpha: 0.65)), - ), - child: Column( - children: [ - Image.asset( - 'assets/ui/generated/final_rewards/cinematic_burst.webp', - height: 250, - fit: BoxFit.contain, - opacity: const AlwaysStoppedAnimation(0.95), - ), - const SizedBox(height: 8), - Text( - titulo, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: color, - fontWeight: FontWeight.w900, - shadows: [ - Shadow(color: color.withValues(alpha: 0.75), blurRadius: 18), - ], - ), - textAlign: TextAlign.center, - ), - ], - ), - ).animate().fadeIn(duration: 320.ms).slideY(begin: -0.08); - } -} - -class _TarjetaProgresoGamificacion extends StatelessWidget { - final ProgresoGamificacionUsuario progreso; - - const _TarjetaProgresoGamificacion({required this.progreso}); - - @override - Widget build(BuildContext context) { - final nuevas = progreso.nuevasMedallas; - return Container( - width: double.infinity, - padding: const EdgeInsets.all(18), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - const Color(0xFF111B28).withValues(alpha: 0.96), - const Color(0xFF180D22).withValues(alpha: 0.94), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: BorderRadius.circular(22), - border: Border.all(color: TemaApp.colorDorado.withValues(alpha: 0.52)), - boxShadow: [ - BoxShadow( - color: TemaApp.colorNaranja.withValues(alpha: 0.18), - blurRadius: 32, - offset: const Offset(0, 16), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.local_fire_department, - color: TemaApp.colorNaranja), - const SizedBox(width: 8), - Expanded( - child: Text( - 'RECOMPENSAS DE PARTIDA', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: TemaApp.colorDorado, - fontWeight: FontWeight.w900, - letterSpacing: 1, - ), - ), - ), - Text( - progreso.incrementoFuego >= 0 - ? '+${progreso.incrementoFuego}' - : '${progreso.incrementoFuego}', - style: Theme.of(context).textTheme.titleMedium?.copyWith( - color: TemaApp.colorDorado, - fontWeight: FontWeight.w900, - ), - ), - ], - ), - const SizedBox(height: 12), - TweenAnimationBuilder( - tween: Tween( - begin: progreso.antes.fuego.clamp(0, 100) / 100, - end: progreso.despues.fuego.clamp(0, 100) / 100, - ), - duration: const Duration(milliseconds: 1300), - curve: Curves.easeOutCubic, - builder: (context, value, _) => ClipRRect( - borderRadius: BorderRadius.circular(999), - child: LinearProgressIndicator( - value: value.clamp(0.0, 1.0).toDouble(), - minHeight: 18, - backgroundColor: Colors.black.withValues(alpha: 0.35), - valueColor: const AlwaysStoppedAnimation(TemaApp.colorNaranja), - ), - ), - ), - const SizedBox(height: 8), - Text( - '${progreso.despues.fuego}% de fuego', - style: Theme.of(context).textTheme.bodySmall, - ), - if (nuevas.isNotEmpty) ...[ - const SizedBox(height: 12), - Text( - 'NUEVAS MEDALLAS', - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: TemaApp.colorDorado, - fontWeight: FontWeight.w800, - ), - ), - const SizedBox(height: 6), - MedallasCompactasFarolero(ids: nuevas, max: nuevas.length), - ], - ], - ), - ) - .animate() - .fadeIn(duration: 360.ms) - .slideY(begin: 0.12) - .shimmer(delay: 700.ms, duration: 1200.ms); + List _impostores(SnapshotPartidaOnline snapshot) { + final porNombre = {for (final jugador in snapshot.jugadores) jugador.nombre: jugador}; + return snapshot.impostores + .map( + (nombre) => + porNombre[nombre] ?? + Jugador(id: nombre, nombre: nombre, esImpostor: true), + ) + .toList(); } } diff --git a/lib/pantallas/pantalla_resultado.dart b/lib/pantallas/pantalla_resultado.dart index 26eb433..22e1e33 100644 --- a/lib/pantallas/pantalla_resultado.dart +++ b/lib/pantallas/pantalla_resultado.dart @@ -84,56 +84,12 @@ class _PantallaResultadoState extends State ], if (_revelado) ...[ - // Resultado revelado FadeTransition( opacity: _animOpacidad, - child: Column( - children: [ - const ArteGameplayFarolero.resultado(height: 164), - const SizedBox(height: 16), - Text( - widget.resultado.eliminadoNombre, - style: Theme.of(context) - .textTheme - .headlineLarge - ?.copyWith(fontSize: 32), - ), - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 24, vertical: 12), - decoration: BoxDecoration( - color: widget.resultado.eraImpostor - ? TemaApp.colorVerde.withValues(alpha: 0.3) - : TemaApp.colorAcento.withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(30), - border: Border.all( - color: widget.resultado.eraImpostor - ? TemaApp.colorVerde - : TemaApp.colorAcento, - ), - ), - child: Text( - widget.resultado.eraImpostor - ? l10n.wasImpostor - : l10n.wasInnocent, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: widget.resultado.eraImpostor - ? TemaApp.colorVerde - : TemaApp.colorAcento, - ), - ), - ), - const SizedBox(height: 24), - - _buildDetalleVotos(context, partida, l10n), - const SizedBox(height: 24), - - // Acciones - _construirBotones(context, estado), - ], + child: ResultadoRondaFarolero( + resultado: widget.resultado, + jugadores: partida?.jugadores ?? const [], + acciones: _construirBotones(context, estado), ), ), ], @@ -145,139 +101,6 @@ class _PantallaResultadoState extends State ); } - Widget _buildDetalleVotos( - BuildContext context, - Partida? partida, - AppLocalizations l10n, - ) { - final jugadores = { - for (final jugador in partida?.jugadores ?? []) jugador.id: jugador, - }; - final conteo = {}; - for (final votadoId in widget.resultado.votos.values) { - conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; - } - final maxVotos = conteo.values.isEmpty - ? 1 - : conteo.values.reduce((a, b) => a > b ? a : b); - final ranking = conteo.entries.toList() - ..sort((a, b) => b.value.compareTo(a.value)); - - return Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(Icons.bar_chart, color: TemaApp.colorNaranja), - const SizedBox(width: 8), - Text( - l10n.votesThisRound, - style: Theme.of(context).textTheme.titleMedium, - ), - ], - ), - const SizedBox(height: 12), - ...ranking.map((entry) { - final jugador = jugadores[entry.key]; - final eliminado = entry.key == widget.resultado.eliminadoId; - return _buildBarraVotos( - context, - nombre: jugador?.nombre ?? '?', - votos: entry.value, - total: maxVotos, - destacado: eliminado, - ); - }), - const Divider(height: 24), - ...widget.resultado.votos.entries.map((entry) { - final votante = jugadores[entry.key]?.nombre ?? '?'; - final votado = jugadores[entry.value]?.nombre ?? '?'; - final fueAlEliminado = - entry.value == widget.resultado.eliminadoId; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - children: [ - Icon( - fueAlEliminado - ? Icons.how_to_vote - : Icons.arrow_forward, - size: 18, - color: fueAlEliminado - ? TemaApp.colorAcento - : TemaApp.colorTextoSecundario, - ), - const SizedBox(width: 8), - Expanded( - child: Text( - '$votante → $votado', - style: TextStyle( - color: fueAlEliminado - ? TemaApp.colorTexto - : TemaApp.colorTextoSecundario, - fontWeight: fueAlEliminado - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ), - ], - ), - ); - }), - ], - ), - ), - ); - } - - Widget _buildBarraVotos( - BuildContext context, { - required String nombre, - required int votos, - required int total, - required bool destacado, - }) { - final color = destacado ? TemaApp.colorAcento : TemaApp.colorNaranja; - final proporcion = total == 0 ? 0.0 : votos / total; - return Padding( - padding: const EdgeInsets.only(bottom: 10), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - nombre, - style: TextStyle( - fontWeight: destacado ? FontWeight.bold : FontWeight.w600, - ), - ), - ), - Text( - '$votos', - style: TextStyle(color: color, fontWeight: FontWeight.bold), - ), - ], - ), - const SizedBox(height: 6), - ClipRRect( - borderRadius: BorderRadius.circular(999), - child: LinearProgressIndicator( - value: proporcion.clamp(0.0, 1.0).toDouble(), - minHeight: 10, - backgroundColor: TemaApp.colorSuperficie, - valueColor: AlwaysStoppedAnimation(color), - ), - ), - ], - ), - ); - } Widget _construirBotones(BuildContext context, EstadoJuego estado) { final l10n = AppLocalizations.of(context)!; final partida = estado.partida; diff --git a/lib/pantallas/pantalla_resultado_online.dart b/lib/pantallas/pantalla_resultado_online.dart index a90df4b..39ea05a 100644 --- a/lib/pantallas/pantalla_resultado_online.dart +++ b/lib/pantallas/pantalla_resultado_online.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:farolero/l10n/generated/app_localizations.dart'; 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/componentes_resultado_farolero.dart'; import '../tema/tema_app.dart'; import 'pantalla_debate_cliente.dart'; import 'pantalla_fin_partida_online.dart'; @@ -171,11 +171,18 @@ class _PantallaResultadoOnlineState extends State { ), body: FondoFarolero( intenso: true, - child: Padding( - padding: const EdgeInsets.all(24), - child: resultado == null - ? _buildEsperaAdivinanza(context, l10n) - : _buildResultado(context, l10n, resultado), + child: SafeArea( + top: false, + child: SingleChildScrollView( + padding: const EdgeInsets.all(24), + child: resultado == null + ? _buildEsperaAdivinanza(context, l10n) + : ResultadoRondaFarolero( + resultado: resultado, + jugadores: _snapshot.jugadores, + mensaje: _snapshot.mensaje, + ), + ), ), ), ); @@ -242,116 +249,5 @@ class _PantallaResultadoOnlineState extends State { ); } - Widget _buildResultado( - BuildContext context, - AppLocalizations l10n, - ResultadoVotacion resultado, - ) { - final conteo = {}; - for (final votadoId in resultado.votos.values) { - conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; - } - final maxVotos = conteo.values.isEmpty - ? 1 - : conteo.values.reduce((a, b) => a > b ? a : b); - final ranking = conteo.entries.toList() - ..sort((a, b) => b.value.compareTo(a.value)); - final jugadores = {for (final jugador in _snapshot.jugadores) jugador.id: jugador}; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ArteGameplayFarolero.resultado(height: 132), - const SizedBox(height: 12), - Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: TemaApp.decoracionPanel( - color: resultado.eraImpostor - ? TemaApp.colorVerde.withValues(alpha: 0.18) - : TemaApp.colorAcento.withValues(alpha: 0.18), - borderColor: - resultado.eraImpostor ? TemaApp.colorVerde : TemaApp.colorAcento, - ), - child: Column( - children: [ - Text( - resultado.eliminadoNombre, - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - resultado.eraImpostor ? l10n.wasImpostor : l10n.wasInnocent, - style: TextStyle( - color: resultado.eraImpostor - ? TemaApp.colorVerde - : TemaApp.colorAcento, - fontWeight: FontWeight.bold, - ), - ), - if (_snapshot.mensaje != null) ...[ - const SizedBox(height: 12), - Text(_snapshot.mensaje!, textAlign: TextAlign.center), - ], - ], - ), - ), - const SizedBox(height: 20), - Text(l10n.votesThisRound, style: Theme.of(context).textTheme.titleLarge), - const SizedBox(height: 12), - Expanded( - child: ListView( - children: [ - ...ranking.map((entry) { - final jugador = jugadores[entry.key]; - final destacado = entry.key == resultado.eliminadoId; - final color = destacado ? TemaApp.colorAcento : TemaApp.colorNaranja; - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded(child: Text(jugador?.nombre ?? '?')), - Text( - '${entry.value}', - style: TextStyle( - color: color, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 6), - ClipRRect( - borderRadius: BorderRadius.circular(999), - child: LinearProgressIndicator( - value: (entry.value / maxVotos).clamp(0.0, 1.0).toDouble(), - minHeight: 10, - backgroundColor: TemaApp.colorSuperficie, - valueColor: AlwaysStoppedAnimation(color), - ), - ), - ], - ), - ); - }), - const Divider(height: 24), - ...resultado.votos.entries.map((entry) { - final votante = jugadores[entry.key]?.nombre ?? '?'; - final votado = jugadores[entry.value]?.nombre ?? '?'; - return ListTile( - dense: true, - leading: const Icon(Icons.how_to_vote), - title: Text('$votante → $votado'), - ); - }), - ], - ), - ), - ], - ); - } } diff --git a/lib/pantallas/pantalla_votacion_cliente.dart b/lib/pantallas/pantalla_votacion_cliente.dart index 5c18c9d..e25af96 100644 --- a/lib/pantallas/pantalla_votacion_cliente.dart +++ b/lib/pantallas/pantalla_votacion_cliente.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; 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/modelos/partida.dart'; import 'package:farolero/modelos/snapshot_partida_online.dart'; import 'package:farolero/pantallas/pantalla_notas_online.dart'; import 'package:farolero/pantallas/pantalla_revision_palabra.dart'; @@ -36,7 +37,6 @@ class PantallaVotacionCliente extends StatefulWidget { class _PantallaVotacionClienteState extends State { final Map _votosPorVotante = {}; - Map? _resultado; OnMensajeCallback? _listener; ServicioNearby? _nearby; @@ -63,7 +63,31 @@ class _PantallaVotacionClienteState extends State { ), ); } else { - setState(() => _resultado = mensaje.datos); + final votosRaw = mensaje.datos['votos'] as Map? ?? {}; + final snapshot = SnapshotPartidaOnline( + roomId: widget.partidaId, + fase: 'resultado', + ronda: 1, + categoria: '', + jugadores: widget.jugadores, + resultadoActual: ResultadoVotacion( + eliminadoId: mensaje.datos['eliminadoId'] as String? ?? '', + eliminadoNombre: mensaje.datos['eliminadoNombre'] as String? ?? '?', + eraImpostor: mensaje.datos['eraImpostor'] as bool? ?? false, + votos: votosRaw.map( + (key, value) => MapEntry(key.toString(), value.toString()), + ), + ), + ); + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (_) => PantallaResultadoOnline( + snapshot: snapshot, + jugadoresControlados: widget.jugadoresControlados, + pistaCategoria: widget.pistaCategoria, + ), + ), + ); } }; WidgetsBinding.instance.addPostFrameCallback((_) { @@ -87,7 +111,6 @@ class _PantallaVotacionClienteState extends State { @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; - if (_resultado != null) return _buildResultado(context, _resultado!); return Scaffold( backgroundColor: TemaApp.colorFondo, @@ -189,172 +212,6 @@ class _PantallaVotacionClienteState extends State { widget.jugadoresControlados.isNotEmpty; } - Widget _buildResultado(BuildContext context, Map resultado) { - final l10n = AppLocalizations.of(context)!; - final eliminadoId = resultado['eliminadoId'] as String?; - final eliminadoNombre = resultado['eliminadoNombre'] as String? ?? '?'; - final eraImpostor = resultado['eraImpostor'] as bool? ?? false; - final votosRaw = resultado['votos'] as Map? ?? {}; - final votos = votosRaw.map( - (key, value) => MapEntry(key.toString(), value.toString()), - ); - final jugadores = {for (final jugador in widget.jugadores) jugador.id: jugador}; - final conteo = {}; - for (final votadoId in votos.values) { - conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; - } - final maxVotos = conteo.values.isEmpty - ? 1 - : conteo.values.reduce((a, b) => a > b ? a : b); - final ranking = conteo.entries.toList() - ..sort((a, b) => b.value.compareTo(a.value)); - - return Scaffold( - backgroundColor: TemaApp.colorFondo, - appBar: AppBar( - title: Text(l10n.result), - automaticallyImplyLeading: false, - backgroundColor: Colors.transparent, - elevation: 0, - actions: [ - IconButton( - tooltip: l10n.seeYourWord, - icon: const Icon(Icons.visibility), - onPressed: widget.jugadoresControlados.isEmpty - ? null - : () => mostrarRevisionPalabraOnline( - context: context, - jugadoresControlados: widget.jugadoresControlados, - pistaCategoria: widget.pistaCategoria, - ), - ), - IconButton( - tooltip: l10n.notesTitle, - icon: const Icon(Icons.edit_note), - onPressed: _puedeAbrirNotas - ? () => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => PantallaNotasOnline( - partidaId: widget.partidaId!, - jugadores: widget.jugadores, - autoresControlados: widget.jugadoresControlados, - ), - ), - ) - : null, - ), - ], - ), - body: FondoFarolero( - intenso: true, - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ArteGameplayFarolero.fase(height: 118), - const SizedBox(height: 10), - Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: eraImpostor - ? TemaApp.colorVerde.withValues(alpha: 0.18) - : TemaApp.colorAcento.withValues(alpha: 0.18), - borderRadius: BorderRadius.circular(18), - border: Border.all( - color: eraImpostor ? TemaApp.colorVerde : TemaApp.colorAcento, - ), - ), - child: Column( - children: [ - Text( - eliminadoNombre, - style: Theme.of(context).textTheme.headlineMedium, - textAlign: TextAlign.center, - ), - const SizedBox(height: 8), - Text( - eraImpostor ? l10n.wasImpostor : l10n.wasInnocent, - style: TextStyle( - color: eraImpostor - ? TemaApp.colorVerde - : TemaApp.colorAcento, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - const SizedBox(height: 20), - Text( - 'Detalle de votos', - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 12), - Expanded( - child: ListView( - children: [ - ...ranking.map((entry) { - final jugador = jugadores[entry.key]; - final destacado = entry.key == eliminadoId; - final color = destacado - ? TemaApp.colorAcento - : TemaApp.colorNaranja; - return Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded(child: Text(jugador?.nombre ?? '?')), - Text( - '${entry.value}', - style: TextStyle( - color: color, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 6), - ClipRRect( - borderRadius: BorderRadius.circular(999), - child: LinearProgressIndicator( - value: (entry.value / maxVotos) - .clamp(0.0, 1.0) - .toDouble(), - minHeight: 10, - backgroundColor: TemaApp.colorSuperficie, - valueColor: AlwaysStoppedAnimation(color), - ), - ), - ], - ), - ); - }), - const Divider(height: 24), - ...votos.entries.map((entry) { - final votante = jugadores[entry.key]?.nombre ?? '?'; - final votado = jugadores[entry.value]?.nombre ?? '?'; - return ListTile( - dense: true, - leading: const Icon(Icons.how_to_vote), - title: Text('$votante → $votado'), - ); - }), - ], - ), - ), - ], - ), - ), - ), - ); - } - Widget _buildSelectorLegacy() { return ListView.builder( itemCount: widget.jugadores.length, diff --git a/lib/tema/componentes_resultado_farolero.dart b/lib/tema/componentes_resultado_farolero.dart new file mode 100644 index 0000000..2eb44d6 --- /dev/null +++ b/lib/tema/componentes_resultado_farolero.dart @@ -0,0 +1,1205 @@ +import 'dart:math' as math; + +import 'package:farolero/l10n/generated/app_localizations.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; + +import '../modelos/gamificacion_usuario.dart'; +import '../modelos/jugador.dart'; +import '../modelos/partida.dart'; +import 'tema_app.dart'; + +class ResultadoRondaFarolero extends StatelessWidget { + final ResultadoVotacion resultado; + final List jugadores; + final String? mensaje; + final Widget? acciones; + + const ResultadoRondaFarolero({ + super.key, + required this.resultado, + required this.jugadores, + this.mensaje, + this.acciones, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final color = + resultado.eraImpostor ? TemaApp.colorVerde : TemaApp.colorAcento; + + return Column( + children: [ + const SizedBox(height: 8), + Stack( + alignment: Alignment.center, + children: [ + SizedBox( + height: 188, + width: double.infinity, + child: CustomPaint( + painter: HeroCinematicoResultadoPainter(color: color), + ), + ), + Image.asset( + 'assets/ui/generated/meta/result_verdict_art.webp', + height: 150, + fit: BoxFit.contain, + ).animate().scale( + duration: 520.ms, + curve: Curves.elasticOut, + begin: const Offset(0.78, 0.78), + ), + ], + ), + const SizedBox(height: 10), + Text( + resultado.eliminadoNombre, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + fontSize: 36, + fontWeight: FontWeight.w900, + shadows: [ + Shadow(color: color.withValues(alpha: 0.55), blurRadius: 18), + ], + ), + ), + const SizedBox(height: 12), + _BadgeResultadoRonda( + texto: resultado.eraImpostor ? l10n.wasImpostor : l10n.wasInnocent, + color: color, + ), + if (mensaje != null && mensaje!.trim().isNotEmpty) ...[ + const SizedBox(height: 14), + PanelResultadoFarolero( + padding: const EdgeInsets.all(16), + child: Text( + mensaje!, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + ], + const SizedBox(height: 22), + DetalleVotosFarolero(resultado: resultado, jugadores: jugadores), + if (acciones != null) ...[ + const SizedBox(height: 24), + acciones!, + ], + ], + ); + } +} + +class DetalleVotosFarolero extends StatelessWidget { + final ResultadoVotacion resultado; + final List jugadores; + + const DetalleVotosFarolero({ + super.key, + required this.resultado, + required this.jugadores, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final jugadoresPorId = {for (final jugador in jugadores) jugador.id: jugador}; + final conteo = {}; + for (final votadoId in resultado.votos.values) { + conteo[votadoId] = (conteo[votadoId] ?? 0) + 1; + } + final maxVotos = conteo.values.isEmpty + ? 1 + : conteo.values.reduce((a, b) => a > b ? a : b); + final ranking = conteo.entries.toList() + ..sort((a, b) => b.value.compareTo(a.value)); + + return PanelResultadoFarolero( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.bar_chart, color: TemaApp.colorNaranja), + const SizedBox(width: 8), + Expanded( + child: Text( + l10n.votesThisRound, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w900, + ), + ), + ), + ], + ), + const SizedBox(height: 14), + ...ranking.map((entry) { + final jugador = jugadoresPorId[entry.key]; + return BarraVotosFarolero( + nombre: jugador?.nombre ?? '?', + votos: entry.value, + total: maxVotos, + destacado: entry.key == resultado.eliminadoId, + ); + }), + const Divider(height: 26), + ...resultado.votos.entries.map((entry) { + final votante = jugadoresPorId[entry.key]?.nombre ?? '?'; + final votado = jugadoresPorId[entry.value]?.nombre ?? '?'; + final fueAlEliminado = entry.value == resultado.eliminadoId; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + children: [ + Icon( + fueAlEliminado ? Icons.how_to_vote : Icons.arrow_forward, + size: 18, + color: fueAlEliminado + ? TemaApp.colorAcento + : TemaApp.colorTextoSecundario, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + '$votante → $votado', + style: TextStyle( + color: fueAlEliminado + ? TemaApp.colorTexto + : TemaApp.colorTextoSecundario, + fontWeight: fueAlEliminado + ? FontWeight.bold + : FontWeight.normal, + ), + ), + ), + ], + ), + ); + }), + ], + ), + ).animate().fadeIn(delay: 160.ms).slideY(begin: 0.08); + } +} + +class BarraVotosFarolero extends StatelessWidget { + final String nombre; + final int votos; + final int total; + final bool destacado; + + const BarraVotosFarolero({ + super.key, + required this.nombre, + required this.votos, + required this.total, + required this.destacado, + }); + + @override + Widget build(BuildContext context) { + final color = destacado ? TemaApp.colorAcento : TemaApp.colorNaranja; + final proporcion = total == 0 ? 0.0 : votos / total; + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + nombre, + style: TextStyle( + fontWeight: destacado ? FontWeight.bold : FontWeight.w600, + ), + ), + ), + Text( + '$votos', + style: TextStyle(color: color, fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 6), + ClipRRect( + borderRadius: BorderRadius.circular(999), + child: LinearProgressIndicator( + value: proporcion.clamp(0.0, 1.0).toDouble(), + minHeight: 12, + backgroundColor: Colors.black.withValues(alpha: 0.42), + valueColor: AlwaysStoppedAnimation(color), + ), + ), + ], + ), + ); + } +} + +class HeroFinalPartidaFarolero extends StatelessWidget { + final String encabezado; + final String titulo; + final IconData icono; + final Color color; + + const HeroFinalPartidaFarolero({ + super.key, + required this.encabezado, + required this.titulo, + required this.icono, + required this.color, + }); + + @override + Widget build(BuildContext context) { + final apertura = String.fromCharCode(0x00A1); + final tituloLimpio = titulo + .replaceAll(apertura, '') + .replaceAll('!', '') + .trim() + .toUpperCase(); + + return Stack( + alignment: Alignment.center, + children: [ + SizedBox( + height: 520, + width: double.infinity, + child: CustomPaint(painter: HeroCinematicoResultadoPainter(color: color)), + ), + Column( + children: [ + Text( + encabezado, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: TemaApp.colorDorado, + fontSize: 38, + fontWeight: FontWeight.w900, + shadows: [ + Shadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.55), + blurRadius: 18, + ), + ], + ), + ).animate().fadeIn(duration: 260.ms).slideY(begin: -0.18), + const SizedBox(height: 58), + Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/ui/generated/final_rewards/cinematic_burst.webp', + width: 260, + height: 260, + fit: BoxFit.contain, + ), + Container( + width: 154, + height: 154, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: RadialGradient( + colors: [ + color.withValues(alpha: 0.36), + const Color(0xFF111116), + Colors.black.withValues(alpha: 0.88), + ], + ), + border: Border.all(color: TemaApp.colorDorado, width: 4), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.75), + blurRadius: 52, + spreadRadius: 7, + ), + BoxShadow( + color: color.withValues(alpha: 0.62), + blurRadius: 36, + spreadRadius: 2, + ), + ], + ), + child: IconoResultadoPremiumFarolero(icono: icono), + ), + ], + ) + .animate() + .scale( + begin: const Offset(0.55, 0.55), + duration: 520.ms, + curve: Curves.elasticOut, + ) + .shimmer(delay: 700.ms, duration: 1500.ms), + const SizedBox(height: 14), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Text( + '$apertura$tituloLimpio!', + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: color, + fontSize: 32, + fontWeight: FontWeight.w900, + letterSpacing: 1.0, + shadows: [ + Shadow( + color: color.withValues(alpha: 0.90), + blurRadius: 24, + ), + ], + ), + ), + ).animate().fadeIn(delay: 180.ms).slideY(begin: 0.25), + ], + ), + ], + ); + } +} + +class IconoResultadoPremiumFarolero extends StatelessWidget { + final IconData icono; + + const IconoResultadoPremiumFarolero({super.key, required this.icono}); + + @override + Widget build(BuildContext context) { + if (icono != Icons.theater_comedy) { + return Icon(icono, size: 82, color: TemaApp.colorDorado); + } + return Stack( + alignment: Alignment.center, + children: [ + Transform.translate( + offset: const Offset(-18, 12), + child: Transform.rotate( + angle: -0.10, + child: Icon( + Icons.mood, + size: 66, + color: TemaApp.colorDorado.withValues(alpha: 0.98), + ), + ), + ), + Transform.translate( + offset: const Offset(20, -13), + child: Transform.rotate( + angle: 0.12, + child: Icon( + Icons.sentiment_dissatisfied, + size: 70, + color: TemaApp.colorDorado.withValues(alpha: 0.98), + ), + ), + ), + ], + ); + } +} + +class TarjetaProgresoGamificacionPremium extends StatelessWidget { + final ProgresoGamificacionUsuario progreso; + + const TarjetaProgresoGamificacionPremium({ + super.key, + required this.progreso, + }); + + @override + Widget build(BuildContext context) { + final nuevas = progreso.nuevasMedallas; + final antes = progreso.antes.fuego.clamp(0, 100); + final despues = progreso.despues.fuego.clamp(0, 100); + final l10n = AppLocalizations.of(context)!; + + return PanelResultadoFarolero( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const _IconoFuegoRecompensa(), + const SizedBox(width: 14), + Expanded( + child: Text( + l10n.matchRewards.toUpperCase(), + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: TemaApp.colorDorado, + fontSize: 20, + fontWeight: FontWeight.w900, + letterSpacing: 1.1, + height: 1.05, + ), + ), + ), + _DeltaFuegoPremium(valor: progreso.incrementoFuego), + ], + ), + const SizedBox(height: 16), + BarraFuegoPremiumFarolero(antes: antes, despues: despues), + const SizedBox(height: 20), + if (nuevas.isEmpty) + Text( + l10n.noNewMedalsKeepFire, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: TemaApp.colorTextoSecundario, + height: 1.35, + ), + ) + else ...[ + Text( + l10n.newMedals.toUpperCase(), + style: Theme.of(context).textTheme.labelLarge?.copyWith( + color: TemaApp.colorDorado, + fontWeight: FontWeight.w800, + letterSpacing: 1.5, + ), + ), + const SizedBox(height: 12), + Wrap( + spacing: 12, + runSpacing: 12, + alignment: WrapAlignment.center, + children: [ + for (final id in nuevas) MedallaDesbloqueadaPremium(id: id), + ], + ), + ], + ], + ), + ).animate().fadeIn(delay: 250.ms).slideY(begin: 0.12); + } +} + +class TarjetaRecompensaCargandoPremium extends StatelessWidget { + const TarjetaRecompensaCargandoPremium({super.key}); + + @override + Widget build(BuildContext context) { + return PanelResultadoFarolero( + child: Row( + children: [ + const SizedBox( + width: 28, + height: 28, + child: CircularProgressIndicator( + strokeWidth: 3, + color: TemaApp.colorNaranja, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + AppLocalizations.of(context)!.calculatingRewards, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: TemaApp.colorDorado, + ), + ), + ), + ], + ), + ); + } +} + +class PanelResultadoFarolero extends StatelessWidget { + final Widget child; + final EdgeInsetsGeometry padding; + + const PanelResultadoFarolero({ + super.key, + required this.child, + this.padding = const EdgeInsets.all(24), + }); + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(32), + child: Container( + width: double.infinity, + padding: padding, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + const Color(0xFF101A27).withValues(alpha: 0.96), + const Color(0xFF1B0E24).withValues(alpha: 0.94), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + borderRadius: BorderRadius.circular(32), + border: Border.all(color: TemaApp.colorDorado.withValues(alpha: 0.58)), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.25), + blurRadius: 44, + offset: const Offset(0, 22), + ), + BoxShadow( + color: Colors.black.withValues(alpha: 0.50), + blurRadius: 28, + offset: const Offset(0, 14), + ), + ], + ), + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.circular(32), + gradient: LinearGradient( + colors: [ + Colors.white.withValues(alpha: 0.06), + Colors.transparent, + TemaApp.colorDorado.withValues(alpha: 0.04), + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ), + child: child, + ), + ); + } +} + +class BarraFuegoPremiumFarolero extends StatelessWidget { + final int antes; + final int despues; + + const BarraFuegoPremiumFarolero({ + super.key, + required this.antes, + required this.despues, + }); + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder( + tween: Tween(begin: antes / 100, end: despues / 100), + duration: const Duration(milliseconds: 1300), + curve: Curves.easeOutCubic, + builder: (context, value, child) { + final normalizado = value.clamp(0.0, 1.0).toDouble(); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.local_fire_department, + color: TemaApp.colorNaranja, size: 18), + const SizedBox(width: 6), + Text( + '${AppLocalizations.of(context)!.fireLabel} ' + '${(normalizado * 100).round()}%', + style: Theme.of(context).textTheme.titleMedium?.copyWith( + color: TemaApp.colorDorado, + fontWeight: FontWeight.w900, + ), + ), + ], + ), + const SizedBox(height: 12), + Container( + height: 38, + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.72), + borderRadius: BorderRadius.circular(999), + border: Border.all( + color: TemaApp.colorDorado.withValues(alpha: 0.38), + ), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.24), + blurRadius: 18, + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(999), + child: Stack( + children: [ + FractionallySizedBox( + widthFactor: normalizado, + child: Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [ + Color(0xFFE53935), + TemaApp.colorNaranja, + TemaApp.colorDorado, + Color(0xFFFFECBE), + ], + ), + ), + ), + ), + Positioned.fill( + child: DecoratedBox( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Colors.white.withValues(alpha: 0.32), + Colors.transparent, + Colors.black.withValues(alpha: 0.18), + ], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + }, + ); + } +} + +class MedallaDesbloqueadaPremium extends StatelessWidget { + final String id; + + const MedallaDesbloqueadaPremium({super.key, required this.id}); + + @override + Widget build(BuildContext context) { + final medalla = EstadisticasPerfilUsuario.catalogoMedallas[id]; + if (medalla == null) return const SizedBox.shrink(); + return Container( + width: 94, + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.28), + borderRadius: BorderRadius.circular(18), + border: Border.all(color: TemaApp.colorDorado.withValues(alpha: 0.48)), + ), + child: Column( + children: [ + Stack( + alignment: Alignment.center, + children: [ + Image.asset( + 'assets/rewards/medal_unlock_burst.webp', + width: 82, + height: 82, + fit: BoxFit.cover, + ), + SizedBox( + width: 70, + height: 70, + child: CustomPaint( + painter: MiniBurstFaroleroPainter(color: TemaApp.colorDorado), + ), + ), + Image.asset( + medalla.assetPath, + width: 58, + height: 58, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) => + Text(medalla.emoji, style: const TextStyle(fontSize: 32)), + ), + ], + ), + const SizedBox(height: 6), + Text( + medalla.nombre, + maxLines: 2, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: TemaApp.colorTexto, + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ) + .animate() + .scale(duration: 520.ms, curve: Curves.elasticOut) + .shimmer(delay: 650.ms, duration: 1200.ms); + } +} + +class TarjetaSecretoPremium extends StatelessWidget { + final String palabra; + final String categoria; + + const TarjetaSecretoPremium({ + super.key, + required this.palabra, + required this.categoria, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + return PanelResultadoFarolero( + child: Column( + children: [ + Text( + l10n.theSecretWordWas, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w900, + color: Colors.white, + ), + ), + const SizedBox(height: 14), + Text( + palabra.toUpperCase(), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineLarge?.copyWith( + color: TemaApp.colorNaranja, + fontSize: 42, + fontWeight: FontWeight.w900, + shadows: [ + Shadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.55), + blurRadius: 18, + ), + ], + ), + ), + const SizedBox(height: 4), + Text( + l10n.categoryLabel(categoria), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: TemaApp.colorTextoSecundario, + ), + ), + ], + ), + ).animate().fadeIn(delay: 380.ms).slideY(begin: 0.1); + } +} + +class TarjetaImpostoresPremium extends StatelessWidget { + final String titulo; + final List impostores; + + const TarjetaImpostoresPremium({ + super.key, + required this.titulo, + required this.impostores, + }); + + @override + Widget build(BuildContext context) { + return PanelResultadoFarolero( + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.theater_comedy, color: TemaApp.colorAcento), + const SizedBox(width: 8), + Flexible( + child: Text( + titulo, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.w900, + color: Colors.white, + ), + ), + ), + ], + ), + const SizedBox(height: 10), + ...impostores.map( + (jugador) => Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.theater_comedy, + size: 20, + color: TemaApp.colorAcento, + ), + const SizedBox(width: 8), + Flexible( + child: Text( + jugador.nombre, + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(color: TemaApp.colorAcento), + ), + ), + if (jugador.eliminado) ...[ + const SizedBox(width: 8), + const Icon( + Icons.close, + size: 16, + color: TemaApp.colorTextoSecundario, + ), + ], + ], + ), + ), + ), + ], + ), + ).animate().fadeIn(delay: 450.ms); + } +} + +class TarjetaHistorialVotosPremium extends StatelessWidget { + final List historial; + final List jugadores; + + const TarjetaHistorialVotosPremium({ + super.key, + required this.historial, + required this.jugadores, + }); + + @override + Widget build(BuildContext context) { + final l10n = AppLocalizations.of(context)!; + final jugadoresPorId = {for (final jugador in jugadores) jugador.id: jugador}; + return PanelResultadoFarolero( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.votingHistory, + style: Theme.of(context).textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w900, + ), + ), + const SizedBox(height: 12), + ...historial.asMap().entries.map((entrada) { + final ronda = entrada.key + 1; + final resultado = entrada.value; + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.roundElimination(ronda, resultado.eliminadoNombre), + style: TextStyle( + fontWeight: FontWeight.bold, + color: resultado.eraImpostor + ? TemaApp.colorVerde + : TemaApp.colorAcento, + ), + ), + ...resultado.votos.entries.map((voto) { + final votante = jugadoresPorId[voto.key]?.nombre ?? '?'; + final votado = jugadoresPorId[voto.value]?.nombre ?? '?'; + return Text( + ' $votante → $votado', + style: Theme.of(context).textTheme.bodyMedium, + ); + }), + ], + ), + ); + }), + ], + ), + ); + } +} + +class EscenarioFinPartidaFaroleroPainter extends CustomPainter { + final Color color; + + const EscenarioFinPartidaFaroleroPainter({required this.color}); + + @override + void paint(Canvas canvas, Size size) { + final paint = Paint()..isAntiAlias = true; + + paint.shader = const LinearGradient( + colors: [Color(0xFF050B14), Color(0xFF091322), Color(0xFF16091F)], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + ).createShader(Offset.zero & size); + canvas.drawRect(Offset.zero & size, paint); + + paint.shader = RadialGradient( + colors: [ + color.withValues(alpha: 0.28), + TemaApp.colorNaranja.withValues(alpha: 0.12), + Colors.transparent, + ], + ).createShader( + Rect.fromCircle( + center: Offset(size.width * 0.5, size.height * 0.30), + radius: size.width * 0.86, + ), + ); + canvas.drawCircle( + Offset(size.width * 0.5, size.height * 0.30), + size.width * 0.86, + paint, + ); + + paint.shader = null; + paint.color = Colors.black.withValues(alpha: 0.36); + final base = size.height * 0.78; + final path = Path() + ..moveTo(0, size.height) + ..lineTo(0, base) + ..lineTo(size.width * 0.16, base - 42) + ..lineTo(size.width * 0.30, base - 12) + ..lineTo(size.width * 0.48, base - 60) + ..lineTo(size.width * 0.66, base - 26) + ..lineTo(size.width * 0.82, base - 72) + ..lineTo(size.width, base - 34) + ..lineTo(size.width, size.height) + ..close(); + canvas.drawPath(path, paint); + + for (var i = 0; i < 52; i++) { + final x = (i * 71 % math.max(size.width, 1)).toDouble(); + final y = (i * 137 % math.max(size.height, 1)).toDouble(); + final palette = [ + TemaApp.colorDorado, + TemaApp.colorNaranja, + TemaApp.colorAcento, + const Color(0xFFFFF1C7), + ]; + final confettiPaint = Paint() + ..isAntiAlias = true + ..color = palette[i % palette.length].withValues(alpha: 0.72); + canvas.save(); + canvas.translate(x, y); + canvas.rotate((i % 9 - 4) * 0.22); + canvas.drawRRect( + RRect.fromRectAndRadius( + Rect.fromCenter( + center: Offset.zero, + width: 8 + (i % 4) * 6, + height: 4 + (i % 3) * 3, + ), + const Radius.circular(1.5), + ), + confettiPaint, + ); + canvas.restore(); + } + } + + @override + bool shouldRepaint(covariant EscenarioFinPartidaFaroleroPainter oldDelegate) { + return oldDelegate.color != color; + } +} + +class HeroCinematicoResultadoPainter extends CustomPainter { + final Color color; + + const HeroCinematicoResultadoPainter({required this.color}); + + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height * 0.54); + final paint = Paint()..isAntiAlias = true; + + paint.shader = RadialGradient( + colors: [ + TemaApp.colorNaranja.withValues(alpha: 0.30), + color.withValues(alpha: 0.14), + Colors.transparent, + ], + ).createShader(Rect.fromCircle(center: center, radius: size.width * 0.54)); + canvas.drawCircle(center, size.width * 0.54, paint); + paint.shader = null; + + for (var i = 0; i < 28; i++) { + final angle = (math.pi * 2 / 28) * i; + final inner = Offset( + center.dx + math.cos(angle - 0.035) * 52, + center.dy + math.sin(angle - 0.035) * 52, + ); + final outer = Offset( + center.dx + math.cos(angle) * size.width * 0.58, + center.dy + math.sin(angle) * size.width * 0.58, + ); + final inner2 = Offset( + center.dx + math.cos(angle + 0.035) * 52, + center.dy + math.sin(angle + 0.035) * 52, + ); + paint.color = (i.isEven ? TemaApp.colorDorado : color) + .withValues(alpha: i.isEven ? 0.16 : 0.09); + canvas.drawPath( + Path() + ..moveTo(inner.dx, inner.dy) + ..lineTo(outer.dx, outer.dy) + ..lineTo(inner2.dx, inner2.dy) + ..close(), + paint, + ); + } + } + + @override + bool shouldRepaint(covariant HeroCinematicoResultadoPainter oldDelegate) { + return oldDelegate.color != color; + } +} + +class MiniBurstFaroleroPainter extends CustomPainter { + final Color color; + + const MiniBurstFaroleroPainter({required this.color}); + + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height / 2); + final paint = Paint() + ..isAntiAlias = true + ..color = color.withValues(alpha: 0.22) + ..strokeCap = StrokeCap.round; + for (var i = 0; i < 16; i++) { + final angle = math.pi * 2 * i / 16; + paint.strokeWidth = i.isEven ? 3 : 1.4; + canvas.drawLine( + center, + Offset( + center.dx + math.cos(angle) * size.width * 0.48, + center.dy + math.sin(angle) * size.height * 0.48, + ), + paint, + ); + } + paint + ..style = PaintingStyle.fill + ..shader = RadialGradient( + colors: [color.withValues(alpha: 0.38), Colors.transparent], + ).createShader(Rect.fromCircle(center: center, radius: size.width * 0.42)); + canvas.drawCircle(center, size.width * 0.42, paint); + } + + @override + bool shouldRepaint(covariant MiniBurstFaroleroPainter oldDelegate) { + return oldDelegate.color != color; + } +} + +class _BadgeResultadoRonda extends StatelessWidget { + final String texto; + final Color color; + + const _BadgeResultadoRonda({required this.texto, required this.color}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.24), + borderRadius: BorderRadius.circular(30), + border: Border.all(color: color), + boxShadow: [ + BoxShadow(color: color.withValues(alpha: 0.28), blurRadius: 18), + ], + ), + child: Text( + texto, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: color, + ), + ), + ).animate().scale(delay: 180.ms, duration: 380.ms); + } +} + +class _IconoFuegoRecompensa extends StatelessWidget { + const _IconoFuegoRecompensa(); + + @override + Widget build(BuildContext context) { + return Container( + width: 52, + height: 52, + decoration: BoxDecoration( + shape: BoxShape.circle, + gradient: LinearGradient( + colors: [ + TemaApp.colorNaranja.withValues(alpha: 0.95), + TemaApp.colorDorado.withValues(alpha: 0.78), + ], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.42), + blurRadius: 22, + ), + ], + ), + child: const Icon( + Icons.local_fire_department, + color: Color(0xFF1B1010), + size: 30, + ), + ); + } +} + +class _DeltaFuegoPremium extends StatelessWidget { + final int valor; + + const _DeltaFuegoPremium({required this.valor}); + + @override + Widget build(BuildContext context) { + final texto = valor >= 0 ? '+$valor' : '$valor'; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + TemaApp.colorNaranja.withValues(alpha: 0.28), + TemaApp.colorDorado.withValues(alpha: 0.14), + ], + ), + borderRadius: BorderRadius.circular(999), + border: Border.all(color: TemaApp.colorNaranja), + boxShadow: [ + BoxShadow( + color: TemaApp.colorNaranja.withValues(alpha: 0.28), + blurRadius: 18, + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon( + Icons.local_fire_department, + color: TemaApp.colorNaranja, + size: 24, + ), + const SizedBox(width: 4), + Text( + texto, + style: Theme.of(context).textTheme.headlineSmall?.copyWith( + color: TemaApp.colorDorado, + fontWeight: FontWeight.w900, + ), + ), + ], + ), + ).animate().scale(delay: 520.ms, duration: 420.ms, curve: Curves.elasticOut); + } +}