From 884bc988f61e7a14ca4d5170e6623e958a5afc3a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Dec 2025 18:13:45 -0600 Subject: [PATCH] Signed-off-by: Matt Bruce --- Baccarat/Theme/DesignConstants.swift | 50 +++++++++++ Baccarat/Views/CardView.swift | 42 ++++----- Baccarat/Views/ChipSelectorView.swift | 12 +-- Baccarat/Views/ChipView.swift | 62 +++++++------- Baccarat/Views/GameTableView.swift | 100 +++++++++++----------- Baccarat/Views/ResultBannerView.swift | 46 +++++----- Baccarat/Views/RoadMapView.swift | 38 ++++++--- Baccarat/Views/SettingsView.swift | 118 +++++++++++++------------- 8 files changed, 268 insertions(+), 200 deletions(-) diff --git a/Baccarat/Theme/DesignConstants.swift b/Baccarat/Theme/DesignConstants.swift index 226dae8..d3482e4 100644 --- a/Baccarat/Theme/DesignConstants.swift +++ b/Baccarat/Theme/DesignConstants.swift @@ -67,17 +67,20 @@ enum Design { enum Size { static let chipSmall: CGFloat = 36 static let chipMedium: CGFloat = 50 + static let chipSelector: CGFloat = 50 static let cardWidthSmall: CGFloat = 45 static let cardWidthMedium: CGFloat = 55 static let cardWidthLarge: CGFloat = 65 static let valueBadge: CGFloat = 26 static let checkmark: CGFloat = 22 static let tableAspectRatio: CGFloat = 1.6 + static let roadMapCell: CGFloat = 16 } // MARK: - Animation enum Animation { + static let quick: Double = 0.3 static let springDuration: Double = 0.4 static let springBounce: Double = 0.3 static let fadeInDuration: Double = 0.3 @@ -90,7 +93,9 @@ enum Design { static let disabled: Double = 0.5 static let subtle: Double = 0.1 static let light: Double = 0.3 + static let overlay: Double = 0.4 static let medium: Double = 0.5 + static let secondary: Double = 0.5 static let strong: Double = 0.7 static let heavy: Double = 0.8 static let nearOpaque: Double = 0.85 @@ -100,6 +105,7 @@ enum Design { enum LineWidth { static let thin: CGFloat = 1 + static let standard: CGFloat = 2 static let medium: CGFloat = 2 static let thick: CGFloat = 3 static let heavy: CGFloat = 4 @@ -128,6 +134,7 @@ extension Color { static let backgroundDark = Color(red: 0.01, green: 0.12, blue: 0.06) static let backgroundLight = Color(red: 0.03, green: 0.25, blue: 0.12) static let baseDark = Color(red: 0.02, green: 0.15, blue: 0.08) + static let preview = Color(red: 0.0, green: 0.3, blue: 0.15) } // MARK: - Border Colors @@ -171,6 +178,49 @@ extension Color { enum Chip { static let gold = Color(red: 0.8, green: 0.65, blue: 0.2) + + // Chip base colors + static let tenBase = Color(red: 0.2, green: 0.4, blue: 0.8) + static let tenHighlight = Color(red: 0.3, green: 0.5, blue: 0.9) + static let twentyFiveBase = Color(red: 0.1, green: 0.6, blue: 0.3) + static let twentyFiveHighlight = Color(red: 0.2, green: 0.7, blue: 0.4) + static let fiftyBase = Color(red: 0.8, green: 0.5, blue: 0.1) + static let fiftyHighlight = Color(red: 0.9, green: 0.6, blue: 0.2) + static let hundredBase = Color(red: 0.1, green: 0.1, blue: 0.1) + static let hundredHighlight = Color(red: 0.3, green: 0.3, blue: 0.3) + static let fiveHundredBase = Color(red: 0.6, green: 0.2, blue: 0.6) + static let fiveHundredHighlight = Color(red: 0.7, green: 0.3, blue: 0.7) + static let thousandBase = Color(red: 0.8, green: 0.65, blue: 0.2) + static let thousandHighlight = Color(red: 0.9, green: 0.75, blue: 0.3) + static let fiveThousandBase = Color(red: 0.7, green: 0.1, blue: 0.2) + static let fiveThousandHighlight = Color(red: 0.85, green: 0.2, blue: 0.3) + static let tenThousandBase = Color(red: 0.2, green: 0.5, blue: 0.5) + static let tenThousandHighlight = Color(red: 0.3, green: 0.6, blue: 0.6) + static let twentyFiveThousandBase = Color(red: 0.5, green: 0.3, blue: 0.1) + static let twentyFiveThousandHighlight = Color(red: 0.65, green: 0.45, blue: 0.2) + static let fiftyThousandBase = Color(red: 0.75, green: 0.75, blue: 0.8) + static let fiftyThousandHighlight = Color(red: 0.85, green: 0.85, blue: 0.9) + static let hundredThousandBase = Color(red: 0.9, green: 0.1, blue: 0.3) + static let hundredThousandHighlight = Color(red: 1.0, green: 0.2, blue: 0.4) + + // Accent stripe colors + static let goldStripe = Color(red: 0.9, green: 0.75, blue: 0.3) + static let darkStripe = Color(red: 0.2, green: 0.2, blue: 0.3) + static let goldRubyStripe = Color(red: 0.9, green: 0.85, blue: 0.3) + } + + // MARK: - Card Colors + + enum Card { + // Card back + static let backDark = Color(red: 0.6, green: 0.1, blue: 0.15) + static let backLight = Color(red: 0.4, green: 0.05, blue: 0.1) + static let patternLight = Color(red: 0.9, green: 0.7, blue: 0.4) + static let patternDark = Color(red: 0.7, green: 0.5, blue: 0.2) + static let innerDark = Color(red: 0.5, green: 0.08, blue: 0.12) + static let innerLight = Color(red: 0.35, green: 0.04, blue: 0.08) + static let diamondPattern = Color(red: 0.9, green: 0.7, blue: 0.4) + static let logoText = Color(red: 0.4, green: 0.05, blue: 0.1) } // MARK: - Modal Colors diff --git a/Baccarat/Views/CardView.swift b/Baccarat/Views/CardView.swift index 1aae806..cbe26c8 100644 --- a/Baccarat/Views/CardView.swift +++ b/Baccarat/Views/CardView.swift @@ -123,12 +123,12 @@ struct CardBackView: View { var body: some View { ZStack { // Base - RoundedRectangle(cornerRadius: 8) + RoundedRectangle(cornerRadius: Design.CornerRadius.small) .fill( LinearGradient( colors: [ - Color(red: 0.6, green: 0.1, blue: 0.15), - Color(red: 0.4, green: 0.05, blue: 0.1) + Color.Card.backDark, + Color.Card.backLight ], startPoint: .topLeading, endPoint: .bottomTrailing @@ -136,26 +136,26 @@ struct CardBackView: View { ) // Border - RoundedRectangle(cornerRadius: 8) + RoundedRectangle(cornerRadius: Design.CornerRadius.small) .strokeBorder( LinearGradient( colors: [ - Color(red: 0.9, green: 0.7, blue: 0.4), - Color(red: 0.7, green: 0.5, blue: 0.2) + Color.Card.patternLight, + Color.Card.patternDark ], startPoint: .topLeading, endPoint: .bottomTrailing ), - lineWidth: 2 + lineWidth: Design.LineWidth.medium ) // Inner pattern area - RoundedRectangle(cornerRadius: 4) + RoundedRectangle(cornerRadius: Design.CornerRadius.small / 2) .fill( LinearGradient( colors: [ - Color(red: 0.5, green: 0.08, blue: 0.12), - Color(red: 0.35, green: 0.04, blue: 0.08) + Color.Card.innerDark, + Color.Card.innerLight ], startPoint: .top, endPoint: .bottom @@ -166,18 +166,18 @@ struct CardBackView: View { // Diamond pattern overlay DiamondPatternView() .foregroundStyle( - Color(red: 0.9, green: 0.7, blue: 0.4).opacity(0.3) + Color.Card.diamondPattern.opacity(Design.Opacity.light) ) .padding(width * 0.12) - .clipShape(RoundedRectangle(cornerRadius: 4)) + .clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.small / 2)) // Center emblem Circle() .fill( RadialGradient( colors: [ - Color(red: 0.9, green: 0.7, blue: 0.4), - Color(red: 0.7, green: 0.5, blue: 0.2) + Color.Card.patternLight, + Color.Card.patternDark ], center: .center, startRadius: 0, @@ -189,10 +189,10 @@ struct CardBackView: View { // B for Baccarat Text("B") .font(.system(size: width * 0.18, weight: .bold, design: .serif)) - .foregroundStyle(Color(red: 0.4, green: 0.05, blue: 0.1)) + .foregroundStyle(Color.Card.logoText) } .frame(width: width, height: height) - .shadow(color: .black.opacity(0.3), radius: 4, x: 2, y: 2) + .shadow(color: .black.opacity(Design.Opacity.light), radius: Design.Shadow.radiusSmall, x: 2, y: 2) } } @@ -230,10 +230,10 @@ struct CardPlaceholderView: View { } var body: some View { - RoundedRectangle(cornerRadius: 8) + RoundedRectangle(cornerRadius: Design.CornerRadius.small) .strokeBorder( - Color.white.opacity(0.3), - style: StrokeStyle(lineWidth: 2, dash: [8, 4]) + Color.white.opacity(Design.Opacity.light), + style: StrokeStyle(lineWidth: Design.LineWidth.medium, dash: [8, 4]) ) .frame(width: width, height: height) } @@ -241,10 +241,10 @@ struct CardPlaceholderView: View { #Preview { ZStack { - Color(red: 0.0, green: 0.3, blue: 0.15) + Color.Table.preview .ignoresSafeArea() - HStack(spacing: 20) { + HStack(spacing: Design.Spacing.xLarge) { CardView(card: Card(suit: .hearts, rank: .ace), isFaceUp: true) CardView(card: Card(suit: .spades, rank: .king), isFaceUp: true) CardView(card: Card(suit: .diamonds, rank: .seven), isFaceUp: false) diff --git a/Baccarat/Views/ChipSelectorView.swift b/Baccarat/Views/ChipSelectorView.swift index b082c88..7f9bc8c 100644 --- a/Baccarat/Views/ChipSelectorView.swift +++ b/Baccarat/Views/ChipSelectorView.swift @@ -28,24 +28,24 @@ struct ChipSelectorView: View { var body: some View { ScrollView(.horizontal) { - HStack(spacing: 10) { + HStack(spacing: Design.Spacing.small) { ForEach(availableChips) { denomination in Button { selectedChip = denomination } label: { ChipView( denomination: denomination, - size: 50, + size: Design.Size.chipSelector, isSelected: selectedChip == denomination ) } .buttonStyle(.plain) - .opacity(balance >= denomination.rawValue ? 1.0 : 0.4) + .opacity(balance >= denomination.rawValue ? 1.0 : Design.Opacity.overlay) .disabled(balance < denomination.rawValue) } } .padding(.horizontal) - .padding(.vertical, 6) // Extra padding for selection scale effect + .padding(.vertical, Design.Spacing.xSmall) // Extra padding for selection scale effect } .scrollIndicators(.hidden) .onChange(of: balance) { _, newBalance in @@ -61,10 +61,10 @@ struct ChipSelectorView: View { #Preview { ZStack { - Color(red: 0.0, green: 0.3, blue: 0.15) + Color.Table.preview .ignoresSafeArea() - VStack(spacing: 20) { + VStack(spacing: Design.Spacing.xLarge) { Text("Balance: $50,000") .foregroundStyle(.white) diff --git a/Baccarat/Views/ChipView.swift b/Baccarat/Views/ChipView.swift index 35eaae5..0a9eb46 100644 --- a/Baccarat/Views/ChipView.swift +++ b/Baccarat/Views/ChipView.swift @@ -68,34 +68,34 @@ enum ChipDenomination: Int, CaseIterable, Identifiable { /// The primary color for this chip. var primaryColor: Color { switch self { - case .ten: return Color(red: 0.2, green: 0.4, blue: 0.8) // Blue - case .twentyFive: return Color(red: 0.1, green: 0.6, blue: 0.3) // Green - case .fifty: return Color(red: 0.8, green: 0.5, blue: 0.1) // Orange - case .hundred: return Color(red: 0.1, green: 0.1, blue: 0.1) // Black - case .fiveHundred: return Color(red: 0.6, green: 0.2, blue: 0.6) // Purple - case .thousand: return Color(red: 0.8, green: 0.65, blue: 0.2) // Gold - case .fiveThousand: return Color(red: 0.7, green: 0.1, blue: 0.2) // Crimson - case .tenThousand: return Color(red: 0.2, green: 0.5, blue: 0.5) // Teal - case .twentyFiveThousand: return Color(red: 0.5, green: 0.3, blue: 0.1) // Bronze - case .fiftyThousand: return Color(red: 0.75, green: 0.75, blue: 0.8) // Platinum - case .hundredThousand: return Color(red: 0.9, green: 0.1, blue: 0.3) // Ruby + case .ten: return Color.Chip.tenBase + case .twentyFive: return Color.Chip.twentyFiveBase + case .fifty: return Color.Chip.fiftyBase + case .hundred: return Color.Chip.hundredBase + case .fiveHundred: return Color.Chip.fiveHundredBase + case .thousand: return Color.Chip.thousandBase + case .fiveThousand: return Color.Chip.fiveThousandBase + case .tenThousand: return Color.Chip.tenThousandBase + case .twentyFiveThousand: return Color.Chip.twentyFiveThousandBase + case .fiftyThousand: return Color.Chip.fiftyThousandBase + case .hundredThousand: return Color.Chip.hundredThousandBase } } /// The secondary/accent color for this chip. var secondaryColor: Color { switch self { - case .ten: return Color(red: 0.3, green: 0.5, blue: 0.9) - case .twentyFive: return Color(red: 0.2, green: 0.7, blue: 0.4) - case .fifty: return Color(red: 0.9, green: 0.6, blue: 0.2) - case .hundred: return Color(red: 0.3, green: 0.3, blue: 0.3) - case .fiveHundred: return Color(red: 0.7, green: 0.3, blue: 0.7) - case .thousand: return Color(red: 0.9, green: 0.75, blue: 0.3) - case .fiveThousand: return Color(red: 0.85, green: 0.2, blue: 0.3) - case .tenThousand: return Color(red: 0.3, green: 0.6, blue: 0.6) - case .twentyFiveThousand: return Color(red: 0.65, green: 0.45, blue: 0.2) - case .fiftyThousand: return Color(red: 0.85, green: 0.85, blue: 0.9) - case .hundredThousand: return Color(red: 1.0, green: 0.2, blue: 0.4) + case .ten: return Color.Chip.tenHighlight + case .twentyFive: return Color.Chip.twentyFiveHighlight + case .fifty: return Color.Chip.fiftyHighlight + case .hundred: return Color.Chip.hundredHighlight + case .fiveHundred: return Color.Chip.fiveHundredHighlight + case .thousand: return Color.Chip.thousandHighlight + case .fiveThousand: return Color.Chip.fiveThousandHighlight + case .tenThousand: return Color.Chip.tenThousandHighlight + case .twentyFiveThousand: return Color.Chip.twentyFiveThousandHighlight + case .fiftyThousand: return Color.Chip.fiftyThousandHighlight + case .hundredThousand: return Color.Chip.hundredThousandHighlight } } @@ -103,14 +103,14 @@ enum ChipDenomination: Int, CaseIterable, Identifiable { var stripeColor: Color { switch self { case .ten, .twentyFive, .fifty: return .white - case .hundred: return Color(red: 0.9, green: 0.75, blue: 0.3) + case .hundred: return Color.Chip.goldStripe case .fiveHundred: return .white case .thousand: return .black - case .fiveThousand: return Color(red: 0.9, green: 0.75, blue: 0.3) // Gold stripes + case .fiveThousand: return Color.Chip.goldStripe case .tenThousand: return .white - case .twentyFiveThousand: return Color(red: 0.9, green: 0.75, blue: 0.3) - case .fiftyThousand: return Color(red: 0.2, green: 0.2, blue: 0.3) // Dark stripes - case .hundredThousand: return Color(red: 0.9, green: 0.85, blue: 0.3) // Gold stripes + case .twentyFiveThousand: return Color.Chip.goldStripe + case .fiftyThousand: return Color.Chip.darkStripe + case .hundredThousand: return Color.Chip.goldRubyStripe } } } @@ -274,13 +274,13 @@ struct ChipStackView: View { #Preview { ZStack { - Color(red: 0.0, green: 0.3, blue: 0.15) + Color.Table.preview .ignoresSafeArea() - VStack(spacing: 30) { - HStack(spacing: 15) { + VStack(spacing: Design.Spacing.xxxLarge) { + HStack(spacing: Design.Spacing.xLarge) { ForEach(ChipDenomination.allCases) { denom in - ChipView(denomination: denom, size: 50) + ChipView(denomination: denom, size: Design.Size.chipSelector) } } diff --git a/Baccarat/Views/GameTableView.swift b/Baccarat/Views/GameTableView.swift index 31d14f3..59e0a59 100644 --- a/Baccarat/Views/GameTableView.swift +++ b/Baccarat/Views/GameTableView.swift @@ -46,7 +46,7 @@ struct GameTableView: View { onSettings: { showSettings = true } ) - Spacer(minLength: 4) + Spacer(minLength: Design.Spacing.xSmall) // Cards display area CardsDisplayArea( @@ -61,7 +61,7 @@ struct GameTableView: View { isTie: isTie ) - Spacer(minLength: 4) + Spacer(minLength: Design.Spacing.xSmall) // Road map history if settings.showHistory && !state.roundHistory.isEmpty { @@ -69,16 +69,16 @@ struct GameTableView: View { .padding(.horizontal) } - Spacer(minLength: 8) + Spacer(minLength: Design.Spacing.small) // Mini Baccarat betting table MiniBaccaratTableView( gameState: state, selectedChip: selectedChip ) - .padding(.horizontal, 12) + .padding(.horizontal, Design.Spacing.medium) - Spacer(minLength: 8) + Spacer(minLength: Design.Spacing.small) // Chip selector - shows higher chips as you win more! ChipSelectorView( @@ -86,7 +86,7 @@ struct GameTableView: View { balance: state.balance, maxBet: state.maxBet ) - .padding(.bottom, 12) + .padding(.bottom, Design.Spacing.medium) // Action buttons ActionButtonsView( @@ -100,7 +100,7 @@ struct GameTableView: View { onNewRound: { state.newRound() } ) .padding(.horizontal) - .padding(.bottom, 4) + .padding(.bottom, Design.Spacing.xSmall) } .safeAreaPadding(.bottom) @@ -295,11 +295,11 @@ struct CardsDisplayArea: View { @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = 14 var body: some View { - HStack(spacing: 32) { + HStack(spacing: Design.Spacing.xxxLarge) { // Player side - VStack(spacing: 10) { + VStack(spacing: Design.Spacing.small) { // Label with value - HStack(spacing: 8) { + HStack(spacing: Design.Spacing.small) { Text("PLAYER") .font(.system(size: labelFontSize, weight: .bold, design: .rounded)) .foregroundStyle(.white) @@ -319,9 +319,9 @@ struct CardsDisplayArea: View { } // Banker side - VStack(spacing: 10) { + VStack(spacing: Design.Spacing.small) { // Label with value - HStack(spacing: 8) { + HStack(spacing: Design.Spacing.small) { Text("BANKER") .font(.system(size: labelFontSize, weight: .bold, design: .rounded)) .foregroundStyle(.white) @@ -340,11 +340,11 @@ struct CardsDisplayArea: View { ) } } - .padding(.top, 16) - .padding(.bottom, 14) - .padding(.horizontal, 20) + .padding(.top, Design.Spacing.large) + .padding(.bottom, Design.Spacing.xLarge) + .padding(.horizontal, Design.Spacing.xLarge) .background( - RoundedRectangle(cornerRadius: 14) + RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge) .fill(Color.black.opacity(0.25)) ) .padding(.horizontal) @@ -385,12 +385,12 @@ struct CompactHandView: View { } } } - .padding(6) + .padding(Design.Spacing.xSmall) .background( - RoundedRectangle(cornerRadius: 8) + RoundedRectangle(cornerRadius: Design.CornerRadius.small) .strokeBorder( isWinner ? Color.yellow : Color.clear, - lineWidth: 2 + lineWidth: Design.LineWidth.standard ) ) .overlay(alignment: .bottom) { @@ -398,13 +398,13 @@ struct CompactHandView: View { Text("WIN") .font(.system(size: winBadgeFontSize, weight: .black)) .foregroundStyle(.black) - .padding(.horizontal, 8) - .padding(.vertical, 2) + .padding(.horizontal, Design.Spacing.small) + .padding(.vertical, Design.Spacing.xxSmall) .background( Capsule() .fill(Color.yellow) ) - .offset(y: 10) + .offset(y: Design.Spacing.small) } } } @@ -437,13 +437,13 @@ struct TableBackgroundView: View { var body: some View { ZStack { // Base dark green - Color(red: 0.02, green: 0.15, blue: 0.08) + Color.Table.baseDark // Radial gradient for depth RadialGradient( colors: [ - Color(red: 0.03, green: 0.25, blue: 0.12), - Color(red: 0.01, green: 0.12, blue: 0.06) + Color.Table.backgroundLight, + Color.Table.backgroundDark ], center: .center, startRadius: 50, @@ -452,7 +452,7 @@ struct TableBackgroundView: View { // Subtle felt texture FeltPatternView() - .opacity(0.03) + .opacity(Design.Opacity.subtle / 3) } .ignoresSafeArea() } @@ -493,13 +493,13 @@ struct TopBarView: View { var body: some View { HStack { // Balance display - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text("BALANCE") .font(.system(size: labelFontSize, weight: .medium, design: .rounded)) .foregroundStyle(.white.opacity(0.6)) .tracking(1) - HStack(spacing: 4) { + HStack(spacing: Design.Spacing.xSmall) { Text("$") .font(.system(size: currencyFontSize, weight: .bold)) .foregroundStyle(.yellow.opacity(0.8)) @@ -508,27 +508,27 @@ struct TopBarView: View { .font(.system(size: balanceFontSize, weight: .black, design: .rounded)) .foregroundStyle(.white) .contentTransition(.numericText()) - .animation(.spring(duration: 0.3), value: balance) + .animation(.spring(duration: Design.Animation.quick), value: balance) } } - .padding(.horizontal, 14) - .padding(.vertical, 6) + .padding(.horizontal, Design.Spacing.xLarge) + .padding(.vertical, Design.Spacing.xSmall) .background( Capsule() - .fill(Color.black.opacity(0.4)) + .fill(Color.black.opacity(Design.Opacity.overlay)) ) Spacer() // Cards remaining indicator (if enabled) if showCardsRemaining { - HStack(spacing: 4) { + HStack(spacing: Design.Spacing.xSmall) { Image(systemName: "rectangle.portrait.on.rectangle.portrait.fill") .font(.system(size: smallFontSize)) Text("\(cardsRemaining)") .font(.system(size: smallFontSize, weight: .medium)) } - .foregroundStyle(.white.opacity(0.5)) + .foregroundStyle(.white.opacity(Design.Opacity.secondary)) Spacer() } @@ -538,25 +538,25 @@ struct TopBarView: View { .labelStyle(.iconOnly) .font(.system(size: buttonFontSize)) .foregroundStyle(.white.opacity(0.6)) - .padding(8) + .padding(Design.Spacing.small) .background( Circle() - .fill(Color.black.opacity(0.4)) + .fill(Color.black.opacity(Design.Opacity.overlay)) ) // Reset button Button("Reset", systemImage: "arrow.counterclockwise", action: onReset) .font(.system(size: smallFontSize, weight: .medium)) .foregroundStyle(.white.opacity(0.6)) - .padding(.horizontal, 10) - .padding(.vertical, 6) + .padding(.horizontal, Design.Spacing.small) + .padding(.vertical, Design.Spacing.xSmall) .background( Capsule() - .fill(Color.black.opacity(0.4)) + .fill(Color.black.opacity(Design.Opacity.overlay)) ) } .padding(.horizontal) - .padding(.top, 4) + .padding(.top, Design.Spacing.xSmall) } } @@ -574,14 +574,14 @@ struct ActionButtonsView: View { @ScaledMetric(relativeTo: .body) private var statusFontSize: CGFloat = 14 var body: some View { - HStack(spacing: 12) { + HStack(spacing: Design.Spacing.medium) { if gameState.currentPhase == .betting { // Clear bets button Button("Clear", systemImage: "xmark.circle", action: onClear) .font(.system(size: clearButtonFontSize, weight: .semibold)) .foregroundStyle(.white) - .padding(.horizontal, 20) - .padding(.vertical, 12) + .padding(.horizontal, Design.Spacing.xLarge) + .padding(.vertical, Design.Spacing.medium) .background( Capsule() .fill(Color.Button.destructive) @@ -593,8 +593,8 @@ struct ActionButtonsView: View { Button("Deal", systemImage: "play.fill", action: onDeal) .font(.system(size: primaryButtonFontSize, weight: .bold)) .foregroundStyle(.black) - .padding(.horizontal, 32) - .padding(.vertical, 12) + .padding(.horizontal, Design.Spacing.xxxLarge) + .padding(.vertical, Design.Spacing.medium) .background( Capsule() .fill( @@ -613,8 +613,8 @@ struct ActionButtonsView: View { Button("New Round", systemImage: "arrow.right.circle", action: onNewRound) .font(.system(size: primaryButtonFontSize, weight: .bold)) .foregroundStyle(.black) - .padding(.horizontal, 32) - .padding(.vertical, 12) + .padding(.horizontal, Design.Spacing.xxxLarge) + .padding(.vertical, Design.Spacing.medium) .background( Capsule() .fill( @@ -628,7 +628,7 @@ struct ActionButtonsView: View { .shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium) } else { // Playing indicator - HStack(spacing: 6) { + HStack(spacing: Design.Spacing.xSmall) { ProgressView() .tint(.white) .scaleEffect(0.8) @@ -636,8 +636,8 @@ struct ActionButtonsView: View { .font(.system(size: statusFontSize, weight: .medium)) .foregroundStyle(.white.opacity(Design.Opacity.heavy)) } - .padding(.horizontal, 20) - .padding(.vertical, 12) + .padding(.horizontal, Design.Spacing.xLarge) + .padding(.vertical, Design.Spacing.medium) } } } diff --git a/Baccarat/Views/ResultBannerView.swift b/Baccarat/Views/ResultBannerView.swift index 672b85a..0afe6e3 100644 --- a/Baccarat/Views/ResultBannerView.swift +++ b/Baccarat/Views/ResultBannerView.swift @@ -16,18 +16,23 @@ struct ResultBannerView: View { @State private var showText = false @State private var showWinnings = false + // MARK: - Scaled Font Sizes (Dynamic Type) + + @ScaledMetric(relativeTo: .largeTitle) private var resultFontSize: CGFloat = Design.BaseFontSize.largeTitle + @ScaledMetric(relativeTo: .title2) private var winningsFontSize: CGFloat = 28 + var body: some View { ZStack { // Background overlay - Color.black.opacity(showBanner ? 0.5 : 0) + Color.black.opacity(showBanner ? Design.Opacity.medium : 0) .ignoresSafeArea() - .animation(.easeIn(duration: 0.3), value: showBanner) + .animation(.easeIn(duration: Design.Animation.fadeInDuration), value: showBanner) // Banner - VStack(spacing: 20) { + VStack(spacing: Design.Spacing.xLarge) { // Result text Text(result.displayText) - .font(.system(size: 36, weight: .black, design: .rounded)) + .font(.system(size: resultFontSize, weight: .black, design: .rounded)) .foregroundStyle( LinearGradient( colors: [.white, result.color], @@ -35,13 +40,13 @@ struct ResultBannerView: View { endPoint: .bottom ) ) - .shadow(color: result.color.opacity(0.8), radius: 10) + .shadow(color: result.color.opacity(Design.Opacity.heavy), radius: Design.Shadow.radiusLarge) .scaleEffect(showText ? 1.0 : 0.5) .opacity(showText ? 1.0 : 0) // Winnings display if winnings != 0 { - HStack(spacing: 8) { + HStack(spacing: Design.Spacing.small) { if winnings > 0 { Image(systemName: "plus.circle.fill") .foregroundStyle(.green) @@ -54,14 +59,14 @@ struct ResultBannerView: View { .foregroundStyle(.red) } } - .font(.system(size: 28, weight: .bold, design: .rounded)) + .font(.system(size: winningsFontSize, weight: .bold, design: .rounded)) .scaleEffect(showWinnings ? 1.0 : 0.5) .opacity(showWinnings ? 1.0 : 0) } } - .padding(40) + .padding(Design.Spacing.xxxLarge + Design.Spacing.small) .background( - RoundedRectangle(cornerRadius: 24) + RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge + Design.Spacing.xSmall) .fill( LinearGradient( colors: [ @@ -73,34 +78,34 @@ struct ResultBannerView: View { ) ) .overlay( - RoundedRectangle(cornerRadius: 24) + RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge + Design.Spacing.xSmall) .strokeBorder( LinearGradient( colors: [ - result.color.opacity(0.8), - result.color.opacity(0.3) + result.color.opacity(Design.Opacity.heavy), + result.color.opacity(Design.Opacity.light) ], startPoint: .topLeading, endPoint: .bottomTrailing ), - lineWidth: 3 + lineWidth: Design.LineWidth.thick ) ) ) - .shadow(color: result.color.opacity(0.3), radius: 30) + .shadow(color: result.color.opacity(Design.Opacity.light), radius: Design.Shadow.radiusXXLarge) .scaleEffect(showBanner ? 1.0 : 0.8) .opacity(showBanner ? 1.0 : 0) } .onAppear { - withAnimation(.spring(duration: 0.4, bounce: 0.3)) { + withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)) { showBanner = true } - withAnimation(.spring(duration: 0.4, bounce: 0.3).delay(0.2)) { + withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(0.2)) { showText = true } - withAnimation(.spring(duration: 0.4, bounce: 0.3).delay(0.4)) { + withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(0.4)) { showWinnings = true } } @@ -114,10 +119,13 @@ struct ConfettiPiece: View { @State private var rotation: Double = 0 @State private var opacity: Double = 1 + private let confettiWidth: CGFloat = 8 + private let confettiHeight: CGFloat = 12 + var body: some View { Rectangle() .fill(color) - .frame(width: 8, height: 12) + .frame(width: confettiWidth, height: confettiHeight) .rotationEffect(.degrees(rotation)) .position(position) .opacity(opacity) @@ -154,7 +162,7 @@ struct ConfettiView: View { #Preview { ZStack { - Color(red: 0.0, green: 0.3, blue: 0.15) + Color.Table.preview .ignoresSafeArea() ResultBannerView(result: .playerWins, winnings: 500) diff --git a/Baccarat/Views/RoadMapView.swift b/Baccarat/Views/RoadMapView.swift index 3ec78af..003efb1 100644 --- a/Baccarat/Views/RoadMapView.swift +++ b/Baccarat/Views/RoadMapView.swift @@ -11,28 +11,33 @@ import SwiftUI struct RoadMapView: View { let results: [RoundResult] + // MARK: - Scaled Fonts (Dynamic Type) + + @ScaledMetric(relativeTo: .caption2) private var historyFontSize: CGFloat = Design.BaseFontSize.small + @ScaledMetric(relativeTo: .caption2) private var dotSize: CGFloat = 22 + var body: some View { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: Design.Spacing.xSmall) { Text("HISTORY") - .font(.system(size: 10, weight: .bold, design: .rounded)) + .font(.system(size: historyFontSize, weight: .bold, design: .rounded)) .foregroundStyle(.white.opacity(0.6)) .tracking(1) ScrollView(.horizontal) { - HStack(spacing: 4) { + HStack(spacing: Design.Spacing.xSmall) { ForEach(results) { result in - RoadDot(result: result.result) + RoadDot(result: result.result, dotSize: dotSize) } } - .padding(.vertical, 4) + .padding(.vertical, Design.Spacing.xSmall) } .scrollIndicators(.hidden) } - .padding(.horizontal, 12) - .padding(.vertical, 8) + .padding(.horizontal, Design.Spacing.medium) + .padding(.vertical, Design.Spacing.small) .background( - RoundedRectangle(cornerRadius: 8) - .fill(Color.black.opacity(0.3)) + RoundedRectangle(cornerRadius: Design.CornerRadius.small) + .fill(Color.black.opacity(Design.Opacity.light)) ) } } @@ -40,6 +45,11 @@ struct RoadMapView: View { /// A single dot in the road display. struct RoadDot: View { let result: GameResult + let dotSize: CGFloat + + // MARK: - Scaled Fonts (Dynamic Type) + + @ScaledMetric(relativeTo: .caption2) private var labelFontSize: CGFloat = Design.BaseFontSize.small private var color: Color { switch result { @@ -61,14 +71,14 @@ struct RoadDot: View { ZStack { Circle() .fill(color) - .frame(width: 22, height: 22) + .frame(width: dotSize, height: dotSize) Circle() - .strokeBorder(Color.white.opacity(0.3), lineWidth: 1) - .frame(width: 22, height: 22) + .strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin) + .frame(width: dotSize, height: dotSize) Text(label) - .font(.system(size: 10, weight: .bold)) + .font(.system(size: labelFontSize, weight: .bold)) .foregroundStyle(.white) } } @@ -76,7 +86,7 @@ struct RoadDot: View { #Preview { ZStack { - Color(red: 0.0, green: 0.3, blue: 0.15) + Color.Table.preview .ignoresSafeArea() RoadMapView(results: [ diff --git a/Baccarat/Views/SettingsView.swift b/Baccarat/Views/SettingsView.swift index 98972f8..e505cdd 100644 --- a/Baccarat/Views/SettingsView.swift +++ b/Baccarat/Views/SettingsView.swift @@ -19,11 +19,11 @@ struct SettingsView: View { NavigationStack { ZStack { // Background - Color(red: 0.08, green: 0.12, blue: 0.08) + Color.Settings.background .ignoresSafeArea() ScrollView { - VStack(spacing: 24) { + VStack(spacing: Design.Spacing.xxLarge) { // Table Limits Section (First!) SettingsSection(title: "TABLE LIMITS", icon: "banknote") { TableLimitsPicker(selection: $settings.tableLimits) @@ -57,7 +57,7 @@ struct SettingsView: View { ) Divider() - .background(Color.white.opacity(0.1)) + .background(Color.white.opacity(Design.Opacity.subtle)) SettingsToggle( title: "Show History", @@ -76,7 +76,7 @@ struct SettingsView: View { if settings.showAnimations { Divider() - .background(Color.white.opacity(0.1)) + .background(Color.white.opacity(Design.Opacity.subtle)) SpeedPicker(speed: $settings.dealingSpeed) } @@ -91,24 +91,24 @@ struct SettingsView: View { Image(systemName: "arrow.counterclockwise") Text("Reset to Defaults") } - .font(.system(size: 14, weight: .medium)) - .foregroundStyle(.red.opacity(0.8)) + .font(.system(size: Design.BaseFontSize.medium, weight: .medium)) + .foregroundStyle(.red.opacity(Design.Opacity.heavy)) .padding() .frame(maxWidth: .infinity) .background( - RoundedRectangle(cornerRadius: 12) - .fill(Color.red.opacity(0.1)) + RoundedRectangle(cornerRadius: Design.CornerRadius.large) + .fill(Color.red.opacity(Design.Opacity.subtle)) ) } .padding(.horizontal) - .padding(.top, 8) + .padding(.top, Design.Spacing.small) } .padding(.vertical) } } .navigationTitle("Settings") .navigationBarTitleDisplayMode(.inline) - .toolbarBackground(Color(red: 0.08, green: 0.12, blue: 0.08), for: .navigationBar) + .toolbarBackground(Color.Settings.background, for: .navigationBar) .toolbarBackground(.visible, for: .navigationBar) .toolbarColorScheme(.dark, for: .navigationBar) .toolbar { @@ -143,19 +143,19 @@ struct SettingsSection: View { @ViewBuilder let content: Content var body: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: Design.Spacing.medium) { // Header - HStack(spacing: 8) { + HStack(spacing: Design.Spacing.small) { Image(systemName: icon) - .font(.system(size: 12, weight: .semibold)) - .foregroundStyle(.yellow.opacity(0.8)) + .font(.system(size: Design.BaseFontSize.body, weight: .semibold)) + .foregroundStyle(.yellow.opacity(Design.Opacity.heavy)) Text(title) - .font(.system(size: 12, weight: .bold, design: .rounded)) + .font(.system(size: Design.BaseFontSize.body, weight: .bold, design: .rounded)) .tracking(1) .foregroundStyle(.white.opacity(0.6)) } - .padding(.horizontal, 4) + .padding(.horizontal, Design.Spacing.xSmall) // Content card VStack(spacing: 0) { @@ -163,7 +163,7 @@ struct SettingsSection: View { } .padding() .background( - RoundedRectangle(cornerRadius: 12) + RoundedRectangle(cornerRadius: Design.CornerRadius.large) .fill(Color.white.opacity(0.05)) ) } @@ -176,44 +176,44 @@ struct DeckCountPicker: View { @Binding var selection: DeckCount var body: some View { - VStack(spacing: 12) { + VStack(spacing: Design.Spacing.medium) { ForEach(DeckCount.allCases) { count in Button { selection = count } label: { HStack { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(count.displayName) - .font(.system(size: 16, weight: .semibold)) + .font(.system(size: Design.BaseFontSize.large, weight: .semibold)) .foregroundStyle(.white) Text(count.description) - .font(.system(size: 12)) - .foregroundStyle(.white.opacity(0.5)) + .font(.system(size: Design.BaseFontSize.body)) + .foregroundStyle(.white.opacity(Design.Opacity.medium)) } Spacer() if selection == count { Image(systemName: "checkmark.circle.fill") - .font(.system(size: 22)) + .font(.system(size: Design.Size.checkmark)) .foregroundStyle(.yellow) } else { Circle() - .strokeBorder(Color.white.opacity(0.3), lineWidth: 2) - .frame(width: 22, height: 22) + .strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium) + .frame(width: Design.Size.checkmark, height: Design.Size.checkmark) } } .padding() .background( - RoundedRectangle(cornerRadius: 10) - .fill(selection == count ? Color.yellow.opacity(0.1) : Color.clear) + RoundedRectangle(cornerRadius: Design.CornerRadius.medium) + .fill(selection == count ? Color.yellow.opacity(Design.Opacity.subtle) : Color.clear) ) .overlay( - RoundedRectangle(cornerRadius: 10) + RoundedRectangle(cornerRadius: Design.CornerRadius.medium) .strokeBorder( - selection == count ? Color.yellow.opacity(0.5) : Color.white.opacity(0.1), - lineWidth: 1 + selection == count ? Color.yellow.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle), + lineWidth: Design.LineWidth.thin ) ) } @@ -234,19 +234,19 @@ struct BalancePicker: View { GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible()) - ], spacing: 10) { + ], spacing: Design.Spacing.small) { ForEach(options, id: \.self) { amount in Button { balance = amount } label: { Text("$\(amount / 1000)K") - .font(.system(size: 14, weight: .bold)) + .font(.system(size: Design.BaseFontSize.medium, weight: .bold)) .foregroundStyle(balance == amount ? .black : .white) - .padding(.vertical, 12) + .padding(.vertical, Design.Spacing.medium) .frame(maxWidth: .infinity) .background( - RoundedRectangle(cornerRadius: 8) - .fill(balance == amount ? Color.yellow : Color.white.opacity(0.1)) + RoundedRectangle(cornerRadius: Design.CornerRadius.small) + .fill(balance == amount ? Color.yellow : Color.white.opacity(Design.Opacity.subtle)) ) } .buttonStyle(.plain) @@ -263,14 +263,14 @@ struct SettingsToggle: View { var body: some View { Toggle(isOn: $isOn) { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(title) .font(.system(size: 15, weight: .medium)) .foregroundStyle(.white) Text(subtitle) - .font(.system(size: 12)) - .foregroundStyle(.white.opacity(0.5)) + .font(.system(size: Design.BaseFontSize.body)) + .foregroundStyle(.white.opacity(Design.Opacity.medium)) } } .tint(.yellow) @@ -288,24 +288,24 @@ struct SpeedPicker: View { ] var body: some View { - VStack(alignment: .leading, spacing: 8) { + VStack(alignment: .leading, spacing: Design.Spacing.small) { Text("Dealing Speed") .font(.system(size: 15, weight: .medium)) .foregroundStyle(.white) - HStack(spacing: 8) { + HStack(spacing: Design.Spacing.small) { ForEach(options, id: \.1) { option in Button { speed = option.1 } label: { Text(option.0) .font(.system(size: 13, weight: .medium)) - .foregroundStyle(speed == option.1 ? .black : .white.opacity(0.7)) - .padding(.vertical, 8) + .foregroundStyle(speed == option.1 ? .black : .white.opacity(Design.Opacity.strong)) + .padding(.vertical, Design.Spacing.small) .frame(maxWidth: .infinity) .background( Capsule() - .fill(speed == option.1 ? Color.yellow : Color.white.opacity(0.1)) + .fill(speed == option.1 ? Color.yellow : Color.white.opacity(Design.Opacity.subtle)) ) } .buttonStyle(.plain) @@ -320,30 +320,30 @@ struct TableLimitsPicker: View { @Binding var selection: TableLimits var body: some View { - VStack(spacing: 10) { + VStack(spacing: Design.Spacing.small) { ForEach(TableLimits.allCases) { limit in Button { selection = limit } label: { HStack { - VStack(alignment: .leading, spacing: 2) { + VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) { Text(limit.displayName) - .font(.system(size: 16, weight: .semibold)) + .font(.system(size: Design.BaseFontSize.large, weight: .semibold)) .foregroundStyle(.white) Text(limit.detailedDescription) - .font(.system(size: 12)) - .foregroundStyle(.white.opacity(0.5)) + .font(.system(size: Design.BaseFontSize.body)) + .foregroundStyle(.white.opacity(Design.Opacity.medium)) } Spacer() // Limits badge Text(limit.description) - .font(.system(size: 12, weight: .bold, design: .rounded)) + .font(.system(size: Design.BaseFontSize.body, weight: .bold, design: .rounded)) .foregroundStyle(selection == limit ? .black : .yellow) - .padding(.horizontal, 10) - .padding(.vertical, 4) + .padding(.horizontal, Design.Spacing.small) + .padding(.vertical, Design.Spacing.xSmall) .background( Capsule() .fill(selection == limit ? Color.yellow : Color.yellow.opacity(0.2)) @@ -351,24 +351,24 @@ struct TableLimitsPicker: View { if selection == limit { Image(systemName: "checkmark.circle.fill") - .font(.system(size: 20)) + .font(.system(size: Design.Size.checkmark - 2)) .foregroundStyle(.yellow) } else { Circle() - .strokeBorder(Color.white.opacity(0.3), lineWidth: 2) - .frame(width: 20, height: 20) + .strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium) + .frame(width: Design.Size.checkmark - 2, height: Design.Size.checkmark - 2) } } .padding() .background( - RoundedRectangle(cornerRadius: 10) - .fill(selection == limit ? Color.yellow.opacity(0.1) : Color.clear) + RoundedRectangle(cornerRadius: Design.CornerRadius.medium) + .fill(selection == limit ? Color.yellow.opacity(Design.Opacity.subtle) : Color.clear) ) .overlay( - RoundedRectangle(cornerRadius: 10) + RoundedRectangle(cornerRadius: Design.CornerRadius.medium) .strokeBorder( - selection == limit ? Color.yellow.opacity(0.5) : Color.white.opacity(0.1), - lineWidth: 1 + selection == limit ? Color.yellow.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle), + lineWidth: Design.LineWidth.thin ) ) }