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

This commit is contained in:
Matt Bruce 2025-12-31 13:29:45 -06:00
parent 43727534e6
commit 3be7fc5884
8 changed files with 1743 additions and 345 deletions

View File

@ -394,6 +394,7 @@ final class GameState: CasinoGameState {
self.engine = BaccaratEngine(deckCount: settings.deckCount.rawValue)
self.balance = settings.startingBalance
self.onboarding = OnboardingState(gameIdentifier: "baccarat")
self.onboarding.registerHintKeys("bettingZone", "dealButton", "firstResult")
self.persistence = CloudSyncManager<BaccaratGameData>()
// Sync sound settings with SoundManager

File diff suppressed because it is too large Load Diff

View File

@ -159,21 +159,17 @@ struct GameTableView: View {
description: String(localized: "Change table limits and display options")
)
],
onStartTutorial: {
showWelcome = false
state.onboarding.completeWelcome()
checkOnboardingHints()
},
onStartPlaying: {
// Mark all hints as shown FIRST so they don't appear
state.onboarding.markHintShown("bettingZone")
state.onboarding.markHintShown("dealButton")
state.onboarding.markHintShown("firstResult")
state.onboarding.completeWelcome()
showWelcome = false
}
onboarding: state.onboarding,
onDismiss: { showWelcome = false },
onShowHints: checkOnboardingHints
)
}
.onChange(of: showWelcome) { wasShowing, isShowing in
// Handle swipe-down dismissal: treat as "Start Playing" (no tooltips)
if wasShowing && !isShowing && !state.onboarding.hasCompletedWelcome {
state.onboarding.skipOnboarding()
}
}
.onChange(of: state.totalBetAmount) { _, newTotal in
if newTotal > 0, state.onboarding.shouldShowHint("dealButton") {
showDealHintWithDelay()

View File

@ -349,6 +349,7 @@ final class GameState: CasinoGameState {
self.balance = settings.startingBalance
self.engine = BlackjackEngine(settings: settings)
self.onboarding = OnboardingState(gameIdentifier: "blackjack")
self.onboarding.registerHintKeys("bettingZone", "dealButton", "playerActions")
self.persistence = CloudSyncManager<BlackjackGameData>()
syncSoundSettings()
loadSavedGame()

View File

@ -876,7 +876,26 @@
},
"ALL TIME SUMMARY" : {
"comment" : "Title for a section in the statistics sheet that provides a summary of the user's overall performance over all time.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "ALL TIME SUMMARY"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "RESUMEN HISTÓRICO"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "RÉSUMÉ GLOBAL"
}
}
}
},
"Allow doubling on split hands" : {
"localizations" : {
@ -1133,7 +1152,26 @@
},
"BALANCE" : {
"comment" : "Title of a section in the session detail view that shows the user's starting and ending balances.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "BALANCE"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "SALDO"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "SOLDE"
}
}
}
},
"Basic Strategy" : {
"localizations" : {
@ -1180,7 +1218,27 @@
}
},
"Beat the Dealer" : {
"comment" : "Welcome screen feature title for game objective.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Beat the Dealer"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vence al Crupier"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Battez le Croupier"
}
}
}
},
"Beat the dealer by getting a hand value closer to 21 without going over." : {
"comment" : "Text for the objective of the game.",
@ -1674,7 +1732,27 @@
}
},
"Built-in hints show optimal plays based on basic strategy" : {
"comment" : "Welcome screen feature description for strategy hints.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Built-in hints show optimal plays based on basic strategy"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Las sugerencias integradas muestran las jugadas óptimas basadas en estrategia básica"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Les conseils intégrés montrent les jeux optimaux basés sur la stratégie de base"
}
}
}
},
"BUST" : {
"localizations" : {
@ -1876,7 +1954,26 @@
},
"CHIPS STATS" : {
"comment" : "Title of a section in the Statistics Sheet that shows statistics related to the user's chips.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "CHIPS STATS"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "ESTADÍSTICAS DE FICHAS"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "STATISTIQUES DES JETONS"
}
}
}
},
"Chips, cards, and results" : {
"localizations" : {
@ -2222,7 +2319,27 @@
}
},
"Customize Rules" : {
"comment" : "Welcome screen feature title for customizing game rules.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Customize Rules"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Personaliza las Reglas"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Personnalisez les Règles"
}
}
}
},
"DATA" : {
"localizations" : {
@ -2638,7 +2755,26 @@
},
"Delete" : {
"comment" : "A button label that deletes a session.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Delete"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Eliminar"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Supprimer"
}
}
}
},
"Delete Session?" : {
@ -3048,7 +3184,26 @@
},
"Doubles" : {
"comment" : "Label for a stat item in the statistics UI that shows the number of times a hand was doubled down.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Doubles"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Doblados"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Doublés"
}
}
}
},
"Enable 'Card Count' in Settings to practice." : {
"localizations" : {
@ -3118,11 +3273,49 @@
},
"End Session" : {
"comment" : "The text for a button that ends the current game session.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "End Session"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Terminar Sesión"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Terminer la Session"
}
}
}
},
"End Session?" : {
"comment" : "A confirmation dialog title that asks if the user wants to end their current session.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "End Session?"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "¿Terminar Sesión?"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Terminer la Session?"
}
}
}
},
"European" : {
"localizations" : {
@ -3326,7 +3519,26 @@
},
"GAME STATS" : {
"comment" : "Title for a section in the statistics sheet dedicated to blackjack-specific statistics.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "GAME STATS"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "ESTADÍSTICAS DEL JUEGO"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "STATISTIQUES DE JEU"
}
}
}
},
"GAME STYLE" : {
"localizations" : {
@ -3447,7 +3659,27 @@
}
},
"Get closer to 21 than the dealer without going over" : {
"comment" : "Welcome screen feature description for game objective.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Get closer to 21 than the dealer without going over"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Acércate más a 21 que el crupier sin pasarte"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Approchez-vous de 21 plus que le croupier sans dépasser"
}
}
}
},
"H17 rule, increases house edge" : {
"localizations" : {
@ -3495,11 +3727,49 @@
},
"Hands" : {
"comment" : "Label for the number of blackjack hands played in a session.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hands"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Manos"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mains"
}
}
}
},
"Hands played" : {
"comment" : "A label describing the number of hands a player has played in a game.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Hands played"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Manos jugadas"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mains jouées"
}
}
}
},
"Haptic Feedback" : {
"localizations" : {
@ -3894,7 +4164,26 @@
},
"IN GAME STATS" : {
"comment" : "Title of a section in the Statistics Sheet that shows in-game statistics.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "IN GAME STATS"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "ESTADÍSTICAS EN JUEGO"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "STATISTIQUES EN JEU"
}
}
}
},
"Increase bets when the count is positive." : {
"localizations" : {
@ -4167,7 +4456,27 @@
}
},
"Learn Strategy" : {
"comment" : "Welcome screen feature title for strategy hints.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Learn Strategy"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aprende Estrategia"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Apprenez la Stratégie"
}
}
}
},
"LEGAL" : {
"localizations" : {
@ -4238,7 +4547,26 @@
},
"Lost" : {
"comment" : "Label for a game outcome circle indicating a loss.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Lost"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Perdido"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Perdu"
}
}
}
},
"Lower house edge" : {
"comment" : "Description of a deck count option when the user selects 2 decks.",
@ -5088,7 +5416,26 @@
},
"PLAYER" : {
"comment" : "Title to display for a player hand when the hand number is not available.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "PLAYER"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "JUGADOR"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "JOUEUR"
}
}
}
},
"Player hand: %@. Value: %@" : {
"comment" : "A user-readable string describing a player's blackjack hand, including the card values and any relevant game results. The argument is a comma-separated list of the card descriptions in the player's hand.",
@ -5206,7 +5553,26 @@
},
"Push" : {
"comment" : "Label for the \"Push\" outcome in the game stats section of the statistics sheet.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Push"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Empate"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Égalité"
}
}
}
},
"PUSH" : {
"localizations" : {
@ -5619,14 +5985,72 @@
},
"See detailed stats for each play session, just like at a real casino" : {
"comment" : "Description of a feature in the welcome sheet that allows users to track their gaming sessions.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "See detailed stats for each play session, just like at a real casino"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ve estadísticas detalladas de cada sesión de juego, como en un casino real"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Consultez les statistiques détaillées de chaque session de jeu, comme dans un vrai casino"
}
}
}
},
"Select a chip and tap the bet area" : {
"comment" : "Onboarding hint for placing bets.",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Select a chip and tap the bet area"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Selecciona una ficha y toca el área de apuesta"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sélectionnez un jeton et touchez la zone de mise"
}
}
}
},
"SESSION PERFORMANCE" : {
"comment" : "Title of a section in the statistics sheet that shows performance metrics for individual sessions.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "SESSION PERFORMANCE"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "RENDIMIENTO DE SESIÓN"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "PERFORMANCE DE SESSION"
}
}
}
},
"SESSION SUMMARY" : {
"extractionState" : "stale",
@ -5653,7 +6077,26 @@
},
"Sessions" : {
"comment" : "Label for the number of blackjack game sessions.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sessions"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sesiones"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sessions"
}
}
}
},
"Settings" : {
"localizations" : {
@ -5817,7 +6260,26 @@
},
"Show Hint" : {
"comment" : "Label for a toolbar button that shows a hint.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Show Hint"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mostrar Sugerencia"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Afficher le Conseil"
}
}
}
},
"Show Hints" : {
"localizations" : {
@ -6139,7 +6601,26 @@
},
"Splits" : {
"comment" : "Label for the number of split hands in the statistics UI.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Splits"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Divisiones"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Séparations"
}
}
}
},
"Stand" : {
"localizations" : {
@ -6806,11 +7287,49 @@
},
"Time" : {
"comment" : "Label for the duration of a blackjack game.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Time"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tiempo"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Temps"
}
}
}
},
"Total game time" : {
"comment" : "Label for a stat row displaying the total game time.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Total game time"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tiempo total de juego"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Temps de jeu total"
}
}
}
},
"Total Winnings" : {
"localizations" : {
@ -6836,7 +7355,26 @@
},
"Track Sessions" : {
"comment" : "Feature description in the welcome sheet for tracking detailed stats for each play session.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Track Sessions"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Seguimiento de Sesiones"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suivez vos Sessions"
}
}
}
},
"Traditional European casino style." : {
"localizations" : {
@ -6998,7 +7536,26 @@
},
"Vegas Strip, Atlantic City, European, or create your own" : {
"comment" : "Feature description in the welcome sheet about customizing the game rules.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vegas Strip, Atlantic City, European, or create your own"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vegas Strip, Atlantic City, Europeo o crea el tuyo"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vegas Strip, Atlantic City, Européen ou créez le vôtre"
}
}
}
},
"Vibration on actions" : {
"localizations" : {
@ -7115,7 +7672,26 @@
},
"Won" : {
"comment" : "Label for a game outcome circle that indicates a win.",
"isCommentAutoGenerated" : true
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Won"
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ganado"
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Gagné"
}
}
}
},
"Worst" : {
"extractionState" : "stale",
@ -7164,13 +7740,24 @@
},
"You played %lld hands with a net result of %@. This session will be saved to your history." : {
"comment" : "A message that appears when a user ends a game session. It includes the number of hands played and the net result of the session.",
"isCommentAutoGenerated" : true,
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "new",
"state" : "translated",
"value" : "You played %1$lld hands with a net result of %2$@. This session will be saved to your history."
}
},
"es-MX" : {
"stringUnit" : {
"state" : "translated",
"value" : "Jugaste %1$lld manos con un resultado neto de %2$@. Esta sesión se guardará en tu historial."
}
},
"fr-CA" : {
"stringUnit" : {
"state" : "translated",
"value" : "Vous avez joué %1$lld mains avec un résultat net de %2$@. Cette session sera sauvegardée dans votre historique."
}
}
}
},

