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

This commit is contained in:
Matt Bruce 2025-12-16 18:13:45 -06:00
parent da7dcc1633
commit 884bc988f6
8 changed files with 268 additions and 200 deletions

View File

@ -67,17 +67,20 @@ enum Design {
enum Size { enum Size {
static let chipSmall: CGFloat = 36 static let chipSmall: CGFloat = 36
static let chipMedium: CGFloat = 50 static let chipMedium: CGFloat = 50
static let chipSelector: CGFloat = 50
static let cardWidthSmall: CGFloat = 45 static let cardWidthSmall: CGFloat = 45
static let cardWidthMedium: CGFloat = 55 static let cardWidthMedium: CGFloat = 55
static let cardWidthLarge: CGFloat = 65 static let cardWidthLarge: CGFloat = 65
static let valueBadge: CGFloat = 26 static let valueBadge: CGFloat = 26
static let checkmark: CGFloat = 22 static let checkmark: CGFloat = 22
static let tableAspectRatio: CGFloat = 1.6 static let tableAspectRatio: CGFloat = 1.6
static let roadMapCell: CGFloat = 16
} }
// MARK: - Animation // MARK: - Animation
enum Animation { enum Animation {
static let quick: Double = 0.3
static let springDuration: Double = 0.4 static let springDuration: Double = 0.4
static let springBounce: Double = 0.3 static let springBounce: Double = 0.3
static let fadeInDuration: Double = 0.3 static let fadeInDuration: Double = 0.3
@ -90,7 +93,9 @@ enum Design {
static let disabled: Double = 0.5 static let disabled: Double = 0.5
static let subtle: Double = 0.1 static let subtle: Double = 0.1
static let light: Double = 0.3 static let light: Double = 0.3
static let overlay: Double = 0.4
static let medium: Double = 0.5 static let medium: Double = 0.5
static let secondary: Double = 0.5
static let strong: Double = 0.7 static let strong: Double = 0.7
static let heavy: Double = 0.8 static let heavy: Double = 0.8
static let nearOpaque: Double = 0.85 static let nearOpaque: Double = 0.85
@ -100,6 +105,7 @@ enum Design {
enum LineWidth { enum LineWidth {
static let thin: CGFloat = 1 static let thin: CGFloat = 1
static let standard: CGFloat = 2
static let medium: CGFloat = 2 static let medium: CGFloat = 2
static let thick: CGFloat = 3 static let thick: CGFloat = 3
static let heavy: CGFloat = 4 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 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 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 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 // MARK: - Border Colors
@ -171,6 +178,49 @@ extension Color {
enum Chip { enum Chip {
static let gold = Color(red: 0.8, green: 0.65, blue: 0.2) 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 // MARK: - Modal Colors

View File

@ -123,12 +123,12 @@ struct CardBackView: View {
var body: some View { var body: some View {
ZStack { ZStack {
// Base // Base
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: Design.CornerRadius.small)
.fill( .fill(
LinearGradient( LinearGradient(
colors: [ colors: [
Color(red: 0.6, green: 0.1, blue: 0.15), Color.Card.backDark,
Color(red: 0.4, green: 0.05, blue: 0.1) Color.Card.backLight
], ],
startPoint: .topLeading, startPoint: .topLeading,
endPoint: .bottomTrailing endPoint: .bottomTrailing
@ -136,26 +136,26 @@ struct CardBackView: View {
) )
// Border // Border
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: Design.CornerRadius.small)
.strokeBorder( .strokeBorder(
LinearGradient( LinearGradient(
colors: [ colors: [
Color(red: 0.9, green: 0.7, blue: 0.4), Color.Card.patternLight,
Color(red: 0.7, green: 0.5, blue: 0.2) Color.Card.patternDark
], ],
startPoint: .topLeading, startPoint: .topLeading,
endPoint: .bottomTrailing endPoint: .bottomTrailing
), ),
lineWidth: 2 lineWidth: Design.LineWidth.medium
) )
// Inner pattern area // Inner pattern area
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: Design.CornerRadius.small / 2)
.fill( .fill(
LinearGradient( LinearGradient(
colors: [ colors: [
Color(red: 0.5, green: 0.08, blue: 0.12), Color.Card.innerDark,
Color(red: 0.35, green: 0.04, blue: 0.08) Color.Card.innerLight
], ],
startPoint: .top, startPoint: .top,
endPoint: .bottom endPoint: .bottom
@ -166,18 +166,18 @@ struct CardBackView: View {
// Diamond pattern overlay // Diamond pattern overlay
DiamondPatternView() DiamondPatternView()
.foregroundStyle( .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) .padding(width * 0.12)
.clipShape(RoundedRectangle(cornerRadius: 4)) .clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.small / 2))
// Center emblem // Center emblem
Circle() Circle()
.fill( .fill(
RadialGradient( RadialGradient(
colors: [ colors: [
Color(red: 0.9, green: 0.7, blue: 0.4), Color.Card.patternLight,
Color(red: 0.7, green: 0.5, blue: 0.2) Color.Card.patternDark
], ],
center: .center, center: .center,
startRadius: 0, startRadius: 0,
@ -189,10 +189,10 @@ struct CardBackView: View {
// B for Baccarat // B for Baccarat
Text("B") Text("B")
.font(.system(size: width * 0.18, weight: .bold, design: .serif)) .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) .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 { var body: some View {
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: Design.CornerRadius.small)
.strokeBorder( .strokeBorder(
Color.white.opacity(0.3), Color.white.opacity(Design.Opacity.light),
style: StrokeStyle(lineWidth: 2, dash: [8, 4]) style: StrokeStyle(lineWidth: Design.LineWidth.medium, dash: [8, 4])
) )
.frame(width: width, height: height) .frame(width: width, height: height)
} }
@ -241,10 +241,10 @@ struct CardPlaceholderView: View {
#Preview { #Preview {
ZStack { ZStack {
Color(red: 0.0, green: 0.3, blue: 0.15) Color.Table.preview
.ignoresSafeArea() .ignoresSafeArea()
HStack(spacing: 20) { HStack(spacing: Design.Spacing.xLarge) {
CardView(card: Card(suit: .hearts, rank: .ace), isFaceUp: true) CardView(card: Card(suit: .hearts, rank: .ace), isFaceUp: true)
CardView(card: Card(suit: .spades, rank: .king), isFaceUp: true) CardView(card: Card(suit: .spades, rank: .king), isFaceUp: true)
CardView(card: Card(suit: .diamonds, rank: .seven), isFaceUp: false) CardView(card: Card(suit: .diamonds, rank: .seven), isFaceUp: false)

View File

@ -28,24 +28,24 @@ struct ChipSelectorView: View {
var body: some View { var body: some View {
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack(spacing: 10) { HStack(spacing: Design.Spacing.small) {
ForEach(availableChips) { denomination in ForEach(availableChips) { denomination in
Button { Button {
selectedChip = denomination selectedChip = denomination
} label: { } label: {
ChipView( ChipView(
denomination: denomination, denomination: denomination,
size: 50, size: Design.Size.chipSelector,
isSelected: selectedChip == denomination isSelected: selectedChip == denomination
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
.opacity(balance >= denomination.rawValue ? 1.0 : 0.4) .opacity(balance >= denomination.rawValue ? 1.0 : Design.Opacity.overlay)
.disabled(balance < denomination.rawValue) .disabled(balance < denomination.rawValue)
} }
} }
.padding(.horizontal) .padding(.horizontal)
.padding(.vertical, 6) // Extra padding for selection scale effect .padding(.vertical, Design.Spacing.xSmall) // Extra padding for selection scale effect
} }
.scrollIndicators(.hidden) .scrollIndicators(.hidden)
.onChange(of: balance) { _, newBalance in .onChange(of: balance) { _, newBalance in
@ -61,10 +61,10 @@ struct ChipSelectorView: View {
#Preview { #Preview {
ZStack { ZStack {
Color(red: 0.0, green: 0.3, blue: 0.15) Color.Table.preview
.ignoresSafeArea() .ignoresSafeArea()
VStack(spacing: 20) { VStack(spacing: Design.Spacing.xLarge) {
Text("Balance: $50,000") Text("Balance: $50,000")
.foregroundStyle(.white) .foregroundStyle(.white)

View File

@ -68,34 +68,34 @@ enum ChipDenomination: Int, CaseIterable, Identifiable {
/// The primary color for this chip. /// The primary color for this chip.
var primaryColor: Color { var primaryColor: Color {
switch self { switch self {
case .ten: return Color(red: 0.2, green: 0.4, blue: 0.8) // Blue case .ten: return Color.Chip.tenBase
case .twentyFive: return Color(red: 0.1, green: 0.6, blue: 0.3) // Green case .twentyFive: return Color.Chip.twentyFiveBase
case .fifty: return Color(red: 0.8, green: 0.5, blue: 0.1) // Orange case .fifty: return Color.Chip.fiftyBase
case .hundred: return Color(red: 0.1, green: 0.1, blue: 0.1) // Black case .hundred: return Color.Chip.hundredBase
case .fiveHundred: return Color(red: 0.6, green: 0.2, blue: 0.6) // Purple case .fiveHundred: return Color.Chip.fiveHundredBase
case .thousand: return Color(red: 0.8, green: 0.65, blue: 0.2) // Gold case .thousand: return Color.Chip.thousandBase
case .fiveThousand: return Color(red: 0.7, green: 0.1, blue: 0.2) // Crimson case .fiveThousand: return Color.Chip.fiveThousandBase
case .tenThousand: return Color(red: 0.2, green: 0.5, blue: 0.5) // Teal case .tenThousand: return Color.Chip.tenThousandBase
case .twentyFiveThousand: return Color(red: 0.5, green: 0.3, blue: 0.1) // Bronze case .twentyFiveThousand: return Color.Chip.twentyFiveThousandBase
case .fiftyThousand: return Color(red: 0.75, green: 0.75, blue: 0.8) // Platinum case .fiftyThousand: return Color.Chip.fiftyThousandBase
case .hundredThousand: return Color(red: 0.9, green: 0.1, blue: 0.3) // Ruby case .hundredThousand: return Color.Chip.hundredThousandBase
} }
} }
/// The secondary/accent color for this chip. /// The secondary/accent color for this chip.
var secondaryColor: Color { var secondaryColor: Color {
switch self { switch self {
case .ten: return Color(red: 0.3, green: 0.5, blue: 0.9) case .ten: return Color.Chip.tenHighlight
case .twentyFive: return Color(red: 0.2, green: 0.7, blue: 0.4) case .twentyFive: return Color.Chip.twentyFiveHighlight
case .fifty: return Color(red: 0.9, green: 0.6, blue: 0.2) case .fifty: return Color.Chip.fiftyHighlight
case .hundred: return Color(red: 0.3, green: 0.3, blue: 0.3) case .hundred: return Color.Chip.hundredHighlight
case .fiveHundred: return Color(red: 0.7, green: 0.3, blue: 0.7) case .fiveHundred: return Color.Chip.fiveHundredHighlight
case .thousand: return Color(red: 0.9, green: 0.75, blue: 0.3) case .thousand: return Color.Chip.thousandHighlight
case .fiveThousand: return Color(red: 0.85, green: 0.2, blue: 0.3) case .fiveThousand: return Color.Chip.fiveThousandHighlight
case .tenThousand: return Color(red: 0.3, green: 0.6, blue: 0.6) case .tenThousand: return Color.Chip.tenThousandHighlight
case .twentyFiveThousand: return Color(red: 0.65, green: 0.45, blue: 0.2) case .twentyFiveThousand: return Color.Chip.twentyFiveThousandHighlight
case .fiftyThousand: return Color(red: 0.85, green: 0.85, blue: 0.9) case .fiftyThousand: return Color.Chip.fiftyThousandHighlight
case .hundredThousand: return Color(red: 1.0, green: 0.2, blue: 0.4) case .hundredThousand: return Color.Chip.hundredThousandHighlight
} }
} }
@ -103,14 +103,14 @@ enum ChipDenomination: Int, CaseIterable, Identifiable {
var stripeColor: Color { var stripeColor: Color {
switch self { switch self {
case .ten, .twentyFive, .fifty: return .white 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 .fiveHundred: return .white
case .thousand: return .black 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 .tenThousand: return .white
case .twentyFiveThousand: return Color(red: 0.9, green: 0.75, blue: 0.3) case .twentyFiveThousand: return Color.Chip.goldStripe
case .fiftyThousand: return Color(red: 0.2, green: 0.2, blue: 0.3) // Dark stripes case .fiftyThousand: return Color.Chip.darkStripe
case .hundredThousand: return Color(red: 0.9, green: 0.85, blue: 0.3) // Gold stripes case .hundredThousand: return Color.Chip.goldRubyStripe
} }
} }
} }
@ -274,13 +274,13 @@ struct ChipStackView: View {
#Preview { #Preview {
ZStack { ZStack {
Color(red: 0.0, green: 0.3, blue: 0.15) Color.Table.preview
.ignoresSafeArea() .ignoresSafeArea()
VStack(spacing: 30) { VStack(spacing: Design.Spacing.xxxLarge) {
HStack(spacing: 15) { HStack(spacing: Design.Spacing.xLarge) {
ForEach(ChipDenomination.allCases) { denom in ForEach(ChipDenomination.allCases) { denom in
ChipView(denomination: denom, size: 50) ChipView(denomination: denom, size: Design.Size.chipSelector)
} }
} }

View File

@ -46,7 +46,7 @@ struct GameTableView: View {
onSettings: { showSettings = true } onSettings: { showSettings = true }
) )
Spacer(minLength: 4) Spacer(minLength: Design.Spacing.xSmall)
// Cards display area // Cards display area
CardsDisplayArea( CardsDisplayArea(
@ -61,7 +61,7 @@ struct GameTableView: View {
isTie: isTie isTie: isTie
) )
Spacer(minLength: 4) Spacer(minLength: Design.Spacing.xSmall)
// Road map history // Road map history
if settings.showHistory && !state.roundHistory.isEmpty { if settings.showHistory && !state.roundHistory.isEmpty {
@ -69,16 +69,16 @@ struct GameTableView: View {
.padding(.horizontal) .padding(.horizontal)
} }
Spacer(minLength: 8) Spacer(minLength: Design.Spacing.small)
// Mini Baccarat betting table // Mini Baccarat betting table
MiniBaccaratTableView( MiniBaccaratTableView(
gameState: state, gameState: state,
selectedChip: selectedChip 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! // Chip selector - shows higher chips as you win more!
ChipSelectorView( ChipSelectorView(
@ -86,7 +86,7 @@ struct GameTableView: View {
balance: state.balance, balance: state.balance,
maxBet: state.maxBet maxBet: state.maxBet
) )
.padding(.bottom, 12) .padding(.bottom, Design.Spacing.medium)
// Action buttons // Action buttons
ActionButtonsView( ActionButtonsView(
@ -100,7 +100,7 @@ struct GameTableView: View {
onNewRound: { state.newRound() } onNewRound: { state.newRound() }
) )
.padding(.horizontal) .padding(.horizontal)
.padding(.bottom, 4) .padding(.bottom, Design.Spacing.xSmall)
} }
.safeAreaPadding(.bottom) .safeAreaPadding(.bottom)
@ -295,11 +295,11 @@ struct CardsDisplayArea: View {
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = 14 @ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = 14
var body: some View { var body: some View {
HStack(spacing: 32) { HStack(spacing: Design.Spacing.xxxLarge) {
// Player side // Player side
VStack(spacing: 10) { VStack(spacing: Design.Spacing.small) {
// Label with value // Label with value
HStack(spacing: 8) { HStack(spacing: Design.Spacing.small) {
Text("PLAYER") Text("PLAYER")
.font(.system(size: labelFontSize, weight: .bold, design: .rounded)) .font(.system(size: labelFontSize, weight: .bold, design: .rounded))
.foregroundStyle(.white) .foregroundStyle(.white)
@ -319,9 +319,9 @@ struct CardsDisplayArea: View {
} }
// Banker side // Banker side
VStack(spacing: 10) { VStack(spacing: Design.Spacing.small) {
// Label with value // Label with value
HStack(spacing: 8) { HStack(spacing: Design.Spacing.small) {
Text("BANKER") Text("BANKER")
.font(.system(size: labelFontSize, weight: .bold, design: .rounded)) .font(.system(size: labelFontSize, weight: .bold, design: .rounded))
.foregroundStyle(.white) .foregroundStyle(.white)
@ -340,11 +340,11 @@ struct CardsDisplayArea: View {
) )
} }
} }
.padding(.top, 16) .padding(.top, Design.Spacing.large)
.padding(.bottom, 14) .padding(.bottom, Design.Spacing.xLarge)
.padding(.horizontal, 20) .padding(.horizontal, Design.Spacing.xLarge)
.background( .background(
RoundedRectangle(cornerRadius: 14) RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
.fill(Color.black.opacity(0.25)) .fill(Color.black.opacity(0.25))
) )
.padding(.horizontal) .padding(.horizontal)
@ -385,12 +385,12 @@ struct CompactHandView: View {
} }
} }
} }
.padding(6) .padding(Design.Spacing.xSmall)
.background( .background(
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: Design.CornerRadius.small)
.strokeBorder( .strokeBorder(
isWinner ? Color.yellow : Color.clear, isWinner ? Color.yellow : Color.clear,
lineWidth: 2 lineWidth: Design.LineWidth.standard
) )
) )
.overlay(alignment: .bottom) { .overlay(alignment: .bottom) {
@ -398,13 +398,13 @@ struct CompactHandView: View {
Text("WIN") Text("WIN")
.font(.system(size: winBadgeFontSize, weight: .black)) .font(.system(size: winBadgeFontSize, weight: .black))
.foregroundStyle(.black) .foregroundStyle(.black)
.padding(.horizontal, 8) .padding(.horizontal, Design.Spacing.small)
.padding(.vertical, 2) .padding(.vertical, Design.Spacing.xxSmall)
.background( .background(
Capsule() Capsule()
.fill(Color.yellow) .fill(Color.yellow)
) )
.offset(y: 10) .offset(y: Design.Spacing.small)
} }
} }
} }
@ -437,13 +437,13 @@ struct TableBackgroundView: View {
var body: some View { var body: some View {
ZStack { ZStack {
// Base dark green // Base dark green
Color(red: 0.02, green: 0.15, blue: 0.08) Color.Table.baseDark
// Radial gradient for depth // Radial gradient for depth
RadialGradient( RadialGradient(
colors: [ colors: [
Color(red: 0.03, green: 0.25, blue: 0.12), Color.Table.backgroundLight,
Color(red: 0.01, green: 0.12, blue: 0.06) Color.Table.backgroundDark
], ],
center: .center, center: .center,
startRadius: 50, startRadius: 50,
@ -452,7 +452,7 @@ struct TableBackgroundView: View {
// Subtle felt texture // Subtle felt texture
FeltPatternView() FeltPatternView()
.opacity(0.03) .opacity(Design.Opacity.subtle / 3)
} }
.ignoresSafeArea() .ignoresSafeArea()
} }
@ -493,13 +493,13 @@ struct TopBarView: View {
var body: some View { var body: some View {
HStack { HStack {
// Balance display // Balance display
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text("BALANCE") Text("BALANCE")
.font(.system(size: labelFontSize, weight: .medium, design: .rounded)) .font(.system(size: labelFontSize, weight: .medium, design: .rounded))
.foregroundStyle(.white.opacity(0.6)) .foregroundStyle(.white.opacity(0.6))
.tracking(1) .tracking(1)
HStack(spacing: 4) { HStack(spacing: Design.Spacing.xSmall) {
Text("$") Text("$")
.font(.system(size: currencyFontSize, weight: .bold)) .font(.system(size: currencyFontSize, weight: .bold))
.foregroundStyle(.yellow.opacity(0.8)) .foregroundStyle(.yellow.opacity(0.8))
@ -508,27 +508,27 @@ struct TopBarView: View {
.font(.system(size: balanceFontSize, weight: .black, design: .rounded)) .font(.system(size: balanceFontSize, weight: .black, design: .rounded))
.foregroundStyle(.white) .foregroundStyle(.white)
.contentTransition(.numericText()) .contentTransition(.numericText())
.animation(.spring(duration: 0.3), value: balance) .animation(.spring(duration: Design.Animation.quick), value: balance)
} }
} }
.padding(.horizontal, 14) .padding(.horizontal, Design.Spacing.xLarge)
.padding(.vertical, 6) .padding(.vertical, Design.Spacing.xSmall)
.background( .background(
Capsule() Capsule()
.fill(Color.black.opacity(0.4)) .fill(Color.black.opacity(Design.Opacity.overlay))
) )
Spacer() Spacer()
// Cards remaining indicator (if enabled) // Cards remaining indicator (if enabled)
if showCardsRemaining { if showCardsRemaining {
HStack(spacing: 4) { HStack(spacing: Design.Spacing.xSmall) {
Image(systemName: "rectangle.portrait.on.rectangle.portrait.fill") Image(systemName: "rectangle.portrait.on.rectangle.portrait.fill")
.font(.system(size: smallFontSize)) .font(.system(size: smallFontSize))
Text("\(cardsRemaining)") Text("\(cardsRemaining)")
.font(.system(size: smallFontSize, weight: .medium)) .font(.system(size: smallFontSize, weight: .medium))
} }
.foregroundStyle(.white.opacity(0.5)) .foregroundStyle(.white.opacity(Design.Opacity.secondary))
Spacer() Spacer()
} }
@ -538,25 +538,25 @@ struct TopBarView: View {
.labelStyle(.iconOnly) .labelStyle(.iconOnly)
.font(.system(size: buttonFontSize)) .font(.system(size: buttonFontSize))
.foregroundStyle(.white.opacity(0.6)) .foregroundStyle(.white.opacity(0.6))
.padding(8) .padding(Design.Spacing.small)
.background( .background(
Circle() Circle()
.fill(Color.black.opacity(0.4)) .fill(Color.black.opacity(Design.Opacity.overlay))
) )
// Reset button // Reset button
Button("Reset", systemImage: "arrow.counterclockwise", action: onReset) Button("Reset", systemImage: "arrow.counterclockwise", action: onReset)
.font(.system(size: smallFontSize, weight: .medium)) .font(.system(size: smallFontSize, weight: .medium))
.foregroundStyle(.white.opacity(0.6)) .foregroundStyle(.white.opacity(0.6))
.padding(.horizontal, 10) .padding(.horizontal, Design.Spacing.small)
.padding(.vertical, 6) .padding(.vertical, Design.Spacing.xSmall)
.background( .background(
Capsule() Capsule()
.fill(Color.black.opacity(0.4)) .fill(Color.black.opacity(Design.Opacity.overlay))
) )
} }
.padding(.horizontal) .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 @ScaledMetric(relativeTo: .body) private var statusFontSize: CGFloat = 14
var body: some View { var body: some View {
HStack(spacing: 12) { HStack(spacing: Design.Spacing.medium) {
if gameState.currentPhase == .betting { if gameState.currentPhase == .betting {
// Clear bets button // Clear bets button
Button("Clear", systemImage: "xmark.circle", action: onClear) Button("Clear", systemImage: "xmark.circle", action: onClear)
.font(.system(size: clearButtonFontSize, weight: .semibold)) .font(.system(size: clearButtonFontSize, weight: .semibold))
.foregroundStyle(.white) .foregroundStyle(.white)
.padding(.horizontal, 20) .padding(.horizontal, Design.Spacing.xLarge)
.padding(.vertical, 12) .padding(.vertical, Design.Spacing.medium)
.background( .background(
Capsule() Capsule()
.fill(Color.Button.destructive) .fill(Color.Button.destructive)
@ -593,8 +593,8 @@ struct ActionButtonsView: View {
Button("Deal", systemImage: "play.fill", action: onDeal) Button("Deal", systemImage: "play.fill", action: onDeal)
.font(.system(size: primaryButtonFontSize, weight: .bold)) .font(.system(size: primaryButtonFontSize, weight: .bold))
.foregroundStyle(.black) .foregroundStyle(.black)
.padding(.horizontal, 32) .padding(.horizontal, Design.Spacing.xxxLarge)
.padding(.vertical, 12) .padding(.vertical, Design.Spacing.medium)
.background( .background(
Capsule() Capsule()
.fill( .fill(
@ -613,8 +613,8 @@ struct ActionButtonsView: View {
Button("New Round", systemImage: "arrow.right.circle", action: onNewRound) Button("New Round", systemImage: "arrow.right.circle", action: onNewRound)
.font(.system(size: primaryButtonFontSize, weight: .bold)) .font(.system(size: primaryButtonFontSize, weight: .bold))
.foregroundStyle(.black) .foregroundStyle(.black)
.padding(.horizontal, 32) .padding(.horizontal, Design.Spacing.xxxLarge)
.padding(.vertical, 12) .padding(.vertical, Design.Spacing.medium)
.background( .background(
Capsule() Capsule()
.fill( .fill(
@ -628,7 +628,7 @@ struct ActionButtonsView: View {
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium) .shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusMedium)
} else { } else {
// Playing indicator // Playing indicator
HStack(spacing: 6) { HStack(spacing: Design.Spacing.xSmall) {
ProgressView() ProgressView()
.tint(.white) .tint(.white)
.scaleEffect(0.8) .scaleEffect(0.8)
@ -636,8 +636,8 @@ struct ActionButtonsView: View {
.font(.system(size: statusFontSize, weight: .medium)) .font(.system(size: statusFontSize, weight: .medium))
.foregroundStyle(.white.opacity(Design.Opacity.heavy)) .foregroundStyle(.white.opacity(Design.Opacity.heavy))
} }
.padding(.horizontal, 20) .padding(.horizontal, Design.Spacing.xLarge)
.padding(.vertical, 12) .padding(.vertical, Design.Spacing.medium)
} }
} }
} }

View File

@ -16,18 +16,23 @@ struct ResultBannerView: View {
@State private var showText = false @State private var showText = false
@State private var showWinnings = 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 { var body: some View {
ZStack { ZStack {
// Background overlay // Background overlay
Color.black.opacity(showBanner ? 0.5 : 0) Color.black.opacity(showBanner ? Design.Opacity.medium : 0)
.ignoresSafeArea() .ignoresSafeArea()
.animation(.easeIn(duration: 0.3), value: showBanner) .animation(.easeIn(duration: Design.Animation.fadeInDuration), value: showBanner)
// Banner // Banner
VStack(spacing: 20) { VStack(spacing: Design.Spacing.xLarge) {
// Result text // Result text
Text(result.displayText) Text(result.displayText)
.font(.system(size: 36, weight: .black, design: .rounded)) .font(.system(size: resultFontSize, weight: .black, design: .rounded))
.foregroundStyle( .foregroundStyle(
LinearGradient( LinearGradient(
colors: [.white, result.color], colors: [.white, result.color],
@ -35,13 +40,13 @@ struct ResultBannerView: View {
endPoint: .bottom 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) .scaleEffect(showText ? 1.0 : 0.5)
.opacity(showText ? 1.0 : 0) .opacity(showText ? 1.0 : 0)
// Winnings display // Winnings display
if winnings != 0 { if winnings != 0 {
HStack(spacing: 8) { HStack(spacing: Design.Spacing.small) {
if winnings > 0 { if winnings > 0 {
Image(systemName: "plus.circle.fill") Image(systemName: "plus.circle.fill")
.foregroundStyle(.green) .foregroundStyle(.green)
@ -54,14 +59,14 @@ struct ResultBannerView: View {
.foregroundStyle(.red) .foregroundStyle(.red)
} }
} }
.font(.system(size: 28, weight: .bold, design: .rounded)) .font(.system(size: winningsFontSize, weight: .bold, design: .rounded))
.scaleEffect(showWinnings ? 1.0 : 0.5) .scaleEffect(showWinnings ? 1.0 : 0.5)
.opacity(showWinnings ? 1.0 : 0) .opacity(showWinnings ? 1.0 : 0)
} }
} }
.padding(40) .padding(Design.Spacing.xxxLarge + Design.Spacing.small)
.background( .background(
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge + Design.Spacing.xSmall)
.fill( .fill(
LinearGradient( LinearGradient(
colors: [ colors: [
@ -73,34 +78,34 @@ struct ResultBannerView: View {
) )
) )
.overlay( .overlay(
RoundedRectangle(cornerRadius: 24) RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge + Design.Spacing.xSmall)
.strokeBorder( .strokeBorder(
LinearGradient( LinearGradient(
colors: [ colors: [
result.color.opacity(0.8), result.color.opacity(Design.Opacity.heavy),
result.color.opacity(0.3) result.color.opacity(Design.Opacity.light)
], ],
startPoint: .topLeading, startPoint: .topLeading,
endPoint: .bottomTrailing 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) .scaleEffect(showBanner ? 1.0 : 0.8)
.opacity(showBanner ? 1.0 : 0) .opacity(showBanner ? 1.0 : 0)
} }
.onAppear { .onAppear {
withAnimation(.spring(duration: 0.4, bounce: 0.3)) { withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)) {
showBanner = true 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 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 showWinnings = true
} }
} }
@ -114,10 +119,13 @@ struct ConfettiPiece: View {
@State private var rotation: Double = 0 @State private var rotation: Double = 0
@State private var opacity: Double = 1 @State private var opacity: Double = 1
private let confettiWidth: CGFloat = 8
private let confettiHeight: CGFloat = 12
var body: some View { var body: some View {
Rectangle() Rectangle()
.fill(color) .fill(color)
.frame(width: 8, height: 12) .frame(width: confettiWidth, height: confettiHeight)
.rotationEffect(.degrees(rotation)) .rotationEffect(.degrees(rotation))
.position(position) .position(position)
.opacity(opacity) .opacity(opacity)
@ -154,7 +162,7 @@ struct ConfettiView: View {
#Preview { #Preview {
ZStack { ZStack {
Color(red: 0.0, green: 0.3, blue: 0.15) Color.Table.preview
.ignoresSafeArea() .ignoresSafeArea()
ResultBannerView(result: .playerWins, winnings: 500) ResultBannerView(result: .playerWins, winnings: 500)

View File

@ -11,28 +11,33 @@ import SwiftUI
struct RoadMapView: View { struct RoadMapView: View {
let results: [RoundResult] 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 { var body: some View {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: Design.Spacing.xSmall) {
Text("HISTORY") Text("HISTORY")
.font(.system(size: 10, weight: .bold, design: .rounded)) .font(.system(size: historyFontSize, weight: .bold, design: .rounded))
.foregroundStyle(.white.opacity(0.6)) .foregroundStyle(.white.opacity(0.6))
.tracking(1) .tracking(1)
ScrollView(.horizontal) { ScrollView(.horizontal) {
HStack(spacing: 4) { HStack(spacing: Design.Spacing.xSmall) {
ForEach(results) { result in ForEach(results) { result in
RoadDot(result: result.result) RoadDot(result: result.result, dotSize: dotSize)
} }
} }
.padding(.vertical, 4) .padding(.vertical, Design.Spacing.xSmall)
} }
.scrollIndicators(.hidden) .scrollIndicators(.hidden)
} }
.padding(.horizontal, 12) .padding(.horizontal, Design.Spacing.medium)
.padding(.vertical, 8) .padding(.vertical, Design.Spacing.small)
.background( .background(
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: Design.CornerRadius.small)
.fill(Color.black.opacity(0.3)) .fill(Color.black.opacity(Design.Opacity.light))
) )
} }
} }
@ -40,6 +45,11 @@ struct RoadMapView: View {
/// A single dot in the road display. /// A single dot in the road display.
struct RoadDot: View { struct RoadDot: View {
let result: GameResult 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 { private var color: Color {
switch result { switch result {
@ -61,14 +71,14 @@ struct RoadDot: View {
ZStack { ZStack {
Circle() Circle()
.fill(color) .fill(color)
.frame(width: 22, height: 22) .frame(width: dotSize, height: dotSize)
Circle() Circle()
.strokeBorder(Color.white.opacity(0.3), lineWidth: 1) .strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin)
.frame(width: 22, height: 22) .frame(width: dotSize, height: dotSize)
Text(label) Text(label)
.font(.system(size: 10, weight: .bold)) .font(.system(size: labelFontSize, weight: .bold))
.foregroundStyle(.white) .foregroundStyle(.white)
} }
} }
@ -76,7 +86,7 @@ struct RoadDot: View {
#Preview { #Preview {
ZStack { ZStack {
Color(red: 0.0, green: 0.3, blue: 0.15) Color.Table.preview
.ignoresSafeArea() .ignoresSafeArea()
RoadMapView(results: [ RoadMapView(results: [

View File

@ -19,11 +19,11 @@ struct SettingsView: View {
NavigationStack { NavigationStack {
ZStack { ZStack {
// Background // Background
Color(red: 0.08, green: 0.12, blue: 0.08) Color.Settings.background
.ignoresSafeArea() .ignoresSafeArea()
ScrollView { ScrollView {
VStack(spacing: 24) { VStack(spacing: Design.Spacing.xxLarge) {
// Table Limits Section (First!) // Table Limits Section (First!)
SettingsSection(title: "TABLE LIMITS", icon: "banknote") { SettingsSection(title: "TABLE LIMITS", icon: "banknote") {
TableLimitsPicker(selection: $settings.tableLimits) TableLimitsPicker(selection: $settings.tableLimits)
@ -57,7 +57,7 @@ struct SettingsView: View {
) )
Divider() Divider()
.background(Color.white.opacity(0.1)) .background(Color.white.opacity(Design.Opacity.subtle))
SettingsToggle( SettingsToggle(
title: "Show History", title: "Show History",
@ -76,7 +76,7 @@ struct SettingsView: View {
if settings.showAnimations { if settings.showAnimations {
Divider() Divider()
.background(Color.white.opacity(0.1)) .background(Color.white.opacity(Design.Opacity.subtle))
SpeedPicker(speed: $settings.dealingSpeed) SpeedPicker(speed: $settings.dealingSpeed)
} }
@ -91,24 +91,24 @@ struct SettingsView: View {
Image(systemName: "arrow.counterclockwise") Image(systemName: "arrow.counterclockwise")
Text("Reset to Defaults") Text("Reset to Defaults")
} }
.font(.system(size: 14, weight: .medium)) .font(.system(size: Design.BaseFontSize.medium, weight: .medium))
.foregroundStyle(.red.opacity(0.8)) .foregroundStyle(.red.opacity(Design.Opacity.heavy))
.padding() .padding()
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background( .background(
RoundedRectangle(cornerRadius: 12) RoundedRectangle(cornerRadius: Design.CornerRadius.large)
.fill(Color.red.opacity(0.1)) .fill(Color.red.opacity(Design.Opacity.subtle))
) )
} }
.padding(.horizontal) .padding(.horizontal)
.padding(.top, 8) .padding(.top, Design.Spacing.small)
} }
.padding(.vertical) .padding(.vertical)
} }
} }
.navigationTitle("Settings") .navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline) .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) .toolbarBackground(.visible, for: .navigationBar)
.toolbarColorScheme(.dark, for: .navigationBar) .toolbarColorScheme(.dark, for: .navigationBar)
.toolbar { .toolbar {
@ -143,19 +143,19 @@ struct SettingsSection<Content: View>: View {
@ViewBuilder let content: Content @ViewBuilder let content: Content
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 12) { VStack(alignment: .leading, spacing: Design.Spacing.medium) {
// Header // Header
HStack(spacing: 8) { HStack(spacing: Design.Spacing.small) {
Image(systemName: icon) Image(systemName: icon)
.font(.system(size: 12, weight: .semibold)) .font(.system(size: Design.BaseFontSize.body, weight: .semibold))
.foregroundStyle(.yellow.opacity(0.8)) .foregroundStyle(.yellow.opacity(Design.Opacity.heavy))
Text(title) Text(title)
.font(.system(size: 12, weight: .bold, design: .rounded)) .font(.system(size: Design.BaseFontSize.body, weight: .bold, design: .rounded))
.tracking(1) .tracking(1)
.foregroundStyle(.white.opacity(0.6)) .foregroundStyle(.white.opacity(0.6))
} }
.padding(.horizontal, 4) .padding(.horizontal, Design.Spacing.xSmall)
// Content card // Content card
VStack(spacing: 0) { VStack(spacing: 0) {
@ -163,7 +163,7 @@ struct SettingsSection<Content: View>: View {
} }
.padding() .padding()
.background( .background(
RoundedRectangle(cornerRadius: 12) RoundedRectangle(cornerRadius: Design.CornerRadius.large)
.fill(Color.white.opacity(0.05)) .fill(Color.white.opacity(0.05))
) )
} }
@ -176,44 +176,44 @@ struct DeckCountPicker: View {
@Binding var selection: DeckCount @Binding var selection: DeckCount
var body: some View { var body: some View {
VStack(spacing: 12) { VStack(spacing: Design.Spacing.medium) {
ForEach(DeckCount.allCases) { count in ForEach(DeckCount.allCases) { count in
Button { Button {
selection = count selection = count
} label: { } label: {
HStack { HStack {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text(count.displayName) Text(count.displayName)
.font(.system(size: 16, weight: .semibold)) .font(.system(size: Design.BaseFontSize.large, weight: .semibold))
.foregroundStyle(.white) .foregroundStyle(.white)
Text(count.description) Text(count.description)
.font(.system(size: 12)) .font(.system(size: Design.BaseFontSize.body))
.foregroundStyle(.white.opacity(0.5)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
Spacer() Spacer()
if selection == count { if selection == count {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 22)) .font(.system(size: Design.Size.checkmark))
.foregroundStyle(.yellow) .foregroundStyle(.yellow)
} else { } else {
Circle() Circle()
.strokeBorder(Color.white.opacity(0.3), lineWidth: 2) .strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium)
.frame(width: 22, height: 22) .frame(width: Design.Size.checkmark, height: Design.Size.checkmark)
} }
} }
.padding() .padding()
.background( .background(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.fill(selection == count ? Color.yellow.opacity(0.1) : Color.clear) .fill(selection == count ? Color.yellow.opacity(Design.Opacity.subtle) : Color.clear)
) )
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.strokeBorder( .strokeBorder(
selection == count ? Color.yellow.opacity(0.5) : Color.white.opacity(0.1), selection == count ? Color.yellow.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle),
lineWidth: 1 lineWidth: Design.LineWidth.thin
) )
) )
} }
@ -234,19 +234,19 @@ struct BalancePicker: View {
GridItem(.flexible()), GridItem(.flexible()),
GridItem(.flexible()), GridItem(.flexible()),
GridItem(.flexible()) GridItem(.flexible())
], spacing: 10) { ], spacing: Design.Spacing.small) {
ForEach(options, id: \.self) { amount in ForEach(options, id: \.self) { amount in
Button { Button {
balance = amount balance = amount
} label: { } label: {
Text("$\(amount / 1000)K") Text("$\(amount / 1000)K")
.font(.system(size: 14, weight: .bold)) .font(.system(size: Design.BaseFontSize.medium, weight: .bold))
.foregroundStyle(balance == amount ? .black : .white) .foregroundStyle(balance == amount ? .black : .white)
.padding(.vertical, 12) .padding(.vertical, Design.Spacing.medium)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background( .background(
RoundedRectangle(cornerRadius: 8) RoundedRectangle(cornerRadius: Design.CornerRadius.small)
.fill(balance == amount ? Color.yellow : Color.white.opacity(0.1)) .fill(balance == amount ? Color.yellow : Color.white.opacity(Design.Opacity.subtle))
) )
} }
.buttonStyle(.plain) .buttonStyle(.plain)
@ -263,14 +263,14 @@ struct SettingsToggle: View {
var body: some View { var body: some View {
Toggle(isOn: $isOn) { Toggle(isOn: $isOn) {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text(title) Text(title)
.font(.system(size: 15, weight: .medium)) .font(.system(size: 15, weight: .medium))
.foregroundStyle(.white) .foregroundStyle(.white)
Text(subtitle) Text(subtitle)
.font(.system(size: 12)) .font(.system(size: Design.BaseFontSize.body))
.foregroundStyle(.white.opacity(0.5)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
} }
.tint(.yellow) .tint(.yellow)
@ -288,24 +288,24 @@ struct SpeedPicker: View {
] ]
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: Design.Spacing.small) {
Text("Dealing Speed") Text("Dealing Speed")
.font(.system(size: 15, weight: .medium)) .font(.system(size: 15, weight: .medium))
.foregroundStyle(.white) .foregroundStyle(.white)
HStack(spacing: 8) { HStack(spacing: Design.Spacing.small) {
ForEach(options, id: \.1) { option in ForEach(options, id: \.1) { option in
Button { Button {
speed = option.1 speed = option.1
} label: { } label: {
Text(option.0) Text(option.0)
.font(.system(size: 13, weight: .medium)) .font(.system(size: 13, weight: .medium))
.foregroundStyle(speed == option.1 ? .black : .white.opacity(0.7)) .foregroundStyle(speed == option.1 ? .black : .white.opacity(Design.Opacity.strong))
.padding(.vertical, 8) .padding(.vertical, Design.Spacing.small)
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.background( .background(
Capsule() 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) .buttonStyle(.plain)
@ -320,30 +320,30 @@ struct TableLimitsPicker: View {
@Binding var selection: TableLimits @Binding var selection: TableLimits
var body: some View { var body: some View {
VStack(spacing: 10) { VStack(spacing: Design.Spacing.small) {
ForEach(TableLimits.allCases) { limit in ForEach(TableLimits.allCases) { limit in
Button { Button {
selection = limit selection = limit
} label: { } label: {
HStack { HStack {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
Text(limit.displayName) Text(limit.displayName)
.font(.system(size: 16, weight: .semibold)) .font(.system(size: Design.BaseFontSize.large, weight: .semibold))
.foregroundStyle(.white) .foregroundStyle(.white)
Text(limit.detailedDescription) Text(limit.detailedDescription)
.font(.system(size: 12)) .font(.system(size: Design.BaseFontSize.body))
.foregroundStyle(.white.opacity(0.5)) .foregroundStyle(.white.opacity(Design.Opacity.medium))
} }
Spacer() Spacer()
// Limits badge // Limits badge
Text(limit.description) 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) .foregroundStyle(selection == limit ? .black : .yellow)
.padding(.horizontal, 10) .padding(.horizontal, Design.Spacing.small)
.padding(.vertical, 4) .padding(.vertical, Design.Spacing.xSmall)
.background( .background(
Capsule() Capsule()
.fill(selection == limit ? Color.yellow : Color.yellow.opacity(0.2)) .fill(selection == limit ? Color.yellow : Color.yellow.opacity(0.2))
@ -351,24 +351,24 @@ struct TableLimitsPicker: View {
if selection == limit { if selection == limit {
Image(systemName: "checkmark.circle.fill") Image(systemName: "checkmark.circle.fill")
.font(.system(size: 20)) .font(.system(size: Design.Size.checkmark - 2))
.foregroundStyle(.yellow) .foregroundStyle(.yellow)
} else { } else {
Circle() Circle()
.strokeBorder(Color.white.opacity(0.3), lineWidth: 2) .strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.medium)
.frame(width: 20, height: 20) .frame(width: Design.Size.checkmark - 2, height: Design.Size.checkmark - 2)
} }
} }
.padding() .padding()
.background( .background(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.fill(selection == limit ? Color.yellow.opacity(0.1) : Color.clear) .fill(selection == limit ? Color.yellow.opacity(Design.Opacity.subtle) : Color.clear)
) )
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
.strokeBorder( .strokeBorder(
selection == limit ? Color.yellow.opacity(0.5) : Color.white.opacity(0.1), selection == limit ? Color.yellow.opacity(Design.Opacity.medium) : Color.white.opacity(Design.Opacity.subtle),
lineWidth: 1 lineWidth: Design.LineWidth.thin
) )
) )
} }