refactor(game): VictoryOverlay에서 크레딧 콘텐츠 분리

- victory_overlay.dart 734줄 → 265줄
- victory_credit_content.dart 475줄 신규 생성
This commit is contained in:
JiWoong Sul
2026-03-19 16:56:12 +09:00
parent 3d5e0af84d
commit d9132a72ea
2 changed files with 490 additions and 484 deletions

View File

@@ -0,0 +1,475 @@
import 'package:flutter/material.dart';
import 'package:asciineverdie/l10n/app_localizations.dart';
import 'package:asciineverdie/src/shared/l10n/game_data_l10n.dart';
import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/shared/retro_colors.dart';
/// 승리 크레딧 콘텐츠(victory credit content) 위젯
///
/// 영웅 정보, 통계, ASCII 아트, 크레딧 텍스트,
/// 명예의 전당 버튼 등 스크롤되는 콘텐츠를 구성한다.
class VictoryCreditContent extends StatelessWidget {
const VictoryCreditContent({
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;
/// 엔딩 완료 콜백(callback) - 명예의 전당으로 이동
final VoidCallback onComplete;
@override
Widget build(BuildContext context) {
final l10n = L10n.of(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: [
_buildVictoryAsciiArt(gold),
const SizedBox(height: 60),
// 축하 메시지(congratulations)
Text(
l10n.endingCongratulations,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 16,
color: gold,
letterSpacing: 2,
),
),
const SizedBox(height: 16),
Text(
l10n.endingGameComplete,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 18,
color: textPrimary,
),
),
const SizedBox(height: 80),
_buildSectionTitle(l10n.endingTheHero, gold),
const SizedBox(height: 20),
_buildHeroInfo(context),
const SizedBox(height: 80),
_buildSectionTitle(l10n.endingJourneyStats, gold),
const SizedBox(height: 20),
_buildStatistics(context),
const SizedBox(height: 80),
_buildSectionTitle(l10n.endingFinalStats, gold),
const SizedBox(height: 20),
_buildFinalStats(context),
const SizedBox(height: 100),
_buildTrophyAsciiArt(gold),
const SizedBox(height: 60),
_buildSectionTitle(l10n.endingCredits, gold),
const SizedBox(height: 20),
_buildCredits(context),
const SizedBox(height: 100),
_buildTheEnd(context, gold),
const SizedBox(height: 60),
_buildHallOfFameButton(context, gold),
const SizedBox(height: 100),
],
),
),
);
}
Widget _buildVictoryAsciiArt(Color gold) {
const asciiArt = '''
╔═══════════════════════════════════════════╗
║ ║
║ ██╗ ██╗██╗ ██████╗████████╗ ██████╗ ║
║ ██║ ██║██║██╔════╝╚══██╔══╝██╔═══██╗ ║
║ ██║ ██║██║██║ ██║ ██║ ██║ ║
║ ╚██╗ ██╔╝██║██║ ██║ ██║ ██║ ║
║ ╚████╔╝ ██║╚██████╗ ██║ ╚██████╔╝ ║
║ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ║
║ ║
║ ██████╗ ██╗ ██╗ ║
║ ██╔══██╗╚██╗ ██╔╝ ║
║ ██████╔╝ ╚████╔╝ ║
║ ██╔══██╗ ╚██╔╝ ║
║ ██║ ██║ ██║ ║
║ ╚═╝ ╚═╝ ╚═╝ ║
║ ║
╚═══════════════════════════════════════════╝''';
return Text(
asciiArt,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 14,
color: gold,
height: 1.0,
),
textAlign: TextAlign.center,
);
}
Widget _buildSectionTitle(String title, Color gold) {
return Column(
children: [
Text(
'═══════════════════',
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.5),
),
),
const SizedBox(height: 8),
Text(
title,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: gold,
letterSpacing: 2,
),
),
const SizedBox(height: 8),
Text(
'═══════════════════',
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.5),
),
),
],
);
}
Widget _buildHeroInfo(BuildContext context) {
final l10n = L10n.of(context);
final gold = RetroColors.goldOf(context);
final textPrimary = RetroColors.textPrimaryOf(context);
final textMuted = RetroColors.textMutedOf(context);
return Column(
children: [
Text(
traits.name,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 18,
color: gold,
),
),
const SizedBox(height: 12),
Text(
l10n.endingLevelFormat(traits.level),
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
color: textPrimary,
),
),
const SizedBox(height: 8),
Text(
'${GameDataL10n.getRaceName(context, traits.race)} '
'${GameDataL10n.getKlassName(context, traits.klass)}',
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: textMuted,
),
),
],
);
}
Widget _buildStatistics(BuildContext context) {
final l10n = L10n.of(context);
final textPrimary = RetroColors.textPrimaryOf(context);
final exp = RetroColors.expOf(context);
// 플레이 시간(play time) 포맷
final playTime = Duration(milliseconds: 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(
l10n.endingMonstersSlain,
'${progress.monstersKilled}',
textPrimary,
exp,
),
const SizedBox(height: 8),
_buildStatLine(
l10n.endingQuestsCompleted,
'${progress.questCount}',
textPrimary,
exp,
),
const SizedBox(height: 8),
_buildStatLine(
l10n.endingPlayTime,
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: 17,
color: labelColor.withValues(alpha: 0.7),
),
),
Text(
value,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
color: valueColor,
),
),
],
);
}
Widget _buildFinalStats(BuildContext context) {
final textPrimary = RetroColors.textPrimaryOf(context);
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: 15,
color: color.withValues(alpha: 0.5),
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: color,
),
),
],
);
}
Widget _buildTrophyAsciiArt(Color gold) {
const trophy = '''
____________
'._==_==_=_.'
.-\\: /-.
| (|:. |) |
'-|:. |-'
\\::. /
'::. .'
) (
_.' '._
'-------' ''';
return Text(
trophy,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold,
height: 1.0,
),
textAlign: TextAlign.center,
);
}
Widget _buildCredits(BuildContext context) {
final l10n = L10n.of(context);
final textPrimary = RetroColors.textPrimaryOf(context);
final textMuted = RetroColors.textMutedOf(context);
final gold = RetroColors.goldOf(context);
return Column(
children: [
Text(
l10n.appTitle,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: gold,
),
),
const SizedBox(height: 16),
Text(
l10n.endingThankYou,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: textPrimary,
),
),
const SizedBox(height: 8),
Text(
l10n.endingLegendLivesOn,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 15,
color: textMuted,
fontStyle: FontStyle.italic,
),
),
],
);
}
Widget _buildTheEnd(BuildContext context, Color gold) {
const theEnd = '''
████████╗██╗ ██╗███████╗ ███████╗███╗ ██╗██████╗
╚══██╔══╝██║ ██║██╔════╝ ██╔════╝████╗ ██║██╔══██╗
██║ ███████║█████╗ █████╗ ██╔██╗ ██║██║ ██║
██║ ██╔══██║██╔══╝ ██╔══╝ ██║╚██╗██║██║ ██║
██║ ██║ ██║███████╗ ███████╗██║ ╚████║██████╔╝
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚══════╝╚═╝ ╚═══╝╚═════╝ ''';
// FittedBox로 감싸서 화면 너비에 맞게 자동 스케일링(scaling)
return FittedBox(
fit: BoxFit.scaleDown,
child: Text(
theEnd,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 12,
color: gold,
height: 1.0,
),
textAlign: TextAlign.center,
),
);
}
/// 명예의 전당(Hall of Fame) 버튼
Widget _buildHallOfFameButton(BuildContext context, Color gold) {
final l10n = L10n.of(context);
return Column(
children: [
Text(
l10n.endingHallOfFameLine1,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.7),
),
),
const SizedBox(height: 4),
Text(
l10n.endingHallOfFameLine2,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.7),
),
),
const SizedBox(height: 24),
SizedBox(
width: 280,
height: 56,
child: ElevatedButton(
onPressed: onComplete,
style: ElevatedButton.styleFrom(
backgroundColor: gold,
foregroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.emoji_events, size: 24),
const SizedBox(width: 12),
Text(
l10n.endingHallOfFameButton,
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
),
),
],
),
),
),
],
);
}
}

