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

This commit is contained in:
Matt Bruce 2025-12-23 16:23:47 -06:00
parent be9fc77605
commit 1df1796b12
6 changed files with 357 additions and 35 deletions

View File

@ -53,9 +53,21 @@
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"state" : "translated",
"value" : "%1$@ bet, pays up to %2$@"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Apuesta %1$@, paga hasta %2$@"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pari %1$@, paie jusqu'à %2$@"
}
}
}
},
@ -633,7 +645,27 @@
},
"21+3" : {
"comment" : "Description of the 21+3 side bet type.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "21+3"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "21+3"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "21+3"
}
}
}
},
"21+3: Poker hand from your cards + dealer's upcard." : {
@ -1814,7 +1846,27 @@
},
"Colored Pair" : {
"comment" : "Description of a Perfect Pairs side bet result when the first two cards form a pair of the same rank but different suit.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Colored Pair"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Par de Color"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paire Colorée"
}
}
}
},
"Colored Pair (same color): 12:1" : {
@ -2827,7 +2879,26 @@
},
"Enable Side Bets" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Enable Side Bets"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Activar Apuestas Laterales"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Activer les Paris Annexes"
}
}
}
},
"European" : {
"localizations" : {
@ -2906,7 +2977,27 @@
},
"Flush" : {
"comment" : "Description of a 21+3 side bet outcome when the user has a flush.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Flush"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Color"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Couleur"
}
}
}
},
"Flush (same suit): 5:1" : {
@ -3918,7 +4009,27 @@
},
"MAX" : {
"comment" : "The text displayed in the center of the chip badge when it represents the maximum bet.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "MAX"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "MÁX"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "MAX"
}
}
}
},
"Max: $%@" : {
"comment" : "A label displaying the maximum bet amount. The argument is the maximum bet amount.",
@ -3992,7 +4103,27 @@
},
"Mixed Pair" : {
"comment" : "Description of a Perfect Pairs side bet outcome when the first two cards have the same rank but different color.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mixed Pair"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Par Mixto"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paire Mixte"
}
}
}
},
"Mixed Pair (diff. color): 6:1" : {
@ -4131,7 +4262,27 @@
},
"No Hand" : {
"comment" : "Description of a 21+3 side bet outcome when there is no qualifying hand.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No Hand"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sin Mano"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pas de Main"
}
}
}
},
"No hole card, dealer stands on soft 17, no surrender" : {
"localizations" : {
@ -4178,8 +4329,27 @@
}
},
"No Pair" : {
"comment" : "Description of a 21+3 side bet outcome when there is no qualifying hand.",
"isCommentAutoGenerated" : true
"comment" : "Description of a Perfect Pairs side bet outcome when there is no pair.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No Pair"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sin Par"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pas de Paire"
}
}
}
},
"No surrender option." : {
"localizations" : {
@ -4440,14 +4610,54 @@
},
"Perfect Pair" : {
"comment" : "Name of the side bet outcome where the first two cards form a perfect pair.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Perfect Pair"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Par Perfecto"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paire Parfaite"
}
}
}
},
"Perfect Pair (same suit): 25:1" : {
},
"Perfect Pairs" : {
"comment" : "Name of the Perfect Pairs side bet type.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Perfect Pairs"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pares Perfectos"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paires Parfaites"
}
}
}
},
"Perfect Pairs (25:1) and 21+3 (100:1)" : {
@ -5136,7 +5346,26 @@
}
},
"Side Bets" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Side Bets"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Apuestas Laterales"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Paris Annexes"
}
}
}
},
"SIDE BETS" : {
@ -5604,21 +5833,81 @@
},
"Straight" : {
"comment" : "Name of a 21+3 result when the user has three consecutive ranks.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Straight"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Escalera"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suite"
}
}
}
},
"Straight (consecutive): 10:1" : {
},
"Straight Flush" : {
"comment" : "Description of a 21+3 side bet outcome when the player has a straight flush.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Straight Flush"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Escalera de Color"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Quinte Flush"
}
}
}
},
"Straight Flush: 40:1" : {
},
"Suited Trips" : {
"comment" : "Name of a 21+3 side bet outcome when the user has three cards of the same rank and suit.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suited Trips"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Trío del Mismo Palo"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Brelan Assorti"
}
}
}
},
"Suited Trips (same rank & suit): 100:1" : {
@ -5917,7 +6206,27 @@
},
"Three of a Kind" : {
"comment" : "Description of a 21+3 side bet outcome when they have three of a kind.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Three of a Kind"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Trío"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Brelan"
}
}
}
},
"Three of a Kind: 30:1" : {

View File

@ -126,4 +126,13 @@ extension Color {
enum TopBar {
static let balance = Color(red: 0.95, green: 0.85, blue: 0.4)
}
// MARK: - Side Bet Colors
enum SideBet {
/// Perfect Pairs - purple theme
static let perfectPairs = Color(red: 0.4, green: 0.2, blue: 0.5)
/// 21+3 - teal/cyan theme
static let twentyOnePlusThree = Color(red: 0.1, green: 0.4, blue: 0.5)
}
}

View File

@ -70,7 +70,10 @@ struct SideBetToastView: View {
.shadow(color: borderColor.opacity(Design.Opacity.medium), radius: Design.Shadow.radiusMedium)
.scaleEffect(isShowing ? 1.0 : 0.5)
.opacity(isShowing ? 1.0 : 0)
.offset(x: isShowing ? 0 : (showOnLeft ? -50 : 50))
.offset(x: isShowing ? 0 : (showOnLeft ? -Design.Spacing.toastSlide : Design.Spacing.toastSlide))
.accessibilityElement(children: .combine)
.accessibilityLabel("\(title): \(result)")
.accessibilityValue(amountText)
.onAppear {
withAnimation(.spring(duration: Design.Animation.springDuration, bounce: 0.4).delay(showOnLeft ? 0 : Design.Animation.staggerDelay1)) {
isShowing = true

View File

@ -100,7 +100,7 @@ struct ChipBadgeView: View {
.frame(width: CasinoDesign.Size.chipBadgeInner, height: CasinoDesign.Size.chipBadgeInner)
if isMax {
Text("MAX")
Text(String(localized: "MAX"))
.font(.system(size: Design.BaseFontSize.xxSmall, weight: .black))
.foregroundStyle(.white)
} else {
@ -120,17 +120,6 @@ struct ChipBadgeView: View {
}
}
// MARK: - Side Bet Colors
extension Color {
enum SideBet {
/// Perfect Pairs - purple theme
static let perfectPairs = Color(red: 0.4, green: 0.2, blue: 0.5)
/// 21+3 - teal/cyan theme
static let twentyOnePlusThree = Color(red: 0.1, green: 0.4, blue: 0.5)
}
}
// MARK: - Previews
#Preview("Perfect Pairs") {

View File

@ -99,7 +99,7 @@ public final class SoundManager {
}
}
/// Master volume (0.0 to 1.0).
/// Master volume (0.0 to 1.0). This is the linear slider value.
public var volume: Float = 1.0 {
didSet {
UserDefaults.standard.set(volume, forKey: "casinokit.soundVolume")
@ -107,6 +107,15 @@ public final class SoundManager {
}
}
/// Perceived volume using an exponential curve.
/// Human hearing is logarithmic, so linear volume feels wrong.
/// This curve makes 50% on the slider sound like 50% to human ears.
private var perceivedVolume: Float {
// Using a power of 3 gives a natural-feeling curve
// 0.0 -> 0.0, 0.5 -> 0.125, 1.0 -> 1.0
pow(volume, 3)
}
/// The bundle to load sound files from. Defaults to CasinoKit's bundle.
/// Set this to `.main` if sounds are in your app bundle instead.
public var soundBundle: Bundle = .module
@ -166,7 +175,7 @@ public final class SoundManager {
do {
let player = try AVAudioPlayer(contentsOf: url)
player.prepareToPlay()
player.volume = volume
player.volume = perceivedVolume
return player
} catch {
print("CasinoKit: Failed to create audio player for \(sound.rawValue): \(error)")
@ -193,8 +202,8 @@ public final class SoundManager {
/// Plays a system sound by ID.
private func playSystemSound(_ soundID: SystemSoundID) {
// Only play if volume is above threshold
guard volume > 0.1 else { return }
// Only play if volume is above threshold (system sounds can't be volume-adjusted)
guard perceivedVolume > 0.01 else { return }
AudioServicesPlaySystemSound(soundID)
}
@ -223,7 +232,7 @@ public final class SoundManager {
private func updatePlayerVolumes() {
for player in audioPlayers.values {
player.volume = volume
player.volume = perceivedVolume
}
}

View File

@ -22,6 +22,9 @@ public enum CasinoDesign {
public static let xLarge: CGFloat = 20
public static let xxLarge: CGFloat = 24
public static let xxxLarge: CGFloat = 32
/// Slide distance for toast animations
public static let toastSlide: CGFloat = 50
}
// MARK: - Corner Radius