Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>

This commit is contained in:
Matt Bruce 2025-12-17 16:10:54 -06:00
parent 7dd1edd351
commit 73019babde
8 changed files with 321 additions and 429 deletions

View File

@ -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)
}
}

View File

@ -6,6 +6,7 @@
//
import SwiftUI
import CasinoKit
/// The available rule pages.
enum RulesPage: Int, CaseIterable, Identifiable {

View File

@ -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" : {

View File

@ -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)
}
}

View File

@ -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 }

View File

@ -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

View File

@ -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.

View File

@ -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
}
}