Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
a2ecfcd580
commit
017000b197
@ -17,7 +17,7 @@ enum Design {
|
||||
// MARK: - Debug
|
||||
|
||||
/// Set to true to show layout debug borders on views
|
||||
static let showDebugBorders = false
|
||||
static let showDebugBorders = true
|
||||
|
||||
// MARK: - Shared Constants (from CasinoKit)
|
||||
|
||||
@ -32,73 +32,45 @@ enum Design {
|
||||
typealias BaseFontSize = CasinoDesign.BaseFontSize
|
||||
typealias IconSize = CasinoDesign.IconSize
|
||||
|
||||
// MARK: - Blackjack-Specific Component Sizes
|
||||
// MARK: - Blackjack-Specific Sizes (use CasinoDesign.Size for shared values)
|
||||
|
||||
enum Size {
|
||||
// Cards
|
||||
static let cardWidth: CGFloat = 90
|
||||
static let cardOverlap: CGFloat = -50 // Negative = more overlap
|
||||
|
||||
// Use shared scaling from CasinoKit
|
||||
static var handScale: CGFloat { CasinoDesign.Size.handScale }
|
||||
static var fontScale: CGFloat { CasinoDesign.Size.fontScale }
|
||||
// Player hands
|
||||
static let playerHandsHeight: CGFloat = 240
|
||||
|
||||
// Cards - scaled for better visibility
|
||||
static let cardWidth: CGFloat = 60 * handScale // 90pt at 1.5x
|
||||
static let cardWidthSmall: CGFloat = CasinoDesign.Size.cardWidthSmall
|
||||
// Hand labels
|
||||
static let handLabelFontSize: CGFloat = 14
|
||||
static let handNumberFontSize: CGFloat = 12
|
||||
static let handValueFontSize: CGFloat = 18
|
||||
|
||||
/// Card overlap (negative = cards stack left over right).
|
||||
/// More negative = more overlap (less card visible).
|
||||
/// With 90pt cards: -40 = ~44% overlap, -50 = ~55% overlap
|
||||
static let cardOverlap: CGFloat = -50
|
||||
// Hints
|
||||
static let hintFontSize: CGFloat = 15
|
||||
static let hintIconSize: CGFloat = 24
|
||||
static let hintPaddingH: CGFloat = 18
|
||||
static let hintPaddingV: CGFloat = 12
|
||||
|
||||
// Player hands container height (accommodates larger cards + labels)
|
||||
// Reduced from 180 to fit content more snugly
|
||||
static let playerHandsHeight: CGFloat = 160 * handScale // 240pt at 1.5x
|
||||
// Hand icons
|
||||
static let handIconSize: CGFloat = 18
|
||||
|
||||
// Hand label font sizes (scaled)
|
||||
static let handLabelFontSize: CGFloat = CasinoDesign.BaseFontSize.medium * fontScale
|
||||
static let handNumberFontSize: CGFloat = CasinoDesign.BaseFontSize.medium * fontScale // Same as label
|
||||
static let handValueFontSize: CGFloat = CasinoDesign.BaseFontSize.xLarge * fontScale
|
||||
// Hi-Lo count badge
|
||||
static let countBadgeFontSize: CGFloat = 10
|
||||
static let countBadgePaddingH: CGFloat = 6
|
||||
static let countBadgePaddingV: CGFloat = 2
|
||||
static let countBadgeOffset: CGFloat = 6
|
||||
|
||||
// Hint font size (scaled to match hands)
|
||||
static let hintFontSize: CGFloat = CasinoDesign.BaseFontSize.small * fontScale
|
||||
static let hintIconSize: CGFloat = CasinoDesign.IconSize.medium * handScale
|
||||
static let hintPaddingH: CGFloat = CasinoDesign.Spacing.medium * handScale
|
||||
static let hintPaddingV: CGFloat = CasinoDesign.Spacing.small * handScale
|
||||
// Betting
|
||||
static let bettingChipSize: CGFloat = 54
|
||||
|
||||
// Hand icons (scaled)
|
||||
static let handIconSize: CGFloat = CasinoDesign.IconSize.medium * handScale
|
||||
// Card count display
|
||||
static let cardCountLabelSize: CGFloat = 11
|
||||
static let cardCountValueSize: CGFloat = 18
|
||||
|
||||
// Hi-Lo count badge (scaled)
|
||||
static let countBadgeFontSize: CGFloat = CasinoDesign.BaseFontSize.xxSmall * fontScale
|
||||
static let countBadgePaddingH: CGFloat = CasinoDesign.Spacing.xSmall * handScale
|
||||
static let countBadgePaddingV: CGFloat = CasinoDesign.Spacing.xxxSmall * handScale
|
||||
static let countBadgeOffset: CGFloat = CasinoDesign.Spacing.xSmall * handScale
|
||||
|
||||
// Betting zone (chip scales, but zone height stays reasonable)
|
||||
static let bettingChipSize: CGFloat = 36 * handScale // 54pt at 1.5x
|
||||
static let bettingZoneHeightScaled: CGFloat = CasinoDesign.Size.bettingZoneHeight // Keep original height to save space
|
||||
|
||||
// Card count display (scaled)
|
||||
static let cardCountLabelSize: CGFloat = CasinoDesign.BaseFontSize.xSmall * fontScale
|
||||
static let cardCountValueSize: CGFloat = CasinoDesign.BaseFontSize.large * handScale
|
||||
|
||||
// Chips - use CasinoDesign values
|
||||
static let chipBadgeSize: CGFloat = CasinoDesign.Size.chipBadge
|
||||
|
||||
// Buttons - use CasinoDesign values
|
||||
static let actionButtonHeight: CGFloat = CasinoDesign.Size.actionButtonHeight
|
||||
static let actionButtonMinWidth: CGFloat = CasinoDesign.Size.actionButtonMinWidth
|
||||
static let bettingZoneHeight: CGFloat = CasinoDesign.Size.bettingZoneHeight
|
||||
|
||||
// Responsive - use CasinoDesign values
|
||||
static let maxContentWidthPortrait: CGFloat = CasinoDesign.Size.maxContentWidthPortrait
|
||||
static let maxContentWidthLandscape: CGFloat = CasinoDesign.Size.maxContentWidthLandscape
|
||||
static let maxModalWidth: CGFloat = CasinoDesign.Size.maxModalWidth
|
||||
|
||||
// Blackjack-specific
|
||||
// Table
|
||||
static let tableHeight: CGFloat = 280
|
||||
|
||||
// Settings - use CasinoDesign values
|
||||
static let checkmark: CGFloat = CasinoDesign.Size.checkmark
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +110,7 @@ extension Color {
|
||||
static let blackjack = Color.yellow
|
||||
}
|
||||
|
||||
// MARK: - Action Button Colors
|
||||
// MARK: - Action Button Colors (Blackjack-specific)
|
||||
|
||||
enum Button {
|
||||
static let hit = Color(red: 0.2, green: 0.6, blue: 0.3)
|
||||
@ -147,25 +119,6 @@ extension Color {
|
||||
static let split = Color(red: 0.3, green: 0.5, blue: 0.7)
|
||||
static let surrender = Color(red: 0.6, green: 0.3, blue: 0.3)
|
||||
static let insurance = Color(red: 0.7, green: 0.6, blue: 0.2)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// MARK: - Settings Colors
|
||||
|
||||
enum Settings {
|
||||
static let background = Color(red: 0.08, green: 0.12, blue: 0.18)
|
||||
static let cardBackground = Color.white.opacity(CasinoDesign.Opacity.verySubtle)
|
||||
static let accent = Color(red: 0.9, green: 0.75, blue: 0.3)
|
||||
}
|
||||
|
||||
// MARK: - Modal Colors
|
||||
|
||||
enum Modal {
|
||||
static let background = Color(red: 0.12, green: 0.18, blue: 0.25)
|
||||
static let backgroundLight = Color(red: 0.15, green: 0.2, blue: 0.3)
|
||||
static let backgroundDark = Color(red: 0.1, green: 0.15, blue: 0.25)
|
||||
}
|
||||
|
||||
// MARK: - TopBar Colors
|
||||
|
||||
@ -61,7 +61,7 @@ struct ActionButton: View {
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
|
||||
@ -13,16 +13,19 @@ struct CardCountView: View {
|
||||
let runningCount: Int
|
||||
let trueCount: Double
|
||||
|
||||
@ScaledMetric(relativeTo: .caption) private var labelSize: CGFloat = Design.Size.cardCountLabelSize
|
||||
@ScaledMetric(relativeTo: .title) private var valueSize: CGFloat = Design.Size.cardCountValueSize
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.large) {
|
||||
// Running count
|
||||
VStack(spacing: Design.Spacing.xxSmall) {
|
||||
Text("Running")
|
||||
.font(.system(size: Design.Size.cardCountLabelSize, weight: .medium))
|
||||
.font(.system(size: labelSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
|
||||
Text(runningCount >= 0 ? "+\(runningCount)" : "\(runningCount)")
|
||||
.font(.system(size: Design.Size.cardCountValueSize, weight: .bold, design: .monospaced))
|
||||
.font(.system(size: valueSize, weight: .bold, design: .monospaced))
|
||||
.foregroundStyle(countColor(for: runningCount))
|
||||
}
|
||||
|
||||
@ -33,11 +36,11 @@ struct CardCountView: View {
|
||||
// True count
|
||||
VStack(spacing: Design.Spacing.xxSmall) {
|
||||
Text("True")
|
||||
.font(.system(size: Design.Size.cardCountLabelSize, weight: .medium))
|
||||
.font(.system(size: labelSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||
|
||||
Text(trueCount >= 0 ? "+\(trueCount, format: .number.precision(.fractionLength(1)))" : "\(trueCount, format: .number.precision(.fractionLength(1)))")
|
||||
.font(.system(size: Design.Size.cardCountValueSize, weight: .bold, design: .monospaced))
|
||||
.font(.system(size: valueSize, weight: .bold, design: .monospaced))
|
||||
.foregroundStyle(countColor(for: Int(trueCount.rounded())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,8 +33,8 @@ struct GameTableView: View {
|
||||
private var maxContentWidth: CGFloat {
|
||||
if isIPad {
|
||||
return verticalSizeClass == .compact
|
||||
? Design.Size.maxContentWidthLandscape
|
||||
: Design.Size.maxContentWidthPortrait
|
||||
? CasinoDesign.Size.maxContentWidthLandscape
|
||||
: CasinoDesign.Size.maxContentWidthPortrait
|
||||
}
|
||||
return .infinity
|
||||
}
|
||||
@ -72,18 +72,16 @@ struct GameTableView: View {
|
||||
|
||||
@ViewBuilder
|
||||
private func mainGameView(state: GameState) -> some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// Background
|
||||
TableBackgroundView()
|
||||
|
||||
mainContent(state: state, screenHeight: geometry.size.height)
|
||||
}
|
||||
mainContent(state: state)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func mainContent(state: GameState, screenHeight: CGFloat) -> some View {
|
||||
private func mainContent(state: GameState) -> some View {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
// Top bar
|
||||
@ -119,8 +117,7 @@ struct GameTableView: View {
|
||||
// Table layout - fills available space
|
||||
BlackjackTableView(
|
||||
state: state,
|
||||
onPlaceBet: { placeBet(state: state) },
|
||||
screenHeight: screenHeight
|
||||
onPlaceBet: { placeBet(state: state) }
|
||||
)
|
||||
.frame(maxWidth: maxContentWidth)
|
||||
|
||||
|
||||
@ -121,7 +121,7 @@ struct ResultBannerView: View {
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
@ -144,7 +144,7 @@ struct ResultBannerView: View {
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
@ -158,7 +158,7 @@ struct ResultBannerView: View {
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.xxLarge)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Modal.backgroundLight, Color.Modal.backgroundDark],
|
||||
colors: [Color.CasinoModal.backgroundLight, Color.CasinoModal.backgroundDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
@ -172,7 +172,7 @@ struct ResultBannerView: View {
|
||||
)
|
||||
)
|
||||
.shadow(color: mainResultColor.opacity(Design.Opacity.hint), radius: Design.Shadow.radiusXLarge)
|
||||
.frame(maxWidth: Design.Size.maxModalWidth)
|
||||
.frame(maxWidth: CasinoDesign.Size.maxModalWidth)
|
||||
.padding(.horizontal, Design.Spacing.large) // Prevent clipping on sides
|
||||
.scaleEffect(showContent ? 1.0 : 0.8)
|
||||
.opacity(showContent ? 1.0 : 0)
|
||||
|
||||
@ -191,7 +191,7 @@ struct RulesHelpView: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
Color.Settings.background
|
||||
Color.Sheet.background
|
||||
.ignoresSafeArea()
|
||||
|
||||
VStack(spacing: 0) {
|
||||
@ -208,7 +208,7 @@ struct RulesHelpView: View {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
ForEach(pages.indices, id: \.self) { index in
|
||||
Circle()
|
||||
.fill(index == currentPage ? Color.Settings.accent : Color.white.opacity(Design.Opacity.light))
|
||||
.fill(index == currentPage ? Color.Sheet.accent : Color.white.opacity(Design.Opacity.light))
|
||||
.frame(width: 8, height: 8)
|
||||
}
|
||||
}
|
||||
@ -222,10 +222,10 @@ struct RulesHelpView: View {
|
||||
Button(String(localized: "Done")) {
|
||||
dismiss()
|
||||
}
|
||||
.foregroundStyle(Color.Settings.accent)
|
||||
.foregroundStyle(Color.Sheet.accent)
|
||||
}
|
||||
}
|
||||
.toolbarBackground(Color.Settings.background, for: .navigationBar)
|
||||
.toolbarBackground(Color.Sheet.background, for: .navigationBar)
|
||||
.toolbarColorScheme(.dark, for: .navigationBar)
|
||||
}
|
||||
}
|
||||
@ -255,7 +255,7 @@ struct RulePageView: View {
|
||||
// Icon
|
||||
Image(systemName: page.icon)
|
||||
.font(.system(size: iconSize))
|
||||
.foregroundStyle(Color.Settings.accent)
|
||||
.foregroundStyle(Color.Sheet.accent)
|
||||
.padding(.top, Design.Spacing.xxLarge)
|
||||
|
||||
// Title
|
||||
@ -268,7 +268,7 @@ struct RulePageView: View {
|
||||
ForEach(page.content.indices, id: \.self) { index in
|
||||
HStack(alignment: .top, spacing: Design.Spacing.medium) {
|
||||
Text("•")
|
||||
.foregroundStyle(Color.Settings.accent)
|
||||
.foregroundStyle(Color.Sheet.accent)
|
||||
|
||||
Text(page.content[index])
|
||||
.font(.system(size: bodySize))
|
||||
|
||||
@ -24,7 +24,7 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
/// Accent color for settings components
|
||||
private let accent = Color.Settings.accent
|
||||
private let accent = Color.Sheet.accent
|
||||
|
||||
var body: some View {
|
||||
SheetContainerView(
|
||||
@ -377,7 +377,7 @@ struct GameStylePicker: View {
|
||||
title: style.displayName,
|
||||
subtitle: style.description,
|
||||
isSelected: selection == style,
|
||||
accentColor: Color.Settings.accent,
|
||||
accentColor: Color.Sheet.accent,
|
||||
action: { selection = style }
|
||||
)
|
||||
}
|
||||
@ -397,7 +397,7 @@ struct DeckCountPicker: View {
|
||||
title: count.displayName,
|
||||
subtitle: count.description,
|
||||
isSelected: selection == count,
|
||||
accentColor: Color.Settings.accent,
|
||||
accentColor: Color.Sheet.accent,
|
||||
action: { selection = count }
|
||||
)
|
||||
}
|
||||
@ -417,8 +417,8 @@ struct TableLimitsPicker: View {
|
||||
title: limit.displayName,
|
||||
subtitle: limit.detailedDescription,
|
||||
isSelected: selection == limit,
|
||||
accentColor: Color.Settings.accent,
|
||||
badge: { BadgePill(text: limit.description, isSelected: selection == limit, accentColor: Color.Settings.accent) },
|
||||
accentColor: Color.Sheet.accent,
|
||||
badge: { BadgePill(text: limit.description, isSelected: selection == limit, accentColor: Color.Sheet.accent) },
|
||||
action: { selection = limit }
|
||||
)
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ struct StatisticsSheetView: View {
|
||||
StatBox(title: String(localized: "Rounds"), value: "\(totalRounds)", color: .white)
|
||||
StatBox(title: String(localized: "Win Rate"), value: formatPercent(winRate), color: winRate >= 50 ? .green : .orange)
|
||||
StatBox(title: String(localized: "Net"), value: formatMoney(totalWinnings), color: totalWinnings >= 0 ? .green : .red)
|
||||
StatBox(title: String(localized: "Balance"), value: "$\(state.balance)", color: Color.Settings.accent)
|
||||
StatBox(title: String(localized: "Balance"), value: "$\(state.balance)", color: Color.Sheet.accent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,9 @@ struct BettingZoneView: View {
|
||||
let onTap: () -> Void
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||
@ScaledMetric(relativeTo: .caption) private var detailFontSize: CGFloat = Design.Size.handNumberFontSize
|
||||
@ScaledMetric(relativeTo: .body) private var chipSize: CGFloat = Design.Size.bettingChipSize
|
||||
@ScaledMetric(relativeTo: .body) private var zoneHeight: CGFloat = CasinoDesign.Size.bettingZoneHeight
|
||||
|
||||
private var isAtMax: Bool {
|
||||
betAmount >= maxBet
|
||||
@ -34,7 +37,7 @@ struct BettingZoneView: View {
|
||||
// Content
|
||||
if betAmount > 0 {
|
||||
// Show chip with amount (scaled)
|
||||
ChipOnTableView(amount: betAmount, showMax: isAtMax, size: Design.Size.bettingChipSize)
|
||||
ChipOnTableView(amount: betAmount, showMax: isAtMax, size: chipSize)
|
||||
} else {
|
||||
// Empty state
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
@ -44,18 +47,18 @@ struct BettingZoneView: View {
|
||||
|
||||
HStack(spacing: Design.Spacing.medium) {
|
||||
Text(String(localized: "Min: $\(minBet)"))
|
||||
.font(.system(size: Design.Size.handNumberFontSize, weight: .medium))
|
||||
.font(.system(size: detailFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.light))
|
||||
|
||||
Text(String(localized: "Max: $\(maxBet.formatted())"))
|
||||
.font(.system(size: Design.Size.handNumberFontSize, weight: .medium))
|
||||
.font(.system(size: detailFontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.light))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: Design.Size.bettingZoneHeightScaled)
|
||||
.frame(height: zoneHeight)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(betAmount > 0 ? "$\(betAmount) bet" + (isAtMax ? ", maximum" : "") : "Place bet")
|
||||
|
||||
@ -12,8 +12,15 @@ struct BlackjackTableView: View {
|
||||
@Bindable var state: GameState
|
||||
let onPlaceBet: () -> Void
|
||||
|
||||
/// Screen height passed from parent for responsive sizing
|
||||
var screenHeight: CGFloat = 800
|
||||
// MARK: - Environment
|
||||
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
|
||||
// MARK: - State
|
||||
|
||||
/// Screen dimensions measured from container for responsive sizing
|
||||
@State private var screenHeight: CGFloat = 800
|
||||
@State private var screenWidth: CGFloat = 375
|
||||
|
||||
/// Whether to show Hi-Lo card count values on cards.
|
||||
var showCardCount: Bool { state.settings.showCardCount }
|
||||
@ -24,10 +31,44 @@ struct BlackjackTableView: View {
|
||||
@ScaledMetric(relativeTo: .title) private var valueFontSize: CGFloat = Design.BaseFontSize.xLarge
|
||||
@ScaledMetric(relativeTo: .caption) private var hintFontSize: CGFloat = Design.BaseFontSize.small
|
||||
|
||||
// MARK: - Layout
|
||||
// MARK: - Dynamic Card Sizing
|
||||
|
||||
private let cardWidth: CGFloat = Design.Size.cardWidth
|
||||
private let cardSpacing: CGFloat = Design.Size.cardOverlap
|
||||
/// Whether we're in landscape mode
|
||||
/// - iPhones: use verticalSizeClass == .compact
|
||||
/// - iPads: use screen dimensions (since iPads always report .regular)
|
||||
private var isLandscape: Bool {
|
||||
if DeviceInfo.isPad {
|
||||
return screenWidth > screenHeight
|
||||
}
|
||||
return verticalSizeClass == .compact
|
||||
}
|
||||
|
||||
/// Dynamic card width based on screen width
|
||||
/// - iPhone SE (375pt): ~80pt
|
||||
/// - iPhone 16 Pro (393pt): ~95pt
|
||||
/// - iPhone 16 Pro Max (430pt): ~120pt
|
||||
/// - iPad mini (landscape): ~90pt (reduced for smaller tablet screen)
|
||||
/// - iPad: ~140pt (capped)
|
||||
private var cardWidth: CGFloat {
|
||||
// iPad mini in landscape gets smaller cards to fit better
|
||||
if DeviceInfo.isPadMini && isLandscape {
|
||||
return 90
|
||||
}
|
||||
|
||||
let baseWidth: CGFloat = 80 // Minimum card width for smallest screens
|
||||
let baseScreen: CGFloat = 375 // iPhone SE width
|
||||
let scale: CGFloat = 0.7 // Scale factor (70% of width increase goes to cards)
|
||||
let maxWidth: CGFloat = 140 // Cap for large screens
|
||||
|
||||
let calculated = baseWidth + (screenWidth - baseScreen) * scale
|
||||
return min(maxWidth, max(baseWidth, calculated))
|
||||
}
|
||||
|
||||
/// Card overlap scales proportionally with card width
|
||||
private var cardSpacing: CGFloat {
|
||||
// Overlap ratio: roughly -55% of card width
|
||||
cardWidth * -0.55
|
||||
}
|
||||
|
||||
/// Fixed height for the hint area to prevent layout shifts
|
||||
private let hintAreaHeight: CGFloat = 44
|
||||
@ -116,6 +157,19 @@ struct BlackjackTableView: View {
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.padding(.vertical, Design.Spacing.medium)
|
||||
.background(
|
||||
GeometryReader { geometry in
|
||||
Color.clear
|
||||
.onAppear {
|
||||
screenHeight = geometry.size.height
|
||||
screenWidth = geometry.size.width
|
||||
}
|
||||
.onChange(of: geometry.size) { _, newSize in
|
||||
screenHeight = newSize.height
|
||||
screenWidth = newSize.width
|
||||
}
|
||||
}
|
||||
)
|
||||
.debugBorder(showDebugBorders, color: .white, label: "TableView")
|
||||
.animation(.spring(duration: Design.Animation.springDuration), value: state.currentPhase)
|
||||
}
|
||||
|
||||
@ -12,17 +12,22 @@ import CasinoKit
|
||||
struct HiLoCountBadge: View {
|
||||
let card: Card
|
||||
|
||||
@ScaledMetric(relativeTo: .caption2) private var fontSize: CGFloat = Design.Size.countBadgeFontSize
|
||||
@ScaledMetric(relativeTo: .caption2) private var paddingH: CGFloat = Design.Size.countBadgePaddingH
|
||||
@ScaledMetric(relativeTo: .caption2) private var paddingV: CGFloat = Design.Size.countBadgePaddingV
|
||||
@ScaledMetric(relativeTo: .caption2) private var offsetAmount: CGFloat = Design.Size.countBadgeOffset
|
||||
|
||||
var body: some View {
|
||||
Text(card.hiLoDisplayText)
|
||||
.font(.system(size: Design.Size.countBadgeFontSize, weight: .bold, design: .rounded))
|
||||
.font(.system(size: fontSize, weight: .bold, design: .rounded))
|
||||
.foregroundStyle(badgeTextColor)
|
||||
.padding(.horizontal, Design.Size.countBadgePaddingH)
|
||||
.padding(.vertical, Design.Size.countBadgePaddingV)
|
||||
.padding(.horizontal, paddingH)
|
||||
.padding(.vertical, paddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(badgeBackgroundColor)
|
||||
)
|
||||
.offset(x: -Design.Size.countBadgeOffset, y: Design.Size.countBadgeOffset)
|
||||
.offset(x: -offsetAmount, y: offsetAmount)
|
||||
}
|
||||
|
||||
private var badgeBackgroundColor: Color {
|
||||
|
||||
@ -14,17 +14,22 @@ import CasinoKit
|
||||
struct HintView: View {
|
||||
let hint: String
|
||||
|
||||
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.hintIconSize
|
||||
@ScaledMetric(relativeTo: .body) private var fontSize: CGFloat = Design.Size.hintFontSize
|
||||
@ScaledMetric(relativeTo: .body) private var paddingH: CGFloat = Design.Size.hintPaddingH
|
||||
@ScaledMetric(relativeTo: .body) private var paddingV: CGFloat = Design.Size.hintPaddingV
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Image(systemName: "lightbulb.fill")
|
||||
.font(.system(size: Design.Size.hintIconSize))
|
||||
.font(.system(size: iconSize))
|
||||
.foregroundStyle(.yellow)
|
||||
Text(String(localized: "Hint: \(hint)"))
|
||||
.font(.system(size: Design.Size.hintFontSize, weight: .medium))
|
||||
.font(.system(size: fontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.strong))
|
||||
}
|
||||
.padding(.horizontal, Design.Size.hintPaddingH)
|
||||
.padding(.vertical, Design.Size.hintPaddingV)
|
||||
.padding(.horizontal, paddingH)
|
||||
.padding(.vertical, paddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color.black.opacity(Design.Opacity.light))
|
||||
@ -42,6 +47,11 @@ struct BettingHintView: View {
|
||||
let hint: String
|
||||
let trueCount: Double
|
||||
|
||||
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.hintIconSize
|
||||
@ScaledMetric(relativeTo: .body) private var fontSize: CGFloat = Design.Size.hintFontSize
|
||||
@ScaledMetric(relativeTo: .body) private var paddingH: CGFloat = Design.Size.hintPaddingH
|
||||
@ScaledMetric(relativeTo: .body) private var paddingV: CGFloat = Design.Size.hintPaddingV
|
||||
|
||||
private var hintColor: Color {
|
||||
let tc = Int(trueCount.rounded())
|
||||
if tc >= 2 {
|
||||
@ -67,14 +77,14 @@ struct BettingHintView: View {
|
||||
var body: some View {
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: Design.Size.hintIconSize))
|
||||
.font(.system(size: iconSize))
|
||||
.foregroundStyle(hintColor)
|
||||
Text(hint)
|
||||
.font(.system(size: Design.Size.hintFontSize, weight: .medium))
|
||||
.font(.system(size: fontSize, weight: .medium))
|
||||
.foregroundStyle(.white.opacity(Design.Opacity.strong))
|
||||
}
|
||||
.padding(.horizontal, Design.Size.hintPaddingH)
|
||||
.padding(.vertical, Design.Size.hintPaddingV)
|
||||
.padding(.horizontal, paddingH)
|
||||
.padding(.vertical, paddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color.black.opacity(Design.Opacity.light))
|
||||
|
||||
@ -71,7 +71,7 @@ struct InsurancePopupView: View {
|
||||
Capsule()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [Color.Button.goldLight, Color.Button.goldDark],
|
||||
colors: [Color.CasinoButton.goldLight, Color.CasinoButton.goldDark],
|
||||
startPoint: .top,
|
||||
endPoint: .bottom
|
||||
)
|
||||
@ -84,7 +84,7 @@ struct InsurancePopupView: View {
|
||||
.padding(Design.Spacing.xLarge)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: Design.CornerRadius.xLarge)
|
||||
.fill(Color.Modal.background)
|
||||
.fill(Color.CasinoModal.backgroundDark)
|
||||
.shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusXLarge)
|
||||
)
|
||||
.overlay(
|
||||
|
||||
@ -25,7 +25,6 @@ struct PlayerHandsView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack(spacing: Design.Spacing.large) {
|
||||
@ -46,7 +45,9 @@ struct PlayerHandsView: View {
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Design.Spacing.large)
|
||||
.frame(minWidth: geometry.size.width)
|
||||
.containerRelativeFrame(.horizontal) { length, _ in
|
||||
length // Ensures content fills container width for centering
|
||||
}
|
||||
}
|
||||
.scrollClipDisabled()
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
@ -66,8 +67,6 @@ struct PlayerHandsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: Design.Size.playerHandsHeight)
|
||||
}
|
||||
|
||||
private func scrollToHand(proxy: ScrollViewProxy, index: Int) {
|
||||
withAnimation(.easeInOut(duration: Design.Animation.quick)) {
|
||||
@ -89,6 +88,9 @@ struct PlayerHandView: View {
|
||||
|
||||
@ScaledMetric(relativeTo: .headline) private var labelFontSize: CGFloat = Design.Size.handLabelFontSize
|
||||
@ScaledMetric(relativeTo: .caption) private var handNumberSize: CGFloat = Design.Size.handNumberFontSize
|
||||
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = Design.Size.handIconSize
|
||||
@ScaledMetric(relativeTo: .body) private var hintPaddingH: CGFloat = Design.Size.hintPaddingH
|
||||
@ScaledMetric(relativeTo: .body) private var hintPaddingV: CGFloat = Design.Size.hintPaddingV
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: Design.Spacing.small) {
|
||||
@ -126,8 +128,25 @@ struct PlayerHandView: View {
|
||||
)
|
||||
)
|
||||
)
|
||||
.overlay {
|
||||
// Result badge - centered on cards
|
||||
if let result = hand.result {
|
||||
Text(result.displayText)
|
||||
.font(.system(size: labelFontSize, weight: .black))
|
||||
.foregroundStyle(.white)
|
||||
.padding(.horizontal, hintPaddingH)
|
||||
.padding(.vertical, hintPaddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(result.color)
|
||||
.shadow(color: .black.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium)
|
||||
)
|
||||
.transition(.scale.combined(with: .opacity))
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.animation(.easeInOut(duration: Design.Animation.quick), value: isActive)
|
||||
.animation(.spring(duration: Design.Animation.springDuration), value: hand.result != nil)
|
||||
|
||||
// Hand info
|
||||
HStack(spacing: Design.Spacing.small) {
|
||||
@ -145,29 +164,16 @@ struct PlayerHandView: View {
|
||||
|
||||
if hand.isDoubledDown {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.font(.system(size: Design.Size.handIconSize))
|
||||
.font(.system(size: iconSize))
|
||||
.foregroundStyle(.purple)
|
||||
}
|
||||
}
|
||||
|
||||
// Result badge
|
||||
if let result = hand.result {
|
||||
Text(result.displayText)
|
||||
.font(.system(size: labelFontSize, weight: .black))
|
||||
.foregroundStyle(result.color)
|
||||
.padding(.horizontal, Design.Size.hintPaddingH)
|
||||
.padding(.vertical, Design.Size.hintPaddingV)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(result.color.opacity(Design.Opacity.hint))
|
||||
)
|
||||
}
|
||||
|
||||
// Bet amount
|
||||
if hand.bet > 0 {
|
||||
HStack(spacing: Design.Spacing.xSmall) {
|
||||
Image(systemName: "dollarsign.circle.fill")
|
||||
.font(.system(size: Design.Size.handIconSize))
|
||||
.font(.system(size: iconSize))
|
||||
.foregroundStyle(.yellow)
|
||||
Text("\(hand.bet * (hand.isDoubledDown ? 2 : 1))")
|
||||
.font(.system(size: handNumberSize, weight: .bold, design: .rounded))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user