diff --git a/Baccarat/Baccarat/Resources/Localizable.xcstrings b/Baccarat/Baccarat/Resources/Localizable.xcstrings index ad140f4..7bf7413 100644 --- a/Baccarat/Baccarat/Resources/Localizable.xcstrings +++ b/Baccarat/Baccarat/Resources/Localizable.xcstrings @@ -21,8 +21,8 @@ "value" : "-%lld $" } } - } - }, + } + }, "%lld" : { "comment" : "The number of rounds a player has played in the game.", "localizations" : { @@ -67,8 +67,8 @@ "value" : "%lld." } } - } - }, + } + }, "%lldpx" : { "comment" : "A text label displaying the size of the app icon. The argument is the size of the icon in pixels.", "localizations" : { @@ -113,8 +113,8 @@ "value" : "•" } } - } - }, + } + }, "• Add to Assets.xcassets/AppIcon" : { "comment" : "A step in the process of exporting app icons.", "localizations" : { @@ -158,8 +158,8 @@ "value" : "• Appeler IconRenderer.renderAppIcon(config: .baccarat)" } } - } - }, + } + }, "• Run the preview in Xcode" : { "localizations" : { "en" : { @@ -203,8 +203,8 @@ "value" : "• Enregistrer l'UIImage résultante dans les fichiers" } } - } - }, + } + }, "• Screenshot the 1024px icon" : { "comment" : "A step in the process of exporting app icons, describing how to take a screenshot of a 1024px icon.", "localizations" : { @@ -249,8 +249,8 @@ "value" : "• Utiliser un outil en ligne pour générer toutes les tailles" } } - } - }, + } + }, "↓ then →" : { "comment" : "A textual instruction for using the road map in the game.", "localizations" : { @@ -295,8 +295,8 @@ "value" : "+%lld" } } - } - }, + } + }, "+$%lld" : { "localizations" : { "en" : { @@ -340,8 +340,8 @@ "value" : "2-9: Valeur faciale" } } - } - }, + } + }, "8 : 1" : { "comment" : "The payout ratio for a tie bet.", "localizations" : { @@ -386,8 +386,8 @@ "value" : "10, Valet, Dame, Roi: 0 point" } } - } - }, + } + }, "11 : 1" : { "comment" : "The payout ratio for a pair bet.", "localizations" : { @@ -432,8 +432,8 @@ "value" : "Un Naturel termine la manche immédiatement." } } - } - }, + } + }, "Ace: 1 point" : { "comment" : "Card value description for an Ace.", "localizations" : { @@ -477,8 +477,8 @@ "value" : "Ajoutez %lld$ de plus pour atteindre le minimum" } } - } - }, + } + }, "After generating:" : { "comment" : "A heading for the instructions section of the icon generator view.", "localizations" : { @@ -523,8 +523,8 @@ "value" : "Toutes les tailles" } } - } - }, + } + }, "Alternative: Use an online tool" : { "comment" : "A section header that suggests using an online tool to generate app icon sizes.", "localizations" : { @@ -569,8 +569,8 @@ "value" : "Misez toujours sur le Banquier — il a les meilleures chances (1.06% d'avantage)." } } - } - }, + } + }, "Animate dealing and flipping" : { "comment" : "Subtitle for card animations toggle.", "localizations" : { @@ -615,8 +615,8 @@ "value" : "Icône de l'app" } } - } - }, + } + }, "App Icon Preview" : { "comment" : "A header describing the preview of the app icon.", "localizations" : { @@ -661,8 +661,8 @@ "value" : "Évitez la mise sur Égalité — 14.4% d'avantage maison!" } } - } - }, + } + }, "B Pair" : { "localizations" : { "en" : { @@ -706,8 +706,8 @@ "value" : "Le Baccarat a l'un des plus faibles avantages maison du casino." } } - } - }, + } + }, "Banker" : { "localizations" : { "en" : { @@ -751,8 +751,8 @@ "value" : "BANQUE" } } - } - }, + } + }, "Banker 0-2: Always draws" : { "comment" : "Description of the third card rule for the Banker when the Player's third card is 0-2.", "localizations" : { @@ -797,8 +797,8 @@ "value" : "Banquier 3: Tire sauf si la 3e du Joueur était 8" } } - } - }, + } + }, "Banker 4: Draws if Player's 3rd was 2-7" : { "comment" : "Side bet rule for the Banker when the Player's third card is 4 and it falls between 2 and 7.", "localizations" : { @@ -843,8 +843,8 @@ "value" : "Banquier 5: Tire si la 3e du Joueur était 4-7" } } - } - }, + } + }, "Banker 6: Draws if Player's 3rd was 6-7" : { "comment" : "Description of the betting strategy for the Banker when the Player's third card is 6-7.", "localizations" : { @@ -958,8 +958,8 @@ "value" : "Main du banquier" } } - } - }, + } + }, "Bet on which hand will win: Player, Banker, or Tie." : { "comment" : "Text describing the objective of the baccarat game.", "localizations" : { @@ -1004,8 +1004,8 @@ "value" : "Blackjack" } } - } - }, + } + }, "BONUS" : { "comment" : "The text displayed in the center of the bonus zone.", "localizations" : { @@ -1232,8 +1232,8 @@ "value" : "Effacer toutes les données?" } } - } - }, + } + }, "CLOUD SYNC" : { "localizations" : { "en" : { @@ -1276,8 +1276,8 @@ "value" : "DONNÉES" } } - } - }, + } + }, "Deal" : { "comment" : "The label of a button that deals cards in a game.", "localizations" : { @@ -1322,8 +1322,8 @@ "value" : "Distribution..." } } - } - }, + } + }, "DECK SETTINGS" : { "comment" : "Section header for deck configuration settings.", "localizations" : { @@ -1391,8 +1391,8 @@ "value" : "Afficher le compteur de cartes en haut" } } - } - }, + } + }, "Display result road map" : { "comment" : "Subtitle for show history toggle.", "localizations" : { @@ -1437,8 +1437,8 @@ "value" : "Terminé" } } - } - }, + } + }, "Dragon Bonus" : { "localizations" : { "en" : { @@ -1482,8 +1482,8 @@ "value" : "Le Bonus Dragon est amusant mais a ~2.7% d'avantage maison." } } - } - }, + } + }, "Example: 5♥ + 5♣ = Pair (wins!)" : { "comment" : "Example of a pair bet winning.", "localizations" : { @@ -1528,8 +1528,8 @@ "value" : "Historique" } } - } - }, + } + }, "Game Over" : { "comment" : "The title of the game over screen.", "localizations" : { @@ -1574,8 +1574,8 @@ "value" : "FIN DE PARTIE" } } - } - }, + } + }, "Generate & Save Icons" : { "comment" : "A button label that triggers the generation of app icons.", "localizations" : { @@ -1620,8 +1620,8 @@ "value" : "Icônes générées:" } } - } - }, + } + }, "Generating..." : { "comment" : "A text that appears while generating icons.", "localizations" : { @@ -1666,8 +1666,8 @@ "value" : "Les valeurs de main n'utilisent que le dernier chiffre (ex., 7+8=15 → 5)." } } - } - }, + } + }, "handValueFormat" : { "comment" : "Format for displaying hand value. The argument is the numeric value of the hand.", "localizations" : { @@ -1712,8 +1712,8 @@ "value" : "Retour Haptique" } } - } - }, + } + }, "HISTORY" : { "comment" : "A label displayed above the road map view, indicating that it shows a history of past game results.", "localizations" : { @@ -1758,8 +1758,8 @@ "value" : "%lld parties : %lld joueur, %lld banquier, %lld égalités" } } - } - }, + } + }, "How to Export Icons" : { "comment" : "A section header explaining how to export app icons.", "localizations" : { @@ -1804,8 +1804,8 @@ "value" : "Comment jouer" } } - } - }, + } + }, "iCloud Sync" : { "localizations" : { "en" : { @@ -1848,8 +1848,8 @@ "value" : "iCloud non disponible" } } - } - }, + } + }, "Icon" : { "comment" : "The title for the tab that displays the app icon preview.", "localizations" : { @@ -1894,8 +1894,8 @@ "value" : "Générateur d'icônes" } } - } - }, + } + }, "If either hand totals 8 or 9 with two cards, it's a 'Natural'." : { "comment" : "Description of the 'Natural' hand in baccarat, explaining when it occurs and its significance.", "localizations" : { @@ -1940,8 +1940,8 @@ "value" : "Si aucune main n'a un Naturel, les règles de la troisième carte s'appliquent." } } - } - }, + } + }, "If Player draws, Banker's action depends on Player's third card:" : { "comment" : "Explanation of the third card decision for the Banker in the Rules Help view.", "localizations" : { @@ -1986,8 +1986,8 @@ "value" : "Si le Joueur reste, le Banquier tire sur 0-5, reste sur 6-7." } } - } - }, + } + }, "Independent of the main game result." : { "comment" : "Note about the independence of the Pair Bonus from the main game result in the Rules Help view.", "localizations" : { @@ -2098,8 +2098,8 @@ "value" : "Tours joués" } } - } - }, + } + }, "Main Bets" : { "comment" : "Title of a rule page in the \"Rules\" help view, describing the main bets available in baccarat.", "localizations" : { @@ -2144,8 +2144,8 @@ "value" : "MAX" } } - } - }, + } + }, "Natural 9 beats Natural 8." : { "comment" : "Explanation of the payout for a Baccarat hand that is a Natural 9, compared to one that is a Natural 8.", "localizations" : { @@ -2190,8 +2190,8 @@ "value" : "Main naturelle" } } - } - }, + } + }, "Natural Win: 1:1" : { "comment" : "Description of the payout for a 'Natural Win' in the Rules Help view.", "localizations" : { @@ -2371,8 +2371,8 @@ "value" : "Objectif" } } - } - }, + } + }, "Only the rank matters (suits are ignored)." : { "comment" : "Explanation of how to determine if the first two cards in a hand form a pair, focusing on the rank rather than the suit.", "localizations" : { @@ -2645,8 +2645,8 @@ "value" : "Joueur" } } - } - }, + } + }, "PLAYER" : { "comment" : "The label for the player's hand in the cards display area.", "localizations" : { @@ -2737,8 +2737,8 @@ "value" : "Joueur avec 0-5: Tire une troisième carte" } } - } - }, + } + }, "Player with 6-7: Stands" : { "comment" : "Description of the action for the Banker when the Player draws a third card.", "localizations" : { @@ -2783,8 +2783,8 @@ "value" : "Joueur avec 8-9: Naturel (pas de troisième carte)" } } - } - }, + } + }, "Poker" : { "comment" : "The name of a poker game.", "localizations" : { @@ -2828,8 +2828,8 @@ "value" : "Politique de confidentialité" } } - } - }, + } + }, "Reset to Defaults" : { "comment" : "A button label that resets game settings to their default values.", "localizations" : { @@ -2874,8 +2874,8 @@ "value" : "Roulette" } } - } - }, + } + }, "Rounds" : { "localizations" : { "en" : { @@ -2988,8 +2988,8 @@ "value" : "Afficher Cartes Restantes" } } - } - }, + } + }, "Show History" : { "comment" : "Toggle label for showing game history.", "localizations" : { @@ -3034,8 +3034,8 @@ "value" : "Mise secondaire sur le Joueur ou Banquier gagnant par une marge." } } - } - }, + } + }, "Side bet on the first two cards being a pair." : { "comment" : "Description of a side bet where the player bets on whether the first two cards dealt in a hand are a pair.", "localizations" : { @@ -3079,8 +3079,8 @@ "value" : "Connectez-vous à iCloud pour synchroniser" } } - } - }, + } + }, "SOUND & HAPTICS" : { "comment" : "Section header for sound and haptic settings.", "localizations" : { @@ -3125,8 +3125,8 @@ "value" : "Effets Sonores" } } - } - }, + } + }, "STARTING BALANCE" : { "comment" : "Section header for starting balance settings.", "localizations" : { @@ -3170,8 +3170,8 @@ "value" : "Statistiques" } } - } - }, + } + }, "Strategy Tips" : { "comment" : "Title of a section in the Rules Help view focused on strategy tips.", "localizations" : { @@ -3284,8 +3284,8 @@ "value" : "%@ $ - %@ $" } } - } - }, + } + }, "The hand closest to 9 wins." : { "comment" : "Explanation of how the hand closest to 9 wins in baccarat.", "localizations" : { @@ -3330,8 +3330,8 @@ "value" : "Aucune compétence requise — profitez simplement du jeu!" } } - } - }, + } + }, "These show how the same pattern works for other games" : { "comment" : "A description below the section of the view that previews icons for other games.", "localizations" : { @@ -3443,8 +3443,8 @@ "value" : "Égalité" } } - } - }, + } + }, "TIE" : { "comment" : "The text displayed in the TIE betting zone.", "localizations" : { @@ -3489,8 +3489,8 @@ "value" : "Mise Égalité: Paie 8:1" } } - } - }, + } + }, "TOTAL" : { "comment" : "A label displayed next to the total winnings in the result banner.", "localizations" : { diff --git a/Baccarat/Baccarat/Views/Sheets/RulesHelpView.swift b/Baccarat/Baccarat/Views/Sheets/RulesHelpView.swift index 4b13542..9181eff 100644 --- a/Baccarat/Baccarat/Views/Sheets/RulesHelpView.swift +++ b/Baccarat/Baccarat/Views/Sheets/RulesHelpView.swift @@ -119,11 +119,11 @@ struct RulesHelpView: View { var body: some View { NavigationStack { - ZStack { + ZStack { Color.Sheet.background - .ignoresSafeArea() - - VStack(spacing: 0) { + .ignoresSafeArea() + + VStack(spacing: 0) { // Page content TabView(selection: $currentPage) { ForEach(pages.indices, id: \.self) { index in @@ -132,15 +132,15 @@ struct RulesHelpView: View { } } .tabViewStyle(.page(indexDisplayMode: .never)) - + // Page indicator HStack(spacing: Design.Spacing.small) { ForEach(pages.indices, id: \.self) { index in Circle() .fill(index == currentPage ? Color.Sheet.accent : Color.white.opacity(Design.Opacity.light)) .frame(width: 8, height: 8) - } - } + } + } .padding(.vertical, Design.Spacing.medium) } } @@ -149,7 +149,7 @@ struct RulesHelpView: View { .toolbar { ToolbarItem(placement: .topBarTrailing) { Button(String(localized: "Done")) { - dismiss() + dismiss() } .foregroundStyle(Color.Sheet.accent) } @@ -190,10 +190,10 @@ struct RulePageView: View { // Title Text(page.title) .font(.system(size: titleSize, weight: .bold)) - .foregroundStyle(.white) - + .foregroundStyle(.white) + // Content - VStack(alignment: .leading, spacing: Design.Spacing.medium) { + VStack(alignment: .leading, spacing: Design.Spacing.medium) { ForEach(page.content.indices, id: \.self) { index in HStack(alignment: .top, spacing: Design.Spacing.medium) { Text("•") diff --git a/Blackjack/Blackjack/Theme/DesignConstants.swift b/Blackjack/Blackjack/Theme/DesignConstants.swift index d59fb50..0c10901 100644 --- a/Blackjack/Blackjack/Theme/DesignConstants.swift +++ b/Blackjack/Blackjack/Theme/DesignConstants.swift @@ -17,7 +17,7 @@ enum Design { // MARK: - Debug /// Set to true to show layout debug borders on views - static let showDebugBorders = false + static let showDebugBorders = true // MARK: - Shared Constants (from CasinoKit) @@ -32,73 +32,45 @@ enum Design { typealias BaseFontSize = CasinoDesign.BaseFontSize typealias IconSize = CasinoDesign.IconSize - // MARK: - Blackjack-Specific Component Sizes + // MARK: - Blackjack-Specific Sizes (use CasinoDesign.Size for shared values) enum Size { + // Cards + static let cardWidth: CGFloat = 90 + static let cardOverlap: CGFloat = -50 // Negative = more overlap - // Use shared scaling from CasinoKit - static var handScale: CGFloat { CasinoDesign.Size.handScale } - static var fontScale: CGFloat { CasinoDesign.Size.fontScale } - - // Cards - scaled for better visibility - static let cardWidth: CGFloat = 60 * handScale // 90pt at 1.5x - static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall + // Player hands + static let playerHandsHeight: CGFloat = 240 - /// Card overlap (negative = cards stack left over right). - /// More negative = more overlap (less card visible). - /// With 90pt cards: -40 = ~44% overlap, -50 = ~55% overlap - static let cardOverlap: CGFloat = -50 + // Hand labels + static let handLabelFontSize: CGFloat = 14 + static let handNumberFontSize: CGFloat = 12 + static let handValueFontSize: CGFloat = 18 - // Player hands container height (accommodates larger cards + labels) - // Reduced from 180 to fit content more snugly - static let playerHandsHeight: CGFloat = 160 * handScale // 240pt at 1.5x + // Hints + static let hintFontSize: CGFloat = 15 + static let hintIconSize: CGFloat = 24 + static let hintPaddingH: CGFloat = 18 + static let hintPaddingV: CGFloat = 12 - // Hand label font sizes (scaled) - static let handLabelFontSize: CGFloat = CasinoDesign.BaseFontSize.medium * fontScale - static let handNumberFontSize: CGFloat = CasinoDesign.BaseFontSize.medium * fontScale // Same as label - static let handValueFontSize: CGFloat = CasinoDesign.BaseFontSize.xLarge * fontScale + // Hand icons + static let handIconSize: CGFloat = 18 - // Hint font size (scaled to match hands) - static let hintFontSize: CGFloat = CasinoDesign.BaseFontSize.small * fontScale - static let hintIconSize: CGFloat = CasinoDesign.IconSize.medium * handScale - static let hintPaddingH: CGFloat = CasinoDesign.Spacing.medium * handScale - static let hintPaddingV: CGFloat = CasinoDesign.Spacing.small * handScale + // Hi-Lo count badge + static let countBadgeFontSize: CGFloat = 10 + static let countBadgePaddingH: CGFloat = 6 + static let countBadgePaddingV: CGFloat = 2 + static let countBadgeOffset: CGFloat = 6 - // Hand icons (scaled) - static let handIconSize: CGFloat = CasinoDesign.IconSize.medium * handScale + // Betting + static let bettingChipSize: CGFloat = 54 - // Hi-Lo count badge (scaled) - static let countBadgeFontSize: CGFloat = CasinoDesign.BaseFontSize.xxSmall * fontScale - static let countBadgePaddingH: CGFloat = CasinoDesign.Spacing.xSmall * handScale - static let countBadgePaddingV: CGFloat = CasinoDesign.Spacing.xxxSmall * handScale - static let countBadgeOffset: CGFloat = CasinoDesign.Spacing.xSmall * handScale + // Card count display + static let cardCountLabelSize: CGFloat = 11 + static let cardCountValueSize: CGFloat = 18 - // Betting zone (chip scales, but zone height stays reasonable) - static let bettingChipSize: CGFloat = 36 * handScale // 54pt at 1.5x - static let bettingZoneHeightScaled: CGFloat = CasinoDesign.Size.bettingZoneHeight // Keep original height to save space - - // Card count display (scaled) - static let cardCountLabelSize: CGFloat = CasinoDesign.BaseFontSize.xSmall * fontScale - static let cardCountValueSize: CGFloat = CasinoDesign.BaseFontSize.large * handScale - - // Chips - use CasinoDesign values - static let chipBadgeSize: CGFloat = CasinoDesign.Size.chipBadge - - // Buttons - use CasinoDesign values - static let actionButtonHeight: CGFloat = CasinoDesign.Size.actionButtonHeight - static let actionButtonMinWidth: CGFloat = CasinoDesign.Size.actionButtonMinWidth - static let bettingZoneHeight: CGFloat = CasinoDesign.Size.bettingZoneHeight - - // Responsive - use CasinoDesign values - static let maxContentWidthPortrait: CGFloat = CasinoDesign.Size.maxContentWidthPortrait - static let maxContentWidthLandscape: CGFloat = CasinoDesign.Size.maxContentWidthLandscape - static let maxModalWidth: CGFloat = CasinoDesign.Size.maxModalWidth - - // Blackjack-specific + // Table static let tableHeight: CGFloat = 280 - - // Settings - use CasinoDesign values - static let checkmark: CGFloat = CasinoDesign.Size.checkmark } } @@ -138,7 +110,7 @@ extension Color { static let blackjack = Color.yellow } - // MARK: - Action Button Colors + // MARK: - Action Button Colors (Blackjack-specific) enum Button { static let hit = Color(red: 0.2, green: 0.6, blue: 0.3) @@ -147,25 +119,6 @@ extension Color { static let split = Color(red: 0.3, green: 0.5, blue: 0.7) static let surrender = Color(red: 0.6, green: 0.3, blue: 0.3) static let insurance = Color(red: 0.7, green: 0.6, blue: 0.2) - - static let goldLight = Color(red: 1.0, green: 0.85, blue: 0.3) - static let goldDark = Color(red: 0.9, green: 0.7, blue: 0.2) - } - - // MARK: - Settings Colors - - enum Settings { - static let background = Color(red: 0.08, green: 0.12, blue: 0.18) - static let cardBackground = Color.white.opacity(CasinoDesign.Opacity.verySubtle) - static let accent = Color(red: 0.9, green: 0.75, blue: 0.3) - } - - // MARK: - Modal Colors - - enum Modal { - static let background = Color(red: 0.12, green: 0.18, blue: 0.25) - static let backgroundLight = Color(red: 0.15, green: 0.2, blue: 0.3) - static let backgroundDark = Color(red: 0.1, green: 0.15, blue: 0.25) } // MARK: - TopBar Colors diff --git a/Blackjack/Blackjack/Views/Game/ActionButton.swift b/Blackjack/Blackjack/Views/Game/ActionButton.swift index f19721f..05c7876 100644 --- a/Blackjack/Blackjack/Views/Game/ActionButton.swift +++ b/Blackjack/Blackjack/Views/Game/ActionButton.swift @@ -61,7 +61,7 @@ struct ActionButton: View { Capsule() .fill( LinearGradient( - colors: [Color.Button.goldLight, Color.Button.goldDark], + colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark], startPoint: .top, endPoint: .bottom ) diff --git a/Blackjack/Blackjack/Views/Game/CardCountView.swift b/Blackjack/Blackjack/Views/Game/CardCountView.swift index d11280a..a3834b0 100644 --- a/Blackjack/Blackjack/Views/Game/CardCountView.swift +++ b/Blackjack/Blackjack/Views/Game/CardCountView.swift @@ -13,16 +13,19 @@ struct CardCountView: View { let runningCount: Int let trueCount: Double + @ScaledMetric(relativeTo: .caption) private var labelSize: CGFloat = Design.Size.cardCountLabelSize + @ScaledMetric(relativeTo: .title) private var valueSize: CGFloat = Design.Size.cardCountValueSize + var body: some View { HStack(spacing: Design.Spacing.large) { // Running count VStack(spacing: Design.Spacing.xxSmall) { Text("Running") - .font(.system(size: Design.Size.cardCountLabelSize, weight: .medium)) + .font(.system(size: labelSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium)) Text(runningCount >= 0 ? "+\(runningCount)" : "\(runningCount)") - .font(.system(size: Design.Size.cardCountValueSize, weight: .bold, design: .monospaced)) + .font(.system(size: valueSize, weight: .bold, design: .monospaced)) .foregroundStyle(countColor(for: runningCount)) } @@ -33,11 +36,11 @@ struct CardCountView: View { // True count VStack(spacing: Design.Spacing.xxSmall) { Text("True") - .font(.system(size: Design.Size.cardCountLabelSize, weight: .medium)) + .font(.system(size: labelSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.medium)) Text(trueCount >= 0 ? "+\(trueCount, format: .number.precision(.fractionLength(1)))" : "\(trueCount, format: .number.precision(.fractionLength(1)))") - .font(.system(size: Design.Size.cardCountValueSize, weight: .bold, design: .monospaced)) + .font(.system(size: valueSize, weight: .bold, design: .monospaced)) .foregroundStyle(countColor(for: Int(trueCount.rounded()))) } } diff --git a/Blackjack/Blackjack/Views/Game/GameTableView.swift b/Blackjack/Blackjack/Views/Game/GameTableView.swift index 20806f6..e9d5a8a 100644 --- a/Blackjack/Blackjack/Views/Game/GameTableView.swift +++ b/Blackjack/Blackjack/Views/Game/GameTableView.swift @@ -33,8 +33,8 @@ struct GameTableView: View { private var maxContentWidth: CGFloat { if isIPad { return verticalSizeClass == .compact - ? Design.Size.maxContentWidthLandscape - : Design.Size.maxContentWidthPortrait + ? CasinoDesign.Size.maxContentWidthLandscape + : CasinoDesign.Size.maxContentWidthPortrait } return .infinity } @@ -72,18 +72,16 @@ struct GameTableView: View { @ViewBuilder private func mainGameView(state: GameState) -> some View { - GeometryReader { geometry in - ZStack { - // Background - TableBackgroundView() - - mainContent(state: state, screenHeight: geometry.size.height) - } + ZStack { + // Background + TableBackgroundView() + + mainContent(state: state) } } @ViewBuilder - private func mainContent(state: GameState, screenHeight: CGFloat) -> some View { + private func mainContent(state: GameState) -> some View { ZStack { VStack(spacing: 0) { // Top bar @@ -119,8 +117,7 @@ struct GameTableView: View { // Table layout - fills available space BlackjackTableView( state: state, - onPlaceBet: { placeBet(state: state) }, - screenHeight: screenHeight + onPlaceBet: { placeBet(state: state) } ) .frame(maxWidth: maxContentWidth) diff --git a/Blackjack/Blackjack/Views/Sheets/ResultBannerView.swift b/Blackjack/Blackjack/Views/Sheets/ResultBannerView.swift index 6f2ae08..ccdc2eb 100644 --- a/Blackjack/Blackjack/Views/Sheets/ResultBannerView.swift +++ b/Blackjack/Blackjack/Views/Sheets/ResultBannerView.swift @@ -121,7 +121,7 @@ struct ResultBannerView: View { Capsule() .fill( LinearGradient( - colors: [Color.Button.goldLight, Color.Button.goldDark], + colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark], startPoint: .top, endPoint: .bottom ) @@ -144,7 +144,7 @@ struct ResultBannerView: View { Capsule() .fill( LinearGradient( - colors: [Color.Button.goldLight, Color.Button.goldDark], + colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark], startPoint: .top, endPoint: .bottom ) @@ -158,7 +158,7 @@ struct ResultBannerView: View { RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge) .fill( LinearGradient( - colors: [Color.Modal.backgroundLight, Color.Modal.backgroundDark], + colors: [Color.CasinoModal.backgroundLight, Color.CasinoModal.backgroundDark], startPoint: .top, endPoint: .bottom ) @@ -172,7 +172,7 @@ struct ResultBannerView: View { ) ) .shadow(color: mainResultColor.opacity(Design.Opacity.hint), radius: Design.Shadow.radiusXLarge) - .frame(maxWidth: Design.Size.maxModalWidth) + .frame(maxWidth: CasinoDesign.Size.maxModalWidth) .padding(.horizontal, Design.Spacing.large) // Prevent clipping on sides .scaleEffect(showContent ? 1.0 : 0.8) .opacity(showContent ? 1.0 : 0) diff --git a/Blackjack/Blackjack/Views/Sheets/RulesHelpView.swift b/Blackjack/Blackjack/Views/Sheets/RulesHelpView.swift index 66e81e8..6b24f6e 100644 --- a/Blackjack/Blackjack/Views/Sheets/RulesHelpView.swift +++ b/Blackjack/Blackjack/Views/Sheets/RulesHelpView.swift @@ -191,7 +191,7 @@ struct RulesHelpView: View { var body: some View { NavigationStack { ZStack { - Color.Settings.background + Color.Sheet.background .ignoresSafeArea() VStack(spacing: 0) { @@ -208,7 +208,7 @@ struct RulesHelpView: View { HStack(spacing: Design.Spacing.small) { ForEach(pages.indices, id: \.self) { index in Circle() - .fill(index == currentPage ? Color.Settings.accent : Color.white.opacity(Design.Opacity.light)) + .fill(index == currentPage ? Color.Sheet.accent : Color.white.opacity(Design.Opacity.light)) .frame(width: 8, height: 8) } } @@ -222,10 +222,10 @@ struct RulesHelpView: View { Button(String(localized: "Done")) { dismiss() } - .foregroundStyle(Color.Settings.accent) + .foregroundStyle(Color.Sheet.accent) } } - .toolbarBackground(Color.Settings.background, for: .navigationBar) + .toolbarBackground(Color.Sheet.background, for: .navigationBar) .toolbarColorScheme(.dark, for: .navigationBar) } } @@ -255,7 +255,7 @@ struct RulePageView: View { // Icon Image(systemName: page.icon) .font(.system(size: iconSize)) - .foregroundStyle(Color.Settings.accent) + .foregroundStyle(Color.Sheet.accent) .padding(.top, Design.Spacing.xxLarge) // Title @@ -268,7 +268,7 @@ struct RulePageView: View { ForEach(page.content.indices, id: \.self) { index in HStack(alignment: .top, spacing: Design.Spacing.medium) { Text("•") - .foregroundStyle(Color.Settings.accent) + .foregroundStyle(Color.Sheet.accent) Text(page.content[index]) .font(.system(size: bodySize)) diff --git a/Blackjack/Blackjack/Views/Sheets/SettingsView.swift b/Blackjack/Blackjack/Views/Sheets/SettingsView.swift index a42e3f8..3d2aba2 100644 --- a/Blackjack/Blackjack/Views/Sheets/SettingsView.swift +++ b/Blackjack/Blackjack/Views/Sheets/SettingsView.swift @@ -24,7 +24,7 @@ struct SettingsView: View { } /// Accent color for settings components - private let accent = Color.Settings.accent + private let accent = Color.Sheet.accent var body: some View { SheetContainerView( @@ -248,7 +248,7 @@ struct SettingsView: View { } } .padding(.vertical, Design.Spacing.xSmall) - } + } } } @@ -335,10 +335,10 @@ struct SettingsView: View { // 12. Version info Text(appVersionString) - .font(.system(size: Design.BaseFontSize.small)) - .foregroundStyle(.white.opacity(Design.Opacity.light)) - .frame(maxWidth: .infinity) - .padding(.top, Design.Spacing.large) + .font(.system(size: Design.BaseFontSize.small)) + .foregroundStyle(.white.opacity(Design.Opacity.light)) + .frame(maxWidth: .infinity) + .padding(.top, Design.Spacing.large) .padding(.bottom, Design.Spacing.medium) }, onCancel: nil, @@ -377,7 +377,7 @@ struct GameStylePicker: View { title: style.displayName, subtitle: style.description, isSelected: selection == style, - accentColor: Color.Settings.accent, + accentColor: Color.Sheet.accent, action: { selection = style } ) } @@ -397,7 +397,7 @@ struct DeckCountPicker: View { title: count.displayName, subtitle: count.description, isSelected: selection == count, - accentColor: Color.Settings.accent, + accentColor: Color.Sheet.accent, action: { selection = count } ) } @@ -417,8 +417,8 @@ struct TableLimitsPicker: View { title: limit.displayName, subtitle: limit.detailedDescription, isSelected: selection == limit, - accentColor: Color.Settings.accent, - badge: { BadgePill(text: limit.description, isSelected: selection == limit, accentColor: Color.Settings.accent) }, + accentColor: Color.Sheet.accent, + badge: { BadgePill(text: limit.description, isSelected: selection == limit, accentColor: Color.Sheet.accent) }, action: { selection = limit } ) } diff --git a/Blackjack/Blackjack/Views/Sheets/StatisticsSheetView.swift b/Blackjack/Blackjack/Views/Sheets/StatisticsSheetView.swift index 7abf3c1..f4c86e0 100644 --- a/Blackjack/Blackjack/Views/Sheets/StatisticsSheetView.swift +++ b/Blackjack/Blackjack/Views/Sheets/StatisticsSheetView.swift @@ -72,7 +72,7 @@ struct StatisticsSheetView: View { StatBox(title: String(localized: "Rounds"), value: "\(totalRounds)", color: .white) StatBox(title: String(localized: "Win Rate"), value: formatPercent(winRate), color: winRate >= 50 ? .green : .orange) StatBox(title: String(localized: "Net"), value: formatMoney(totalWinnings), color: totalWinnings >= 0 ? .green : .red) - StatBox(title: String(localized: "Balance"), value: "$\(state.balance)", color: Color.Settings.accent) + StatBox(title: String(localized: "Balance"), value: "$\(state.balance)", color: Color.Sheet.accent) } } diff --git a/Blackjack/Blackjack/Views/Table/BettingZoneView.swift b/Blackjack/Blackjack/Views/Table/BettingZoneView.swift index 9dc0735..4149f03 100644 --- a/Blackjack/Blackjack/Views/Table/BettingZoneView.swift +++ b/Blackjack/Blackjack/Views/Table/BettingZoneView.swift @@ -15,6 +15,9 @@ struct BettingZoneView: View { let onTap: () -> Void @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize + @ScaledMetric(relativeTo: .caption) private var detailFontSize: CGFloat = Design.Size.handNumberFontSize + @ScaledMetric(relativeTo: .body) private var chipSize: CGFloat = Design.Size.bettingChipSize + @ScaledMetric(relativeTo: .body) private var zoneHeight: CGFloat = CasinoDesign.Size.bettingZoneHeight private var isAtMax: Bool { betAmount >= maxBet @@ -34,7 +37,7 @@ struct BettingZoneView: View { // Content if betAmount > 0 { // Show chip with amount (scaled) - ChipOnTableView(amount: betAmount, showMax: isAtMax, size: Design.Size.bettingChipSize) + ChipOnTableView(amount: betAmount, showMax: isAtMax, size: chipSize) } else { // Empty state VStack(spacing: Design.Spacing.small) { @@ -44,18 +47,18 @@ struct BettingZoneView: View { HStack(spacing: Design.Spacing.medium) { Text(String(localized: "Min: $\(minBet)")) - .font(.system(size: Design.Size.handNumberFontSize, weight: .medium)) + .font(.system(size: detailFontSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.light)) Text(String(localized: "Max: $\(maxBet.formatted())")) - .font(.system(size: Design.Size.handNumberFontSize, weight: .medium)) + .font(.system(size: detailFontSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.light)) } } } } .frame(maxWidth: .infinity) - .frame(height: Design.Size.bettingZoneHeightScaled) + .frame(height: zoneHeight) } .buttonStyle(.plain) .accessibilityLabel(betAmount > 0 ? "$\(betAmount) bet" + (isAtMax ? ", maximum" : "") : "Place bet") diff --git a/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift b/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift index b1ad001..4b18929 100644 --- a/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift +++ b/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift @@ -12,8 +12,15 @@ struct BlackjackTableView: View { @Bindable var state: GameState let onPlaceBet: () -> Void - /// Screen height passed from parent for responsive sizing - var screenHeight: CGFloat = 800 + // MARK: - Environment + + @Environment(\.verticalSizeClass) private var verticalSizeClass + + // MARK: - State + + /// Screen dimensions measured from container for responsive sizing + @State private var screenHeight: CGFloat = 800 + @State private var screenWidth: CGFloat = 375 /// Whether to show Hi-Lo card count values on cards. var showCardCount: Bool { state.settings.showCardCount } @@ -24,10 +31,44 @@ struct BlackjackTableView: View { @ScaledMetric(relativeTo: .title) private var valueFontSize: CGFloat = Design.BaseFontSize.xLarge @ScaledMetric(relativeTo: .caption) private var hintFontSize: CGFloat = Design.BaseFontSize.small - // MARK: - Layout + // MARK: - Dynamic Card Sizing - private let cardWidth: CGFloat = Design.Size.cardWidth - private let cardSpacing: CGFloat = Design.Size.cardOverlap + /// Whether we're in landscape mode + /// - iPhones: use verticalSizeClass == .compact + /// - iPads: use screen dimensions (since iPads always report .regular) + private var isLandscape: Bool { + if DeviceInfo.isPad { + return screenWidth > screenHeight + } + return verticalSizeClass == .compact + } + + /// Dynamic card width based on screen width + /// - iPhone SE (375pt): ~80pt + /// - iPhone 16 Pro (393pt): ~95pt + /// - iPhone 16 Pro Max (430pt): ~120pt + /// - iPad mini (landscape): ~90pt (reduced for smaller tablet screen) + /// - iPad: ~140pt (capped) + private var cardWidth: CGFloat { + // iPad mini in landscape gets smaller cards to fit better + if DeviceInfo.isPadMini && isLandscape { + return 90 + } + + let baseWidth: CGFloat = 80 // Minimum card width for smallest screens + let baseScreen: CGFloat = 375 // iPhone SE width + let scale: CGFloat = 0.7 // Scale factor (70% of width increase goes to cards) + let maxWidth: CGFloat = 140 // Cap for large screens + + let calculated = baseWidth + (screenWidth - baseScreen) * scale + return min(maxWidth, max(baseWidth, calculated)) + } + + /// Card overlap scales proportionally with card width + private var cardSpacing: CGFloat { + // Overlap ratio: roughly -55% of card width + cardWidth * -0.55 + } /// Fixed height for the hint area to prevent layout shifts private let hintAreaHeight: CGFloat = 44 @@ -116,6 +157,19 @@ struct BlackjackTableView: View { } .padding(.horizontal, Design.Spacing.large) .padding(.vertical, Design.Spacing.medium) + .background( + GeometryReader { geometry in + Color.clear + .onAppear { + screenHeight = geometry.size.height + screenWidth = geometry.size.width + } + .onChange(of: geometry.size) { _, newSize in + screenHeight = newSize.height + screenWidth = newSize.width + } + } + ) .debugBorder(showDebugBorders, color: .white, label: "TableView") .animation(.spring(duration: Design.Animation.springDuration), value: state.currentPhase) } diff --git a/Blackjack/Blackjack/Views/Table/HiLoCountBadge.swift b/Blackjack/Blackjack/Views/Table/HiLoCountBadge.swift index a525fed..da937fc 100644 --- a/Blackjack/Blackjack/Views/Table/HiLoCountBadge.swift +++ b/Blackjack/Blackjack/Views/Table/HiLoCountBadge.swift @@ -12,17 +12,22 @@ import CasinoKit struct HiLoCountBadge: View { let card: Card + @ScaledMetric(relativeTo: .caption2) private var fontSize: CGFloat = Design.Size.countBadgeFontSize + @ScaledMetric(relativeTo: .caption2) private var paddingH: CGFloat = Design.Size.countBadgePaddingH + @ScaledMetric(relativeTo: .caption2) private var paddingV: CGFloat = Design.Size.countBadgePaddingV + @ScaledMetric(relativeTo: .caption2) private var offsetAmount: CGFloat = Design.Size.countBadgeOffset + var body: some View { Text(card.hiLoDisplayText) - .font(.system(size: Design.Size.countBadgeFontSize, weight: .bold, design: .rounded)) + .font(.system(size: fontSize, weight: .bold, design: .rounded)) .foregroundStyle(badgeTextColor) - .padding(.horizontal, Design.Size.countBadgePaddingH) - .padding(.vertical, Design.Size.countBadgePaddingV) + .padding(.horizontal, paddingH) + .padding(.vertical, paddingV) .background( Capsule() .fill(badgeBackgroundColor) ) - .offset(x: -Design.Size.countBadgeOffset, y: Design.Size.countBadgeOffset) + .offset(x: -offsetAmount, y: offsetAmount) } private var badgeBackgroundColor: Color { diff --git a/Blackjack/Blackjack/Views/Table/HintViews.swift b/Blackjack/Blackjack/Views/Table/HintViews.swift index e689109..b69c4e7 100644 --- a/Blackjack/Blackjack/Views/Table/HintViews.swift +++ b/Blackjack/Blackjack/Views/Table/HintViews.swift @@ -14,17 +14,22 @@ import CasinoKit struct HintView: View { let hint: String + @ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.hintIconSize + @ScaledMetric(relativeTo: .body) private var fontSize: CGFloat = Design.Size.hintFontSize + @ScaledMetric(relativeTo: .body) private var paddingH: CGFloat = Design.Size.hintPaddingH + @ScaledMetric(relativeTo: .body) private var paddingV: CGFloat = Design.Size.hintPaddingV + var body: some View { HStack(spacing: Design.Spacing.small) { Image(systemName: "lightbulb.fill") - .font(.system(size: Design.Size.hintIconSize)) + .font(.system(size: iconSize)) .foregroundStyle(.yellow) Text(String(localized: "Hint: \(hint)")) - .font(.system(size: Design.Size.hintFontSize, weight: .medium)) + .font(.system(size: fontSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.strong)) } - .padding(.horizontal, Design.Size.hintPaddingH) - .padding(.vertical, Design.Size.hintPaddingV) + .padding(.horizontal, paddingH) + .padding(.vertical, paddingV) .background( Capsule() .fill(Color.black.opacity(Design.Opacity.light)) @@ -42,6 +47,11 @@ struct BettingHintView: View { let hint: String let trueCount: Double + @ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.hintIconSize + @ScaledMetric(relativeTo: .body) private var fontSize: CGFloat = Design.Size.hintFontSize + @ScaledMetric(relativeTo: .body) private var paddingH: CGFloat = Design.Size.hintPaddingH + @ScaledMetric(relativeTo: .body) private var paddingV: CGFloat = Design.Size.hintPaddingV + private var hintColor: Color { let tc = Int(trueCount.rounded()) if tc >= 2 { @@ -67,14 +77,14 @@ struct BettingHintView: View { var body: some View { HStack(spacing: Design.Spacing.small) { Image(systemName: icon) - .font(.system(size: Design.Size.hintIconSize)) + .font(.system(size: iconSize)) .foregroundStyle(hintColor) Text(hint) - .font(.system(size: Design.Size.hintFontSize, weight: .medium)) + .font(.system(size: fontSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.strong)) } - .padding(.horizontal, Design.Size.hintPaddingH) - .padding(.vertical, Design.Size.hintPaddingV) + .padding(.horizontal, paddingH) + .padding(.vertical, paddingV) .background( Capsule() .fill(Color.black.opacity(Design.Opacity.light)) diff --git a/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift b/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift index b48ca13..180aefc 100644 --- a/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift +++ b/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift @@ -71,7 +71,7 @@ struct InsurancePopupView: View { Capsule() .fill( LinearGradient( - colors: [Color.Button.goldLight, Color.Button.goldDark], + colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark], startPoint: .top, endPoint: .bottom ) @@ -84,7 +84,7 @@ struct InsurancePopupView: View { .padding(Design.Spacing.xLarge) .background( RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge) - .fill(Color.Modal.background) + .fill(Color.CasinoModal.backgroundDark) .shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusXLarge) ) .overlay( diff --git a/Blackjack/Blackjack/Views/Table/PlayerHandView.swift b/Blackjack/Blackjack/Views/Table/PlayerHandView.swift index 812bba9..5f5405c 100644 --- a/Blackjack/Blackjack/Views/Table/PlayerHandView.swift +++ b/Blackjack/Blackjack/Views/Table/PlayerHandView.swift @@ -25,48 +25,47 @@ struct PlayerHandsView: View { } var body: some View { - GeometryReader { geometry in - ScrollViewReader { proxy in - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: Design.Spacing.large) { - // Display hands in reverse order (right to left play order) - // Visual order: Hand 3, Hand 2, Hand 1 (left to right) - // Play order: Hand 1 played first (rightmost), then Hand 2, etc. - ForEach(hands.indices.reversed(), id: \.self) { index in - PlayerHandView( - hand: hands[index], - isActive: index == activeHandIndex && isPlayerTurn, - showCardCount: showCardCount, - // Hand numbers: rightmost (index 0) is Hand 1, played first - handNumber: hands.count > 1 ? index + 1 : nil, - cardWidth: cardWidth, - cardSpacing: cardSpacing - ) - .id(index) - } + ScrollViewReader { proxy in + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: Design.Spacing.large) { + // Display hands in reverse order (right to left play order) + // Visual order: Hand 3, Hand 2, Hand 1 (left to right) + // Play order: Hand 1 played first (rightmost), then Hand 2, etc. + ForEach(hands.indices.reversed(), id: \.self) { index in + PlayerHandView( + hand: hands[index], + isActive: index == activeHandIndex && isPlayerTurn, + showCardCount: showCardCount, + // Hand numbers: rightmost (index 0) is Hand 1, played first + handNumber: hands.count > 1 ? index + 1 : nil, + cardWidth: cardWidth, + cardSpacing: cardSpacing + ) + .id(index) } - .padding(.horizontal, Design.Spacing.large) - .frame(minWidth: geometry.size.width) } - .scrollClipDisabled() - .scrollBounceBehavior(.basedOnSize) - .onChange(of: activeHandIndex) { _, newIndex in - scrollToHand(proxy: proxy, index: newIndex) - } - .onChange(of: totalCardCount) { _, _ in - // Scroll to active hand when cards are added (hit) - scrollToHand(proxy: proxy, index: activeHandIndex) - } - .onChange(of: hands.count) { _, _ in - // Scroll to active hand when split occurs - scrollToHand(proxy: proxy, index: activeHandIndex) - } - .onAppear { - scrollToHand(proxy: proxy, index: activeHandIndex) + .padding(.horizontal, Design.Spacing.large) + .containerRelativeFrame(.horizontal) { length, _ in + length // Ensures content fills container width for centering } } + .scrollClipDisabled() + .scrollBounceBehavior(.basedOnSize) + .onChange(of: activeHandIndex) { _, newIndex in + scrollToHand(proxy: proxy, index: newIndex) + } + .onChange(of: totalCardCount) { _, _ in + // Scroll to active hand when cards are added (hit) + scrollToHand(proxy: proxy, index: activeHandIndex) + } + .onChange(of: hands.count) { _, _ in + // Scroll to active hand when split occurs + scrollToHand(proxy: proxy, index: activeHandIndex) + } + .onAppear { + scrollToHand(proxy: proxy, index: activeHandIndex) + } } - .frame(height: Design.Size.playerHandsHeight) } private func scrollToHand(proxy: ScrollViewProxy, index: Int) { @@ -89,6 +88,9 @@ struct PlayerHandView: View { @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize @ScaledMetric(relativeTo: .caption) private var handNumberSize: CGFloat = Design.Size.handNumberFontSize + @ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.handIconSize + @ScaledMetric(relativeTo: .body) private var hintPaddingH: CGFloat = Design.Size.hintPaddingH + @ScaledMetric(relativeTo: .body) private var hintPaddingV: CGFloat = Design.Size.hintPaddingV var body: some View { VStack(spacing: Design.Spacing.small) { @@ -126,8 +128,25 @@ struct PlayerHandView: View { ) ) ) + .overlay { + // Result badge - centered on cards + if let result = hand.result { + Text(result.displayText) + .font(.system(size: labelFontSize, weight: .black)) + .foregroundStyle(.white) + .padding(.horizontal, hintPaddingH) + .padding(.vertical, hintPaddingV) + .background( + Capsule() + .fill(result.color) + .shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium) + ) + .transition(.scale.combined(with: .opacity)) + } + } .contentShape(Rectangle()) .animation(.easeInOut(duration: Design.Animation.quick), value: isActive) + .animation(.spring(duration: Design.Animation.springDuration), value: hand.result != nil) // Hand info HStack(spacing: Design.Spacing.small) { @@ -145,29 +164,16 @@ struct PlayerHandView: View { if hand.isDoubledDown { Image(systemName: "xmark.circle.fill") - .font(.system(size: Design.Size.handIconSize)) + .font(.system(size: iconSize)) .foregroundStyle(.purple) } } - // Result badge - if let result = hand.result { - Text(result.displayText) - .font(.system(size: labelFontSize, weight: .black)) - .foregroundStyle(result.color) - .padding(.horizontal, Design.Size.hintPaddingH) - .padding(.vertical, Design.Size.hintPaddingV) - .background( - Capsule() - .fill(result.color.opacity(Design.Opacity.hint)) - ) - } - // Bet amount if hand.bet > 0 { HStack(spacing: Design.Spacing.xSmall) { Image(systemName: "dollarsign.circle.fill") - .font(.system(size: Design.Size.handIconSize)) + .font(.system(size: iconSize)) .foregroundStyle(.yellow) Text("\(hand.bet * (hand.isDoubledDown ? 2 : 1))") .font(.system(size: handNumberSize, weight: .bold, design: .rounded))