View File

@@ -1,11 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:asciineverdie/l10n/app_localizations.dart'; import 'package:asciineverdie/l10n/app_localizations.dart';
import 'package:asciineverdie/src/shared/l10n/game_data_l10n.dart';
import 'package:asciineverdie/src/core/model/game_state.dart'; import 'package:asciineverdie/src/core/model/game_state.dart';
import 'package:asciineverdie/src/features/game/widgets/victory_credit_content.dart';
import 'package:asciineverdie/src/shared/retro_colors.dart'; import 'package:asciineverdie/src/shared/retro_colors.dart';
/// 게임 클리어 엔딩 오버레이 (Act V 완료 시) /// 게임 클리어 엔딩 오버레이(overlay) - Act V 완료 시
/// ///
/// 영화 엔딩 크레딧 스타일로 텍스트가 아래에서 위로 스크롤됨 /// 영화 엔딩 크레딧 스타일로 텍스트가 아래에서 위로 스크롤됨
/// - 탭/클릭 시 스크롤 최하단으로 즉시 이동 /// - 탭/클릭 시 스크롤 최하단으로 즉시 이동
@@ -25,7 +25,7 @@ class VictoryOverlay extends StatefulWidget {
final ProgressState progress; final ProgressState progress;
final int elapsedMs; final int elapsedMs;
/// 엔딩 완료 콜백 (명예의 전당으로 이동) /// 엔딩 완료 콜백(callback) - 명예의 전당으로 이동
final VoidCallback onComplete; final VoidCallback onComplete;
@override @override
@@ -97,7 +97,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
}); });
} }
/// 터치 시작 - 스크롤 속도업 /// 터치 시작 - 스크롤 속도업(speed up)
void _onTouchStart() { void _onTouchStart() {
if (_isScrollComplete) return; if (_isScrollComplete) return;
@@ -105,7 +105,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
_isTouching = true; _isTouching = true;
}); });
// 현재 진행도 저장 // 현재 진행도(progress) 저장
final currentProgress = _animationController.value; final currentProgress = _animationController.value;
final remainingProgress = 1.0 - currentProgress; final remainingProgress = 1.0 - currentProgress;
@@ -225,7 +225,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
final screenHeight = MediaQuery.of(context).size.height; final screenHeight = MediaQuery.of(context).size.height;
final contentHeight = _estimateContentHeight(); final contentHeight = _estimateContentHeight();
// 스크롤 오프셋: 화면 아래에서 시작 → 화면 위로 사라짐 // 스크롤 오프셋(offset): 화면 아래에서 시작 → 화면 위로 사라짐
final totalScrollDistance = screenHeight + contentHeight; final totalScrollDistance = screenHeight + contentHeight;
final currentOffset = final currentOffset =
screenHeight - (_scrollAnimation.value * totalScrollDistance); screenHeight - (_scrollAnimation.value * totalScrollDistance);
@@ -234,7 +234,7 @@ class _VictoryOverlayState extends State<VictoryOverlay>
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
child: Transform.translate( child: Transform.translate(
offset: Offset(0, currentOffset), offset: Offset(0, currentOffset),
child: _buildCreditContent(context), child: _buildCreditContent(),
), ),
); );
} }
@@ -244,491 +244,22 @@ class _VictoryOverlayState extends State<VictoryOverlay>
return SingleChildScrollView( return SingleChildScrollView(
controller: _manualScrollController, controller: _manualScrollController,
physics: const BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
child: _buildCreditContent(context), child: _buildCreditContent(),
); );
} }
double _estimateContentHeight() { double _estimateContentHeight() {
// 대략적인 콘텐츠 높이 추정 (스크롤 계산용) // 대략적인 콘텐츠 높이 추정 (스크롤 계산용)
// 명예의 전당 버튼 추가로 인해 높이 증가
return 1600.0; return 1600.0;
} }
Widget _buildCreditContent(BuildContext context) { Widget _buildCreditContent() {
final l10n = L10n.of(context); return VictoryCreditContent(
final gold = RetroColors.goldOf(context); traits: widget.traits,
final textPrimary = RetroColors.textPrimaryOf(context); stats: widget.stats,
progress: widget.progress,
return Center( elapsedMs: widget.elapsedMs,
child: Container( onComplete: widget.onComplete,
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(
l10n.endingCongratulations,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 16,
color: gold,
letterSpacing: 2,
),
),
const SizedBox(height: 16),
Text(
l10n.endingGameComplete,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 18,
color: textPrimary,
),
),
const SizedBox(height: 80),
// ═══════════════════════════════════
// THE HERO
// ═══════════════════════════════════
_buildSectionTitle(l10n.endingTheHero, gold),
const SizedBox(height: 20),
_buildHeroInfo(context),
const SizedBox(height: 80),
// ═══════════════════════════════════
// JOURNEY STATISTICS
// ═══════════════════════════════════
_buildSectionTitle(l10n.endingJourneyStats, gold),
const SizedBox(height: 20),
_buildStatistics(context),
const SizedBox(height: 80),
// ═══════════════════════════════════
// FINAL STATS
// ═══════════════════════════════════
_buildSectionTitle(l10n.endingFinalStats, gold),
const SizedBox(height: 20),
_buildFinalStats(context),
const SizedBox(height: 100),
// ═══════════════════════════════════
// ASCII TROPHY
// ═══════════════════════════════════
_buildTrophyAsciiArt(gold),
const SizedBox(height: 60),
// ═══════════════════════════════════
// CREDITS
// ═══════════════════════════════════
_buildSectionTitle(l10n.endingCredits, gold),
const SizedBox(height: 20),
_buildCredits(context),
const SizedBox(height: 100),
// ═══════════════════════════════════
// THE END
// ═══════════════════════════════════
_buildTheEnd(context, gold),
const SizedBox(height: 60),
// ═══════════════════════════════════
// HALL OF FAME BUTTON
// ═══════════════════════════════════
_buildHallOfFameButton(context, gold),
const SizedBox(height: 100), // 여백 (스크롤 끝)
],
),
),
);
}
Widget _buildVictoryAsciiArt(Color gold) {
const asciiArt = '''
╔═══════════════════════════════════════════╗
║ ║
║ ██╗ ██╗██╗ ██████╗████████╗ ██████╗ ║
║ ██║ ██║██║██╔════╝╚══██╔══╝██╔═══██╗ ║
║ ██║ ██║██║██║ ██║ ██║ ██║ ║
║ ╚██╗ ██╔╝██║██║ ██║ ██║ ██║ ║
║ ╚████╔╝ ██║╚██████╗ ██║ ╚██████╔╝ ║
║ ╚═══╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ║
║ ║
║ ██████╗ ██╗ ██╗ ║
║ ██╔══██╗╚██╗ ██╔╝ ║
║ ██████╔╝ ╚████╔╝ ║
║ ██╔══██╗ ╚██╔╝ ║
║ ██║ ██║ ██║ ║
║ ╚═╝ ╚═╝ ╚═╝ ║
║ ║
╚═══════════════════════════════════════════╝''';
return Text(
asciiArt,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 14,
color: gold,
height: 1.0,
),
textAlign: TextAlign.center,
);
}
Widget _buildSectionTitle(String title, Color gold) {
return Column(
children: [
Text(
'═══════════════════',
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.5),
),
),
const SizedBox(height: 8),
Text(
title,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: gold,
letterSpacing: 2,
),
),
const SizedBox(height: 8),
Text(
'═══════════════════',
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.5),
),
),
],
);
}
Widget _buildHeroInfo(BuildContext context) {
final l10n = L10n.of(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: 18,
color: gold,
),
),
const SizedBox(height: 12),
// 레벨, 종족, 직업
Text(
l10n.endingLevelFormat(widget.traits.level),
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
color: textPrimary,
),
),
const SizedBox(height: 8),
Text(
'${GameDataL10n.getRaceName(context, widget.traits.race)} '
'${GameDataL10n.getKlassName(context, widget.traits.klass)}',
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: textMuted,
),
),
],
);
}
Widget _buildStatistics(BuildContext context) {
final l10n = L10n.of(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(
l10n.endingMonstersSlain,
'${widget.progress.monstersKilled}',
textPrimary,
exp,
),
const SizedBox(height: 8),
_buildStatLine(
l10n.endingQuestsCompleted,
'${widget.progress.questCount}',
textPrimary,
exp,
),
const SizedBox(height: 8),
_buildStatLine(
l10n.endingPlayTime,
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: 17,
color: labelColor.withValues(alpha: 0.7),
),
),
Text(
value,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
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: 15,
color: color.withValues(alpha: 0.5),
),
),
const SizedBox(height: 4),
Text(
value,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: color,
),
),
],
);
}
Widget _buildTrophyAsciiArt(Color gold) {
// 중앙 정렬을 위해 각 줄 좌우 공백 균형 맞춤
const trophy = '''
____________
'._==_==_=_.'
.-\\: /-.
| (|:. |) |
'-|:. |-'
\\::. /
'::. .'
) (
_.' '._
'-------' ''';
return Text(
trophy,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold,
height: 1.0,
),
textAlign: TextAlign.center,
);
}
Widget _buildCredits(BuildContext context) {
final l10n = L10n.of(context);
final textPrimary = RetroColors.textPrimaryOf(context);
final textMuted = RetroColors.textMutedOf(context);
final gold = RetroColors.goldOf(context);
return Column(
children: [
Text(
l10n.appTitle,
style: TextStyle(
fontFamily: 'PressStart2P',
fontSize: 15,
color: gold,
),
),
const SizedBox(height: 16),
Text(
l10n.endingThankYou,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: textPrimary,
),
),
const SizedBox(height: 8),
Text(
l10n.endingLegendLivesOn,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 15,
color: textMuted,
fontStyle: FontStyle.italic,
),
),
],
);
}
Widget _buildTheEnd(BuildContext context, Color gold) {
const theEnd = '''
████████╗██╗ ██╗███████╗ ███████╗███╗ ██╗██████╗
╚══██╔══╝██║ ██║██╔════╝ ██╔════╝████╗ ██║██╔══██╗
██║ ███████║█████╗ █████╗ ██╔██╗ ██║██║ ██║
██║ ██╔══██║██╔══╝ ██╔══╝ ██║╚██╗██║██║ ██║
██║ ██║ ██║███████╗ ███████╗██║ ╚████║██████╔╝
╚═╝ ╚═╝ ╚═╝╚══════╝ ╚══════╝╚═╝ ╚═══╝╚═════╝ ''';
// FittedBox로 감싸서 화면 너비에 맞게 자동 스케일링
return FittedBox(
fit: BoxFit.scaleDown,
child: Text(
theEnd,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 12,
color: gold,
height: 1.0,
),
textAlign: TextAlign.center,
),
);
}
/// 명예의 전당 버튼 (최하단)
Widget _buildHallOfFameButton(BuildContext context, Color gold) {
final l10n = L10n.of(context);
return Column(
children: [
// 안내 텍스트
Text(
l10n.endingHallOfFameLine1,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.7),
),
),
const SizedBox(height: 4),
Text(
l10n.endingHallOfFameLine2,
style: TextStyle(
fontFamily: 'JetBrainsMono',
fontSize: 17,
color: gold.withValues(alpha: 0.7),
),
),
const SizedBox(height: 24),
// 명예의 전당 버튼
SizedBox(
width: 280,
height: 56,
child: ElevatedButton(
onPressed: widget.onComplete,
style: ElevatedButton.styleFrom(
backgroundColor: gold,
foregroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
elevation: 8,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.emoji_events, size: 24),
const SizedBox(width: 12),
Text(
l10n.endingHallOfFameButton,
style: const TextStyle(
fontFamily: 'PressStart2P',
fontSize: 14,
),
),
],
),
),
),
],
); );
} }
} }