Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
c846ef05ac
commit
b08e92a402
@ -43,7 +43,9 @@ enum Design {
|
||||
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
|
||||
@ -83,22 +85,46 @@ enum Design {
|
||||
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.2
|
||||
static let staggerDelay2: Double = 0.4
|
||||
}
|
||||
|
||||
// MARK: - Opacity
|
||||
|
||||
enum Opacity {
|
||||
static let disabled: Double = 0.5
|
||||
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 overlay: Double = 0.4
|
||||
static let medium: Double = 0.5
|
||||
static let secondary: Double = 0.5
|
||||
static let disabled: Double = 0.5
|
||||
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
|
||||
|
||||
@ -35,7 +35,7 @@ struct CardView: View {
|
||||
.degrees(isFaceUp ? 0 : 180),
|
||||
axis: (x: 0, y: 1, z: 0)
|
||||
)
|
||||
.animation(.spring(duration: 0.4, bounce: 0.2), value: isFaceUp)
|
||||
.animation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.cardFlipBounce), value: isFaceUp)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,16 @@ struct CardFrontView: View {
|
||||
let width: CGFloat
|
||||
let height: CGFloat
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let rankFontRatio: CGFloat = 0.22
|
||||
private let suitFontRatio: CGFloat = 0.18
|
||||
private let centerSuitFontRatio: CGFloat = 0.5
|
||||
private let contentPaddingRatio: CGFloat = 0.08
|
||||
private let backgroundWhite: Double = 0.96
|
||||
private let borderLightGray: Double = 0.8
|
||||
private let borderDarkGray: Double = 0.6
|
||||
|
||||
private var suitColor: Color {
|
||||
card.suit.isRed ? .red : .black
|
||||
}
|
||||
@ -52,24 +62,24 @@ struct CardFrontView: View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Card background with subtle gradient
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.small)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [.white, Color(white: 0.96)],
|
||||
colors: [.white, Color(white: backgroundWhite)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
|
||||
// Card border
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.small)
|
||||
.strokeBorder(
|
||||
LinearGradient(
|
||||
colors: [Color(white: 0.8), Color(white: 0.6)],
|
||||
colors: [Color(white: borderLightGray), Color(white: borderDarkGray)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
lineWidth: Design.LineWidth.thin
|
||||
)
|
||||
|
||||
// Card content
|
||||
@ -78,9 +88,9 @@ struct CardFrontView: View {
|
||||
HStack {
|
||||
VStack(spacing: 0) {
|
||||
Text(card.rank.symbol)
|
||||
.font(.system(size: width * 0.22, weight: .bold, design: .serif))
|
||||
.font(.system(size: width * rankFontRatio, weight: .bold, design: .serif))
|
||||
Text(card.suit.rawValue)
|
||||
.font(.system(size: width * 0.18))
|
||||
.font(.system(size: width * suitFontRatio))
|
||||
}
|
||||
.foregroundStyle(suitColor)
|
||||
Spacer()
|
||||
@ -90,7 +100,7 @@ struct CardFrontView: View {
|
||||
|
||||
// Center suit (large)
|
||||
Text(card.suit.rawValue)
|
||||
.font(.system(size: width * 0.5))
|
||||
.font(.system(size: width * centerSuitFontRatio))
|
||||
.foregroundStyle(suitColor)
|
||||
|
||||
Spacer()
|
||||
@ -100,18 +110,18 @@ struct CardFrontView: View {
|
||||
Spacer()
|
||||
VStack(spacing: 0) {
|
||||
Text(card.suit.rawValue)
|
||||
.font(.system(size: width * 0.18))
|
||||
.font(.system(size: width * suitFontRatio))
|
||||
Text(card.rank.symbol)
|
||||
.font(.system(size: width * 0.22, weight: .bold, design: .serif))
|
||||
.font(.system(size: width * rankFontRatio, weight: .bold, design: .serif))
|
||||
}
|
||||
.foregroundStyle(suitColor)
|
||||
.rotationEffect(.degrees(180))
|
||||
}
|
||||
}
|
||||
.padding(width * 0.08)
|
||||
.padding(width * contentPaddingRatio)
|
||||
}
|
||||
.frame(width: width, height: height)
|
||||
.shadow(color: .black.opacity(0.2), radius: 4, x: 2, y: 2)
|
||||
.shadow(color: .black.opacity(Design.Opacity.hint), radius: Design.Shadow.radiusSmall, x: 2, y: 2)
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,6 +130,14 @@ struct CardBackView: View {
|
||||
let width: CGFloat
|
||||
let height: CGFloat
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let innerPaddingRatio: CGFloat = 0.1
|
||||
private let patternPaddingRatio: CGFloat = 0.12
|
||||
private let emblemGradientRatio: CGFloat = 0.15
|
||||
private let emblemSizeRatio: CGFloat = 0.3
|
||||
private let logoFontRatio: CGFloat = 0.18
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Base
|
||||
@ -161,14 +179,14 @@ struct CardBackView: View {
|
||||
endPoint: .bottom
|
||||
)
|
||||
)
|
||||
.padding(width * 0.1)
|
||||
.padding(width * innerPaddingRatio)
|
||||
|
||||
// Diamond pattern overlay
|
||||
DiamondPatternView()
|
||||
.foregroundStyle(
|
||||
Color.Card.diamondPattern.opacity(Design.Opacity.light)
|
||||
)
|
||||
.padding(width * 0.12)
|
||||
.padding(width * patternPaddingRatio)
|
||||
.clipShape(RoundedRectangle(cornerRadius: Design.CornerRadius.small / 2))
|
||||
|
||||
// Center emblem
|
||||
@ -181,14 +199,14 @@ struct CardBackView: View {
|
||||
],
|
||||
center: .center,
|
||||
startRadius: 0,
|
||||
endRadius: width * 0.15
|
||||
endRadius: width * emblemGradientRatio
|
||||
)
|
||||
)
|
||||
.frame(width: width * 0.3, height: width * 0.3)
|
||||
.frame(width: width * emblemSizeRatio, height: width * emblemSizeRatio)
|
||||
|
||||
// B for Baccarat
|
||||
Text("B")
|
||||
.font(.system(size: width * 0.18, weight: .bold, design: .serif))
|
||||
.font(.system(size: width * logoFontRatio, weight: .bold, design: .serif))
|
||||
.foregroundStyle(Color.Card.logoText)
|
||||
}
|
||||
.frame(width: width, height: height)
|
||||
|
||||
@ -19,6 +19,15 @@ struct ChipView: View {
|
||||
self.isSelected = isSelected
|
||||
}
|
||||
|
||||
// MARK: - Layout Constants
|
||||
|
||||
private let innerCircleRatio: CGFloat = 0.65
|
||||
private let innerGradientRatio: CGFloat = 0.4
|
||||
private let textSizeRatio: CGFloat = 0.25
|
||||
private let selectionGlowPadding: CGFloat = 6
|
||||
private let shadowOffset: CGFloat = 2
|
||||
private let shadowOffsetY: CGFloat = 3
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
// Base circle with gradient
|
||||
@ -28,7 +37,7 @@ struct ChipView: View {
|
||||
colors: [
|
||||
denomination.secondaryColor,
|
||||
denomination.primaryColor,
|
||||
denomination.primaryColor.opacity(0.8)
|
||||
denomination.primaryColor.opacity(Design.Opacity.heavy)
|
||||
],
|
||||
center: .topLeading,
|
||||
startRadius: 0,
|
||||
@ -50,24 +59,24 @@ struct ChipView: View {
|
||||
],
|
||||
center: .topLeading,
|
||||
startRadius: 0,
|
||||
endRadius: size * 0.4
|
||||
endRadius: size * innerGradientRatio
|
||||
)
|
||||
)
|
||||
.frame(width: size * 0.65, height: size * 0.65)
|
||||
.frame(width: size * innerCircleRatio, height: size * innerCircleRatio)
|
||||
|
||||
// Inner border
|
||||
Circle()
|
||||
.strokeBorder(
|
||||
denomination.stripeColor.opacity(0.8),
|
||||
denomination.stripeColor.opacity(Design.Opacity.heavy),
|
||||
lineWidth: Design.LineWidth.medium
|
||||
)
|
||||
.frame(width: size * 0.65, height: size * 0.65)
|
||||
.frame(width: size * innerCircleRatio, height: size * innerCircleRatio)
|
||||
|
||||
// Denomination text
|
||||
Text(denomination.displayText)
|
||||
.font(.system(size: size * 0.25, weight: .heavy, design: .rounded))
|
||||
.font(.system(size: size * textSizeRatio, weight: .heavy, design: .rounded))
|
||||
.foregroundStyle(denomination.stripeColor)
|
||||
.shadow(color: .black.opacity(Design.Opacity.light), radius: 1, x: 1, y: 1)
|
||||
.shadow(color: .black.opacity(Design.Opacity.light), radius: Design.LineWidth.thin, x: 1, y: 1)
|
||||
|
||||
// Outer border
|
||||
Circle()
|
||||
@ -87,13 +96,13 @@ struct ChipView: View {
|
||||
if isSelected {
|
||||
Circle()
|
||||
.strokeBorder(Color.yellow, lineWidth: Design.LineWidth.thick)
|
||||
.frame(width: size + 6, height: size + 6)
|
||||
.frame(width: size + selectionGlowPadding, height: size + selectionGlowPadding)
|
||||
}
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
.shadow(color: .black.opacity(Design.Opacity.overlay), radius: isSelected ? 8 : 4, x: 2, y: 3)
|
||||
.scaleEffect(isSelected ? 1.1 : 1.0)
|
||||
.animation(.spring(duration: 0.2), value: isSelected)
|
||||
.shadow(color: .black.opacity(Design.Opacity.overlay), radius: isSelected ? Design.Shadow.radiusSmall * 2 : Design.Shadow.radiusSmall, x: shadowOffset, y: shadowOffsetY)
|
||||
.scaleEffect(isSelected ? Design.Scale.selected : Design.Scale.normal)
|
||||
.animation(.spring(duration: Design.Animation.selectionDuration), value: isSelected)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -267,7 +267,7 @@ struct GameOverView: View {
|
||||
)
|
||||
.shadow(color: .red.opacity(0.2), radius: Design.Shadow.radiusXXLarge)
|
||||
.padding(.horizontal, Design.Spacing.xxLarge)
|
||||
.scaleEffect(showContent ? 1.0 : 0.8)
|
||||
.scaleEffect(showContent ? Design.Scale.normal : Design.Scale.slightShrink)
|
||||
.opacity(showContent ? 1.0 : 0)
|
||||
}
|
||||
.onAppear {
|
||||
@ -366,11 +366,13 @@ struct CompactHandView: View {
|
||||
// Fixed size: cards have strict visual constraints
|
||||
|
||||
private let cardWidth: CGFloat = 45
|
||||
private let cardOverlap: CGFloat = -12
|
||||
private let placeholderSpacing: CGFloat = 8
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: -12) {
|
||||
HStack(spacing: cards.isEmpty ? placeholderSpacing : cardOverlap) {
|
||||
if cards.isEmpty {
|
||||
// Placeholders
|
||||
// Placeholders - no overlap, just side by side
|
||||
ForEach(0..<2, id: \.self) { _ in
|
||||
CardPlaceholderView(width: cardWidth)
|
||||
}
|
||||
@ -516,7 +518,7 @@ struct TopBarView: View {
|
||||
.contentTransition(.numericText())
|
||||
.animation(.spring(duration: Design.Animation.quick), value: balance)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.tight)
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.medium)
|
||||
.padding(.vertical, Design.Spacing.xSmall)
|
||||
@ -611,7 +613,7 @@ struct ActionButtonsView: View {
|
||||
.font(.system(size: statusFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.heavy))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.7)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.relaxed)
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.xLarge)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
|
||||
@ -70,7 +70,7 @@ struct MiniBaccaratTableView: View {
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
.tracking(1)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.6)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.comfortable)
|
||||
|
||||
ZStack {
|
||||
// Table felt background with arc shape
|
||||
@ -248,13 +248,13 @@ struct TieBettingZone: View {
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(2)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.tight)
|
||||
|
||||
Text("PAYS 8 TO 1")
|
||||
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||
.opacity(Design.Opacity.heavy)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.tight)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
@ -338,13 +338,13 @@ struct BankerBettingZone: View {
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(3)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.tight)
|
||||
|
||||
Text("PAYS 0.95 TO 1")
|
||||
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||
.opacity(Design.Opacity.heavy)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.tight)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
@ -428,13 +428,13 @@ struct PlayerBettingZone: View {
|
||||
.font(.system(size: titleFontSize, weight: .black, design: .rounded))
|
||||
.tracking(3)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.tight)
|
||||
|
||||
Text("PAYS 1 TO 1")
|
||||
.font(.system(size: subtitleFontSize, weight: .medium))
|
||||
.opacity(Design.Opacity.heavy)
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
.minimumScaleFactor(Design.MinScaleFactor.tight)
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
|
||||
@ -41,8 +41,8 @@ struct ResultBannerView: View {
|
||||
)
|
||||
)
|
||||
.shadow(color: result.color.opacity(Design.Opacity.heavy), radius: Design.Shadow.radiusLarge)
|
||||
.scaleEffect(showText ? 1.0 : 0.5)
|
||||
.opacity(showText ? 1.0 : 0)
|
||||
.scaleEffect(showText ? Design.Scale.normal : Design.Scale.shrunk)
|
||||
.opacity(showText ? Design.Scale.normal : 0)
|
||||
|
||||
// Winnings display
|
||||
if winnings != 0 {
|
||||
@ -60,8 +60,8 @@ struct ResultBannerView: View {
|
||||
}
|
||||
}
|
||||
.font(.system(size: winningsFontSize, weight: .bold, design: .rounded))
|
||||
.scaleEffect(showWinnings ? 1.0 : 0.5)
|
||||
.opacity(showWinnings ? 1.0 : 0)
|
||||
.scaleEffect(showWinnings ? Design.Scale.normal : Design.Scale.shrunk)
|
||||
.opacity(showWinnings ? Design.Scale.normal : 0)
|
||||
}
|
||||
}
|
||||
.padding(Design.Spacing.xxxLarge + Design.Spacing.small)
|
||||
@ -93,19 +93,19 @@ struct ResultBannerView: View {
|
||||
)
|
||||
)
|
||||
.shadow(color: result.color.opacity(Design.Opacity.light), radius: Design.Shadow.radiusXXLarge)
|
||||
.scaleEffect(showBanner ? 1.0 : 0.8)
|
||||
.opacity(showBanner ? 1.0 : 0)
|
||||
.scaleEffect(showBanner ? Design.Scale.normal : Design.Scale.slightShrink)
|
||||
.opacity(showBanner ? Design.Scale.normal : 0)
|
||||
}
|
||||
.onAppear {
|
||||
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce)) {
|
||||
showBanner = true
|
||||
}
|
||||
|
||||
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(0.2)) {
|
||||
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(Design.Animation.staggerDelay1)) {
|
||||
showText = true
|
||||
}
|
||||
|
||||
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(0.4)) {
|
||||
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: Design.Animation.springBounce).delay(Design.Animation.staggerDelay2)) {
|
||||
showWinnings = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,7 +265,7 @@ struct SettingsToggle: View {
|
||||
Toggle(isOn: $isOn) {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||
Text(title)
|
||||
.font(.system(size: 15, weight: .medium))
|
||||
.font(.system(size: Design.BaseFontSize.subheadline, weight: .medium))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
Text(subtitle)
|
||||
@ -290,7 +290,7 @@ struct SpeedPicker: View {
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: Design.Spacing.small) {
|
||||
Text("Dealing Speed")
|
||||
.font(.system(size: 15, weight: .medium))
|
||||
.font(.system(size: Design.BaseFontSize.subheadline, weight: .medium))
|
||||
.foregroundStyle(.white)
|
||||
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
@ -299,7 +299,7 @@ struct SpeedPicker: View {
|
||||
speed = option.1
|
||||
} label: {
|
||||
Text(option.0)
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
.font(.system(size: Design.BaseFontSize.callout, weight: .medium))
|
||||
.foregroundStyle(speed == option.1 ? .black : .white.opacity(Design.Opacity.strong))
|
||||
.padding(.vertical, Design.Spacing.small)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user