Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
7dd1edd351
commit
73019babde
@ -3,190 +3,75 @@
|
||||
// Baccarat
|
||||
//
|
||||
// Design system constants for consistent styling across the app.
|
||||
// Uses CasinoDesign from CasinoKit for shared values, with game-specific overrides.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CasinoKit
|
||||
|
||||
/// Design constants for the Baccarat app.
|
||||
/// Shared constants are imported from CasinoDesign; game-specific values are defined here.
|
||||
enum Design {
|
||||
|
||||
// MARK: - Spacing
|
||||
// MARK: - Shared Constants (from CasinoKit)
|
||||
|
||||
enum Spacing {
|
||||
static let xxSmall: CGFloat = 2
|
||||
static let xSmall: CGFloat = 4
|
||||
static let small: CGFloat = 8
|
||||
static let medium: CGFloat = 12
|
||||
static let large: CGFloat = 16
|
||||
static let xLarge: CGFloat = 20
|
||||
static let xxLarge: CGFloat = 24
|
||||
static let xxxLarge: CGFloat = 32
|
||||
}
|
||||
typealias Spacing = CasinoDesign.Spacing
|
||||
typealias CornerRadius = CasinoDesign.CornerRadius
|
||||
typealias LineWidth = CasinoDesign.LineWidth
|
||||
typealias Shadow = CasinoDesign.Shadow
|
||||
typealias Opacity = CasinoDesign.Opacity
|
||||
typealias Animation = CasinoDesign.Animation
|
||||
typealias Scale = CasinoDesign.Scale
|
||||
typealias MinScaleFactor = CasinoDesign.MinScaleFactor
|
||||
typealias BaseFontSize = CasinoDesign.BaseFontSize
|
||||
typealias IconSize = CasinoDesign.IconSize
|
||||
|
||||
// MARK: - Corner Radii
|
||||
|
||||
enum CornerRadius {
|
||||
static let small: CGFloat = 8
|
||||
static let medium: CGFloat = 10
|
||||
static let large: CGFloat = 12
|
||||
static let xLarge: CGFloat = 14
|
||||
static let xxLarge: CGFloat = 20
|
||||
static let xxxLarge: CGFloat = 28
|
||||
}
|
||||
|
||||
// MARK: - Base Font Sizes
|
||||
// These are base values for use with @ScaledMetric in views.
|
||||
// They will scale automatically based on user accessibility settings.
|
||||
|
||||
enum BaseFontSize {
|
||||
static let xxSmall: CGFloat = 7
|
||||
static let xSmall: CGFloat = 9
|
||||
static let small: CGFloat = 10
|
||||
static let body: CGFloat = 12
|
||||
static let callout: CGFloat = 13
|
||||
static let medium: CGFloat = 14
|
||||
static let subheadline: CGFloat = 15
|
||||
static let large: CGFloat = 16
|
||||
static let xLarge: CGFloat = 18
|
||||
static let xxLarge: CGFloat = 20
|
||||
static let title: CGFloat = 32
|
||||
static let largeTitle: CGFloat = 36
|
||||
static let display: CGFloat = 70
|
||||
}
|
||||
|
||||
// MARK: - Icon Sizes
|
||||
|
||||
enum IconSize {
|
||||
static let small: CGFloat = 12
|
||||
static let medium: CGFloat = 16
|
||||
static let large: CGFloat = 22
|
||||
static let xLarge: CGFloat = 60
|
||||
static let xxLarge: CGFloat = 70
|
||||
}
|
||||
|
||||
// MARK: - Component Sizes
|
||||
// MARK: - Baccarat-Specific Component Sizes
|
||||
|
||||
enum Size {
|
||||
static let chipSmall: CGFloat = 36
|
||||
static let chipMedium: CGFloat = 50
|
||||
static let chipSelector: CGFloat = 50
|
||||
static let chipBadge: CGFloat = 32
|
||||
static let chipBadgeInner: CGFloat = 28
|
||||
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
|
||||
// Cards - use CasinoDesign values
|
||||
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
||||
static let cardWidthMedium: CGFloat = CasinoDesign.Size.cardWidthMedium
|
||||
static let cardWidthLarge: CGFloat = CasinoDesign.Size.cardWidthLarge
|
||||
|
||||
// Chips - use CasinoDesign values
|
||||
static let chipSmall: CGFloat = CasinoDesign.Size.chipSmall
|
||||
static let chipMedium: CGFloat = CasinoDesign.Size.chipMedium
|
||||
static let chipSelector: CGFloat = CasinoDesign.Size.chipMedium
|
||||
static let chipBadge: CGFloat = CasinoDesign.Size.chipBadge
|
||||
static let chipBadgeInner: CGFloat = CasinoDesign.Size.chipBadgeInner
|
||||
|
||||
// Shared values
|
||||
static let valueBadge: CGFloat = CasinoDesign.Size.valueBadge
|
||||
static let checkmark: CGFloat = CasinoDesign.Size.checkmark
|
||||
static let maxContentWidthPortrait: CGFloat = CasinoDesign.Size.maxContentWidthPortrait
|
||||
static let maxContentWidthLandscape: CGFloat = CasinoDesign.Size.maxContentWidthLandscape
|
||||
static let maxModalWidth: CGFloat = CasinoDesign.Size.maxModalWidth
|
||||
|
||||
// Baccarat-specific table layout
|
||||
static let tableAspectRatio: CGFloat = 1.6
|
||||
static let roadMapCell: CGFloat = 16
|
||||
static let diamondIcon: CGFloat = 24
|
||||
static let topBetRowHeight: CGFloat = 52
|
||||
static let mainBetRowHeight: CGFloat = 65
|
||||
static let bonusZoneWidth: CGFloat = 80
|
||||
|
||||
// iPad max widths
|
||||
static let maxContentWidthPortrait: CGFloat = 500
|
||||
static let maxContentWidthLandscape: CGFloat = 800
|
||||
static let maxModalWidth: CGFloat = 450
|
||||
}
|
||||
|
||||
// MARK: - Animation
|
||||
|
||||
enum Animation {
|
||||
static let quick: Double = 0.3
|
||||
static let springDuration: Double = 0.4
|
||||
static let springBounce: Double = 0.3
|
||||
static let cardFlipBounce: Double = 0.2
|
||||
static let fadeInDuration: Double = 0.3
|
||||
static let cardFlipDuration: Double = 0.5
|
||||
static let selectionDuration: Double = 0.2
|
||||
static let staggerDelay1: Double = 0.1
|
||||
static let staggerDelay2: Double = 0.25
|
||||
static let staggerDelay3: Double = 0.4
|
||||
}
|
||||
|
||||
// MARK: - Opacity
|
||||
|
||||
enum Opacity {
|
||||
static let verySubtle: Double = 0.05
|
||||
static let subtle: Double = 0.1
|
||||
static let selection: Double = 0.15
|
||||
static let hint: Double = 0.2
|
||||
static let quarter: Double = 0.25
|
||||
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 disabled: Double = 0.5
|
||||
static let accent: Double = 0.6
|
||||
static let strong: Double = 0.7
|
||||
static let heavy: Double = 0.8
|
||||
static let nearOpaque: Double = 0.85
|
||||
static let almostFull: Double = 0.9
|
||||
}
|
||||
|
||||
// MARK: - Scale Effects
|
||||
|
||||
enum Scale {
|
||||
static let shrunk: Double = 0.5
|
||||
static let slightShrink: Double = 0.8
|
||||
static let normal: Double = 1.0
|
||||
static let selected: Double = 1.1
|
||||
}
|
||||
|
||||
// MARK: - Minimum Scale Factor (for text)
|
||||
|
||||
enum MinScaleFactor {
|
||||
static let tight: Double = 0.5
|
||||
static let comfortable: Double = 0.6
|
||||
static let relaxed: Double = 0.7
|
||||
}
|
||||
|
||||
// MARK: - Line Widths
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// MARK: - Shadow
|
||||
|
||||
enum Shadow {
|
||||
static let radiusSmall: CGFloat = 2
|
||||
static let radiusMedium: CGFloat = 6
|
||||
static let radiusLarge: CGFloat = 10
|
||||
static let radiusXLarge: CGFloat = 12
|
||||
static let radiusXXLarge: CGFloat = 30
|
||||
|
||||
static let offsetSmall: CGFloat = 1
|
||||
static let offsetMedium: CGFloat = 3
|
||||
static let offsetLarge: CGFloat = 5
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - App Colors
|
||||
// MARK: - Baccarat App Colors
|
||||
|
||||
extension Color {
|
||||
|
||||
// MARK: - Table Colors
|
||||
// MARK: - Table Colors (use CasinoTable from CasinoKit)
|
||||
|
||||
enum Table {
|
||||
static let feltDark = Color(red: 0.0, green: 0.28, blue: 0.12)
|
||||
static let feltLight = Color(red: 0.0, green: 0.35, blue: 0.18)
|
||||
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)
|
||||
}
|
||||
/// Typealias for consistent table colors across all games.
|
||||
typealias Table = CasinoTable
|
||||
|
||||
// MARK: - Border Colors
|
||||
|
||||
enum Border {
|
||||
static let goldLight = Color(red: 0.85, green: 0.7, blue: 0.35)
|
||||
static let goldDark = Color(red: 0.65, green: 0.5, blue: 0.2)
|
||||
static let goldLight = CasinoTable.goldLight
|
||||
static let goldDark = CasinoTable.goldDark
|
||||
static let gold = Color(red: 0.7, green: 0.55, blue: 0.25)
|
||||
static let silver = Color(red: 0.6, green: 0.6, blue: 0.65)
|
||||
}
|
||||
@ -223,12 +108,12 @@ extension Color {
|
||||
static let destructive = Color(red: 0.6, green: 0.2, blue: 0.2)
|
||||
}
|
||||
|
||||
// MARK: - Chip Colors
|
||||
// MARK: - Chip Colors (for theming)
|
||||
|
||||
enum Chip {
|
||||
static let gold = Color(red: 0.8, green: 0.65, blue: 0.2)
|
||||
|
||||
// Chip base colors
|
||||
// Chip base colors by denomination
|
||||
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)
|
||||
@ -301,4 +186,3 @@ extension String {
|
||||
return String(format: format, arguments: arguments)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import CasinoKit
|
||||
|
||||
/// The available rule pages.
|
||||
enum RulesPage: Int, CaseIterable, Identifiable {
|
||||
|
||||
@ -39,14 +39,6 @@
|
||||
"comment" : "A step in the process of exporting app icons.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"$%lld" : {
|
||||
"comment" : "A label displaying the current bet amount in the betting zone. The argument is the amount of the current bet.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"$%lld bet" : {
|
||||
"comment" : "An accessibility label and hint for the betting zone button.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"2-10: Face value" : {
|
||||
"comment" : "Description of the card values for cards with values 2 through 10.",
|
||||
"isCommentAutoGenerated" : true
|
||||
@ -1541,10 +1533,6 @@
|
||||
"comment" : "Description of the insurance payout when the player wins.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Place bet" : {
|
||||
"comment" : "An accessibility label for the betting zone when no bet is placed.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Play Again" : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// Blackjack
|
||||
//
|
||||
// Centralized design constants for the Blackjack app.
|
||||
// Uses CasinoDesign from CasinoKit for shared values, with game-specific overrides.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
@ -10,132 +11,57 @@ import CasinoKit
|
||||
|
||||
// MARK: - Design Namespace
|
||||
|
||||
/// Design constants for the Blackjack app.
|
||||
/// Shared constants are imported from CasinoDesign; game-specific values are defined here.
|
||||
enum Design {
|
||||
// Reuse CasinoDesign where appropriate
|
||||
|
||||
// MARK: - Shared Constants (from CasinoKit)
|
||||
|
||||
typealias Spacing = CasinoDesign.Spacing
|
||||
typealias CornerRadius = CasinoDesign.CornerRadius
|
||||
typealias LineWidth = CasinoDesign.LineWidth
|
||||
typealias Shadow = CasinoDesign.Shadow
|
||||
typealias Opacity = CasinoDesign.Opacity
|
||||
typealias Animation = CasinoDesign.Animation
|
||||
typealias Scale = CasinoDesign.Scale
|
||||
typealias MinScaleFactor = CasinoDesign.MinScaleFactor
|
||||
typealias BaseFontSize = CasinoDesign.BaseFontSize
|
||||
typealias IconSize = CasinoDesign.IconSize
|
||||
|
||||
// MARK: - Spacing
|
||||
|
||||
enum Spacing {
|
||||
static let xxSmall: CGFloat = 2
|
||||
static let xSmall: CGFloat = 4
|
||||
static let small: CGFloat = 8
|
||||
static let medium: CGFloat = 12
|
||||
static let large: CGFloat = 16
|
||||
static let xLarge: CGFloat = 20
|
||||
static let xxLarge: CGFloat = 24
|
||||
static let xxxLarge: CGFloat = 32
|
||||
}
|
||||
|
||||
// MARK: - Corner Radius
|
||||
|
||||
enum CornerRadius {
|
||||
static let xSmall: CGFloat = 4
|
||||
static let small: CGFloat = 8
|
||||
static let medium: CGFloat = 12
|
||||
static let large: CGFloat = 16
|
||||
static let xLarge: CGFloat = 20
|
||||
static let xxLarge: CGFloat = 24
|
||||
static let xxxLarge: CGFloat = 32
|
||||
}
|
||||
|
||||
// MARK: - Base Font Sizes
|
||||
|
||||
enum BaseFontSize {
|
||||
static let xxSmall: CGFloat = 8
|
||||
static let xSmall: CGFloat = 10
|
||||
static let small: CGFloat = 12
|
||||
static let body: CGFloat = 14
|
||||
static let medium: CGFloat = 16
|
||||
static let large: CGFloat = 18
|
||||
static let xLarge: CGFloat = 20
|
||||
static let xxLarge: CGFloat = 24
|
||||
static let title: CGFloat = 28
|
||||
static let largeTitle: CGFloat = 32
|
||||
static let display: CGFloat = 48
|
||||
}
|
||||
|
||||
// MARK: - Opacity
|
||||
|
||||
enum Opacity {
|
||||
static let verySubtle: Double = 0.05
|
||||
static let subtle: Double = 0.1
|
||||
static let hint: Double = 0.2
|
||||
static let light: Double = 0.3
|
||||
static let medium: Double = 0.5
|
||||
static let accent: Double = 0.6
|
||||
static let strong: Double = 0.7
|
||||
static let heavy: Double = 0.8
|
||||
static let almostFull: Double = 0.9
|
||||
}
|
||||
|
||||
// MARK: - Line Width
|
||||
|
||||
enum LineWidth {
|
||||
static let thin: CGFloat = 1
|
||||
static let medium: CGFloat = 2
|
||||
static let thick: CGFloat = 3
|
||||
static let heavy: CGFloat = 4
|
||||
}
|
||||
|
||||
// MARK: - Shadow
|
||||
|
||||
enum Shadow {
|
||||
static let radiusSmall: CGFloat = 2
|
||||
static let radiusMedium: CGFloat = 6
|
||||
static let radiusLarge: CGFloat = 10
|
||||
static let radiusXLarge: CGFloat = 15
|
||||
static let offsetSmall: CGFloat = 1
|
||||
static let offsetMedium: CGFloat = 3
|
||||
static let offsetLarge: CGFloat = 5
|
||||
}
|
||||
|
||||
// MARK: - Sizes
|
||||
// MARK: - Blackjack-Specific Component Sizes
|
||||
|
||||
enum Size {
|
||||
// Cards
|
||||
static let cardWidth: CGFloat = 55
|
||||
static let cardWidthSmall: CGFloat = 45
|
||||
static let cardOverlap: CGFloat = -15
|
||||
// Cards - slightly larger than medium for better visibility
|
||||
static let cardWidth: CGFloat = 60
|
||||
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
||||
static let cardOverlap: CGFloat = CasinoDesign.Size.cardOverlap
|
||||
|
||||
// Table
|
||||
// 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
|
||||
static let tableHeight: CGFloat = 280
|
||||
static let bettingZoneHeight: CGFloat = 80
|
||||
static let chipBadgeSize: CGFloat = 32
|
||||
|
||||
// Buttons
|
||||
static let actionButtonHeight: CGFloat = 50
|
||||
static let actionButtonMinWidth: CGFloat = 80
|
||||
|
||||
// Responsive
|
||||
static let maxContentWidthPortrait: CGFloat = 500
|
||||
static let maxContentWidthLandscape: CGFloat = 800
|
||||
static let maxModalWidth: CGFloat = 450
|
||||
}
|
||||
|
||||
// MARK: - Icon Sizes
|
||||
|
||||
enum IconSize {
|
||||
static let small: CGFloat = 16
|
||||
static let medium: CGFloat = 20
|
||||
static let large: CGFloat = 24
|
||||
static let xLarge: CGFloat = 32
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Color Extensions
|
||||
// MARK: - Blackjack App Colors
|
||||
|
||||
extension Color {
|
||||
// MARK: - Table Colors
|
||||
|
||||
enum Table {
|
||||
static let felt = Color(red: 0.05, green: 0.35, blue: 0.15)
|
||||
static let feltDark = Color(red: 0.03, green: 0.25, blue: 0.1)
|
||||
static let feltLight = Color(red: 0.08, green: 0.45, blue: 0.2)
|
||||
static let border = Color(red: 0.6, green: 0.5, blue: 0.3)
|
||||
}
|
||||
// MARK: - Table Colors (use CasinoTable from CasinoKit for consistency)
|
||||
|
||||
/// Typealias for consistent table colors across all games.
|
||||
typealias Table = CasinoTable
|
||||
|
||||
// MARK: - Betting Zone Colors
|
||||
|
||||
@ -152,7 +78,7 @@ extension Color {
|
||||
static let player = Color(red: 0.2, green: 0.5, blue: 0.8)
|
||||
static let dealer = Color(red: 0.8, green: 0.3, blue: 0.3)
|
||||
static let active = Color.yellow
|
||||
static let inactive = Color.white.opacity(0.5)
|
||||
static let inactive = Color.white.opacity(CasinoDesign.Opacity.medium)
|
||||
}
|
||||
|
||||
// MARK: - Result Colors
|
||||
@ -164,7 +90,7 @@ extension Color {
|
||||
static let blackjack = Color.yellow
|
||||
}
|
||||
|
||||
// MARK: - Button Colors
|
||||
// MARK: - Action Button Colors
|
||||
|
||||
enum Button {
|
||||
static let hit = Color(red: 0.2, green: 0.6, blue: 0.3)
|
||||
@ -182,7 +108,7 @@ extension Color {
|
||||
|
||||
enum Settings {
|
||||
static let background = Color(red: 0.08, green: 0.12, blue: 0.18)
|
||||
static let cardBackground = Color.white.opacity(Design.Opacity.verySubtle)
|
||||
static let cardBackground = Color.white.opacity(CasinoDesign.Opacity.verySubtle)
|
||||
static let accent = Color(red: 0.9, green: 0.75, blue: 0.3)
|
||||
}
|
||||
|
||||
@ -199,4 +125,3 @@ extension Color {
|
||||
static let balance = Color(red: 0.95, green: 0.85, blue: 0.4)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -195,18 +195,46 @@ struct PlayerHandsView: View {
|
||||
let cardWidth: CGFloat
|
||||
let cardSpacing: CGFloat
|
||||
|
||||
/// Adaptive card width based on number of hands (smaller cards for more splits)
|
||||
private var adaptiveCardWidth: CGFloat {
|
||||
switch hands.count {
|
||||
case 1, 2:
|
||||
return cardWidth
|
||||
case 3:
|
||||
return cardWidth * 0.85
|
||||
default: // 4+ hands
|
||||
return cardWidth * 0.75
|
||||
}
|
||||
}
|
||||
|
||||
/// Adaptive spacing based on number of hands
|
||||
private var adaptiveSpacing: CGFloat {
|
||||
switch hands.count {
|
||||
case 1, 2:
|
||||
return Design.Spacing.xxLarge
|
||||
case 3:
|
||||
return Design.Spacing.large
|
||||
default: // 4+ hands
|
||||
return Design.Spacing.medium
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.xxLarge) {
|
||||
ForEach(hands.indices, id: \.self) { index in
|
||||
HStack(spacing: adaptiveSpacing) {
|
||||
// Display hands in reverse order (right to left play order)
|
||||
// So hand 0 (played first) appears on the right
|
||||
ForEach(hands.indices.reversed(), id: \.self) { index in
|
||||
PlayerHandView(
|
||||
hand: hands[index],
|
||||
isActive: index == activeHandIndex && isPlayerTurn,
|
||||
handNumber: hands.count > 1 ? index + 1 : nil,
|
||||
cardWidth: cardWidth,
|
||||
// Hand numbers: rightmost is Hand 1, leftmost is Hand 2, etc.
|
||||
handNumber: hands.count > 1 ? hands.count - index : nil,
|
||||
cardWidth: adaptiveCardWidth,
|
||||
cardSpacing: cardSpacing
|
||||
)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity) // Center the hands horizontally
|
||||
}
|
||||
}
|
||||
|
||||
@ -222,16 +250,7 @@ struct PlayerHandView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
// Cards
|
||||
ZStack {
|
||||
// Active indicator
|
||||
if isActive {
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.strokeBorder(Color.Hand.active, lineWidth: Design.LineWidth.medium)
|
||||
.frame(width: containerWidth, height: containerHeight)
|
||||
.animation(.easeInOut(duration: 0.5).repeatForever(autoreverses: true), value: isActive)
|
||||
}
|
||||
|
||||
// Cards with container - uses dynamic sizing based on card count
|
||||
HStack(spacing: hand.cards.isEmpty ? Design.Spacing.small : cardSpacing) {
|
||||
if hand.cards.isEmpty {
|
||||
CardPlaceholderView(width: cardWidth)
|
||||
@ -247,7 +266,21 @@ struct PlayerHandView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.fill(Color.Table.feltDark.opacity(Design.Opacity.light))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||
.strokeBorder(
|
||||
isActive ? Color.Hand.active : Color.white.opacity(Design.Opacity.hint),
|
||||
lineWidth: isActive ? Design.LineWidth.thick : Design.LineWidth.thin
|
||||
)
|
||||
)
|
||||
)
|
||||
.contentShape(Rectangle()) // Ensure tap area matches visual
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: isActive)
|
||||
|
||||
// Hand info
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
@ -298,14 +331,6 @@ struct PlayerHandView: View {
|
||||
.accessibilityLabel(playerAccessibilityLabel)
|
||||
}
|
||||
|
||||
private var containerWidth: CGFloat {
|
||||
cardWidth + (cardWidth + cardSpacing) * 2 + Design.Spacing.medium
|
||||
}
|
||||
|
||||
private var containerHeight: CGFloat {
|
||||
cardWidth * CasinoDesign.Size.cardAspectRatio + Design.Spacing.medium
|
||||
}
|
||||
|
||||
private var valueColor: Color {
|
||||
if hand.isBlackjack { return .yellow }
|
||||
if hand.isBusted { return .red }
|
||||
|
||||
@ -71,10 +71,7 @@ struct GameTableView: View {
|
||||
private func mainGameView(state: GameState) -> some View {
|
||||
ZStack {
|
||||
// Background
|
||||
TableBackgroundView(
|
||||
feltColor: Color.Table.felt,
|
||||
edgeColor: Color.Table.feltDark
|
||||
)
|
||||
TableBackgroundView()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
// Top bar
|
||||
@ -124,8 +121,8 @@ struct GameTableView: View {
|
||||
)
|
||||
}
|
||||
|
||||
// Confetti for blackjack
|
||||
if state.showResultBanner && (state.lastRoundResult?.wasBlackjack ?? false) {
|
||||
// Confetti for wins (matching Baccarat pattern)
|
||||
if state.showResultBanner && (state.lastRoundResult?.totalWinnings ?? 0) > 0 {
|
||||
ConfettiView()
|
||||
}
|
||||
|
||||
@ -276,10 +273,10 @@ struct ActionButton: View {
|
||||
let action: () -> Void
|
||||
|
||||
enum ButtonStyle {
|
||||
case primary
|
||||
case destructive
|
||||
case secondary
|
||||
case custom(Color)
|
||||
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 {
|
||||
@ -287,15 +284,6 @@ struct ActionButton: View {
|
||||
case .destructive, .secondary, .custom: return .white
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor: Color {
|
||||
switch self {
|
||||
case .primary: return .yellow
|
||||
case .destructive: return .red.opacity(Design.Opacity.heavy)
|
||||
case .secondary: return .white.opacity(Design.Opacity.hint)
|
||||
case .custom(let color): return color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(_ title: String, icon: String? = nil, style: ButtonStyle = .primary, action: @escaping () -> Void) {
|
||||
@ -313,18 +301,38 @@ struct ActionButton: View {
|
||||
}
|
||||
Text(title)
|
||||
}
|
||||
.font(.system(size: Design.BaseFontSize.medium, weight: .bold))
|
||||
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
|
||||
.foregroundStyle(style.foregroundColor)
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(style.backgroundColor)
|
||||
)
|
||||
.shadow(color: style.backgroundColor.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
|
||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||
.padding(.vertical, Design.Spacing.medium + Design.Spacing.xxSmall)
|
||||
.background(backgroundView)
|
||||
}
|
||||
.accessibilityLabel(title)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var backgroundView: some View {
|
||||
switch style {
|
||||
case .primary:
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.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: - Preview
|
||||
|
||||
@ -28,17 +28,18 @@ public enum CasinoDesign {
|
||||
public enum CornerRadius {
|
||||
public static let xSmall: CGFloat = 4
|
||||
public static let small: CGFloat = 8
|
||||
public static let medium: CGFloat = 12
|
||||
public static let large: CGFloat = 16
|
||||
public static let xLarge: CGFloat = 20
|
||||
public static let xxLarge: CGFloat = 24
|
||||
public static let xxxLarge: CGFloat = 32
|
||||
public static let medium: CGFloat = 10
|
||||
public static let large: CGFloat = 12
|
||||
public static let xLarge: CGFloat = 14
|
||||
public static let xxLarge: CGFloat = 20
|
||||
public static let xxxLarge: CGFloat = 28
|
||||
}
|
||||
|
||||
// MARK: - Line Width
|
||||
|
||||
public enum LineWidth {
|
||||
public static let thin: CGFloat = 1
|
||||
public static let standard: CGFloat = 2
|
||||
public static let medium: CGFloat = 2
|
||||
public static let thick: CGFloat = 3
|
||||
public static let heavy: CGFloat = 4
|
||||
@ -61,25 +62,37 @@ public enum CasinoDesign {
|
||||
// MARK: - Opacity
|
||||
|
||||
public enum Opacity {
|
||||
public static let verySubtle: CGFloat = 0.05
|
||||
public static let subtle: CGFloat = 0.1
|
||||
public static let hint: CGFloat = 0.2
|
||||
public static let light: CGFloat = 0.3
|
||||
public static let quarter: CGFloat = 0.25
|
||||
public static let medium: CGFloat = 0.5
|
||||
public static let accent: CGFloat = 0.6
|
||||
public static let strong: CGFloat = 0.7
|
||||
public static let heavy: CGFloat = 0.8
|
||||
public static let nearOpaque: CGFloat = 0.95
|
||||
public static let verySubtle: Double = 0.05
|
||||
public static let subtle: Double = 0.1
|
||||
public static let selection: Double = 0.15
|
||||
public static let hint: Double = 0.2
|
||||
public static let quarter: Double = 0.25
|
||||
public static let light: Double = 0.3
|
||||
public static let overlay: Double = 0.4
|
||||
public static let medium: Double = 0.5
|
||||
public static let secondary: Double = 0.5
|
||||
public static let disabled: Double = 0.5
|
||||
public static let accent: Double = 0.6
|
||||
public static let strong: Double = 0.7
|
||||
public static let heavy: Double = 0.8
|
||||
public static let nearOpaque: Double = 0.85
|
||||
public static let almostFull: Double = 0.9
|
||||
}
|
||||
|
||||
// MARK: - Animation
|
||||
|
||||
public enum Animation {
|
||||
public static let quick: Double = 0.2
|
||||
public static let quick: Double = 0.3
|
||||
public static let standard: Double = 0.3
|
||||
public static let springDuration: Double = 0.4
|
||||
public static let springBounce: Double = 0.2
|
||||
public static let springBounce: Double = 0.3
|
||||
public static let cardFlipBounce: Double = 0.2
|
||||
public static let fadeInDuration: Double = 0.3
|
||||
public static let cardFlipDuration: Double = 0.5
|
||||
public static let selectionDuration: Double = 0.2
|
||||
public static let staggerDelay1: Double = 0.1
|
||||
public static let staggerDelay2: Double = 0.25
|
||||
public static let staggerDelay3: Double = 0.4
|
||||
}
|
||||
|
||||
// MARK: - Sizes
|
||||
@ -90,11 +103,18 @@ public enum CasinoDesign {
|
||||
public static let chipMedium: CGFloat = 50
|
||||
public static let chipLarge: CGFloat = 60
|
||||
|
||||
/// Default card width.
|
||||
/// Default card widths.
|
||||
public static let cardWidthSmall: CGFloat = 45
|
||||
public static let cardWidthMedium: CGFloat = 55
|
||||
public static let cardWidthLarge: CGFloat = 65
|
||||
public static let cardWidth: CGFloat = 70
|
||||
|
||||
/// Card overlap for stacking.
|
||||
public static let cardOverlap: CGFloat = -15
|
||||
|
||||
/// Card aspect ratio (height = width * this value).
|
||||
public static let cardAspectRatio: CGFloat = 1.4
|
||||
/// Standard poker is 1.4, but 1.35 looks better on screen.
|
||||
public static let cardAspectRatio: CGFloat = 1.35
|
||||
|
||||
/// Pattern dimensions for decorative elements.
|
||||
public static let patternSpacing: CGFloat = 12
|
||||
@ -114,24 +134,48 @@ public enum CasinoDesign {
|
||||
/// Value badge size.
|
||||
public static let valueBadge: CGFloat = 26
|
||||
|
||||
/// Icon sizes.
|
||||
public static let iconSmall: CGFloat = 16
|
||||
public static let iconMedium: CGFloat = 20
|
||||
public static let iconLarge: CGFloat = 24
|
||||
/// Chip badge for bet indicators.
|
||||
public static let chipBadge: CGFloat = 32
|
||||
public static let chipBadgeInner: CGFloat = 28
|
||||
|
||||
/// Checkmark size.
|
||||
public static let checkmark: CGFloat = 22
|
||||
|
||||
/// Common button dimensions.
|
||||
public static let actionButtonHeight: CGFloat = 50
|
||||
public static let actionButtonMinWidth: CGFloat = 80
|
||||
|
||||
/// Betting zone height.
|
||||
public static let bettingZoneHeight: CGFloat = 80
|
||||
}
|
||||
|
||||
// MARK: - Icon Sizes
|
||||
|
||||
public enum IconSize {
|
||||
public static let small: CGFloat = 12
|
||||
public static let medium: CGFloat = 16
|
||||
public static let large: CGFloat = 22
|
||||
public static let xLarge: CGFloat = 32
|
||||
public static let xxLarge: CGFloat = 60
|
||||
public static let xxxLarge: CGFloat = 70
|
||||
}
|
||||
|
||||
// MARK: - Font Sizes (Base values for @ScaledMetric)
|
||||
|
||||
public enum BaseFontSize {
|
||||
public static let xxSmall: CGFloat = 8
|
||||
public static let xSmall: CGFloat = 10
|
||||
public static let small: CGFloat = 12
|
||||
public static let body: CGFloat = 14
|
||||
public static let medium: CGFloat = 16
|
||||
public static let large: CGFloat = 20
|
||||
public static let xLarge: CGFloat = 24
|
||||
public static let xxLarge: CGFloat = 28
|
||||
public static let xxSmall: CGFloat = 7
|
||||
public static let xSmall: CGFloat = 9
|
||||
public static let small: CGFloat = 10
|
||||
public static let body: CGFloat = 12
|
||||
public static let callout: CGFloat = 13
|
||||
public static let medium: CGFloat = 14
|
||||
public static let subheadline: CGFloat = 15
|
||||
public static let large: CGFloat = 16
|
||||
public static let xLarge: CGFloat = 18
|
||||
public static let xxLarge: CGFloat = 20
|
||||
public static let title: CGFloat = 32
|
||||
public static let largeTitle: CGFloat = 36
|
||||
public static let display: CGFloat = 70
|
||||
}
|
||||
|
||||
// MARK: - Scale
|
||||
@ -199,16 +243,30 @@ public extension Color {
|
||||
public static let backgroundDark = Color(white: 0.08)
|
||||
}
|
||||
|
||||
/// Table colors.
|
||||
/// Table colors - shared across all casino games.
|
||||
enum CasinoTable {
|
||||
/// Casino table green felt.
|
||||
public static let felt = Color(red: 0.05, green: 0.25, blue: 0.15)
|
||||
/// Darker felt for gradients.
|
||||
public static let feltDark = Color(red: 0.02, green: 0.15, blue: 0.08)
|
||||
/// Casino table green felt (main color).
|
||||
public static let felt = Color(red: 0.0, green: 0.3, blue: 0.15)
|
||||
/// Darker felt for gradients and shadows.
|
||||
public static let feltDark = Color(red: 0.0, green: 0.2, blue: 0.1)
|
||||
/// Lighter felt for highlights.
|
||||
public static let feltLight = Color(red: 0.0, green: 0.35, blue: 0.18)
|
||||
/// Background gradient dark.
|
||||
public static let backgroundDark = Color(red: 0.01, green: 0.12, blue: 0.06)
|
||||
/// Background gradient light.
|
||||
public static let backgroundLight = Color(red: 0.03, green: 0.25, blue: 0.12)
|
||||
/// Base dark (for table edges).
|
||||
public static let baseDark = Color(red: 0.02, green: 0.15, blue: 0.08)
|
||||
/// Preview background (same as felt).
|
||||
public static let preview = felt
|
||||
/// Table edge border.
|
||||
public static let border = Color(red: 0.3, green: 0.2, blue: 0.1)
|
||||
public static let border = Color(red: 0.6, green: 0.5, blue: 0.3)
|
||||
/// Gold accent for table elements.
|
||||
public static let gold = Color(red: 0.85, green: 0.65, blue: 0.2)
|
||||
/// Gold light for highlights.
|
||||
public static let goldLight = Color(red: 0.85, green: 0.7, blue: 0.35)
|
||||
/// Gold dark for gradients.
|
||||
public static let goldDark = Color(red: 0.65, green: 0.5, blue: 0.2)
|
||||
}
|
||||
|
||||
/// Top bar colors.
|
||||
|
||||
@ -8,43 +8,46 @@
|
||||
import SwiftUI
|
||||
|
||||
/// A casino table felt background with radial gradient.
|
||||
/// This creates a consistent look across all casino games.
|
||||
public struct TableBackgroundView: View {
|
||||
/// The primary felt color (center of gradient).
|
||||
public let feltColor: Color
|
||||
|
||||
/// The darker edge color for the gradient.
|
||||
public let edgeColor: Color
|
||||
|
||||
/// Whether to show the decorative felt pattern.
|
||||
public let showPattern: Bool
|
||||
|
||||
/// Creates a table background.
|
||||
/// - Parameters:
|
||||
/// - feltColor: The main felt color (default: casino green).
|
||||
/// - edgeColor: The darker edge color (default: dark green).
|
||||
/// - showPattern: Whether to show the decorative pattern (default: true).
|
||||
/// Creates a table background with the standard casino felt colors.
|
||||
/// - Parameter showPattern: Whether to show the decorative pattern (default: true).
|
||||
public init(showPattern: Bool = true) {
|
||||
self.showPattern = showPattern
|
||||
}
|
||||
|
||||
/// Legacy initializer for backwards compatibility (colors are ignored).
|
||||
@available(*, deprecated, message: "Use init(showPattern:) - colors are now standardized")
|
||||
public init(
|
||||
feltColor: Color = Color.CasinoTable.felt,
|
||||
edgeColor: Color = Color.CasinoTable.feltDark,
|
||||
feltColor: Color,
|
||||
edgeColor: Color,
|
||||
showPattern: Bool = true
|
||||
) {
|
||||
self.feltColor = feltColor
|
||||
self.edgeColor = edgeColor
|
||||
self.showPattern = showPattern
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
ZStack {
|
||||
// Base gradient
|
||||
// Base dark layer (deepest background)
|
||||
Color.CasinoTable.baseDark
|
||||
.ignoresSafeArea()
|
||||
|
||||
// Radial gradient for depth (lighter center, darker edges)
|
||||
RadialGradient(
|
||||
colors: [feltColor, edgeColor],
|
||||
colors: [
|
||||
Color.CasinoTable.backgroundLight,
|
||||
Color.CasinoTable.backgroundDark
|
||||
],
|
||||
center: .center,
|
||||
startRadius: 50,
|
||||
endRadius: 600
|
||||
)
|
||||
.ignoresSafeArea()
|
||||
|
||||
// Optional pattern overlay
|
||||
// Optional subtle felt texture pattern
|
||||
if showPattern {
|
||||
FeltPatternView()
|
||||
.opacity(CasinoDesign.Opacity.verySubtle)
|
||||
@ -55,39 +58,39 @@ public struct TableBackgroundView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// A subtle decorative pattern for the felt.
|
||||
/// A subtle decorative pattern for the felt (random dot texture).
|
||||
public struct FeltPatternView: View {
|
||||
public init() {}
|
||||
|
||||
public var body: some View {
|
||||
GeometryReader { geometry in
|
||||
Canvas { context, size in
|
||||
let spacing = CasinoDesign.Size.patternSpacing
|
||||
let diamondSize = CasinoDesign.Size.patternDiamondSize
|
||||
// Use deterministic pseudo-random for consistent appearance
|
||||
var rng = SeededRandomNumberGenerator(seed: 12345)
|
||||
let dotCount = 2000
|
||||
|
||||
for x in stride(from: 0, to: size.width, by: spacing) {
|
||||
for y in stride(from: 0, to: size.height, by: spacing) {
|
||||
let offsetX = Int(y / spacing).isMultiple(of: 2) ? spacing / 2 : 0
|
||||
let rect = CGRect(
|
||||
x: x + offsetX - diamondSize / 2,
|
||||
y: y - diamondSize / 2,
|
||||
width: diamondSize,
|
||||
height: diamondSize
|
||||
)
|
||||
for _ in 0..<dotCount {
|
||||
let x = Double.random(in: 0...size.width, using: &rng)
|
||||
let y = Double.random(in: 0...size.height, using: &rng)
|
||||
let dotSize = Double.random(in: 1...2, using: &rng)
|
||||
|
||||
let path = Path { p in
|
||||
p.move(to: CGPoint(x: rect.midX, y: rect.minY))
|
||||
p.addLine(to: CGPoint(x: rect.maxX, y: rect.midY))
|
||||
p.addLine(to: CGPoint(x: rect.midX, y: rect.maxY))
|
||||
p.addLine(to: CGPoint(x: rect.minX, y: rect.midY))
|
||||
p.closeSubpath()
|
||||
let rect = CGRect(x: x, y: y, width: dotSize, height: dotSize)
|
||||
context.fill(Path(ellipseIn: rect), with: .color(.white))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple seeded random number generator for consistent felt pattern.
|
||||
private struct SeededRandomNumberGenerator: RandomNumberGenerator {
|
||||
private var state: UInt64
|
||||
|
||||
init(seed: UInt64) {
|
||||
state = seed
|
||||
}
|
||||
|
||||
context.fill(path, with: .color(.white))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mutating func next() -> UInt64 {
|
||||
state = state &* 6364136223846793005 &+ 1442695040888963407
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user