diff --git a/Blackjack/Blackjack/Theme/DesignConstants.swift b/Blackjack/Blackjack/Theme/DesignConstants.swift index 562320c..978e7f2 100644 --- a/Blackjack/Blackjack/Theme/DesignConstants.swift +++ b/Blackjack/Blackjack/Theme/DesignConstants.swift @@ -50,8 +50,8 @@ enum Design { // Hints static let hintFontSize: CGFloat = 15 static let hintIconSize: CGFloat = 24 - static let hintPaddingH: CGFloat = 18 - static let hintPaddingV: CGFloat = 12 + static let hintPaddingH: CGFloat = 10 + static let hintPaddingV: CGFloat = 10 // Hand icons static let handIconSize: CGFloat = 18 diff --git a/Blackjack/Blackjack/Views/Game/ActionButton.swift b/Blackjack/Blackjack/Views/Game/ActionButton.swift deleted file mode 100644 index 05c7876..0000000 --- a/Blackjack/Blackjack/Views/Game/ActionButton.swift +++ /dev/null @@ -1,108 +0,0 @@ -// -// ActionButton.swift -// Blackjack -// -// Reusable styled button for game actions. -// - -import SwiftUI -import CasinoKit - -struct ActionButton: View { - let title: String - let icon: String? - let style: ButtonStyle - let action: () -> Void - - enum ButtonStyle { - case primary // Gold gradient (Deal, New Round) - case destructive // Red (Clear) - case secondary // Subtle white - case custom(Color) // Game-specific colors (Hit, Stand, etc.) - - var foregroundColor: Color { - switch self { - case .primary: return .black - case .destructive, .secondary, .custom: return .white - } - } - } - - init(_ title: String, icon: String? = nil, style: ButtonStyle = .primary, action: @escaping () -> Void) { - self.title = title - self.icon = icon - self.style = style - self.action = action - } - - var body: some View { - Button(action: action) { - HStack(spacing: Design.Spacing.small) { - if let icon = icon { - Image(systemName: icon) - } - Text(title) - .lineLimit(1) - .minimumScaleFactor(CasinoDesign.MinScaleFactor.relaxed) - } - .font(.system(size: Design.BaseFontSize.large, weight: .semibold)) - .foregroundStyle(style.foregroundColor) - .padding(.horizontal, Design.Spacing.large) - .padding(.vertical, Design.Spacing.medium) - .background(backgroundView) - } - .accessibilityLabel(title) - } - - @ViewBuilder - private var backgroundView: some View { - switch style { - case .primary: - Capsule() - .fill( - LinearGradient( - colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark], - startPoint: .top, - endPoint: .bottom - ) - ) - case .destructive: - Capsule() - .fill(Color.red.opacity(Design.Opacity.heavy)) - case .secondary: - Capsule() - .fill(Color.white.opacity(Design.Opacity.hint)) - case .custom(let color): - Capsule() - .fill(color) - } - } -} - -// MARK: - Previews - -#Preview("Primary") { - ZStack { - Color.Table.felt.ignoresSafeArea() - ActionButton("Deal", icon: "play.fill", style: .primary) {} - } -} - -#Preview("Destructive") { - ZStack { - Color.Table.felt.ignoresSafeArea() - ActionButton("Clear", icon: "xmark.circle", style: .destructive) {} - } -} - -#Preview("Custom Colors") { - ZStack { - Color.Table.felt.ignoresSafeArea() - HStack(spacing: Design.Spacing.medium) { - ActionButton("Hit", style: .custom(Color.Button.hit)) {} - ActionButton("Stand", style: .custom(Color.Button.stand)) {} - ActionButton("Double", style: .custom(Color.Button.doubleDown)) {} - } - } -} - diff --git a/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift b/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift index 9da0e90..b589427 100644 --- a/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift +++ b/Blackjack/Blackjack/Views/Table/BlackjackTableView.swift @@ -73,8 +73,8 @@ struct BlackjackTableView: View { /// - iPad Pro 12.9" (~1366pt): ~150pt (capped) private var dealerPlayerSpacing: CGFloat { let baseline: CGFloat = 550 // Below this, use minimum - let scale: CGFloat = 0.2 // 20% of height above baseline - let minSpacing: CGFloat = 20 // Floor for smallest screens + let scale: CGFloat = 0.18 // 20% of height above baseline + let minSpacing: CGFloat = 10 // Floor for smallest screens let maxSpacing: CGFloat = 150 // Ceiling for largest screens let calculated = (screenHeight - baseline) * scale @@ -107,6 +107,7 @@ struct BlackjackTableView: View { cardWidth: cardWidth, cardSpacing: cardSpacing ) + .padding(.bottom, 5) .transition(.opacity) .debugBorder(showDebugBorders, color: .green, label: "Player") } diff --git a/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift b/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift index 180aefc..bf6217e 100644 --- a/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift +++ b/Blackjack/Blackjack/Views/Table/InsurancePopupView.swift @@ -30,17 +30,17 @@ struct InsurancePopupView: View { // Title Text(String(localized: "INSURANCE?")) - .font(.system(size: Design.BaseFontSize.xLarge, weight: .bold)) + .font(.system(size: Design.BaseFontSize.largeTitle, weight: .black, design: .rounded)) .foregroundStyle(.white) // Subtitle Text(String(localized: "Dealer showing Ace")) - .font(.system(size: Design.BaseFontSize.medium)) + .font(.system(size: Design.BaseFontSize.xxLarge)) .foregroundStyle(.white.opacity(Design.Opacity.strong)) // Cost info Text(String(localized: "Cost: $\(betAmount) (half your bet)")) - .font(.system(size: Design.BaseFontSize.small)) + .font(.system(size: Design.BaseFontSize.xxLarge)) .foregroundStyle(.white.opacity(Design.Opacity.medium)) .padding(.bottom, Design.Spacing.small) diff --git a/CasinoKit/Sources/CasinoKit/Resources/Localizable.xcstrings b/CasinoKit/Sources/CasinoKit/Resources/Localizable.xcstrings index d858c34..ce434f6 100644 --- a/CasinoKit/Sources/CasinoKit/Resources/Localizable.xcstrings +++ b/CasinoKit/Sources/CasinoKit/Resources/Localizable.xcstrings @@ -2066,6 +2066,7 @@ } }, "WIN" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { diff --git a/CasinoKit/Sources/CasinoKit/Views/Buttons/ActionButton.swift b/CasinoKit/Sources/CasinoKit/Views/Buttons/ActionButton.swift index 605b7ea..c4ddf42 100644 --- a/CasinoKit/Sources/CasinoKit/Views/Buttons/ActionButton.swift +++ b/CasinoKit/Sources/CasinoKit/Views/Buttons/ActionButton.swift @@ -28,6 +28,9 @@ public struct ActionButton: View { private let fontSize: CGFloat = 18 private let iconSize: CGFloat = 20 + /// Minimum button width to prevent tiny buttons for short words like "Hit" + private let minButtonWidth: CGFloat = 35 + /// Creates an action button. /// - Parameters: /// - title: The button title. @@ -58,15 +61,19 @@ public struct ActionButton: View { } Text(title) .font(.system(size: fontSize, weight: .bold)) + .lineLimit(1) + .minimumScaleFactor(CasinoDesign.MinScaleFactor.relaxed) } .foregroundStyle(style.foregroundColor) - .padding(.horizontal, CasinoDesign.Spacing.xxLarge) + .frame(minWidth: minButtonWidth) + .padding(.horizontal, CasinoDesign.Spacing.xLarge) .padding(.vertical, CasinoDesign.Spacing.medium) .background(style.background) .shadow(color: style.shadowColor, radius: CasinoDesign.Shadow.radiusMedium) } .disabled(!isEnabled) .opacity(isEnabled ? 1.0 : CasinoDesign.Opacity.medium) + .accessibilityLabel(title) } } @@ -78,12 +85,13 @@ public enum ActionButtonStyle { case destructive /// Secondary subtle button case secondary + /// Custom color button for game-specific actions + case custom(Color) var foregroundColor: Color { switch self { case .primary: return .black - case .destructive: return .white - case .secondary: return .white + case .destructive, .secondary, .custom: return .white } } @@ -105,6 +113,9 @@ public enum ActionButtonStyle { case .secondary: Capsule() .fill(Color.white.opacity(CasinoDesign.Opacity.hint)) + case .custom(let color): + Capsule() + .fill(color) } } @@ -112,7 +123,7 @@ public enum ActionButtonStyle { switch self { case .primary: return .yellow.opacity(CasinoDesign.Opacity.light) case .destructive: return .red.opacity(CasinoDesign.Opacity.light) - case .secondary: return .clear + case .secondary, .custom: return .clear } } } @@ -125,7 +136,21 @@ public enum ActionButtonStyle { ActionButton("Deal", icon: "play.fill", style: .primary) { } ActionButton("Clear", icon: "xmark.circle", style: .destructive) { } ActionButton("Stand", style: .secondary) { } - ActionButton("Hit", style: .primary, isEnabled: false) { } + ActionButton("Hit", style: .custom(.green)) { } + ActionButton("Disabled", style: .primary, isEnabled: false) { } + } + } +} + +#Preview("BlackJack Action Buttons") { + ZStack { + Color.CasinoTable.felt.ignoresSafeArea() + + HStack(spacing: CasinoDesign.Spacing.medium) { + ActionButton("Hit", style: .custom(.green)) { } + ActionButton("Stand", style: .secondary) { } + ActionButton("Double", style: .custom(Color.purple)) {} + ActionButton("Split", style: .custom(Color.yellow)) {} } } } diff --git a/CasinoKit/Sources/CasinoKit/Views/Cards/HandDisplayView.swift b/CasinoKit/Sources/CasinoKit/Views/Cards/HandDisplayView.swift deleted file mode 100644 index 835ad61..0000000 --- a/CasinoKit/Sources/CasinoKit/Views/Cards/HandDisplayView.swift +++ /dev/null @@ -1,194 +0,0 @@ -// -// HandDisplayView.swift -// CasinoKit -// -// A generic view for displaying a hand of cards with optional overlap. -// - -import SwiftUI - -/// A view displaying a hand of cards with configurable layout. -public struct HandDisplayView: View { - /// The cards in the hand. - public let cards: [Card] - - /// Which cards are face up (by index). - public let cardsFaceUp: [Bool] - - /// The width of each card. - public let cardWidth: CGFloat - - /// The overlap between cards (negative = overlap, positive = gap). - public let cardSpacing: CGFloat - - /// Whether this hand is the winner. - public let isWinner: Bool - - /// Optional label to show (e.g., "PLAYER", "DEALER"). - public let label: String? - - /// Optional value badge to show. - public let value: Int? - - /// Badge color for the value. - public let valueColor: Color - - /// Maximum number of card slots to reserve space for. - public let maxCards: Int - - // Layout - @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = 14 - @ScaledMetric(relativeTo: .caption) private var winBadgeFontSize: CGFloat = 10 - - /// Creates a hand display view. - /// - Parameters: - /// - cards: The cards to display. - /// - cardsFaceUp: Which cards are face up. - /// - cardWidth: Width of each card. - /// - cardSpacing: Spacing between cards (negative for overlap). - /// - isWinner: Whether to show winner styling. - /// - label: Optional label above cards. - /// - value: Optional value badge to show. - /// - valueColor: Color for value badge. - /// - maxCards: Max cards to reserve space for (default: 3). - public init( - cards: [Card], - cardsFaceUp: [Bool] = [], - cardWidth: CGFloat = 45, - cardSpacing: CGFloat = -12, - isWinner: Bool = false, - label: String? = nil, - value: Int? = nil, - valueColor: Color = .blue, - maxCards: Int = 3 - ) { - self.cards = cards - self.cardsFaceUp = cardsFaceUp - self.cardWidth = cardWidth - self.cardSpacing = cardSpacing - self.isWinner = isWinner - self.label = label - self.value = value - self.valueColor = valueColor - self.maxCards = maxCards - } - - /// Card height based on aspect ratio. - private var cardHeight: CGFloat { - cardWidth * CasinoDesign.Size.cardAspectRatio - } - - /// Fixed container width based on max cards. - private var containerWidth: CGFloat { - if maxCards <= 1 { - return cardWidth + CasinoDesign.Spacing.xSmall * 2 - } - let cardsWidth = cardWidth + (cardWidth + cardSpacing) * CGFloat(maxCards - 1) - return cardsWidth + CasinoDesign.Spacing.xSmall * 2 - } - - /// Fixed container height. - private var containerHeight: CGFloat { - cardHeight + CasinoDesign.Spacing.xSmall * 2 - } - - public var body: some View { - VStack(spacing: CasinoDesign.Spacing.small) { - // Label with optional value badge - if label != nil || value != nil { - HStack(spacing: CasinoDesign.Spacing.small) { - if let label = label { - Text(label) - .font(.system(size: labelFontSize, weight: .bold, design: .rounded)) - .foregroundStyle(.white) - } - - if let value = value, !cards.isEmpty { - ValueBadge(value: value, color: valueColor) - } - } - .frame(minHeight: CasinoDesign.Spacing.xxxLarge) - } - - // Cards container - ZStack { - // Fixed-size container - Color.clear - .frame(width: containerWidth, height: containerHeight) - - // Cards - HStack(spacing: cards.isEmpty ? CasinoDesign.Spacing.small : cardSpacing) { - if cards.isEmpty { - // Placeholders - ForEach(0..