View File

@ -109,21 +109,17 @@ struct GameTableView: View {
description: String(localized: "Vegas Strip, Atlantic City, European, or create your own")
)
],
onStartTutorial: {
showWelcome = false
state.onboarding.completeWelcome()
checkOnboardingHints()
},
onStartPlaying: {
// Mark all hints as shown FIRST so they don't appear
state.onboarding.markHintShown("bettingZone")
state.onboarding.markHintShown("dealButton")
state.onboarding.markHintShown("playerActions")
state.onboarding.completeWelcome()
showWelcome = false
}
onboarding: state.onboarding,
onDismiss: { showWelcome = false },
onShowHints: checkOnboardingHints
)
}
.onChange(of: showWelcome) { wasShowing, isShowing in
// Handle swipe-down dismissal: treat as "Start Playing" (no tooltips)
if wasShowing && !isShowing && !state.onboarding.hasCompletedWelcome {
state.onboarding.skipOnboarding()
}
}
.onChange(of: state.currentBet) { _, newBet in
if newBet > 0, state.onboarding.shouldShowHint("dealButton") {
showDealHintWithDelay()

View File

@ -26,6 +26,10 @@ public final class OnboardingState {
/// Set of hint keys that have been shown to the user.
public var hintsShown: Set<String> = []
/// Hint keys registered by the app for automatic skipping.
/// When the user skips onboarding, all registered hints are marked as shown.
private var registeredHintKeys: Set<String> = []
// MARK: - Initialization
private let persistenceKey: String
@ -35,6 +39,18 @@ public final class OnboardingState {
load()
}
/// Registers hint keys that should be marked as shown when skipping onboarding.
/// Call this once during app setup with all hint keys used by the game.
public func registerHintKeys(_ keys: Set<String>) {
registeredHintKeys = keys
}
/// Registers hint keys that should be marked as shown when skipping onboarding.
/// Call this once during app setup with all hint keys used by the game.
public func registerHintKeys(_ keys: String...) {
registeredHintKeys = Set(keys)
}
// MARK: - Hint Management
/// Marks a hint as shown and persists the state.
@ -55,6 +71,18 @@ public final class OnboardingState {
save()
}
/// Skips onboarding entirely - marks all registered hints as shown and completes welcome.
/// Use this when the user dismisses the welcome sheet without choosing tutorial mode
/// (e.g., swiping down to dismiss, or tapping "Start Playing").
public func skipOnboarding() {
for key in registeredHintKeys {
hintsShown.insert(key)
}
hasLaunchedBefore = true
hasCompletedWelcome = true
save()
}
/// Enables tutorial mode (shows all hints again).
public func startTutorialMode() {
isTutorialMode = true

View File

@ -21,6 +21,42 @@ public struct WelcomeSheet: View {
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = CasinoDesign.IconSize.large
@ScaledMetric(relativeTo: .body) private var buttonPadding: CGFloat = CasinoDesign.Spacing.medium
/// Creates a welcome sheet with automatic onboarding state management.
///
/// This initializer handles the common pattern of:
/// - "Show Me How" completes welcome and triggers hint display
/// - "Start Playing" skips all hints and completes welcome
///
/// - Parameters:
/// - gameName: The name of the game to display
/// - gameEmoji: An emoji representing the game
/// - features: List of features to highlight
/// - onboarding: The onboarding state to manage (must have hint keys registered)
/// - onDismiss: Called after the sheet is dismissed
/// - onShowHints: Called when user chooses "Show Me How" - use this to trigger tooltip display
public init(
gameName: String,
gameEmoji: String = "🎰",
features: [WelcomeFeature],
onboarding: OnboardingState,
onDismiss: @escaping () -> Void,
onShowHints: @escaping () -> Void
) {
self.gameName = gameName
self.gameEmoji = gameEmoji
self.features = features
self.onStartTutorial = {
onboarding.completeWelcome()
onDismiss()
onShowHints()
}
self.onStartPlaying = {
onboarding.skipOnboarding()
onDismiss()
}
}
/// Creates a welcome sheet with custom callbacks for full control.
public init(
gameName: String,
gameEmoji: String = "🎰",