Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
a898da7664
commit
acd0064776
@ -64,6 +64,70 @@ If SwiftData is configured to use CloudKit:
|
|||||||
- All relationships must be marked optional.
|
- All relationships must be marked optional.
|
||||||
|
|
||||||
|
|
||||||
|
## Localization instructions
|
||||||
|
|
||||||
|
- Use **String Catalogs** (`.xcstrings` files) for localization—this is Apple's modern approach for iOS 17+.
|
||||||
|
- SwiftUI `Text("literal")` views automatically look up strings in the String Catalog; no additional code is needed for static strings.
|
||||||
|
- For strings outside of `Text` views or with dynamic content, use `String(localized:)` or create a helper extension:
|
||||||
|
```swift
|
||||||
|
extension String {
|
||||||
|
static func localized(_ key: String) -> String {
|
||||||
|
String(localized: String.LocalizationValue(key))
|
||||||
|
}
|
||||||
|
static func localized(_ key: String, _ arguments: CVarArg...) -> String {
|
||||||
|
let format = String(localized: String.LocalizationValue(key))
|
||||||
|
return String(format: format, arguments: arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- For format strings with interpolation (e.g., "Balance: $%@"), define a key in the String Catalog and use `String.localized("key", value)`.
|
||||||
|
- Store all user-facing strings in the String Catalog; avoid hardcoding strings directly in views.
|
||||||
|
- Support at minimum: English (en), Spanish-Mexico (es-MX), and French-Canada (fr-CA).
|
||||||
|
- Never use `NSLocalizedString`; prefer the modern `String(localized:)` API.
|
||||||
|
|
||||||
|
|
||||||
|
## Design constants instructions
|
||||||
|
|
||||||
|
- Avoid magic numbers for layout values (padding, spacing, corner radii, font sizes, etc.).
|
||||||
|
- Create a centralized design constants file (e.g., `DesignConstants.swift`) using enums for namespacing:
|
||||||
|
```swift
|
||||||
|
enum Design {
|
||||||
|
enum Spacing {
|
||||||
|
static let small: CGFloat = 8
|
||||||
|
static let medium: CGFloat = 12
|
||||||
|
static let large: CGFloat = 16
|
||||||
|
}
|
||||||
|
enum CornerRadius {
|
||||||
|
static let small: CGFloat = 8
|
||||||
|
static let medium: CGFloat = 12
|
||||||
|
}
|
||||||
|
enum FontSize {
|
||||||
|
static let body: CGFloat = 14
|
||||||
|
static let title: CGFloat = 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- For colors used across the app, extend `Color` with semantic color definitions:
|
||||||
|
```swift
|
||||||
|
extension Color {
|
||||||
|
enum Primary {
|
||||||
|
static let background = Color(red: 0.1, green: 0.2, blue: 0.3)
|
||||||
|
static let accent = Color(red: 0.8, green: 0.6, blue: 0.2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Within each view, extract view-specific magic numbers to private constants at the top of the struct:
|
||||||
|
```swift
|
||||||
|
struct MyView: View {
|
||||||
|
private let cardWidth: CGFloat = 45
|
||||||
|
private let headerFontSize: CGFloat = 18
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Reference design constants in views: `Design.Spacing.medium`, `Design.CornerRadius.large`, `Color.Primary.accent`.
|
||||||
|
- Keep design constants organized by category: Spacing, CornerRadius, FontSize, IconSize, Size, Animation, Opacity, LineWidth, Shadow.
|
||||||
|
|
||||||
|
|
||||||
## Project structure
|
## Project structure
|
||||||
|
|
||||||
- Use a consistent project structure, with folder layout determined by app features.
|
- Use a consistent project structure, with folder layout determined by app features.
|
||||||
|
|||||||
@ -14,12 +14,12 @@ enum GameResult: Equatable {
|
|||||||
case bankerWins
|
case bankerWins
|
||||||
case tie
|
case tie
|
||||||
|
|
||||||
/// Display text for the result.
|
/// Display text for the result (localized).
|
||||||
var displayText: String {
|
var displayText: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .playerWins: return "Player Wins!"
|
case .playerWins: return String.localized("PLAYER WINS")
|
||||||
case .bankerWins: return "Banker Wins!"
|
case .bankerWins: return String.localized("BANKER WINS")
|
||||||
case .tie: return "Tie!"
|
case .tie: return String.localized("TIE GAME")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
1287
Baccarat/Resources/Localizable.xcstrings
Normal file
1287
Baccarat/Resources/Localizable.xcstrings
Normal file
File diff suppressed because it is too large
Load Diff
203
Baccarat/Theme/DesignConstants.swift
Normal file
203
Baccarat/Theme/DesignConstants.swift
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
//
|
||||||
|
// DesignConstants.swift
|
||||||
|
// Baccarat
|
||||||
|
//
|
||||||
|
// Design system constants for consistent styling across the app.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
/// Design constants for the Baccarat app.
|
||||||
|
enum Design {
|
||||||
|
|
||||||
|
// 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 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: - Font Sizes
|
||||||
|
|
||||||
|
enum FontSize {
|
||||||
|
static let xxSmall: CGFloat = 7
|
||||||
|
static let xSmall: CGFloat = 9
|
||||||
|
static let small: CGFloat = 10
|
||||||
|
static let body: CGFloat = 12
|
||||||
|
static let medium: CGFloat = 14
|
||||||
|
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
|
||||||
|
|
||||||
|
enum Size {
|
||||||
|
static let chipSmall: CGFloat = 36
|
||||||
|
static let chipMedium: CGFloat = 50
|
||||||
|
static let cardWidthSmall: CGFloat = 45
|
||||||
|
static let cardWidthMedium: CGFloat = 55
|
||||||
|
static let cardWidthLarge: CGFloat = 65
|
||||||
|
static let valueBadge: CGFloat = 26
|
||||||
|
static let checkmark: CGFloat = 22
|
||||||
|
static let tableAspectRatio: CGFloat = 1.6
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Animation
|
||||||
|
|
||||||
|
enum Animation {
|
||||||
|
static let springDuration: Double = 0.4
|
||||||
|
static let springBounce: Double = 0.3
|
||||||
|
static let fadeInDuration: Double = 0.3
|
||||||
|
static let cardFlipDuration: Double = 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Opacity
|
||||||
|
|
||||||
|
enum Opacity {
|
||||||
|
static let disabled: Double = 0.5
|
||||||
|
static let subtle: Double = 0.1
|
||||||
|
static let light: Double = 0.3
|
||||||
|
static let medium: Double = 0.5
|
||||||
|
static let strong: Double = 0.7
|
||||||
|
static let heavy: Double = 0.8
|
||||||
|
static let nearOpaque: Double = 0.85
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Line Widths
|
||||||
|
|
||||||
|
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 = 3
|
||||||
|
static let radiusMedium: CGFloat = 6
|
||||||
|
static let radiusLarge: CGFloat = 10
|
||||||
|
static let radiusXLarge: CGFloat = 12
|
||||||
|
static let radiusXXLarge: CGFloat = 30
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - App Colors
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
|
||||||
|
// MARK: - Table Colors
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 gold = Color(red: 0.7, green: 0.55, blue: 0.25)
|
||||||
|
static let silver = Color(red: 0.6, green: 0.6, blue: 0.65)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Betting Zone Colors
|
||||||
|
|
||||||
|
enum BettingZone {
|
||||||
|
// Player (Blue)
|
||||||
|
static let playerLight = Color(red: 0.1, green: 0.25, blue: 0.55)
|
||||||
|
static let playerDark = Color(red: 0.05, green: 0.15, blue: 0.4)
|
||||||
|
static let playerMaxLight = Color(red: 0.08, green: 0.18, blue: 0.4)
|
||||||
|
static let playerMaxDark = Color(red: 0.04, green: 0.1, blue: 0.28)
|
||||||
|
|
||||||
|
// Banker (Red)
|
||||||
|
static let bankerLight = Color(red: 0.55, green: 0.12, blue: 0.12)
|
||||||
|
static let bankerDark = Color(red: 0.4, green: 0.08, blue: 0.08)
|
||||||
|
static let bankerMaxLight = Color(red: 0.4, green: 0.1, blue: 0.1)
|
||||||
|
static let bankerMaxDark = Color(red: 0.28, green: 0.06, blue: 0.06)
|
||||||
|
|
||||||
|
// Tie (Green)
|
||||||
|
static let tie = Color(red: 0.1, green: 0.45, blue: 0.25)
|
||||||
|
static let tieMax = Color(red: 0.08, green: 0.32, blue: 0.18)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Button Colors
|
||||||
|
|
||||||
|
enum Button {
|
||||||
|
static let goldLight = Color(red: 1.0, green: 0.85, blue: 0.3)
|
||||||
|
static let goldDark = Color(red: 0.9, green: 0.7, blue: 0.2)
|
||||||
|
static let destructive = Color(red: 0.6, green: 0.2, blue: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Chip Colors
|
||||||
|
|
||||||
|
enum Chip {
|
||||||
|
static let gold = Color(red: 0.8, green: 0.65, blue: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Modal Colors
|
||||||
|
|
||||||
|
enum Modal {
|
||||||
|
static let backgroundLight = Color(red: 0.12, green: 0.12, blue: 0.14)
|
||||||
|
static let backgroundDark = Color(red: 0.08, green: 0.08, blue: 0.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Settings Colors
|
||||||
|
|
||||||
|
enum Settings {
|
||||||
|
static let background = Color(red: 0.08, green: 0.12, blue: 0.08)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Localized Strings Helper
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
|
||||||
|
/// Returns a localized string for the given key.
|
||||||
|
static func localized(_ key: String) -> String {
|
||||||
|
String(localized: String.LocalizationValue(key))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a localized string with format arguments.
|
||||||
|
static func localized(_ key: String, _ arguments: CVarArg...) -> String {
|
||||||
|
let format = String(localized: String.LocalizationValue(key))
|
||||||
|
return String(format: format, arguments: arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ -148,6 +148,22 @@ struct GameOverView: View {
|
|||||||
|
|
||||||
@State private var showContent = false
|
@State private var showContent = false
|
||||||
|
|
||||||
|
// MARK: - Layout Constants
|
||||||
|
|
||||||
|
private let iconSize = Design.FontSize.display
|
||||||
|
private let titleFontSize = Design.FontSize.largeTitle
|
||||||
|
private let messageFontSize = Design.FontSize.xLarge
|
||||||
|
private let statsFontSize: CGFloat = 17
|
||||||
|
private let buttonFontSize = Design.FontSize.xLarge
|
||||||
|
private let modalCornerRadius = Design.CornerRadius.xxxLarge
|
||||||
|
private let statsCornerRadius = Design.CornerRadius.large
|
||||||
|
private let cardPadding = Design.Spacing.xxxLarge
|
||||||
|
private let contentSpacing: CGFloat = 28
|
||||||
|
private let buttonHorizontalPadding: CGFloat = 48
|
||||||
|
private let buttonVerticalPadding: CGFloat = 18
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Solid dark backdrop - fully opaque
|
// Solid dark backdrop - fully opaque
|
||||||
@ -155,110 +171,104 @@ struct GameOverView: View {
|
|||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
|
||||||
// Modal card
|
// Modal card
|
||||||
VStack(spacing: 28) {
|
VStack(spacing: contentSpacing) {
|
||||||
// Broke icon
|
// Broke icon
|
||||||
Image(systemName: "creditcard.trianglebadge.exclamationmark")
|
Image(systemName: "creditcard.trianglebadge.exclamationmark")
|
||||||
.font(.system(size: 70))
|
.font(.system(size: iconSize))
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
.symbolEffect(.pulse, options: .repeating)
|
.symbolEffect(.pulse, options: .repeating)
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
Text("GAME OVER")
|
Text("GAME OVER")
|
||||||
.font(.system(size: 36, weight: .black, design: .rounded))
|
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
// Message
|
// Message
|
||||||
Text("You've run out of chips!")
|
Text("You've run out of chips!")
|
||||||
.font(.system(size: 18, weight: .medium))
|
.font(.system(size: messageFontSize, weight: .medium))
|
||||||
.foregroundStyle(.white.opacity(0.7))
|
.foregroundStyle(.white.opacity(Design.Opacity.strong))
|
||||||
|
|
||||||
// Stats card
|
// Stats card
|
||||||
VStack(spacing: 12) {
|
VStack(spacing: Design.Spacing.medium) {
|
||||||
HStack {
|
HStack {
|
||||||
Text("Rounds Played")
|
Text("Rounds Played")
|
||||||
.foregroundStyle(.white.opacity(0.6))
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
Spacer()
|
Spacer()
|
||||||
Text("\(roundsPlayed)")
|
Text("\(roundsPlayed)")
|
||||||
.bold()
|
.bold()
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.font(.system(size: 17))
|
.font(.system(size: statsFontSize))
|
||||||
.padding()
|
.padding()
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: statsCornerRadius)
|
||||||
.fill(Color.white.opacity(0.08))
|
.fill(Color.white.opacity(0.08))
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 12)
|
RoundedRectangle(cornerRadius: statsCornerRadius)
|
||||||
.strokeBorder(Color.white.opacity(0.1), lineWidth: 1)
|
.strokeBorder(Color.white.opacity(Design.Opacity.subtle), lineWidth: Design.LineWidth.thin)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, Design.Spacing.xLarge)
|
||||||
|
|
||||||
// Play Again button
|
// Play Again button
|
||||||
Button {
|
Button {
|
||||||
onPlayAgain()
|
onPlayAgain()
|
||||||
} label: {
|
} label: {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: Design.Spacing.small) {
|
||||||
Image(systemName: "arrow.counterclockwise")
|
Image(systemName: "arrow.counterclockwise")
|
||||||
Text("Play Again")
|
Text("Play Again")
|
||||||
}
|
}
|
||||||
.font(.system(size: 18, weight: .bold))
|
.font(.system(size: buttonFontSize, weight: .bold))
|
||||||
.foregroundStyle(.black)
|
.foregroundStyle(.black)
|
||||||
.padding(.horizontal, 48)
|
.padding(.horizontal, buttonHorizontalPadding)
|
||||||
.padding(.vertical, 18)
|
.padding(.vertical, buttonVerticalPadding)
|
||||||
.background(
|
.background(
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [
|
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||||
Color(red: 1.0, green: 0.85, blue: 0.3),
|
|
||||||
Color(red: 0.9, green: 0.7, blue: 0.2)
|
|
||||||
],
|
|
||||||
startPoint: .top,
|
startPoint: .top,
|
||||||
endPoint: .bottom
|
endPoint: .bottom
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.shadow(color: .yellow.opacity(0.4), radius: 12)
|
.shadow(color: .yellow.opacity(Design.Opacity.light), radius: Design.Shadow.radiusXLarge)
|
||||||
}
|
}
|
||||||
.padding(.top, 12)
|
.padding(.top, Design.Spacing.medium)
|
||||||
}
|
}
|
||||||
.padding(32)
|
.padding(cardPadding)
|
||||||
.background(
|
.background(
|
||||||
RoundedRectangle(cornerRadius: 28)
|
RoundedRectangle(cornerRadius: modalCornerRadius)
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [
|
colors: [Color.Modal.backgroundLight, Color.Modal.backgroundDark],
|
||||||
Color(red: 0.12, green: 0.12, blue: 0.14),
|
|
||||||
Color(red: 0.08, green: 0.08, blue: 0.1)
|
|
||||||
],
|
|
||||||
startPoint: .top,
|
startPoint: .top,
|
||||||
endPoint: .bottom
|
endPoint: .bottom
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 28)
|
RoundedRectangle(cornerRadius: modalCornerRadius)
|
||||||
.strokeBorder(
|
.strokeBorder(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
Color.red.opacity(0.5),
|
Color.red.opacity(Design.Opacity.medium),
|
||||||
Color.red.opacity(0.2)
|
Color.red.opacity(0.2)
|
||||||
],
|
],
|
||||||
startPoint: .topLeading,
|
startPoint: .topLeading,
|
||||||
endPoint: .bottomTrailing
|
endPoint: .bottomTrailing
|
||||||
),
|
),
|
||||||
lineWidth: 2
|
lineWidth: Design.LineWidth.medium
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.shadow(color: .red.opacity(0.2), radius: 30)
|
.shadow(color: .red.opacity(0.2), radius: Design.Shadow.radiusXXLarge)
|
||||||
.padding(.horizontal, 24)
|
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||||
.scaleEffect(showContent ? 1.0 : 0.8)
|
.scaleEffect(showContent ? 1.0 : 0.8)
|
||||||
.opacity(showContent ? 1.0 : 0)
|
.opacity(showContent ? 1.0 : 0)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
withAnimation(.spring(duration: 0.4, bounce: 0.3)) {
|
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)) {
|
||||||
showContent = true
|
showContent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,20 @@ struct MiniBaccaratTableView: View {
|
|||||||
@Bindable var gameState: GameState
|
@Bindable var gameState: GameState
|
||||||
let selectedChip: ChipDenomination
|
let selectedChip: ChipDenomination
|
||||||
|
|
||||||
|
// MARK: - Layout Constants
|
||||||
|
|
||||||
|
private let tableLimitsFontSize = Design.FontSize.small
|
||||||
|
private let tieZoneHeight: CGFloat = 55
|
||||||
|
private let mainZoneHeight: CGFloat = 60
|
||||||
|
private let tieHorizontalPadding: CGFloat = 50
|
||||||
|
private let bankerHorizontalPadding: CGFloat = 30
|
||||||
|
private let playerHorizontalPadding: CGFloat = 20
|
||||||
|
private let zoneTopPadding = Design.Spacing.medium
|
||||||
|
private let zoneBottomPadding = Design.Spacing.medium
|
||||||
|
private let minSpacerLength = Design.Spacing.small
|
||||||
|
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
private func betAmount(for type: BetType) -> Int {
|
private func betAmount(for type: BetType) -> Int {
|
||||||
gameState.betAmount(for: type)
|
gameState.betAmount(for: type)
|
||||||
}
|
}
|
||||||
@ -34,12 +48,22 @@ struct MiniBaccaratTableView: View {
|
|||||||
gameState.mainBet?.type == .banker
|
gameState.mainBet?.type == .banker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var tableLimitsText: String {
|
||||||
|
String.localized(
|
||||||
|
"tableLimitsFormat",
|
||||||
|
gameState.minBet.formatted(),
|
||||||
|
gameState.maxBet.formatted()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: Design.Spacing.xSmall) {
|
||||||
// Table limits label
|
// Table limits label
|
||||||
Text("TABLE LIMITS: $\(gameState.minBet) - $\(gameState.maxBet.formatted())")
|
Text(tableLimitsText)
|
||||||
.font(.system(size: 10, weight: .bold, design: .rounded))
|
.font(.system(size: tableLimitsFontSize, weight: .bold, design: .rounded))
|
||||||
.foregroundStyle(.white.opacity(0.5))
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
.tracking(1)
|
.tracking(1)
|
||||||
|
|
||||||
ZStack {
|
ZStack {
|
||||||
@ -47,10 +71,7 @@ struct MiniBaccaratTableView: View {
|
|||||||
TableFeltShape()
|
TableFeltShape()
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [
|
colors: [Color.Table.feltLight, Color.Table.feltDark],
|
||||||
Color(red: 0.0, green: 0.35, blue: 0.18),
|
|
||||||
Color(red: 0.0, green: 0.28, blue: 0.12)
|
|
||||||
],
|
|
||||||
startPoint: .top,
|
startPoint: .top,
|
||||||
endPoint: .bottom
|
endPoint: .bottom
|
||||||
)
|
)
|
||||||
@ -60,14 +81,11 @@ struct MiniBaccaratTableView: View {
|
|||||||
TableFeltShape()
|
TableFeltShape()
|
||||||
.strokeBorder(
|
.strokeBorder(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: [
|
colors: [Color.Border.goldLight, Color.Border.goldDark],
|
||||||
Color(red: 0.85, green: 0.7, blue: 0.35),
|
|
||||||
Color(red: 0.65, green: 0.5, blue: 0.2)
|
|
||||||
],
|
|
||||||
startPoint: .top,
|
startPoint: .top,
|
||||||
endPoint: .bottom
|
endPoint: .bottom
|
||||||
),
|
),
|
||||||
lineWidth: 4
|
lineWidth: Design.LineWidth.heavy
|
||||||
)
|
)
|
||||||
|
|
||||||
// Betting zones layout
|
// Betting zones layout
|
||||||
@ -80,11 +98,11 @@ struct MiniBaccaratTableView: View {
|
|||||||
) {
|
) {
|
||||||
gameState.placeBet(type: .tie, amount: selectedChip.rawValue)
|
gameState.placeBet(type: .tie, amount: selectedChip.rawValue)
|
||||||
}
|
}
|
||||||
.frame(height: 55)
|
.frame(height: tieZoneHeight)
|
||||||
.padding(.horizontal, 50)
|
.padding(.horizontal, tieHorizontalPadding)
|
||||||
.padding(.top, 12)
|
.padding(.top, zoneTopPadding)
|
||||||
|
|
||||||
Spacer(minLength: 8)
|
Spacer(minLength: minSpacerLength)
|
||||||
|
|
||||||
// BANKER zone in middle
|
// BANKER zone in middle
|
||||||
BankerBettingZone(
|
BankerBettingZone(
|
||||||
@ -95,10 +113,10 @@ struct MiniBaccaratTableView: View {
|
|||||||
) {
|
) {
|
||||||
gameState.placeBet(type: .banker, amount: selectedChip.rawValue)
|
gameState.placeBet(type: .banker, amount: selectedChip.rawValue)
|
||||||
}
|
}
|
||||||
.frame(height: 60)
|
.frame(height: mainZoneHeight)
|
||||||
.padding(.horizontal, 30)
|
.padding(.horizontal, bankerHorizontalPadding)
|
||||||
|
|
||||||
Spacer(minLength: 8)
|
Spacer(minLength: minSpacerLength)
|
||||||
|
|
||||||
// PLAYER zone at bottom
|
// PLAYER zone at bottom
|
||||||
PlayerBettingZone(
|
PlayerBettingZone(
|
||||||
@ -109,12 +127,12 @@ struct MiniBaccaratTableView: View {
|
|||||||
) {
|
) {
|
||||||
gameState.placeBet(type: .player, amount: selectedChip.rawValue)
|
gameState.placeBet(type: .player, amount: selectedChip.rawValue)
|
||||||
}
|
}
|
||||||
.frame(height: 60)
|
.frame(height: mainZoneHeight)
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, playerHorizontalPadding)
|
||||||
.padding(.bottom, 12)
|
.padding(.bottom, zoneBottomPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.aspectRatio(1.6, contentMode: .fit)
|
.aspectRatio(Design.Size.tableAspectRatio, contentMode: .fit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,12 +141,14 @@ struct MiniBaccaratTableView: View {
|
|||||||
struct TableFeltShape: InsettableShape {
|
struct TableFeltShape: InsettableShape {
|
||||||
var insetAmount: CGFloat = 0
|
var insetAmount: CGFloat = 0
|
||||||
|
|
||||||
|
private let shapeCornerRadius = Design.CornerRadius.xxLarge
|
||||||
|
|
||||||
func path(in rect: CGRect) -> Path {
|
func path(in rect: CGRect) -> Path {
|
||||||
var path = Path()
|
var path = Path()
|
||||||
|
|
||||||
let insetRect = rect.insetBy(dx: insetAmount, dy: insetAmount)
|
let insetRect = rect.insetBy(dx: insetAmount, dy: insetAmount)
|
||||||
let height = insetRect.height
|
let height = insetRect.height
|
||||||
let cornerRadius: CGFloat = 20
|
let cornerRadius = shapeCornerRadius
|
||||||
|
|
||||||
// Start from bottom left
|
// Start from bottom left
|
||||||
path.move(to: CGPoint(x: insetRect.minX + cornerRadius, y: insetRect.maxY))
|
path.move(to: CGPoint(x: insetRect.minX + cornerRadius, y: insetRect.maxY))
|
||||||
@ -178,22 +198,25 @@ struct TieBettingZone: View {
|
|||||||
var isAtMax: Bool = false
|
var isAtMax: Bool = false
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
|
// MARK: - Layout Constants
|
||||||
|
|
||||||
|
private let cornerRadius = Design.CornerRadius.small
|
||||||
|
private let titleFontSize = Design.FontSize.medium
|
||||||
|
private let subtitleFontSize = Design.FontSize.xSmall
|
||||||
|
private let chipTrailingPadding = Design.Spacing.small
|
||||||
|
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
private var backgroundColor: Color {
|
private var backgroundColor: Color {
|
||||||
if isAtMax {
|
isAtMax ? Color.BettingZone.tieMax : Color.BettingZone.tie
|
||||||
// Darker/muted green when at max
|
|
||||||
return Color(red: 0.08, green: 0.32, blue: 0.18)
|
|
||||||
}
|
|
||||||
return Color(red: 0.1, green: 0.45, blue: 0.25)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var borderColor: Color {
|
private var borderColor: Color {
|
||||||
if isAtMax {
|
isAtMax ? Color.Border.silver : Color.Border.gold
|
||||||
// Silver border when at max
|
|
||||||
return Color(red: 0.6, green: 0.6, blue: 0.65)
|
|
||||||
}
|
|
||||||
return Color(red: 0.7, green: 0.55, blue: 0.25)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button {
|
Button {
|
||||||
if isEnabled {
|
if isEnabled {
|
||||||
@ -202,22 +225,22 @@ struct TieBettingZone: View {
|
|||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Background
|
// Background
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.fill(backgroundColor)
|
.fill(backgroundColor)
|
||||||
|
|
||||||
// Border
|
// Border
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.strokeBorder(borderColor, lineWidth: 2)
|
.strokeBorder(borderColor, lineWidth: Design.LineWidth.medium)
|
||||||
|
|
||||||
// Centered text content
|
// Centered text content
|
||||||
VStack(spacing: 2) {
|
VStack(spacing: Design.Spacing.xxSmall) {
|
||||||
Text("TIE")
|
Text("TIE")
|
||||||
.font(.system(size: 14, weight: .black, design: .rounded))
|
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||||
.tracking(2)
|
.tracking(2)
|
||||||
|
|
||||||
Text("PAYS 8 TO 1")
|
Text("PAYS 8 TO 1")
|
||||||
.font(.system(size: 9, weight: .medium))
|
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||||
.opacity(0.8)
|
.opacity(Design.Opacity.heavy)
|
||||||
}
|
}
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
@ -225,7 +248,7 @@ struct TieBettingZone: View {
|
|||||||
.overlay(alignment: .trailing) {
|
.overlay(alignment: .trailing) {
|
||||||
if betAmount > 0 {
|
if betAmount > 0 {
|
||||||
ChipOnTable(amount: betAmount, showMax: isAtMax)
|
ChipOnTable(amount: betAmount, showMax: isAtMax)
|
||||||
.padding(.trailing, 8)
|
.padding(.trailing, chipTrailingPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -241,27 +264,28 @@ struct BankerBettingZone: View {
|
|||||||
var isAtMax: Bool = false
|
var isAtMax: Bool = false
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
|
// MARK: - Layout Constants
|
||||||
|
|
||||||
|
private let cornerRadius = Design.CornerRadius.medium
|
||||||
|
private let titleFontSize = Design.FontSize.large
|
||||||
|
private let subtitleFontSize = Design.FontSize.xSmall
|
||||||
|
private let chipTrailingPadding = Design.Spacing.medium
|
||||||
|
private let selectionShadowRadius = Design.Shadow.radiusSmall
|
||||||
|
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
private var backgroundColors: [Color] {
|
private var backgroundColors: [Color] {
|
||||||
if isAtMax {
|
isAtMax
|
||||||
// Darker/muted red when at max
|
? [Color.BettingZone.bankerMaxLight, Color.BettingZone.bankerMaxDark]
|
||||||
return [
|
: [Color.BettingZone.bankerLight, Color.BettingZone.bankerDark]
|
||||||
Color(red: 0.4, green: 0.1, blue: 0.1),
|
|
||||||
Color(red: 0.28, green: 0.06, blue: 0.06)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
Color(red: 0.55, green: 0.12, blue: 0.12),
|
|
||||||
Color(red: 0.4, green: 0.08, blue: 0.08)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var borderColor: Color {
|
private var borderColor: Color {
|
||||||
if isAtMax {
|
isAtMax ? Color.Border.silver : Color.Border.gold
|
||||||
return Color(red: 0.6, green: 0.6, blue: 0.65)
|
|
||||||
}
|
|
||||||
return Color(red: 0.7, green: 0.55, blue: 0.25)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button {
|
Button {
|
||||||
if isEnabled {
|
if isEnabled {
|
||||||
@ -270,7 +294,7 @@ struct BankerBettingZone: View {
|
|||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Background
|
// Background
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: backgroundColors,
|
colors: backgroundColors,
|
||||||
@ -281,24 +305,24 @@ struct BankerBettingZone: View {
|
|||||||
|
|
||||||
// Selection glow
|
// Selection glow
|
||||||
if isSelected {
|
if isSelected {
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.strokeBorder(Color.yellow, lineWidth: 3)
|
.strokeBorder(Color.yellow, lineWidth: Design.LineWidth.thick)
|
||||||
.shadow(color: .yellow.opacity(0.5), radius: 8)
|
.shadow(color: .yellow.opacity(Design.Opacity.medium), radius: selectionShadowRadius)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Border
|
// Border
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.strokeBorder(borderColor, lineWidth: 2)
|
.strokeBorder(borderColor, lineWidth: Design.LineWidth.medium)
|
||||||
|
|
||||||
// Centered text content
|
// Centered text content
|
||||||
VStack(spacing: 2) {
|
VStack(spacing: Design.Spacing.xxSmall) {
|
||||||
Text("BANKER")
|
Text("BANKER")
|
||||||
.font(.system(size: 16, weight: .black, design: .rounded))
|
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||||
.tracking(3)
|
.tracking(3)
|
||||||
|
|
||||||
Text("PAYS 0.95 TO 1")
|
Text("PAYS 0.95 TO 1")
|
||||||
.font(.system(size: 9, weight: .medium))
|
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||||
.opacity(0.8)
|
.opacity(Design.Opacity.heavy)
|
||||||
}
|
}
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
@ -306,7 +330,7 @@ struct BankerBettingZone: View {
|
|||||||
.overlay(alignment: .trailing) {
|
.overlay(alignment: .trailing) {
|
||||||
if betAmount > 0 {
|
if betAmount > 0 {
|
||||||
ChipOnTable(amount: betAmount, showMax: isAtMax)
|
ChipOnTable(amount: betAmount, showMax: isAtMax)
|
||||||
.padding(.trailing, 12)
|
.padding(.trailing, chipTrailingPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -322,27 +346,28 @@ struct PlayerBettingZone: View {
|
|||||||
var isAtMax: Bool = false
|
var isAtMax: Bool = false
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
|
// MARK: - Layout Constants
|
||||||
|
|
||||||
|
private let cornerRadius = Design.CornerRadius.medium
|
||||||
|
private let titleFontSize = Design.FontSize.large
|
||||||
|
private let subtitleFontSize = Design.FontSize.xSmall
|
||||||
|
private let chipTrailingPadding = Design.Spacing.medium
|
||||||
|
private let selectionShadowRadius = Design.Shadow.radiusSmall
|
||||||
|
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
private var backgroundColors: [Color] {
|
private var backgroundColors: [Color] {
|
||||||
if isAtMax {
|
isAtMax
|
||||||
// Darker/muted blue when at max
|
? [Color.BettingZone.playerMaxLight, Color.BettingZone.playerMaxDark]
|
||||||
return [
|
: [Color.BettingZone.playerLight, Color.BettingZone.playerDark]
|
||||||
Color(red: 0.08, green: 0.18, blue: 0.4),
|
|
||||||
Color(red: 0.04, green: 0.1, blue: 0.28)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
Color(red: 0.1, green: 0.25, blue: 0.55),
|
|
||||||
Color(red: 0.05, green: 0.15, blue: 0.4)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var borderColor: Color {
|
private var borderColor: Color {
|
||||||
if isAtMax {
|
isAtMax ? Color.Border.silver : Color.Border.gold
|
||||||
return Color(red: 0.6, green: 0.6, blue: 0.65)
|
|
||||||
}
|
|
||||||
return Color(red: 0.7, green: 0.55, blue: 0.25)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button {
|
Button {
|
||||||
if isEnabled {
|
if isEnabled {
|
||||||
@ -351,7 +376,7 @@ struct PlayerBettingZone: View {
|
|||||||
} label: {
|
} label: {
|
||||||
ZStack {
|
ZStack {
|
||||||
// Background
|
// Background
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.fill(
|
.fill(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
colors: backgroundColors,
|
colors: backgroundColors,
|
||||||
@ -362,24 +387,24 @@ struct PlayerBettingZone: View {
|
|||||||
|
|
||||||
// Selection glow
|
// Selection glow
|
||||||
if isSelected {
|
if isSelected {
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.strokeBorder(Color.yellow, lineWidth: 3)
|
.strokeBorder(Color.yellow, lineWidth: Design.LineWidth.thick)
|
||||||
.shadow(color: .yellow.opacity(0.5), radius: 8)
|
.shadow(color: .yellow.opacity(Design.Opacity.medium), radius: selectionShadowRadius)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Border
|
// Border
|
||||||
RoundedRectangle(cornerRadius: 10)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.strokeBorder(borderColor, lineWidth: 2)
|
.strokeBorder(borderColor, lineWidth: Design.LineWidth.medium)
|
||||||
|
|
||||||
// Centered text content
|
// Centered text content
|
||||||
VStack(spacing: 2) {
|
VStack(spacing: Design.Spacing.xxSmall) {
|
||||||
Text("PLAYER")
|
Text("PLAYER")
|
||||||
.font(.system(size: 16, weight: .black, design: .rounded))
|
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||||
.tracking(3)
|
.tracking(3)
|
||||||
|
|
||||||
Text("PAYS 1 TO 1")
|
Text("PAYS 1 TO 1")
|
||||||
.font(.system(size: 9, weight: .medium))
|
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||||
.opacity(0.8)
|
.opacity(Design.Opacity.heavy)
|
||||||
}
|
}
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
@ -387,7 +412,7 @@ struct PlayerBettingZone: View {
|
|||||||
.overlay(alignment: .trailing) {
|
.overlay(alignment: .trailing) {
|
||||||
if betAmount > 0 {
|
if betAmount > 0 {
|
||||||
ChipOnTable(amount: betAmount, showMax: isAtMax)
|
ChipOnTable(amount: betAmount, showMax: isAtMax)
|
||||||
.padding(.trailing, 12)
|
.padding(.trailing, chipTrailingPadding)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -400,24 +425,37 @@ struct ChipOnTable: View {
|
|||||||
let amount: Int
|
let amount: Int
|
||||||
var showMax: Bool = false
|
var showMax: Bool = false
|
||||||
|
|
||||||
|
// MARK: - Layout Constants
|
||||||
|
|
||||||
|
private let chipSize = Design.Size.chipSmall
|
||||||
|
private let innerRingSize: CGFloat = 26
|
||||||
|
private let gradientEndRadius: CGFloat = 20
|
||||||
|
private let maxBadgeFontSize = Design.FontSize.xxSmall
|
||||||
|
private let maxBadgeOffsetX: CGFloat = 6
|
||||||
|
private let maxBadgeOffsetY: CGFloat = -4
|
||||||
|
|
||||||
|
// MARK: - Computed Properties
|
||||||
|
|
||||||
private var chipColor: Color {
|
private var chipColor: Color {
|
||||||
switch amount {
|
switch amount {
|
||||||
case 0..<50: return .blue
|
case 0..<50: return .blue
|
||||||
case 50..<100: return .orange
|
case 50..<100: return .orange
|
||||||
case 100..<500: return .black
|
case 100..<500: return .black
|
||||||
case 500..<1000: return .purple
|
case 500..<1000: return .purple
|
||||||
default: return Color(red: 0.8, green: 0.65, blue: 0.2)
|
default: return Color.Chip.gold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var displayText: String {
|
private var displayText: String {
|
||||||
if amount >= 1000 {
|
amount >= 1000 ? "\(amount / 1000)K" : "\(amount)"
|
||||||
return "\(amount / 1000)K"
|
|
||||||
} else {
|
|
||||||
return "\(amount)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var textFontSize: CGFloat {
|
||||||
|
amount >= 1000 ? Design.FontSize.small : 11
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Circle()
|
Circle()
|
||||||
@ -426,36 +464,36 @@ struct ChipOnTable: View {
|
|||||||
colors: [chipColor.opacity(0.9), chipColor],
|
colors: [chipColor.opacity(0.9), chipColor],
|
||||||
center: .topLeading,
|
center: .topLeading,
|
||||||
startRadius: 0,
|
startRadius: 0,
|
||||||
endRadius: 20
|
endRadius: gradientEndRadius
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.frame(width: 36, height: 36)
|
.frame(width: chipSize, height: chipSize)
|
||||||
|
|
||||||
Circle()
|
Circle()
|
||||||
.strokeBorder(Color.white.opacity(0.8), lineWidth: 2)
|
.strokeBorder(Color.white.opacity(Design.Opacity.heavy), lineWidth: Design.LineWidth.medium)
|
||||||
.frame(width: 36, height: 36)
|
.frame(width: chipSize, height: chipSize)
|
||||||
|
|
||||||
Circle()
|
Circle()
|
||||||
.strokeBorder(Color.white.opacity(0.4), lineWidth: 1)
|
.strokeBorder(Color.white.opacity(Design.Opacity.light), lineWidth: Design.LineWidth.thin)
|
||||||
.frame(width: 26, height: 26)
|
.frame(width: innerRingSize, height: innerRingSize)
|
||||||
|
|
||||||
Text(displayText)
|
Text(displayText)
|
||||||
.font(.system(size: amount >= 1000 ? 10 : 11, weight: .bold))
|
.font(.system(size: textFontSize, weight: .bold))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
}
|
}
|
||||||
.shadow(color: .black.opacity(0.4), radius: 3, x: 1, y: 2)
|
.shadow(color: .black.opacity(Design.Opacity.light), radius: Design.Shadow.radiusSmall, x: 1, y: 2)
|
||||||
.overlay(alignment: .topTrailing) {
|
.overlay(alignment: .topTrailing) {
|
||||||
if showMax {
|
if showMax {
|
||||||
Text("MAX")
|
Text("MAX")
|
||||||
.font(.system(size: 7, weight: .black))
|
.font(.system(size: maxBadgeFontSize, weight: .black))
|
||||||
.foregroundStyle(.white)
|
.foregroundStyle(.white)
|
||||||
.padding(.horizontal, 4)
|
.padding(.horizontal, Design.Spacing.xSmall)
|
||||||
.padding(.vertical, 2)
|
.padding(.vertical, Design.Spacing.xxSmall)
|
||||||
.background(
|
.background(
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(Color.red)
|
.fill(Color.red)
|
||||||
)
|
)
|
||||||
.offset(x: 6, y: -4)
|
.offset(x: maxBadgeOffsetX, y: maxBadgeOffsetY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -463,7 +501,7 @@ struct ChipOnTable: View {
|
|||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(red: 0.05, green: 0.2, blue: 0.1)
|
Color.Table.baseDark
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
|
|
||||||
MiniBaccaratTableView(
|
MiniBaccaratTableView(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user