Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
55855e9141
commit
045a705d83
694
BRANDING_IMPLEMENTATION_GUIDE.md
Normal file
694
BRANDING_IMPLEMENTATION_GUIDE.md
Normal file
@ -0,0 +1,694 @@
|
|||||||
|
# Branding Implementation Guide
|
||||||
|
|
||||||
|
A step-by-step guide to implementing the CasinoKit branding system (app icon and launch screen) in your casino game app.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Overview](#overview)
|
||||||
|
2. [Prerequisites](#prerequisites)
|
||||||
|
3. [Step 1: Copy Branding Files](#step-1-copy-branding-files)
|
||||||
|
4. [Step 2: Create BrandingConfig.swift](#step-2-create-brandingconfigswift)
|
||||||
|
5. [Step 3: Add Launch Screen to App Entry Point](#step-3-add-launch-screen-to-app-entry-point)
|
||||||
|
6. [Step 4: Add Branding Tools to Settings (Optional)](#step-4-add-branding-tools-to-settings-optional)
|
||||||
|
7. [Step 5: Generate Your App Icon](#step-5-generate-your-app-icon)
|
||||||
|
8. [Step 6: Add Icon to Xcode Assets](#step-6-add-icon-to-xcode-assets)
|
||||||
|
9. [Complete Example](#complete-example)
|
||||||
|
10. [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The CasinoKit branding system provides:
|
||||||
|
|
||||||
|
- **AppIconView**: A customizable icon design with gradient backgrounds, SF Symbols, and text
|
||||||
|
- **LaunchScreenView**: An animated launch screen that matches your icon
|
||||||
|
- **AppLaunchView**: A wrapper that seamlessly transitions from launch to app
|
||||||
|
- **IconGeneratorView**: A development tool to generate and export icon images
|
||||||
|
- **BrandingPreviewView**: A preview tool to see icons and launch screens side-by-side
|
||||||
|
|
||||||
|
**Key Files in CasinoKit:**
|
||||||
|
- `AppIconView.swift` - Icon design view
|
||||||
|
- `LaunchScreenView.swift` - Launch screen design view
|
||||||
|
- `AppLaunchView.swift` - Launch wrapper with animation
|
||||||
|
- `IconGeneratorView.swift` - Icon export tool
|
||||||
|
- `IconRenderer.swift` - Rendering utilities
|
||||||
|
- `BrandingPreviewView.swift` - Preview tool
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### If Using CasinoKit Package
|
||||||
|
|
||||||
|
Your app must have `CasinoKit` as a dependency. No additional files needed—everything is available through `import CasinoKit`.
|
||||||
|
|
||||||
|
### If You Copied the Branding Folder
|
||||||
|
|
||||||
|
If you manually copied the branding views from CasinoKit into your app, ensure you have:
|
||||||
|
|
||||||
|
1. All 6 files from `CasinoKit/Sources/CasinoKit/Views/Branding/`:
|
||||||
|
- `AppIconView.swift`
|
||||||
|
- `LaunchScreenView.swift`
|
||||||
|
- `AppLaunchView.swift`
|
||||||
|
- `IconGeneratorView.swift`
|
||||||
|
- `IconRenderer.swift`
|
||||||
|
- `BrandingPreviewView.swift`
|
||||||
|
|
||||||
|
2. Dependencies these files require:
|
||||||
|
- `DiamondPatternView.swift` (used by `AppIconView`)
|
||||||
|
- `DebugSection.swift` (if using debug tools)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Copy Branding Files
|
||||||
|
|
||||||
|
**Skip this step if using CasinoKit as a package dependency.**
|
||||||
|
|
||||||
|
If copying manually, create a `Theme/` folder in your app target and copy all branding files:
|
||||||
|
|
||||||
|
```
|
||||||
|
YourApp/
|
||||||
|
YourApp/
|
||||||
|
Theme/
|
||||||
|
AppIconView.swift
|
||||||
|
LaunchScreenView.swift
|
||||||
|
AppLaunchView.swift
|
||||||
|
IconGeneratorView.swift
|
||||||
|
IconRenderer.swift
|
||||||
|
BrandingPreviewView.swift
|
||||||
|
BrandingConfig.swift ← You'll create this in Step 2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** Ensure you also have `DiamondPatternView.swift` if `AppIconView` references it, or copy that pattern code inline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Create BrandingConfig.swift
|
||||||
|
|
||||||
|
Create a new Swift file in your app's `Theme/` folder called `BrandingConfig.swift`. This file defines your game's branding.
|
||||||
|
|
||||||
|
### Template
|
||||||
|
|
||||||
|
```swift
|
||||||
|
//
|
||||||
|
// BrandingConfig.swift
|
||||||
|
// YourGame
|
||||||
|
//
|
||||||
|
// App-specific branding configurations for icons and launch screens.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit // Remove this line if not using CasinoKit package
|
||||||
|
|
||||||
|
// MARK: - App Icon Configuration
|
||||||
|
|
||||||
|
extension AppIconConfig {
|
||||||
|
/// YourGame app icon configuration.
|
||||||
|
static let yourGame = AppIconConfig(
|
||||||
|
title: "YOUR GAME", // App name (uppercase looks best)
|
||||||
|
subtitle: "21", // Optional: number or short text below icon
|
||||||
|
iconSymbol: "suit.club.fill", // SF Symbol for the main icon
|
||||||
|
primaryColor: Color(red: 0.1, green: 0.2, blue: 0.35), // Top gradient color
|
||||||
|
secondaryColor: Color(red: 0.05, green: 0.12, blue: 0.25), // Bottom gradient color
|
||||||
|
accentColor: .yellow // Color for icon and text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Launch Screen Configuration
|
||||||
|
|
||||||
|
extension LaunchScreenConfig {
|
||||||
|
/// YourGame launch screen configuration.
|
||||||
|
static let yourGame = LaunchScreenConfig(
|
||||||
|
title: "YOUR GAME", // App name (uppercase looks best)
|
||||||
|
subtitle: "21", // Optional: appears below icon symbols
|
||||||
|
tagline: "Beat the Dealer", // Optional: tagline at bottom
|
||||||
|
iconSymbols: [ // 1-3 SF Symbols for the launch logo
|
||||||
|
"suit.club.fill",
|
||||||
|
"suit.diamond.fill"
|
||||||
|
],
|
||||||
|
primaryColor: Color(red: 0.1, green: 0.2, blue: 0.35), // Top gradient color
|
||||||
|
secondaryColor: Color(red: 0.05, green: 0.12, blue: 0.25), // Bottom gradient color
|
||||||
|
accentColor: .yellow // Color for text and accents
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customization Tips
|
||||||
|
|
||||||
|
**Title:**
|
||||||
|
- Use uppercase for a casino aesthetic
|
||||||
|
- Keep it concise (6-9 characters max)
|
||||||
|
- The view automatically scales longer titles
|
||||||
|
|
||||||
|
**Subtitle:**
|
||||||
|
- Optional, best for short numbers or text ("21", "DELUXE", etc.)
|
||||||
|
- Appears larger and more prominent than the title
|
||||||
|
|
||||||
|
**Icon Symbol:**
|
||||||
|
- Use SF Symbols names (e.g., `"suit.spade.fill"`, `"diamond.fill"`)
|
||||||
|
- Browse available symbols in Xcode: Editor → Insert SF Symbol
|
||||||
|
- Card suits work great: `suit.spade.fill`, `suit.heart.fill`, `suit.diamond.fill`, `suit.club.fill`
|
||||||
|
|
||||||
|
**Colors:**
|
||||||
|
- Use `Color(red:green:blue:)` with values 0.0-1.0
|
||||||
|
- Primary color = top of gradient
|
||||||
|
- Secondary color = bottom of gradient (usually darker)
|
||||||
|
- Accent color = icon symbol and text highlights
|
||||||
|
|
||||||
|
**Launch Screen Icon Symbols:**
|
||||||
|
- Use 1-3 symbols for visual variety
|
||||||
|
- Hearts and diamonds automatically render red
|
||||||
|
- Spades and clubs render white
|
||||||
|
|
||||||
|
### Real Examples
|
||||||
|
|
||||||
|
**Blackjack:**
|
||||||
|
```swift
|
||||||
|
extension AppIconConfig {
|
||||||
|
static let blackjack = AppIconConfig(
|
||||||
|
title: "BLACKJACK",
|
||||||
|
subtitle: "21",
|
||||||
|
iconSymbol: "suit.club.fill",
|
||||||
|
primaryColor: Color(red: 0.05, green: 0.35, blue: 0.15), // Green felt
|
||||||
|
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1),
|
||||||
|
accentColor: .yellow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LaunchScreenConfig {
|
||||||
|
static let blackjack = LaunchScreenConfig(
|
||||||
|
title: "BLACKJACK",
|
||||||
|
subtitle: "21",
|
||||||
|
tagline: "Beat the Dealer",
|
||||||
|
iconSymbols: ["suit.club.fill", "suit.diamond.fill"],
|
||||||
|
primaryColor: Color(red: 0.05, green: 0.35, blue: 0.15),
|
||||||
|
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1),
|
||||||
|
accentColor: .yellow
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Baccarat:**
|
||||||
|
```swift
|
||||||
|
extension AppIconConfig {
|
||||||
|
static let baccarat = AppIconConfig(
|
||||||
|
title: "BACCARAT",
|
||||||
|
iconSymbol: "suit.spade.fill",
|
||||||
|
primaryColor: Color(red: 0.1, green: 0.2, blue: 0.35), // Blue elegant
|
||||||
|
secondaryColor: Color(red: 0.05, green: 0.12, blue: 0.25)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LaunchScreenConfig {
|
||||||
|
static let baccarat = LaunchScreenConfig(
|
||||||
|
title: "BACCARAT",
|
||||||
|
tagline: "The Classic Casino Card Game",
|
||||||
|
iconSymbols: ["suit.spade.fill", "suit.heart.fill"]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Add Launch Screen to App Entry Point
|
||||||
|
|
||||||
|
Update your `@main` App struct to wrap your content with `AppLaunchView`.
|
||||||
|
|
||||||
|
### Before
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@main
|
||||||
|
struct YourGameApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### After
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit // If using the package
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct YourGameApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
AppLaunchView(config: .yourGame) {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**What this does:**
|
||||||
|
- Shows an animated launch screen for ~2 seconds
|
||||||
|
- Fades smoothly into your main content
|
||||||
|
- Creates a polished, professional app opening experience
|
||||||
|
|
||||||
|
**Note:** Replace `.yourGame` with the static property name you defined in `BrandingConfig.swift` (e.g., `.blackjack`, `.poker`, `.roulette`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Add Branding Tools to Settings (Optional)
|
||||||
|
|
||||||
|
Add debug tools to your settings view so you can easily generate and preview icons during development.
|
||||||
|
|
||||||
|
### If Using CasinoKit Package
|
||||||
|
|
||||||
|
Add this to your settings view inside a `#if DEBUG` block:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
#if DEBUG
|
||||||
|
SheetSection(title: "DEBUG", icon: "ant.fill") {
|
||||||
|
BrandingDebugRows(
|
||||||
|
iconConfig: .yourGame,
|
||||||
|
launchConfig: .yourGame,
|
||||||
|
appName: "YourGame"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full Example in Settings
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
SheetContainerView(
|
||||||
|
title: "Settings",
|
||||||
|
content: {
|
||||||
|
// ... your normal settings sections ...
|
||||||
|
|
||||||
|
// DEBUG section at the bottom
|
||||||
|
#if DEBUG
|
||||||
|
SheetSection(title: "DEBUG", icon: "ant.fill") {
|
||||||
|
BrandingDebugRows(
|
||||||
|
iconConfig: .yourGame,
|
||||||
|
launchConfig: .yourGame,
|
||||||
|
appName: "YourGame"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
},
|
||||||
|
onCancel: nil,
|
||||||
|
onDone: {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**What this adds:**
|
||||||
|
- **Icon Generator** button: Generates and saves a 1024px PNG to the Files app
|
||||||
|
- **Branding Preview** button: Shows a live preview of your icon and launch screen
|
||||||
|
|
||||||
|
**Important:** This only appears in DEBUG builds and will automatically be excluded from App Store releases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Generate Your App Icon
|
||||||
|
|
||||||
|
Now it's time to create your actual app icon image.
|
||||||
|
|
||||||
|
### Method 1: Using IconGeneratorView (Recommended)
|
||||||
|
|
||||||
|
1. **Build and run your app** (simulator or device)
|
||||||
|
2. **Open Settings** in your app
|
||||||
|
3. **Scroll to the DEBUG section** (only visible in DEBUG builds)
|
||||||
|
4. **Tap "Icon Generator"**
|
||||||
|
5. **Tap "Generate & Save Icon"**
|
||||||
|
6. **Wait for confirmation**: "✅ Icon saved to Documents folder!"
|
||||||
|
|
||||||
|
The icon is now saved as `AppIcon.png` (1024×1024) in your app's Documents folder.
|
||||||
|
|
||||||
|
### Method 2: Using Xcode Previews
|
||||||
|
|
||||||
|
1. Open `BrandingConfig.swift` in Xcode
|
||||||
|
2. Add a preview at the bottom:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
#Preview("App Icon") {
|
||||||
|
AppIconView(config: .yourGame, size: 512)
|
||||||
|
.clipShape(.rect(cornerRadius: 512 * 0.22))
|
||||||
|
.padding()
|
||||||
|
.background(Color.gray)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Open the Canvas (Editor → Canvas)
|
||||||
|
4. Take a screenshot of the preview
|
||||||
|
5. Crop and scale to 1024×1024 in an image editor
|
||||||
|
|
||||||
|
### Method 3: Programmatic Export
|
||||||
|
|
||||||
|
Create a temporary view or function:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@MainActor
|
||||||
|
func exportIcon() {
|
||||||
|
let config = AppIconConfig.yourGame
|
||||||
|
let view = AppIconView(config: config, size: 1024)
|
||||||
|
let renderer = ImageRenderer(content: view)
|
||||||
|
renderer.scale = 1.0
|
||||||
|
|
||||||
|
if let image = renderer.uiImage,
|
||||||
|
let data = image.pngData() {
|
||||||
|
let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||||
|
.appending(path: "AppIcon.png")
|
||||||
|
try? data.write(to: url)
|
||||||
|
print("Icon saved to: \(url.path)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Add Icon to Xcode Assets
|
||||||
|
|
||||||
|
### Retrieve the Icon from Device/Simulator
|
||||||
|
|
||||||
|
**On Simulator:**
|
||||||
|
1. Open **Finder**
|
||||||
|
2. Go to: `~/Library/Developer/CoreSimulator/Devices/`
|
||||||
|
3. Find your simulator device folder
|
||||||
|
4. Navigate to: `data/Containers/Data/Application/[YourApp]/Documents/`
|
||||||
|
5. Copy `AppIcon.png` to your Mac desktop
|
||||||
|
|
||||||
|
**On Physical Device:**
|
||||||
|
1. Open the **Files** app on your device
|
||||||
|
2. Navigate to: **On My iPhone** → **YourGame**
|
||||||
|
3. Find `AppIcon.png`
|
||||||
|
4. **AirDrop** or **share** it to your Mac
|
||||||
|
|
||||||
|
**Alternative (Xcode):**
|
||||||
|
1. In Xcode, go to **Window** → **Devices and Simulators**
|
||||||
|
2. Select your device/simulator
|
||||||
|
3. Find your app in the Installed Apps list
|
||||||
|
4. Click the **⚙️ gear** icon → **Download Container**
|
||||||
|
5. Right-click the downloaded `.xcappdata` file → **Show Package Contents**
|
||||||
|
6. Navigate to `AppData/Documents/` and copy `AppIcon.png`
|
||||||
|
|
||||||
|
### Add to Xcode Assets
|
||||||
|
|
||||||
|
1. **Open your Xcode project**
|
||||||
|
2. **Navigate to** `Assets.xcassets` in the Project Navigator
|
||||||
|
3. **Click on AppIcon** (the app icon asset)
|
||||||
|
4. **Drag `AppIcon.png`** from Finder into the **1024×1024** slot (labeled "iOS App Store")
|
||||||
|
5. **Xcode automatically generates** all required sizes from this single image
|
||||||
|
|
||||||
|
**Important:** Modern iOS projects only need the 1024×1024 image. Xcode generates all other sizes automatically.
|
||||||
|
|
||||||
|
### Verify the Icon
|
||||||
|
|
||||||
|
1. **Build and run** your app
|
||||||
|
2. **Press the Home button** (or swipe up)
|
||||||
|
3. Check that your new icon appears on the home screen
|
||||||
|
4. If it doesn't update immediately, **delete the app** and reinstall
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
Here's a full example for a Poker game:
|
||||||
|
|
||||||
|
### File: `Poker/Theme/BrandingConfig.swift`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
//
|
||||||
|
// BrandingConfig.swift
|
||||||
|
// Poker
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
extension AppIconConfig {
|
||||||
|
static let poker = AppIconConfig(
|
||||||
|
title: "POKER",
|
||||||
|
iconSymbol: "suit.diamond.fill",
|
||||||
|
primaryColor: Color(red: 0.2, green: 0.05, blue: 0.1),
|
||||||
|
secondaryColor: Color(red: 0.1, green: 0.02, blue: 0.05),
|
||||||
|
accentColor: Color(red: 1.0, green: 0.85, blue: 0.3) // Gold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension LaunchScreenConfig {
|
||||||
|
static let poker = LaunchScreenConfig(
|
||||||
|
title: "POKER",
|
||||||
|
tagline: "All In",
|
||||||
|
iconSymbols: ["suit.diamond.fill", "suit.heart.fill", "suit.club.fill"],
|
||||||
|
primaryColor: Color(red: 0.2, green: 0.05, blue: 0.1),
|
||||||
|
secondaryColor: Color(red: 0.1, green: 0.02, blue: 0.05),
|
||||||
|
accentColor: Color(red: 1.0, green: 0.85, blue: 0.3)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File: `Poker/PokerApp.swift`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct PokerApp: App {
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
AppLaunchView(config: .poker) {
|
||||||
|
ContentView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### File: `Poker/Views/SettingsView.swift`
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
SheetContainerView(
|
||||||
|
title: "Settings",
|
||||||
|
content: {
|
||||||
|
// Game settings sections...
|
||||||
|
|
||||||
|
SheetSection(title: "SOUND", icon: "speaker.wave.2.fill") {
|
||||||
|
// Sound settings...
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
SheetSection(title: "DEBUG", icon: "ant.fill") {
|
||||||
|
BrandingDebugRows(
|
||||||
|
iconConfig: .poker,
|
||||||
|
launchConfig: .poker,
|
||||||
|
appName: "Poker"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
},
|
||||||
|
onDone: {
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Can't find `AppIconView` or other branding types
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- If using CasinoKit package: Ensure `import CasinoKit` is at the top of your file
|
||||||
|
- If copying manually: Ensure all 6 branding files are in your app target
|
||||||
|
- Check that files are added to your target's "Compile Sources" in Build Phases
|
||||||
|
|
||||||
|
### Issue: Launch screen doesn't appear
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Verify `AppLaunchView` wraps your content in the App struct
|
||||||
|
- Check that you're using the correct config name (e.g., `.poker` not `.example`)
|
||||||
|
- Ensure the config extension is defined in `BrandingConfig.swift`
|
||||||
|
|
||||||
|
### Issue: Icon looks cut off at the edges
|
||||||
|
|
||||||
|
**Explanation:** iOS applies a superellipse mask to all app icons. The branding system accounts for this, but if you see clipping:
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Don't add rounded corners yourself—iOS does this automatically
|
||||||
|
- Ensure decorative borders are inset from edges
|
||||||
|
- The system is designed to work with iOS's mask; trust the 22% corner radius preview
|
||||||
|
|
||||||
|
### Issue: "Icon saved" message appears but can't find the file
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Open the **Files** app on your device/simulator
|
||||||
|
- Navigate to: **Browse** → **On My iPhone/iPad** → **[Your App Name]**
|
||||||
|
- If folder doesn't exist, try granting Files access in Settings
|
||||||
|
|
||||||
|
**Alternative:** Use Xcode's Devices and Simulators window to download the app container (see Step 6).
|
||||||
|
|
||||||
|
### Issue: Icon doesn't update after adding to Assets
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. **Clean build folder**: Product → Clean Build Folder (Cmd+Shift+K)
|
||||||
|
2. **Delete app** from device/simulator
|
||||||
|
3. **Rebuild and reinstall**
|
||||||
|
4. If still not working, check that the 1024×1024 slot is filled in Assets.xcassets
|
||||||
|
|
||||||
|
### Issue: `BrandingDebugRows` not showing in settings
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Ensure you're running a DEBUG build (not Release)
|
||||||
|
- Wrap the section in `#if DEBUG ... #endif`
|
||||||
|
- Verify `import CasinoKit` if using the package
|
||||||
|
- Check that your settings view is inside a `NavigationStack` (required for navigation rows)
|
||||||
|
|
||||||
|
### Issue: Colors don't match between icon and launch screen
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
- Ensure both configs use identical color values
|
||||||
|
- Copy-paste color definitions to avoid typos
|
||||||
|
- Consider extracting colors to a shared constant:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
extension Color {
|
||||||
|
static let pokerPrimary = Color(red: 0.2, green: 0.05, blue: 0.1)
|
||||||
|
static let pokerSecondary = Color(red: 0.1, green: 0.02, blue: 0.05)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AppIconConfig {
|
||||||
|
static let poker = AppIconConfig(
|
||||||
|
title: "POKER",
|
||||||
|
iconSymbol: "suit.diamond.fill",
|
||||||
|
primaryColor: .pokerPrimary,
|
||||||
|
secondaryColor: .pokerSecondary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Title text is too small or too large
|
||||||
|
|
||||||
|
**Solution:** The system automatically scales titles based on length:
|
||||||
|
- **6 characters or less**: Full size (100%)
|
||||||
|
- **7 characters**: 95% scale
|
||||||
|
- **8 characters**: 85% scale
|
||||||
|
- **9 characters**: 75% scale
|
||||||
|
- **10+ characters**: 65% scale
|
||||||
|
|
||||||
|
If your title is very long, consider:
|
||||||
|
- Using an abbreviation in the icon
|
||||||
|
- Using subtitle for additional text
|
||||||
|
- Manually adjusting the `titleSize` calculation in `AppIconView.swift`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
### Color Palette Ideas
|
||||||
|
|
||||||
|
**Classic Casino:**
|
||||||
|
```swift
|
||||||
|
primaryColor: Color(red: 0.1, green: 0.2, blue: 0.35) // Deep blue
|
||||||
|
secondaryColor: Color(red: 0.05, green: 0.12, blue: 0.25)
|
||||||
|
accentColor: .yellow
|
||||||
|
```
|
||||||
|
|
||||||
|
**Luxury Gold:**
|
||||||
|
```swift
|
||||||
|
primaryColor: Color(red: 0.2, green: 0.15, blue: 0.05) // Deep gold
|
||||||
|
secondaryColor: Color(red: 0.1, green: 0.08, blue: 0.02)
|
||||||
|
accentColor: Color(red: 1.0, green: 0.85, blue: 0.3) // Bright gold
|
||||||
|
```
|
||||||
|
|
||||||
|
**Green Felt:**
|
||||||
|
```swift
|
||||||
|
primaryColor: Color(red: 0.05, green: 0.35, blue: 0.15) // Casino green
|
||||||
|
secondaryColor: Color(red: 0.03, green: 0.2, blue: 0.1)
|
||||||
|
accentColor: .yellow
|
||||||
|
```
|
||||||
|
|
||||||
|
**Elegant Red:**
|
||||||
|
```swift
|
||||||
|
primaryColor: Color(red: 0.3, green: 0.05, blue: 0.1) // Deep red
|
||||||
|
secondaryColor: Color(red: 0.15, green: 0.02, blue: 0.05)
|
||||||
|
accentColor: Color(red: 1.0, green: 0.85, blue: 0.3)
|
||||||
|
```
|
||||||
|
|
||||||
|
### SF Symbol Recommendations
|
||||||
|
|
||||||
|
**Card Suits:**
|
||||||
|
- `suit.spade.fill` - Classic, elegant
|
||||||
|
- `suit.heart.fill` - Warm, friendly
|
||||||
|
- `suit.diamond.fill` - Luxurious
|
||||||
|
- `suit.club.fill` - Traditional
|
||||||
|
|
||||||
|
**Other Casino Icons:**
|
||||||
|
- `diamond.fill` - Luxury, wealth
|
||||||
|
- `star.fill` - Premium, winning
|
||||||
|
- `crown.fill` - VIP, royalty
|
||||||
|
- `dollarsign.circle.fill` - Money, stakes
|
||||||
|
|
||||||
|
### Launch Screen Animation Timing
|
||||||
|
|
||||||
|
The default timing is:
|
||||||
|
- **Logo fade-in**: 0.6 seconds
|
||||||
|
- **Tagline fade-in**: 0.6 seconds (delayed by 0.3s)
|
||||||
|
- **Total display**: 2.0 seconds
|
||||||
|
- **Fade-out**: 0.5 seconds
|
||||||
|
|
||||||
|
To customize, modify `AppLaunchView.swift`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
.task {
|
||||||
|
try? await Task.sleep(for: .seconds(3.0)) // Change display duration
|
||||||
|
withAnimation(.easeOut(duration: 1.0)) { // Change fade-out duration
|
||||||
|
showLaunchScreen = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary Checklist
|
||||||
|
|
||||||
|
- [ ] Copy branding files from CasinoKit or ensure package dependency is set up
|
||||||
|
- [ ] Create `BrandingConfig.swift` with your game's configurations
|
||||||
|
- [ ] Add `AppLaunchView` wrapper to your App entry point
|
||||||
|
- [ ] (Optional) Add `BrandingDebugRows` to your settings view
|
||||||
|
- [ ] Build and run app in DEBUG mode
|
||||||
|
- [ ] Generate icon using Icon Generator tool
|
||||||
|
- [ ] Retrieve icon PNG from device/simulator
|
||||||
|
- [ ] Add 1024×1024 PNG to Assets.xcassets AppIcon slot
|
||||||
|
- [ ] Clean build and reinstall to verify icon appears
|
||||||
|
- [ ] Test launch screen animation on device
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Need Help?**
|
||||||
|
|
||||||
|
- Check the `BrandingPreviewView` to see your icon and launch screen side-by-side
|
||||||
|
- Use Xcode previews to iterate on colors and layout quickly
|
||||||
|
- Test on multiple device sizes to ensure text scales properly
|
||||||
|
- Remember: DEBUG tools are automatically excluded from Release builds
|
||||||
|
|
||||||
|
**Happy Branding! 🎰✨**
|
||||||
957
SETTINGS_STYLING_GUIDE.md
Normal file
957
SETTINGS_STYLING_GUIDE.md
Normal file
@ -0,0 +1,957 @@
|
|||||||
|
# Settings View Styling Guide
|
||||||
|
|
||||||
|
A comprehensive guide to achieving the polished casino-style settings interface with golden accents, proper section separation, and consistent styling.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Overview](#overview)
|
||||||
|
2. [The Complete Stack](#the-complete-stack)
|
||||||
|
3. [Step 1: Set Up Design Constants](#step-1-set-up-design-constants)
|
||||||
|
4. [Step 2: Use SheetContainerView](#step-2-use-sheetcontainerview)
|
||||||
|
5. [Step 3: Structure with SheetSection](#step-3-structure-with-sheetsection)
|
||||||
|
6. [Step 4: Use Proper Settings Components](#step-4-use-proper-settings-components)
|
||||||
|
7. [Step 5: Apply Consistent Colors](#step-5-apply-consistent-colors)
|
||||||
|
8. [Complete Example](#complete-example)
|
||||||
|
9. [Color Reference](#color-reference)
|
||||||
|
10. [Troubleshooting](#troubleshooting)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The polished casino settings interface you see in Blackjack and Baccarat is achieved through a combination of:
|
||||||
|
|
||||||
|
1. **SheetContainerView** - Dark background, proper navigation bar styling
|
||||||
|
2. **SheetSection** - Icon + title headers with card-like content containers
|
||||||
|
3. **CasinoKit components** - Pre-styled toggles, pickers, and selectable rows
|
||||||
|
4. **Color.Sheet constants** - Consistent golden accent and background colors
|
||||||
|
5. **Design constants** - Standardized spacing, corner radius, and opacity values
|
||||||
|
|
||||||
|
### Key Styling Elements
|
||||||
|
|
||||||
|
- **Golden accent color**: `Color.Sheet.accent = Color(red: 0.9, green: 0.75, blue: 0.3)`
|
||||||
|
- **Dark blue background**: `Color.Sheet.background = Color(red: 0.08, green: 0.12, blue: 0.18)`
|
||||||
|
- **Section cards**: White opacity fill with rounded corners
|
||||||
|
- **Radio buttons**: Golden checkmark circles for selected items
|
||||||
|
- **Proper spacing**: xxLarge (24pt) between sections
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Complete Stack
|
||||||
|
|
||||||
|
### Required Components from CasinoKit
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import CasinoKit
|
||||||
|
```
|
||||||
|
|
||||||
|
**Views:**
|
||||||
|
- `SheetContainerView` - Outer container with navigation and dark background
|
||||||
|
- `SheetSection` - Section container with icon/title header and card
|
||||||
|
- `SelectableRow` - Radio button rows for pickers
|
||||||
|
- `SelectionIndicator` - Golden checkmark circles
|
||||||
|
- `SettingsToggle` - Toggle switches with titles and subtitles
|
||||||
|
- `SpeedPicker`, `VolumePicker`, `BalancePicker` - Specialized pickers
|
||||||
|
- `BadgePill` - Badge for displaying values like "$10 - $1,000"
|
||||||
|
|
||||||
|
**Constants:**
|
||||||
|
- `CasinoDesign` - Spacing, corner radius, opacity, font sizes, etc.
|
||||||
|
- `Color.Sheet` - Sheet-specific colors (background, accent, etc.)
|
||||||
|
|
||||||
|
### Local Design Constants Setup
|
||||||
|
|
||||||
|
Create a `DesignConstants.swift` file in your app that imports and typealias CasinoKit constants:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
enum Design {
|
||||||
|
// Import shared constants via typealias
|
||||||
|
typealias Spacing = CasinoDesign.Spacing
|
||||||
|
typealias CornerRadius = CasinoDesign.CornerRadius
|
||||||
|
typealias LineWidth = CasinoDesign.LineWidth
|
||||||
|
typealias Shadow = CasinoDesign.Shadow
|
||||||
|
typealias Opacity = CasinoDesign.Opacity
|
||||||
|
typealias Animation = CasinoDesign.Animation
|
||||||
|
typealias BaseFontSize = CasinoDesign.BaseFontSize
|
||||||
|
typealias IconSize = CasinoDesign.IconSize
|
||||||
|
|
||||||
|
// Your game-specific constants
|
||||||
|
enum Size {
|
||||||
|
static let cardWidth: CGFloat = 90
|
||||||
|
// ... other game-specific sizes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows you to write `Design.Spacing.large` instead of `CasinoDesign.Spacing.large` throughout your app.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Set Up Design Constants
|
||||||
|
|
||||||
|
### 1.1 Create DesignConstants.swift
|
||||||
|
|
||||||
|
Create a file called `DesignConstants.swift` in your app's `Theme/` folder:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
//
|
||||||
|
// DesignConstants.swift
|
||||||
|
// YourGame
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
enum Design {
|
||||||
|
// MARK: - Shared Constants (from CasinoKit)
|
||||||
|
|
||||||
|
typealias Spacing = CasinoDesign.Spacing
|
||||||
|
typealias CornerRadius = CasinoDesign.CornerRadius
|
||||||
|
typealias LineWidth = CasinoDesign.LineWidth
|
||||||
|
typealias Shadow = CasinoDesign.Shadow
|
||||||
|
typealias Opacity = CasinoDesign.Opacity
|
||||||
|
typealias Animation = CasinoDesign.Animation
|
||||||
|
typealias Scale = CasinoDesign.Scale
|
||||||
|
typealias MinScaleFactor = CasinoDesign.MinScaleFactor
|
||||||
|
typealias BaseFontSize = CasinoDesign.BaseFontSize
|
||||||
|
typealias IconSize = CasinoDesign.IconSize
|
||||||
|
|
||||||
|
// MARK: - Game-Specific Sizes
|
||||||
|
|
||||||
|
enum Size {
|
||||||
|
// Add your game-specific sizes here
|
||||||
|
static let cardWidth: CGFloat = 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - App Colors
|
||||||
|
|
||||||
|
extension Color {
|
||||||
|
// Import CasinoKit table colors
|
||||||
|
typealias Table = CasinoTable
|
||||||
|
|
||||||
|
// Add game-specific colors here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Why This Matters
|
||||||
|
|
||||||
|
By typealiasing `CasinoDesign`, you get:
|
||||||
|
- Consistent spacing throughout your app
|
||||||
|
- Proper opacity values for layering
|
||||||
|
- Standardized corner radii
|
||||||
|
- Matching font sizes
|
||||||
|
|
||||||
|
**Don't hardcode values!** Use constants instead:
|
||||||
|
|
||||||
|
❌ **Bad:**
|
||||||
|
```swift
|
||||||
|
.padding(24)
|
||||||
|
.opacity(0.1)
|
||||||
|
.cornerRadius(12)
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Good:**
|
||||||
|
```swift
|
||||||
|
.padding(Design.Spacing.xxLarge)
|
||||||
|
.opacity(Design.Opacity.subtle)
|
||||||
|
.clipShape(.rect(cornerRadius: Design.CornerRadius.large))
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Use SheetContainerView
|
||||||
|
|
||||||
|
### 2.1 Basic Structure
|
||||||
|
|
||||||
|
`SheetContainerView` provides the foundation: dark background, navigation bar, and done button.
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
SheetContainerView(
|
||||||
|
title: "Settings",
|
||||||
|
content: {
|
||||||
|
// Your sections go here
|
||||||
|
},
|
||||||
|
onCancel: nil, // Optional cancel button
|
||||||
|
onDone: {
|
||||||
|
dismiss()
|
||||||
|
},
|
||||||
|
doneButtonText: "Done"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 What SheetContainerView Provides
|
||||||
|
|
||||||
|
**Automatic Styling:**
|
||||||
|
- Dark blue background: `Color.Sheet.background`
|
||||||
|
- NavigationStack with inline title
|
||||||
|
- Golden "Done" button: `Color.Sheet.accent`
|
||||||
|
- Optional "Cancel" button
|
||||||
|
- Toolbar with proper dark scheme
|
||||||
|
- ScrollView with proper vertical spacing
|
||||||
|
|
||||||
|
**Key Parameters:**
|
||||||
|
- `title` - The navigation bar title
|
||||||
|
- `content` - A `@ViewBuilder` closure for your sections
|
||||||
|
- `onCancel` - Optional cancel action (if nil, no cancel button)
|
||||||
|
- `onDone` - Done button action
|
||||||
|
- `doneButtonText` - Text for done button (default: "Done")
|
||||||
|
- `cancelButtonText` - Text for cancel button (default: "Cancel")
|
||||||
|
|
||||||
|
### 2.3 Section Spacing
|
||||||
|
|
||||||
|
SheetContainerView automatically adds `xxLarge` (24pt) spacing between sections:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
VStack(spacing: CasinoDesign.Spacing.xxLarge) {
|
||||||
|
content // Your sections
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates the clean separation you see between sections.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Structure with SheetSection
|
||||||
|
|
||||||
|
### 3.1 Basic SheetSection
|
||||||
|
|
||||||
|
Each logical group of settings should be in a `SheetSection`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
SheetSection(title: "DISPLAY", icon: "eye") {
|
||||||
|
// Settings content here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 What SheetSection Provides
|
||||||
|
|
||||||
|
**Header:**
|
||||||
|
- Icon in golden color with opacity
|
||||||
|
- Title in uppercase, bold, rounded font
|
||||||
|
- Subtle spacing and padding
|
||||||
|
|
||||||
|
**Content Card:**
|
||||||
|
- White opacity background (`Color.Sheet.sectionFill`)
|
||||||
|
- Rounded corners (`CornerRadius.large`)
|
||||||
|
- Proper padding
|
||||||
|
- Horizontal margins
|
||||||
|
|
||||||
|
**Visual Structure:**
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────┐
|
||||||
|
│ 👁 DISPLAY │ ← Header (icon + title)
|
||||||
|
│ ┌─────────────────────────────────┐ │
|
||||||
|
│ │ │ │ ← Content card
|
||||||
|
│ │ [Settings content here] │ │ (white opacity fill)
|
||||||
|
│ │ │ │
|
||||||
|
│ └─────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Section Best Practices
|
||||||
|
|
||||||
|
**Use semantic grouping:**
|
||||||
|
- GAME STYLE - Game variants and modes
|
||||||
|
- RULES - Rule customization
|
||||||
|
- TABLE LIMITS - Betting limits
|
||||||
|
- DECK SETTINGS - Shoe configuration
|
||||||
|
- STARTING BALANCE - Balance picker
|
||||||
|
- DISPLAY - Visual settings
|
||||||
|
- SOUND - Audio settings
|
||||||
|
- DATA - Reset and export
|
||||||
|
|
||||||
|
**Choose appropriate icons:**
|
||||||
|
```swift
|
||||||
|
SheetSection(title: "GAME STYLE", icon: "suit.club.fill")
|
||||||
|
SheetSection(title: "RULES", icon: "list.bullet.clipboard")
|
||||||
|
SheetSection(title: "TABLE LIMITS", icon: "banknote")
|
||||||
|
SheetSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait")
|
||||||
|
SheetSection(title: "STARTING BALANCE", icon: "dollarsign.circle")
|
||||||
|
SheetSection(title: "DISPLAY", icon: "eye")
|
||||||
|
SheetSection(title: "SOUND", icon: "speaker.wave.2.fill")
|
||||||
|
SheetSection(title: "DATA", icon: "externaldrive")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Use Proper Settings Components
|
||||||
|
|
||||||
|
### 4.1 SelectableRow (Radio Button Rows)
|
||||||
|
|
||||||
|
Use `SelectableRow` for picker options with radio buttons:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
SheetSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait") {
|
||||||
|
VStack(spacing: Design.Spacing.small) {
|
||||||
|
SelectableRow(
|
||||||
|
title: "1 Deck",
|
||||||
|
subtitle: "Single deck, higher variance",
|
||||||
|
isSelected: deckCount == 1,
|
||||||
|
accentColor: Color.Sheet.accent,
|
||||||
|
action: { deckCount = 1 }
|
||||||
|
)
|
||||||
|
|
||||||
|
SelectableRow(
|
||||||
|
title: "6 Decks",
|
||||||
|
subtitle: "Standard casino shoe",
|
||||||
|
isSelected: deckCount == 6,
|
||||||
|
accentColor: Color.Sheet.accent,
|
||||||
|
action: { deckCount = 6 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**What SelectableRow Provides:**
|
||||||
|
- Title in white, large font
|
||||||
|
- Subtitle in white with medium opacity
|
||||||
|
- Optional badge (e.g., "$10 - $1,000")
|
||||||
|
- Golden checkmark circle when selected
|
||||||
|
- Outlined circle when not selected
|
||||||
|
- Golden border when selected
|
||||||
|
- Subtle golden background fill when selected
|
||||||
|
- Proper padding and rounded corners
|
||||||
|
|
||||||
|
**With Badge:**
|
||||||
|
```swift
|
||||||
|
SelectableRow(
|
||||||
|
title: "Low Stakes",
|
||||||
|
subtitle: "Standard mini table",
|
||||||
|
isSelected: true,
|
||||||
|
accentColor: Color.Sheet.accent,
|
||||||
|
badge: {
|
||||||
|
BadgePill(text: "$10 - $1,000", isSelected: true)
|
||||||
|
},
|
||||||
|
action: { }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 SettingsToggle (Toggle Switches)
|
||||||
|
|
||||||
|
Use `SettingsToggle` for on/off options:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
SheetSection(title: "DISPLAY", icon: "eye") {
|
||||||
|
VStack(spacing: Design.Spacing.small) {
|
||||||
|
SettingsToggle(
|
||||||
|
title: "Show Animations",
|
||||||
|
subtitle: "Card dealing animations",
|
||||||
|
isOn: $showAnimations,
|
||||||
|
accentColor: Color.Sheet.accent
|
||||||
|
)
|
||||||
|
|
||||||
|
Divider().background(Color.white.opacity(Design.Opacity.hint))
|
||||||
|
|
||||||
|
SettingsToggle(
|
||||||
|
title: "Show Hints",
|
||||||
|
subtitle: "Basic strategy suggestions",
|
||||||
|
isOn: $showHints,
|
||||||
|
accentColor: Color.Sheet.accent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**What SettingsToggle Provides:**
|
||||||
|
- Title in large font, white
|
||||||
|
- Subtitle in body font, white with medium opacity
|
||||||
|
- Toggle switch with golden accent color
|
||||||
|
- Proper vertical alignment
|
||||||
|
- Minimum touch target height (44pt)
|
||||||
|
|
||||||
|
### 4.3 Specialized Pickers
|
||||||
|
|
||||||
|
**SpeedPicker:**
|
||||||
|
```swift
|
||||||
|
SpeedPicker(speed: $dealingSpeed, accentColor: Color.Sheet.accent)
|
||||||
|
```
|
||||||
|
|
||||||
|
**VolumePicker:**
|
||||||
|
```swift
|
||||||
|
VolumePicker(volume: $soundVolume, accentColor: Color.Sheet.accent)
|
||||||
|
```
|
||||||
|
|
||||||
|
**BalancePicker:**
|
||||||
|
```swift
|
||||||
|
BalancePicker(balance: $startingBalance, accentColor: Color.Sheet.accent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 Dividers Between Items
|
||||||
|
|
||||||
|
Use dividers to separate items within a section:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
VStack(spacing: Design.Spacing.small) {
|
||||||
|
SettingsToggle(title: "Option 1", isOn: $option1, accentColor: accent)
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.background(Color.white.opacity(Design.Opacity.hint))
|
||||||
|
|
||||||
|
SettingsToggle(title: "Option 2", isOn: $option2, accentColor: accent)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Divider opacity levels:**
|
||||||
|
- `Design.Opacity.hint` (0.2) - Very subtle, recommended
|
||||||
|
- `Design.Opacity.subtle` (0.1) - Almost invisible
|
||||||
|
- `Design.Opacity.light` (0.3) - More prominent
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Apply Consistent Colors
|
||||||
|
|
||||||
|
### 5.1 Use Color.Sheet Constants
|
||||||
|
|
||||||
|
**Always use `Color.Sheet.accent` for highlights:**
|
||||||
|
|
||||||
|
```swift
|
||||||
|
private let accent = Color.Sheet.accent
|
||||||
|
|
||||||
|
// Then use it everywhere:
|
||||||
|
SelectableRow(..., accentColor: accent)
|
||||||
|
SettingsToggle(..., accentColor: accent)
|
||||||
|
SpeedPicker(..., accentColor: accent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Color Constants Available
|
||||||
|
|
||||||
|
**From CasinoKit:**
|
||||||
|
```swift
|
||||||
|
Color.Sheet.background // Dark blue: Color(red: 0.08, green: 0.12, blue: 0.18)
|
||||||
|
Color.Sheet.accent // Gold: Color(red: 0.9, green: 0.75, blue: 0.3)
|
||||||
|
Color.Sheet.sectionFill // White.opacity(0.1)
|
||||||
|
Color.Sheet.cardBackground // White.opacity(0.05)
|
||||||
|
Color.Sheet.secondaryText // White.opacity(0.6)
|
||||||
|
Color.Sheet.cancelText // White.opacity(0.7)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 Text Colors
|
||||||
|
|
||||||
|
**Primary text (titles):**
|
||||||
|
```swift
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Secondary text (subtitles, descriptions):**
|
||||||
|
```swift
|
||||||
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
|
// or
|
||||||
|
.foregroundStyle(Color.Sheet.secondaryText)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Accent highlights:**
|
||||||
|
```swift
|
||||||
|
.foregroundStyle(Color.Sheet.accent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Background Colors
|
||||||
|
|
||||||
|
**Section card fill:**
|
||||||
|
```swift
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.large)
|
||||||
|
.fill(Color.Sheet.sectionFill)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Selection highlight:**
|
||||||
|
```swift
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
|
.fill(isSelected ? accent.opacity(Design.Opacity.subtle) : .clear)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
Here's a complete settings view demonstrating all the concepts:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
//
|
||||||
|
// SettingsView.swift
|
||||||
|
// YourGame
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit
|
||||||
|
|
||||||
|
struct SettingsView: View {
|
||||||
|
@Bindable var settings: GameSettings
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
@State private var showClearDataAlert = false
|
||||||
|
|
||||||
|
/// Accent color for settings components
|
||||||
|
private let accent = Color.Sheet.accent
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
SheetContainerView(
|
||||||
|
title: "Settings",
|
||||||
|
content: {
|
||||||
|
// 1. Table Limits
|
||||||
|
SheetSection(title: "TABLE LIMITS", icon: "banknote") {
|
||||||
|
TableLimitsPicker(selection: $settings.tableLimits)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Deck Settings
|
||||||
|
SheetSection(title: "DECK SETTINGS", icon: "rectangle.portrait.on.rectangle.portrait") {
|
||||||
|
DeckCountPicker(selection: $settings.deckCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Starting Balance
|
||||||
|
SheetSection(title: "STARTING BALANCE", icon: "dollarsign.circle") {
|
||||||
|
BalancePicker(balance: $settings.startingBalance, accentColor: accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Display
|
||||||
|
SheetSection(title: "DISPLAY", icon: "eye") {
|
||||||
|
VStack(spacing: Design.Spacing.small) {
|
||||||
|
SettingsToggle(
|
||||||
|
title: "Show Animations",
|
||||||
|
subtitle: "Card dealing animations",
|
||||||
|
isOn: $settings.showAnimations,
|
||||||
|
accentColor: accent
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.showAnimations {
|
||||||
|
Divider()
|
||||||
|
.background(Color.white.opacity(Design.Opacity.hint))
|
||||||
|
|
||||||
|
SpeedPicker(speed: $settings.dealingSpeed, accentColor: accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.background(Color.white.opacity(Design.Opacity.hint))
|
||||||
|
|
||||||
|
SettingsToggle(
|
||||||
|
title: "Show Hints",
|
||||||
|
subtitle: "Strategy suggestions",
|
||||||
|
isOn: $settings.showHints,
|
||||||
|
accentColor: accent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Sound
|
||||||
|
SheetSection(title: "SOUND", icon: "speaker.wave.2.fill") {
|
||||||
|
VStack(spacing: Design.Spacing.small) {
|
||||||
|
SettingsToggle(
|
||||||
|
title: "Sound Effects",
|
||||||
|
subtitle: "Play game sounds",
|
||||||
|
isOn: $settings.soundEnabled,
|
||||||
|
accentColor: accent
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.soundEnabled {
|
||||||
|
Divider()
|
||||||
|
.background(Color.white.opacity(Design.Opacity.hint))
|
||||||
|
|
||||||
|
VolumePicker(volume: $settings.soundVolume, accentColor: accent)
|
||||||
|
}
|
||||||
|
|
||||||
|
Divider()
|
||||||
|
.background(Color.white.opacity(Design.Opacity.hint))
|
||||||
|
|
||||||
|
SettingsToggle(
|
||||||
|
title: "Haptics",
|
||||||
|
subtitle: "Vibration feedback",
|
||||||
|
isOn: $settings.hapticsEnabled,
|
||||||
|
accentColor: accent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Data
|
||||||
|
SheetSection(title: "DATA", icon: "externaldrive") {
|
||||||
|
Button {
|
||||||
|
showClearDataAlert = true
|
||||||
|
} label: {
|
||||||
|
HStack {
|
||||||
|
VStack(alignment: .leading, spacing: Design.Spacing.xxSmall) {
|
||||||
|
Text("Clear All Data")
|
||||||
|
.font(.system(size: Design.BaseFontSize.large, weight: .semibold))
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
|
||||||
|
Text("Reset progress and statistics")
|
||||||
|
.font(.system(size: Design.BaseFontSize.body))
|
||||||
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Image(systemName: "trash")
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
|
.fill(.clear)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
|
.strokeBorder(Color.white.opacity(Design.Opacity.subtle), lineWidth: Design.LineWidth.thin)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version info
|
||||||
|
Text("YourGame v1.0 (1)")
|
||||||
|
.font(.system(size: Design.BaseFontSize.body))
|
||||||
|
.foregroundStyle(.white.opacity(Design.Opacity.light))
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding(.top, Design.Spacing.large)
|
||||||
|
.padding(.bottom, Design.Spacing.medium)
|
||||||
|
},
|
||||||
|
onCancel: nil,
|
||||||
|
onDone: {
|
||||||
|
settings.save()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.alert("Clear All Data?", isPresented: $showClearDataAlert) {
|
||||||
|
Button("Cancel", role: .cancel) { }
|
||||||
|
Button("Clear", role: .destructive) {
|
||||||
|
// Clear data logic
|
||||||
|
}
|
||||||
|
} message: {
|
||||||
|
Text("This will delete all saved progress and statistics. This cannot be undone.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Custom Pickers
|
||||||
|
|
||||||
|
struct TableLimitsPicker: View {
|
||||||
|
@Binding var selection: TableLimits
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: Design.Spacing.small) {
|
||||||
|
ForEach(TableLimits.allCases) { limit in
|
||||||
|
SelectableRow(
|
||||||
|
title: limit.displayName,
|
||||||
|
subtitle: limit.description,
|
||||||
|
isSelected: selection == limit,
|
||||||
|
accentColor: Color.Sheet.accent,
|
||||||
|
badge: {
|
||||||
|
BadgePill(
|
||||||
|
text: "$\(limit.minBet) - $\(limit.maxBet)",
|
||||||
|
isSelected: selection == limit
|
||||||
|
)
|
||||||
|
},
|
||||||
|
action: { selection = limit }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DeckCountPicker: View {
|
||||||
|
@Binding var selection: Int
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: Design.Spacing.small) {
|
||||||
|
ForEach([1, 2, 4, 6, 8], id: \.self) { count in
|
||||||
|
SelectableRow(
|
||||||
|
title: "\(count) Deck\(count == 1 ? "" : "s")",
|
||||||
|
subtitle: subtitleFor(count: count),
|
||||||
|
isSelected: selection == count,
|
||||||
|
accentColor: Color.Sheet.accent,
|
||||||
|
action: { selection = count }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func subtitleFor(count: Int) -> String {
|
||||||
|
switch count {
|
||||||
|
case 1: return "Single deck, higher variance"
|
||||||
|
case 2: return "Lower house edge"
|
||||||
|
case 4: return "Common shoe game"
|
||||||
|
case 6: return "Standard casino"
|
||||||
|
case 8: return "Maximum penetration"
|
||||||
|
default: return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Reference
|
||||||
|
|
||||||
|
### Complete Color.Sheet Definitions
|
||||||
|
|
||||||
|
From `CasinoKit/Sources/CasinoKit/Theme/CasinoDesign.swift`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public extension Color {
|
||||||
|
enum Sheet {
|
||||||
|
/// Dark background for sheets and popups.
|
||||||
|
public static let background = Color(red: 0.08, green: 0.12, blue: 0.18)
|
||||||
|
|
||||||
|
/// Subtle fill for section cards.
|
||||||
|
public static let sectionFill = Color.white.opacity(CasinoDesign.Opacity.subtle)
|
||||||
|
|
||||||
|
/// Card background in settings/sheets.
|
||||||
|
public static let cardBackground = Color.white.opacity(CasinoDesign.Opacity.verySubtle)
|
||||||
|
|
||||||
|
/// Accent color for buttons and highlights (gold).
|
||||||
|
public static let accent = Color(red: 0.9, green: 0.75, blue: 0.3)
|
||||||
|
|
||||||
|
/// Secondary text color.
|
||||||
|
public static let secondaryText = Color.white.opacity(CasinoDesign.Opacity.accent)
|
||||||
|
|
||||||
|
/// Cancel button color.
|
||||||
|
public static let cancelText = Color.white.opacity(CasinoDesign.Opacity.strong)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opacity Reference
|
||||||
|
|
||||||
|
```swift
|
||||||
|
public enum Opacity {
|
||||||
|
public static let verySubtle: Double = 0.05 // Barely visible backgrounds
|
||||||
|
public static let subtle: Double = 0.1 // Section fills
|
||||||
|
public static let selection: Double = 0.15 // Selection highlights
|
||||||
|
public static let hint: Double = 0.2 // Dividers, borders
|
||||||
|
public static let quarter: Double = 0.25 // Quarter opacity
|
||||||
|
public static let light: Double = 0.3 // Light text, borders
|
||||||
|
public static let overlay: Double = 0.4 // Modal overlays
|
||||||
|
public static let medium: Double = 0.5 // Subtitles, secondary info
|
||||||
|
public static let secondary: Double = 0.5 // Same as medium
|
||||||
|
public static let disabled: Double = 0.5 // Disabled controls
|
||||||
|
public static let accent: Double = 0.6 // Accent text
|
||||||
|
public static let strong: Double = 0.7 // Cancel buttons
|
||||||
|
public static let heavy: Double = 0.8 // Icons, strong text
|
||||||
|
public static let nearOpaque: Double = 0.85 // Almost solid
|
||||||
|
public static let almostFull: Double = 0.9 // Very solid
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Color Patterns
|
||||||
|
|
||||||
|
**Selected row:**
|
||||||
|
```swift
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
|
.fill(accent.opacity(Design.Opacity.subtle)) // 0.1
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
|
.strokeBorder(accent.opacity(Design.Opacity.medium), lineWidth: Design.LineWidth.thin) // 0.5
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Unselected row:**
|
||||||
|
```swift
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
|
.fill(.clear)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: Design.CornerRadius.medium)
|
||||||
|
.strokeBorder(Color.white.opacity(Design.Opacity.subtle), lineWidth: Design.LineWidth.thin) // 0.1
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: Colors don't look golden
|
||||||
|
|
||||||
|
**Problem:** Accent colors appear white or wrong color.
|
||||||
|
|
||||||
|
**Solution:** Ensure you're passing `Color.Sheet.accent` to the `accentColor` parameter:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// ❌ Wrong
|
||||||
|
SelectableRow(..., accentColor: .yellow)
|
||||||
|
|
||||||
|
// ✅ Correct
|
||||||
|
private let accent = Color.Sheet.accent
|
||||||
|
SelectableRow(..., accentColor: accent)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Sections not separated properly
|
||||||
|
|
||||||
|
**Problem:** Sections appear cramped together.
|
||||||
|
|
||||||
|
**Solution:** Ensure you're using `SheetContainerView` which automatically adds `xxLarge` (24pt) spacing:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
SheetContainerView(title: "Settings", content: {
|
||||||
|
SheetSection(title: "SECTION 1", icon: "icon1") { ... }
|
||||||
|
SheetSection(title: "SECTION 2", icon: "icon2") { ... }
|
||||||
|
// Auto-spaced by xxLarge (24pt)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Section headers not styled properly
|
||||||
|
|
||||||
|
**Problem:** Section headers missing icon, wrong font, or poor spacing.
|
||||||
|
|
||||||
|
**Solution:** Use `SheetSection`, not custom VStack:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// ❌ Wrong
|
||||||
|
VStack {
|
||||||
|
Text("DISPLAY")
|
||||||
|
// content
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct
|
||||||
|
SheetSection(title: "DISPLAY", icon: "eye") {
|
||||||
|
// content
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Radio buttons not golden
|
||||||
|
|
||||||
|
**Problem:** Selection indicators are white or blue circles.
|
||||||
|
|
||||||
|
**Solution:** Pass `accentColor: Color.Sheet.accent` to `SelectableRow`:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
SelectableRow(
|
||||||
|
title: "Option",
|
||||||
|
subtitle: "Description",
|
||||||
|
isSelected: true,
|
||||||
|
accentColor: Color.Sheet.accent, // ← Must include this
|
||||||
|
action: { }
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Background is wrong color
|
||||||
|
|
||||||
|
**Problem:** Background is white, gray, or wrong shade of blue.
|
||||||
|
|
||||||
|
**Solution:** Use `SheetContainerView` which sets `Color.Sheet.background` automatically:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
// ❌ Wrong
|
||||||
|
var body: some View {
|
||||||
|
NavigationStack {
|
||||||
|
ScrollView {
|
||||||
|
// sections
|
||||||
|
}
|
||||||
|
.background(Color.gray) // Wrong!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct
|
||||||
|
var body: some View {
|
||||||
|
SheetContainerView(title: "Settings", content: {
|
||||||
|
// sections - background automatically applied
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Dividers are too prominent or invisible
|
||||||
|
|
||||||
|
**Problem:** Dividers within sections are too thick or can't be seen.
|
||||||
|
|
||||||
|
**Solution:** Use white with `Design.Opacity.hint` (0.2):
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Divider()
|
||||||
|
.background(Color.white.opacity(Design.Opacity.hint))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Text not readable
|
||||||
|
|
||||||
|
**Problem:** Text appears dark gray or hard to read.
|
||||||
|
|
||||||
|
**Solution:** Always use `.foregroundStyle(.white)` for primary text:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
Text("Title")
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
|
||||||
|
Text("Subtitle")
|
||||||
|
.foregroundStyle(.white.opacity(Design.Opacity.medium))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Spacing inconsistent
|
||||||
|
|
||||||
|
**Problem:** Some spacing is 10pt, some 12pt, some 15pt.
|
||||||
|
|
||||||
|
**Solution:** Always use `Design.Spacing` constants:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
VStack(spacing: Design.Spacing.small) { // 8pt
|
||||||
|
// items
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding(Design.Spacing.medium) // 12pt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Missing import
|
||||||
|
|
||||||
|
**Problem:** `SheetContainerView`, `SelectableRow`, or other components not found.
|
||||||
|
|
||||||
|
**Solution:** Add `import CasinoKit` to the top of your file:
|
||||||
|
|
||||||
|
```swift
|
||||||
|
import SwiftUI
|
||||||
|
import CasinoKit // ← Required for CasinoKit components
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Design constants not available
|
||||||
|
|
||||||
|
**Problem:** `Design.Spacing`, `Design.Opacity`, etc. not found.
|
||||||
|
|
||||||
|
**Solution:** Create `DesignConstants.swift` with typealias to CasinoKit constants (see Step 1).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Checklist
|
||||||
|
|
||||||
|
Use this checklist to ensure your settings view has the proper styling:
|
||||||
|
|
||||||
|
- [ ] Import CasinoKit at top of file
|
||||||
|
- [ ] Create local `DesignConstants.swift` with typealiases
|
||||||
|
- [ ] Wrap entire settings view in `SheetContainerView`
|
||||||
|
- [ ] Each logical group in a `SheetSection` with icon and title
|
||||||
|
- [ ] Use `SelectableRow` for radio button options
|
||||||
|
- [ ] Use `SettingsToggle` for on/off switches
|
||||||
|
- [ ] Pass `accentColor: Color.Sheet.accent` to all components
|
||||||
|
- [ ] Use `Design.Spacing.small` between items in a section
|
||||||
|
- [ ] Use `Divider().background(Color.white.opacity(Design.Opacity.hint))` between items
|
||||||
|
- [ ] Use `.foregroundStyle(.white)` for primary text
|
||||||
|
- [ ] Use `.foregroundStyle(.white.opacity(Design.Opacity.medium))` for secondary text
|
||||||
|
- [ ] No hardcoded spacing, opacity, or corner radius values
|
||||||
|
- [ ] Version info at bottom with subtle opacity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The polished casino settings interface is achieved through:
|
||||||
|
|
||||||
|
1. **SheetContainerView** for the dark background and navigation
|
||||||
|
2. **SheetSection** for icon headers and card-like content containers
|
||||||
|
3. **SelectableRow** for radio button pickers with golden checkmarks
|
||||||
|
4. **SettingsToggle** for toggle switches
|
||||||
|
5. **Color.Sheet.accent** for consistent golden highlights
|
||||||
|
6. **Design constants** for spacing, opacity, and font sizes
|
||||||
|
7. **Proper text colors** (white for primary, white with opacity for secondary)
|
||||||
|
8. **Subtle dividers** with 0.2 opacity between items
|
||||||
|
|
||||||
|
Follow this guide and your settings will match the professional casino aesthetic! 🎰✨
|
||||||
34
WORKSPACE.md
Normal file
34
WORKSPACE.md
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# CasinoGames Workspace
|
||||||
|
|
||||||
|
This is a multi-project workspace for casino card games.
|
||||||
|
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
|
||||||
|
| Project | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `Blackjack/` | Blackjack card game app |
|
||||||
|
| `Baccarat/` | Baccarat card game app |
|
||||||
|
| `CasinoKit/` | Shared framework (protocols, views, audio, utilities) |
|
||||||
|
|
||||||
|
|
||||||
|
## Shared Code
|
||||||
|
|
||||||
|
`CasinoKit` contains reusable components adopted by all games:
|
||||||
|
|
||||||
|
- Card models and rendering
|
||||||
|
- Chip and betting UI components
|
||||||
|
- Audio playback service
|
||||||
|
- Design constants patterns
|
||||||
|
- Shared view components (settings, statistics, walkthroughs)
|
||||||
|
|
||||||
|
When adding functionality that could be shared across games, consider adding it to `CasinoKit` first.
|
||||||
|
|
||||||
|
|
||||||
|
## Per-Project Documentation
|
||||||
|
|
||||||
|
Each project has its own `README.md` with game-specific details:
|
||||||
|
|
||||||
|
- `Blackjack/README.md` — Blackjack rules, settings, and architecture
|
||||||
|
- `Baccarat/README.md` — Baccarat rules, settings, and architecture
|
||||||
|
- `CasinoKit/README.md` — Shared framework API and components
|
||||||
Loading…
Reference in New Issue
Block a user