From a6d3c1e42f128eb59440472f20291fcfabbc2e58 Mon Sep 17 00:00:00 2001 From: JiWoong Sul Date: Wed, 31 Dec 2025 18:52:18 +0900 Subject: [PATCH] =?UTF-8?q?feat(game):=20=EA=B2=8C=EC=9E=84=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AC=EC=96=B4=20=EC=8B=9C=20VictoryOverlay=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - VictoryOverlay 위젯 신규 생성 - GameSessionController에 isComplete 상태 추가 - 레벨 100 도달 시 승리 오버레이 표시 - 승리 후 명예의 전당 화면으로 이동 --- lib/src/core/engine/progress_service.dart | 6 +- lib/src/features/game/game_play_screen.dart | 81 +-- .../game/game_session_controller.dart | 41 +- .../game/widgets/victory_overlay.dart | 572 ++++++++++++++++++ 4 files changed, 635 insertions(+), 65 deletions(-) create mode 100644 lib/src/features/game/widgets/victory_overlay.dart diff --git a/lib/src/core/engine/progress_service.dart b/lib/src/core/engine/progress_service.dart index 786a004..4b2bce0 100644 --- a/lib/src/core/engine/progress_service.dart +++ b/lib/src/core/engine/progress_service.dart @@ -627,9 +627,9 @@ class ProgressService { /// Advances plot to next act and applies any act-level rewards. /// Returns gameComplete=true if Act V was completed (game ends). ({GameState state, bool gameComplete}) completeAct(GameState state) { - // Act V 완료 시 (plotStageCount == 5) 게임 클리어 - // plotStageCount: 0=Prologue, 1=Act I, 2=Act II, 3=Act III, 4=Act IV, 5=Act V - if (state.progress.plotStageCount >= 5) { + // Act V 완료 시 (plotStageCount == 6) 게임 클리어 + // plotStageCount: 1=Prologue, 2=Act I, 3=Act II, 4=Act III, 5=Act IV, 6=Act V + if (state.progress.plotStageCount >= 6) { // Act V 완료 - 게임 클리어! // 히스토리만 업데이트하고 새 Act는 생성하지 않음 final updatedPlotHistory = [ diff --git a/lib/src/features/game/game_play_screen.dart b/lib/src/features/game/game_play_screen.dart index 7687b0d..448359e 100644 --- a/lib/src/features/game/game_play_screen.dart +++ b/lib/src/features/game/game_play_screen.dart @@ -12,18 +12,16 @@ import 'package:asciineverdie/src/core/animation/ascii_animation_type.dart'; import 'package:asciineverdie/src/core/engine/story_service.dart'; import 'package:asciineverdie/src/core/model/combat_event.dart'; import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; -import 'package:asciineverdie/src/core/model/combat_stats.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; -import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; import 'package:asciineverdie/src/core/model/skill.dart'; import 'package:asciineverdie/src/core/notification/notification_service.dart'; -import 'package:asciineverdie/src/core/storage/hall_of_fame_storage.dart'; import 'package:asciineverdie/src/core/util/pq_logic.dart' as pq_logic; import 'package:asciineverdie/src/features/game/game_session_controller.dart'; import 'package:asciineverdie/src/features/hall_of_fame/hall_of_fame_screen.dart'; import 'package:asciineverdie/src/features/game/widgets/cinematic_view.dart'; import 'package:asciineverdie/src/features/game/widgets/combat_log.dart'; import 'package:asciineverdie/src/features/game/widgets/death_overlay.dart'; +import 'package:asciineverdie/src/features/game/widgets/victory_overlay.dart'; import 'package:asciineverdie/src/features/game/widgets/hp_mp_bar.dart'; import 'package:asciineverdie/src/features/game/widgets/notification_overlay.dart'; import 'package:asciineverdie/src/features/game/widgets/stats_panel.dart'; @@ -138,14 +136,9 @@ class _GamePlayScreenState extends State if (newAct != _lastAct && !_showingCinematic) { _lastAct = newAct; - // Phase 10: 엔딩 도달 시 클리어 처리 (시네마틱 대신 클리어 다이얼로그) - // 다음 프레임에서 실행 (리스너 콜백 중 showDialog 문제 방지) - if (newAct == StoryAct.ending && state.traits.level >= 100) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) _handleGameClear(state); - }); - } else { - // 일반 Act 전환 시 시네마틱 표시 + // 엔딩은 controller.isComplete 상태에서 VictoryOverlay로 처리 + // 일반 Act 전환 시에만 시네마틱 표시 + if (newAct != StoryAct.ending) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) _showCinematicForAct(newAct); }); @@ -419,49 +412,13 @@ class _GamePlayScreenState extends State _showingCinematic = false; } - /// Phase 10: 게임 클리어 처리 (Handle Game Clear) - Future _handleGameClear(GameState state) async { - // 게임 일시 정지 - await widget.controller.pause(saveOnStop: true); - - // 최종 전투 스탯 계산 - final combatStats = CombatStats.fromStats( - stats: state.stats, - equipment: state.equipment, - level: state.traits.level, + /// VictoryOverlay 완료 후 명예의 전당 화면으로 이동 + void _handleVictoryComplete() { + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: (context) => const HallOfFameScreen(), + ), ); - - // 명예의 전당 엔트리 생성 - final entry = HallOfFameEntry.fromGameState( - state: state, - totalDeaths: state.progress.deathCount, - monstersKilled: state.progress.monstersKilled, - combatStats: combatStats, - ); - - // 명예의 전당에 저장 - final storage = HallOfFameStorage(); - await storage.addEntry(entry); - - // 클리어 다이얼로그 표시 - if (mounted) { - await showGameClearDialog( - context, - entry: entry, - onNewGame: () { - // 프론트 화면으로 돌아가기 - Navigator.of(context).popUntil((route) => route.isFirst); - }, - onViewHallOfFame: () { - // 명예의 전당 화면으로 이동 - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) => const HallOfFameScreen(), - ), - ); - }, - ); - } } void _resetSpecialAnimationAfterFrame() { @@ -857,6 +814,15 @@ class _GamePlayScreenState extends State traits: state.traits, onResurrect: _handleResurrect, ), + // 승리 오버레이 (게임 클리어) + if (widget.controller.isComplete) + VictoryOverlay( + traits: state.traits, + stats: state.stats, + progress: state.progress, + elapsedMs: state.skillSystem.elapsedMs, + onComplete: _handleVictoryComplete, + ), ], ), ), @@ -990,6 +956,15 @@ class _GamePlayScreenState extends State traits: state.traits, onResurrect: _handleResurrect, ), + // 승리 오버레이 (게임 클리어) + if (widget.controller.isComplete) + VictoryOverlay( + traits: state.traits, + stats: state.stats, + progress: state.progress, + elapsedMs: state.skillSystem.elapsedMs, + onComplete: _handleVictoryComplete, + ), ], ), ), diff --git a/lib/src/features/game/game_session_controller.dart b/lib/src/features/game/game_session_controller.dart index d07e2bb..0f3dae6 100644 --- a/lib/src/features/game/game_session_controller.dart +++ b/lib/src/features/game/game_session_controller.dart @@ -4,6 +4,7 @@ import 'package:asciineverdie/src/core/engine/progress_loop.dart'; import 'package:asciineverdie/src/core/engine/progress_service.dart'; import 'package:asciineverdie/src/core/engine/resurrection_service.dart'; import 'package:asciineverdie/src/core/engine/shop_service.dart'; +import 'package:asciineverdie/src/core/model/combat_stats.dart'; import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/game_statistics.dart'; import 'package:asciineverdie/src/core/model/hall_of_fame.dart'; @@ -285,18 +286,40 @@ class GameSessionController extends ChangeNotifier { /// 명예의 전당 등록 Future _registerToHallOfFame() async { - if (_state == null) return; + if (_state == null) { + debugPrint('[HallOfFame] _state is null, skipping registration'); + return; + } - final entry = HallOfFameEntry.fromGameState( - state: _state!, - totalDeaths: _sessionStats.deathCount, - monstersKilled: _state!.progress.monstersKilled, - ); + try { + debugPrint('[HallOfFame] Starting registration...'); - await _hallOfFameStorage.addEntry(entry); + // 최종 전투 스탯 계산 (CombatStats) + final combatStats = CombatStats.fromStats( + stats: _state!.stats, + equipment: _state!.equipment, + level: _state!.traits.level, + ); - // 통계 기록 - await _statisticsStorage.recordGameComplete(); + final entry = HallOfFameEntry.fromGameState( + state: _state!, + totalDeaths: _sessionStats.deathCount, + monstersKilled: _state!.progress.monstersKilled, + combatStats: combatStats, + ); + + debugPrint('[HallOfFame] Entry created: ${entry.characterName} Lv.${entry.level}'); + + final success = await _hallOfFameStorage.addEntry(entry); + debugPrint('[HallOfFame] Storage save result: $success'); + + // 통계 기록 + await _statisticsStorage.recordGameComplete(); + debugPrint('[HallOfFame] Registration complete'); + } catch (e, st) { + debugPrint('[HallOfFame] ERROR: $e'); + debugPrint('[HallOfFame] StackTrace: $st'); + } } /// 플레이어 부활 처리 (상태만 업데이트, 게임 재개는 별도로) diff --git a/lib/src/features/game/widgets/victory_overlay.dart b/lib/src/features/game/widgets/victory_overlay.dart new file mode 100644 index 0000000..6c828e0 --- /dev/null +++ b/lib/src/features/game/widgets/victory_overlay.dart @@ -0,0 +1,572 @@ +import 'package:flutter/material.dart'; + +import 'package:asciineverdie/src/core/l10n/game_data_l10n.dart'; +import 'package:asciineverdie/src/core/model/game_state.dart'; +import 'package:asciineverdie/src/shared/retro_colors.dart'; + +/// 게임 클리어 엔딩 오버레이 (Act V 완료 시) +/// +/// 영화 엔딩 크레딧 스타일로 텍스트가 아래에서 위로 스크롤됨 +class VictoryOverlay extends StatefulWidget { + const VictoryOverlay({ + super.key, + required this.traits, + required this.stats, + required this.progress, + required this.elapsedMs, + required this.onComplete, + }); + + final Traits traits; + final Stats stats; + final ProgressState progress; + final int elapsedMs; + + /// 엔딩 완료 콜백 (명예의 전당으로 이동) + final VoidCallback onComplete; + + @override + State createState() => _VictoryOverlayState(); +} + +class _VictoryOverlayState extends State + with SingleTickerProviderStateMixin { + late AnimationController _scrollController; + late Animation _scrollAnimation; + + // 스크롤 지속 시간 (밀리초) + static const _scrollDurationMs = 25000; // 25초 + + @override + void initState() { + super.initState(); + + _scrollController = AnimationController( + duration: const Duration(milliseconds: _scrollDurationMs), + vsync: this, + ); + + _scrollAnimation = Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation(parent: _scrollController, curve: Curves.linear), + ); + + // 스크롤 완료 시 자동 종료 + _scrollController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + widget.onComplete(); + } + }); + + _scrollController.forward(); + } + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final gold = RetroColors.goldOf(context); + + return GestureDetector( + onTap: widget.onComplete, // 탭으로 스킵 + child: Material( + color: Colors.black, + child: SafeArea( + child: Stack( + children: [ + // 스크롤되는 크레딧 + AnimatedBuilder( + animation: _scrollAnimation, + builder: (context, child) { + return _buildScrollingCredits(context); + }, + ), + + // 스킵 버튼 + Positioned( + top: 16, + right: 16, + child: TextButton( + onPressed: widget.onComplete, + child: Text( + 'SKIP', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: gold.withValues(alpha: 0.5), + ), + ), + ), + ), + + // 하단 탭 힌트 + Positioned( + bottom: 16, + left: 0, + right: 0, + child: Text( + 'TAP TO SKIP', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 8, + color: Colors.white.withValues(alpha: 0.2), + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + ); + } + + Widget _buildScrollingCredits(BuildContext context) { + final screenHeight = MediaQuery.of(context).size.height; + final contentHeight = _estimateContentHeight(); + + // 스크롤 오프셋: 화면 아래에서 시작 → 화면 위로 사라짐 + final totalScrollDistance = screenHeight + contentHeight; + final currentOffset = + screenHeight - (_scrollAnimation.value * totalScrollDistance); + + return SingleChildScrollView( + physics: const NeverScrollableScrollPhysics(), + child: Transform.translate( + offset: Offset(0, currentOffset), + child: _buildCreditContent(context), + ), + ); + } + + double _estimateContentHeight() { + // 대략적인 콘텐츠 높이 추정 (스크롤 계산용) + return 1500.0; + } + + Widget _buildCreditContent(BuildContext context) { + final gold = RetroColors.goldOf(context); + final textPrimary = RetroColors.textPrimaryOf(context); + + return Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 500), + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // ═══════════════════════════════════ + // VICTORY ASCII ART + // ═══════════════════════════════════ + _buildVictoryAsciiArt(gold), + const SizedBox(height: 60), + + // ═══════════════════════════════════ + // CONGRATULATIONS + // ═══════════════════════════════════ + Text( + '★ CONGRATULATIONS ★', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 14, + color: gold, + letterSpacing: 2, + ), + ), + const SizedBox(height: 16), + Text( + 'You have completed the game!', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 14, + color: textPrimary, + ), + ), + const SizedBox(height: 80), + + // ═══════════════════════════════════ + // THE HERO + // ═══════════════════════════════════ + _buildSectionTitle('THE HERO', gold), + const SizedBox(height: 20), + _buildHeroInfo(context), + const SizedBox(height: 80), + + // ═══════════════════════════════════ + // JOURNEY STATISTICS + // ═══════════════════════════════════ + _buildSectionTitle('JOURNEY STATISTICS', gold), + const SizedBox(height: 20), + _buildStatistics(context), + const SizedBox(height: 80), + + // ═══════════════════════════════════ + // FINAL STATS + // ═══════════════════════════════════ + _buildSectionTitle('FINAL STATS', gold), + const SizedBox(height: 20), + _buildFinalStats(context), + const SizedBox(height: 100), + + // ═══════════════════════════════════ + // ASCII TROPHY + // ═══════════════════════════════════ + _buildTrophyAsciiArt(gold), + const SizedBox(height: 60), + + // ═══════════════════════════════════ + // CREDITS + // ═══════════════════════════════════ + _buildSectionTitle('CREDITS', gold), + const SizedBox(height: 20), + _buildCredits(context), + const SizedBox(height: 100), + + // ═══════════════════════════════════ + // THE END + // ═══════════════════════════════════ + _buildTheEnd(gold), + const SizedBox(height: 200), // 여백 (스크롤 끝) + ], + ), + ), + ); + } + + Widget _buildVictoryAsciiArt(Color gold) { + const asciiArt = ''' +╔═══════════════════════════════════════════╗ +║ ║ +║ ██╗ ██╗██╗ ██████╗████████╗ ██████╗ ║ +║ ██║ ██║██║██╔════╝╚══██╔══╝██╔═══██╗ ║ +║ ██║ ██║██║██║ ██║ ██║ ██║ ║ +║ ╚██╗ ██╔╝██║██║ ██║ ██║ ██║ ║ +║ ╚████╔╝ ██║╚██████╗ ██║ ╚██████╔╝ ║ +║ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ║ +║ ║ +║ ██████╗ ██╗ ██╗ ║ +║ ██╔══██╗╚██╗ ██╔╝ ║ +║ ██████╔╝ ╚████╔╝ ║ +║ ██╔══██╗ ╚██╔╝ ║ +║ ██║ ██║ ██║ ║ +║ ╚═╝ ╚═╝ ╚═╝ ║ +║ ║ +╚═══════════════════════════════════════════╝'''; + + return Text( + asciiArt, + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 8, + color: gold, + height: 1.0, + ), + textAlign: TextAlign.center, + ); + } + + Widget _buildSectionTitle(String title, Color gold) { + return Column( + children: [ + Text( + '═══════════════════', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 12, + color: gold.withValues(alpha: 0.5), + ), + ), + const SizedBox(height: 8), + Text( + title, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 12, + color: gold, + letterSpacing: 2, + ), + ), + const SizedBox(height: 8), + Text( + '═══════════════════', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 12, + color: gold.withValues(alpha: 0.5), + ), + ), + ], + ); + } + + Widget _buildHeroInfo(BuildContext context) { + final gold = RetroColors.goldOf(context); + final textPrimary = RetroColors.textPrimaryOf(context); + final textMuted = RetroColors.textMutedOf(context); + + return Column( + children: [ + // 캐릭터 이름 + Text( + widget.traits.name, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 16, + color: gold, + ), + ), + const SizedBox(height: 12), + // 레벨, 종족, 직업 + Text( + 'Level ${widget.traits.level}', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: textPrimary, + ), + ), + const SizedBox(height: 8), + Text( + '${GameDataL10n.getRaceName(context, widget.traits.race)} ' + '${GameDataL10n.getKlassName(context, widget.traits.klass)}', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 12, + color: textMuted, + ), + ), + ], + ); + } + + Widget _buildStatistics(BuildContext context) { + final textPrimary = RetroColors.textPrimaryOf(context); + final exp = RetroColors.expOf(context); + + // 플레이 시간 포맷 + final playTime = Duration(milliseconds: widget.elapsedMs); + final hours = playTime.inHours; + final minutes = playTime.inMinutes % 60; + final seconds = playTime.inSeconds % 60; + final playTimeStr = '${hours.toString().padLeft(2, '0')}:' + '${minutes.toString().padLeft(2, '0')}:' + '${seconds.toString().padLeft(2, '0')}'; + + return Column( + children: [ + _buildStatLine('Monsters Slain', '${widget.progress.monstersKilled}', + textPrimary, exp), + const SizedBox(height: 8), + _buildStatLine('Quests Completed', '${widget.progress.questCount}', + textPrimary, exp), + const SizedBox(height: 8), + _buildStatLine('Play Time', playTimeStr, textPrimary, textPrimary), + ], + ); + } + + Widget _buildStatLine( + String label, String value, Color labelColor, Color valueColor) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '$label: ', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 12, + color: labelColor.withValues(alpha: 0.7), + ), + ), + Text( + value, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: valueColor, + ), + ), + ], + ); + } + + Widget _buildFinalStats(BuildContext context) { + final textPrimary = RetroColors.textPrimaryOf(context); + final stats = widget.stats; + + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildStatBox('STR', '${stats.str}', textPrimary), + const SizedBox(width: 16), + _buildStatBox('CON', '${stats.con}', textPrimary), + const SizedBox(width: 16), + _buildStatBox('DEX', '${stats.dex}', textPrimary), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildStatBox('INT', '${stats.intelligence}', textPrimary), + const SizedBox(width: 16), + _buildStatBox('WIS', '${stats.wis}', textPrimary), + const SizedBox(width: 16), + _buildStatBox('CHA', '${stats.cha}', textPrimary), + ], + ), + ], + ); + } + + Widget _buildStatBox(String label, String value, Color color) { + return Column( + children: [ + Text( + label, + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 10, + color: color.withValues(alpha: 0.5), + ), + ), + const SizedBox(height: 4), + Text( + value, + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 12, + color: color, + ), + ), + ], + ); + } + + Widget _buildTrophyAsciiArt(Color gold) { + const trophy = ''' + ___________ + '._==_==_=_.' + .-\\: /-. + | (|:. |) | + '-|:. |-' + \\::. / + '::. .' + ) ( + _.' '._ + '-------' '''; + + return Text( + trophy, + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 12, + color: gold, + height: 1.0, + ), + textAlign: TextAlign.center, + ); + } + + Widget _buildCredits(BuildContext context) { + final textPrimary = RetroColors.textPrimaryOf(context); + final textMuted = RetroColors.textMutedOf(context); + + return Column( + children: [ + Text( + 'Based on', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 10, + color: textMuted, + ), + ), + const SizedBox(height: 4), + Text( + 'Progress Quest', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: textPrimary, + ), + ), + const SizedBox(height: 4), + Text( + 'by Eric Fredricksen', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 10, + color: textMuted, + ), + ), + const SizedBox(height: 24), + Text( + 'Flutter Port', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 10, + color: textMuted, + ), + ), + const SizedBox(height: 4), + Text( + 'ASCII Never Die', + style: TextStyle( + fontFamily: 'PressStart2P', + fontSize: 10, + color: textPrimary, + ), + ), + ], + ); + } + + Widget _buildTheEnd(Color gold) { + const theEnd = ''' +████████╗██╗ ██╗███████╗ ███████╗███╗ ██╗██████╗ +╚══██╔══╝██║ ██║██╔════╝ ██╔════╝████╗ ██║██╔══██╗ + ██║ ███████║█████╗ █████╗ ██╔██╗ ██║██║ ██║ + ██║ ██╔══██║██╔══╝ ██╔══╝ ██║╚██╗██║██║ ██║ + ██║ ██║ ██║███████╗ ███████╗██║ ╚████║██████╔╝ + ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚══════╝╚═╝ ╚═══╝╚═════╝ '''; + + return Column( + children: [ + Text( + theEnd, + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 6, + color: gold, + height: 1.0, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 24), + Text( + 'Your heroic deeds will be', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 12, + color: gold.withValues(alpha: 0.7), + ), + ), + const SizedBox(height: 4), + Text( + 'remembered in the Hall of Fame', + style: TextStyle( + fontFamily: 'JetBrainsMono', + fontSize: 12, + color: gold.withValues(alpha: 0.7), + ), + ), + ], + ); + } +}